@pmate/chat-sdk 0.12.0-next.68 → 0.12.0-next.69

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 CHANGED
@@ -138,50 +138,65 @@ to the SDK. The default API presents one semantic message list, including delive
138
138
  recalls and reactions. It never presents a partial migration as a ready window.
139
139
 
140
140
  ```ts
141
- import {createChat, ChatError} from "@pmate/chat-sdk/chat"
142
- const chat = createChat({appId, accountId, profileId, teamId, token, onError})
143
- await chat.start() // also handles an optional shared calls/presence connection
144
-
145
- // Existing conversations already have a threadId. For a new conversation:
146
- const threadId = await chat.getConversation({peerId}) // or {groupId}
147
- try {
148
- await chat.getMessages(threadId)
149
- openThread(threadId) // cold failure leaves the usable conversation list in place
150
- } catch (error) {
151
- if (error instanceof ChatError && error.retryable) showRetry()
152
- }
153
- const off = chat.subscribe(threadId, state => render(state.messages))
141
+ import {createChat} from "@pmate/chat-sdk/chat"
142
+ const chat = createChat({appId, accountId, profileId, teamId,
143
+ getToken: ({forceRefresh, signal}) => auth.getAccessToken({forceRefresh, signal}),
144
+ })
145
+ await chat.start()
146
+ const threadId = await chat.resolveThreadId({peerId}) // or {groupId}; no remote creation
147
+ const off = chat.subscribeThread(threadId, state => {
148
+ if (state.status === "ready") render(state.messages)
149
+ else if (state.status === "error") showRetry(state.error)
150
+ }) // listens first, opens local state, then owns background synchronization
154
151
  await chat.sendMessage(threadId, {text: "Hello"})
155
- await chat.getMessages(threadId, {earlier: true})
152
+ await chat.loadEarlier(threadId)
156
153
  off()
157
154
  await chat.close()
158
155
  ```
159
156
 
