@solarpunkltd/swarm-chat-js 7.3.0 → 8.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 CHANGED
@@ -6,7 +6,8 @@ that feed. The browser talks only to a Bee node or gateway, never to a web serve
6
6
 
7
7
  The library needs its server,
8
8
  [Solar-Punk-Ltd/swarm-chat-aggregator-js](https://github.com/Solar-Punk-Ltd/swarm-chat-aggregator-js), which listens on
9
- the inbox and writes the feed. Version 7 of the library reads and writes the v7 message format only.
9
+ the inbox and writes the feed. Version 8 of the library reads and writes the v8 message format only, and a v7 message
10
+ fails its shape check.
10
11
 
11
12
  ## How it works
12
13
 
@@ -43,12 +44,13 @@ bee-js 13 is a peer dependency. The package runs in browsers and in Node 24 or l
43
44
  ## Usage
44
45
 
45
46
  ```ts
46
- import { EVENTS, MessageType, SwarmChat, type MessageData } from '@solarpunkltd/swarm-chat-js';
47
+ import { EVENTS, MessageType, MessageVia, SwarmChat, type MessageData } from '@solarpunkltd/swarm-chat-js';
47
48
 
48
49
  const chat = new SwarmChat({
49
50
  user: { privateKey: userKey, nickname: 'alice' },
50
51
  infra: {
51
52
  beeUrl: 'https://gateway.example.com',
53
+ via: MessageVia.GATEWAY,
52
54
  gsocTopic: inboxIdentifier,
53
55
  gsocResourceId: minedInboxKey,
54
56
  chatTopic: 'chat-my-stream',
@@ -76,9 +78,12 @@ await chat.stop();
76
78
  | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
77
79
  | `user.privateKey` | Signs every message. A viewer that only reads may pass any key. |
78
80
  | `user.nickname` | The name on every message, 1 to 20 characters. Nothing can be sent without one. |
81
+ | `user.binding` | Optional. A signed-in viewer's Swarm ID chat binding, `{ msg, sig }`, for the chat address of `user.privateKey`. Every message then carries it. |
79
82
  | `infra.beeUrl` | The Bee node or gateway the chat is read through, and written through when `infra.writeUrl` is not set. |
80
83
  | `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. |
81
84
  | `infra.stamp` | The batch the inbox writes are stamped with. Left out for a gateway that stamps writes itself. |
85
+ | `infra.via` | Required. Who pays for the inbox writes, `MessageVia.GATEWAY` or `MessageVia.OWN_STAMP`, as every message says in `via`. |
86
+ | `infra.own` | Optional, `false` by default. Whether the sender keeps a copy of every message in its own feed, as every message says in `own`. See the own feed. |
82
87
  | `infra.gsocTopic` | The inbox's identifier string, the same for every chat. |
83
88
  | `infra.gsocResourceId` | The mined key every sender signs inbox writes with. The server's operator mines it. |
84
89
  | `infra.chatTopic` | The chat's topic, which every message carries and the feed is named by. |
@@ -127,8 +132,10 @@ be failed and then shown, for example when the server published it after the las
127
132
  follow `MESSAGE_REQUEST_ERROR` for the same `id`, and a viewer settles on the received one.
128
133
 
129
134
  A `MessageData` has `id`, `type`, `message` (the text or emoji), `username`, `address`, `timestamp` (the server's
130
- receive time, or the sender's clock while pending), `targetMessageId` for a reply or reaction, `chatTopic`,
131
- `signature`, `index` (the feed index, -1 while pending) and `sentAt` (the sender's clock, which nothing checks).
135
+ receive time, or the sender's clock while pending), `targetMessageId` for a reply or reaction, `index` (the feed index,
136
+ -1 while pending) and `signedMessage`, the message exactly as its sender signed it. Everything else the message says,
137
+ its topic, its signature, the sender's clock, `via`, `own` and the binding, is read from `signedMessage`, and a page
138
+ that shows who sent it hands `signedMessage` to `verifyChatMessage`. See [Verifying](#verifying).
132
139
 
133
140
  ## The message, for servers and tools
134
141
 
@@ -138,38 +145,109 @@ it rather than keeping a copy. It loads none of the reader.
138
145
  ```ts
139
146
  import { parseChatMessage, MAX_MESSAGE_BYTES } from '@solarpunkltd/swarm-chat-js/message';
140
147
 
141
- const check = parseChatMessage(payload);
148
+ const check = parseChatMessage(payload, { origin: 'https://example.com' });
142
149
  if (check.ok) publish(check.message);
143
150
  else count(check.reason);
144
151
  ```
145
152
 
146
- A message is a JSON object of at most 2,048 bytes of UTF-8:
153
+ A message is a JSON object of at most 3,072 bytes of UTF-8, its fields in this order:
147
154
 
148
155
  | Field | Meaning |
149
156
  | -------- | ------------------------------------------------------------------------------------------------------------ |
150
- | `v` | `7` |
157
+ | `v` | `8` |
151
158
  | `topic` | The chat's topic, 1 to 128 characters |
152
159
  | `id` | A random id, 32 lowercase hex characters |
153
160
  | `type` | `text`, `thread` (a reply, `target` is the parent) or `reaction` (`target` is the message, `text` the emoji) |
154
161
  | `target` | The id it refers to, or `""` for a text |
155
162
  | `text` | 1 to 500 characters |
156
163
  | `name` | 1 to 20 characters |
157
- | `addr` | The sender's address, 40 lowercase hex characters without `0x` |
164
+ | `addr` | The sender's chat address, 40 lowercase hex characters without `0x` |
158
165
  | `ts` | The sender's clock in milliseconds |
159
- | `sig` | 130 hex characters |
166
+ | `via` | Who paid for the inbox write: `gateway` (the gateway's stamp) or `own-stamp` (the sender's own) |
167
+ | `own` | `true` when the sender also keeps a copy of the message in its own feed |
168
+ | `idb` | Only for a signed-in sender: its Swarm ID chat binding, `{ msg, sig }` |
169
+ | `sig` | 130 lowercase hex characters of r, s and v, with v 27 or 28 and s in the lower half |
160
170
 
161
171
  Characters are counted as Unicode code points, so an emoji counts once, and `countCharacters` counts the same way for
162
- a composer. The signed bytes are the UTF-8 of `JSON.stringify([7, topic, id, type, target, text, name, addr, ts])`,
163
- signed with bee-js's `PrivateKey.sign` and checked with `Signature.recoverPublicKey`, both over the raw bytes.
164
-
165
- | Export | Use |
166
- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
167
- | `parseChatMessage(bytes)` | The server's check of a raw payload: byte cap, UTF-8, JSON, shape, signature. Never throws. |
168
- | `checkChatMessage(value)` | The same for a parsed object. |
169
- | `createChatMessage(key, draft)` | Builds and signs, throwing a `ChatMessageError` for anything the check would refuse. |
170
- | `chatMessageSchema`, `hasValidSignature`, `signedBytes`, `encodeChatMessage` | The parts. |
171
- | `feedEntrySchema`, `parseFeedEntry` | The feed entry, `{v, seq, at, msg, history}`. |
172
- | `historyFileSchema`, `parseHistoryFile` | The history file, `{v, topic, fromSeq, toSeq, messages, prev}`. |
172
+ a composer. The signed bytes are the UTF-8 of
173
+ `JSON.stringify([8, topic, id, type, target, text, name, addr, ts, via, own, idb?.msg ?? '', idb?.sig ?? ''])`, signed
174
+ with bee-js's `PrivateKey.sign` and checked with `Signature.recoverPublicKey`, both over the raw bytes. So `via`, `own`
175
+ and the binding cannot be changed or taken off without breaking the signature. Every signature has a second valid
176
+ spelling, s replaced by n - s with v flipped, and bee-js reads any v but 27 as 28, so the check refuses all but the
177
+ low s spelling with v 27 or 28, the one bee-js signs. One message has one valid signature.
178
+
179
+ ### The Swarm ID chat binding
180
+
181
+ A Swarm ID account gives each site its own chat key, and nothing in that key says which account it came from. The fork
182
+ derives it from the site's app secret, itself HMAC-SHA256 of the site's origin under the account key: the chat key is
183
+ HMAC-SHA256 of `swarm-id chat key v1` under the app secret, asked again under `swarm-id chat key v1/1`, `/2` and on
184
+ while the result is not a valid secp256k1 private key. This library never derives it. It only checks the address a
185
+ binding names. The binding is the account's statement, signed with the account's own key, that one chat address on one
186
+ site is its own. The Swarm ID fork signs it, and this library reads it exactly as the fork's `verifyChatBinding` does,
187
+ without importing the fork. `msg` is four lines joined by `\n`:
188
+
189
+ ```text
190
+ Swarm ID chat binding v1
191
+ Site: https://example.com
192
+ Chat address: 0x<addr>
193
+ Date: 2026-10-14T12:00:00.000Z
194
+ ```
195
+
196
+ The site is a bare https origin as a browser reports it (plain http only for `localhost`, `127.0.0.1` and `[::1]`, and
197
+ only with `allowLocalHttp`), the chat address is `0x` and 40 lowercase hex characters, and the date is ISO 8601 UTC with
198
+ milliseconds. `sig` is EIP-191 `personal_sign` over `msg`, written as the fork writes it: `0x` and 130 lowercase hex
199
+ characters of r, s and v, v 27 or 28 and s in the lower half. Recovering it gives the account's address.
200
+
201
+ ### Verifying
202
+
203
+ `verifyChatMessage(value, { origin, allowLocalHttp? })` checks a message's shape and its signature, and then, when it
204
+ carries a binding, that the binding's text is those four lines, its chat address is the message's `addr`, its signature
205
+ recovers an account, and its site is `origin`. It returns `{ ok: true, message, identity }`, `identity` being
206
+ `{ account, site, date }` for a signed-in sender and `null` for a guest. A message whose binding fails is refused with
207
+ the reason `binding` and is never shown as a guest.
208
+
209
+ `origin` is required, the bare origin of the site whose chat this is, such as `https://example.com`. A binding signed
210
+ for one site therefore never passes in another site's chat because a caller left the origin out. A JavaScript caller
211
+ that passes none anyway gets every bound message refused as `binding`. The server passes its configured site origin,
212
+ and a page passes its own.
213
+
214
+ **What `identity.account` proves, and what it does not.** It proves only that whoever controls that Swarm ID account
215
+ bound this chat key for this site, on the date the binding names. It is not a verified person, and it is not an
216
+ account anyone has allowed. One person can hold many accounts, an account can be made in a minute, and a lost or shared
217
+ account key binds just as well. A page that wants more, such as a badge for known people or a list of accounts allowed
218
+ to post, keeps that list itself and compares `identity.account` with it.
219
+
220
+ The server calls it, through `parseChatMessage`, on every payload. The reader in this library still checks no
221
+ signature, since that cost 6.x about 36 ms of main thread per message, so a page that shows identities runs
222
+ `verifyChatMessage` in a Web Worker on each event's `MessageData.signedMessage`, which is the feed
223
+ entry's `msg` or, while pending, the message as it was signed. It never throws, and it touches no DOM and no Node API.
224
+
225
+ ### The own feed
226
+
227
+ A sender that also keeps a copy of each message it sends says so with `own: true`. The copies live in a feed owned by
228
+ the sender's chat address `addr`, under the topic `ownFeedTopic(chatTopic)`, which is `swarm-chat-own-feed-v1:` and the
229
+ chat's topic, turned into a feed topic with `Topic.fromString` as the chat's own feed is.
230
+
231
+ `SwarmChat` signs `own: true` when `infra.own` is `true`, once per message, so the pending message a page shows and every
232
+ resend carry it too. The library does not write the copy itself. A page that keeps copies passes its own inbox write,
233
+ a `GsocWrite`, as `new SwarmChat(settings, { write })`. It is handed the signed payload, the same bytes on every resend.
234
+ A write that stores the copy from those bytes before it writes them to the inbox never sends a message that claims a
235
+ copy it does not have.
236
+
237
+ | Export | Use |
238
+ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
239
+ | `parseChatMessage(bytes, { origin })` | The server's check of a raw payload: byte cap, UTF-8, JSON, then `verifyChatMessage`. Never throws. |
240
+ | `verifyChatMessage(value, { origin })` | Shape, signature and binding of a parsed object, with the sender's identity. Never throws. |
241
+ | `createChatMessage(key, draft)` | Builds and signs, throwing a `ChatMessageError` for anything the check would refuse. `via` is required. |
242
+ | `MessageType`, `MessageVia` | The values of `type` and `via`. |
243
+ | `checkChatBinding(binding, addr, options?)`, `chatBindingSchema` | The binding check on its own and its shape. Its `origin` is optional, for a sender checking its own binding. |
244
+ | `ownFeedTopic(chatTopic)`, `OWN_FEED_TOPIC_PREFIX` | The topic of a sender's own feed of copies. |
245
+ | `chatMessageSchema`, `hasValidSignature`, `signedBytes`, `encodeChatMessage` | The parts. |
246
+ | `feedEntrySchema`, `parseFeedEntry` | The feed entry, `{v, seq, at, msg, history}`. |
247
+ | `historyFileSchema`, `parseHistoryFile` | The history file, `{v, topic, fromSeq, toSeq, messages, prev}`. |
248
+
249
+ The feed entry and the history file carry `v` 8 like the message. A feed entry, at most 4,096 bytes, holds a message at
250
+ the byte cap inside its largest wrapper.
173
251
 
174
252
  ## Slot notes, for servers and tools
175
253
 
@@ -197,6 +275,26 @@ notes are found it trusts its own clock, moving reads earlier only when that bou
197
275
  no note is found for longer than a heartbeat, it waits out the whole bound from then on, which is never early. A
198
276
  clock that runs behind sees every stamp as old as it expects, so its messages arrive as late as it runs behind.
199
277
 
278
+ ## Changes in 8.1
279
+
280
+ - `infra.own` signs every message with `own: true`, for a page whose write keeps a copy in the sender's own feed. Left
281
+ out, messages say `own: false` as in 8.0.
282
+
283
+ ## Changes in 8.0
284
+
285
+ - The v8 message adds `via`, `own` and, for a signed-in sender, `idb`, its Swarm ID chat binding, all signed. A v7
286
+ message, entry or history file fails its shape check, and nothing reads both.
287
+ - `verifyChatMessage` replaces `checkChatMessage` and also checks the binding, returning the sender's `identity`.
288
+ Both it and `parseChatMessage` require `{ origin }`, the site whose chat this is. A failed binding is refused with the
289
+ new reason `binding`.
290
+ - `MAX_MESSAGE_BYTES` is 3,072, so a long text fits beside a binding.
291
+ - `createChatMessage` requires `via` and takes `own` and `idb`. `SwarmChat` requires `infra.via` and takes
292
+ `user.binding`.
293
+ - `ownFeedTopic` names the feed where a sender keeps its copies.
294
+ - `MessageData.signedMessage` carries the whole signed message, for a page to verify. `chatTopic`, `signature` and
295
+ `sentAt` are gone from `MessageData`, since `signedMessage` carries them as `topic`, `sig` and `ts`.
296
+ - The binding is recovered through `@ethersphere/core-sdk`, which bee-js 13 already installs, now a dependency here.
297
+
200
298
  ## Changes in 7.3
201
299
 
202
300
  - A reader asks Bee for an address that came back missing no more than three times a minute, a feed slot and a note
package/dist/chat.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ChatEmitter, type MessageData } from './events.js';
2
2
  import { FeedStatus, type FollowerSettings } from './follower.js';
3
- import { MessageType } from './message/index.js';
3
+ import { MessageType, type ChatBinding, type MessageVia } from './message/index.js';
4
4
  import { type NoteSettings } from './notes.js';
5
5
  import { type GsocWrite, type SenderSettings } from './sender.js';
6
6
  import { type ChatSource } from './swarm.js';
@@ -10,6 +10,11 @@ export interface ChatSettings {
10
10
  privateKey: string;
11
11
  /** The name shown on every message, 1 to 20 characters. */
12
12
  nickname: string;
13
+ /**
14
+ * The Swarm ID chat binding of a signed-in viewer, whose chat address must be `privateKey`'s. Every message then
15
+ * carries it, and a reader that verifies it shows the account. Left out for a guest.
16
+ */
17
+ binding?: ChatBinding;
13
18
  };
14
19
  infra: {
15
20
  /** The Bee node or gateway the chat is read through, and written through too when `writeUrl` is not set. */
@@ -21,6 +26,13 @@ export interface ChatSettings {
21
26
  writeUrl?: string;
22
27
  /** The batch the inbox writes are stamped with. Left out for a gateway that stamps writes itself. */
23
28
  stamp?: string;
29
+ /** Who pays for the inbox writes, the gateway's stamp or the sender's own, as every message says in `via`. */
30
+ via: MessageVia;
31
+ /**
32
+ * Whether the sender keeps a copy of every message in its own feed, as every message says in `own`. False when left
33
+ * out. The chat writes no copy: a `write` passed in `ChatParts` stores it from the payload it is handed.
34
+ */
35
+ own?: boolean;
24
36
  /** The inbox's identifier string, one for every chat, as the server listens on it. */
25
37
  gsocTopic: string;
26
38
  /** The mined key every sender signs inbox writes with, which puts them in the server node's neighbourhood. */
@@ -65,7 +77,7 @@ export declare class SwarmChat {
65
77
  private readonly emitter;
66
78
  private readonly key;
67
79
  private readonly topic;
68
- private readonly nickname;
80
+ private readonly profile;
69
81
  private readonly source;
70
82
  private readonly write;
71
83
  private readonly senderSettings;
package/dist/events.d.ts CHANGED
@@ -26,7 +26,10 @@ export declare const EVENTS: {
26
26
  readonly ERROR: 'error';
27
27
  };
28
28
  export type ChatEvent = (typeof EVENTS)[keyof typeof EVENTS];
29
- /** A message as the viewer shows it. The field names are 6.x's, so a viewer built on 6.x changes little. */
29
+ /**
30
+ * A message as the viewer shows it. The field names are 6.x's, so a viewer built on 6.x changes little. The fields a
31
+ * viewer renders are kept beside `signedMessage`, and everything else the message says is read from it.
32
+ */
30
33
  export interface MessageData {
31
34
  id: string;
32
35
  type: MessageType;
@@ -38,12 +41,13 @@ export interface MessageData {
38
41
  timestamp: number;
39
42
  /** The message a thread reply or a reaction refers to. */
40
43
  targetMessageId?: string;
41
- chatTopic: string;
42
- signature: string;
43
44
  /** The message's place in the chat, its feed index, or -1 while it is pending. */
44
45
  index: number;
45
- /** The sender's own clock, which nothing checks. */
46
- sentAt: number;
46
+ /**
47
+ * The message exactly as its sender signed it. The reader checks no signature, so a page that shows who sent it
48
+ * hands this to `verifyChatMessage`, in a Web Worker.
49
+ */
50
+ signedMessage: ChatMessage;
47
51
  }
48
52
  export interface SkippedMessage {
49
53
  /** The feed index, or -1 for rows of a history file. */
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=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;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./message-Bmwh3SCs.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}:{},index:t,signedMessage:e}}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.n){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.s(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.v(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.y(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.v(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.v(t,n,this.topic);if(r.ok)return r.value.history}return null}async readFile(t){try{let n=e.y(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.n,heartbeatMs:e.t,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.u(t-this.settings.marginMs-this.clock.ms,this.settings.slotMs)-1}readableAt(t){return e.l(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.d(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.profile=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.M(this.key,{...t,...this.profile}),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.O.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.profile={name:i.nickname,via:a.via,...i.binding?{idb:i.binding}:{},...a.own===void 0?{}:{own:a.own}},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.O.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.profile,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.CHAT_BINDING_TITLE=e.z,exports.ChatEmitter=o,exports.ChatMessageError=e.S,exports.DEFAULT_NOTE_HEARTBEAT_MS=e.t,exports.DEFAULT_NOTE_SLOT_MS=e.n,exports.EVENTS=n,exports.FeedStatus=_,exports.HeadLookupTimeoutError=l,exports.MAX_BINDING_CHARACTERS=e.B,exports.MAX_ENTRY_BYTES=e.p,exports.MAX_MESSAGE_BYTES=e.C,exports.MAX_NAME_CHARACTERS=e.w,exports.MAX_NOTE_BYTES=e.r,exports.MAX_TEXT_CHARACTERS=e.T,exports.MAX_TOPIC_CHARACTERS=e.E,exports.MESSAGE_VERSION=e.D,exports.MessageType=e.O,exports.MessageVia=e.k,exports.NOTE_VERSION=e.i,exports.OWN_FEED_TOPIC_PREFIX=e.b,exports.SwarmChat=M,exports.UnreadableGatewayError=y,exports.UnreadableSlotError=u,exports.chatBindingSchema=e.V,exports.chatMessageSchema=e.A,exports.checkChatBinding=e.H,exports.countCharacters=e.j,exports.createChatMessage=e.M,exports.encodeChatMessage=e.N,exports.encodeSlotNote=e.a,exports.feedEntrySchema=e.m,exports.hasValidSignature=e.P,exports.historyFileSchema=e.h,exports.historyLinkSchema=e.g,exports.historyRowSchema=e._,exports.newMessageId=e.F,exports.noteAddress=e.o,exports.noteIdentifier=e.s,exports.noteIdentifierText=e.c,exports.noteSlotEnd=e.l,exports.noteSlotOf=e.u,exports.ownFeedTopic=e.x,exports.parseChatMessage=e.I,exports.parseFeedEntry=e.v,exports.parseHistoryFile=e.y,exports.parseSlotNote=e.d,exports.signedBytes=e.L,exports.slotNoteSchema=e.f,exports.verifyChatMessage=e.R;