@pmate/chat-sdk 0.12.0-next.20 → 0.12.0-next.21
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 +8 -0
- package/lib/runtime/batchedReadIndex.d.ts +11 -2
- package/lib/runtime/batchedReadIndex.js +21 -1
- package/lib/runtime/chatSession.d.ts +10 -0
- package/lib/runtime/chatSession.js +120 -17
- package/lib/runtime/durableSender.d.ts +28 -4
- package/lib/runtime/durableSender.js +154 -34
- package/lib/runtime/messageClient.d.ts +17 -1
- package/lib/runtime/messageClient.js +164 -39
- package/lib/runtime/messageReaders.d.ts +4 -0
- package/lib/runtime/messageReaders.js +12 -4
- package/lib/runtime/messageStore.d.ts +16 -3
- package/lib/runtime/messageStore.js +74 -14
- package/lib/runtime/readCounts.d.ts +3 -0
- package/lib/runtime/readCounts.js +23 -5
- package/lib/runtime/readIndex.d.ts +21 -2
- package/lib/runtime/readIndex.js +47 -2
- package/lib/runtime/readSync.js +12 -4
- package/lib/runtime/readingClient.d.ts +11 -0
- package/lib/runtime/readingClient.js +78 -11
- package/lib/runtime/responseCache.d.ts +15 -0
- package/lib/runtime/responseCache.js +64 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -158,9 +158,17 @@ await chat.observeMessages(threadId, [voiceId], "played")
|
|
|
158
158
|
await chat.sendMessage(threadId, { to, body, recipients })
|
|
159
159
|
const readers = await chat.getReaders(threadId, messageId)
|
|
160
160
|
off(); offSummary()
|
|
161
|
+
// Ordinary navigation/shutdown preserves offline data.
|
|
161
162
|
await chat.close()
|
|
162
163
|
```
|
|
163
164
|
|
|
165
|
+
```ts
|
|
166
|
+
// When team access is withdrawn, use this instead of ordinary close(). It hides
|
|
167
|
+
// snapshots immediately, retires durable caches/outbox and closes this session.
|
|
168
|
+
// Other teams' reading operations remain intact. Create a new session to recover.
|
|
169
|
+
await chat.revokeAccess()
|
|
170
|
+
```
|
|
171
|
+
|
|
164
172
|
The facade owns all clients below. Message reads transparently restore IndexedDB
|
|
165
173
|
windows and fetch missing ranges through the authenticated Hub. Its normalized
|
|
166
174
|
messages retain the protocol body; product rendering remains with the host.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type ReadIndexPersistence, type ReadIndexState } from "./readIndex.js";
|
|
1
|
+
import { type ReadIndexPersistence, type ReadIndexState, type ReadIndexTransform } from "./readIndex.js";
|
|
2
2
|
type Change = {
|
|
3
|
-
transform:
|
|
3
|
+
transform: ReadIndexTransform;
|
|
4
4
|
resolve: (state: ReadIndexState) => void;
|
|
5
5
|
reject: (error: unknown) => void;
|
|
6
6
|
};
|
|
@@ -15,6 +15,15 @@ export declare class BatchedReadIndex implements ReadIndexPersistence {
|
|
|
15
15
|
private closed;
|
|
16
16
|
constructor(storage: ReadIndexPersistence, windowMs?: number, batchSize?: number, queueLimit?: number);
|
|
17
17
|
load(appId: string, profileId: string): Promise<ReadIndexState | undefined>;
|
|
18
|
+
loadVersioned(appId: string, profileId: string): Promise<{
|
|
19
|
+
state: ReadIndexState | undefined;
|
|
20
|
+
versions: import("./readIndex.js").ReadIndexVersions;
|
|
21
|
+
} | {
|
|
22
|
+
state: ReadIndexState | undefined;
|
|
23
|
+
versions: {};
|
|
24
|
+
}>;
|
|
25
|
+
loadVersions(appId: string, profileId: string): Promise<{}>;
|
|
26
|
+
invalidateTeam(appId: string, profileId: string, teamId: string, version: number, knownThreadIds?: readonly string[]): Promise<ReadIndexState>;
|
|
18
27
|
update(appId: string, profileId: string, transform: Change["transform"]): Promise<ReadIndexState>;
|
|
19
28
|
private drain;
|
|
20
29
|
flush(appId: string, profileId: string): Promise<void>;
|
|
@@ -22,6 +22,26 @@ export class BatchedReadIndex {
|
|
|
22
22
|
throw new Error("READ_STORAGE_CLOSED");
|
|
23
23
|
return this.storage.load(appId, profileId);
|
|
24
24
|
}
|
|
25
|
+
async loadVersioned(appId, profileId) {
|
|
26
|
+
await this.flush(appId, profileId);
|
|
27
|
+
if (this.closed)
|
|
28
|
+
throw new Error("READ_STORAGE_CLOSED");
|
|
29
|
+
return this.storage.loadVersioned ? this.storage.loadVersioned(appId, profileId) : { state: await this.storage.load(appId, profileId), versions: {} };
|
|
30
|
+
}
|
|
31
|
+
async loadVersions(appId, profileId) {
|
|
32
|
+
if (this.closed)
|
|
33
|
+
throw new Error("READ_STORAGE_CLOSED");
|
|
34
|
+
return this.storage.loadVersions ? this.storage.loadVersions(appId, profileId) : {};
|
|
35
|
+
}
|
|
36
|
+
async invalidateTeam(appId, profileId, teamId, version, knownThreadIds) {
|
|
37
|
+
if (this.closed)
|
|
38
|
+
throw new Error("READ_STORAGE_CLOSED");
|
|
39
|
+
if (!this.storage.invalidateTeam)
|
|
40
|
+
throw new Error("READ_SCOPE_INVALIDATION_UNAVAILABLE");
|
|
41
|
+
// Do not flush a blocked old HTTP transform first. The durable epoch is the
|
|
42
|
+
// barrier; any queued transform sees it inside its eventual transaction.
|
|
43
|
+
return this.storage.invalidateTeam(appId, profileId, teamId, version, knownThreadIds);
|
|
44
|
+
}
|
|
25
45
|
update(appId, profileId, transform) {
|
|
26
46
|
if (this.closed)
|
|
27
47
|
return Promise.reject(new Error("READ_STORAGE_CLOSED"));
|
|
@@ -48,7 +68,7 @@ export class BatchedReadIndex {
|
|
|
48
68
|
return;
|
|
49
69
|
const run = (async () => {
|
|
50
70
|
try {
|
|
51
|
-
const next = await this.storage.update(queue.appId, queue.profileId, state => batch.reduce((current, change) => change.transform(current), state));
|
|
71
|
+
const next = await this.storage.update(queue.appId, queue.profileId, (state, versions) => batch.reduce((current, change) => change.transform(current, versions), state));
|
|
52
72
|
batch.forEach(change => change.resolve(next));
|
|
53
73
|
}
|
|
54
74
|
catch (error) {
|
|
@@ -59,9 +59,14 @@ export declare class ChatSession<T extends StoredMessage> {
|
|
|
59
59
|
private off;
|
|
60
60
|
private globalListeners;
|
|
61
61
|
private globalSnapshot?;
|
|
62
|
+
private closedThread;
|
|
63
|
+
private closedSummary;
|
|
62
64
|
private connectionState;
|
|
63
65
|
private catchUp;
|
|
64
66
|
private readingStarted;
|
|
67
|
+
private readingStarting?;
|
|
68
|
+
private withdrawn;
|
|
69
|
+
private revoking?;
|
|
65
70
|
private closed;
|
|
66
71
|
private starting?;
|
|
67
72
|
private initializing?;
|
|
@@ -71,6 +76,7 @@ export declare class ChatSession<T extends StoredMessage> {
|
|
|
71
76
|
/** Initializes local state and background reading sync without opening a
|
|
72
77
|
* second transport when the host already owns calls/presence. */
|
|
73
78
|
initialize(): Promise<void>;
|
|
79
|
+
private startReading;
|
|
74
80
|
/** One-time migration only: never establishes trusted history coverage. */
|
|
75
81
|
importLegacyMessages(threadId: string, messages: T[]): Promise<void>;
|
|
76
82
|
getReadingSnapshot(): import("./readIndex.js").ReadIndexState | undefined;
|
|
@@ -117,5 +123,9 @@ export declare class ChatSession<T extends StoredMessage> {
|
|
|
117
123
|
private buildGlobal;
|
|
118
124
|
private publishGlobal;
|
|
119
125
|
private publish;
|
|
126
|
+
/** Permanently retire this identity/team session. UI withdrawal is immediate;
|
|
127
|
+
* completion means all durable stores have crossed the same authority epoch. */
|
|
128
|
+
revokeAccess(): Promise<void>;
|
|
120
129
|
close(): Promise<void>;
|
|
130
|
+
private dispose;
|
|
121
131
|
}
|
|
@@ -27,9 +27,14 @@ export class ChatSession {
|
|
|
27
27
|
off = [];
|
|
28
28
|
globalListeners = new Set();
|
|
29
29
|
globalSnapshot;
|
|
30
|
+
closedThread = { messages: [], outgoing: [], hasEarlier: false, resetVersion: 0, complete: false, unread: undefined };
|
|
31
|
+
closedSummary = { threads: [], unreadMessageCount: undefined, badgeCount: undefined, connection: "closed" };
|
|
30
32
|
connectionState = "closed";
|
|
31
33
|
catchUp;
|
|
32
34
|
readingStarted = false;
|
|
35
|
+
readingStarting;
|
|
36
|
+
withdrawn = false;
|
|
37
|
+
revoking;
|
|
33
38
|
closed = false;
|
|
34
39
|
starting;
|
|
35
40
|
initializing;
|
|
@@ -54,8 +59,8 @@ export class ChatSession {
|
|
|
54
59
|
throw new Error("REACH_HANDLER_UNAVAILABLE");
|
|
55
60
|
} });
|
|
56
61
|
this.reading = new ReadingClient({ ...options.reading, storageScope: JSON.stringify([scope.accountId]), migrateLegacy: true });
|
|
57
|
-
this.readers = new MessageReadersClient({ appId: scope.appId, profileId: scope.profileId, token: options.reading.sessionToken, scope, onError: options.onError, hubHttpUrl: options.reading.hubHttpUrl, fetch: options.reading.fetch });
|
|
58
|
-
this.sender = new DurableChatSender(this.connection, scope, options.onError);
|
|
62
|
+
this.readers = new MessageReadersClient({ appId: scope.appId, profileId: scope.profileId, token: options.reading.sessionToken, scope, access: () => this.messages.scopeAccess(), onError: options.onError, hubHttpUrl: options.reading.hubHttpUrl, fetch: options.reading.fetch });
|
|
63
|
+
this.sender = new DurableChatSender(this.connection, scope, options.onError, { access: () => this.messages.scopeAccess() });
|
|
59
64
|
this.catchUp = new WindowedTasks(1000, error => { if (!this.closed)
|
|
60
65
|
options.onError(error); });
|
|
61
66
|
this.countTasks = new WindowedTasks(50, error => { if (!this.closed)
|
|
@@ -66,6 +71,8 @@ export class ChatSession {
|
|
|
66
71
|
window.addEventListener("focus", wake);
|
|
67
72
|
this.off.push(() => { window.removeEventListener("online", wake); window.removeEventListener("focus", wake); });
|
|
68
73
|
}
|
|
74
|
+
this.off.push(this.messages.subscribeAccessRevoked(() => { if (!this.withdrawn && !this.closed)
|
|
75
|
+
void this.revokeAccess().catch(error => options.onError(error)); }));
|
|
69
76
|
this.off.push(this.messages.subscribeAll(thread => this.publish(thread)));
|
|
70
77
|
this.off.push(this.sender.subscribe(() => this.publishAll()), this.reading.subscribe(() => this.publishAll()));
|
|
71
78
|
this.off.push(this.connection.watchMessages(message => {
|
|
@@ -112,21 +119,35 @@ export class ChatSession {
|
|
|
112
119
|
return this.initializing ??= (async () => {
|
|
113
120
|
await this.sender.initialize();
|
|
114
121
|
this.assertOpen();
|
|
115
|
-
this.
|
|
116
|
-
await this.reading.start();
|
|
122
|
+
await this.startReading();
|
|
117
123
|
this.assertOpen();
|
|
118
124
|
})().catch(error => { this.initializing = undefined; throw error; });
|
|
119
125
|
}
|
|
126
|
+
startReading() {
|
|
127
|
+
if (this.readingStarted)
|
|
128
|
+
return Promise.resolve();
|
|
129
|
+
return this.readingStarting ??= (async () => {
|
|
130
|
+
const access = await this.messages.scopeAccess();
|
|
131
|
+
if (!access.allowed)
|
|
132
|
+
return;
|
|
133
|
+
this.assertOpen();
|
|
134
|
+
await this.reading.prepareTeam(this.options.messages.scope.teamId, access.version);
|
|
135
|
+
await this.reading.start();
|
|
136
|
+
this.assertOpen();
|
|
137
|
+
this.readingStarted = true;
|
|
138
|
+
})().finally(() => { this.readingStarting = undefined; });
|
|
139
|
+
}
|
|
120
140
|
/** One-time migration only: never establishes trusted history coverage. */
|
|
121
141
|
importLegacyMessages(threadId, messages) { this.assertOpen(); return this.messages.importLegacy(threadId, messages); }
|
|
122
|
-
getReadingSnapshot() { return this.reading.getSnapshot(); }
|
|
142
|
+
getReadingSnapshot() { return this.closed || this.withdrawn ? undefined : this.reading.getSnapshot(); }
|
|
123
143
|
subscribeReading(listener) { this.assertOpen(); return this.reading.subscribe(listener); }
|
|
124
144
|
refreshReading() { this.assertOpen(); this.reading.refresh(); }
|
|
125
145
|
synchronizeReading(cursor, profileId) { this.assertOpen(); return this.reading.synchronize(cursor, profileId); }
|
|
126
146
|
/** Explicit text-history acknowledgement; unheard audio stays unread. */
|
|
127
147
|
markRead(threadId, messages = []) { this.assertOpen(); return this.reading.markRead(threadId, messages); }
|
|
128
148
|
updateReading(transform) { this.assertOpen(); return this.reading.update(transform); }
|
|
129
|
-
assertOpen() { if (this.
|
|
149
|
+
assertOpen() { if (this.withdrawn)
|
|
150
|
+
throw new Error("CHAT_ACCESS_REVOKED"); if (this.closed)
|
|
130
151
|
throw new Error("CHAT_SESSION_CLOSED"); }
|
|
131
152
|
async getMessages(threadId, options = {}) {
|
|
132
153
|
this.assertOpen();
|
|
@@ -145,7 +166,10 @@ export class ChatSession {
|
|
|
145
166
|
return this.readAll(threadId);
|
|
146
167
|
}
|
|
147
168
|
const page = await (options.before ? this.messages.earlier(threadId, options.before, options.limit) : this.messages.read(threadId, options.limit));
|
|
169
|
+
this.assertOpen();
|
|
148
170
|
await this.sender.confirm(page.messages.map(message => message.id));
|
|
171
|
+
if (this.initializing && !this.readingStarted && page.complete)
|
|
172
|
+
await this.startReading();
|
|
149
173
|
const edge = this.historyEdges.get(threadId);
|
|
150
174
|
if (!options.before && page.complete && (!edge || edge.version !== page.resetVersion))
|
|
151
175
|
this.rememberHistoryEdge(threadId, page);
|
|
@@ -285,6 +309,8 @@ export class ChatSession {
|
|
|
285
309
|
this.listeners.get(thread)?.forEach(listener => listener());
|
|
286
310
|
}
|
|
287
311
|
publishCounts(value) {
|
|
312
|
+
if (this.closed || this.withdrawn)
|
|
313
|
+
return;
|
|
288
314
|
const previous = this.countSnapshots.get(value.threadId);
|
|
289
315
|
if ((previous && previous.revision > value.revision) || JSON.stringify(previous) === JSON.stringify(value))
|
|
290
316
|
return;
|
|
@@ -311,13 +337,13 @@ export class ChatSession {
|
|
|
311
337
|
this.countClosings.add(closing);
|
|
312
338
|
}
|
|
313
339
|
const scope = this.options.messages.scope;
|
|
314
|
-
const client = new ReadCountsClient({ appId: scope.appId, profileId: scope.profileId, scope, token: this.options.reading.sessionToken, clientInstanceId: receiver, hubHttpUrl: this.options.reading.hubHttpUrl, fetch: this.options.reading.fetch,
|
|
340
|
+
const client = new ReadCountsClient({ appId: scope.appId, profileId: scope.profileId, scope, access: () => this.messages.scopeAccess(), token: this.options.reading.sessionToken, clientInstanceId: receiver, hubHttpUrl: this.options.reading.hubHttpUrl, fetch: this.options.reading.fetch,
|
|
315
341
|
onChange: value => { if (!this.closed && this.counts === client)
|
|
316
342
|
this.publishCounts(value); },
|
|
317
343
|
onError: error => {
|
|
318
344
|
if (this.closed || this.counts !== client)
|
|
319
345
|
return;
|
|
320
|
-
if (/READ_COUNTS_HTTP_(401|403)/.test(String(error))) {
|
|
346
|
+
if (/READ_COUNTS_HTTP_(401|403)|RESPONSE_CACHE_(ACCESS_REVOKED|SCOPE_CHANGED|INVALIDATED)/.test(String(error))) {
|
|
321
347
|
const threads = [...this.countSnapshots.keys()];
|
|
322
348
|
this.countSnapshots.clear();
|
|
323
349
|
threads.forEach(thread => this.publishCountSnapshot(thread));
|
|
@@ -331,6 +357,11 @@ export class ChatSession {
|
|
|
331
357
|
this.counts.watch(threadId, ids);
|
|
332
358
|
}
|
|
333
359
|
getSnapshot(threadId) {
|
|
360
|
+
if (this.closed || this.withdrawn) {
|
|
361
|
+
if (threadId === undefined)
|
|
362
|
+
return this.closedSummary;
|
|
363
|
+
return this.closedThread;
|
|
364
|
+
}
|
|
334
365
|
if (threadId === undefined) {
|
|
335
366
|
if (!this.globalSnapshot)
|
|
336
367
|
this.globalSnapshot = this.buildGlobal();
|
|
@@ -377,6 +408,8 @@ export class ChatSession {
|
|
|
377
408
|
};
|
|
378
409
|
}
|
|
379
410
|
publishAll() {
|
|
411
|
+
if (this.closed || this.withdrawn)
|
|
412
|
+
return;
|
|
380
413
|
for (const threadId of [...this.snapshots.keys()])
|
|
381
414
|
this.publish(threadId, false);
|
|
382
415
|
this.publishGlobal();
|
|
@@ -397,6 +430,8 @@ export class ChatSession {
|
|
|
397
430
|
return { threads, unreadMessageCount: known ? threads.reduce((sum, t) => sum + (t.unread?.unreadMessageCount ?? 0), 0) : undefined, badgeCount: known ? threads.reduce((sum, t) => sum + (t.unread?.badgeCount ?? 0), 0) : undefined, connection: this.connectionState };
|
|
398
431
|
}
|
|
399
432
|
publishGlobal() {
|
|
433
|
+
if (this.closed || this.withdrawn)
|
|
434
|
+
return;
|
|
400
435
|
const next = this.buildGlobal();
|
|
401
436
|
if (JSON.stringify(next) === JSON.stringify(this.globalSnapshot))
|
|
402
437
|
return;
|
|
@@ -415,7 +450,67 @@ export class ChatSession {
|
|
|
415
450
|
if (notifyGlobal)
|
|
416
451
|
this.publishGlobal();
|
|
417
452
|
}
|
|
418
|
-
|
|
453
|
+
/** Permanently retire this identity/team session. UI withdrawal is immediate;
|
|
454
|
+
* completion means all durable stores have crossed the same authority epoch. */
|
|
455
|
+
revokeAccess() {
|
|
456
|
+
if (this.revoking)
|
|
457
|
+
return this.revoking;
|
|
458
|
+
if (this.closed)
|
|
459
|
+
return Promise.reject(new Error("CHAT_SESSION_CLOSED"));
|
|
460
|
+
this.withdrawn = true;
|
|
461
|
+
const knownThreads = [...new Set([...this.snapshots.keys(), ...this.historyEdges.keys()])];
|
|
462
|
+
this.catchUp.close();
|
|
463
|
+
this.countTasks.close();
|
|
464
|
+
this.off.splice(0).forEach(off => off());
|
|
465
|
+
this.sender.withdraw();
|
|
466
|
+
this.reading.withdraw();
|
|
467
|
+
this.readers.withdraw();
|
|
468
|
+
this.counts?.watch(undefined, []);
|
|
469
|
+
this.countSnapshots.clear();
|
|
470
|
+
this.snapshots.clear();
|
|
471
|
+
this.historyEdges.clear();
|
|
472
|
+
this.earlierReads.clear();
|
|
473
|
+
this.globalSnapshot = undefined;
|
|
474
|
+
this.listeners.forEach(listeners => listeners.forEach(listener => listener()));
|
|
475
|
+
this.globalListeners.forEach(listener => listener());
|
|
476
|
+
if (!this.options.sharedConnection)
|
|
477
|
+
this.connection.close();
|
|
478
|
+
return this.revoking = Promise.resolve().then(async () => {
|
|
479
|
+
const failures = [];
|
|
480
|
+
let version;
|
|
481
|
+
try {
|
|
482
|
+
version = await this.messages.revokeScope();
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
failures.push(error);
|
|
486
|
+
version = 1;
|
|
487
|
+
try {
|
|
488
|
+
version = Math.max(1, (await this.messages.scopeAccess()).version);
|
|
489
|
+
}
|
|
490
|
+
catch (failure) {
|
|
491
|
+
failures.push(failure);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const results = await Promise.allSettled([
|
|
495
|
+
this.sender.revoke(version),
|
|
496
|
+
this.reading.revokeTeam(this.options.messages.scope.teamId, version, knownThreads),
|
|
497
|
+
this.readers.retireScope(version), this.counts?.retireScope(version),
|
|
498
|
+
]);
|
|
499
|
+
for (const result of results)
|
|
500
|
+
if (result.status === "rejected")
|
|
501
|
+
failures.push(result.reason);
|
|
502
|
+
try {
|
|
503
|
+
await this.dispose();
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
failures.push(error);
|
|
507
|
+
}
|
|
508
|
+
if (failures.length)
|
|
509
|
+
throw new AggregateError(failures, "CHAT_ACCESS_CLEANUP_FAILED");
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
close() { return this.revoking ?? this.dispose(); }
|
|
513
|
+
dispose() {
|
|
419
514
|
return this.closing ??= (async () => {
|
|
420
515
|
this.closed = true;
|
|
421
516
|
this.catchUp.close();
|
|
@@ -423,14 +518,22 @@ export class ChatSession {
|
|
|
423
518
|
this.off.splice(0).forEach(off => off());
|
|
424
519
|
if (!this.options.sharedConnection)
|
|
425
520
|
this.connection.close();
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
521
|
+
const senderClosing = this.sender.close();
|
|
522
|
+
try {
|
|
523
|
+
const results = await Promise.allSettled([this.counts?.close(), ...this.countClosings, this.readers.close(), senderClosing, this.reading.close(), this.messages.close(senderClosing)]);
|
|
524
|
+
const failed = results.find((result) => result.status === "rejected");
|
|
525
|
+
if (failed)
|
|
526
|
+
throw failed.reason;
|
|
527
|
+
}
|
|
528
|
+
finally {
|
|
529
|
+
this.listeners.clear();
|
|
530
|
+
this.globalListeners.clear();
|
|
531
|
+
this.snapshots.clear();
|
|
532
|
+
this.countSnapshots.clear();
|
|
533
|
+
this.historyEdges.clear();
|
|
534
|
+
this.earlierReads.clear();
|
|
535
|
+
this.globalSnapshot = undefined;
|
|
536
|
+
}
|
|
434
537
|
})();
|
|
435
538
|
}
|
|
436
539
|
}
|
|
@@ -8,13 +8,30 @@ export type OutgoingMessage = {
|
|
|
8
8
|
message: ChatRuntimeMessage;
|
|
9
9
|
status: "sending" | "failed" | "sent";
|
|
10
10
|
confirmed?: boolean;
|
|
11
|
+
accessVersion?: number;
|
|
12
|
+
};
|
|
13
|
+
export type SenderScopeAccess = {
|
|
14
|
+
version: number;
|
|
15
|
+
allowed: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type DurableSenderOptions = {
|
|
18
|
+
indexedDB?: IDBFactory;
|
|
19
|
+
name?: string;
|
|
20
|
+
access?: () => Promise<SenderScopeAccess>;
|
|
11
21
|
};
|
|
12
22
|
/** Persists the exact wire message before sending; retries preserve its hash. */
|
|
13
23
|
export declare class DurableChatSender {
|
|
14
24
|
private client;
|
|
15
25
|
private onError;
|
|
26
|
+
private options;
|
|
16
27
|
private db;
|
|
17
28
|
private outbox;
|
|
29
|
+
private epochs;
|
|
30
|
+
private version;
|
|
31
|
+
private withdrawn;
|
|
32
|
+
private revoking?;
|
|
33
|
+
private channel?;
|
|
34
|
+
private accepted;
|
|
18
35
|
private scope;
|
|
19
36
|
private identity;
|
|
20
37
|
private values;
|
|
@@ -22,12 +39,19 @@ export declare class DurableChatSender {
|
|
|
22
39
|
private listeners;
|
|
23
40
|
private running;
|
|
24
41
|
private closed;
|
|
42
|
+
private closing;
|
|
43
|
+
private empty;
|
|
25
44
|
private offConnection;
|
|
26
45
|
private ready;
|
|
27
|
-
constructor(client: ChatClient, scope: MessageScope, onError: (error: unknown) => void, options?:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
46
|
+
constructor(client: ChatClient, scope: MessageScope, onError: (error: unknown) => void, options?: DurableSenderOptions);
|
|
47
|
+
private readAccess;
|
|
48
|
+
private restore;
|
|
49
|
+
private checkAccess;
|
|
50
|
+
private withEpoch;
|
|
51
|
+
private deleteRows;
|
|
52
|
+
/** Stop disclosure immediately; durable invalidation is coordinated separately. */
|
|
53
|
+
withdraw(): void;
|
|
54
|
+
revoke(targetVersion?: number): Promise<void>;
|
|
31
55
|
initialize(): Promise<void>;
|
|
32
56
|
subscribe(listener: () => void): () => void;
|
|
33
57
|
snapshot(threadId?: string): readonly OutgoingMessage[];
|