160
- `getMessages` returns the same stable snapshot as `getSnapshot` and `subscribe`.
161
- It resolves with a trusted recent window, or rejects with `ChatError`; Apps do not
162
- check coverage or select local/remote sources. `{earlier:true}` extends the SDK's
163
- current window without timestamps, limits or cursors; `{all:true}` traverses
164
- accessible history and rejects if the scan is incomplete. A failed history scan
165
- can leave the already trusted recent window usable. Cache hits do not repeat
166
- history requests. Reconnection and browser wake reconcile newer changes.
167
-
168
- A thread snapshot contains `threadId`, one renderable `messages` list, `ready`,
169
- `hasEarlier`, `unread`, `group`, `readCounts`, optional `conversation` target identity,
170
- and optional `error`. Target identity survives recalls without exposing redacted
171
- message bodies. `ready` helps
172
- route entry and startup before the first read; it is not a cache-source indicator.
173
- `getSnapshot()` and `subscribe(listener)` provide conversation summaries, aggregate
174
- unread counts and connection state. Unknown unread counts remain `undefined`.
175
- Subscription always emits the current snapshot first, including with
176
- `{fields:["messages","unread"]}`, then meaningful changes only. Messages expose `kind`, `text`, optional `image`,
177
- `voice`, `quote`, explicit `richContent`, semantic `reactions` and reading/delivery
178
- state. Apps do not inspect transport bodies or control-event revisions. Group
179
- causal version numbers and raw message records remain under `advanced`.
180
- `observeMessages` and `markRead` resolve with `void` after durable submission.
181
- Object references
182
- remain stable for unchanged results. Framework adapters can use
183
- `{emitCurrent:false}`; React can import `useChatSnapshot(chat, threadId)` from
184
- `@pmate/chat-sdk`. Establishing a subscription never starts a read or marks it read.
157
+ `getThread(id)` is the one-shot equivalent; it returns the same stable snapshot
158
+ as `subscribeThread`. Cache hits do not repeat history requests. `loadEarlier(id)`
159
+ extends the SDK window without limits or cursors. `refreshThread(id)` explicitly
160
+ reconciles authority; ordinary reconnect/wake synchronization is automatic.
161
+ For a cold route, keep the usable list visible until `getThread` resolves, or
162
+ until a subscription delivers `status: "ready"`; no intermediate message loader
163
+ is necessary. Subscription initialization has four bounded concurrent lanes.
164
+ Multiple consumers share work; disposing one does not stop another consumer.
165
+
166
+ Thread state is `pending`, `ready`, or `error`. A failed initial read is `error`;
167
+ a failed background refresh retains `ready` messages and includes `error`.
168
+ `ready` is retained as a derived compatibility field; use `status` in new code.
169
+ Connection state is independent. A snapshot also contains `hasEarlier`, `unread`,
170
+ `group`, `readCounts` and scoped target identity. Unknown unread stays undefined.
171
+ `subscribeChat` delivers known conversation summaries and aggregate reading state.
172
+ Subscriptions emit their current snapshot first, then meaningful changes only;
173
+ `fields` filters notifications. Callbacks cannot corrupt a shared cache read.
174
+ Opening or subscribing does not mark messages read.
175
+
176
+ Messages provide kind/text/image/voice/quote, reactions and reading/delivery state.
177
+ No raw wire or control revision is needed. The public models do not derive their
178
+ types from Session, storage or projection internals. Object references stay stable
179
+ when content has not changed. React `useChatSnapshot(chat, threadId)` manages the
180
+ external-store subscription; custom integrations use `getChatStore` from
181
+ `@pmate/chat-sdk/framework`, where synchronous memory snapshots and change-only
182
+ notifications are explicitly separated from ordinary asynchronous reads.
183
+
184
+ `getThread`, `loadEarlier`, `refreshThread`, `getGroup`, `refreshGroup`,
185
+ `resolveThreadId`, and `getVoiceAttachment` accept `{signal}`. Reader queries accept
186
+ `{afterProfileId, revision, signal}`. Aborting rejects that consumer with the signal
187
+ reason (normally AbortError); it does not abort shared work, publish a fake cache
188
+ failure, or undo a durable send. A late abandoned voice lease is released.
189
+ Complete-history search/export is an explicit, potentially large operation:
190
+ `loadThreadHistory(chat, id, {signal})` from `@pmate/chat-sdk/history`. It traverses
191
+ accessible history and rejects incomplete scans. It is not a flag on normal reads.
192
+
193
+ The credential provider is invoked on connection/reconnection and authenticated
194
+ HTTP reads; concurrent requests share acquisition. HTTP 401 gets one coalesced
195
+ forced refresh and at most one replay. HTTP 403 is never retried automatically.
196
+ Providers must preserve account/app/profile identity. A fixed `token` remains
197
+ supported as an alternative; select one source. An ambiguous WebSocket AUTH_FAILED
198
+ requires authentication rather than guessing that a permission failure expired.
199
+ Closing fences late providers and prevents further authenticated network work.
185
200
 
186
201
  Sending accepts text/mentions/quotes, images, voice, recalls, reactions and
187
202
  invitations through the same typed content union. The SDK resolves the target
@@ -217,35 +232,28 @@ wait for confirmed delivery and a causal server version. SDK-owned requests shar
217
232
  bounded queues and time windows; Apps do not poll. `watchReaders(threadId,ids,receiver)`
218
233
  selects receipt counts for the current viewport; clear with `watchReaders(undefined,[])`.
219
234
 
220
- `ChatError` exposes `code`, `retryable`, optional HTTP `status`, and the original
221
- `cause`. Recovery categories are `unavailable`, `access-denied`,
222
- `storage-unavailable`, `invalid-input`, `conflict`, `closed`, and `busy`.
223
- Permission failures withdraw the current scoped session and retire its caches;
224
- failed cleanup remains in the error cause. They never turn into trusted offline
225
- data. Dispose and recreate
226
- the SDK when account, profile, team or token changes.
235
+ `ChatError` exposes code, retryable, optional HTTP status and cause. Categories are
236
+ unavailable, authentication-required, access-denied, storage-unavailable,
237
+ invalid-input, conflict, closed and busy. Token expiry requires authentication;
238
+ a 403 withdraws the scoped session and its caches. Account/profile/team changes
239
+ require a new SDK instance. Token renewal alone uses the provider.
227
240
 
