@solarpunkltd/swarm-chat-js 7.0.0 → 7.1.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 +15 -14
- package/dist/chat.d.ts +6 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,20 +64,21 @@ await chat.stop();
|
|
|
64
64
|
|
|
65
65
|
## Settings
|
|
66
66
|
|
|
67
|
-
| Setting | Meaning
|
|
68
|
-
| ------------------------ |
|
|
69
|
-
| `user.privateKey` | Signs every message. A viewer that only reads may pass any key.
|
|
70
|
-
| `user.nickname` | The name on every message, 1 to 20 characters. Nothing can be sent without one.
|
|
71
|
-
| `infra.beeUrl` | The Bee node or gateway
|
|
72
|
-
| `infra.
|
|
73
|
-
| `infra.
|
|
74
|
-
| `infra.
|
|
75
|
-
| `infra.
|
|
76
|
-
| `infra.
|
|
77
|
-
| `infra.
|
|
78
|
-
| `infra.
|
|
79
|
-
| `infra.
|
|
80
|
-
| `infra.
|
|
67
|
+
| Setting | Meaning |
|
|
68
|
+
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
69
|
+
| `user.privateKey` | Signs every message. A viewer that only reads may pass any key. |
|
|
70
|
+
| `user.nickname` | The name on every message, 1 to 20 characters. Nothing can be sent without one. |
|
|
71
|
+
| `infra.beeUrl` | The Bee node or gateway the chat is read through, and written through when `infra.writeUrl` is not set. |
|
|
72
|
+
| `infra.writeUrl` | Optional. The Bee node or gateway the inbox writes go through, such as a write gateway that stamps them, when they go somewhere other than the reads. |
|
|
73
|
+
| `infra.stamp` | The batch the inbox writes are stamped with. Left out for a gateway that stamps writes itself. |
|
|
74
|
+
| `infra.gsocTopic` | The inbox's identifier string, the same for every chat. |
|
|
75
|
+
| `infra.gsocResourceId` | The mined key every sender signs inbox writes with. The server's operator mines it. |
|
|
76
|
+
| `infra.chatTopic` | The chat's topic, which every message carries and the feed is named by. |
|
|
77
|
+
| `infra.chatAddress` | The server's feed owner address. |
|
|
78
|
+
| `infra.pollingInterval` | How often a reader polls at the live edge, 1,000 ms by default. |
|
|
79
|
+
| `infra.socReadTimeout` | One feed slot read, 5,000 ms by default. |
|
|
80
|
+
| `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. |
|
|
81
|
+
| `infra.gsocWriteTimeout` | One inbox write, 10,000 ms by default. |
|
|
81
82
|
|
|
82
83
|
## Methods
|
|
83
84
|
|
package/dist/chat.d.ts
CHANGED
|
@@ -11,8 +11,13 @@ export interface ChatSettings {
|
|
|
11
11
|
nickname: string;
|
|
12
12
|
};
|
|
13
13
|
infra: {
|
|
14
|
-
/** The Bee node or gateway
|
|
14
|
+
/** The Bee node or gateway the chat is read through, and written through too when `writeUrl` is not set. */
|
|
15
15
|
beeUrl: string;
|
|
16
|
+
/**
|
|
17
|
+
* The Bee node or gateway the inbox writes go through, when they go somewhere other than the reads, such as a write
|
|
18
|
+
* gateway that stamps them. Left out, writes go to `beeUrl`.
|
|
19
|
+
*/
|
|
20
|
+
writeUrl?: string;
|
|
16
21
|
/** The batch the inbox writes are stamped with. Left out for a gateway that stamps writes itself. */
|
|
17
22
|
stamp?: string;
|
|
18
23
|
/** The inbox's identifier string, one for every chat, as the server listens on it. */
|
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(e,n,r,i){let a=t.Topic.fromString(r),o=t=>e.feed.makeReader(a,n,f(t));return{readSlot:e=>u(e,async()=>(await o(i.slotReadMs).downloadPayload({index:e})).payload.toUint8Array()),async readHead(){let t=f(i.feedReadMs);try{let{payload:r,feedIndex:i}=await e.feed.makeReader(a,n,t).downloadPayload();return{index:Number(i.toBigInt()),payload:r.toUint8Array()}}catch(e){if(l(e))return null;throw t.signal?.aborted?new s({cause:e}):e}},async readFile(t){return(await e.data.download(t,void 0,f(i.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={}){this.source=e,this.topic=t,this.events=n,this.next=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()]}start(e){this.stop(),this.next=e,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{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.settings.pollIntervalMs)}}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,r=await this.read(n);if(e!==this.generation)return t;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.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){this.source=e,this.topic=t,this.events=n,this.older=null,this.loading=null}async open(){this.older=null;let e=await this.readHead();if(e===null)return{startAt:0,rows:[]};let t=await this.newestLink(e.index,e.payload);if(t===null)return{startAt:0,rows:[]};let n=await this.readFile(t);return n===`refused`?{startAt:0,rows:[]}:{startAt:t.toSeq+1,rows:n??[]}}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=e.parseFeedEntry(n,t,this.topic);if(r.ok)return r.value.history;for(let n=t-1;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={resendAfterMs:1e4,maxResends:5,reactionWindowMs:1e3,isLive:()=>!0},S=class{constructor(e,t,n,r,i=x){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)}},C=[1e3,2e3,4e3,8e3],w=3;function T(e){return(e.startsWith(`0x`)?e.slice(2):e).toLowerCase()}var E=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(T(i.privateKey)),this.topic=a.chatTopic,this.nickname=i.nickname,this.openRetryMs=r.openRetryMs??C,this.senderSettings={...x,isLive:()=>this.status===h.LIVE,...r.sender};let s={slotReadMs:a.socReadTimeout??5e3,feedReadMs:a.feedReadTimeout??15e3,writeMs:a.gsocWriteTimeout??1e4},c=!r.source||!r.write?new t.Bee(a.beeUrl):null;this.source=r.source??d(c,T(a.chatAddress),a.chatTopic,s),this.write=r.write??m(c,a.stamp?T(a.stamp):p,T(a.gsocResourceId),a.gsocTopic,s),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)},{pollIntervalMs:a.pollingInterval??1e3,...r.follower}),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.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),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===w&&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 S(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.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_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.SwarmChat=E,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.feedEntrySchema=e.feedEntrySchema,exports.hasValidSignature=e.hasValidSignature,exports.historyFileSchema=e.historyFileSchema,exports.historyLinkSchema=e.historyLinkSchema,exports.historyRowSchema=e.historyRowSchema,exports.newMessageId=e.newMessageId,exports.parseChatMessage=e.parseChatMessage,exports.parseFeedEntry=e.parseFeedEntry,exports.parseHistoryFile=e.parseHistoryFile,exports.signedBytes=e.signedBytes;
|
|
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(e,n,r,i){let a=t.Topic.fromString(r),o=t=>e.feed.makeReader(a,n,f(t));return{readSlot:e=>u(e,async()=>(await o(i.slotReadMs).downloadPayload({index:e})).payload.toUint8Array()),async readHead(){let t=f(i.feedReadMs);try{let{payload:r,feedIndex:i}=await e.feed.makeReader(a,n,t).downloadPayload();return{index:Number(i.toBigInt()),payload:r.toUint8Array()}}catch(e){if(l(e))return null;throw t.signal?.aborted?new s({cause:e}):e}},async readFile(t){return(await e.data.download(t,void 0,f(i.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={}){this.source=e,this.topic=t,this.events=n,this.next=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()]}start(e){this.stop(),this.next=e,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{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.settings.pollIntervalMs)}}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,r=await this.read(n);if(e!==this.generation)return t;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.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){this.source=e,this.topic=t,this.events=n,this.older=null,this.loading=null}async open(){this.older=null;let e=await this.readHead();if(e===null)return{startAt:0,rows:[]};let t=await this.newestLink(e.index,e.payload);if(t===null)return{startAt:0,rows:[]};let n=await this.readFile(t);return n===`refused`?{startAt:0,rows:[]}:{startAt:t.toSeq+1,rows:n??[]}}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=e.parseFeedEntry(n,t,this.topic);if(r.ok)return r.value.history;for(let n=t-1;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={resendAfterMs:1e4,maxResends:5,reactionWindowMs:1e3,isLive:()=>!0},S=class{constructor(e,t,n,r,i=x){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)}},C=[1e3,2e3,4e3,8e3],w=3;function T(e){return(e.startsWith(`0x`)?e.slice(2):e).toLowerCase()}var E=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(T(i.privateKey)),this.topic=a.chatTopic,this.nickname=i.nickname,this.openRetryMs=r.openRetryMs??C,this.senderSettings={...x,isLive:()=>this.status===h.LIVE,...r.sender};let s={slotReadMs:a.socReadTimeout??5e3,feedReadMs:a.feedReadTimeout??15e3,writeMs:a.gsocWriteTimeout??1e4};this.source=r.source??d(new t.Bee(a.beeUrl),T(a.chatAddress),a.chatTopic,s),this.write=r.write??m(new t.Bee(a.writeUrl??a.beeUrl),a.stamp?T(a.stamp):p,T(a.gsocResourceId),a.gsocTopic,s),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)},{pollIntervalMs:a.pollingInterval??1e3,...r.follower}),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.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),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===w&&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 S(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.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_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.SwarmChat=E,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.feedEntrySchema=e.feedEntrySchema,exports.hasValidSignature=e.hasValidSignature,exports.historyFileSchema=e.historyFileSchema,exports.historyLinkSchema=e.historyLinkSchema,exports.historyRowSchema=e.historyRowSchema,exports.newMessageId=e.newMessageId,exports.parseChatMessage=e.parseChatMessage,exports.parseFeedEntry=e.parseFeedEntry,exports.parseHistoryFile=e.parseHistoryFile,exports.signedBytes=e.signedBytes;
|
package/dist/index.js
CHANGED
|
@@ -451,8 +451,8 @@ var Q = class {
|
|
|
451
451
|
slotReadMs: r.socReadTimeout ?? 5e3,
|
|
452
452
|
feedReadMs: r.feedReadTimeout ?? 15e3,
|
|
453
453
|
writeMs: r.gsocWriteTimeout ?? 1e4
|
|
454
|
-
}
|
|
455
|
-
this.source = t.source ?? L(
|
|
454
|
+
};
|
|
455
|
+
this.source = t.source ?? L(new C(r.beeUrl), Z(r.chatAddress), r.chatTopic, i), this.write = t.write ?? B(new C(r.writeUrl ?? r.beeUrl), r.stamp ? Z(r.stamp) : z, Z(r.gsocResourceId), r.gsocTopic, i), this.follower = new W(this.source, this.topic, {
|
|
456
456
|
entry: (e) => this.show(e),
|
|
457
457
|
skipped: (e, t, n) => this.emitter.emit(O.MESSAGE_SKIPPED, {
|
|
458
458
|
index: e,
|