@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,277 @@
|
|
|
1
|
+
import { ApiClient } from './ApiClient.js';
|
|
2
|
+
import { EventBus } from './EventBus.js';
|
|
3
|
+
/**
|
|
4
|
+
* Local comment store + CRUD API. Keeps in-memory maps keyed by ids, listens
|
|
5
|
+
* to server events to stay in sync, and emits client-side `comment:*` /
|
|
6
|
+
* `reply:*` / `reaction:*` events for UI modules.
|
|
7
|
+
*
|
|
8
|
+
* Replies and reactions are loaded lazily — most comments don't have either,
|
|
9
|
+
* and pre-fetching them all on bootstrap would inflate the payload.
|
|
10
|
+
*/
|
|
11
|
+
export class CommentEngine {
|
|
12
|
+
api;
|
|
13
|
+
bus;
|
|
14
|
+
byPage = new Map();
|
|
15
|
+
/** commentId → replies. Populated on demand by loadReplies. */
|
|
16
|
+
repliesByComment = new Map();
|
|
17
|
+
/** "${kind}:${id}" → reactions. Populated on demand or via the reactions.changed event. */
|
|
18
|
+
reactionsByTarget = new Map();
|
|
19
|
+
constructor(api, bus) {
|
|
20
|
+
this.api = api;
|
|
21
|
+
this.bus = bus;
|
|
22
|
+
bus.on('comments.created', (event) => {
|
|
23
|
+
const c = event.comment;
|
|
24
|
+
const list = this.byPage.get(c.trackedPageId) ?? [];
|
|
25
|
+
if (!list.find((x) => x.id === c.id))
|
|
26
|
+
list.push(c);
|
|
27
|
+
this.byPage.set(c.trackedPageId, list);
|
|
28
|
+
this.bus.emit('comment:created', c);
|
|
29
|
+
});
|
|
30
|
+
bus.on('comments.updated', (event) => {
|
|
31
|
+
const c = event.comment;
|
|
32
|
+
const list = this.byPage.get(c.trackedPageId) ?? [];
|
|
33
|
+
const idx = list.findIndex((x) => x.id === c.id);
|
|
34
|
+
if (idx >= 0)
|
|
35
|
+
list[idx] = c;
|
|
36
|
+
this.byPage.set(c.trackedPageId, list);
|
|
37
|
+
this.bus.emit('comment:updated', c);
|
|
38
|
+
});
|
|
39
|
+
bus.on('comments.deleted', (event) => {
|
|
40
|
+
const list = this.byPage.get(event.trackedPageId) ?? [];
|
|
41
|
+
const next = list.filter((x) => x.id !== event.commentId);
|
|
42
|
+
this.byPage.set(event.trackedPageId, next);
|
|
43
|
+
this.repliesByComment.delete(event.commentId);
|
|
44
|
+
this.reactionsByTarget.delete(`comment:${event.commentId}`);
|
|
45
|
+
this.bus.emit('comment:deleted', { id: event.commentId });
|
|
46
|
+
});
|
|
47
|
+
bus.on('comments.bulkDeleted', (event) => {
|
|
48
|
+
this.byPage.set(event.trackedPageId, []);
|
|
49
|
+
this.bus.emit('comment:bulkDeleted', event);
|
|
50
|
+
});
|
|
51
|
+
bus.on('replies.created', (event) => {
|
|
52
|
+
const reply = event.reply;
|
|
53
|
+
const existing = this.repliesByComment.get(reply.commentId) ?? [];
|
|
54
|
+
if (!existing.find((r) => r.id === reply.id))
|
|
55
|
+
existing.push(reply);
|
|
56
|
+
this.repliesByComment.set(reply.commentId, existing);
|
|
57
|
+
this.bus.emit('reply:created', reply);
|
|
58
|
+
});
|
|
59
|
+
bus.on('replies.updated', (event) => {
|
|
60
|
+
const reply = event.reply;
|
|
61
|
+
const existing = this.repliesByComment.get(reply.commentId);
|
|
62
|
+
if (existing) {
|
|
63
|
+
const idx = existing.findIndex((r) => r.id === reply.id);
|
|
64
|
+
if (idx >= 0)
|
|
65
|
+
existing[idx] = reply;
|
|
66
|
+
this.repliesByComment.set(reply.commentId, existing);
|
|
67
|
+
}
|
|
68
|
+
this.bus.emit('reply:updated', reply);
|
|
69
|
+
});
|
|
70
|
+
bus.on('replies.deleted', (event) => {
|
|
71
|
+
const list = this.repliesByComment.get(event.commentId);
|
|
72
|
+
if (list) {
|
|
73
|
+
this.repliesByComment.set(event.commentId, list.filter((r) => r.id !== event.replyId));
|
|
74
|
+
}
|
|
75
|
+
this.bus.emit('reply:deleted', { id: event.replyId, commentId: event.commentId });
|
|
76
|
+
});
|
|
77
|
+
bus.on('reactions.changed', (event) => {
|
|
78
|
+
this.reactionsByTarget.set(`${event.targetKind}:${event.targetId}`, event.reactions);
|
|
79
|
+
this.bus.emit('reaction:changed', event);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
load(pageId) {
|
|
83
|
+
return this.byPage.get(pageId) ?? [];
|
|
84
|
+
}
|
|
85
|
+
async loadFromServer(pageId) {
|
|
86
|
+
const comments = await this.api.call('comments.list', { trackedPageId: pageId });
|
|
87
|
+
this.byPage.set(pageId, comments);
|
|
88
|
+
this.bus.emit('comments:loaded', { pageId, comments });
|
|
89
|
+
return comments;
|
|
90
|
+
}
|
|
91
|
+
/** Create a comment + immediately fold the response into the local
|
|
92
|
+
* cache so the sidebar / bubble layer paint without waiting for
|
|
93
|
+
* a WS broadcast echo (comments.* events currently route to no
|
|
94
|
+
* topic, so the echo never arrives — the local-emit closes the
|
|
95
|
+
* refresh-required gap). */
|
|
96
|
+
async create(input) {
|
|
97
|
+
const created = await this.api.call('comments.create', input);
|
|
98
|
+
this.applyLocalCreate(created);
|
|
99
|
+
return created;
|
|
100
|
+
}
|
|
101
|
+
/** Append a new comment to the per-page cache + fire the derived
|
|
102
|
+
* `comment:created` event. De-dupes against the WS-arrival path
|
|
103
|
+
* so a future fan-out wouldn't double-render. */
|
|
104
|
+
applyLocalCreate(c) {
|
|
105
|
+
const list = this.byPage.get(c.trackedPageId) ?? [];
|
|
106
|
+
if (!list.find((x) => x.id === c.id)) {
|
|
107
|
+
list.push(c);
|
|
108
|
+
this.byPage.set(c.trackedPageId, list);
|
|
109
|
+
}
|
|
110
|
+
this.bus.emit('comment:created', c);
|
|
111
|
+
}
|
|
112
|
+
/** Edit a comment + fold the updated row into the local cache so the
|
|
113
|
+
* editor's own change repaints immediately. Same rationale as
|
|
114
|
+
* `create`/`delete`: `comments.updated` doesn't route back to the
|
|
115
|
+
* editing connection today, so without this local-emit the edited
|
|
116
|
+
* body/status sits stale until an unrelated refresh. */
|
|
117
|
+
async update(input) {
|
|
118
|
+
const updated = await this.api.call('comments.update', input);
|
|
119
|
+
this.applyLocalUpdate(updated);
|
|
120
|
+
return updated;
|
|
121
|
+
}
|
|
122
|
+
/** Promote/demote a comment to/from a task, or toggle its completion.
|
|
123
|
+
* Gated server-side on write access to the page (any commenter). Folds
|
|
124
|
+
* the result into the local cache so the sidebar repaints immediately. */
|
|
125
|
+
async setTask(input) {
|
|
126
|
+
const updated = await this.api.call('comments.setTask', input);
|
|
127
|
+
this.applyLocalUpdate(updated);
|
|
128
|
+
return updated;
|
|
129
|
+
}
|
|
130
|
+
/** Replace a cached comment in place + fire `comment:updated`. Locates
|
|
131
|
+
* the page bucket by the comment's own `trackedPageId`; de-dupes so a
|
|
132
|
+
* later WS echo re-renders with identical data instead of duplicating. */
|
|
133
|
+
applyLocalUpdate(c) {
|
|
134
|
+
const list = this.byPage.get(c.trackedPageId) ?? [];
|
|
135
|
+
const idx = list.findIndex((x) => x.id === c.id);
|
|
136
|
+
if (idx >= 0)
|
|
137
|
+
list[idx] = c;
|
|
138
|
+
else
|
|
139
|
+
list.push(c);
|
|
140
|
+
this.byPage.set(c.trackedPageId, list);
|
|
141
|
+
this.bus.emit('comment:updated', c);
|
|
142
|
+
}
|
|
143
|
+
async delete(commentId) {
|
|
144
|
+
await this.api.call('comments.delete', { commentId });
|
|
145
|
+
this.applyLocalDelete(commentId);
|
|
146
|
+
}
|
|
147
|
+
/** Mirror of `applyLocalCreate` for deletes. The WS `comments.deleted`
|
|
148
|
+
* fan-out doesn't route to the deleter's connection right now, so
|
|
149
|
+
* without this local-emit the sidebar + bubble layer hold the
|
|
150
|
+
* stale row until a page reload. Walks every cached page bucket
|
|
151
|
+
* to find the comment because `delete()` doesn't take a page id. */
|
|
152
|
+
applyLocalDelete(commentId) {
|
|
153
|
+
let trackedPageId = null;
|
|
154
|
+
for (const [pageId, list] of this.byPage) {
|
|
155
|
+
if (list.some((c) => c.id === commentId)) {
|
|
156
|
+
trackedPageId = pageId;
|
|
157
|
+
this.byPage.set(pageId, list.filter((c) => c.id !== commentId));
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
this.repliesByComment.delete(commentId);
|
|
162
|
+
this.reactionsByTarget.delete(`comment:${commentId}`);
|
|
163
|
+
this.bus.emit('comment:deleted', { id: commentId, trackedPageId });
|
|
164
|
+
}
|
|
165
|
+
/** Post a reply + fold it into the cached reply list so the thread
|
|
166
|
+
* paints without waiting for a (currently-unrouted) `replies.created`
|
|
167
|
+
* echo. Mirrors the comment-create local-emit pattern. */
|
|
168
|
+
async reply(commentId, bodyMd, guestName) {
|
|
169
|
+
const created = await this.api.call('replies.create', { commentId, bodyMd, guestName });
|
|
170
|
+
this.applyLocalReplyCreate(created);
|
|
171
|
+
return created;
|
|
172
|
+
}
|
|
173
|
+
/** Edit a reply + repaint locally. */
|
|
174
|
+
async updateReply(replyId, bodyMd) {
|
|
175
|
+
const updated = await this.api.call('replies.update', { replyId, bodyMd });
|
|
176
|
+
this.applyLocalReplyUpdate(updated);
|
|
177
|
+
return updated;
|
|
178
|
+
}
|
|
179
|
+
/** Delete a reply + drop it from the cached list locally. `replyId` is
|
|
180
|
+
* all we have, so walk every cached comment's reply list to find it. */
|
|
181
|
+
async deleteReply(replyId) {
|
|
182
|
+
await this.api.call('replies.delete', { replyId });
|
|
183
|
+
this.applyLocalReplyDelete(replyId);
|
|
184
|
+
}
|
|
185
|
+
/** Append a reply to its comment's cached list (de-duped) + emit
|
|
186
|
+
* `reply:created`. Only mutates if the list was already loaded; an
|
|
187
|
+
* unloaded thread will fetch fresh on next open. */
|
|
188
|
+
applyLocalReplyCreate(reply) {
|
|
189
|
+
const existing = this.repliesByComment.get(reply.commentId);
|
|
190
|
+
if (existing) {
|
|
191
|
+
if (!existing.find((r) => r.id === reply.id))
|
|
192
|
+
existing.push(reply);
|
|
193
|
+
this.repliesByComment.set(reply.commentId, existing);
|
|
194
|
+
}
|
|
195
|
+
this.bus.emit('reply:created', reply);
|
|
196
|
+
}
|
|
197
|
+
/** Replace a reply in place within its comment's cached list + emit
|
|
198
|
+
* `reply:updated`. */
|
|
199
|
+
applyLocalReplyUpdate(reply) {
|
|
200
|
+
const existing = this.repliesByComment.get(reply.commentId);
|
|
201
|
+
if (existing) {
|
|
202
|
+
const idx = existing.findIndex((r) => r.id === reply.id);
|
|
203
|
+
if (idx >= 0)
|
|
204
|
+
existing[idx] = reply;
|
|
205
|
+
this.repliesByComment.set(reply.commentId, existing);
|
|
206
|
+
}
|
|
207
|
+
this.bus.emit('reply:updated', reply);
|
|
208
|
+
}
|
|
209
|
+
/** Remove a reply from whichever comment bucket holds it + emit
|
|
210
|
+
* `reply:deleted` with the owning commentId so the sidebar can target
|
|
211
|
+
* the right thread. */
|
|
212
|
+
applyLocalReplyDelete(replyId) {
|
|
213
|
+
let commentId = null;
|
|
214
|
+
for (const [cid, list] of this.repliesByComment) {
|
|
215
|
+
if (list.some((r) => r.id === replyId)) {
|
|
216
|
+
commentId = cid;
|
|
217
|
+
this.repliesByComment.set(cid, list.filter((r) => r.id !== replyId));
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
this.bus.emit('reply:deleted', { id: replyId, commentId });
|
|
222
|
+
}
|
|
223
|
+
/** Cached replies for a comment (or `null` if never loaded). */
|
|
224
|
+
loadedReplies(commentId) {
|
|
225
|
+
return this.repliesByComment.get(commentId) ?? null;
|
|
226
|
+
}
|
|
227
|
+
/** Force-fetch replies for a comment from the server. */
|
|
228
|
+
async fetchReplies(commentId) {
|
|
229
|
+
const replies = await this.api.call('replies.list', { commentId });
|
|
230
|
+
this.repliesByComment.set(commentId, replies);
|
|
231
|
+
return replies;
|
|
232
|
+
}
|
|
233
|
+
/** Cached reactions for a comment or reply (may be stale; use `fetchReactions` to refresh). */
|
|
234
|
+
loadedReactions(targetKind, targetId) {
|
|
235
|
+
return this.reactionsByTarget.get(`${targetKind}:${targetId}`) ?? [];
|
|
236
|
+
}
|
|
237
|
+
async fetchReactions(targetKind, targetId) {
|
|
238
|
+
const reactions = await this.api.call('reactions.list', { targetKind, targetId });
|
|
239
|
+
this.reactionsByTarget.set(`${targetKind}:${targetId}`, reactions);
|
|
240
|
+
return reactions;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Toggle a reaction on a comment or reply. Returns the new reaction list
|
|
244
|
+
* so callers can update UI without waiting for the WS broadcast (the
|
|
245
|
+
* event also fires asynchronously).
|
|
246
|
+
*/
|
|
247
|
+
async toggleReaction(targetKind, targetId, kind) {
|
|
248
|
+
const result = await this.api.call('reactions.toggle', { targetKind, targetId, kind });
|
|
249
|
+
this.reactionsByTarget.set(`${targetKind}:${targetId}`, result.reactions);
|
|
250
|
+
// Immediately notify listeners (renderer / chat panel) so the
|
|
251
|
+
// local tab repaints without waiting for the WS broadcast echo.
|
|
252
|
+
// The WS path also feeds this same channel — duplicate events
|
|
253
|
+
// are cheap (listeners just re-render with the same cached
|
|
254
|
+
// data) and the local emit guarantees the user who toggled
|
|
255
|
+
// sees their pill update.
|
|
256
|
+
this.bus.emit('reaction:changed', {
|
|
257
|
+
targetKind,
|
|
258
|
+
targetId,
|
|
259
|
+
reactions: result.reactions,
|
|
260
|
+
});
|
|
261
|
+
return result.reactions;
|
|
262
|
+
}
|
|
263
|
+
/** Bulk-clear all comments on a page (editor+ only). Locally drops
|
|
264
|
+
* the cached list + fires `comment:bulkDeleted` so the renderer
|
|
265
|
+
* repaints without waiting for a (currently-unrouted) WS echo. */
|
|
266
|
+
async bulkDeleteForPage(trackedPageId) {
|
|
267
|
+
const result = await this.api.call('comments.bulkDelete', { trackedPageId });
|
|
268
|
+
this.byPage.set(trackedPageId, []);
|
|
269
|
+
this.bus.emit('comment:bulkDeleted', { trackedPageId, deleted: result.deleted });
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
/** Replace the cached list (used from bootstrap). */
|
|
273
|
+
prime(pageId, comments) {
|
|
274
|
+
this.byPage.set(pageId, comments);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=CommentEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommentEngine.js","sourceRoot":"","sources":["../src/CommentEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { ServerEvent } from '@pageloop/shared';
|
|
2
|
+
export type ClientEventName = 'page:change' | 'page:resolved' | 'comments:loaded' | 'comment:created' | 'comment:updated' | 'comment:deleted' | 'comment:bulkDeleted' | 'reply:created' | 'reply:updated' | 'reply:deleted' | 'reaction:changed' | 'auth:changed' | 'project:updated' | 'connection:open' | 'connection:closed'
|
|
3
|
+
/** Emitted by ApiClient between a dropped WebSocket and the next dial
|
|
4
|
+
* attempt while exponential-backoff reconnect is in flight. Drives the
|
|
5
|
+
* 3-state connection pill (CR-8): connected / reconnecting / offline. */
|
|
6
|
+
| 'connection:reconnecting' | 'sidebar:toggle'
|
|
7
|
+
/** UI-only request from an empty-state CTA to start the element picker
|
|
8
|
+
* (CR-6). Renderer listens + toggles its picker; keeps the sidebar
|
|
9
|
+
* tab decoupled from the picker instance. */
|
|
10
|
+
| 'sidebar:request-pick' | 'bubbles:toggle'
|
|
11
|
+
/** User changed their bubble colour from the widget settings panel.
|
|
12
|
+
* Renderers re-tint the bubble CSS vars live; PageLoop updates its
|
|
13
|
+
* cached `bubbleColor()` so reopening the panel shows the new value.
|
|
14
|
+
* `color: null` clears back to the stylesheet default. */
|
|
15
|
+
| 'bubbles:recolor' | 'toast:show'
|
|
16
|
+
/** Structured error channel — every catch site that surfaces a user-
|
|
17
|
+
* visible failure should also emit this so embedders can wire it to
|
|
18
|
+
* their own observability layer. The toast is the user-facing
|
|
19
|
+
* artifact; `pl-error` is the machine-readable artifact. */
|
|
20
|
+
| 'pl-error'
|
|
21
|
+
/** Server-driven config changed (bootstrap revalidate or live admin
|
|
22
|
+
* edit). Renderers re-apply cheap knobs like theme; layout knobs
|
|
23
|
+
* take effect on the next reload. */
|
|
24
|
+
| 'config:changed'
|
|
25
|
+
/** Live-chat manager → renderer events. Payloads are intentionally
|
|
26
|
+
* `unknown` at the bus level (rich types live on ChatManager); the
|
|
27
|
+
* renderer casts at the listener site. */
|
|
28
|
+
| 'chat:room-changed' | 'chat:history-loaded' | 'chat:message-appended' | 'chat:presence-updated' | 'chat:settings-changed' | 'chat:known-rooms-changed' | 'chat:current-page-drift' | 'chat:typing-updated' | 'chat:older-loading' | 'chat:older-loaded' | 'chat:read-state-updated'
|
|
29
|
+
/** Fired by the bubble layer at the end of every anchor-resolve
|
|
30
|
+
* pass. Payload: `{ ids: Set<string> }` listing comments whose
|
|
31
|
+
* anchor couldn't be matched against the current DOM. Sidebar
|
|
32
|
+
* renderers subscribe to mark those comment rows as "no anchor". */
|
|
33
|
+
| 'bubbles:dangling' | ServerEvent['name'];
|
|
34
|
+
/**
|
|
35
|
+
* Payload of the `pl-error` bus event. Mirrors a typical observability
|
|
36
|
+
* sink shape (a `captureException`-style report) so embedders don't
|
|
37
|
+
* need to translate.
|
|
38
|
+
*/
|
|
39
|
+
export interface PageLoopErrorPayload {
|
|
40
|
+
/** Where the error happened — short, stable identifier. Stable
|
|
41
|
+
* enough to use as an error-grouping fingerprint (e.g. `comment.create`,
|
|
42
|
+
* `reply.delete`, `versions.list`). */
|
|
43
|
+
operation: string;
|
|
44
|
+
/** Coarse classification — lets consumers route or filter. */
|
|
45
|
+
code?: 'network' | 'auth' | 'validation' | 'plan-limit' | 'rate-limit' | 'unknown';
|
|
46
|
+
/** Human-readable summary, safe to display to end users. */
|
|
47
|
+
message: string;
|
|
48
|
+
/** Underlying error object if available — preserved for stack
|
|
49
|
+
* traces / native message. */
|
|
50
|
+
cause?: unknown;
|
|
51
|
+
/** Free-form metadata. Avoid stuffing PII; this WILL ship to
|
|
52
|
+
* whatever sink the embedder configures. */
|
|
53
|
+
extra?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
export type ClientEventHandler = (payload: any) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Type-level map from event name → payload shape. Adding an entry here
|
|
58
|
+
* gives every `bus.on(name, ...)` / `bus.emit(name, ...)` callsite
|
|
59
|
+
* strong static typing on the payload. Events not listed default to
|
|
60
|
+
* `unknown` so the bus stays back-compatible with the existing
|
|
61
|
+
* dozens of ad-hoc emitters — new emitters get safety, old ones
|
|
62
|
+
* still compile.
|
|
63
|
+
*
|
|
64
|
+
* Goal is to migrate every entry in `ClientEventName` here over time;
|
|
65
|
+
* this map is the foundation for that.
|
|
66
|
+
*/
|
|
67
|
+
export interface EventMap {
|
|
68
|
+
'connection:open': {
|
|
69
|
+
transport: 'ws' | 'sse' | 'rest';
|
|
70
|
+
};
|
|
71
|
+
'connection:closed': {
|
|
72
|
+
transport?: 'ws' | 'sse' | 'rest';
|
|
73
|
+
reason?: string;
|
|
74
|
+
};
|
|
75
|
+
/** `attempt` is 1-based; `delayMs` is the backoff window before the
|
|
76
|
+
* next dial. Lets a UI surface a "retrying in Ns" affordance. */
|
|
77
|
+
'connection:reconnecting': {
|
|
78
|
+
attempt: number;
|
|
79
|
+
delayMs: number;
|
|
80
|
+
};
|
|
81
|
+
'sidebar:toggle': {
|
|
82
|
+
visible: boolean;
|
|
83
|
+
};
|
|
84
|
+
'bubbles:toggle': {
|
|
85
|
+
visible: boolean;
|
|
86
|
+
};
|
|
87
|
+
'bubbles:recolor': {
|
|
88
|
+
color: string | null;
|
|
89
|
+
};
|
|
90
|
+
'toast:show': {
|
|
91
|
+
kind: 'success' | 'error' | 'info' | 'warn';
|
|
92
|
+
message: string;
|
|
93
|
+
title?: string;
|
|
94
|
+
};
|
|
95
|
+
'pl-error': PageLoopErrorPayload;
|
|
96
|
+
'page:change': {
|
|
97
|
+
url: string;
|
|
98
|
+
key: string;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** Resolve a payload type for an event name — strict when known,
|
|
102
|
+
* `unknown` for ad-hoc events. */
|
|
103
|
+
export type PayloadOf<K extends string> = K extends keyof EventMap ? EventMap[K] : unknown;
|
|
104
|
+
/**
|
|
105
|
+
* Tiny pub/sub for cross-module communication on the client. Server-emitted
|
|
106
|
+
* events arrive via the ApiClient and are re-emitted here so UI modules can
|
|
107
|
+
* subscribe without knowing about the transport.
|
|
108
|
+
*
|
|
109
|
+
* Two overloaded call signatures:
|
|
110
|
+
* - Typed: `bus.on('connection:open', e => e.transport)` — `e` is
|
|
111
|
+
* `EventMap['connection:open']`.
|
|
112
|
+
* - Untyped: `bus.on('something:custom', e => ...)` — `e` is
|
|
113
|
+
* `unknown`; cast at the use site or add the event to `EventMap`.
|
|
114
|
+
*/
|
|
115
|
+
export declare class EventBus {
|
|
116
|
+
private readonly listeners;
|
|
117
|
+
on<K extends keyof EventMap>(name: K, handler: (payload: EventMap[K]) => void): () => void;
|
|
118
|
+
on(name: ClientEventName, handler: ClientEventHandler): () => void;
|
|
119
|
+
emit<K extends keyof EventMap>(name: K, payload: EventMap[K]): void;
|
|
120
|
+
emit(name: ClientEventName, payload: unknown): void;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=EventBus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventBus.d.ts","sourceRoot":"","sources":["../src/EventBus.ts"],"names":[],"mappings":"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"}
|
package/dist/EventBus.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny pub/sub for cross-module communication on the client. Server-emitted
|
|
3
|
+
* events arrive via the ApiClient and are re-emitted here so UI modules can
|
|
4
|
+
* subscribe without knowing about the transport.
|
|
5
|
+
*
|
|
6
|
+
* Two overloaded call signatures:
|
|
7
|
+
* - Typed: `bus.on('connection:open', e => e.transport)` — `e` is
|
|
8
|
+
* `EventMap['connection:open']`.
|
|
9
|
+
* - Untyped: `bus.on('something:custom', e => ...)` — `e` is
|
|
10
|
+
* `unknown`; cast at the use site or add the event to `EventMap`.
|
|
11
|
+
*/
|
|
12
|
+
export class EventBus {
|
|
13
|
+
listeners = new Map();
|
|
14
|
+
on(name, handler) {
|
|
15
|
+
const set = this.listeners.get(name) ?? new Set();
|
|
16
|
+
set.add(handler);
|
|
17
|
+
this.listeners.set(name, set);
|
|
18
|
+
return () => set.delete(handler);
|
|
19
|
+
}
|
|
20
|
+
emit(name, payload) {
|
|
21
|
+
const set = this.listeners.get(name);
|
|
22
|
+
if (!set)
|
|
23
|
+
return;
|
|
24
|
+
for (const fn of set) {
|
|
25
|
+
try {
|
|
26
|
+
fn(payload);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// listener errors must not break the bus
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=EventBus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventBus.js","sourceRoot":"","sources":["../src/EventBus.ts"],"names":[],"mappings":"AA4HA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,QAAQ;IACH,SAAS,GAAG,IAAI,GAAG,EAAmC,CAAC;IAIxE,EAAE,CAAC,IAAY,EAAE,OAA2B;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,EAAE,CAAC;QAClD,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACjB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC9B,OAAO,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAID,IAAI,CAAC,IAAY,EAAE,OAAgB;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC;gBACJ,EAAE,CAAC,OAAO,CAAC,CAAC;YACb,CAAC;YAAC,MAAM,CAAC;gBACR,yCAAyC;YAC1C,CAAC;QACF,CAAC;IACF,CAAC;CACD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Comment, TrackedPage } from '@pageloop/shared';
|
|
2
|
+
/**
|
|
3
|
+
* IndexedDB-backed read-through cache for pages and comments. The SDK consults
|
|
4
|
+
* the cache before hitting the network so cold reloads of the same page show
|
|
5
|
+
* comments instantly while a fresh fetch reconciles in the background.
|
|
6
|
+
*
|
|
7
|
+
* Keys are namespaced by projectId so a single browser handling multiple
|
|
8
|
+
* PageLoop deployments stays isolated. Entries carry a `cachedAt` timestamp;
|
|
9
|
+
* the SDK doesn't TTL them — cache is invalidated by server events instead.
|
|
10
|
+
*/
|
|
11
|
+
export declare class IdbCache {
|
|
12
|
+
private dbPromise;
|
|
13
|
+
private dbName;
|
|
14
|
+
constructor(projectId: string);
|
|
15
|
+
private db;
|
|
16
|
+
getPage(key: string): Promise<TrackedPage | null>;
|
|
17
|
+
putPage(page: TrackedPage): Promise<void>;
|
|
18
|
+
getComments(pageId: string): Promise<Comment[] | null>;
|
|
19
|
+
putComments(pageId: string, comments: Comment[]): Promise<void>;
|
|
20
|
+
clear(): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=IdbCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IdbCache.d.ts","sourceRoot":"","sources":["../src/IdbCache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE7D;;;;;;;;GAQG;AACH,qBAAa,QAAQ;IACpB,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,MAAM,CAAS;gBAEX,SAAS,EAAE,MAAM;IAI7B,OAAO,CAAC,EAAE;IAcJ,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAUjD,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IASzC,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;IAUtD,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAS5B"}
|
package/dist/IdbCache.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { openDB } from 'idb';
|
|
2
|
+
/**
|
|
3
|
+
* IndexedDB-backed read-through cache for pages and comments. The SDK consults
|
|
4
|
+
* the cache before hitting the network so cold reloads of the same page show
|
|
5
|
+
* comments instantly while a fresh fetch reconciles in the background.
|
|
6
|
+
*
|
|
7
|
+
* Keys are namespaced by projectId so a single browser handling multiple
|
|
8
|
+
* PageLoop deployments stays isolated. Entries carry a `cachedAt` timestamp;
|
|
9
|
+
* the SDK doesn't TTL them — cache is invalidated by server events instead.
|
|
10
|
+
*/
|
|
11
|
+
export class IdbCache {
|
|
12
|
+
dbPromise = null;
|
|
13
|
+
dbName;
|
|
14
|
+
constructor(projectId) {
|
|
15
|
+
this.dbName = `pageloop:${projectId}`;
|
|
16
|
+
}
|
|
17
|
+
db() {
|
|
18
|
+
if (!this.dbPromise) {
|
|
19
|
+
this.dbPromise = openDB(this.dbName, 1, {
|
|
20
|
+
upgrade(db) {
|
|
21
|
+
if (!db.objectStoreNames.contains('pages'))
|
|
22
|
+
db.createObjectStore('pages', { keyPath: 'key' });
|
|
23
|
+
if (!db.objectStoreNames.contains('comments'))
|
|
24
|
+
db.createObjectStore('comments', { keyPath: 'pageId' });
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return this.dbPromise;
|
|
29
|
+
}
|
|
30
|
+
async getPage(key) {
|
|
31
|
+
try {
|
|
32
|
+
const db = await this.db();
|
|
33
|
+
const row = await db.get('pages', key);
|
|
34
|
+
return row?.page ?? null;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async putPage(page) {
|
|
41
|
+
try {
|
|
42
|
+
const db = await this.db();
|
|
43
|
+
await db.put('pages', { key: page.key, page, cachedAt: Date.now() });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// IDB unavailable (private mode, quota); silent fall-through.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async getComments(pageId) {
|
|
50
|
+
try {
|
|
51
|
+
const db = await this.db();
|
|
52
|
+
const row = await db.get('comments', pageId);
|
|
53
|
+
return row?.comments ?? null;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async putComments(pageId, comments) {
|
|
60
|
+
try {
|
|
61
|
+
const db = await this.db();
|
|
62
|
+
await db.put('comments', { pageId, comments, cachedAt: Date.now() });
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// ignore
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async clear() {
|
|
69
|
+
try {
|
|
70
|
+
const db = await this.db();
|
|
71
|
+
await db.clear('pages');
|
|
72
|
+
await db.clear('comments');
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// ignore
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=IdbCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IdbCache.js","sourceRoot":"","sources":["../src/IdbCache.ts"],"names":[],"mappings":"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"}
|