228
241
  ### Advanced integration and migration
229
242
 
230
- `chat.advanced` exposes the underlying `ChatSession` for one-time legacy imports,
231
- custom transports and diagnostics. Its `messages` are raw records, `timeline` is
232
- the semantic projection, and `complete/resetVersion` describe coverage internals.
233
- The older `createChat` export from `@pmate/chat-sdk/runtime` retains this low-level
234
- contract for compatibility. New Apps should import from `@pmate/chat-sdk/chat`
235
- or the package root. Do not mix the two snapshot contracts.
236
-
237
- For migration, replace `subscribe(id, () => render(getSnapshot(id).timeline))`
238
- with `subscribe(id, state => render(state.messages))`; replace
239
- `item.message.body` parsing with `item.kind/text/image/voice/quote`; replace conversation
240
- objects and App-supplied recipients with `threadId`; await `getMessages` before
241
- opening a cold thread instead of checking `page.complete`. Replace `initialize()`
242
- with `start()`; a shared connection is initialized without another connect call.
243
- Raw wire sends are available only through `advanced.sendMessage`. Group changes
244
- use `createGroup/updateGroup`; UI never assigns reaction versions or recipients.
245
-
246
- The advanced storage and transport classes remain exported from `/runtime`.
247
- They must share one authenticated scope and must not bypass scope authority.
248
- Normal UI code should not maintain another message cache or outbox.
243
+ The default client has no `advanced`, `getSnapshot`, `getMessages`, `getConversation`
244
+ or generic `subscribe`. Use resolveThreadId, getThread, subscribeThread,
245
+ loadEarlier and refreshThread. Public readers no longer accept a separate fourth
246
+ signal argument. Use `{signal}` together with the query instead.
247
+
248
+ Host-owned calls/presence transports use `createChatWithTransport` from
249
+ `@pmate/chat-sdk/advanced`; it returns the same public client. `getChatRuntime(chat)`
250
+ provides explicit one-time migration/diagnostic access. `handleChatReach` and
251
+ `revokeChatAccess` are advanced integration helpers. A deprecated
252
+ `getLegacyChat(chat)` adapter is available only from this entry during migration.
253
+ The raw factory is named `createChatSession` in `/runtime`; its old `createChat`
254
+ name is deprecated. Raw storage/transport clients remain advanced capabilities.
255
+ They must share identity and respect scope authority. Apps do not maintain a second
256
+ message cache, outbox, recipient roster or control revision generator.
249
257
 
250
258
  ### Reading storage recovery
251
259
 
