@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 +118 -20
- package/dist/chat.d.ts +14 -2
- package/dist/events.d.ts +9 -5
- package/dist/index.cjs +1 -1
- package/dist/index.js +102 -99
- package/dist/message/binding.d.ts +48 -0
- package/dist/message/canonicalSignature.d.ts +6 -0
- package/dist/message/entry.d.ts +25 -6
- package/dist/message/index.d.ts +2 -0
- package/dist/message/message.d.ts +60 -14
- package/dist/message/ownFeed.d.ts +9 -0
- package/dist/message-Bmwh3SCs.cjs +3 -0
- package/dist/message-Dbh8qrpg.js +455 -0
- package/dist/message.cjs +1 -1
- package/dist/message.js +2 -308
- package/dist/sender.d.ts +16 -4
- package/package.json +13 -10
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
|
|
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, `
|
|
131
|
-
|
|
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
|
|
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` | `
|
|
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
|
-
| `
|
|
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
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
46
|
-
|
|
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;
|