@solarpunkltd/swarm-chat-js 7.2.0 → 7.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -4
- package/dist/chat.d.ts +1 -1
- package/dist/follower.d.ts +17 -5
- package/dist/index.cjs +1 -1
- package/dist/index.js +134 -98
- package/dist/notes.d.ts +8 -1
- package/dist/pacer.d.ts +31 -0
- package/package.json +10 -12
package/README.md
CHANGED
|
@@ -25,9 +25,11 @@ the inbox and writes the feed. Version 7 of the library reads and writes the v7
|
|
|
25
25
|
feed entries up to the newest one a note named, never further. So it never asks Bee for an address before it is
|
|
26
26
|
written. Bee answers such a request by skipping its peers for that address for a minute, which made new messages
|
|
27
27
|
arrive about forty seconds late while readers polled the next slot. An entry a note named that does not load is
|
|
28
|
-
read again
|
|
29
|
-
|
|
30
|
-
writes
|
|
28
|
+
read again later, so a message arrives late rather than never. A slot that fails its checks is skipped and never
|
|
29
|
+
stops the chat. Gateway failures back off with jitter up to eight seconds. A reader that finds no note, which is a
|
|
30
|
+
server that writes none or a chat it has not opened yet, polls the next slot, and is led by notes from the first
|
|
31
|
+
one it finds. Any address that came back missing, a feed slot or a note, is asked for again no sooner than twenty
|
|
32
|
+
seconds later, three times a minute at most, which keeps Bee from skipping its peers for it.
|
|
31
33
|
5. **Older messages.** Loading older messages is a click, which reads the history file before the oldest one shown.
|
|
32
34
|
|
|
33
35
|
## Installation
|
|
@@ -81,7 +83,7 @@ await chat.stop();
|
|
|
81
83
|
| `infra.gsocResourceId` | The mined key every sender signs inbox writes with. The server's operator mines it. |
|
|
82
84
|
| `infra.chatTopic` | The chat's topic, which every message carries and the feed is named by. |
|
|
83
85
|
| `infra.chatAddress` | The server's feed owner address. |
|
|
84
|
-
| `infra.pollingInterval` |
|
|
86
|
+
| `infra.pollingInterval` | The shortest wait between two polls, and the first wait after the gateway failed, 1,000 ms by default. |
|
|
85
87
|
| `infra.socReadTimeout` | One feed slot read, 5,000 ms by default. |
|
|
86
88
|
| `infra.feedReadTimeout` | The head lookup and a history file download, 15,000 ms by default. A head lookup that runs out of time reads the chat from slot 0. |
|
|
87
89
|
| `infra.gsocWriteTimeout` | One inbox write, 10,000 ms by default. |
|
|
@@ -195,6 +197,12 @@ notes are found it trusts its own clock, moving reads earlier only when that bou
|
|
|
195
197
|
no note is found for longer than a heartbeat, it waits out the whole bound from then on, which is never early. A
|
|
196
198
|
clock that runs behind sees every stamp as old as it expects, so its messages arrive as late as it runs behind.
|
|
197
199
|
|
|
200
|
+
## Changes in 7.3
|
|
201
|
+
|
|
202
|
+
- A reader asks Bee for an address that came back missing no more than three times a minute, a feed slot and a note
|
|
203
|
+
alike. Polling the next slot every second, as a reader that found no note did, kept that slot on Bee's skip list,
|
|
204
|
+
so the first messages in a quiet room arrived 40 to 55 s late. A slot a note names is still read at once.
|
|
205
|
+
|
|
198
206
|
## Changes in 7.2
|
|
199
207
|
|
|
200
208
|
- Readers follow a chat by its slot notes and no longer ask for the next feed slot before it exists, so a new message
|
package/dist/chat.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export interface ChatSettings {
|
|
|
29
29
|
chatTopic: string;
|
|
30
30
|
/** The server's feed owner address. */
|
|
31
31
|
chatAddress: string;
|
|
32
|
-
/**
|
|
32
|
+
/** The shortest wait between two polls, and the first wait after the gateway failed, 1,000 ms by default. */
|
|
33
33
|
pollingInterval?: number;
|
|
34
34
|
/**
|
|
35
35
|
* The head lookup and a history file download, 15,000 ms by default. An idle Bee answers the head lookup of a
|
package/dist/follower.d.ts
CHANGED
|
@@ -13,7 +13,10 @@ export type FeedStatus = (typeof FeedStatus)[keyof typeof FeedStatus];
|
|
|
13
13
|
/** Why a slot was not shown: its payload failed a check, or it was stepped past and the retry list was full. */
|
|
14
14
|
export type SkipReason = ReadRefusal | 'unreachable';
|
|
15
15
|
export interface FollowerSettings {
|
|
16
|
+
/** The shortest wait between two polls, and the first wait after the gateway failed. */
|
|
16
17
|
pollIntervalMs: number;
|
|
18
|
+
/** The shortest wait between two reads of a feed slot that came back missing, see `pacer.ts` for why. */
|
|
19
|
+
missingReadSpacingMs: number;
|
|
17
20
|
/** Slots one poll may walk before handing back, which only a viewer catching up on a backlog reaches. */
|
|
18
21
|
maxSlotsPerPoll: number;
|
|
19
22
|
/** Polls the next slot may be refused before asking whether anything lies behind it. */
|
|
@@ -62,7 +65,9 @@ export interface FollowerEvents {
|
|
|
62
65
|
* goes to the retry list. Silence longer than the heartbeat makes the follower stop trusting its own clock.
|
|
63
66
|
*
|
|
64
67
|
* Polling, when no note has been found, which is how a server without notes is followed: reads after the last slot
|
|
65
|
-
* seen, several in one poll, until one is refused. A refused next slot is the live edge and is
|
|
68
|
+
* seen, several in one poll, until one is refused. A refused next slot is the live edge and is read again, but no
|
|
69
|
+
* sooner than `missingReadSpacingMs` after it was refused, since reading a missing address every second is what keeps
|
|
70
|
+
* Bee skipping its peers for it once it is written. The same spacing holds for every slot read again. When it
|
|
66
71
|
* keeps being refused, the follower looks a few slots past it, and a later slot that answers means the refused one is
|
|
67
72
|
* a message no peer is giving out right now. That one is stepped past and kept on a short list to read again, so a
|
|
68
73
|
* message skipped arrives late rather than never. A follower that can read notes keeps asking for them while it polls
|
|
@@ -88,6 +93,7 @@ export declare class FeedFollower {
|
|
|
88
93
|
private running;
|
|
89
94
|
private status;
|
|
90
95
|
private readonly settings;
|
|
96
|
+
private readonly pacer;
|
|
91
97
|
constructor(source: ChatSource, topic: string, events: FollowerEvents, settings?: Partial<FollowerSettings>, notes?: NoteReader | null);
|
|
92
98
|
/** The index of the next slot this follower will ask for. */
|
|
93
99
|
get nextIndex(): number;
|
|
@@ -103,7 +109,11 @@ export declare class FeedFollower {
|
|
|
103
109
|
stop(): void;
|
|
104
110
|
private schedule;
|
|
105
111
|
private poll;
|
|
106
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* The wait before the next read: until the next note is due, the next slot may be polled, or a slot on the retry
|
|
114
|
+
* list may be read again, whichever comes first. A slot the pacer does not hold back, one served unreadable, waits
|
|
115
|
+
* the poll interval.
|
|
116
|
+
*/
|
|
107
117
|
private untilNextRead;
|
|
108
118
|
/**
|
|
109
119
|
* Asks for the notes of every time slot that has ended since the last ask. One slot is read on its own. Several,
|
|
@@ -113,6 +123,8 @@ export declare class FeedFollower {
|
|
|
113
123
|
private readDueNotes;
|
|
114
124
|
/** A slot's payload, null when refused, or 'unreadable' when it was served and failed its own check. */
|
|
115
125
|
private read;
|
|
126
|
+
/** As `read`, for a slot no note has named, or 'held' without asking Bee while its last miss is too recent. */
|
|
127
|
+
private readUnnamed;
|
|
116
128
|
/**
|
|
117
129
|
* Reads forward from the next slot. Returns how many slots it showed. Throws when the gateway fails, and when the
|
|
118
130
|
* next slot has come back unreadable on several polls in a row with nothing behind it, which is the gateway answering
|
|
@@ -139,9 +151,9 @@ export declare class FeedFollower {
|
|
|
139
151
|
private probePast;
|
|
140
152
|
private remember;
|
|
141
153
|
/**
|
|
142
|
-
* Reads the stepped-past slots tried least recently, and lets go of any unread for
|
|
143
|
-
* its entries in the order they were last tried, so a slot that never loads moves to
|
|
144
|
-
* ones behind it from being read.
|
|
154
|
+
* Reads the stepped-past slots tried least recently whose last miss is old enough, and lets go of any unread for
|
|
155
|
+
* `missedExpiryMs`. The map keeps its entries in the order they were last tried, so a slot that never loads moves to
|
|
156
|
+
* the back and cannot keep the ones behind it from being read.
|
|
145
157
|
*/
|
|
146
158
|
private readMissed;
|
|
147
159
|
private accept;
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./message.cjs");let t=require("@ethersphere/bee-js");var n={LOADING_INIT:`loadingInit`,LOADING_PREVIOUS_MESSAGES:`loadingPreviousMessages`,MESSAGE_RECEIVED:`messageReceived`,MESSAGE_REQUEST_INITIATED:`messageRequestInitiated`,MESSAGE_REQUEST_UPLOADED:`messageRequestUploaded`,MESSAGE_REQUEST_ERROR:`messageRequestError`,CRITICAL_ERROR:`criticalError`,STATUS:`status`,MESSAGE_SKIPPED:`messageSkipped`,ERROR:`error`};function r(e){return a(e,-1,e.ts)}function i(e){return a(e.msg,e.seq,e.at)}function a(e,t,n){return{id:e.id,type:e.type,message:e.text,username:e.name,address:e.addr,timestamp:n,...e.target?{targetMessageId:e.target}:{},chatTopic:e.topic,signature:e.sig,index:t,sentAt:e.ts}}var o=class{constructor(){this.listeners=new Map,this.on=(e,t)=>{this.listeners.set(e,[...this.listeners.get(e)??[],t])},this.off=(e,t)=>{this.listeners.set(e,(this.listeners.get(e)??[]).filter(e=>e!==t))},this.cleanAll=()=>{this.listeners.clear()}}emit(e,t){for(let n of this.listeners.get(e)??[])try{n(t)}catch(t){console.error(`a listener for ${e} threw`,t)}}},s=class extends Error{constructor(e){super(`the feed head lookup did not answer in time`,e),this.name=`HeadLookupTimeoutError`}},c=class extends Error{constructor(e,t){super(`feed slot ${e} was served but could not be read`,t),this.index=e,this.name=`UnreadableSlotError`}};function l(e){return e instanceof t.BeeResponseError&&(e.status===404||e.status===500)}async function u(e,n){try{return await n()}catch(n){if(l(n))return null;throw n instanceof t.BeeResponseError?n:new c(e,{cause:n})}}function d(n,r,i,a,o=e.DEFAULT_NOTE_SLOT_MS){let c=t.Topic.fromString(i),d=e=>n.feed.makeReader(c,r,f(e));return{readNote:t=>u(t,async()=>(await n.soc.makeReader(r,f(a.slotReadMs)).download(e.noteIdentifier(i,o,t))).payload.toUint8Array()),readSlot:e=>u(e,async()=>(await d(a.slotReadMs).downloadPayload({index:e})).payload.toUint8Array()),async readHead(){let e=f(a.feedReadMs);try{let{payload:t,feedIndex:i}=await n.feed.makeReader(c,r,e).downloadPayload();return{index:Number(i.toBigInt()),payload:t.toUint8Array()}}catch(t){if(l(t))return null;throw e.signal?.aborted?new s({cause:t}):t}},async readFile(e){return(await n.data.download(e,void 0,f(a.feedReadMs))).toUint8Array()}}}function f(e){return{signal:AbortSignal.timeout(e)}}var p=`0123456789abcdef`.repeat(4);function m(e,n,r,i,a){let o=new t.PrivateKey(r),s=t.Identifier.fromString(i);return async t=>{await e.messaging.gsocSend(n,o,s,t,{deferred:!1},f(a.writeMs))}}var h={LIVE:`live`,RECONNECTING:`reconnecting`,STALLED:`stalled`},g={pollIntervalMs:1e3,maxSlotsPerPoll:16,unservedPollsBeforeProbe:3,unreadablePollsBeforeFailure:3,probeIntervalCapMs:6e4,probeDistances:[1,2,4,8],missedLimit:32,missedReadsPerPoll:4,missedExpiryMs:3e5,backoffCapMs:8e3,stallAfterMs:8e3,random:Math.random,now:Date.now},_=class extends Error{constructor(e){super(`the gateway keeps answering feed slot ${e} with something that is not a chunk`),this.index=e,this.name=`UnreadableGatewayError`}},v=class{constructor(e,t,n,r={},i=null){this.source=e,this.topic=t,this.events=n,this.notes=i,this.next=0,this.ceiling=null,this.nextNoteSlot=0,this.lastNoteFoundAt=0,this.unservedPolls=0,this.nextProbeAt=0,this.unreadablePolls=0,this.gatewayFailures=0,this.missed=new Map,this.timer=null,this.generation=0,this.running=!1,this.status=null,this.settings={...g,...r}}get nextIndex(){return this.next}get missedIndices(){return[...this.missed.keys()]}get ledByNotes(){return this.ceiling!==null}start(e,t=null){this.stop(),this.next=e,this.ceiling=t?.newest??null,this.nextNoteSlot=t?.nextNoteSlot??(this.notes?this.notes.lastReadableSlot()+1:0),this.lastNoteFoundAt=this.settings.now(),this.resetRefusals(),this.unreadablePolls=0,this.gatewayFailures=0,this.missed.clear(),this.status=null,this.running=!0,this.schedule(0)}stop(){this.running=!1,this.generation++,this.timer&&=(clearTimeout(this.timer),null)}schedule(e){let t=this.generation;this.timer=setTimeout(()=>{this.timer=null,this.poll(t)},e)}async poll(e){let t=0,n=!1;try{this.notes!==null&&await this.readDueNotes(this.notes,e),t=await this.walk(e),await this.readMissed(e)}catch(t){n=!0,e===this.generation&&this.gatewayFailed(t)}if(e===this.generation&&this.running){if(n){this.schedule(this.backoffMs());return}this.gatewayAnswered(),this.schedule(t>=this.settings.maxSlotsPerPoll?0:this.untilNextRead())}}untilNextRead(){return this.ceiling===null||this.notes===null?this.settings.pollIntervalMs:Math.max(0,this.notes.readableAt(this.nextNoteSlot)-this.settings.now())}async readDueNotes(e,t){let n=this.settings.now(),r=e.lastReadableSlot(n);if(r<this.nextNoteSlot)return;let i=Math.max(this.nextNoteSlot,r-e.lookBackSlots+1),a=await e.scan(r,i);if(t===this.generation){if(this.nextNoteSlot=r+1,a){this.lastNoteFoundAt=n,this.ceiling=Math.max(this.ceiling??-1,a.note.newest,this.next-1);return}n-this.lastNoteFoundAt>=e.silenceMs&&(this.lastNoteFoundAt=n,e.clock.distrust(),this.nextNoteSlot=Math.min(this.nextNoteSlot,e.lastReadableSlot(n)+1))}}async read(e){try{return await this.source.readSlot(e)}catch(e){if(e instanceof c)return`unreadable`;throw e}}async walk(e){for(let t=0;t<this.settings.maxSlotsPerPoll;t++){let n=this.next;if(this.ceiling!==null&&n>this.ceiling)return t;let r=await this.read(n);if(e!==this.generation)return t;if(this.ceiling!==null&&(r===null||r===`unreadable`)){this.remember(n),this.next=n+1;continue}if(r===null||r===`unreadable`){if(t===0){if(this.unservedPolls++,this.unreadablePolls=r===`unreadable`?this.unreadablePolls+1:0,this.shouldProbe()&&await this.probePast(n,e)){this.unreadablePolls=0;continue}if(this.unreadablePolls>=this.settings.unreadablePollsBeforeFailure)throw new _(n)}return t}this.resetRefusals(),this.unreadablePolls=0,this.next=n+1,this.accept(n,r)}return this.settings.maxSlotsPerPoll}shouldProbe(){if(this.unservedPolls!==this.nextProbeAt)return!1;let{probeIntervalCapMs:e,pollIntervalMs:t}=this.settings,n=Math.max(1,Math.ceil(e/t));return this.nextProbeAt+=Math.min(this.nextProbeAt,n),!0}resetRefusals(){this.unservedPolls=0,this.nextProbeAt=this.settings.unservedPollsBeforeProbe}async probePast(e,t){let n=new Set([e]);for(let r of this.settings.probeDistances){let i=e+r,a=await this.read(i);if(t!==this.generation)return!1;if(a===null||a===`unreadable`){n.add(i);continue}this.resetRefusals(),this.remember(e);for(let r=e+1;r<i;r++){let e=n.has(r)?null:await this.read(r);if(t!==this.generation)return!1;e===null||e===`unreadable`?this.remember(r):this.accept(r,e)}return this.next=i+1,this.accept(i,a),!0}return!1}remember(e){if(!this.missed.has(e)){if(this.missed.size>=this.settings.missedLimit){this.events.skipped(e,`unreachable`,`stepped past with the retry list full`);return}this.missed.set(e,this.settings.now())}}async readMissed(e){let t=this.settings.now();for(let[e,n]of this.missed)t-n>=this.settings.missedExpiryMs&&(this.missed.delete(e),this.events.skipped(e,`unreachable`,`not served within the time a stepped-past slot is kept`));let n=[...this.missed.keys()].slice(0,this.settings.missedReadsPerPoll);for(let t of n){let n=await this.read(t);if(e!==this.generation)return;let r=this.missed.get(t);this.missed.delete(t),n!==null&&n!==`unreadable`?this.accept(t,n):r!==void 0&&this.missed.set(t,r)}}accept(t,n){let r=e.parseFeedEntry(n,t,this.topic);r.ok?(this.notes?.clock.observe(r.value.at,this.settings.now()),this.events.entry(r.value)):this.events.skipped(t,r.reason,r.detail)}gatewayFailed(e){this.gatewayFailures++,this.setStatus(h.RECONNECTING),this.events.error(e)}gatewayAnswered(){this.gatewayFailures=0;let e=this.settings.now(),t=[...this.missed.values()].some(t=>e-t>=this.settings.stallAfterMs);this.setStatus(t?h.STALLED:h.LIVE)}backoffMs(){let{pollIntervalMs:e,backoffCapMs:t,random:n}=this.settings,r=Math.min(t,e*2**this.gatewayFailures);return r/2+n()*r/2}setStatus(e){e!==this.status&&(this.status=e,this.events.status(e))}},y=16,b=class{constructor(e,t,n,r=null){this.source=e,this.topic=t,this.events=n,this.notes=r,this.older=null,this.loading=null}async open(){this.older=null;let e=await this.newestNote();if(e!==null)return this.openAt(e.newest,e);let t=await this.readHead();return t===null?{startAt:0,rows:[],notes:null}:this.openAt(t.index,null,t.payload)}async newestNote(){if(this.notes===null)return null;let e=this.notes.lastReadableSlot(),t=await this.notes.scan(e,e-this.notes.lookBackSlots+1);return t===null?null:{newest:t.note.newest,nextNoteSlot:e+1}}async openAt(e,t,n){let r=e<0?null:await this.newestLink(e,n);if(r===null)return{startAt:0,rows:[],notes:t};let i=await this.readFile(r);return i===`refused`?{startAt:0,rows:[],notes:t}:{startAt:r.toSeq+1,rows:i??[],notes:t}}async readHead(){try{return await this.source.readHead()}catch(e){if(e instanceof s)return this.events.error(e),null;throw e}}hasOlder(){return this.older!==null}loadOlder(){return this.loading??=this.loadOlderOnce().finally(()=>{this.loading=null}),this.loading}async loadOlderOnce(){let t=this.older;if(t===null)return[];let n=await this.source.readFile(t.ref),r=e.parseHistoryFile(n,this.topic,t);if(!r.ok)throw this.older=null,Error(`history file ${t.ref} refused (${r.reason}): ${r.detail}`);return this.report(r.value.skipped),this.older=r.value.prev,r.value.rows}async newestLink(t,n){let r=n===void 0?t:t-1;if(n!==void 0){let r=e.parseFeedEntry(n,t,this.topic);if(r.ok)return r.value.history}for(let n=r;n>=Math.max(0,t-y);n--){let t=await this.source.readSlot(n).catch(e=>{if(e instanceof c)return null;throw e});if(t===null)continue;let r=e.parseFeedEntry(t,n,this.topic);if(r.ok)return r.value.history}return null}async readFile(t){try{let n=e.parseHistoryFile(await this.source.readFile(t.ref),this.topic,t);return n.ok?(this.report(n.value.skipped),this.older=n.value.prev,n.value.rows):(this.events.error(Error(`history file ${t.ref} refused (${n.reason}): ${n.detail}`)),`refused`)}catch(e){return this.events.error(e),this.older=t,null}}report(e){e>0&&this.events.skipped(e)}},x={slotMs:e.DEFAULT_NOTE_SLOT_MS,heartbeatMs:e.DEFAULT_NOTE_HEARTBEAT_MS,marginMs:1e3,scanBatch:4,clockLimitMs:3e5};function S(e){return typeof e.readNote==`function`}var C=class{constructor(e){this.limitMs=e,this.aheadAtMost=1/0,this.trusted=!0}observe(e,t){this.aheadAtMost=Math.min(this.aheadAtMost,t-e)}distrust(){this.trusted=!1}get isTrusted(){return this.trusted}get ms(){let e=Number.isFinite(this.aheadAtMost),t=this.trusted||!e?Math.min(0,this.aheadAtMost):this.aheadAtMost;return Math.max(-this.limitMs,Math.min(this.limitMs,t))}},w=class{constructor(e,t,n){this.source=e,this.now=n,this.counts={found:0,empty:0},this.settings={...x,...t},this.clock=new C(this.settings.clockLimitMs)}lastReadableSlot(t=this.now()){return e.noteSlotOf(t-this.settings.marginMs-this.clock.ms,this.settings.slotMs)-1}readableAt(t){return e.noteSlotEnd(t,this.settings.slotMs)+this.settings.marginMs+this.clock.ms}get lookBackSlots(){return Math.ceil(this.settings.heartbeatMs/this.settings.slotMs)+2}get silenceMs(){return this.settings.heartbeatMs+2*this.settings.slotMs+this.settings.marginMs}async read(t){let n;try{n=await this.source.readNote(t)}catch(e){if(!(e instanceof c))throw e;n=null}let r=n===null?null:e.parseSlotNote(n);return r===null?(this.counts.empty++,null):(this.counts.found++,this.clock.observe(r.writtenAt,this.now()),r)}async scan(e,t){for(let n=e;n>=t;n-=this.settings.scanBatch){let e=[];for(let r=n;r>n-this.settings.scanBatch&&r>=t;r--)e.push(r);let r=await Promise.all(e.map(e=>this.read(e))),i=r.findIndex(e=>e!==null);if(i>=0)return{slot:e[i],note:r[i]}}return null}},T={resendAfterMs:1e4,maxResends:5,reactionWindowMs:1e3,isLive:()=>!0},E=class{constructor(e,t,n,r,i=T){this.key=e,this.name=t,this.write=n,this.events=r,this.settings=i,this.outgoing=new Map,this.reactions=new Map,this.stopped=!1}get address(){return this.key.publicKey().address().toHex()}send(t){let{message:n,bytes:r}=e.createChatMessage(this.key,{...t,name:this.name}),i={message:n,bytes:r,writes:0,written:!1,inFlight:!1,timer:null,lastError:null};return this.outgoing.set(n.id,i),this.events.pending(n),this.attempt(i),n}react(t,n,r){let i=JSON.stringify([t,n,r]),a=this.reactions.get(i);if(a){a.taps++;return}let o=setTimeout(()=>{let a=this.reactions.get(i);this.reactions.delete(i),a&&a.taps%2==1&&!this.stopped&&this.send({topic:t,type:e.MessageType.REACTION,target:n,text:r})},this.settings.reactionWindowMs);this.reactions.set(i,{taps:1,timer:o})}confirm(e){if(e.addr!==this.address)return;let t=this.outgoing.get(e.id);t&&(this.clearTimer(t),this.outgoing.delete(e.id),this.events.confirmed(t.message))}retry(e){let t=this.outgoing.get(e);return!t||this.stopped?!1:(this.clearTimer(t),t.writes=0,t.inFlight||this.attempt(t),!0)}isPending(e){return this.outgoing.has(e)}stop(){this.stopped=!0;for(let e of this.outgoing.values())this.clearTimer(e);for(let{timer:e}of this.reactions.values())clearTimeout(e);this.reactions.clear()}attempt(e){!this.stopped&&this.outgoing.has(e.message.id)&&(e.writes++,e.inFlight=!0,this.write(e.bytes).then(()=>{e.inFlight=!1,!e.written&&this.outgoing.has(e.message.id)&&(e.written=!0,this.events.written(e.message)),this.scheduleNext(e)},t=>{e.inFlight=!1,e.lastError=t,this.scheduleNext(e)}))}scheduleNext(e){!this.stopped&&this.outgoing.has(e.message.id)&&(e.timer=setTimeout(()=>{if(e.timer=null,!this.settings.isLive()){this.scheduleNext(e);return}if(e.writes>this.settings.maxResends){this.events.failed(e.message,e.lastError??Error(`never seen on the chat feed`));return}this.attempt(e)},this.settings.resendAfterMs))}clearTimer(e){e.timer&&=(clearTimeout(e.timer),null)}},D=[1e3,2e3,4e3,8e3],O=3;function k(e){return(e.startsWith(`0x`)?e.slice(2):e).toLowerCase()}var A=class{constructor(e,r={}){this.emitter=new o,this.seen=new Set,this.status=null,this.generation=0,this.running=!1,this.starting=null,this.openTimer=null,this.wakeOpen=null;let{user:i,infra:a}=e;this.key=new t.PrivateKey(k(i.privateKey)),this.topic=a.chatTopic,this.nickname=i.nickname,this.openRetryMs=r.openRetryMs??D,this.senderSettings={...T,isLive:()=>this.status===h.LIVE,...r.sender};let s={slotReadMs:a.socReadTimeout??5e3,feedReadMs:a.feedReadTimeout??15e3,writeMs:a.gsocWriteTimeout??1e4},c={...a.noteSlotMs===void 0?{}:{slotMs:a.noteSlotMs},...a.noteHeartbeatMs===void 0?{}:{heartbeatMs:a.noteHeartbeatMs},...r.notes};this.source=r.source??d(new t.Bee(a.beeUrl),k(a.chatAddress),a.chatTopic,s,c.slotMs),this.write=r.write??m(new t.Bee(a.writeUrl??a.beeUrl),a.stamp?k(a.stamp):p,k(a.gsocResourceId),a.gsocTopic,s);let l=r.follower?.now??(()=>Date.now()),u={pollIntervalMs:a.pollingInterval??1e3,...r.follower,now:l},f=this.source;this.notes=S(f)?new w(f,c,l):null,this.follower=new v(this.source,this.topic,{entry:e=>this.show(e),skipped:(e,t,r)=>this.emitter.emit(n.MESSAGE_SKIPPED,{index:e,reason:t,detail:r}),status:e=>this.setStatus(e),error:e=>this.emitter.emit(n.ERROR,e)},u,this.notes),this.history=new b(this.source,this.topic,{skipped:e=>this.emitter.emit(n.MESSAGE_SKIPPED,{index:-1,reason:`history-row`,detail:`${e} rows of a history file`}),error:e=>this.emitter.emit(n.ERROR,e)},this.notes),this.sender=this.makeSender()}getEmitter(){return this.emitter}getStatus(){return this.status}getAddress(){return this.key.publicKey().address().toHex()}start(){if(this.running)return this.starting??Promise.resolve();this.running=!0,this.generation++,this.seen.clear(),this.sender=this.makeSender();let e=this.open(this.generation).finally(()=>{this.starting===e&&(this.starting=null)});return this.starting=e,e}async stop(){this.running=!1,this.generation++,this.openTimer&&=(clearTimeout(this.openTimer),null),this.wakeOpen?.(),this.follower.stop(),this.sender.stop()}async sendMessage(t,n,i){if(n===e.MessageType.REACTION){if(!i)throw Error(`a reaction names the message it reacts to`);return this.sender.react(this.topic,i,t),null}return r(this.sender.send({topic:this.topic,type:n,target:i??``,text:t}))}retrySendMessage(e){return this.sender.retry(typeof e==`string`?e:e.id)}async fetchPreviousMessages(){this.emitter.emit(n.LOADING_PREVIOUS_MESSAGES,!0);try{let e=this.generation,t=await this.history.loadOlder();return e===this.generation?this.showRows(t):[]}finally{this.emitter.emit(n.LOADING_PREVIOUS_MESSAGES,!1)}}hasPreviousMessages(){return this.history.hasOlder()}orderMessages(e){return[...e].sort((e,t)=>e.index>=0&&t.index>=0?e.index-t.index:e.index>=0||t.index>=0?e.index>=0?-1:1:e.timestamp-t.timestamp)}async open(e){this.emitter.emit(n.LOADING_INIT,!0);let t=0;for(;e===this.generation;)try{let t=await this.history.open();if(e!==this.generation)return;this.showRows(t.rows),this.follower.start(t.startAt,t.notes),this.emitter.emit(n.LOADING_INIT,!1);return}catch(r){if(e!==this.generation)return;t++,this.emitter.emit(n.ERROR,r),this.setStatus(h.RECONNECTING),t===O&&this.emitter.emit(n.CRITICAL_ERROR,r),await this.waitToOpen(this.openRetryMs[Math.min(t,this.openRetryMs.length)-1]??8e3)}}waitToOpen(e){return new Promise(t=>{this.wakeOpen=()=>{this.wakeOpen=null,t()},this.openTimer=setTimeout(()=>{this.openTimer=null,this.wakeOpen?.()},e)})}makeSender(){return new E(this.key,this.nickname,this.write,{pending:e=>this.emitter.emit(n.MESSAGE_REQUEST_INITIATED,r(e)),written:e=>this.emitter.emit(n.MESSAGE_REQUEST_UPLOADED,r(e)),failed:(e,t)=>{this.emitter.emit(n.ERROR,t),this.emitter.emit(n.MESSAGE_REQUEST_ERROR,r(e))},confirmed:()=>{}},this.senderSettings)}show(e){this.seen.has(e.seq)||(this.seen.add(e.seq),this.sender.confirm(e.msg),this.emitter.emit(n.MESSAGE_RECEIVED,i(e)))}showRows(e){let t=[];for(let r of e){if(this.seen.has(r.seq))continue;this.seen.add(r.seq),this.sender.confirm(r.msg);let e=i(r);t.push(e),this.emitter.emit(n.MESSAGE_RECEIVED,e)}return t}setStatus(e){e!==this.status&&(this.status=e,this.emitter.emit(n.STATUS,e))}};exports.ChatEmitter=o,exports.ChatMessageError=e.ChatMessageError,exports.DEFAULT_NOTE_HEARTBEAT_MS=e.DEFAULT_NOTE_HEARTBEAT_MS,exports.DEFAULT_NOTE_SLOT_MS=e.DEFAULT_NOTE_SLOT_MS,exports.EVENTS=n,exports.FeedStatus=h,exports.HeadLookupTimeoutError=s,exports.MAX_ENTRY_BYTES=e.MAX_ENTRY_BYTES,exports.MAX_MESSAGE_BYTES=e.MAX_MESSAGE_BYTES,exports.MAX_NAME_CHARACTERS=e.MAX_NAME_CHARACTERS,exports.MAX_NOTE_BYTES=e.MAX_NOTE_BYTES,exports.MAX_TEXT_CHARACTERS=e.MAX_TEXT_CHARACTERS,exports.MAX_TOPIC_CHARACTERS=e.MAX_TOPIC_CHARACTERS,exports.MESSAGE_VERSION=e.MESSAGE_VERSION,exports.MessageType=e.MessageType,exports.NOTE_VERSION=e.NOTE_VERSION,exports.SwarmChat=A,exports.UnreadableGatewayError=_,exports.UnreadableSlotError=c,exports.chatMessageSchema=e.chatMessageSchema,exports.checkChatMessage=e.checkChatMessage,exports.countCharacters=e.countCharacters,exports.createChatMessage=e.createChatMessage,exports.encodeChatMessage=e.encodeChatMessage,exports.encodeSlotNote=e.encodeSlotNote,exports.feedEntrySchema=e.feedEntrySchema,exports.hasValidSignature=e.hasValidSignature,exports.historyFileSchema=e.historyFileSchema,exports.historyLinkSchema=e.historyLinkSchema,exports.historyRowSchema=e.historyRowSchema,exports.newMessageId=e.newMessageId,exports.noteAddress=e.noteAddress,exports.noteIdentifier=e.noteIdentifier,exports.noteIdentifierText=e.noteIdentifierText,exports.noteSlotEnd=e.noteSlotEnd,exports.noteSlotOf=e.noteSlotOf,exports.parseChatMessage=e.parseChatMessage,exports.parseFeedEntry=e.parseFeedEntry,exports.parseHistoryFile=e.parseHistoryFile,exports.parseSlotNote=e.parseSlotNote,exports.signedBytes=e.signedBytes,exports.slotNoteSchema=e.slotNoteSchema;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./message.cjs");let t=require("@ethersphere/bee-js");var n={LOADING_INIT:`loadingInit`,LOADING_PREVIOUS_MESSAGES:`loadingPreviousMessages`,MESSAGE_RECEIVED:`messageReceived`,MESSAGE_REQUEST_INITIATED:`messageRequestInitiated`,MESSAGE_REQUEST_UPLOADED:`messageRequestUploaded`,MESSAGE_REQUEST_ERROR:`messageRequestError`,CRITICAL_ERROR:`criticalError`,STATUS:`status`,MESSAGE_SKIPPED:`messageSkipped`,ERROR:`error`};function r(e){return a(e,-1,e.ts)}function i(e){return a(e.msg,e.seq,e.at)}function a(e,t,n){return{id:e.id,type:e.type,message:e.text,username:e.name,address:e.addr,timestamp:n,...e.target?{targetMessageId:e.target}:{},chatTopic:e.topic,signature:e.sig,index:t,sentAt:e.ts}}var o=class{constructor(){this.listeners=new Map,this.on=(e,t)=>{this.listeners.set(e,[...this.listeners.get(e)??[],t])},this.off=(e,t)=>{this.listeners.set(e,(this.listeners.get(e)??[]).filter(e=>e!==t))},this.cleanAll=()=>{this.listeners.clear()}}emit(e,t){for(let n of this.listeners.get(e)??[])try{n(t)}catch(t){console.error(`a listener for ${e} threw`,t)}}},s=2e4,c=class{constructor(e,t){this.spacingMs=e,this.now=t,this.missedAt=new Map}readableAt(e,t=this.now()){let n=this.missedAt.get(e);return n===void 0||this.expired(n,t)?-1/0:n+this.spacingMs}mayRead(e,t=this.now()){return t>=this.readableAt(e,t)}missed(e,t=this.now()){for(let[e,n]of this.missedAt)this.expired(n,t)&&this.missedAt.delete(e);this.missedAt.set(e,t)}found(e){this.missedAt.delete(e)}clear(){this.missedAt.clear()}expired(e,t){return e>t||t-e>=this.spacingMs}},l=class extends Error{constructor(e){super(`the feed head lookup did not answer in time`,e),this.name=`HeadLookupTimeoutError`}},u=class extends Error{constructor(e,t){super(`feed slot ${e} was served but could not be read`,t),this.index=e,this.name=`UnreadableSlotError`}};function d(e){return e instanceof t.BeeResponseError&&(e.status===404||e.status===500)}async function f(e,n){try{return await n()}catch(n){if(d(n))return null;throw n instanceof t.BeeResponseError?n:new u(e,{cause:n})}}function p(n,r,i,a,o=e.DEFAULT_NOTE_SLOT_MS){let s=t.Topic.fromString(i),c=e=>n.feed.makeReader(s,r,m(e));return{readNote:t=>f(t,async()=>(await n.soc.makeReader(r,m(a.slotReadMs)).download(e.noteIdentifier(i,o,t))).payload.toUint8Array()),readSlot:e=>f(e,async()=>(await c(a.slotReadMs).downloadPayload({index:e})).payload.toUint8Array()),async readHead(){let e=m(a.feedReadMs);try{let{payload:t,feedIndex:i}=await n.feed.makeReader(s,r,e).downloadPayload();return{index:Number(i.toBigInt()),payload:t.toUint8Array()}}catch(t){if(d(t))return null;throw e.signal?.aborted?new l({cause:t}):t}},async readFile(e){return(await n.data.download(e,void 0,m(a.feedReadMs))).toUint8Array()}}}function m(e){return{signal:AbortSignal.timeout(e)}}var h=`0123456789abcdef`.repeat(4);function g(e,n,r,i,a){let o=new t.PrivateKey(r),s=t.Identifier.fromString(i);return async t=>{await e.messaging.gsocSend(n,o,s,t,{deferred:!1},m(a.writeMs))}}var _={LIVE:`live`,RECONNECTING:`reconnecting`,STALLED:`stalled`},v={pollIntervalMs:1e3,missingReadSpacingMs:s,maxSlotsPerPoll:16,unservedPollsBeforeProbe:3,unreadablePollsBeforeFailure:3,probeIntervalCapMs:6e4,probeDistances:[1,2,4,8],missedLimit:32,missedReadsPerPoll:4,missedExpiryMs:3e5,backoffCapMs:8e3,stallAfterMs:8e3,random:Math.random,now:Date.now},y=class extends Error{constructor(e){super(`the gateway keeps answering feed slot ${e} with something that is not a chunk`),this.index=e,this.name=`UnreadableGatewayError`}},b=class{constructor(e,t,n,r={},i=null){this.source=e,this.topic=t,this.events=n,this.notes=i,this.next=0,this.ceiling=null,this.nextNoteSlot=0,this.lastNoteFoundAt=0,this.unservedPolls=0,this.nextProbeAt=0,this.unreadablePolls=0,this.gatewayFailures=0,this.missed=new Map,this.timer=null,this.generation=0,this.running=!1,this.status=null,this.settings={...v,...r},this.pacer=new c(this.settings.missingReadSpacingMs,this.settings.now)}get nextIndex(){return this.next}get missedIndices(){return[...this.missed.keys()]}get ledByNotes(){return this.ceiling!==null}start(e,t=null){this.stop(),this.next=e,this.ceiling=t?.newest??null,this.nextNoteSlot=t?.nextNoteSlot??(this.notes?this.notes.lastReadableSlot()+1:0),this.lastNoteFoundAt=this.settings.now(),this.resetRefusals(),this.unreadablePolls=0,this.gatewayFailures=0,this.missed.clear(),this.pacer.clear(),this.status=null,this.running=!0,this.schedule(0)}stop(){this.running=!1,this.generation++,this.timer&&=(clearTimeout(this.timer),null)}schedule(e){let t=this.generation;this.timer=setTimeout(()=>{this.timer=null,this.poll(t)},e)}async poll(e){let t=0,n=!1;try{this.notes!==null&&await this.readDueNotes(this.notes,e),t=await this.walk(e),await this.readMissed(e)}catch(t){n=!0,e===this.generation&&this.gatewayFailed(t)}if(e===this.generation&&this.running){if(n){this.schedule(this.backoffMs());return}this.gatewayAnswered(),this.schedule(t>=this.settings.maxSlotsPerPoll?0:this.untilNextRead())}}untilNextRead(){let e=this.settings.now(),t=t=>{let n=this.pacer.readableAt(t);return n>e?n:e+this.settings.pollIntervalMs},n=[...this.missed.keys()].map(t);return this.notes!==null&&n.push(this.notes.readableAt(this.nextNoteSlot)),this.ceiling===null&&n.push(t(this.next)),Math.max(0,Math.min(...n)-e)}async readDueNotes(e,t){let n=this.settings.now(),r=e.lastReadableSlot(n);if(r<this.nextNoteSlot)return;let i=Math.max(this.nextNoteSlot,r-e.lookBackSlots+1),a=await e.scan(r,i);if(t===this.generation){if(this.nextNoteSlot=r+1,a){this.lastNoteFoundAt=n,this.ceiling=Math.max(this.ceiling??-1,a.note.newest,this.next-1);return}n-this.lastNoteFoundAt>=e.silenceMs&&(this.lastNoteFoundAt=n,e.clock.distrust(),this.nextNoteSlot=Math.min(this.nextNoteSlot,e.lastReadableSlot(n)+1))}}async read(e){let t;try{t=await this.source.readSlot(e)}catch(e){if(e instanceof u)return`unreadable`;throw e}return t===null?this.pacer.missed(e):this.pacer.found(e),t}async readUnnamed(e){return this.pacer.mayRead(e)?this.read(e):`held`}async walk(e){for(let t=0;t<this.settings.maxSlotsPerPoll;t++){let n=this.next;if(this.ceiling!==null&&n>this.ceiling)return t;let r=this.ceiling===null?await this.readUnnamed(n):await this.read(n);if(e!==this.generation||r===`held`)return t;if(this.ceiling!==null&&(r===null||r===`unreadable`)){this.remember(n),this.next=n+1;continue}if(r===null||r===`unreadable`){if(t===0){if(this.unservedPolls++,this.unreadablePolls=r===`unreadable`?this.unreadablePolls+1:0,this.shouldProbe()&&await this.probePast(n,e)){this.unreadablePolls=0;continue}if(this.unreadablePolls>=this.settings.unreadablePollsBeforeFailure)throw new y(n)}return t}this.resetRefusals(),this.unreadablePolls=0,this.next=n+1,this.accept(n,r)}return this.settings.maxSlotsPerPoll}shouldProbe(){if(this.unservedPolls!==this.nextProbeAt)return!1;let{probeIntervalCapMs:e,pollIntervalMs:t,missingReadSpacingMs:n}=this.settings,r=Math.max(1,Math.ceil(e/Math.max(t,n)));return this.nextProbeAt+=Math.min(this.nextProbeAt,r),!0}resetRefusals(){this.unservedPolls=0,this.nextProbeAt=this.settings.unservedPollsBeforeProbe}async probePast(e,t){let n=new Set([e]);for(let r of this.settings.probeDistances){let i=e+r,a=await this.readUnnamed(i);if(t!==this.generation)return!1;if(a===null||a===`unreadable`||a===`held`){n.add(i);continue}this.resetRefusals(),this.remember(e);for(let r=e+1;r<i;r++){let e=n.has(r)?null:await this.readUnnamed(r);if(t!==this.generation)return!1;e===null||e===`unreadable`||e===`held`?this.remember(r):this.accept(r,e)}return this.next=i+1,this.accept(i,a),!0}return!1}remember(e){if(!this.missed.has(e)){if(this.missed.size>=this.settings.missedLimit){this.events.skipped(e,`unreachable`,`stepped past with the retry list full`);return}this.missed.set(e,this.settings.now())}}async readMissed(e){let t=this.settings.now();for(let[e,n]of this.missed)t-n>=this.settings.missedExpiryMs&&(this.missed.delete(e),this.events.skipped(e,`unreachable`,`not served within the time a stepped-past slot is kept`));let n=[...this.missed.keys()].filter(e=>this.pacer.mayRead(e,t)).slice(0,this.settings.missedReadsPerPoll);for(let t of n){let n=await this.read(t);if(e!==this.generation)return;let r=this.missed.get(t);this.missed.delete(t),n!==null&&n!==`unreadable`?this.accept(t,n):r!==void 0&&this.missed.set(t,r)}}accept(t,n){let r=e.parseFeedEntry(n,t,this.topic);r.ok?(this.notes?.clock.observe(r.value.at,this.settings.now()),this.events.entry(r.value)):this.events.skipped(t,r.reason,r.detail)}gatewayFailed(e){this.gatewayFailures++,this.setStatus(_.RECONNECTING),this.events.error(e)}gatewayAnswered(){this.gatewayFailures=0;let e=this.settings.now(),t=[...this.missed.values()].some(t=>e-t>=this.settings.stallAfterMs);this.setStatus(t?_.STALLED:_.LIVE)}backoffMs(){let{pollIntervalMs:e,backoffCapMs:t,random:n}=this.settings,r=Math.min(t,e*2**this.gatewayFailures);return r/2+n()*r/2}setStatus(e){e!==this.status&&(this.status=e,this.events.status(e))}},x=16,S=class{constructor(e,t,n,r=null){this.source=e,this.topic=t,this.events=n,this.notes=r,this.older=null,this.loading=null}async open(){this.older=null;let e=await this.newestNote();if(e!==null)return this.openAt(e.newest,e);let t=await this.readHead();return t===null?{startAt:0,rows:[],notes:null}:this.openAt(t.index,null,t.payload)}async newestNote(){if(this.notes===null)return null;let e=this.notes.lastReadableSlot(),t=await this.notes.scan(e,e-this.notes.lookBackSlots+1);return t===null?null:{newest:t.note.newest,nextNoteSlot:e+1}}async openAt(e,t,n){let r=e<0?null:await this.newestLink(e,n);if(r===null)return{startAt:0,rows:[],notes:t};let i=await this.readFile(r);return i===`refused`?{startAt:0,rows:[],notes:t}:{startAt:r.toSeq+1,rows:i??[],notes:t}}async readHead(){try{return await this.source.readHead()}catch(e){if(e instanceof l)return this.events.error(e),null;throw e}}hasOlder(){return this.older!==null}loadOlder(){return this.loading??=this.loadOlderOnce().finally(()=>{this.loading=null}),this.loading}async loadOlderOnce(){let t=this.older;if(t===null)return[];let n=await this.source.readFile(t.ref),r=e.parseHistoryFile(n,this.topic,t);if(!r.ok)throw this.older=null,Error(`history file ${t.ref} refused (${r.reason}): ${r.detail}`);return this.report(r.value.skipped),this.older=r.value.prev,r.value.rows}async newestLink(t,n){let r=n===void 0?t:t-1;if(n!==void 0){let r=e.parseFeedEntry(n,t,this.topic);if(r.ok)return r.value.history}for(let n=r;n>=Math.max(0,t-x);n--){let t=await this.source.readSlot(n).catch(e=>{if(e instanceof u)return null;throw e});if(t===null)continue;let r=e.parseFeedEntry(t,n,this.topic);if(r.ok)return r.value.history}return null}async readFile(t){try{let n=e.parseHistoryFile(await this.source.readFile(t.ref),this.topic,t);return n.ok?(this.report(n.value.skipped),this.older=n.value.prev,n.value.rows):(this.events.error(Error(`history file ${t.ref} refused (${n.reason}): ${n.detail}`)),`refused`)}catch(e){return this.events.error(e),this.older=t,null}}report(e){e>0&&this.events.skipped(e)}},C={slotMs:e.DEFAULT_NOTE_SLOT_MS,heartbeatMs:e.DEFAULT_NOTE_HEARTBEAT_MS,marginMs:1e3,scanBatch:4,clockLimitMs:3e5,missingReadSpacingMs:s};function w(e){return typeof e.readNote==`function`}var T=class{constructor(e){this.limitMs=e,this.aheadAtMost=1/0,this.trusted=!0}observe(e,t){this.aheadAtMost=Math.min(this.aheadAtMost,t-e)}distrust(){this.trusted=!1}get isTrusted(){return this.trusted}get ms(){let e=Number.isFinite(this.aheadAtMost),t=this.trusted||!e?Math.min(0,this.aheadAtMost):this.aheadAtMost;return Math.max(-this.limitMs,Math.min(this.limitMs,t))}},E=class{constructor(e,t,n){this.source=e,this.now=n,this.counts={found:0,empty:0},this.settings={...C,...t},this.clock=new T(this.settings.clockLimitMs),this.pacer=new c(this.settings.missingReadSpacingMs,n)}lastReadableSlot(t=this.now()){return e.noteSlotOf(t-this.settings.marginMs-this.clock.ms,this.settings.slotMs)-1}readableAt(t){return e.noteSlotEnd(t,this.settings.slotMs)+this.settings.marginMs+this.clock.ms}get lookBackSlots(){return Math.ceil(this.settings.heartbeatMs/this.settings.slotMs)+2}get silenceMs(){return this.settings.heartbeatMs+2*this.settings.slotMs+this.settings.marginMs}async read(t){if(!this.pacer.mayRead(t))return null;let n;try{n=await this.source.readNote(t),n===null&&this.pacer.missed(t)}catch(e){if(!(e instanceof u))throw e;n=null}let r=n===null?null:e.parseSlotNote(n);return r===null?(this.counts.empty++,null):(this.counts.found++,this.clock.observe(r.writtenAt,this.now()),r)}async scan(e,t){for(let n=e;n>=t;n-=this.settings.scanBatch){let e=[];for(let r=n;r>n-this.settings.scanBatch&&r>=t;r--)e.push(r);let r=await Promise.all(e.map(e=>this.read(e))),i=r.findIndex(e=>e!==null);if(i>=0)return{slot:e[i],note:r[i]}}return null}},D={resendAfterMs:1e4,maxResends:5,reactionWindowMs:1e3,isLive:()=>!0},O=class{constructor(e,t,n,r,i=D){this.key=e,this.name=t,this.write=n,this.events=r,this.settings=i,this.outgoing=new Map,this.reactions=new Map,this.stopped=!1}get address(){return this.key.publicKey().address().toHex()}send(t){let{message:n,bytes:r}=e.createChatMessage(this.key,{...t,name:this.name}),i={message:n,bytes:r,writes:0,written:!1,inFlight:!1,timer:null,lastError:null};return this.outgoing.set(n.id,i),this.events.pending(n),this.attempt(i),n}react(t,n,r){let i=JSON.stringify([t,n,r]),a=this.reactions.get(i);if(a){a.taps++;return}let o=setTimeout(()=>{let a=this.reactions.get(i);this.reactions.delete(i),a&&a.taps%2==1&&!this.stopped&&this.send({topic:t,type:e.MessageType.REACTION,target:n,text:r})},this.settings.reactionWindowMs);this.reactions.set(i,{taps:1,timer:o})}confirm(e){if(e.addr!==this.address)return;let t=this.outgoing.get(e.id);t&&(this.clearTimer(t),this.outgoing.delete(e.id),this.events.confirmed(t.message))}retry(e){let t=this.outgoing.get(e);return!t||this.stopped?!1:(this.clearTimer(t),t.writes=0,t.inFlight||this.attempt(t),!0)}isPending(e){return this.outgoing.has(e)}stop(){this.stopped=!0;for(let e of this.outgoing.values())this.clearTimer(e);for(let{timer:e}of this.reactions.values())clearTimeout(e);this.reactions.clear()}attempt(e){!this.stopped&&this.outgoing.has(e.message.id)&&(e.writes++,e.inFlight=!0,this.write(e.bytes).then(()=>{e.inFlight=!1,!e.written&&this.outgoing.has(e.message.id)&&(e.written=!0,this.events.written(e.message)),this.scheduleNext(e)},t=>{e.inFlight=!1,e.lastError=t,this.scheduleNext(e)}))}scheduleNext(e){!this.stopped&&this.outgoing.has(e.message.id)&&(e.timer=setTimeout(()=>{if(e.timer=null,!this.settings.isLive()){this.scheduleNext(e);return}if(e.writes>this.settings.maxResends){this.events.failed(e.message,e.lastError??Error(`never seen on the chat feed`));return}this.attempt(e)},this.settings.resendAfterMs))}clearTimer(e){e.timer&&=(clearTimeout(e.timer),null)}},k=[1e3,2e3,4e3,8e3],A=3;function j(e){return(e.startsWith(`0x`)?e.slice(2):e).toLowerCase()}var M=class{constructor(e,r={}){this.emitter=new o,this.seen=new Set,this.status=null,this.generation=0,this.running=!1,this.starting=null,this.openTimer=null,this.wakeOpen=null;let{user:i,infra:a}=e;this.key=new t.PrivateKey(j(i.privateKey)),this.topic=a.chatTopic,this.nickname=i.nickname,this.openRetryMs=r.openRetryMs??k,this.senderSettings={...D,isLive:()=>this.status===_.LIVE,...r.sender};let s={slotReadMs:a.socReadTimeout??5e3,feedReadMs:a.feedReadTimeout??15e3,writeMs:a.gsocWriteTimeout??1e4},c={...a.noteSlotMs===void 0?{}:{slotMs:a.noteSlotMs},...a.noteHeartbeatMs===void 0?{}:{heartbeatMs:a.noteHeartbeatMs},...r.notes};this.source=r.source??p(new t.Bee(a.beeUrl),j(a.chatAddress),a.chatTopic,s,c.slotMs),this.write=r.write??g(new t.Bee(a.writeUrl??a.beeUrl),a.stamp?j(a.stamp):h,j(a.gsocResourceId),a.gsocTopic,s);let l=r.follower?.now??(()=>Date.now()),u={pollIntervalMs:a.pollingInterval??1e3,...r.follower,now:l},d=this.source;this.notes=w(d)?new E(d,c,l):null,this.follower=new b(this.source,this.topic,{entry:e=>this.show(e),skipped:(e,t,r)=>this.emitter.emit(n.MESSAGE_SKIPPED,{index:e,reason:t,detail:r}),status:e=>this.setStatus(e),error:e=>this.emitter.emit(n.ERROR,e)},u,this.notes),this.history=new S(this.source,this.topic,{skipped:e=>this.emitter.emit(n.MESSAGE_SKIPPED,{index:-1,reason:`history-row`,detail:`${e} rows of a history file`}),error:e=>this.emitter.emit(n.ERROR,e)},this.notes),this.sender=this.makeSender()}getEmitter(){return this.emitter}getStatus(){return this.status}getAddress(){return this.key.publicKey().address().toHex()}start(){if(this.running)return this.starting??Promise.resolve();this.running=!0,this.generation++,this.seen.clear(),this.sender=this.makeSender();let e=this.open(this.generation).finally(()=>{this.starting===e&&(this.starting=null)});return this.starting=e,e}async stop(){this.running=!1,this.generation++,this.openTimer&&=(clearTimeout(this.openTimer),null),this.wakeOpen?.(),this.follower.stop(),this.sender.stop()}async sendMessage(t,n,i){if(n===e.MessageType.REACTION){if(!i)throw Error(`a reaction names the message it reacts to`);return this.sender.react(this.topic,i,t),null}return r(this.sender.send({topic:this.topic,type:n,target:i??``,text:t}))}retrySendMessage(e){return this.sender.retry(typeof e==`string`?e:e.id)}async fetchPreviousMessages(){this.emitter.emit(n.LOADING_PREVIOUS_MESSAGES,!0);try{let e=this.generation,t=await this.history.loadOlder();return e===this.generation?this.showRows(t):[]}finally{this.emitter.emit(n.LOADING_PREVIOUS_MESSAGES,!1)}}hasPreviousMessages(){return this.history.hasOlder()}orderMessages(e){return[...e].sort((e,t)=>e.index>=0&&t.index>=0?e.index-t.index:e.index>=0||t.index>=0?e.index>=0?-1:1:e.timestamp-t.timestamp)}async open(e){this.emitter.emit(n.LOADING_INIT,!0);let t=0;for(;e===this.generation;)try{let t=await this.history.open();if(e!==this.generation)return;this.showRows(t.rows),this.follower.start(t.startAt,t.notes),this.emitter.emit(n.LOADING_INIT,!1);return}catch(r){if(e!==this.generation)return;t++,this.emitter.emit(n.ERROR,r),this.setStatus(_.RECONNECTING),t===A&&this.emitter.emit(n.CRITICAL_ERROR,r),await this.waitToOpen(this.openRetryMs[Math.min(t,this.openRetryMs.length)-1]??8e3)}}waitToOpen(e){return new Promise(t=>{this.wakeOpen=()=>{this.wakeOpen=null,t()},this.openTimer=setTimeout(()=>{this.openTimer=null,this.wakeOpen?.()},e)})}makeSender(){return new O(this.key,this.nickname,this.write,{pending:e=>this.emitter.emit(n.MESSAGE_REQUEST_INITIATED,r(e)),written:e=>this.emitter.emit(n.MESSAGE_REQUEST_UPLOADED,r(e)),failed:(e,t)=>{this.emitter.emit(n.ERROR,t),this.emitter.emit(n.MESSAGE_REQUEST_ERROR,r(e))},confirmed:()=>{}},this.senderSettings)}show(e){this.seen.has(e.seq)||(this.seen.add(e.seq),this.sender.confirm(e.msg),this.emitter.emit(n.MESSAGE_RECEIVED,i(e)))}showRows(e){let t=[];for(let r of e){if(this.seen.has(r.seq))continue;this.seen.add(r.seq),this.sender.confirm(r.msg);let e=i(r);t.push(e),this.emitter.emit(n.MESSAGE_RECEIVED,e)}return t}setStatus(e){e!==this.status&&(this.status=e,this.emitter.emit(n.STATUS,e))}};exports.ChatEmitter=o,exports.ChatMessageError=e.ChatMessageError,exports.DEFAULT_NOTE_HEARTBEAT_MS=e.DEFAULT_NOTE_HEARTBEAT_MS,exports.DEFAULT_NOTE_SLOT_MS=e.DEFAULT_NOTE_SLOT_MS,exports.EVENTS=n,exports.FeedStatus=_,exports.HeadLookupTimeoutError=l,exports.MAX_ENTRY_BYTES=e.MAX_ENTRY_BYTES,exports.MAX_MESSAGE_BYTES=e.MAX_MESSAGE_BYTES,exports.MAX_NAME_CHARACTERS=e.MAX_NAME_CHARACTERS,exports.MAX_NOTE_BYTES=e.MAX_NOTE_BYTES,exports.MAX_TEXT_CHARACTERS=e.MAX_TEXT_CHARACTERS,exports.MAX_TOPIC_CHARACTERS=e.MAX_TOPIC_CHARACTERS,exports.MESSAGE_VERSION=e.MESSAGE_VERSION,exports.MessageType=e.MessageType,exports.NOTE_VERSION=e.NOTE_VERSION,exports.SwarmChat=M,exports.UnreadableGatewayError=y,exports.UnreadableSlotError=u,exports.chatMessageSchema=e.chatMessageSchema,exports.checkChatMessage=e.checkChatMessage,exports.countCharacters=e.countCharacters,exports.createChatMessage=e.createChatMessage,exports.encodeChatMessage=e.encodeChatMessage,exports.encodeSlotNote=e.encodeSlotNote,exports.feedEntrySchema=e.feedEntrySchema,exports.hasValidSignature=e.hasValidSignature,exports.historyFileSchema=e.historyFileSchema,exports.historyLinkSchema=e.historyLinkSchema,exports.historyRowSchema=e.historyRowSchema,exports.newMessageId=e.newMessageId,exports.noteAddress=e.noteAddress,exports.noteIdentifier=e.noteIdentifier,exports.noteIdentifierText=e.noteIdentifierText,exports.noteSlotEnd=e.noteSlotEnd,exports.noteSlotOf=e.noteSlotOf,exports.parseChatMessage=e.parseChatMessage,exports.parseFeedEntry=e.parseFeedEntry,exports.parseHistoryFile=e.parseHistoryFile,exports.parseSlotNote=e.parseSlotNote,exports.signedBytes=e.signedBytes,exports.slotNoteSchema=e.slotNoteSchema;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ChatMessageError as e, DEFAULT_NOTE_HEARTBEAT_MS as t, DEFAULT_NOTE_SLOT_MS as n, MAX_ENTRY_BYTES as r, MAX_MESSAGE_BYTES as i, MAX_NAME_CHARACTERS as a, MAX_NOTE_BYTES as o, MAX_TEXT_CHARACTERS as s, MAX_TOPIC_CHARACTERS as c, MESSAGE_VERSION as l, MessageType as u, NOTE_VERSION as d, chatMessageSchema as f, checkChatMessage as p, countCharacters as m, createChatMessage as h, encodeChatMessage as g, encodeSlotNote as _, feedEntrySchema as
|
|
2
|
-
import { Bee as j, BeeResponseError as M, Identifier as
|
|
1
|
+
import { ChatMessageError as e, DEFAULT_NOTE_HEARTBEAT_MS as t, DEFAULT_NOTE_SLOT_MS as n, MAX_ENTRY_BYTES as r, MAX_MESSAGE_BYTES as i, MAX_NAME_CHARACTERS as a, MAX_NOTE_BYTES as o, MAX_TEXT_CHARACTERS as s, MAX_TOPIC_CHARACTERS as c, MESSAGE_VERSION as l, MessageType as u, NOTE_VERSION as d, chatMessageSchema as f, checkChatMessage as p, countCharacters as m, createChatMessage as h, encodeChatMessage as g, encodeSlotNote as _, feedEntrySchema as ee, hasValidSignature as te, historyFileSchema as ne, historyLinkSchema as v, historyRowSchema as y, newMessageId as b, noteAddress as x, noteIdentifier as S, noteIdentifierText as re, noteSlotEnd as C, noteSlotOf as w, parseChatMessage as T, parseFeedEntry as E, parseHistoryFile as D, parseSlotNote as O, signedBytes as k, slotNoteSchema as A } from "./message.js";
|
|
2
|
+
import { Bee as j, BeeResponseError as M, Identifier as ie, PrivateKey as N, Topic as P } from "@ethersphere/bee-js";
|
|
3
3
|
//#region src/events.ts
|
|
4
|
-
var
|
|
4
|
+
var F = {
|
|
5
5
|
LOADING_INIT: "loadingInit",
|
|
6
6
|
LOADING_PREVIOUS_MESSAGES: "loadingPreviousMessages",
|
|
7
7
|
MESSAGE_RECEIVED: "messageReceived",
|
|
@@ -13,13 +13,13 @@ var I = {
|
|
|
13
13
|
MESSAGE_SKIPPED: "messageSkipped",
|
|
14
14
|
ERROR: "error"
|
|
15
15
|
};
|
|
16
|
-
function
|
|
17
|
-
return
|
|
16
|
+
function I(e) {
|
|
17
|
+
return R(e, -1, e.ts);
|
|
18
18
|
}
|
|
19
|
-
function
|
|
20
|
-
return
|
|
19
|
+
function L(e) {
|
|
20
|
+
return R(e.msg, e.seq, e.at);
|
|
21
21
|
}
|
|
22
|
-
function
|
|
22
|
+
function R(e, t, n) {
|
|
23
23
|
return {
|
|
24
24
|
id: e.id,
|
|
25
25
|
type: e.type,
|
|
@@ -34,7 +34,7 @@ function z(e, t, n) {
|
|
|
34
34
|
sentAt: e.ts
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
-
var
|
|
37
|
+
var z = class {
|
|
38
38
|
constructor() {
|
|
39
39
|
this.listeners = /* @__PURE__ */ new Map(), this.on = (e, t) => {
|
|
40
40
|
this.listeners.set(e, [...this.listeners.get(e) ?? [], t]);
|
|
@@ -51,33 +51,57 @@ var B = class {
|
|
|
51
51
|
console.error(`a listener for ${e} threw`, t);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
}, V = class
|
|
54
|
+
}, B = 2e4, V = class {
|
|
55
|
+
constructor(e, t) {
|
|
56
|
+
this.spacingMs = e, this.now = t, this.missedAt = /* @__PURE__ */ new Map();
|
|
57
|
+
}
|
|
58
|
+
readableAt(e, t = this.now()) {
|
|
59
|
+
let n = this.missedAt.get(e);
|
|
60
|
+
return n === void 0 || this.expired(n, t) ? -Infinity : n + this.spacingMs;
|
|
61
|
+
}
|
|
62
|
+
mayRead(e, t = this.now()) {
|
|
63
|
+
return t >= this.readableAt(e, t);
|
|
64
|
+
}
|
|
65
|
+
missed(e, t = this.now()) {
|
|
66
|
+
for (let [e, n] of this.missedAt) this.expired(n, t) && this.missedAt.delete(e);
|
|
67
|
+
this.missedAt.set(e, t);
|
|
68
|
+
}
|
|
69
|
+
found(e) {
|
|
70
|
+
this.missedAt.delete(e);
|
|
71
|
+
}
|
|
72
|
+
clear() {
|
|
73
|
+
this.missedAt.clear();
|
|
74
|
+
}
|
|
75
|
+
expired(e, t) {
|
|
76
|
+
return e > t || t - e >= this.spacingMs;
|
|
77
|
+
}
|
|
78
|
+
}, H = class extends Error {
|
|
55
79
|
constructor(e) {
|
|
56
80
|
super("the feed head lookup did not answer in time", e), this.name = "HeadLookupTimeoutError";
|
|
57
81
|
}
|
|
58
|
-
},
|
|
82
|
+
}, U = class extends Error {
|
|
59
83
|
constructor(e, t) {
|
|
60
84
|
super(`feed slot ${e} was served but could not be read`, t), this.index = e, this.name = "UnreadableSlotError";
|
|
61
85
|
}
|
|
62
86
|
};
|
|
63
|
-
function
|
|
87
|
+
function W(e) {
|
|
64
88
|
return e instanceof M && (e.status === 404 || e.status === 500);
|
|
65
89
|
}
|
|
66
|
-
async function
|
|
90
|
+
async function G(e, t) {
|
|
67
91
|
try {
|
|
68
92
|
return await t();
|
|
69
93
|
} catch (t) {
|
|
70
|
-
if (
|
|
71
|
-
throw t instanceof M ? t : new
|
|
94
|
+
if (W(t)) return null;
|
|
95
|
+
throw t instanceof M ? t : new U(e, { cause: t });
|
|
72
96
|
}
|
|
73
97
|
}
|
|
74
|
-
function
|
|
75
|
-
let o =
|
|
98
|
+
function K(e, t, r, i, a = n) {
|
|
99
|
+
let o = P.fromString(r), s = (n) => e.feed.makeReader(o, t, q(n));
|
|
76
100
|
return {
|
|
77
|
-
readNote: (n) =>
|
|
78
|
-
readSlot: (e) =>
|
|
101
|
+
readNote: (n) => G(n, async () => (await e.soc.makeReader(t, q(i.slotReadMs)).download(S(r, a, n))).payload.toUint8Array()),
|
|
102
|
+
readSlot: (e) => G(e, async () => (await s(i.slotReadMs).downloadPayload({ index: e })).payload.toUint8Array()),
|
|
79
103
|
async readHead() {
|
|
80
|
-
let n =
|
|
104
|
+
let n = q(i.feedReadMs);
|
|
81
105
|
try {
|
|
82
106
|
let { payload: r, feedIndex: i } = await e.feed.makeReader(o, t, n).downloadPayload();
|
|
83
107
|
return {
|
|
@@ -85,33 +109,34 @@ function G(e, t, r, i, a = n) {
|
|
|
85
109
|
payload: r.toUint8Array()
|
|
86
110
|
};
|
|
87
111
|
} catch (e) {
|
|
88
|
-
if (
|
|
89
|
-
throw n.signal?.aborted ? new
|
|
112
|
+
if (W(e)) return null;
|
|
113
|
+
throw n.signal?.aborted ? new H({ cause: e }) : e;
|
|
90
114
|
}
|
|
91
115
|
},
|
|
92
116
|
async readFile(t) {
|
|
93
|
-
return (await e.data.download(t, void 0,
|
|
117
|
+
return (await e.data.download(t, void 0, q(i.feedReadMs))).toUint8Array();
|
|
94
118
|
}
|
|
95
119
|
};
|
|
96
120
|
}
|
|
97
|
-
function
|
|
121
|
+
function q(e) {
|
|
98
122
|
return { signal: AbortSignal.timeout(e) };
|
|
99
123
|
}
|
|
100
|
-
var
|
|
101
|
-
function
|
|
102
|
-
let a = new
|
|
124
|
+
var ae = "0123456789abcdef".repeat(4);
|
|
125
|
+
function J(e, t, n, r, i) {
|
|
126
|
+
let a = new N(n), o = ie.fromString(r);
|
|
103
127
|
return async (n) => {
|
|
104
|
-
await e.messaging.gsocSend(t, a, o, n, { deferred: !1 },
|
|
128
|
+
await e.messaging.gsocSend(t, a, o, n, { deferred: !1 }, q(i.writeMs));
|
|
105
129
|
};
|
|
106
130
|
}
|
|
107
131
|
//#endregion
|
|
108
132
|
//#region src/follower.ts
|
|
109
|
-
var
|
|
133
|
+
var Y = {
|
|
110
134
|
LIVE: "live",
|
|
111
135
|
RECONNECTING: "reconnecting",
|
|
112
136
|
STALLED: "stalled"
|
|
113
|
-
},
|
|
137
|
+
}, oe = {
|
|
114
138
|
pollIntervalMs: 1e3,
|
|
139
|
+
missingReadSpacingMs: B,
|
|
115
140
|
maxSlotsPerPoll: 16,
|
|
116
141
|
unservedPollsBeforeProbe: 3,
|
|
117
142
|
unreadablePollsBeforeFailure: 3,
|
|
@@ -133,12 +158,12 @@ var J = {
|
|
|
133
158
|
constructor(e) {
|
|
134
159
|
super(`the gateway keeps answering feed slot ${e} with something that is not a chunk`), this.index = e, this.name = "UnreadableGatewayError";
|
|
135
160
|
}
|
|
136
|
-
},
|
|
161
|
+
}, se = class {
|
|
137
162
|
constructor(e, t, n, r = {}, i = null) {
|
|
138
163
|
this.source = e, this.topic = t, this.events = n, this.notes = i, this.next = 0, this.ceiling = null, this.nextNoteSlot = 0, this.lastNoteFoundAt = 0, this.unservedPolls = 0, this.nextProbeAt = 0, this.unreadablePolls = 0, this.gatewayFailures = 0, this.missed = /* @__PURE__ */ new Map(), this.timer = null, this.generation = 0, this.running = !1, this.status = null, this.settings = {
|
|
139
|
-
...
|
|
164
|
+
...oe,
|
|
140
165
|
...r
|
|
141
|
-
};
|
|
166
|
+
}, this.pacer = new V(this.settings.missingReadSpacingMs, this.settings.now);
|
|
142
167
|
}
|
|
143
168
|
get nextIndex() {
|
|
144
169
|
return this.next;
|
|
@@ -150,7 +175,7 @@ var J = {
|
|
|
150
175
|
return this.ceiling !== null;
|
|
151
176
|
}
|
|
152
177
|
start(e, t = null) {
|
|
153
|
-
this.stop(), this.next = e, this.ceiling = t?.newest ?? null, this.nextNoteSlot = t?.nextNoteSlot ?? (this.notes ? this.notes.lastReadableSlot() + 1 : 0), this.lastNoteFoundAt = this.settings.now(), this.resetRefusals(), this.unreadablePolls = 0, this.gatewayFailures = 0, this.missed.clear(), this.status = null, this.running = !0, this.schedule(0);
|
|
178
|
+
this.stop(), this.next = e, this.ceiling = t?.newest ?? null, this.nextNoteSlot = t?.nextNoteSlot ?? (this.notes ? this.notes.lastReadableSlot() + 1 : 0), this.lastNoteFoundAt = this.settings.now(), this.resetRefusals(), this.unreadablePolls = 0, this.gatewayFailures = 0, this.missed.clear(), this.pacer.clear(), this.status = null, this.running = !0, this.schedule(0);
|
|
154
179
|
}
|
|
155
180
|
stop() {
|
|
156
181
|
this.running = !1, this.generation++, this.timer &&= (clearTimeout(this.timer), null);
|
|
@@ -177,7 +202,11 @@ var J = {
|
|
|
177
202
|
}
|
|
178
203
|
}
|
|
179
204
|
untilNextRead() {
|
|
180
|
-
|
|
205
|
+
let e = this.settings.now(), t = (t) => {
|
|
206
|
+
let n = this.pacer.readableAt(t);
|
|
207
|
+
return n > e ? n : e + this.settings.pollIntervalMs;
|
|
208
|
+
}, n = [...this.missed.keys()].map(t);
|
|
209
|
+
return this.notes !== null && n.push(this.notes.readableAt(this.nextNoteSlot)), this.ceiling === null && n.push(t(this.next)), Math.max(0, Math.min(...n) - e);
|
|
181
210
|
}
|
|
182
211
|
async readDueNotes(e, t) {
|
|
183
212
|
let n = this.settings.now(), r = e.lastReadableSlot(n);
|
|
@@ -192,19 +221,24 @@ var J = {
|
|
|
192
221
|
}
|
|
193
222
|
}
|
|
194
223
|
async read(e) {
|
|
224
|
+
let t;
|
|
195
225
|
try {
|
|
196
|
-
|
|
226
|
+
t = await this.source.readSlot(e);
|
|
197
227
|
} catch (e) {
|
|
198
|
-
if (e instanceof
|
|
228
|
+
if (e instanceof U) return "unreadable";
|
|
199
229
|
throw e;
|
|
200
230
|
}
|
|
231
|
+
return t === null ? this.pacer.missed(e) : this.pacer.found(e), t;
|
|
232
|
+
}
|
|
233
|
+
async readUnnamed(e) {
|
|
234
|
+
return this.pacer.mayRead(e) ? this.read(e) : "held";
|
|
201
235
|
}
|
|
202
236
|
async walk(e) {
|
|
203
237
|
for (let t = 0; t < this.settings.maxSlotsPerPoll; t++) {
|
|
204
238
|
let n = this.next;
|
|
205
239
|
if (this.ceiling !== null && n > this.ceiling) return t;
|
|
206
|
-
let r = await this.read(n);
|
|
207
|
-
if (e !== this.generation) return t;
|
|
240
|
+
let r = this.ceiling === null ? await this.readUnnamed(n) : await this.read(n);
|
|
241
|
+
if (e !== this.generation || r === "held") return t;
|
|
208
242
|
if (this.ceiling !== null && (r === null || r === "unreadable")) {
|
|
209
243
|
this.remember(n), this.next = n + 1;
|
|
210
244
|
continue;
|
|
@@ -225,8 +259,8 @@ var J = {
|
|
|
225
259
|
}
|
|
226
260
|
shouldProbe() {
|
|
227
261
|
if (this.unservedPolls !== this.nextProbeAt) return !1;
|
|
228
|
-
let { probeIntervalCapMs: e, pollIntervalMs: t } = this.settings,
|
|
229
|
-
return this.nextProbeAt += Math.min(this.nextProbeAt,
|
|
262
|
+
let { probeIntervalCapMs: e, pollIntervalMs: t, missingReadSpacingMs: n } = this.settings, r = Math.max(1, Math.ceil(e / Math.max(t, n)));
|
|
263
|
+
return this.nextProbeAt += Math.min(this.nextProbeAt, r), !0;
|
|
230
264
|
}
|
|
231
265
|
resetRefusals() {
|
|
232
266
|
this.unservedPolls = 0, this.nextProbeAt = this.settings.unservedPollsBeforeProbe;
|
|
@@ -234,17 +268,17 @@ var J = {
|
|
|
234
268
|
async probePast(e, t) {
|
|
235
269
|
let n = /* @__PURE__ */ new Set([e]);
|
|
236
270
|
for (let r of this.settings.probeDistances) {
|
|
237
|
-
let i = e + r, a = await this.
|
|
271
|
+
let i = e + r, a = await this.readUnnamed(i);
|
|
238
272
|
if (t !== this.generation) return !1;
|
|
239
|
-
if (a === null || a === "unreadable") {
|
|
273
|
+
if (a === null || a === "unreadable" || a === "held") {
|
|
240
274
|
n.add(i);
|
|
241
275
|
continue;
|
|
242
276
|
}
|
|
243
277
|
this.resetRefusals(), this.remember(e);
|
|
244
278
|
for (let r = e + 1; r < i; r++) {
|
|
245
|
-
let e = n.has(r) ? null : await this.
|
|
279
|
+
let e = n.has(r) ? null : await this.readUnnamed(r);
|
|
246
280
|
if (t !== this.generation) return !1;
|
|
247
|
-
e === null || e === "unreadable" ? this.remember(r) : this.accept(r, e);
|
|
281
|
+
e === null || e === "unreadable" || e === "held" ? this.remember(r) : this.accept(r, e);
|
|
248
282
|
}
|
|
249
283
|
return this.next = i + 1, this.accept(i, a), !0;
|
|
250
284
|
}
|
|
@@ -262,7 +296,7 @@ var J = {
|
|
|
262
296
|
async readMissed(e) {
|
|
263
297
|
let t = this.settings.now();
|
|
264
298
|
for (let [e, n] of this.missed) t - n >= this.settings.missedExpiryMs && (this.missed.delete(e), this.events.skipped(e, "unreachable", "not served within the time a stepped-past slot is kept"));
|
|
265
|
-
let n = [...this.missed.keys()].slice(0, this.settings.missedReadsPerPoll);
|
|
299
|
+
let n = [...this.missed.keys()].filter((e) => this.pacer.mayRead(e, t)).slice(0, this.settings.missedReadsPerPoll);
|
|
266
300
|
for (let t of n) {
|
|
267
301
|
let n = await this.read(t);
|
|
268
302
|
if (e !== this.generation) return;
|
|
@@ -271,16 +305,16 @@ var J = {
|
|
|
271
305
|
}
|
|
272
306
|
}
|
|
273
307
|
accept(e, t) {
|
|
274
|
-
let n =
|
|
308
|
+
let n = E(t, e, this.topic);
|
|
275
309
|
n.ok ? (this.notes?.clock.observe(n.value.at, this.settings.now()), this.events.entry(n.value)) : this.events.skipped(e, n.reason, n.detail);
|
|
276
310
|
}
|
|
277
311
|
gatewayFailed(e) {
|
|
278
|
-
this.gatewayFailures++, this.setStatus(
|
|
312
|
+
this.gatewayFailures++, this.setStatus(Y.RECONNECTING), this.events.error(e);
|
|
279
313
|
}
|
|
280
314
|
gatewayAnswered() {
|
|
281
315
|
this.gatewayFailures = 0;
|
|
282
316
|
let e = this.settings.now(), t = [...this.missed.values()].some((t) => e - t >= this.settings.stallAfterMs);
|
|
283
|
-
this.setStatus(t ?
|
|
317
|
+
this.setStatus(t ? Y.STALLED : Y.LIVE);
|
|
284
318
|
}
|
|
285
319
|
backoffMs() {
|
|
286
320
|
let { pollIntervalMs: e, backoffCapMs: t, random: n } = this.settings, r = Math.min(t, e * 2 ** this.gatewayFailures);
|
|
@@ -289,7 +323,7 @@ var J = {
|
|
|
289
323
|
setStatus(e) {
|
|
290
324
|
e !== this.status && (this.status = e, this.events.status(e));
|
|
291
325
|
}
|
|
292
|
-
},
|
|
326
|
+
}, ce = 16, le = class {
|
|
293
327
|
constructor(e, t, n, r = null) {
|
|
294
328
|
this.source = e, this.topic = t, this.events = n, this.notes = r, this.older = null, this.loading = null;
|
|
295
329
|
}
|
|
@@ -334,7 +368,7 @@ var J = {
|
|
|
334
368
|
try {
|
|
335
369
|
return await this.source.readHead();
|
|
336
370
|
} catch (e) {
|
|
337
|
-
if (e instanceof
|
|
371
|
+
if (e instanceof H) return this.events.error(e), null;
|
|
338
372
|
throw e;
|
|
339
373
|
}
|
|
340
374
|
}
|
|
@@ -349,30 +383,30 @@ var J = {
|
|
|
349
383
|
async loadOlderOnce() {
|
|
350
384
|
let e = this.older;
|
|
351
385
|
if (e === null) return [];
|
|
352
|
-
let t = await this.source.readFile(e.ref), n =
|
|
386
|
+
let t = await this.source.readFile(e.ref), n = D(t, this.topic, e);
|
|
353
387
|
if (!n.ok) throw this.older = null, Error(`history file ${e.ref} refused (${n.reason}): ${n.detail}`);
|
|
354
388
|
return this.report(n.value.skipped), this.older = n.value.prev, n.value.rows;
|
|
355
389
|
}
|
|
356
390
|
async newestLink(e, t) {
|
|
357
391
|
let n = t === void 0 ? e : e - 1;
|
|
358
392
|
if (t !== void 0) {
|
|
359
|
-
let n =
|
|
393
|
+
let n = E(t, e, this.topic);
|
|
360
394
|
if (n.ok) return n.value.history;
|
|
361
395
|
}
|
|
362
|
-
for (let t = n; t >= Math.max(0, e -
|
|
396
|
+
for (let t = n; t >= Math.max(0, e - ce); t--) {
|
|
363
397
|
let e = await this.source.readSlot(t).catch((e) => {
|
|
364
|
-
if (e instanceof
|
|
398
|
+
if (e instanceof U) return null;
|
|
365
399
|
throw e;
|
|
366
400
|
});
|
|
367
401
|
if (e === null) continue;
|
|
368
|
-
let n =
|
|
402
|
+
let n = E(e, t, this.topic);
|
|
369
403
|
if (n.ok) return n.value.history;
|
|
370
404
|
}
|
|
371
405
|
return null;
|
|
372
406
|
}
|
|
373
407
|
async readFile(e) {
|
|
374
408
|
try {
|
|
375
|
-
let t =
|
|
409
|
+
let t = D(await this.source.readFile(e.ref), this.topic, e);
|
|
376
410
|
return t.ok ? (this.report(t.value.skipped), this.older = t.value.prev, t.value.rows) : (this.events.error(/* @__PURE__ */ Error(`history file ${e.ref} refused (${t.reason}): ${t.detail}`)), "refused");
|
|
377
411
|
} catch (t) {
|
|
378
412
|
return this.events.error(t), this.older = e, null;
|
|
@@ -381,17 +415,18 @@ var J = {
|
|
|
381
415
|
report(e) {
|
|
382
416
|
e > 0 && this.events.skipped(e);
|
|
383
417
|
}
|
|
384
|
-
},
|
|
418
|
+
}, ue = {
|
|
385
419
|
slotMs: n,
|
|
386
420
|
heartbeatMs: t,
|
|
387
421
|
marginMs: 1e3,
|
|
388
422
|
scanBatch: 4,
|
|
389
|
-
clockLimitMs: 3e5
|
|
423
|
+
clockLimitMs: 3e5,
|
|
424
|
+
missingReadSpacingMs: B
|
|
390
425
|
};
|
|
391
|
-
function
|
|
426
|
+
function Z(e) {
|
|
392
427
|
return typeof e.readNote == "function";
|
|
393
428
|
}
|
|
394
|
-
var
|
|
429
|
+
var de = class {
|
|
395
430
|
constructor(e) {
|
|
396
431
|
this.limitMs = e, this.aheadAtMost = Infinity, this.trusted = !0;
|
|
397
432
|
}
|
|
@@ -408,21 +443,21 @@ var ue = class {
|
|
|
408
443
|
let e = Number.isFinite(this.aheadAtMost), t = this.trusted || !e ? Math.min(0, this.aheadAtMost) : this.aheadAtMost;
|
|
409
444
|
return Math.max(-this.limitMs, Math.min(this.limitMs, t));
|
|
410
445
|
}
|
|
411
|
-
},
|
|
446
|
+
}, fe = class {
|
|
412
447
|
constructor(e, t, n) {
|
|
413
448
|
this.source = e, this.now = n, this.counts = {
|
|
414
449
|
found: 0,
|
|
415
450
|
empty: 0
|
|
416
451
|
}, this.settings = {
|
|
417
|
-
...
|
|
452
|
+
...ue,
|
|
418
453
|
...t
|
|
419
|
-
}, this.clock = new
|
|
454
|
+
}, this.clock = new de(this.settings.clockLimitMs), this.pacer = new V(this.settings.missingReadSpacingMs, n);
|
|
420
455
|
}
|
|
421
456
|
lastReadableSlot(e = this.now()) {
|
|
422
|
-
return
|
|
457
|
+
return w(e - this.settings.marginMs - this.clock.ms, this.settings.slotMs) - 1;
|
|
423
458
|
}
|
|
424
459
|
readableAt(e) {
|
|
425
|
-
return
|
|
460
|
+
return C(e, this.settings.slotMs) + this.settings.marginMs + this.clock.ms;
|
|
426
461
|
}
|
|
427
462
|
get lookBackSlots() {
|
|
428
463
|
return Math.ceil(this.settings.heartbeatMs / this.settings.slotMs) + 2;
|
|
@@ -431,14 +466,15 @@ var ue = class {
|
|
|
431
466
|
return this.settings.heartbeatMs + 2 * this.settings.slotMs + this.settings.marginMs;
|
|
432
467
|
}
|
|
433
468
|
async read(e) {
|
|
469
|
+
if (!this.pacer.mayRead(e)) return null;
|
|
434
470
|
let t;
|
|
435
471
|
try {
|
|
436
|
-
t = await this.source.readNote(e);
|
|
472
|
+
t = await this.source.readNote(e), t === null && this.pacer.missed(e);
|
|
437
473
|
} catch (e) {
|
|
438
|
-
if (!(e instanceof
|
|
474
|
+
if (!(e instanceof U)) throw e;
|
|
439
475
|
t = null;
|
|
440
476
|
}
|
|
441
|
-
let n = t === null ? null :
|
|
477
|
+
let n = t === null ? null : O(t);
|
|
442
478
|
return n === null ? (this.counts.empty++, null) : (this.counts.found++, this.clock.observe(n.writtenAt, this.now()), n);
|
|
443
479
|
}
|
|
444
480
|
async scan(e, t) {
|
|
@@ -458,7 +494,7 @@ var ue = class {
|
|
|
458
494
|
maxResends: 5,
|
|
459
495
|
reactionWindowMs: 1e3,
|
|
460
496
|
isLive: () => !0
|
|
461
|
-
},
|
|
497
|
+
}, pe = class {
|
|
462
498
|
constructor(e, t, n, r, i = Q) {
|
|
463
499
|
this.key = e, this.name = t, this.write = n, this.events = r, this.settings = i, this.outgoing = /* @__PURE__ */ new Map(), this.reactions = /* @__PURE__ */ new Map(), this.stopped = !1;
|
|
464
500
|
}
|
|
@@ -545,22 +581,22 @@ var ue = class {
|
|
|
545
581
|
clearTimer(e) {
|
|
546
582
|
e.timer &&= (clearTimeout(e.timer), null);
|
|
547
583
|
}
|
|
548
|
-
},
|
|
584
|
+
}, me = [
|
|
549
585
|
1e3,
|
|
550
586
|
2e3,
|
|
551
587
|
4e3,
|
|
552
588
|
8e3
|
|
553
|
-
],
|
|
589
|
+
], he = 3;
|
|
554
590
|
function $(e) {
|
|
555
591
|
return (e.startsWith("0x") ? e.slice(2) : e).toLowerCase();
|
|
556
592
|
}
|
|
557
|
-
var
|
|
593
|
+
var ge = class {
|
|
558
594
|
constructor(e, t = {}) {
|
|
559
|
-
this.emitter = new
|
|
595
|
+
this.emitter = new z(), this.seen = /* @__PURE__ */ new Set(), this.status = null, this.generation = 0, this.running = !1, this.starting = null, this.openTimer = null, this.wakeOpen = null;
|
|
560
596
|
let { user: n, infra: r } = e;
|
|
561
|
-
this.key = new
|
|
597
|
+
this.key = new N($(n.privateKey)), this.topic = r.chatTopic, this.nickname = n.nickname, this.openRetryMs = t.openRetryMs ?? me, this.senderSettings = {
|
|
562
598
|
...Q,
|
|
563
|
-
isLive: () => this.status ===
|
|
599
|
+
isLive: () => this.status === Y.LIVE,
|
|
564
600
|
...t.sender
|
|
565
601
|
};
|
|
566
602
|
let i = {
|
|
@@ -572,28 +608,28 @@ var me = class {
|
|
|
572
608
|
...r.noteHeartbeatMs === void 0 ? {} : { heartbeatMs: r.noteHeartbeatMs },
|
|
573
609
|
...t.notes
|
|
574
610
|
};
|
|
575
|
-
this.source = t.source ??
|
|
611
|
+
this.source = t.source ?? K(new j(r.beeUrl), $(r.chatAddress), r.chatTopic, i, a.slotMs), this.write = t.write ?? J(new j(r.writeUrl ?? r.beeUrl), r.stamp ? $(r.stamp) : ae, $(r.gsocResourceId), r.gsocTopic, i);
|
|
576
612
|
let o = t.follower?.now ?? (() => Date.now()), s = {
|
|
577
613
|
pollIntervalMs: r.pollingInterval ?? 1e3,
|
|
578
614
|
...t.follower,
|
|
579
615
|
now: o
|
|
580
616
|
}, c = this.source;
|
|
581
|
-
this.notes =
|
|
617
|
+
this.notes = Z(c) ? new fe(c, a, o) : null, this.follower = new se(this.source, this.topic, {
|
|
582
618
|
entry: (e) => this.show(e),
|
|
583
|
-
skipped: (e, t, n) => this.emitter.emit(
|
|
619
|
+
skipped: (e, t, n) => this.emitter.emit(F.MESSAGE_SKIPPED, {
|
|
584
620
|
index: e,
|
|
585
621
|
reason: t,
|
|
586
622
|
detail: n
|
|
587
623
|
}),
|
|
588
624
|
status: (e) => this.setStatus(e),
|
|
589
|
-
error: (e) => this.emitter.emit(
|
|
590
|
-
}, s, this.notes), this.history = new
|
|
591
|
-
skipped: (e) => this.emitter.emit(
|
|
625
|
+
error: (e) => this.emitter.emit(F.ERROR, e)
|
|
626
|
+
}, s, this.notes), this.history = new le(this.source, this.topic, {
|
|
627
|
+
skipped: (e) => this.emitter.emit(F.MESSAGE_SKIPPED, {
|
|
592
628
|
index: -1,
|
|
593
629
|
reason: "history-row",
|
|
594
630
|
detail: `${e} rows of a history file`
|
|
595
631
|
}),
|
|
596
|
-
error: (e) => this.emitter.emit(
|
|
632
|
+
error: (e) => this.emitter.emit(F.ERROR, e)
|
|
597
633
|
}, this.notes), this.sender = this.makeSender();
|
|
598
634
|
}
|
|
599
635
|
getEmitter() {
|
|
@@ -621,7 +657,7 @@ var me = class {
|
|
|
621
657
|
if (!n) throw Error("a reaction names the message it reacts to");
|
|
622
658
|
return this.sender.react(this.topic, n, e), null;
|
|
623
659
|
}
|
|
624
|
-
return
|
|
660
|
+
return I(this.sender.send({
|
|
625
661
|
topic: this.topic,
|
|
626
662
|
type: t,
|
|
627
663
|
target: n ?? "",
|
|
@@ -632,12 +668,12 @@ var me = class {
|
|
|
632
668
|
return this.sender.retry(typeof e == "string" ? e : e.id);
|
|
633
669
|
}
|
|
634
670
|
async fetchPreviousMessages() {
|
|
635
|
-
this.emitter.emit(
|
|
671
|
+
this.emitter.emit(F.LOADING_PREVIOUS_MESSAGES, !0);
|
|
636
672
|
try {
|
|
637
673
|
let e = this.generation, t = await this.history.loadOlder();
|
|
638
674
|
return e === this.generation ? this.showRows(t) : [];
|
|
639
675
|
} finally {
|
|
640
|
-
this.emitter.emit(
|
|
676
|
+
this.emitter.emit(F.LOADING_PREVIOUS_MESSAGES, !1);
|
|
641
677
|
}
|
|
642
678
|
}
|
|
643
679
|
hasPreviousMessages() {
|
|
@@ -647,16 +683,16 @@ var me = class {
|
|
|
647
683
|
return [...e].sort((e, t) => e.index >= 0 && t.index >= 0 ? e.index - t.index : e.index >= 0 || t.index >= 0 ? e.index >= 0 ? -1 : 1 : e.timestamp - t.timestamp);
|
|
648
684
|
}
|
|
649
685
|
async open(e) {
|
|
650
|
-
this.emitter.emit(
|
|
686
|
+
this.emitter.emit(F.LOADING_INIT, !0);
|
|
651
687
|
let t = 0;
|
|
652
688
|
for (; e === this.generation;) try {
|
|
653
689
|
let t = await this.history.open();
|
|
654
690
|
if (e !== this.generation) return;
|
|
655
|
-
this.showRows(t.rows), this.follower.start(t.startAt, t.notes), this.emitter.emit(
|
|
691
|
+
this.showRows(t.rows), this.follower.start(t.startAt, t.notes), this.emitter.emit(F.LOADING_INIT, !1);
|
|
656
692
|
return;
|
|
657
693
|
} catch (n) {
|
|
658
694
|
if (e !== this.generation) return;
|
|
659
|
-
t++, this.emitter.emit(
|
|
695
|
+
t++, this.emitter.emit(F.ERROR, n), this.setStatus(Y.RECONNECTING), t === he && this.emitter.emit(F.CRITICAL_ERROR, n), await this.waitToOpen(this.openRetryMs[Math.min(t, this.openRetryMs.length) - 1] ?? 8e3);
|
|
660
696
|
}
|
|
661
697
|
}
|
|
662
698
|
waitToOpen(e) {
|
|
@@ -669,31 +705,31 @@ var me = class {
|
|
|
669
705
|
});
|
|
670
706
|
}
|
|
671
707
|
makeSender() {
|
|
672
|
-
return new
|
|
673
|
-
pending: (e) => this.emitter.emit(
|
|
674
|
-
written: (e) => this.emitter.emit(
|
|
708
|
+
return new pe(this.key, this.nickname, this.write, {
|
|
709
|
+
pending: (e) => this.emitter.emit(F.MESSAGE_REQUEST_INITIATED, I(e)),
|
|
710
|
+
written: (e) => this.emitter.emit(F.MESSAGE_REQUEST_UPLOADED, I(e)),
|
|
675
711
|
failed: (e, t) => {
|
|
676
|
-
this.emitter.emit(
|
|
712
|
+
this.emitter.emit(F.ERROR, t), this.emitter.emit(F.MESSAGE_REQUEST_ERROR, I(e));
|
|
677
713
|
},
|
|
678
714
|
confirmed: () => {}
|
|
679
715
|
}, this.senderSettings);
|
|
680
716
|
}
|
|
681
717
|
show(e) {
|
|
682
|
-
this.seen.has(e.seq) || (this.seen.add(e.seq), this.sender.confirm(e.msg), this.emitter.emit(
|
|
718
|
+
this.seen.has(e.seq) || (this.seen.add(e.seq), this.sender.confirm(e.msg), this.emitter.emit(F.MESSAGE_RECEIVED, L(e)));
|
|
683
719
|
}
|
|
684
720
|
showRows(e) {
|
|
685
721
|
let t = [];
|
|
686
722
|
for (let n of e) {
|
|
687
723
|
if (this.seen.has(n.seq)) continue;
|
|
688
724
|
this.seen.add(n.seq), this.sender.confirm(n.msg);
|
|
689
|
-
let e =
|
|
690
|
-
t.push(e), this.emitter.emit(
|
|
725
|
+
let e = L(n);
|
|
726
|
+
t.push(e), this.emitter.emit(F.MESSAGE_RECEIVED, e);
|
|
691
727
|
}
|
|
692
728
|
return t;
|
|
693
729
|
}
|
|
694
730
|
setStatus(e) {
|
|
695
|
-
e !== this.status && (this.status = e, this.emitter.emit(
|
|
731
|
+
e !== this.status && (this.status = e, this.emitter.emit(F.STATUS, e));
|
|
696
732
|
}
|
|
697
733
|
};
|
|
698
734
|
//#endregion
|
|
699
|
-
export {
|
|
735
|
+
export { z as ChatEmitter, e as ChatMessageError, t as DEFAULT_NOTE_HEARTBEAT_MS, n as DEFAULT_NOTE_SLOT_MS, F as EVENTS, Y as FeedStatus, H as HeadLookupTimeoutError, r as MAX_ENTRY_BYTES, i as MAX_MESSAGE_BYTES, a as MAX_NAME_CHARACTERS, o as MAX_NOTE_BYTES, s as MAX_TEXT_CHARACTERS, c as MAX_TOPIC_CHARACTERS, l as MESSAGE_VERSION, u as MessageType, d as NOTE_VERSION, ge as SwarmChat, X as UnreadableGatewayError, U as UnreadableSlotError, f as chatMessageSchema, p as checkChatMessage, m as countCharacters, h as createChatMessage, g as encodeChatMessage, _ as encodeSlotNote, ee as feedEntrySchema, te as hasValidSignature, ne as historyFileSchema, v as historyLinkSchema, y as historyRowSchema, b as newMessageId, x as noteAddress, S as noteIdentifier, re as noteIdentifierText, C as noteSlotEnd, w as noteSlotOf, T as parseChatMessage, E as parseFeedEntry, D as parseHistoryFile, O as parseSlotNote, k as signedBytes, A as slotNoteSchema };
|
package/dist/notes.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface NoteSettings {
|
|
|
11
11
|
scanBatch: number;
|
|
12
12
|
/** The furthest the clock correction goes either way. */
|
|
13
13
|
clockLimitMs: number;
|
|
14
|
+
/** The shortest wait between two reads of a note that came back missing, see `pacer.ts` for why. */
|
|
15
|
+
missingReadSpacingMs: number;
|
|
14
16
|
}
|
|
15
17
|
export declare const DEFAULT_NOTE_SETTINGS: NoteSettings;
|
|
16
18
|
/** A note that was found, and the time slot it was found at. */
|
|
@@ -61,6 +63,7 @@ export declare class NoteReader {
|
|
|
61
63
|
empty: number;
|
|
62
64
|
};
|
|
63
65
|
readonly settings: NoteSettings;
|
|
66
|
+
private readonly pacer;
|
|
64
67
|
constructor(source: NoteSource, settings: Partial<NoteSettings>, now: () => number);
|
|
65
68
|
/** The newest time slot whose note may be asked for at `now`: over, and the margin past, by the server's clock. */
|
|
66
69
|
lastReadableSlot(now?: number): number;
|
|
@@ -70,7 +73,11 @@ export declare class NoteReader {
|
|
|
70
73
|
get lookBackSlots(): number;
|
|
71
74
|
/** How long a follower goes without finding a note before it stops trusting its clock. */
|
|
72
75
|
get silenceMs(): number;
|
|
73
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* One slot's note, or null when there is none to be had. Rejects only when the gateway fails. A note that came back
|
|
78
|
+
* missing is not asked for again within the spacing, which only an opening tried again or a clock found to run
|
|
79
|
+
* ahead would do, and is then null without asking.
|
|
80
|
+
*/
|
|
74
81
|
read(slot: number): Promise<SlotNote | null>;
|
|
75
82
|
/**
|
|
76
83
|
* The newest note from slot `newest` back to slot `oldest`, read newest first, `scanBatch` at a time, stopping at
|
package/dist/pacer.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shortest wait between two reads of one address that came back missing: three reads a minute at most.
|
|
3
|
+
*
|
|
4
|
+
* Bee answers the first few reads of a chunk it cannot find 404, and puts every peer it asked on a skip list for that
|
|
5
|
+
* chunk for a minute (`ethersphere/bee`, `pkg/retrieval/retrieval.go`, `errSkip` and `skiplistDur = time.Minute`).
|
|
6
|
+
* Once no peer is left it answers 500 at once, because "no peer left" is not `storage.ErrNotFound`
|
|
7
|
+
* (`pkg/api/chunk.go`). A chunk written while its address is on that list stays unreadable on that node for up to a
|
|
8
|
+
* minute. Measured on swarm-dev on 2026-10-12: an address read every second for 20 s and then written was readable
|
|
9
|
+
* after 33 to 36 s, one nobody had asked for at once. About four reads of a missing address exhaust the list, so one
|
|
10
|
+
* read every twenty seconds stays under it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_MISSING_READ_SPACING_MS = 20000;
|
|
13
|
+
/**
|
|
14
|
+
* Holds back a read of a slot whose address came back missing until `spacingMs` has passed since that answer. A slot
|
|
15
|
+
* that was found, or never read, may be read at once. A follower keeps one for feed slots and a note reader one for
|
|
16
|
+
* time slots.
|
|
17
|
+
*/
|
|
18
|
+
export declare class MissingReadPacer {
|
|
19
|
+
private readonly spacingMs;
|
|
20
|
+
private readonly now;
|
|
21
|
+
private readonly missedAt;
|
|
22
|
+
constructor(spacingMs: number, now: () => number);
|
|
23
|
+
/** The local time from which `slot` may be read again, which is in the past for an address not held back. */
|
|
24
|
+
readableAt(slot: number, now?: number): number;
|
|
25
|
+
mayRead(slot: number, now?: number): boolean;
|
|
26
|
+
missed(slot: number, now?: number): void;
|
|
27
|
+
found(slot: number): void;
|
|
28
|
+
clear(): void;
|
|
29
|
+
/** A miss recorded after `now` was recorded before the clock was set back, and holds nothing. */
|
|
30
|
+
private expired;
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solarpunkltd/swarm-chat-js",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.3.0",
|
|
4
4
|
"description": "JS library for chatting on Swarm",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chat",
|
|
@@ -36,15 +36,6 @@
|
|
|
36
36
|
"require": "./dist/message.cjs"
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "vite build && tsc -p tsconfig.build.json",
|
|
41
|
-
"prepack": "npm run build",
|
|
42
|
-
"lint": "oxlint --type-aware .",
|
|
43
|
-
"format": "oxfmt .",
|
|
44
|
-
"format:check": "oxfmt --check .",
|
|
45
|
-
"typecheck": "tsc --noEmit",
|
|
46
|
-
"test": "vitest run"
|
|
47
|
-
},
|
|
48
39
|
"dependencies": {
|
|
49
40
|
"zod": "4.6.5"
|
|
50
41
|
},
|
|
@@ -64,5 +55,12 @@
|
|
|
64
55
|
"engines": {
|
|
65
56
|
"node": ">=24"
|
|
66
57
|
},
|
|
67
|
-
"
|
|
68
|
-
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "vite build && tsc -p tsconfig.build.json",
|
|
60
|
+
"lint": "oxlint --type-aware .",
|
|
61
|
+
"format": "oxfmt .",
|
|
62
|
+
"format:check": "oxfmt --check .",
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
64
|
+
"test": "vitest run"
|
|
65
|
+
}
|
|
66
|
+
}
|