@@ -0,0 +1,10 @@
1
+ import type { Chat } from "./chatContract.js";
2
+ import type { CreateChatOptions } from "./runtime/createChat.js";
3
+ /** Host-owned transport integration. The default factory never accepts raw transports. */
4
+ export declare function createChatWithTransport(options: CreateChatOptions): Chat;
5
+ /** Explicit migration/diagnostic access, outside the ordinary client. */
6
+ export declare function getChatRuntime(chat: Chat): import("./runtime/chatSession.js").ChatSession<import("./runtime/cachedMessage.js").CachedChatMessage>;
7
+ export declare function handleChatReach(chat: Chat, event: Parameters<ReturnType<typeof getChatRuntime>["handleReach"]>[0]): Promise<"handled" | undefined>;
8
+ export declare function revokeChatAccess(chat: Chat): Promise<void>;
9
+ /** @deprecated Transitional next.68 adapter. New App code must use the public client. */
10
+ export declare function getLegacyChat(chat: Chat): import("./internal/chatEngine.js").ChatEngine;
@@ -0,0 +1,10 @@
1
+ import { createPublicChat } from "./internal/createPublicChat.js";
2
+ import { chatEngine } from "./internal/chatRegistry.js";
3
+ /** Host-owned transport integration. The default factory never accepts raw transports. */
4
+ export function createChatWithTransport(options) { return createPublicChat(options); }
5
+ /** Explicit migration/diagnostic access, outside the ordinary client. */
6
+ export function getChatRuntime(chat) { return chatEngine(chat).advanced; }
7
+ export function handleChatReach(chat, event) { return chatEngine(chat).handleReach(event); }
8
+ export function revokeChatAccess(chat) { return chatEngine(chat).revokeAccess(); }
9
+ /** @deprecated Transitional next.68 adapter. New App code must use the public client. */
10
+ export function getLegacyChat(chat) { return chatEngine(chat); }
package/lib/chat.d.ts CHANGED
@@ -1,131 +1,5 @@
1
- import type { ChatGroup as RuntimeGroup } from "./runtime/groupClient.js";
2
- import { type ChatMessage } from "./messageView.js";
3
- export type { ChatMessage } from "./messageView.js";
4
- import { type CreateChatOptions } from "./runtime/createChat.js";
5
- import type { ChatSession, ChatSummarySnapshot as SessionSummary, ChatThreadSnapshot as SessionThread } from "./runtime/chatSession.js";
6
- import type { CachedChatMessage } from "./runtime/cachedMessage.js";
7
- import type { ConversationTarget } from "./runtime/conversationDirectory.js";
8
- import type { OutgoingContent as RuntimeContent } from "./runtime/outgoingContent.js";
9
- import { ChatError } from "./runtime/chatError.js";
10
- export { ChatError } from "./runtime/chatError.js";
11
- export type { ChatErrorCode } from "./runtime/chatError.js";
12
- export type { CreateChatOptions, ConversationTarget };
13
- export type OutgoingContent = Exclude<RuntimeContent, {
14
- groupCreate: unknown;
15
- } | {
16
- groupUpdate: unknown;
17
- }>;
18
- export type ChatGroup = Readonly<Omit<RuntimeGroup, "versionNumber">>;
19
- export type ConversationInfo = Readonly<{
20
- kind: "dm" | "group";
21
- targetId: string;
22
- }>;
23
- export type ChatThreadSnapshot = Readonly<{
24
- threadId: string;
25
- messages: readonly ChatMessage[];
26
- ready: boolean;
27
- hasEarlier: boolean;
28
- conversation?: ConversationInfo;
29
- unread: SessionThread<CachedChatMessage>["unread"];
30
- group: ChatGroup | undefined;
31
- readCounts: SessionThread<CachedChatMessage>["readCounts"];
32
- error?: ChatError;
33
- }>;
34
- export type ChatSnapshot = Readonly<{
35
- threads: readonly {
36
- threadId: string;
37
- lastMessage?: ChatMessage;
38
- group: ChatGroup | undefined;
39
- unread: ChatThreadSnapshot["unread"];
40
- outgoingCount: number;
41
- }[];
42
- unreadMessageCount: number | undefined;
43
- badgeCount: number | undefined;
44
- connection: SessionSummary<CachedChatMessage>["connection"];
45
- }>;
46
- export type MessageQuery = {
47
- earlier?: boolean;
48
- all?: boolean;
49
- refresh?: boolean;
50
- };
51
- export type ReadersQuery = {
52
- afterProfileId?: string;
53
- revision?: number;
54
- };
55
- export type GroupChanges = {
56
- expectedRevision?: string;
57
- addMembers?: readonly string[];
58
- removeMembers?: readonly string[];
59
- title?: string;
60
- avatar?: string;
61
- };
62
- export type SendOptions = {
63
- waitFor?: "accepted" | "delivered";
64
- };
65
- export type SentMessage = Readonly<{
66
- id: string;
67
- timestamp: number;
68
- }>;
69
- type SubscribeOptions<T> = {
70
- fields?: readonly (keyof T)[];
71
- emitCurrent?: boolean;
72
- };
73
- /** App-facing API. Cache coverage, raw protocol and synchronization stay in advanced. */
74
- export declare class Chat {
75
- readonly advanced: ChatSession<CachedChatMessage>;
76
- private options;
77
- private groups;
78
- private group;
79
- private present;
80
- private redactedQuotes;
81
- private messageLists;
82
- private messages;
83
- private views;
84
- private summary?;
85
- private targets;
86
- private errors;
87
- private listeners;
88
- private closed;
89
- constructor(advanced: ChatSession<CachedChatMessage>, options: CreateChatOptions);
90
- private operation;
91
- private deny;
92
- private threadOperation;
93
- start(): Promise<void>;
94
- getConversation(target: ConversationTarget): Promise<string>;
95
- getSnapshot(): ChatSnapshot;
96
- getSnapshot(threadId: string): ChatThreadSnapshot;
97
- getMessages(threadId: string, query?: MessageQuery): Promise<ChatThreadSnapshot>;
98
- sendMessage(threadId: string, content: OutgoingContent, options?: SendOptions): Promise<SentMessage>;
99
- retryMessage(messageId: string): Promise<SentMessage>;
100
- subscribe(listener: (snapshot: ChatSnapshot) => void, options?: SubscribeOptions<ChatSnapshot>): () => void;
101
- subscribe(threadId: string, listener: (snapshot: ChatThreadSnapshot) => void, options?: SubscribeOptions<ChatThreadSnapshot>): () => void;
102
- private sessionFields;
103
- private notify;
104
- observeMessages(threadId: string, ids: string[], event: "visible" | "played"): Promise<void>;
105
- markRead(threadId: string): Promise<void>;
106
- getReaders(threadId: string, messageId: string, page?: ReadersQuery, signal?: AbortSignal): Promise<import("./runtime/messageReaders.js").MessageReadersPage>;
107
- watchReaders(threadId: string | undefined, ids: string[], receiver?: string): Promise<void>;
108
- handleReach(event: Parameters<ChatSession<CachedChatMessage>["handleReach"]>[0]): Promise<"handled" | undefined>;
109
- getVoiceAttachment(threadId: string, messageId: string): Promise<import("./runtime/durableSender.js").VoiceAttachmentLease>;
110
- getGroup(threadId: string, options?: {
111
- refresh?: boolean;
112
- }): Promise<Readonly<Omit<RuntimeGroup, "versionNumber">>>;
113
- createGroup(input: {
114
- groupId?: string;
115
- title: string;
116
- members: readonly string[];
117
- }): Promise<{
118
- threadId: string;
119
- revision: string;
120
- teamId: string;
121
- title: string;
122
- groupId: string;
123
- avatar?: string;
124
- ownerId: string;
125
- memberIds: readonly string[];
126
- }>;
127
- updateGroup(threadId: string, changes: GroupChanges): Promise<Readonly<Omit<RuntimeGroup, "versionNumber">>>;
128
- revokeAccess(): Promise<void>;
129
- close(): Promise<void>;
130
- }
1
+ import type { Chat, CreateChatOptions } from "./chatContract.js";
2
+ export type * from "./chatContract.js";
3
+ export type * from "./chatTypes.js";
4
+ export { ChatError, type ChatErrorCode } from "./chatError.js";
131
5
  export declare function createChat(options: CreateChatOptions): Chat;
