@solarpunkltd/swarm-chat-js 6.2.7 → 7.0.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 +175 -196
- package/dist/chat.d.ts +106 -0
- package/dist/events.d.ts +80 -0
- package/dist/follower.d.ts +122 -0
- package/dist/history.d.ts +51 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +7 -8
- package/dist/index.js +576 -11
- package/dist/message/entry.d.ts +97 -0
- package/dist/message/index.d.ts +2 -0
- package/dist/message/message.d.ts +90 -0
- package/dist/message.cjs +1 -0
- package/dist/message.js +264 -0
- package/dist/sender.d.ts +62 -0
- package/dist/swarm.d.ts +74 -0
- package/package.json +48 -54
- package/dist/index.d.ts.map +0 -1
- package/dist/index.esm.js +0 -6581
- package/dist/interfaces/chat.d.ts +0 -41
- package/dist/interfaces/chat.d.ts.map +0 -1
- package/dist/interfaces/index.d.ts +0 -3
- package/dist/interfaces/index.d.ts.map +0 -1
- package/dist/interfaces/message.d.ts +0 -31
- package/dist/interfaces/message.d.ts.map +0 -1
- package/dist/lib/constants.d.ts +0 -10
- package/dist/lib/constants.d.ts.map +0 -1
- package/dist/lib/core.d.ts +0 -31
- package/dist/lib/core.d.ts.map +0 -1
- package/dist/lib/history.d.ts +0 -27
- package/dist/lib/history.d.ts.map +0 -1
- package/dist/lib/utils.d.ts +0 -32
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/scripts/mine.d.ts +0 -3
- package/dist/scripts/mine.d.ts.map +0 -1
- package/dist/swarm-chat-umd.js +0 -11
- package/dist/transports/MessageTransport.d.ts +0 -7
- package/dist/transports/MessageTransport.d.ts.map +0 -1
- package/dist/transports/PollingTransport.d.ts +0 -20
- package/dist/transports/PollingTransport.d.ts.map +0 -1
- package/dist/utils/bee.d.ts +0 -18
- package/dist/utils/bee.d.ts.map +0 -1
- package/dist/utils/common.d.ts +0 -5
- package/dist/utils/common.d.ts.map +0 -1
- package/dist/utils/error.d.ts +0 -8
- package/dist/utils/error.d.ts.map +0 -1
- package/dist/utils/eventEmitter.d.ts +0 -11
- package/dist/utils/eventEmitter.d.ts.map +0 -1
- package/dist/utils/logger.d.ts +0 -12
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/validation.d.ts +0 -18
- package/dist/utils/validation.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,214 +1,193 @@
|
|
|
1
|
-
# Swarm Chat JS
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
The
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
**
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- **Polling mode**: Polls the main chat feed for new messages and displays them
|
|
29
|
-
- **Waku mode**: Receives real-time message updates via the Waku network
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## 📦 Installation
|
|
34
|
-
|
|
35
|
-
You can install the library using npm or pnpm:
|
|
1
|
+
# Swarm Chat JS
|
|
2
|
+
|
|
3
|
+
A client library for chat over [Swarm](https://www.ethswarm.org/). A browser signs each message and sends it as one
|
|
4
|
+
GSOC write to a shared inbox. A chat server verifies it and publishes it to the chat's feed, and every reader follows
|
|
5
|
+
that feed. The browser talks only to a Bee node or gateway, never to a web server of its own.
|
|
6
|
+
|
|
7
|
+
The library needs its server,
|
|
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.
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
1. **Sending.** The sender builds the message, signs it with the user's key, and writes it once to the inbox, a GSOC
|
|
14
|
+
address every chat shares. The message stays pending until the chat feed shows it. If it does not, the identical
|
|
15
|
+
bytes are written again every ten seconds, up to five times, and then it is failed with a manual retry.
|
|
16
|
+
2. **Publishing.** The server checks each message's shape and signature and writes it to the chat's feed as the next
|
|
17
|
+
entry, at index 0, 1, 2 and on. After a message is published it saves a history file of the chat, and every entry
|
|
18
|
+
links to the newest file saved.
|
|
19
|
+
3. **Opening.** A reader asks Bee for the feed's head once, shows the history file the head entry links to, and reads
|
|
20
|
+
the entries after it.
|
|
21
|
+
4. **Following.** A reader polls explicit feed slots after the last one it saw, several in one poll. A slot not
|
|
22
|
+
written yet is the ordinary live state and is simply asked again. A slot that fails its checks is skipped and never
|
|
23
|
+
stops the chat. A slot no peer gives out while later slots exist is stepped past and read again on later polls, so a
|
|
24
|
+
message skipped arrives late rather than never. Gateway failures back off with jitter up to eight seconds.
|
|
25
|
+
5. **Older messages.** Loading older messages is a click, which reads the history file before the oldest one shown.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
36
28
|
|
|
37
29
|
```bash
|
|
38
|
-
|
|
30
|
+
pnpm add @solarpunkltd/swarm-chat-js @ethersphere/bee-js
|
|
39
31
|
```
|
|
40
32
|
|
|
41
|
-
|
|
33
|
+
bee-js 13 is a peer dependency. The package runs in browsers and in Node 24 or later, as ESM or CommonJS.
|
|
42
34
|
|
|
43
|
-
##
|
|
35
|
+
## Usage
|
|
44
36
|
|
|
45
|
-
|
|
37
|
+
```ts
|
|
38
|
+
import { EVENTS, MessageType, SwarmChat, type MessageData } from '@solarpunkltd/swarm-chat-js';
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### `ChatSettings` Interface
|
|
52
|
-
|
|
53
|
-
This configuration object is crucial for initializing the `SwarmChat` instance.
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
export interface ChatSettings {
|
|
57
|
-
user: {
|
|
58
|
-
/** Private key of the chat user, used for signing updates to their own Swarm feed. */
|
|
59
|
-
privateKey: string;
|
|
60
|
-
/** Display name or nickname of the current user. */
|
|
61
|
-
nickname: string;
|
|
62
|
-
};
|
|
40
|
+
const chat = new SwarmChat({
|
|
41
|
+
user: { privateKey: userKey, nickname: 'alice' },
|
|
63
42
|
infra: {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
43
|
+
beeUrl: 'https://gateway.example.com',
|
|
44
|
+
gsocTopic: inboxIdentifier,
|
|
45
|
+
gsocResourceId: minedInboxKey,
|
|
46
|
+
chatTopic: 'chat-my-stream',
|
|
47
|
+
chatAddress: serverFeedOwner,
|
|
48
|
+
pollingInterval: 500,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const { on } = chat.getEmitter();
|
|
53
|
+
on(EVENTS.MESSAGE_RECEIVED, (message: MessageData) => show(message));
|
|
54
|
+
on(EVENTS.MESSAGE_REQUEST_ERROR, (message: MessageData) => offerRetry(message));
|
|
55
|
+
on(EVENTS.STATUS, (status) => showConnection(status));
|
|
56
|
+
|
|
57
|
+
await chat.start();
|
|
58
|
+
await chat.sendMessage('hello', MessageType.TEXT);
|
|
59
|
+
await chat.sendMessage('👍', MessageType.REACTION, someMessageId);
|
|
60
|
+
|
|
61
|
+
// Later, and in any order, as often as needed.
|
|
62
|
+
await chat.stop();
|
|
85
63
|
```
|
|
86
64
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
```
|
|
150
|
-
import {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const handleSendMessage = (text: string) => {
|
|
157
|
-
sendMessage(text, MessageType.TEXT);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const handleReaction = (targetMessageId: string, emoji: string) => {
|
|
161
|
-
sendMessage(emoji, MessageType.REACTION, targetMessageId);
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const handleReply = (targetMessageId: string, replyText: string) => {
|
|
165
|
-
sendMessage(replyText, MessageType.THREAD, targetMessageId);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
return (
|
|
169
|
-
<div className="chat-container">
|
|
170
|
-
{/* Your chat UI implementation */}
|
|
171
|
-
{hasPreviousMessages() && <button onClick={loadPreviousMessages}>Load Previous Messages</button>}
|
|
172
|
-
{/* Message list, input field, etc. */}
|
|
173
|
-
</div>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
65
|
+
## Settings
|
|
66
|
+
|
|
67
|
+
| Setting | Meaning |
|
|
68
|
+
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
69
|
+
| `user.privateKey` | Signs every message. A viewer that only reads may pass any key. |
|
|
70
|
+
| `user.nickname` | The name on every message, 1 to 20 characters. Nothing can be sent without one. |
|
|
71
|
+
| `infra.beeUrl` | The Bee node or gateway every read and write goes through. |
|
|
72
|
+
| `infra.stamp` | The batch the inbox writes are stamped with. Left out for a gateway that stamps writes itself. |
|
|
73
|
+
| `infra.gsocTopic` | The inbox's identifier string, the same for every chat. |
|
|
74
|
+
| `infra.gsocResourceId` | The mined key every sender signs inbox writes with. The server's operator mines it. |
|
|
75
|
+
| `infra.chatTopic` | The chat's topic, which every message carries and the feed is named by. |
|
|
76
|
+
| `infra.chatAddress` | The server's feed owner address. |
|
|
77
|
+
| `infra.pollingInterval` | How often a reader polls at the live edge, 1,000 ms by default. |
|
|
78
|
+
| `infra.socReadTimeout` | One feed slot read, 5,000 ms by default. |
|
|
79
|
+
| `infra.feedReadTimeout` | The head lookup and a history file download, 15,000 ms by default. A head lookup that runs out of time reads the chat from slot 0. |
|
|
80
|
+
| `infra.gsocWriteTimeout` | One inbox write, 10,000 ms by default. |
|
|
81
|
+
|
|
82
|
+
## Methods
|
|
83
|
+
|
|
84
|
+
| Method | What it does |
|
|
85
|
+
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `start()` | Opens the chat and follows it. Resolves once it is open, or once `stop` is called. Opening is retried for as long as the chat runs. |
|
|
87
|
+
| `stop()` | Stops every poll, resend and retry. Listeners stay, so a later `start` reports to them again. |
|
|
88
|
+
| `getEmitter()` | The events below, with `on` and `off`. |
|
|
89
|
+
| `sendMessage(text, type, targetMessageId?)` | Sends a text or a thread reply and resolves to the pending message. A reaction resolves to `null`: taps on one reaction inside a second cancel in pairs, and only an odd count is sent. Rejects with a `ChatMessageError`, sending nothing, for a message the server would refuse. |
|
|
90
|
+
| `retrySendMessage(message)` | Writes a failed or pending message again with its identical bytes. |
|
|
91
|
+
| `fetchPreviousMessages()` | Shows the history file before the oldest one shown, and resolves to its messages. |
|
|
92
|
+
| `hasPreviousMessages()` | Whether there is an older file to load. |
|
|
93
|
+
| `orderMessages(messages)` | Published messages in chat order, then pending ones by time. |
|
|
94
|
+
| `getStatus()` | `live`, `reconnecting`, `stalled`, or `null` before the first read. |
|
|
95
|
+
| `getAddress()` | The address of the key, which is every sent message's `address`. |
|
|
96
|
+
|
|
97
|
+
## Events
|
|
98
|
+
|
|
99
|
+
| Event | Carries | When |
|
|
100
|
+
| --------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
101
|
+
| `LOADING_INIT` | `boolean` | `true` when opening starts, `false` once the chat is open. |
|
|
102
|
+
| `LOADING_PREVIOUS_MESSAGES` | `boolean` | Around a load of older messages. |
|
|
103
|
+
| `MESSAGE_RECEIVED` | `MessageData` | A message the feed or a history file shows. For a sender, this is what "sent" means. |
|
|
104
|
+
| `MESSAGE_REQUEST_INITIATED` | `MessageData` | A message signed and about to be written, with `index` -1. |
|
|
105
|
+
| `MESSAGE_REQUEST_UPLOADED` | `MessageData` | Its first write the node accepted. It is still pending. |
|
|
106
|
+
| `MESSAGE_REQUEST_ERROR` | `MessageData` | Its resends ran out unseen. |
|
|
107
|
+
| `STATUS` | `FeedStatus` | `live`: the gateway answers. `reconnecting`: it does not. `stalled`: a message known to exist has not loaded for eight seconds. |
|
|
108
|
+
| `MESSAGE_SKIPPED` | `SkippedMessage` | A feed slot or history row not shown, and why. |
|
|
109
|
+
| `ERROR` | `unknown` | A failure the chat recovers from on its own. |
|
|
110
|
+
| `CRITICAL_ERROR` | `unknown` | Opening failed three times in a row. It keeps trying. |
|
|
111
|
+
|
|
112
|
+
**The order of a sent message's events.** `MESSAGE_REQUEST_INITIATED` comes first, then `MESSAGE_REQUEST_UPLOADED`
|
|
113
|
+
once a write is accepted, then `MESSAGE_RECEIVED` when the feed shows it. The resends and the give-up wait while the
|
|
114
|
+
status is not `live`, so a message is not failed only because this viewer could not see the feed. A message can still
|
|
115
|
+
be failed and then shown, for example when the server published it after the last resend, so `MESSAGE_RECEIVED` may
|
|
116
|
+
follow `MESSAGE_REQUEST_ERROR` for the same `id`, and a viewer settles on the received one.
|
|
117
|
+
|
|
118
|
+
A `MessageData` has `id`, `type`, `message` (the text or emoji), `username`, `address`, `timestamp` (the server's
|
|
119
|
+
receive time, or the sender's clock while pending), `targetMessageId` for a reply or reaction, `chatTopic`,
|
|
120
|
+
`signature`, `index` (the feed index, -1 while pending) and `sentAt` (the sender's clock, which nothing checks).
|
|
121
|
+
|
|
122
|
+
## The message, for servers and tools
|
|
123
|
+
|
|
124
|
+
`@solarpunkltd/swarm-chat-js/message` is the one place the message is built, signed and checked, and the server imports
|
|
125
|
+
it rather than keeping a copy. It loads none of the reader.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { parseChatMessage, MAX_MESSAGE_BYTES } from '@solarpunkltd/swarm-chat-js/message';
|
|
129
|
+
|
|
130
|
+
const check = parseChatMessage(payload);
|
|
131
|
+
if (check.ok) publish(check.message);
|
|
132
|
+
else count(check.reason);
|
|
176
133
|
```
|
|
177
134
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
135
|
+
A message is a JSON object of at most 2,048 bytes of UTF-8:
|
|
136
|
+
|
|
137
|
+
| Field | Meaning |
|
|
138
|
+
| -------- | ------------------------------------------------------------------------------------------------------------ |
|
|
139
|
+
| `v` | `7` |
|
|
140
|
+
| `topic` | The chat's topic, 1 to 128 characters |
|
|
141
|
+
| `id` | A random id, 32 lowercase hex characters |
|
|
142
|
+
| `type` | `text`, `thread` (a reply, `target` is the parent) or `reaction` (`target` is the message, `text` the emoji) |
|
|
143
|
+
| `target` | The id it refers to, or `""` for a text |
|
|
144
|
+
| `text` | 1 to 500 characters |
|
|
145
|
+
| `name` | 1 to 20 characters |
|
|
146
|
+
| `addr` | The sender's address, 40 lowercase hex characters without `0x` |
|
|
147
|
+
| `ts` | The sender's clock in milliseconds |
|
|
148
|
+
| `sig` | 130 hex characters |
|
|
149
|
+
|
|
150
|
+
Characters are counted as Unicode code points, so an emoji counts once, and `countCharacters` counts the same way for
|
|
151
|
+
a composer. The signed bytes are the UTF-8 of `JSON.stringify([7, topic, id, type, target, text, name, addr, ts])`,
|
|
152
|
+
signed with bee-js's `PrivateKey.sign` and checked with `Signature.recoverPublicKey`, both over the raw bytes.
|
|
153
|
+
|
|
154
|
+
| Export | Use |
|
|
155
|
+
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
156
|
+
| `parseChatMessage(bytes)` | The server's check of a raw payload: byte cap, UTF-8, JSON, shape, signature. Never throws. |
|
|
157
|
+
| `checkChatMessage(value)` | The same for a parsed object. |
|
|
158
|
+
| `createChatMessage(key, draft)` | Builds and signs, throwing a `ChatMessageError` for anything the check would refuse. |
|
|
159
|
+
| `chatMessageSchema`, `hasValidSignature`, `signedBytes`, `encodeChatMessage` | The parts. |
|
|
160
|
+
| `feedEntrySchema`, `parseFeedEntry` | The feed entry, `{v, seq, at, msg, history}`. |
|
|
161
|
+
| `historyFileSchema`, `parseHistoryFile` | The history file, `{v, topic, fromSeq, toSeq, messages, prev}`. |
|
|
162
|
+
|
|
163
|
+
## Changes from 6.x
|
|
164
|
+
|
|
165
|
+
- Messages are signed over every field and verified by the server. 6.x signed four fields with a double prefix, and its
|
|
166
|
+
check passed forged messages.
|
|
167
|
+
- One inbox write per message. The write to the sender's own feed is gone, and so are `userTopic`, `additionalProps`,
|
|
168
|
+
and the `enveloped` setting.
|
|
169
|
+
- A bad message or history row is skipped, never a freeze. A slot no peer gives out is stepped past and read again.
|
|
170
|
+
- No signature check per message on the read path, which cost 6.x about 36 ms of main thread per message on every open.
|
|
171
|
+
- `sendMessage` takes three arguments, and a reaction resolves to `null`. `retrySendMessage` covers what
|
|
172
|
+
`retryBroadcastUserMessage` did.
|
|
173
|
+
- `stop` keeps listeners. New events: `STATUS`, `MESSAGE_SKIPPED` and `ERROR`. `CRITICAL_ERROR` means three failed
|
|
174
|
+
openings in a row, and opening keeps trying.
|
|
175
|
+
- `MessageData.index` is the feed index, `timestamp` is the server's receive time, and `sentAt` is new.
|
|
176
|
+
- The GSOC mining script moved to the server repository, whose operator mines the inbox key.
|
|
177
|
+
- The package is ESM first with a CommonJS build. The UMD bundle and the Node polyfills are gone.
|
|
178
|
+
|
|
179
|
+
## Development
|
|
180
|
+
|
|
181
|
+
Node 24 and pnpm 12, through `packageManager`.
|
|
187
182
|
|
|
188
183
|
```bash
|
|
189
|
-
|
|
184
|
+
pnpm install
|
|
185
|
+
pnpm test
|
|
190
186
|
```
|
|
191
187
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
## ⚠️ Limitations
|
|
195
|
-
|
|
196
|
-
- **Polling Mechanism:** The current version of `swarm-chat-js` relies on polling the aggregator's chat feed to fetch new messages. This approach can be resource-heavy.
|
|
197
|
-
|
|
198
|
-
---
|
|
199
|
-
|
|
200
|
-
## 💡 Future Development
|
|
201
|
-
|
|
202
|
-
- **Push-Based Event System:** We are actively designing a new architecture to transition from the polling mechanism to a more efficient, push-based event system for message delivery. This will significantly reduce node load and improve real-time message propagation.
|
|
203
|
-
- **Performance Optimizations:** Further improvements to message state handling and caching mechanisms.
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
## 📚 Further Reading & Resources
|
|
188
|
+
`lint` (oxlint, type-aware), `format` and `format:check` (oxfmt), `typecheck`, `test` (vitest) and `build` (vite, with
|
|
189
|
+
types from `tsc`) are the scripts CI runs. `pnpm pack` builds before it packs.
|
|
208
190
|
|
|
209
|
-
|
|
210
|
-
- [GSOC Introduction (Official Swarm Documentation)](https://docs.ethswarm.org/docs/develop/tools-and-features/gsoc/#introduction)
|
|
211
|
-
- [Example Aggregator: Solar-Punk-Ltd/swarm-chat-aggregator-js](https://github.com/Solar-Punk-Ltd/swarm-chat-aggregator-js)
|
|
212
|
-
- [Example React client](https://github.com/Solar-Punk-Ltd/swarm-chat-react-example)
|
|
191
|
+
## License
|
|
213
192
|
|
|
214
|
-
|
|
193
|
+
Apache-2.0
|
package/dist/chat.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ChatEmitter, type MessageData } from './events.js';
|
|
2
|
+
import { FeedStatus, type FollowerSettings } from './follower.js';
|
|
3
|
+
import { MessageType } from './message/index.js';
|
|
4
|
+
import { type GsocWrite, type SenderSettings } from './sender.js';
|
|
5
|
+
import { type ChatSource } from './swarm.js';
|
|
6
|
+
export interface ChatSettings {
|
|
7
|
+
user: {
|
|
8
|
+
/** Signs every message. A viewer that only reads may pass any key, since nothing is sent without a nickname. */
|
|
9
|
+
privateKey: string;
|
|
10
|
+
/** The name shown on every message, 1 to 20 characters. */
|
|
11
|
+
nickname: string;
|
|
12
|
+
};
|
|
13
|
+
infra: {
|
|
14
|
+
/** The Bee node or gateway every read and write goes through. */
|
|
15
|
+
beeUrl: string;
|
|
16
|
+
/** The batch the inbox writes are stamped with. Left out for a gateway that stamps writes itself. */
|
|
17
|
+
stamp?: string;
|
|
18
|
+
/** The inbox's identifier string, one for every chat, as the server listens on it. */
|
|
19
|
+
gsocTopic: string;
|
|
20
|
+
/** The mined key every sender signs inbox writes with, which puts them in the server node's neighbourhood. */
|
|
21
|
+
gsocResourceId: string;
|
|
22
|
+
/** The chat's topic, which every message carries and the server's feed is named by. */
|
|
23
|
+
chatTopic: string;
|
|
24
|
+
/** The server's feed owner address. */
|
|
25
|
+
chatAddress: string;
|
|
26
|
+
/** How often the reader polls at the live edge, 1,000 ms by default. */
|
|
27
|
+
pollingInterval?: number;
|
|
28
|
+
/**
|
|
29
|
+
* The head lookup and a history file download, 15,000 ms by default. An idle Bee answers the head lookup of a
|
|
30
|
+
* 1,000 message chat in about five seconds, so this is three times that. A lookup slower than this is a loaded
|
|
31
|
+
* gateway, and the chat is then read from slot 0 rather than waiting on it.
|
|
32
|
+
*/
|
|
33
|
+
feedReadTimeout?: number;
|
|
34
|
+
/** One inbox write, 10,000 ms by default. */
|
|
35
|
+
gsocWriteTimeout?: number;
|
|
36
|
+
/** One feed slot read, 5,000 ms by default. */
|
|
37
|
+
socReadTimeout?: number;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** What a test hands in instead of the network, and the tuning a test shortens. */
|
|
41
|
+
export interface ChatParts {
|
|
42
|
+
source: ChatSource;
|
|
43
|
+
write: GsocWrite;
|
|
44
|
+
follower: Partial<FollowerSettings>;
|
|
45
|
+
sender: Partial<SenderSettings>;
|
|
46
|
+
/** The waits between opening attempts, the last repeated. */
|
|
47
|
+
openRetryMs: readonly number[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* One chat: opens it from its newest history file, follows its feed, and sends messages to the server's inbox. Its
|
|
51
|
+
* events carry everything a viewer shows, and `start` and `stop` may be called in any order and any number of times.
|
|
52
|
+
*/
|
|
53
|
+
export declare class SwarmChat {
|
|
54
|
+
private readonly emitter;
|
|
55
|
+
private readonly key;
|
|
56
|
+
private readonly topic;
|
|
57
|
+
private readonly nickname;
|
|
58
|
+
private readonly source;
|
|
59
|
+
private readonly write;
|
|
60
|
+
private readonly senderSettings;
|
|
61
|
+
private readonly openRetryMs;
|
|
62
|
+
private readonly follower;
|
|
63
|
+
private readonly history;
|
|
64
|
+
private sender;
|
|
65
|
+
private readonly seen;
|
|
66
|
+
private status;
|
|
67
|
+
private generation;
|
|
68
|
+
private running;
|
|
69
|
+
private starting;
|
|
70
|
+
private openTimer;
|
|
71
|
+
private wakeOpen;
|
|
72
|
+
constructor(settings: ChatSettings, parts?: Partial<ChatParts>);
|
|
73
|
+
getEmitter(): ChatEmitter;
|
|
74
|
+
/** The reader's state, or null before the chat has been read. */
|
|
75
|
+
getStatus(): FeedStatus | null;
|
|
76
|
+
/** This key's address, the `address` of every message it sends. */
|
|
77
|
+
getAddress(): string;
|
|
78
|
+
/**
|
|
79
|
+
* Opens the chat and starts following it. Resolves once it is open, or once `stop` is called first. Opening is
|
|
80
|
+
* retried with backoff for as long as the chat runs: every failure is an ERROR event, and three in a row a
|
|
81
|
+
* CRITICAL_ERROR.
|
|
82
|
+
*/
|
|
83
|
+
start(): Promise<void>;
|
|
84
|
+
/** Stops every poll, resend and retry. Listeners stay, so a later `start` reports to them again. */
|
|
85
|
+
stop(): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Sends a text or a thread reply, or taps a reaction. A text and a reply resolve to the pending message, which
|
|
88
|
+
* MESSAGE_RECEIVED later shows as published. A reaction resolves to null: taps inside a second cancel in pairs,
|
|
89
|
+
* and only an odd count is sent. Rejects with a ChatMessageError, sending nothing, for a message the server would
|
|
90
|
+
* refuse, such as one with no nickname.
|
|
91
|
+
*/
|
|
92
|
+
sendMessage(text: string, type: MessageType, targetMessageId?: string): Promise<MessageData | null>;
|
|
93
|
+
/** Sends a failed or pending message again, with its identical bytes. Returns whether it was still held. */
|
|
94
|
+
retrySendMessage(message: MessageData | string): boolean;
|
|
95
|
+
/** Loads the history file before the oldest one shown, and shows its messages. */
|
|
96
|
+
fetchPreviousMessages(): Promise<MessageData[]>;
|
|
97
|
+
hasPreviousMessages(): boolean;
|
|
98
|
+
/** Published messages in chat order, then pending ones by the sender's clock. */
|
|
99
|
+
orderMessages<T extends Pick<MessageData, 'index' | 'timestamp'>>(messages: T[]): T[];
|
|
100
|
+
private open;
|
|
101
|
+
private waitToOpen;
|
|
102
|
+
private makeSender;
|
|
103
|
+
private show;
|
|
104
|
+
private showRows;
|
|
105
|
+
private setStatus;
|
|
106
|
+
}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { FeedStatus, SkipReason } from './follower.js';
|
|
2
|
+
import type { ChatMessage, FeedEntry, HistoryRow, MessageType } from './message/index.js';
|
|
3
|
+
export declare const EVENTS: {
|
|
4
|
+
/** `true` when opening starts, `false` once the chat is open and being followed. */
|
|
5
|
+
readonly LOADING_INIT: 'loadingInit';
|
|
6
|
+
/** `true` while "load older" reads a history file, then `false`. */
|
|
7
|
+
readonly LOADING_PREVIOUS_MESSAGES: 'loadingPreviousMessages';
|
|
8
|
+
/** A message the chat feed shows, a MessageData. Also how the sender learns its message went out. */
|
|
9
|
+
readonly MESSAGE_RECEIVED: 'messageReceived';
|
|
10
|
+
/** A message built, signed and about to be written, a MessageData with index -1. */
|
|
11
|
+
readonly MESSAGE_REQUEST_INITIATED: 'messageRequestInitiated';
|
|
12
|
+
/** The first write of a message that the node accepted. It is still pending until MESSAGE_RECEIVED. */
|
|
13
|
+
readonly MESSAGE_REQUEST_UPLOADED: 'messageRequestUploaded';
|
|
14
|
+
/**
|
|
15
|
+
* A message whose resends ran out unseen while the reader was live. `retrySendMessage` sends it again. A
|
|
16
|
+
* MESSAGE_RECEIVED for the same id may still follow, if the server published it late.
|
|
17
|
+
*/
|
|
18
|
+
readonly MESSAGE_REQUEST_ERROR: 'messageRequestError';
|
|
19
|
+
/** Opening has failed three times in a row. It keeps trying, and a later LOADING_INIT `false` means it got through. */
|
|
20
|
+
readonly CRITICAL_ERROR: 'criticalError';
|
|
21
|
+
/** The reader's state, a FeedStatus: live, reconnecting or stalled. */
|
|
22
|
+
readonly STATUS: 'status';
|
|
23
|
+
/** A feed slot or history row not shown, a SkippedMessage. */
|
|
24
|
+
readonly MESSAGE_SKIPPED: 'messageSkipped';
|
|
25
|
+
/** Any failure the chat recovers from on its own, for logging. */
|
|
26
|
+
readonly ERROR: 'error';
|
|
27
|
+
};
|
|
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. */
|
|
30
|
+
export interface MessageData {
|
|
31
|
+
id: string;
|
|
32
|
+
type: MessageType;
|
|
33
|
+
/** The text, or the emoji of a reaction. */
|
|
34
|
+
message: string;
|
|
35
|
+
username: string;
|
|
36
|
+
address: string;
|
|
37
|
+
/** The server's receive time once published, the sender's clock while pending. What the viewer shows and sorts by. */
|
|
38
|
+
timestamp: number;
|
|
39
|
+
/** The message a thread reply or a reaction refers to. */
|
|
40
|
+
targetMessageId?: string;
|
|
41
|
+
chatTopic: string;
|
|
42
|
+
signature: string;
|
|
43
|
+
/** The message's place in the chat, its feed index, or -1 while it is pending. */
|
|
44
|
+
index: number;
|
|
45
|
+
/** The sender's own clock, which nothing checks. */
|
|
46
|
+
sentAt: number;
|
|
47
|
+
}
|
|
48
|
+
export interface SkippedMessage {
|
|
49
|
+
/** The feed index, or -1 for rows of a history file. */
|
|
50
|
+
index: number;
|
|
51
|
+
reason: SkipReason | 'history-row';
|
|
52
|
+
detail: string;
|
|
53
|
+
}
|
|
54
|
+
export declare function pendingMessageData(message: ChatMessage): MessageData;
|
|
55
|
+
export declare function publishedMessageData(entry: FeedEntry | HistoryRow): MessageData;
|
|
56
|
+
export interface ChatEventPayloads {
|
|
57
|
+
[EVENTS.LOADING_INIT]: boolean;
|
|
58
|
+
[EVENTS.LOADING_PREVIOUS_MESSAGES]: boolean;
|
|
59
|
+
[EVENTS.MESSAGE_RECEIVED]: MessageData;
|
|
60
|
+
[EVENTS.MESSAGE_REQUEST_INITIATED]: MessageData;
|
|
61
|
+
[EVENTS.MESSAGE_REQUEST_UPLOADED]: MessageData;
|
|
62
|
+
[EVENTS.MESSAGE_REQUEST_ERROR]: MessageData;
|
|
63
|
+
[EVENTS.CRITICAL_ERROR]: unknown;
|
|
64
|
+
[EVENTS.STATUS]: FeedStatus;
|
|
65
|
+
[EVENTS.MESSAGE_SKIPPED]: SkippedMessage;
|
|
66
|
+
[EVENTS.ERROR]: unknown;
|
|
67
|
+
}
|
|
68
|
+
type Listener<T> = (data: T) => void;
|
|
69
|
+
/**
|
|
70
|
+
* The chat's events. `on` and `off` are bound, so a caller may take them apart from the emitter. A listener that
|
|
71
|
+
* throws is reported on the console and never stops the listeners after it or the chat behind it.
|
|
72
|
+
*/
|
|
73
|
+
export declare class ChatEmitter {
|
|
74
|
+
private readonly listeners;
|
|
75
|
+
on: <E extends ChatEvent>(event: E, listener: Listener<ChatEventPayloads[E]>) => void;
|
|
76
|
+
off: <E extends ChatEvent>(event: E, listener: Listener<ChatEventPayloads[E]>) => void;
|
|
77
|
+
cleanAll: () => void;
|
|
78
|
+
emit<E extends ChatEvent>(event: E, data: ChatEventPayloads[E]): void;
|
|
79
|
+
}
|
|
80
|
+
export {};
|