Sandwich
2 years ago
committed by
GitHub
29 changed files with 1113 additions and 206 deletions
@ -0,0 +1,211 @@ |
|||
require('dotenv').config() |
|||
|
|||
const fs = require('fs'), |
|||
yaml= require('js-yaml'), |
|||
crypto = require('crypto'), |
|||
nostrTools = require('nostr-tools'), |
|||
{ RelayPool } = require('nostr'), |
|||
{ validateEvent, verifySignature, signEvent, getEventHash, getPublicKey } = nostrTools, |
|||
|
|||
uniques = new Set() |
|||
|
|||
let relays = yaml.load(fs.readFileSync('./relays.yaml', 'utf8')).relays, |
|||
canonicals = new Object(), |
|||
missing = new Array(), |
|||
hashes = new Object(), |
|||
discovered = true, |
|||
totalSent = 0, |
|||
oks = 0, |
|||
notices = 0 |
|||
|
|||
const pool = RelayPool(relays, {reconnect: false}) |
|||
|
|||
pool |
|||
.on('ok', (Relay) => { |
|||
oks++ |
|||
// console.log('OK', Relay.url)
|
|||
}) |
|||
.on('notice', (Relay, notice) => { |
|||
notices++ |
|||
// console.log('NOTICE', Relay.url, notice)
|
|||
}) |
|||
|
|||
async function run(){ |
|||
// setup()
|
|||
// deletions()
|
|||
// await process.exit()
|
|||
await discover() |
|||
// process.exit()
|
|||
// console.log(`wtf`, relays.length)
|
|||
// console.log(`hashes`, Object.keys(hashes).length)
|
|||
await sieve() |
|||
await broadcast() |
|||
process.exit() |
|||
|
|||
} |
|||
|
|||
function setup(){ |
|||
const event = { |
|||
"id": "a2640e8a6640c595942ccf290eae404ac58569b59af5c8c8e3334d9cf809fff6", |
|||
"pubkey": "b3b0d247f66bf40c4c9f4ce721abfe1fd3b7529fbc1ea5e64d5f0f8df3a4b6e6", |
|||
"created_at": 1673275222, |
|||
"kind": 1, |
|||
"tags": [], |
|||
"content": "<3 to all the relays", |
|||
"sig": "e536be52a04f95c54e5cc82caafb9b25c8d47e00182c0eac0b6b678b482710288cc7fd85c62b0f97f5ed33dfbd7e15555c9bfeac059794767e414666d807f9cf" |
|||
} |
|||
|
|||
pool.send(['EVENT', event]) |
|||
} |
|||
|
|||
async function discover(){ |
|||
|
|||
|
|||
console.log('relays', relays.length) |
|||
|
|||
return new Promise(resolve => { |
|||
const subid = crypto.randomBytes(40).toString('hex') |
|||
|
|||
pool |
|||
.on('open', Relay => { |
|||
console.log('open', Relay.url) |
|||
Relay.subscribe(subid, {limit: relays.length, kinds:[1], "#t": ['canonical'], authors:[ getPublicKey(process.env.PRIVATE_KEY) ] }, ) |
|||
relays.forEach( relay => { |
|||
hashes[hash(relay)] = relay |
|||
// Relay.subscribe(`subid_${relay}`, {limit: 1, kinds:[1], authors:[ getPublicKey(process.env.PRIVATE_KEY) ] }, )
|
|||
}) |
|||
|
|||
}) |
|||
.on('event', (Relay, _subid, event) => { |
|||
if(!discovered){ |
|||
// console.log('published event found', event.id)
|
|||
} |
|||
if(_subid.includes(subid) && discovered) { |
|||
// console.log('event', event.content, event.id)
|
|||
|
|||
if(uniques.has(event.id)) |
|||
return |
|||
|
|||
const relayHash = event.tags.map( tag => tag[0]=='h' ? tag[1] : false )[0] |
|||
|
|||
if(!relayHash) |
|||
return |
|||
|
|||
// console.log('relay hash', Relay.url, relayHash)
|
|||
|
|||
const relay = hashes?.[relayHash] |
|||
|
|||
uniques.add(event.id) |
|||
canonicals[relay] = event |
|||
} |
|||
}) |
|||
|
|||
setTimeout( () => { |
|||
// pool.close()
|
|||
discovered = false |
|||
resolve(relays) |
|||
}, 10*1000 ) |
|||
}) |
|||
} |
|||
|
|||
async function sieve(){ |
|||
console.log('filtering relays', relays.length) |
|||
checkMissing() |
|||
console.log('missing', missing.length) |
|||
return |
|||
} |
|||
|
|||
function checkMissing(){ |
|||
missing = new Array() |
|||
relays.forEach( relay => { |
|||
// console.log('check missing', relay, (canonicals?.[relay] instanceof Object) )
|
|||
if( !(canonicals?.[relay] instanceof Object) ) |
|||
missing.push(relay) |
|||
}) |
|||
} |
|||
|
|||
async function broadcast(){ |
|||
setInterval( ()=> { |
|||
console.log('status', '\ntotal sent:', totalSent, '\noks:', oks, '\nnotices:', notices, '\n\n') |
|||
}, 60000) |
|||
|
|||
for(let i=0;i<missing.length;i++){ |
|||
const relay = missing[i] |
|||
const event = { |
|||
created_at: Math.floor(Date.now()/1000), |
|||
content: `<3 ${relay}, canonical note for https://nostr.watch/relay/${relay.replace('wss://', '')}`, |
|||
kind: 1, |
|||
tags: [ |
|||
['h', hash(relay)], |
|||
['t', 'canonical'], |
|||
['e', process.env.CANONICAL_NOTE, 'wss://nostr.sandwich.farm'] |
|||
] |
|||
} |
|||
const signedEvent = await sign(event, relay) |
|||
|
|||
if(!signedEvent) |
|||
return |
|||
|
|||
// console.log("sending to pool", signedEvent)
|
|||
pool.send(['EVENT', signedEvent]) |
|||
totalSent++ |
|||
console.log('total sent, backlog', totalSent) |
|||
await delay(10*1000) |
|||
} |
|||
console.log('finished.') |
|||
} |
|||
|
|||
async function sign(event, relay){ |
|||
// console.log('event to sign', event)
|
|||
event.pubkey = getPublicKey(process.env.PRIVATE_KEY) |
|||
event.id = getEventHash(event) |
|||
event.sig = await signEvent(event, process.env.PRIVATE_KEY) |
|||
|
|||
let ok = validateEvent(event) |
|||
let veryOk = await verifySignature(event) |
|||
|
|||
// if(relay)
|
|||
// console.log('sign valid', relay, ':', ok, veryOk)
|
|||
// else
|
|||
// console.log('sign valid', ':', ok, veryOk)
|
|||
|
|||
if( ok && veryOk ) |
|||
return event |
|||
else |
|||
console.error('event is invalid', event) |
|||
} |
|||
|
|||
async function deletions(){ |
|||
const tags = [ |
|||
["e", "8e68215676f0bfcc386e3cc0d9e975e7fab1aed91d781c4ec3aac5f4c2c11e24"], |
|||
["e", "00834b0779cd0a87b6eeb5d25e22e887b007a30239a1e75cb567324a687e000b"], |
|||
["e", "783f57bfbeb3c4e1cd13cc493b021cb0c353ab98c1d02f5378dfdfb0afcc77fd"] |
|||
] |
|||
|
|||
const event = { |
|||
"kind": 5, |
|||
created_at: Math.floor(Date.now()/1000), |
|||
"tags": tags, |
|||
"content": "delete dev posts" |
|||
} |
|||
const signedEvent = await sign(event) |
|||
|
|||
if(!signedEvent) |
|||
return |
|||
|
|||
console.log("sending to pool", signedEvent) |
|||
|
|||
pool.send(['EVENT', signedEvent]) |
|||
|
|||
return |
|||
} |
|||
|
|||
async function delay(ms) { |
|||
return new Promise( resolve => setTimeout(resolve, ms) ) |
|||
} |
|||
|
|||
function hash(relay){ |
|||
return crypto.createHash('md5').update(relay).digest('hex'); |
|||
} |
|||
|
|||
run() |
@ -0,0 +1,158 @@ |
|||
<template> |
|||
|
|||
<span |
|||
v-if="this.store.tasks.getActiveSlug === taskSlug" |
|||
class="text-white lg:text-sm mr-2 ml-2 mt-1.5 text-xs"> |
|||
<span>Getting canonicals...</span> |
|||
</span> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
|
|||
</style> |
|||
|
|||
<script> |
|||
import crypto from 'crypto' |
|||
import { RelayPool } from 'nostr' |
|||
|
|||
import { defineComponent, toRefs } from 'vue' |
|||
|
|||
import { setupStore } from '@/store' |
|||
|
|||
import SharedMethods from '@/shared/relays-lib.js' |
|||
import SharedComputed from '@/shared/computed.js' |
|||
|
|||
import { relays } from '../../../../relays.yaml' |
|||
|
|||
const localMethods = { |
|||
queueJob: function(id, fn, unique){ |
|||
this.store.tasks.addJob({ |
|||
id: id, |
|||
handler: fn, |
|||
unique: unique |
|||
}) |
|||
}, |
|||
invalidate(force){ |
|||
if( (!this.isExpired(this.taskSlug) && !force) ) |
|||
return |
|||
|
|||
const subid = crypto.randomBytes(40).toString('hex') |
|||
|
|||
this.queueJob( |
|||
this.taskSlug, |
|||
async () => { |
|||
const instance = new RelayPool(['wss://nostr.sandwich.farm', 'wss://relay.nostr.ch']) |
|||
|
|||
instance |
|||
.on('open', r => { |
|||
r.subscribe(subid, { |
|||
limit: 1000, |
|||
kinds: [1], |
|||
"#t": ['canonical'], |
|||
authors:[ 'b3b0d247f66bf40c4c9f4ce721abfe1fd3b7529fbc1ea5e64d5f0f8df3a4b6e6' ] |
|||
}) |
|||
}) |
|||
.on('event', (relay, _subid, event) => { |
|||
if(_subid.includes(subid)){ |
|||
console.log('canonical event', event.id) |
|||
const hash = event.tags.filter( tag => tag[0] === 'h')[0][1] |
|||
this.hashes[hash] = event.id |
|||
} |
|||
}) |
|||
|
|||
await this.delay(5000) |
|||
|
|||
instance.unsubscribe() |
|||
instance.close() |
|||
|
|||
relays.forEach( relay => { |
|||
const hash = this.hash(relay) |
|||
if( typeof this.hashes[hash] === "undefined" ) |
|||
return |
|||
this.canonicals[relay] = this.hashes[hash] //event.id |
|||
}) |
|||
|
|||
console.log('hashes found', Object.keys(this.hashes).length) |
|||
console.log('canonicals found', Object.keys(this.canonicals).length, this.canonicals) |
|||
console.log('from store', this.store.relays.getCanonicals) |
|||
|
|||
this.store.relays.setCanonicals(this.canonicals) |
|||
|
|||
this.store.tasks.completeJob() |
|||
}, |
|||
true |
|||
) |
|||
}, |
|||
timeUntilRefresh(){ |
|||
return this.timeSince(Date.now()-(this.store.tasks.getLastUpdate(this.taskSlug)+this.store.prefs.duration-Date.now())) |
|||
}, |
|||
timeSinceRefresh(){ |
|||
return this.timeSince(this.store.tasks.getLastUpdate(this.taskSlug)) || Date.now() |
|||
}, |
|||
hash(relay){ |
|||
return crypto.createHash('md5').update(relay).digest('hex'); |
|||
} |
|||
} |
|||
|
|||
export default defineComponent({ |
|||
name: 'TemplateTask', |
|||
components: {}, |
|||
data() { |
|||
return { |
|||
taskSlug: 'relays/canonicals', |
|||
canonicals: new Object(), |
|||
hashes: new Object() |
|||
} |
|||
}, |
|||
setup(props){ |
|||
const {resultsProp: results} = toRefs(props) |
|||
return { |
|||
store : setupStore(), |
|||
results: results |
|||
} |
|||
}, |
|||
created(){ |
|||
clearInterval(this.interval) |
|||
}, |
|||
unmounted(){ |
|||
clearInterval(this.interval) |
|||
}, |
|||
beforeMount(){ |
|||
this.lastUpdate = this.store.tasks.getLastUpdate(this.taskSlug) |
|||
this.untilNext = this.timeUntilRefresh() |
|||
this.sinceLast = this.timeSinceRefresh() |
|||
|
|||
this.relays = Array.from(new Set(relays)) |
|||
}, |
|||
mounted(){ |
|||
console.log('task', this.taskSlug, 'is processing:', this.store.tasks.isProcessing(this.taskSlug)) |
|||
if(this.store.tasks.isProcessing(this.taskSlug)) |
|||
this.invalidate(true) |
|||
else |
|||
this.invalidate() |
|||
}, |
|||
updated(){}, |
|||
computed: Object.assign(SharedComputed, { |
|||
getDynamicTimeout: function(){ |
|||
return this.averageLatency*this.relays.length |
|||
}, |
|||
}), |
|||
methods: Object.assign(localMethods, SharedMethods), |
|||
props: { |
|||
resultsProp: { |
|||
type: Object, |
|||
default(){ |
|||
return {} |
|||
} |
|||
}, |
|||
}, |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
#refresh { font-size: 12pt; color:#666; margin-bottom:15px } |
|||
#refresh button { cursor: pointer; border-radius: 3px; border: 1px solid #a0a0a0; color:#333 } |
|||
#refresh button:hover {color:#000;} |
|||
#refresh button[disabled] {color:#999 !important; border-color:#e0e0e0} |
|||
</style> |
|||
|
@ -0,0 +1,164 @@ |
|||
<template> |
|||
<span |
|||
v-if="this.store.tasks.getActiveSlug === taskSlug" |
|||
class="text-white lg:text-sm mr-2 ml-2 mt-1.5 text-xs"> |
|||
<span>Retrieving operator profiles...</span> |
|||
</span> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
|
|||
</style> |
|||
|
|||
<script> |
|||
import { defineComponent, toRefs } from 'vue' |
|||
|
|||
import crypto from 'crypto' |
|||
|
|||
import { setupStore } from '@/store' |
|||
|
|||
import { RelayPool } from 'nostr' |
|||
|
|||
import SharedMethods from '@/shared/relays-lib.js' |
|||
import SharedComputed from '@/shared/computed.js' |
|||
|
|||
import { relays } from '../../../../relays.yaml' |
|||
|
|||
const localMethods = { |
|||
queueJob: function(id, fn, unique){ |
|||
this.store.tasks.addJob({ |
|||
id: id, |
|||
handler: fn, |
|||
unique: unique |
|||
}) |
|||
}, |
|||
invalidate(force){ |
|||
if( !this.isExpired(this.taskSlug) && !force ) |
|||
return |
|||
|
|||
this.queueJob( |
|||
this.taskSlug, |
|||
() => { |
|||
const relays = this.store.relays.getAggregateCache('public') |
|||
|
|||
console.log('public relays', this.store.relays.getAggregateCache('public').length) |
|||
|
|||
const pool = new RelayPool(relays) |
|||
const subid = crypto.randomBytes(40).toString('hex') |
|||
const uniques = { |
|||
0: new Set(), |
|||
1: new Set(), |
|||
7: new Set(), |
|||
} |
|||
|
|||
const limits = { |
|||
0: 1, |
|||
1: 20, |
|||
7: 100 |
|||
} |
|||
|
|||
const kinds = [0,1,7] |
|||
//remove kind 1 for non-single page tasks |
|||
pool |
|||
.on('open', relay => { |
|||
relay.subscribe(subid, { limit:10, kinds:kinds, authors:[this.result.info.pubkey] }) |
|||
}) |
|||
.on('event', (relay, sub_id, event) => { |
|||
console.log(event) |
|||
if(!kinds.includes(event.kind)) |
|||
return |
|||
if(sub_id !== subid) |
|||
return |
|||
const u = uniques[event.kind], |
|||
l = limits[event.kind] |
|||
if( u.has(event.id) || u.size > l ) |
|||
return |
|||
if( !(event instanceof Object) ) |
|||
return |
|||
|
|||
if( !( this.events[event.kind] instanceof Object )) |
|||
this.events[event.kind] = new Object() |
|||
this.events[event.kind][event.id] = event |
|||
u.add(event.id) |
|||
if(event.kind === 0) |
|||
this.store.profile.setProfile(JSON.parse(event.content)).catch() |
|||
console.log(`kind: ${event.kind} found`, '... total', u.size, Object.keys(this.events[event.kind]).length) |
|||
console.log( 'event!', event.content ) |
|||
}) |
|||
|
|||
this.store.tasks.completeJob() |
|||
// .on('eose', relay => { |
|||
// relay.close() |
|||
// }) |
|||
}, |
|||
true |
|||
) |
|||
}, |
|||
timeUntilRefresh(){ |
|||
return this.timeSince(Date.now()-(this.store.tasks.getLastUpdate(this.taskSlug)+this.store.prefs.duration-Date.now())) |
|||
}, |
|||
timeSinceRefresh(){ |
|||
return this.timeSince(this.store.tasks.getLastUpdate(this.taskSlug)) || Date.now() |
|||
}, |
|||
} |
|||
|
|||
export default defineComponent({ |
|||
name: 'TemplateTask', |
|||
components: {}, |
|||
data() { |
|||
return { |
|||
taskSlug: 'relays/operatorprofiles' |
|||
} |
|||
}, |
|||
setup(props){ |
|||
const {resultsProp: results} = toRefs(props) |
|||
return { |
|||
store : setupStore(), |
|||
results: results |
|||
} |
|||
}, |
|||
created(){ |
|||
clearInterval(this.interval) |
|||
}, |
|||
unmounted(){ |
|||
clearInterval(this.interval) |
|||
}, |
|||
beforeMount(){ |
|||
this.lastUpdate = this.store.tasks.getLastUpdate(this.taskSlug) |
|||
this.untilNext = this.timeUntilRefresh() |
|||
this.sinceLast = this.timeSinceRefresh() |
|||
|
|||
this.relays = Array.from(new Set(relays)) |
|||
}, |
|||
mounted(){ |
|||
console.log('task', this.taskSlug, 'is processing:', this.store.tasks.isProcessing(this.taskSlug)) |
|||
if(this.store.tasks.isProcessing(this.taskSlug)) |
|||
this.invalidate(true) |
|||
else |
|||
this.invalidate() |
|||
}, |
|||
updated(){}, |
|||
computed: Object.assign(SharedComputed, { |
|||
getDynamicTimeout: function(){ |
|||
return this.averageLatency*this.relays.length |
|||
}, |
|||
}), |
|||
methods: Object.assign(localMethods, SharedMethods), |
|||
props: { |
|||
resultsProp: { |
|||
type: Object, |
|||
default(){ |
|||
return {} |
|||
} |
|||
}, |
|||
}, |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
#refresh { font-size: 12pt; color:#666; margin-bottom:15px } |
|||
#refresh button { cursor: pointer; border-radius: 3px; border: 1px solid #a0a0a0; color:#333 } |
|||
#refresh button:hover {color:#000;} |
|||
#refresh button[disabled] {color:#999 !important; border-color:#e0e0e0} |
|||
</style> |
|||
|
@ -0,0 +1,81 @@ |
|||
<template> |
|||
<RefreshTask |
|||
v-bind:resultsProp="results" /> |
|||
<!-- <RelayCanonicalsTask |
|||
:resultsProp="results" /> |
|||
<RelayOperatorTask |
|||
:resultsProp="results" /> --> |
|||
</template> |
|||
|
|||
<script> |
|||
import { defineComponent, toRefs } from 'vue' |
|||
import { useRoute } from 'vue-router' |
|||
|
|||
import { setupStore } from '@/store' |
|||
|
|||
import SharedComputed from '@/shared/computed.js' |
|||
|
|||
import RefreshTask from './RefreshTask.vue' |
|||
// import RelayCanonicalsTask from './RelayCanonicalsTask.vue' |
|||
// import RelayOperatorTask from './RelayOperatorTask.vue' |
|||
|
|||
export default defineComponent({ |
|||
name: "TasksManager", |
|||
components: { |
|||
RefreshTask, |
|||
// RelayCanonicalsTask, |
|||
// RelayOperatorTask |
|||
}, |
|||
data(){ |
|||
return { |
|||
interval: null, |
|||
currentTask: null |
|||
} |
|||
}, |
|||
setup(props){ |
|||
const {resultsProp: results} = toRefs(props) |
|||
return { |
|||
store : setupStore(), |
|||
results: results |
|||
} |
|||
}, |
|||
beforeMount(){ |
|||
//https://github.com/iendeavor/pinia-plugin-persistedstate-2/issues/136 |
|||
this.store.tasks.active = new Array() |
|||
this.store.tasks.pending = new Array() |
|||
this.store.tasks.completed = new Array() |
|||
}, |
|||
mounted(){ |
|||
this.currentTask = this.store.tasks.currentTask |
|||
this.interval = setInterval( () => { |
|||
if(this.currentTask === this.store.tasks.currentTask) |
|||
return |
|||
this.processJob() |
|||
this.currentTask = this.store.tasks.currentTask |
|||
}, 1000) |
|||
this.processJob() |
|||
}, |
|||
unmounted(){ |
|||
clearInterval(this.interval) |
|||
}, |
|||
props: { |
|||
resultsProp: { |
|||
type: Object, |
|||
default(){ |
|||
return {} |
|||
} |
|||
}, |
|||
}, |
|||
methods: { |
|||
processJob(){ |
|||
// console.log('trying processJob()') |
|||
if(!this.store.tasks.active?.handler) |
|||
return |
|||
this.store.tasks.active.handler() |
|||
} |
|||
}, |
|||
computed: Object.assign(SharedComputed, { |
|||
path: function() { return useRoute().path }, |
|||
}) |
|||
}); |
|||
</script> |
@ -0,0 +1,106 @@ |
|||
<template> |
|||
|
|||
<span class="text-white lg:text-sm mr-2 ml-2 mt-1.5 text-xs"> |
|||
<span">Task Status here</span> |
|||
</span> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
|
|||
</style> |
|||
|
|||
<script> |
|||
import { defineComponent, toRefs } from 'vue' |
|||
|
|||
import { setupStore } from '@/store' |
|||
|
|||
import SharedMethods from '@/shared/relays-lib.js' |
|||
import SharedComputed from '@/shared/computed.js' |
|||
|
|||
import { relays } from '../../../../relays.yaml' |
|||
|
|||
const localMethods = { |
|||
queueJob: function(id, fn, unique){ |
|||
this.store.tasks.addJob({ |
|||
id: id, |
|||
handler: fn, |
|||
unique: unique |
|||
}) |
|||
}, |
|||
invalidate(force){ |
|||
if( (!this.isExpired(this.taskSlug) && !force) ) |
|||
return |
|||
}, |
|||
timeUntilRefresh(){ |
|||
return this.timeSince(Date.now()-(this.store.tasks.getLastUpdate(this.taskSlug)+this.store.prefs.duration-Date.now())) |
|||
}, |
|||
timeSinceRefresh(){ |
|||
return this.timeSince(this.store.tasks.getLastUpdate(this.taskSlug)) || Date.now() |
|||
}, |
|||
} |
|||
|
|||
export default defineComponent({ |
|||
name: 'TemplateTask', |
|||
components: {}, |
|||
data() { |
|||
return { |
|||
taskSlug: 'relays/*' |
|||
} |
|||
}, |
|||
setup(props){ |
|||
const {resultsProp: results} = toRefs(props) |
|||
return { |
|||
store : setupStore(), |
|||
results: results |
|||
} |
|||
}, |
|||
created(){ |
|||
clearInterval(this.interval) |
|||
}, |
|||
unmounted(){ |
|||
clearInterval(this.interval) |
|||
}, |
|||
beforeMount(){ |
|||
this.lastUpdate = this.store.tasks.getLastUpdate(this.taskSlug) |
|||
this.untilNext = this.timeUntilRefresh() |
|||
this.sinceLast = this.timeSinceRefresh() |
|||
|
|||
this.relays = Array.from(new Set(relays)) |
|||
}, |
|||
mounted(){ |
|||
this.migrateLegacy() |
|||
|
|||
console.log('is processing', this.store.tasks.isProcessing(this.taskSlug)) |
|||
|
|||
if(this.store.tasks.isProcessing(this.taskSlug)) |
|||
this.invalidate(true) |
|||
else |
|||
this.invalidate() |
|||
|
|||
this.setRefreshInterval() |
|||
}, |
|||
updated(){}, |
|||
computed: Object.assign(SharedComputed, { |
|||
getDynamicTimeout: function(){ |
|||
return this.averageLatency*this.relays.length |
|||
}, |
|||
}), |
|||
methods: Object.assign(localMethods, SharedMethods), |
|||
props: { |
|||
resultsProp: { |
|||
type: Object, |
|||
default(){ |
|||
return {} |
|||
} |
|||
}, |
|||
}, |
|||
}) |
|||
</script> |
|||
|
|||
<style scoped> |
|||
#refresh { font-size: 12pt; color:#666; margin-bottom:15px } |
|||
#refresh button { cursor: pointer; border-radius: 3px; border: 1px solid #a0a0a0; color:#333 } |
|||
#refresh button:hover {color:#000;} |
|||
#refresh button[disabled] {color:#999 !important; border-color:#e0e0e0} |
|||
</style> |
|||
|
@ -1,5 +1,5 @@ |
|||
export default { |
|||
isExpired: function(){ |
|||
return !this.store.relays.getLastUpdate || Date.now() - this.store.relays.getLastUpdate > this.store.prefs.expireAfter |
|||
return (slug) => !this.store.tasks.getLastUpdate(slug) || Date.now() - this.store.tasks.getLastUpdate(slug) > this.store.prefs.expireAfter |
|||
}, |
|||
} |
Loading…
Reference in new issue