package/lib/chat.js CHANGED
@@ -1,219 +1,11 @@
1
- import { createMessagePresenter } from "./messageView.js";
2
- import { createChat as createSession } from "./runtime/createChat.js";
3
- import { asChatError, ChatError } from "./runtime/chatError.js";
4
- export { ChatError } from "./runtime/chatError.js";
5
- const emptyMessages = Object.freeze([]);
6
- /** App-facing API. Cache coverage, raw protocol and synchronization stay in advanced. */
7
- export class Chat {
8
- advanced;
9
- options;
10
- groups = new WeakMap();
11
- group(value) {
12
- if (!value)
13
- return undefined;
14
- let projected = this.groups.get(value);
15
- if (!projected) {
16
- const { versionNumber: _, ...visible } = value;
17
- projected = visible;
18
- this.groups.set(value, projected);
19
- }
20
- return projected;
21
- }
22
- present = createMessagePresenter();
23
- redactedQuotes = new WeakMap();
24
- messageLists = new WeakMap();
25
- messages(items) {
26
- let values = this.messageLists.get(items);
27
- if (!values) {
28
- const recalled = new Set(items.filter(item => item.recalled).map(item => item.id));
29
- values = items.map(item => {
30
- const value = this.present(item);
31
- if (!value.quote?.messageId || !recalled.has(value.quote.messageId))
32
- return value;
33
- let hidden = this.redactedQuotes.get(value);
34
- if (!hidden) {
35
- hidden = { ...value, quote: { ...value.quote, content: "", recalled: true } };
36
- this.redactedQuotes.set(value, hidden);
37
- }
38
- return hidden;
39
- });
40
- this.messageLists.set(items, values);
41
- }
42
- return values;
43
- }
44
- views = new Map();
45
- summary;
46
- targets = new Map();
47
- errors = new Map();
48
- listeners = new Map();
49
- closed = false;
50
- constructor(advanced, options) {
51
- this.advanced = advanced;
52
- this.options = options;
53
- }
54
- async operation(run) { try {
55
- return await run();
56
- }
57
- catch (error) {
58
- throw asChatError(error);
59
- } }
60
- async deny(threadId, failure) {
61
- this.errors.set(threadId, failure);
62
- this.targets.delete(threadId);
63
- const retiring = this.advanced.revokeAccess();
64
- this.notify(threadId);
65
- try {
66
- await retiring;
67
- }
68
- catch (error) {
69
- failure.cause = new AggregateError([failure.cause, error], "Access withdrawn; cache cleanup incomplete");
70
- }
71
- }
72
- threadOperation(threadId, run) {
73
- return this.operation(async () => {
74
- const denied = threadId ? this.errors.get(threadId) : undefined;
75
- if (denied?.code === "access-denied")
76
- throw denied;
77
- try {
78
- return await run();
79
- }
80
- catch (error) {
81
- const failure = asChatError(error);
82
- if (threadId && failure.code === "access-denied")
83
- await this.deny(threadId, failure);
84
- throw failure;
85
- }
86
- });
87
- }
88
- start() { return this.operation(() => this.options.connection ? this.advanced.initialize() : this.advanced.start()); }
89
- getConversation(target) {
90
- return this.operation(async () => {
91
- const id = await this.advanced.getConversation(target);
92
- const value = target.groupId !== undefined ? { kind: "group", targetId: target.groupId } : { kind: "dm", targetId: target.peerId };
93
- this.targets.delete(id);
94
- this.targets.set(id, value);
95
- if (this.targets.size > 256)
96
- this.targets.delete(this.targets.keys().next().value);
97
- this.notify(id);
98
- return id;
99
- });
100
- }
101
- getSnapshot(threadId) {
102
- if (threadId === undefined) {
103
- const source = this.advanced.getSnapshot(), previous = this.summary;
104
- const threads = source.threads.filter(thread => this.errors.get(thread.threadId)?.code !== "access-denied").map(thread => ({ threadId: thread.threadId, lastMessage: this.messages(this.advanced.getSnapshot(thread.threadId).timeline).at(-1), group: this.group(thread.group), unread: thread.unread, outgoingCount: thread.outgoingCount }));
105
- const unreadMessageCount = source.unreadMessageCount === undefined ? undefined : threads.reduce((sum, thread) => sum + (thread.unread?.unreadMessageCount ?? 0), 0);
106
- const badgeCount = source.badgeCount === undefined ? undefined : threads.reduce((sum, thread) => sum + (thread.unread?.badgeCount ?? 0), 0);
107
- if (previous && previous.connection === source.connection && previous.badgeCount === badgeCount && previous.unreadMessageCount === unreadMessageCount && previous.threads.length === threads.length && threads.every((thread, i) => { const old = previous.threads[i]; return thread.threadId === old.threadId && thread.lastMessage === old.lastMessage && thread.group === old.group && thread.unread === old.unread && thread.outgoingCount === old.outgoingCount; }))
108
- return previous;
109
- return this.summary = { threads, connection: source.connection, badgeCount, unreadMessageCount };
110
- }
111
- const source = this.advanced.getSnapshot(threadId), previous = this.views.get(threadId);
112
- const error = this.closed ? undefined : this.errors.get(threadId), denied = error?.code === "access-denied";
113
- const target = this.closed || !this.advanced.active || denied ? undefined : this.advanced.getConversationTarget(threadId) ?? this.targets.get(threadId);
114
- const conversation = previous?.conversation?.kind === target?.kind && previous?.conversation?.targetId === target?.targetId ? previous?.conversation : target ? { kind: target.kind, targetId: target.targetId } : undefined;
115
- // Partial migrated rows never masquerade as a trusted recent window.
116
- const next = { threadId, conversation, messages: !denied && source.complete ? this.messages(source.timeline) : emptyMessages, ready: !denied && source.complete, hasEarlier: !denied && source.hasEarlier, unread: denied ? undefined : source.unread, group: denied ? undefined : this.group(source.group), readCounts: denied ? undefined : source.readCounts, error };
117
- if (previous && Object.keys(next).every(key => next[key] === previous[key]))
118
- return previous;
119
- this.views.set(threadId, next);
120
- return next;
121
- }
122
- async getMessages(threadId, query = {}) {
123
- try {
124
- if (Object.keys(query).some(key => !["earlier", "all", "refresh"].includes(key)))
125
- throw new ChatError("invalid-input", "Unsupported message query");
126
- const page = await this.advanced.getMessages(threadId, query);
127
- if (!page.complete)
128
- throw new ChatError("unavailable", "Messages are not ready; retry when connected");
129
- this.errors.delete(threadId);
130
- this.notify(threadId);
131
- return this.getSnapshot(threadId);
132
- }
133
- catch (error) {
134
- const failure = asChatError(error);
135
- if (!this.closed) {
136
- if (failure.code === "access-denied")
137
- await this.deny(threadId, failure);
138
- else {
139
- this.errors.set(threadId, failure);
140
- this.notify(threadId);
141
- }
142
- }
143
- throw failure;
144
- }
145
- }
146
- sendMessage(threadId, content, options = {}) {
147
- return this.threadOperation(threadId, async () => {
148
- if (content && ("groupCreate" in content || "groupUpdate" in content))
149
- throw new ChatError("invalid-input", "Use createGroup or updateGroup for group changes");
150
- const result = await this.advanced.sendMessage(threadId, content, options);
151
- return { id: result.hash, timestamp: result.t };
152
- });
153
- }
154
- retryMessage(messageId) { return this.operation(async () => { const result = await this.advanced.retryMessage(messageId); return { id: result.hash, timestamp: result.t }; }); }
155
- subscribe(target, callback, options = {}) {
156
- const global = typeof target === "function", key = global ? "" : target;
157
- const config = global ? callback : options;
158
- const read = () => global ? this.getSnapshot() : this.getSnapshot(key);
159
- let previous = read();
160
- const emit = () => {
161
- const value = read();
162
- const fields = config?.fields;
163
- const changed = fields ? fields.some(field => previous[field] !== value[field]) : value !== previous;
164
- previous = value;
165
- if (changed)
166
- (global ? target : callback)(value);
167
- };
168
- let listeners = this.listeners.get(key);
169
- if (!listeners) {
170
- listeners = new Set();
171
- this.listeners.set(key, listeners);
172
- }
173
- listeners.add(emit);
174
- const off = global ? this.advanced.subscribeChanges(emit) : this.advanced.subscribeChanges(key, emit, this.sessionFields(options.fields));
175
- const dispose = () => { off(); listeners.delete(emit); if (!listeners.size)
176
- this.listeners.delete(key); };
177
- if (config?.emitCurrent !== false) {
178
- try {
179
- (global ? target : callback)(previous);
180
- }
181
- catch (error) {
182
- dispose();
183
- throw error;
184
- }
185
- }
186
- return dispose;
187
- }
188
- sessionFields(fields) {
189
- return fields ? [...new Set(fields.flatMap(field => field === "messages" ? ["timeline", "complete"] : field === "ready" ? ["complete"] : field === "conversation" ? ["messages", "group"] : field === "error" || field === "threadId" ? [] : [field]))] : undefined;
190
- }
191
- notify(threadId) { this.listeners.get(threadId)?.forEach(listener => listener()); this.listeners.get("")?.forEach(listener => listener()); }
192
- observeMessages(threadId, ids, event) { return this.threadOperation(threadId, async () => { await this.advanced.observeMessages(threadId, ids, event); }); }
193
- markRead(threadId) { return this.threadOperation(threadId, async () => { await this.advanced.markRead(threadId); }); }
194
- getReaders(threadId, messageId, page = {}, signal) {
195
- return this.threadOperation(threadId, async () => {
196
- if (Object.keys(page).some(key => !["afterProfileId", "revision"].includes(key)))
197
- throw new ChatError("invalid-input", "Unsupported reader query");
198
- return this.advanced.getReaders(threadId, messageId, page, signal);
199
- });
200
- }
201
- watchReaders(threadId, ids, receiver) { return this.threadOperation(threadId, () => this.advanced.observeMessages(threadId, ids, "receipts", receiver)); }
202
- handleReach(event) { return this.operation(() => this.advanced.handleReach(event)); }
203
- getVoiceAttachment(threadId, messageId) { return this.threadOperation(threadId, () => this.advanced.getVoiceAttachment(threadId, messageId)); }
204
- getGroup(threadId, options = {}) { return this.threadOperation(threadId, async () => this.group(await this.advanced.getGroup(threadId, options))); }
205
- createGroup(input) {
206
- return this.operation(async () => {
207
- const groupId = input.groupId ?? crypto.randomUUID(), threadId = await this.getConversation({ groupId });
208
- const group = await this.advanced.createGroup({ id: threadId, kind: "group", targetId: groupId, teamId: this.options.teamId }, { title: input.title, members: input.members });
209
- return { ...this.group(group), threadId };
210
- });
211
- }
212
- updateGroup(threadId, changes) { return this.threadOperation(threadId, async () => this.group(await this.advanced.updateGroup(threadId, changes))); }
213
- revokeAccess() { return this.operation(() => this.advanced.revokeAccess()); }
214
- close() { this.closed = true; return this.operation(() => this.advanced.close()).finally(() => { this.errors.clear(); this.targets.clear(); this.listeners.clear(); this.views.clear(); this.summary = undefined; this.present = createMessagePresenter(); this.messageLists = new WeakMap(); this.groups = new WeakMap(); this.redactedQuotes = new WeakMap(); }); }
215
- }
1
+ import { createPublicChat } from "./internal/createPublicChat.js";
2
+ import { ChatError } from "./chatError.js";
3
+ export { ChatError } from "./chatError.js";
216
4
  export function createChat(options) {
217
- const configured = { ...options, onError: (error) => options.onError(asChatError(error)) };
218
- return new Chat(createSession(configured), configured);
5
+ const allowed = ["appId", "accountId", "profileId", "teamId", "threadId", "endpoint", "uploadEndpoint", "clientInstanceId", "fetch", "onError", "token", "getToken"];
6
+ if (Object.keys(options).some(key => !allowed.includes(key)))
7
+ throw new ChatError("invalid-input", "Unsupported chat option; transport integration belongs to /advanced");
8
+ if (options.token !== undefined && options.getToken !== undefined)
9
+ throw new ChatError("invalid-input", "Select one credential source");
10
+ return createPublicChat({ ...options, onError: error => options.onError?.(error) });
219
11
  }