@pageloop/client 0.5.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/LICENSE +94 -0
- package/README.md +97 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/.tsbuildinfo.preact +1 -0
- package/dist/.tsbuildinfo.react +1 -0
- package/dist/.tsbuildinfo.solid +1 -0
- package/dist/ApiClient.d.ts +121 -0
- package/dist/ApiClient.d.ts.map +1 -0
- package/dist/ApiClient.js +512 -0
- package/dist/ApiClient.js.map +1 -0
- package/dist/CommentEngine.d.ts +111 -0
- package/dist/CommentEngine.d.ts.map +1 -0
- package/dist/CommentEngine.js +277 -0
- package/dist/CommentEngine.js.map +1 -0
- package/dist/EventBus.d.ts +122 -0
- package/dist/EventBus.d.ts.map +1 -0
- package/dist/EventBus.js +34 -0
- package/dist/EventBus.js.map +1 -0
- package/dist/IdbCache.d.ts +22 -0
- package/dist/IdbCache.d.ts.map +1 -0
- package/dist/IdbCache.js +79 -0
- package/dist/IdbCache.js.map +1 -0
- package/dist/PageLoop.d.ts +424 -0
- package/dist/PageLoop.d.ts.map +1 -0
- package/dist/PageLoop.js +1092 -0
- package/dist/PageLoop.js.map +1 -0
- package/dist/PageTracker.d.ts +32 -0
- package/dist/PageTracker.d.ts.map +1 -0
- package/dist/PageTracker.js +105 -0
- package/dist/PageTracker.js.map +1 -0
- package/dist/UIRenderer.d.ts +218 -0
- package/dist/UIRenderer.d.ts.map +1 -0
- package/dist/UIRenderer.js +2 -0
- package/dist/UIRenderer.js.map +1 -0
- package/dist/auth/resolveInitialToken.d.ts +49 -0
- package/dist/auth/resolveInitialToken.d.ts.map +1 -0
- package/dist/auth/resolveInitialToken.js +97 -0
- package/dist/auth/resolveInitialToken.js.map +1 -0
- package/dist/errorCode.d.ts +12 -0
- package/dist/errorCode.d.ts.map +1 -0
- package/dist/errorCode.js +21 -0
- package/dist/errorCode.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/notifications/BrowserNotifications.d.ts +68 -0
- package/dist/notifications/BrowserNotifications.d.ts.map +1 -0
- package/dist/notifications/BrowserNotifications.js +147 -0
- package/dist/notifications/BrowserNotifications.js.map +1 -0
- package/dist/preact/index.d.ts +37 -0
- package/dist/preact/index.d.ts.map +1 -0
- package/dist/preact/index.js +150 -0
- package/dist/preact/index.js.map +1 -0
- package/dist/preact/style.css +12 -0
- package/dist/react/index.d.ts +52 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +165 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/style.css +12 -0
- package/dist/safeStorage.d.ts +26 -0
- package/dist/safeStorage.d.ts.map +1 -0
- package/dist/safeStorage.js +78 -0
- package/dist/safeStorage.js.map +1 -0
- package/dist/solid/index.d.ts +40 -0
- package/dist/solid/index.d.ts.map +1 -0
- package/dist/solid/index.jsx +134 -0
- package/dist/solid/index.jsx.map +1 -0
- package/dist/solid/style.css +12 -0
- package/package.json +85 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import { RpcError } from '@pageloop/shared';
|
|
2
|
+
import { EventBus } from './EventBus.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every server-event name, as a runtime array. The SSE transport must
|
|
5
|
+
* register one `addEventListener` per named event (EventSource has no
|
|
6
|
+
* generic catch-all for named events), so it needs the names at runtime
|
|
7
|
+
* — whereas `ServerEvent['name']` is a type that's erased at compile.
|
|
8
|
+
*
|
|
9
|
+
* `satisfies readonly ServerEvent['name'][]` makes this list authoritative:
|
|
10
|
+
* adding a new event to the `ServerEvent` union without listing it here is
|
|
11
|
+
* still allowed (the array stays a valid subset), but the exhaustiveness
|
|
12
|
+
* guard below turns a *missing* name into a compile error, so SSE can't
|
|
13
|
+
* silently drop a freshly-added server event (CW-8). Done entirely
|
|
14
|
+
* client-side; no server change required.
|
|
15
|
+
*/
|
|
16
|
+
const SERVER_EVENT_NAMES = [
|
|
17
|
+
'projects.updated',
|
|
18
|
+
'memberships.updated',
|
|
19
|
+
'comments.created',
|
|
20
|
+
'comments.updated',
|
|
21
|
+
'comments.deleted',
|
|
22
|
+
'comments.bulkDeleted',
|
|
23
|
+
'replies.created',
|
|
24
|
+
'replies.updated',
|
|
25
|
+
'replies.deleted',
|
|
26
|
+
'reactions.changed',
|
|
27
|
+
'versions.refreshed',
|
|
28
|
+
'repo.sync.started',
|
|
29
|
+
'repo.sync.finished',
|
|
30
|
+
'repo.sync.failed',
|
|
31
|
+
'integration.sync.started',
|
|
32
|
+
'integration.sync.finished',
|
|
33
|
+
'integration.sync.failed',
|
|
34
|
+
'notifications.new',
|
|
35
|
+
'media.processed',
|
|
36
|
+
'pages.changed',
|
|
37
|
+
'pageGroups.changed',
|
|
38
|
+
'managedDocs.changed',
|
|
39
|
+
'chat.message',
|
|
40
|
+
'chat.presence',
|
|
41
|
+
'chat.read',
|
|
42
|
+
'chat.typing',
|
|
43
|
+
'notification.show',
|
|
44
|
+
// Server-internal admin signals — never broadcast to clients (the
|
|
45
|
+
// server's topicsForEvent returns [] for them). Listed only to satisfy
|
|
46
|
+
// the exhaustiveness guard below.
|
|
47
|
+
'user.registered',
|
|
48
|
+
'org.member.joined',
|
|
49
|
+
];
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
51
|
+
const _assertNoMissingServerEventName = true;
|
|
52
|
+
/**
|
|
53
|
+
* Single point of network IO for the SDK. Tries WebSocket first, falls back
|
|
54
|
+
* to SSE for events + REST for commands. The same `call()` API works
|
|
55
|
+
* regardless of transport — callers don't know whether their command went
|
|
56
|
+
* over a WS frame or a fetch.
|
|
57
|
+
*/
|
|
58
|
+
export class ApiClient {
|
|
59
|
+
config;
|
|
60
|
+
bus;
|
|
61
|
+
socket = null;
|
|
62
|
+
sse = null;
|
|
63
|
+
nextId = 1;
|
|
64
|
+
pending = new Map();
|
|
65
|
+
connected = false;
|
|
66
|
+
currentTransport = null;
|
|
67
|
+
/** Topics this client wants to be on. Re-sent on every WS reconnect
|
|
68
|
+
* so a transport bounce doesn't silently drop the user out of
|
|
69
|
+
* chat rooms / other topic streams. */
|
|
70
|
+
desiredTopics = new Set();
|
|
71
|
+
/** Resolver bookkeeping for `subscribeTopic` calls awaiting an
|
|
72
|
+
* `{type:'subscribed', topic, ok, error?}` ack. Keyed by topic. */
|
|
73
|
+
pendingTopicAcks = new Map();
|
|
74
|
+
/** Extra context to send with each subscribe frame (e.g. guestName
|
|
75
|
+
* for anonymous chat presence). Keyed by topic; re-sent on every
|
|
76
|
+
* reconnect via the same desiredTopics flush. */
|
|
77
|
+
topicExtras = new Map();
|
|
78
|
+
/** Set by `disconnect()` so a deliberate teardown (logout, token swap)
|
|
79
|
+
* does NOT trigger the auto-reconnect loop. Cleared on the next
|
|
80
|
+
* `connect()`. */
|
|
81
|
+
intentionalClose = false;
|
|
82
|
+
/** Current backoff attempt count for the WS auto-reconnect loop.
|
|
83
|
+
* Reset to 0 on a clean open. */
|
|
84
|
+
reconnectAttempts = 0;
|
|
85
|
+
/** Pending reconnect timer handle so we never stack two loops. */
|
|
86
|
+
reconnectTimer = null;
|
|
87
|
+
/** Base + cap for the exponential backoff (with jitter). */
|
|
88
|
+
static RECONNECT_BASE_MS = 1000;
|
|
89
|
+
static RECONNECT_MAX_MS = 30_000;
|
|
90
|
+
constructor(config, bus) {
|
|
91
|
+
this.config = config;
|
|
92
|
+
this.bus = bus;
|
|
93
|
+
}
|
|
94
|
+
/** The base URL the API client talks to. Renderers read this to build
|
|
95
|
+
* out-of-band URLs (e.g. the OAuth `/auth/:provider/start` redirect). */
|
|
96
|
+
getEndpoint() {
|
|
97
|
+
return this.config.endpoint;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Re-emit the current connection state on the bus. Used after the renderer
|
|
101
|
+
* mounts (and therefore registers its connection-state listener) to make
|
|
102
|
+
* sure it sees connections that completed during pre-mount bootstrap.
|
|
103
|
+
*/
|
|
104
|
+
replayConnectionState() {
|
|
105
|
+
if (this.connected && this.currentTransport) {
|
|
106
|
+
this.bus.emit('connection:open', { transport: this.currentTransport });
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
this.bus.emit('connection:closed', {});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
setToken(token) {
|
|
113
|
+
this.config.token = token;
|
|
114
|
+
// Force a reconnect so the new token is used in the WS query string.
|
|
115
|
+
this.disconnect();
|
|
116
|
+
void this.connect();
|
|
117
|
+
}
|
|
118
|
+
/** Currently-attached bearer token, or null when anonymous. */
|
|
119
|
+
getToken() {
|
|
120
|
+
return this.config.token;
|
|
121
|
+
}
|
|
122
|
+
async connect() {
|
|
123
|
+
// A fresh connect cancels any in-flight backoff loop and re-opens
|
|
124
|
+
// the auto-reconnect gate that `disconnect()` may have closed.
|
|
125
|
+
this.intentionalClose = false;
|
|
126
|
+
if (this.reconnectTimer) {
|
|
127
|
+
clearTimeout(this.reconnectTimer);
|
|
128
|
+
this.reconnectTimer = null;
|
|
129
|
+
}
|
|
130
|
+
const kind = this.config.transport ?? 'auto';
|
|
131
|
+
if (kind === 'ws' || kind === 'auto') {
|
|
132
|
+
try {
|
|
133
|
+
await this.connectWs();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
if (kind === 'ws')
|
|
138
|
+
throw new Error('WebSocket connect failed');
|
|
139
|
+
// fall through to SSE
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (kind === 'sse' || kind === 'auto') {
|
|
143
|
+
try {
|
|
144
|
+
this.connectSse();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// fall through to REST-only
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// REST-only mode: no persistent connection. We're effectively done.
|
|
152
|
+
this.connected = true;
|
|
153
|
+
this.currentTransport = 'rest';
|
|
154
|
+
this.bus.emit('connection:open', { transport: 'rest' });
|
|
155
|
+
}
|
|
156
|
+
disconnect() {
|
|
157
|
+
// Deliberate teardown: suppress the auto-reconnect loop and cancel
|
|
158
|
+
// any pending backoff timer so a logout / token swap doesn't
|
|
159
|
+
// immediately re-dial the old session.
|
|
160
|
+
this.intentionalClose = true;
|
|
161
|
+
if (this.reconnectTimer) {
|
|
162
|
+
clearTimeout(this.reconnectTimer);
|
|
163
|
+
this.reconnectTimer = null;
|
|
164
|
+
}
|
|
165
|
+
if (this.socket) {
|
|
166
|
+
this.socket.close();
|
|
167
|
+
this.socket = null;
|
|
168
|
+
}
|
|
169
|
+
if (this.sse) {
|
|
170
|
+
this.sse.close();
|
|
171
|
+
this.sse = null;
|
|
172
|
+
}
|
|
173
|
+
this.connected = false;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Force an immediate reconnect attempt, bypassing the backoff delay.
|
|
177
|
+
* Used by the connection pill's click-to-retry while offline (CR-8).
|
|
178
|
+
* No-op when a healthy socket is already open or a deliberate
|
|
179
|
+
* teardown is in effect.
|
|
180
|
+
*/
|
|
181
|
+
retryNow() {
|
|
182
|
+
if (this.intentionalClose)
|
|
183
|
+
return;
|
|
184
|
+
if (this.socket && this.socket.readyState === this.socket.OPEN)
|
|
185
|
+
return;
|
|
186
|
+
if (this.reconnectTimer) {
|
|
187
|
+
clearTimeout(this.reconnectTimer);
|
|
188
|
+
this.reconnectTimer = null;
|
|
189
|
+
}
|
|
190
|
+
this.reconnectAttempts = 0;
|
|
191
|
+
void this.connect();
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Schedule a WebSocket re-dial with exponential backoff + jitter
|
|
195
|
+
* (1s → 2s → 4s … capped at 30s). Gated on `intentionalClose` so a
|
|
196
|
+
* deliberate teardown never reconnects. Emits `connection:reconnecting`
|
|
197
|
+
* before each attempt so the connection pill can show the
|
|
198
|
+
* reconnecting state. On a successful re-open the existing
|
|
199
|
+
* `desiredTopics` flush (in `connectWs`'s onopen) re-subscribes every
|
|
200
|
+
* topic, and `reconnectAttempts` resets to 0.
|
|
201
|
+
*/
|
|
202
|
+
scheduleReconnect() {
|
|
203
|
+
if (this.intentionalClose)
|
|
204
|
+
return;
|
|
205
|
+
// Only the WS transport auto-reconnects; SSE has its own browser-
|
|
206
|
+
// managed retry, and REST has no persistent connection.
|
|
207
|
+
if (this.config.transport && this.config.transport !== 'auto' && this.config.transport !== 'ws') {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (this.reconnectTimer)
|
|
211
|
+
return; // a loop is already armed
|
|
212
|
+
const attempt = this.reconnectAttempts + 1;
|
|
213
|
+
const expo = ApiClient.RECONNECT_BASE_MS * 2 ** (attempt - 1);
|
|
214
|
+
const capped = Math.min(expo, ApiClient.RECONNECT_MAX_MS);
|
|
215
|
+
// Full jitter on the upper half of the window keeps a fleet of
|
|
216
|
+
// widgets from reconnecting in lockstep after a server restart.
|
|
217
|
+
const delayMs = Math.round(capped / 2 + Math.random() * (capped / 2));
|
|
218
|
+
this.bus.emit('connection:reconnecting', { attempt, delayMs });
|
|
219
|
+
this.reconnectTimer = setTimeout(() => {
|
|
220
|
+
this.reconnectTimer = null;
|
|
221
|
+
this.reconnectAttempts = attempt;
|
|
222
|
+
this.connect().catch(() => {
|
|
223
|
+
// connect() already falls WS→SSE→REST internally; if it
|
|
224
|
+
// still rejected, arm the next backoff step.
|
|
225
|
+
this.scheduleReconnect();
|
|
226
|
+
});
|
|
227
|
+
}, delayMs);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Send a typed command/query. Goes over WS when available, otherwise REST.
|
|
231
|
+
* Always returns a Promise; subscriptions are managed separately.
|
|
232
|
+
*/
|
|
233
|
+
async call(method, params) {
|
|
234
|
+
if (this.socket && this.socket.readyState === this.socket.OPEN) {
|
|
235
|
+
return this.callWs(method, params);
|
|
236
|
+
}
|
|
237
|
+
return this.callRest(method, params);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Subscribe this socket to a server-side broadcast topic (e.g.
|
|
241
|
+
* `chat:page:<pageId>`). Returns when the server acks the
|
|
242
|
+
* subscribe; rejects on FORBIDDEN.
|
|
243
|
+
*
|
|
244
|
+
* The topic is remembered in `desiredTopics` so a transport bounce
|
|
245
|
+
* automatically re-subscribes after reconnect — callers don't have
|
|
246
|
+
* to re-arm anything on `connection:open`. Likewise, calling
|
|
247
|
+
* subscribeTopic BEFORE the initial WS handshake completes is
|
|
248
|
+
* fine: the topic queues here and the on-open handler flushes it
|
|
249
|
+
* once the socket is up. The returned Promise resolves whenever
|
|
250
|
+
* the eventual ack lands (or times out after ~30s).
|
|
251
|
+
*
|
|
252
|
+
* Events still arrive via the `EventBus`; callers filter by event
|
|
253
|
+
* name + scope/id. This method only handles the server-side
|
|
254
|
+
* permission handshake.
|
|
255
|
+
*/
|
|
256
|
+
async subscribeTopic(topic, extras) {
|
|
257
|
+
if (!topic)
|
|
258
|
+
throw new Error('subscribeTopic: empty topic');
|
|
259
|
+
this.desiredTopics.add(topic);
|
|
260
|
+
if (extras)
|
|
261
|
+
this.topicExtras.set(topic, extras);
|
|
262
|
+
else
|
|
263
|
+
this.topicExtras.delete(topic);
|
|
264
|
+
if (this.socket && this.socket.readyState === this.socket.OPEN) {
|
|
265
|
+
return this.sendSubscribeFrame(topic);
|
|
266
|
+
}
|
|
267
|
+
// WS not connected yet (or has dropped). Register an ack waiter;
|
|
268
|
+
// the on-open handler will send the subscribe frame and the
|
|
269
|
+
// `subscribed` reply will resolve us. Use a longer timeout than
|
|
270
|
+
// the per-frame one since we're also waiting for the initial
|
|
271
|
+
// transport handshake.
|
|
272
|
+
return this.queueTopicAck(topic, 30_000);
|
|
273
|
+
}
|
|
274
|
+
/** Drop this socket from a topic. Idempotent. */
|
|
275
|
+
unsubscribeTopic(topic) {
|
|
276
|
+
this.desiredTopics.delete(topic);
|
|
277
|
+
if (this.socket && this.socket.readyState === this.socket.OPEN) {
|
|
278
|
+
this.socket.send(JSON.stringify({ type: 'unsubscribe', topic }));
|
|
279
|
+
}
|
|
280
|
+
// Reject any still-pending ack waiters for this topic — they'll
|
|
281
|
+
// never receive a subscribed ack after an unsubscribe.
|
|
282
|
+
const waiters = this.pendingTopicAcks.get(topic);
|
|
283
|
+
if (waiters) {
|
|
284
|
+
this.pendingTopicAcks.delete(topic);
|
|
285
|
+
for (const w of waiters) {
|
|
286
|
+
clearTimeout(w.timer);
|
|
287
|
+
w.reject(new Error(`unsubscribed before ack: ${topic}`));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// --- WS ---
|
|
292
|
+
connectWs() {
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
const wsUrl = this.config.endpoint.replace(/^http/, 'ws') +
|
|
295
|
+
'/ws' +
|
|
296
|
+
`?projectId=${encodeURIComponent(this.config.projectId)}` +
|
|
297
|
+
(this.config.token ? `&token=${encodeURIComponent(this.config.token)}` : '');
|
|
298
|
+
const sock = new WebSocket(wsUrl);
|
|
299
|
+
const t = setTimeout(() => {
|
|
300
|
+
sock.close();
|
|
301
|
+
reject(new Error('WS connect timeout'));
|
|
302
|
+
}, 4000);
|
|
303
|
+
sock.onopen = () => {
|
|
304
|
+
clearTimeout(t);
|
|
305
|
+
this.socket = sock;
|
|
306
|
+
this.connected = true;
|
|
307
|
+
this.currentTransport = 'ws';
|
|
308
|
+
// Clean open — reset the backoff ladder so the *next* drop
|
|
309
|
+
// starts again at the 1s base rather than wherever the last
|
|
310
|
+
// reconnect storm left off.
|
|
311
|
+
this.reconnectAttempts = 0;
|
|
312
|
+
this.bus.emit('connection:open', { transport: 'ws' });
|
|
313
|
+
// Re-arm topic subscriptions across reconnects. We swallow
|
|
314
|
+
// per-topic failures (server may legitimately deny after a
|
|
315
|
+
// permission change) — the topic will simply drop out.
|
|
316
|
+
for (const topic of this.desiredTopics) {
|
|
317
|
+
void this.sendSubscribeFrame(topic).catch(() => undefined);
|
|
318
|
+
}
|
|
319
|
+
resolve();
|
|
320
|
+
};
|
|
321
|
+
sock.onerror = () => {
|
|
322
|
+
clearTimeout(t);
|
|
323
|
+
reject(new Error('WS connect error'));
|
|
324
|
+
};
|
|
325
|
+
sock.onclose = () => {
|
|
326
|
+
this.connected = false;
|
|
327
|
+
// Drop the dead socket reference so `call()` routes over REST
|
|
328
|
+
// (and a stale `this.socket` can't be re-used) until the
|
|
329
|
+
// reconnect loop installs a fresh one.
|
|
330
|
+
if (this.socket === sock)
|
|
331
|
+
this.socket = null;
|
|
332
|
+
this.bus.emit('connection:closed', { transport: 'ws' });
|
|
333
|
+
// Re-arm pending topic-ack timers with a generous reconnect
|
|
334
|
+
// window. Their original 8s per-frame timers were anchored
|
|
335
|
+
// to a now-dead socket; without this they'd fire mid-
|
|
336
|
+
// reconnect and reject as "subscribe timeout" — which
|
|
337
|
+
// surfaces in the chat panel as "Could not reach the chat
|
|
338
|
+
// server" even though the next WS open would have flushed
|
|
339
|
+
// the subscribe cleanly.
|
|
340
|
+
for (const [topic, waiters] of this.pendingTopicAcks) {
|
|
341
|
+
for (const w of waiters) {
|
|
342
|
+
clearTimeout(w.timer);
|
|
343
|
+
w.timer = this.armAckTimer(topic, 60_000, w.reject);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// Auto-reconnect with exponential backoff unless this was a
|
|
347
|
+
// deliberate teardown. The reconnected socket's onopen
|
|
348
|
+
// flushes desiredTopics so chat rooms / topic streams
|
|
349
|
+
// resume without any caller re-arming anything.
|
|
350
|
+
this.scheduleReconnect();
|
|
351
|
+
};
|
|
352
|
+
sock.onmessage = (e) => this.handleWsMessage(e.data);
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
callWs(method, params) {
|
|
356
|
+
return new Promise((resolve, reject) => {
|
|
357
|
+
const id = String(this.nextId++);
|
|
358
|
+
this.pending.set(id, { resolve, reject });
|
|
359
|
+
this.socket.send(JSON.stringify({ type: 'request', id, method, params }));
|
|
360
|
+
// 30s timeout — tunable later.
|
|
361
|
+
setTimeout(() => {
|
|
362
|
+
if (this.pending.has(id)) {
|
|
363
|
+
this.pending.delete(id);
|
|
364
|
+
reject(new Error(`Request timeout: ${method}`));
|
|
365
|
+
}
|
|
366
|
+
}, 30_000);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
sendSubscribeFrame(topic) {
|
|
370
|
+
return new Promise((resolve, reject) => {
|
|
371
|
+
const timer = this.armAckTimer(topic, 8000, reject);
|
|
372
|
+
const waiters = this.pendingTopicAcks.get(topic) ?? [];
|
|
373
|
+
waiters.push({ resolve, reject, timer });
|
|
374
|
+
this.pendingTopicAcks.set(topic, waiters);
|
|
375
|
+
const extras = this.topicExtras.get(topic);
|
|
376
|
+
this.socket.send(JSON.stringify({ type: 'subscribe', topic, ...(extras ?? {}) }));
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/** Park a topic-subscribe in the pending map without sending a
|
|
380
|
+
* frame yet — the next `connection:open` handler will send the
|
|
381
|
+
* subscribe and the `subscribed` ack resolves us. */
|
|
382
|
+
queueTopicAck(topic, timeoutMs) {
|
|
383
|
+
return new Promise((resolve, reject) => {
|
|
384
|
+
const timer = this.armAckTimer(topic, timeoutMs, reject);
|
|
385
|
+
const waiters = this.pendingTopicAcks.get(topic) ?? [];
|
|
386
|
+
waiters.push({ resolve, reject, timer });
|
|
387
|
+
this.pendingTopicAcks.set(topic, waiters);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
/** Shared timer-cleanup for both immediate-send and queued
|
|
391
|
+
* subscribe paths. */
|
|
392
|
+
armAckTimer(topic, timeoutMs, reject) {
|
|
393
|
+
const timer = setTimeout(() => {
|
|
394
|
+
const list = this.pendingTopicAcks.get(topic);
|
|
395
|
+
if (!list)
|
|
396
|
+
return;
|
|
397
|
+
const idx = list.findIndex((w) => w.timer === timer);
|
|
398
|
+
if (idx >= 0) {
|
|
399
|
+
list.splice(idx, 1);
|
|
400
|
+
if (list.length === 0)
|
|
401
|
+
this.pendingTopicAcks.delete(topic);
|
|
402
|
+
}
|
|
403
|
+
reject(new Error(`subscribe timeout: ${topic}`));
|
|
404
|
+
}, timeoutMs);
|
|
405
|
+
return timer;
|
|
406
|
+
}
|
|
407
|
+
resolveTopicAck(topic, ok, error) {
|
|
408
|
+
const waiters = this.pendingTopicAcks.get(topic);
|
|
409
|
+
if (!waiters)
|
|
410
|
+
return;
|
|
411
|
+
this.pendingTopicAcks.delete(topic);
|
|
412
|
+
for (const w of waiters) {
|
|
413
|
+
clearTimeout(w.timer);
|
|
414
|
+
if (ok)
|
|
415
|
+
w.resolve();
|
|
416
|
+
else
|
|
417
|
+
w.reject(new RpcError(error?.code ?? 'FORBIDDEN', error?.message ?? 'subscribe denied'));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
handleWsMessage(raw) {
|
|
421
|
+
let msg;
|
|
422
|
+
try {
|
|
423
|
+
msg = JSON.parse(typeof raw === 'string' ? raw : raw.toString());
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (msg.type === 'response') {
|
|
429
|
+
const pending = this.pending.get(msg.id);
|
|
430
|
+
if (!pending)
|
|
431
|
+
return;
|
|
432
|
+
this.pending.delete(msg.id);
|
|
433
|
+
if (msg.error)
|
|
434
|
+
pending.reject(new RpcError(msg.error.code, msg.error.message));
|
|
435
|
+
else
|
|
436
|
+
pending.resolve(msg.result);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (msg.type === 'subscribed') {
|
|
440
|
+
this.resolveTopicAck(msg.topic, !!msg.ok, msg.error);
|
|
441
|
+
// On a denied subscribe, drop the topic from desiredTopics so
|
|
442
|
+
// we don't keep re-trying after every reconnect.
|
|
443
|
+
if (!msg.ok)
|
|
444
|
+
this.desiredTopics.delete(msg.topic);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (msg.type === 'unsubscribed') {
|
|
448
|
+
// Silent — desiredTopics already updated client-side.
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (msg.type === 'event') {
|
|
452
|
+
const event = msg.event;
|
|
453
|
+
this.bus.emit(event.name, event);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// --- SSE ---
|
|
457
|
+
connectSse() {
|
|
458
|
+
const url = `${this.config.endpoint}/api/v1/events?projectId=${encodeURIComponent(this.config.projectId)}`;
|
|
459
|
+
this.sse = new EventSource(url, { withCredentials: true });
|
|
460
|
+
this.sse.onopen = () => {
|
|
461
|
+
this.connected = true;
|
|
462
|
+
this.currentTransport = 'sse';
|
|
463
|
+
this.bus.emit('connection:open', { transport: 'sse' });
|
|
464
|
+
};
|
|
465
|
+
this.sse.onerror = () => {
|
|
466
|
+
this.connected = false;
|
|
467
|
+
this.bus.emit('connection:closed', { transport: 'sse' });
|
|
468
|
+
};
|
|
469
|
+
// Each named server event arrives as its own SSE `event:` line, so
|
|
470
|
+
// we register one listener per name. The list is derived from the
|
|
471
|
+
// shared `ServerEvent['name']` union (SERVER_EVENT_NAMES, guarded
|
|
472
|
+
// for exhaustiveness at compile time) — the WS path re-emits any
|
|
473
|
+
// event generically, and this keeps SSE-only clients (WS-blocking
|
|
474
|
+
// proxies, some mobile webviews) at parity without a server change.
|
|
475
|
+
const reemit = (e) => {
|
|
476
|
+
try {
|
|
477
|
+
const event = JSON.parse(e.data);
|
|
478
|
+
this.bus.emit(event.name, event);
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
// bad frame — ignore
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
for (const name of SERVER_EVENT_NAMES) {
|
|
485
|
+
this.sse.addEventListener(name, (e) => reemit(e));
|
|
486
|
+
}
|
|
487
|
+
// Belt-and-suspenders: if the server ever emits a bare `data:` frame
|
|
488
|
+
// (no `event:` line) or a name we haven't listed, the default
|
|
489
|
+
// `message` handler still re-emits by the payload's own `name`.
|
|
490
|
+
this.sse.onmessage = (e) => reemit(e);
|
|
491
|
+
}
|
|
492
|
+
// --- REST ---
|
|
493
|
+
async callRest(method, params) {
|
|
494
|
+
const headers = {
|
|
495
|
+
'content-type': 'application/json',
|
|
496
|
+
'x-pageloop-project': this.config.projectId,
|
|
497
|
+
};
|
|
498
|
+
if (this.config.token)
|
|
499
|
+
headers.authorization = `Bearer ${this.config.token}`;
|
|
500
|
+
const res = await fetch(`${this.config.endpoint}/api/v1/rpc`, {
|
|
501
|
+
method: 'POST',
|
|
502
|
+
headers,
|
|
503
|
+
body: JSON.stringify({ method, params, projectId: this.config.projectId }),
|
|
504
|
+
});
|
|
505
|
+
const json = await res.json();
|
|
506
|
+
if (!res.ok || json.error) {
|
|
507
|
+
throw new RpcError(json.error?.code ?? 'HTTP', json.error?.message ?? res.statusText, res.status);
|
|
508
|
+
}
|
|
509
|
+
return json.result;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
//# sourceMappingURL=ApiClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiClient.js","sourceRoot":"","sources":["../src/ApiClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { Comment, Reply, Reaction, ReactionTargetKind, Anchor } from '@pageloop/shared';
|
|
2
|
+
import { ApiClient } from './ApiClient.js';
|
|
3
|
+
import { EventBus } from './EventBus.js';
|
|
4
|
+
/**
|
|
5
|
+
* Local comment store + CRUD API. Keeps in-memory maps keyed by ids, listens
|
|
6
|
+
* to server events to stay in sync, and emits client-side `comment:*` /
|
|
7
|
+
* `reply:*` / `reaction:*` events for UI modules.
|
|
8
|
+
*
|
|
9
|
+
* Replies and reactions are loaded lazily — most comments don't have either,
|
|
10
|
+
* and pre-fetching them all on bootstrap would inflate the payload.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CommentEngine {
|
|
13
|
+
private readonly api;
|
|
14
|
+
private readonly bus;
|
|
15
|
+
private readonly byPage;
|
|
16
|
+
/** commentId → replies. Populated on demand by loadReplies. */
|
|
17
|
+
private readonly repliesByComment;
|
|
18
|
+
/** "${kind}:${id}" → reactions. Populated on demand or via the reactions.changed event. */
|
|
19
|
+
private readonly reactionsByTarget;
|
|
20
|
+
constructor(api: ApiClient, bus: EventBus);
|
|
21
|
+
load(pageId: string): Comment[];
|
|
22
|
+
loadFromServer(pageId: string): Promise<Comment[]>;
|
|
23
|
+
/** Create a comment + immediately fold the response into the local
|
|
24
|
+
* cache so the sidebar / bubble layer paint without waiting for
|
|
25
|
+
* a WS broadcast echo (comments.* events currently route to no
|
|
26
|
+
* topic, so the echo never arrives — the local-emit closes the
|
|
27
|
+
* refresh-required gap). */
|
|
28
|
+
create(input: {
|
|
29
|
+
trackedPageId: string;
|
|
30
|
+
/** Null/omitted for a "general" (non-anchored, page-level) comment. */
|
|
31
|
+
anchor?: Anchor | null;
|
|
32
|
+
bodyMd: string;
|
|
33
|
+
/** Required when the user is anonymous and the project allows public comments. */
|
|
34
|
+
guestName?: string;
|
|
35
|
+
}): Promise<Comment>;
|
|
36
|
+
/** Append a new comment to the per-page cache + fire the derived
|
|
37
|
+
* `comment:created` event. De-dupes against the WS-arrival path
|
|
38
|
+
* so a future fan-out wouldn't double-render. */
|
|
39
|
+
private applyLocalCreate;
|
|
40
|
+
/** Edit a comment + fold the updated row into the local cache so the
|
|
41
|
+
* editor's own change repaints immediately. Same rationale as
|
|
42
|
+
* `create`/`delete`: `comments.updated` doesn't route back to the
|
|
43
|
+
* editing connection today, so without this local-emit the edited
|
|
44
|
+
* body/status sits stale until an unrelated refresh. */
|
|
45
|
+
update(input: {
|
|
46
|
+
commentId: string;
|
|
47
|
+
bodyMd?: string;
|
|
48
|
+
status?: Comment['status'];
|
|
49
|
+
}): Promise<Comment>;
|
|
50
|
+
/** Promote/demote a comment to/from a task, or toggle its completion.
|
|
51
|
+
* Gated server-side on write access to the page (any commenter). Folds
|
|
52
|
+
* the result into the local cache so the sidebar repaints immediately. */
|
|
53
|
+
setTask(input: {
|
|
54
|
+
commentId: string;
|
|
55
|
+
isTask?: boolean;
|
|
56
|
+
resolved?: boolean;
|
|
57
|
+
}): Promise<Comment>;
|
|
58
|
+
/** Replace a cached comment in place + fire `comment:updated`. Locates
|
|
59
|
+
* the page bucket by the comment's own `trackedPageId`; de-dupes so a
|
|
60
|
+
* later WS echo re-renders with identical data instead of duplicating. */
|
|
61
|
+
private applyLocalUpdate;
|
|
62
|
+
delete(commentId: string): Promise<void>;
|
|
63
|
+
/** Mirror of `applyLocalCreate` for deletes. The WS `comments.deleted`
|
|
64
|
+
* fan-out doesn't route to the deleter's connection right now, so
|
|
65
|
+
* without this local-emit the sidebar + bubble layer hold the
|
|
66
|
+
* stale row until a page reload. Walks every cached page bucket
|
|
67
|
+
* to find the comment because `delete()` doesn't take a page id. */
|
|
68
|
+
private applyLocalDelete;
|
|
69
|
+
/** Post a reply + fold it into the cached reply list so the thread
|
|
70
|
+
* paints without waiting for a (currently-unrouted) `replies.created`
|
|
71
|
+
* echo. Mirrors the comment-create local-emit pattern. */
|
|
72
|
+
reply(commentId: string, bodyMd: string, guestName?: string): Promise<Reply>;
|
|
73
|
+
/** Edit a reply + repaint locally. */
|
|
74
|
+
updateReply(replyId: string, bodyMd: string): Promise<Reply>;
|
|
75
|
+
/** Delete a reply + drop it from the cached list locally. `replyId` is
|
|
76
|
+
* all we have, so walk every cached comment's reply list to find it. */
|
|
77
|
+
deleteReply(replyId: string): Promise<void>;
|
|
78
|
+
/** Append a reply to its comment's cached list (de-duped) + emit
|
|
79
|
+
* `reply:created`. Only mutates if the list was already loaded; an
|
|
80
|
+
* unloaded thread will fetch fresh on next open. */
|
|
81
|
+
private applyLocalReplyCreate;
|
|
82
|
+
/** Replace a reply in place within its comment's cached list + emit
|
|
83
|
+
* `reply:updated`. */
|
|
84
|
+
private applyLocalReplyUpdate;
|
|
85
|
+
/** Remove a reply from whichever comment bucket holds it + emit
|
|
86
|
+
* `reply:deleted` with the owning commentId so the sidebar can target
|
|
87
|
+
* the right thread. */
|
|
88
|
+
private applyLocalReplyDelete;
|
|
89
|
+
/** Cached replies for a comment (or `null` if never loaded). */
|
|
90
|
+
loadedReplies(commentId: string): Reply[] | null;
|
|
91
|
+
/** Force-fetch replies for a comment from the server. */
|
|
92
|
+
fetchReplies(commentId: string): Promise<Reply[]>;
|
|
93
|
+
/** Cached reactions for a comment or reply (may be stale; use `fetchReactions` to refresh). */
|
|
94
|
+
loadedReactions(targetKind: ReactionTargetKind, targetId: string): Reaction[];
|
|
95
|
+
fetchReactions(targetKind: ReactionTargetKind, targetId: string): Promise<Reaction[]>;
|
|
96
|
+
/**
|
|
97
|
+
* Toggle a reaction on a comment or reply. Returns the new reaction list
|
|
98
|
+
* so callers can update UI without waiting for the WS broadcast (the
|
|
99
|
+
* event also fires asynchronously).
|
|
100
|
+
*/
|
|
101
|
+
toggleReaction(targetKind: ReactionTargetKind, targetId: string, kind: Reaction['kind']): Promise<Reaction[]>;
|
|
102
|
+
/** Bulk-clear all comments on a page (editor+ only). Locally drops
|
|
103
|
+
* the cached list + fires `comment:bulkDeleted` so the renderer
|
|
104
|
+
* repaints without waiting for a (currently-unrouted) WS echo. */
|
|
105
|
+
bulkDeleteForPage(trackedPageId: string): Promise<{
|
|
106
|
+
deleted: number;
|
|
107
|
+
}>;
|
|
108
|
+
/** Replace the cached list (used from bootstrap). */
|
|
109
|
+
prime(pageId: string, comments: Comment[]): void;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=CommentEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommentEngine.d.ts","sourceRoot":"","sources":["../src/CommentEngine.ts"],"names":[],"mappings":"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"}
|