@pmate/chat-sdk 0.12.0-next.5 → 0.12.0-next.51
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 +34 -1
- package/lib/runtime/batchedReadIndex.d.ts +16 -3
- package/lib/runtime/batchedReadIndex.js +50 -4
- package/lib/runtime/cachedMessage.d.ts +12 -0
- package/lib/runtime/cachedMessage.js +18 -0
- package/lib/runtime/chatClient.d.ts +1 -0
- package/lib/runtime/chatClient.js +1 -1
- package/lib/runtime/chatSession.d.ts +78 -13
- package/lib/runtime/chatSession.js +494 -43
- package/lib/runtime/connectivity.d.ts +4 -0
- package/lib/runtime/connectivity.js +8 -0
- package/lib/runtime/conversationRead.d.ts +13 -0
- package/lib/runtime/conversationRead.js +34 -0
- package/lib/runtime/conversationReading.d.ts +28 -0
- package/lib/runtime/conversationReading.js +47 -0
- package/lib/runtime/createChat.d.ts +5 -5
- package/lib/runtime/createChat.js +16 -17
- package/lib/runtime/durableSender.d.ts +63 -14
- package/lib/runtime/durableSender.js +419 -45
- package/lib/runtime/index.d.ts +4 -0
- package/lib/runtime/index.js +4 -0
- package/lib/runtime/messageClient.d.ts +32 -8
- package/lib/runtime/messageClient.js +415 -75
- package/lib/runtime/messageControls.d.ts +19 -0
- package/lib/runtime/messageControls.js +44 -0
- package/lib/runtime/messageLaunch.d.ts +42 -0
- package/lib/runtime/messageLaunch.js +133 -0
- package/lib/runtime/messageQuotes.d.ts +3 -0
- package/lib/runtime/messageQuotes.js +17 -0
- package/lib/runtime/messageReaders.d.ts +12 -1
- package/lib/runtime/messageReaders.js +67 -3
- package/lib/runtime/messageStore.d.ts +40 -2
- package/lib/runtime/messageStore.js +218 -10
- package/lib/runtime/messageTimeline.d.ts +50 -0
- package/lib/runtime/messageTimeline.js +120 -0
- package/lib/runtime/readCounts.d.ts +15 -1
- package/lib/runtime/readCounts.js +96 -8
- package/lib/runtime/readIndex.d.ts +55 -6
- package/lib/runtime/readIndex.js +363 -14
- package/lib/runtime/readSync.d.ts +1 -0
- package/lib/runtime/readSync.js +25 -8
- package/lib/runtime/readingClient.d.ts +19 -0
- package/lib/runtime/readingClient.js +131 -19
- package/lib/runtime/readingContent.d.ts +5 -0
- package/lib/runtime/readingContent.js +23 -0
- package/lib/runtime/recordCache.d.ts +9 -0
- package/lib/runtime/recordCache.js +62 -5
- package/lib/runtime/responseCache.d.ts +44 -0
- package/lib/runtime/responseCache.js +173 -0
- package/lib/runtime/voiceUpload.d.ts +2 -0
- package/lib/runtime/voiceUpload.js +20 -0
- package/package.json +6 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Browser connectivity is advisory: only suppress network errors while the
|
|
2
|
+
* browser explicitly reports offline. Permission and data errors stay visible. */
|
|
3
|
+
export declare function isOffline(): boolean;
|
|
4
|
+
export declare function isOfflineNetworkFailure(error: unknown): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Browser connectivity is advisory: only suppress network errors while the
|
|
2
|
+
* browser explicitly reports offline. Permission and data errors stay visible. */
|
|
3
|
+
export function isOffline() {
|
|
4
|
+
return typeof navigator !== "undefined" && navigator.onLine === false;
|
|
5
|
+
}
|
|
6
|
+
export function isOfflineNetworkFailure(error) {
|
|
7
|
+
return isOffline() && (error instanceof TypeError || error?.name === "AbortError");
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ReadIndexState } from "./readIndex.js";
|
|
2
|
+
/** Optional observations whose index metadata may still be in flight. */
|
|
3
|
+
export type ConversationReadMessage = {
|
|
4
|
+
id: string;
|
|
5
|
+
threadId: string;
|
|
6
|
+
mine?: boolean;
|
|
7
|
+
from?: string;
|
|
8
|
+
recalled?: boolean;
|
|
9
|
+
readPolicy?: "visible" | "explicit";
|
|
10
|
+
kind?: string;
|
|
11
|
+
};
|
|
12
|
+
/** Explicitly mark ordinary text history read; unheard audio stays unread. */
|
|
13
|
+
export declare function recordConversationRead(state: ReadIndexState, conversationId: string, messages?: readonly ConversationReadMessage[], operationId?: () => `${string}-${string}-${string}-${string}-${string}`): ReadIndexState;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { recordReadIntent, sourcesInConversation } from "./readIndex.js";
|
|
2
|
+
/** Explicitly mark ordinary text history read; unheard audio stays unread. */
|
|
3
|
+
export function recordConversationRead(state, conversationId, messages = [], operationId = () => crypto.randomUUID()) {
|
|
4
|
+
if (!conversationId)
|
|
5
|
+
throw new Error("INVALID_READING_THREAD");
|
|
6
|
+
const sources = sourcesInConversation(state, conversationId);
|
|
7
|
+
if (new Set(sources.map(source => source.teamId)).size > 1)
|
|
8
|
+
throw new Error("READ_CONVERSATION_TEAM_MISMATCH");
|
|
9
|
+
let next = state;
|
|
10
|
+
const indexedIds = new Set();
|
|
11
|
+
for (const source of sources) {
|
|
12
|
+
const incoming = source.messages.filter(message => {
|
|
13
|
+
indexedIds.add(message.id);
|
|
14
|
+
return !message.recalled && message.from !== state.profileId && message.recipients.includes(state.profileId);
|
|
15
|
+
});
|
|
16
|
+
const latest = incoming.filter(message => message.readPolicy === "visible" && message.seq > source.readThroughSeq)
|
|
17
|
+
.reduce((last, message) => !last || message.seq > last.seq ? message : last, undefined);
|
|
18
|
+
if (latest)
|
|
19
|
+
next = recordReadIntent(next, { operationId: operationId(), threadId: source.id, messageId: latest.id, readPolicy: "visible" });
|
|
20
|
+
}
|
|
21
|
+
// Live/history messages can precede their read-index metadata. Persist their
|
|
22
|
+
// observations so leaving the page or reloading cannot resurrect the badge.
|
|
23
|
+
const observed = new Set(next.observedReadIntents?.filter(intent => intent.threadId === conversationId).map(intent => intent.messageId));
|
|
24
|
+
for (const message of messages) {
|
|
25
|
+
if (message.threadId !== conversationId || message.mine || message.from === state.profileId || message.recalled || indexedIds.has(message.id) || observed.has(message.id))
|
|
26
|
+
continue;
|
|
27
|
+
const readPolicy = message.readPolicy ?? (message.kind === "voice" ? "explicit" : "visible");
|
|
28
|
+
if (message.kind === "voice" || readPolicy === "explicit")
|
|
29
|
+
continue;
|
|
30
|
+
next = recordReadIntent(next, { operationId: operationId(), threadId: conversationId, messageId: message.id, readPolicy });
|
|
31
|
+
observed.add(message.id);
|
|
32
|
+
}
|
|
33
|
+
return next;
|
|
34
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { unreadInConversation, type ReadIndexState } from "./readIndex.js";
|
|
2
|
+
export type ReadingCandidate = {
|
|
3
|
+
id: string;
|
|
4
|
+
threadId: string;
|
|
5
|
+
from?: string;
|
|
6
|
+
mine?: boolean;
|
|
7
|
+
recalled?: boolean;
|
|
8
|
+
unread?: boolean;
|
|
9
|
+
attentionEligible?: boolean;
|
|
10
|
+
readPolicy?: "visible" | "explicit";
|
|
11
|
+
};
|
|
12
|
+
export type MessageReadingState = {
|
|
13
|
+
seq?: number;
|
|
14
|
+
read: boolean;
|
|
15
|
+
readPolicy: "visible" | "explicit";
|
|
16
|
+
};
|
|
17
|
+
export type ConversationReadingView = {
|
|
18
|
+
messageReading: Readonly<Record<string, MessageReadingState>>;
|
|
19
|
+
unread: ReturnType<typeof unreadInConversation> | undefined;
|
|
20
|
+
badgeCount: number | undefined;
|
|
21
|
+
};
|
|
22
|
+
/** One projection for indexed authority and observations awaiting indexing. A
|
|
23
|
+
* directory badge is optional compatibility metadata, never proof of coverage. */
|
|
24
|
+
export declare function projectConversationReading(state: ReadIndexState | undefined, conversation: {
|
|
25
|
+
id: string;
|
|
26
|
+
kind: "dm" | "group";
|
|
27
|
+
badgeCount?: number;
|
|
28
|
+
}, candidates?: readonly ReadingCandidate[]): ConversationReadingView;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { sourcesInConversation, unreadInConversation } from "./readIndex.js";
|
|
2
|
+
/** One projection for indexed authority and observations awaiting indexing. A
|
|
3
|
+
* directory badge is optional compatibility metadata, never proof of coverage. */
|
|
4
|
+
export function projectConversationReading(state, conversation, candidates = []) {
|
|
5
|
+
const unique = new Map(candidates.filter(message => message.threadId === conversation.id).map(message => [message.id, message]));
|
|
6
|
+
const hidden = new Map();
|
|
7
|
+
for (const message of unique.values())
|
|
8
|
+
if (message.recalled && message.from)
|
|
9
|
+
hidden.set(message.id, new Set([message.from]));
|
|
10
|
+
const sources = state ? sourcesInConversation(state, conversation.id) : [];
|
|
11
|
+
const sourceIds = new Set([conversation.id, ...sources.map(source => source.id)]);
|
|
12
|
+
const indexed = new Set(), messageReading = Object.create(null);
|
|
13
|
+
for (const source of sources)
|
|
14
|
+
for (const message of source.messages) {
|
|
15
|
+
indexed.add(message.id);
|
|
16
|
+
messageReading[message.id] = { seq: message.seq, readPolicy: message.readPolicy, read: message.recalled || hidden.get(message.id)?.has(message.from) === true || message.from === state.profileId || !message.recipients.includes(state.profileId) || (message.readPolicy === "explicit" ? source.explicitReadMessageIds.includes(message.id) : message.seq <= source.readThroughSeq) };
|
|
17
|
+
}
|
|
18
|
+
const observed = new Map();
|
|
19
|
+
for (const intent of state?.observedReadIntents ?? [])
|
|
20
|
+
if (sourceIds.has(intent.threadId)) {
|
|
21
|
+
if (!observed.has(intent.messageId))
|
|
22
|
+
observed.set(intent.messageId, new Set());
|
|
23
|
+
observed.get(intent.messageId).add(intent.readPolicy);
|
|
24
|
+
}
|
|
25
|
+
let pendingMessages = 0, pendingMentions = 0, observedBadges = 0;
|
|
26
|
+
for (const message of unique.values()) {
|
|
27
|
+
const incoming = !message.mine && message.from !== state?.profileId && !message.recalled;
|
|
28
|
+
const attention = conversation.kind === "dm" || message.attentionEligible === true;
|
|
29
|
+
const policy = message.readPolicy ?? "visible";
|
|
30
|
+
const acknowledged = observed.get(message.id)?.has(policy) === true;
|
|
31
|
+
if (!indexed.has(message.id) && acknowledged)
|
|
32
|
+
messageReading[message.id] = { read: true, readPolicy: policy };
|
|
33
|
+
if (!incoming)
|
|
34
|
+
continue;
|
|
35
|
+
if (acknowledged && attention)
|
|
36
|
+
observedBadges++;
|
|
37
|
+
if (indexed.has(message.id) || acknowledged || message.unread !== true)
|
|
38
|
+
continue;
|
|
39
|
+
pendingMessages++;
|
|
40
|
+
if (message.attentionEligible === true)
|
|
41
|
+
pendingMentions++;
|
|
42
|
+
}
|
|
43
|
+
const authoritative = sources.length && state ? unreadInConversation(state, conversation.id, hidden) : undefined;
|
|
44
|
+
const unread = authoritative ? { unreadMessageCount: authoritative.unreadMessageCount + pendingMessages, unreadMentionCount: authoritative.unreadMentionCount + pendingMentions, badgeCount: authoritative.badgeCount + (conversation.kind === "group" ? pendingMentions : pendingMessages) } : undefined;
|
|
45
|
+
const pendingBadge = conversation.kind === "group" ? pendingMentions : pendingMessages;
|
|
46
|
+
return { messageReading, unread, badgeCount: unread?.badgeCount ?? (conversation.badgeCount === undefined ? undefined : Math.max(Math.max(0, conversation.badgeCount - observedBadges), pendingBadge)) };
|
|
47
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { ChatSession } from "./chatSession.js";
|
|
2
|
-
import type { ChatReaderSocialMessage } from "./readerClient.js";
|
|
3
2
|
import type { ChatClient, ChatClientOptions } from "./chatClient.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
timestamp: number;
|
|
7
|
-
};
|
|
3
|
+
import { type CachedChatMessage } from "./cachedMessage.js";
|
|
4
|
+
export type { CachedChatMessage } from "./cachedMessage.js";
|
|
8
5
|
export type CreateChatOptions = {
|
|
9
6
|
appId: string;
|
|
10
7
|
accountId: string;
|
|
11
8
|
profileId: string;
|
|
12
9
|
teamId: string;
|
|
13
10
|
token: string;
|
|
11
|
+
/** Limit reconnect reconciliation for a dedicated conversation document. */
|
|
12
|
+
threadId?: string;
|
|
13
|
+
uploadEndpoint?: string;
|
|
14
14
|
endpoint?: string;
|
|
15
15
|
clientInstanceId?: string;
|
|
16
16
|
fetch?: typeof fetch;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { voiceUploader } from "./voiceUpload.js";
|
|
2
|
+
import { isReadingControl, messageRequiresPlayback, messageMentionsProfile } from "./readingContent.js";
|
|
1
3
|
import { ChatSession } from "./chatSession.js";
|
|
2
4
|
import { SocialReadClient } from "./socialReadClient.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
+
import { isValidChatTeamId } from "./thread.js";
|
|
6
|
+
import { decodeCachedChatMessage } from "./cachedMessage.js";
|
|
5
7
|
/** Standard browser entry: storage, paging, realtime and reading share one identity. */
|
|
6
8
|
export function createChat(options) {
|
|
7
9
|
const { appId, accountId, profileId, teamId, token, onError } = options;
|
|
@@ -16,24 +18,21 @@ export function createChat(options) {
|
|
|
16
18
|
};
|
|
17
19
|
return new ChatSession({
|
|
18
20
|
sharedConnection: options.connection,
|
|
21
|
+
uploadVoice: voiceUploader(profileId, options.uploadEndpoint, options.fetch),
|
|
22
|
+
activeThreadId: options.threadId,
|
|
19
23
|
messages: { scope: { appId, accountId, profileId, teamId }, transport: { load: (thread, signal) => page(thread, signal), changes: (thread, after, signal) => page(thread, signal, { after }), older: (thread, before, signal) => page(thread, signal, { before }) }, onError },
|
|
20
24
|
reading: { appId, profileId, sessionToken: token, hubHttpUrl: http, fetch: options.fetch, onError },
|
|
21
25
|
connection: { appId, profileId, teamId, hubUrl: ws, clientInstanceId: options.clientInstanceId, fetchImpl: options.fetch,
|
|
22
26
|
session: { token, identity: { accountId, app: appId, profileId } }, onNotification: options.onNotification, onReach: options.onReach },
|
|
23
|
-
|
|
27
|
+
readingCandidate: message => {
|
|
28
|
+
if (isReadingControl(message.body, appId))
|
|
29
|
+
return undefined;
|
|
30
|
+
const body = message.body;
|
|
31
|
+
if (!body || !["message", "quote"].includes(String(body.kind)) && !(body.kind === "contact" && ["friendRequest", "requestFriend"].includes(String(body.action))))
|
|
32
|
+
return undefined;
|
|
33
|
+
return { id: message.id, threadId: message.threadId, from: message.from, mine: message.from === profileId, recalled: message.recalled, unread: message.from !== profileId,
|
|
34
|
+
readPolicy: message.readPolicy === "explicit" || message.legacyDisplay?.kind === "voice" || messageRequiresPlayback(body) ? "explicit" : "visible", attentionEligible: messageMentionsProfile(body, profileId) };
|
|
35
|
+
},
|
|
36
|
+
decode: message => decodeCachedChatMessage(message, { appId, profileId, teamId }), onError,
|
|
24
37
|
});
|
|
25
38
|
}
|
|
26
|
-
function decodeIncoming(message, scope) {
|
|
27
|
-
if (message.opcode !== 1 || !message.body || typeof message.body !== "object")
|
|
28
|
-
return;
|
|
29
|
-
const body = message.body;
|
|
30
|
-
if (body.kind !== "message" || body.content?.appId !== scope.appId || chatTeamId(body) !== scope.teamId)
|
|
31
|
-
return;
|
|
32
|
-
if (body.threadKind !== "dm" && body.threadKind !== "group")
|
|
33
|
-
return;
|
|
34
|
-
if (body.threadKind === "dm" && message.from !== scope.profileId && message.to !== scope.profileId)
|
|
35
|
-
return;
|
|
36
|
-
const threadId = body.threadKind === "dm" ? dmThreadId(scope.appId, message.from, message.to, scope.teamId) : groupThreadId(scope.appId, message.to, scope.teamId);
|
|
37
|
-
return { appId: scope.appId, teamId: scope.teamId, threadId, threadKind: body.threadKind, messageId: message.hash, id: message.hash,
|
|
38
|
-
timestamp: message.t, serverCreatedAt: message.t, clientCreatedAt: message.t, from: message.from, to: message.to, body: message.body, recipients: message.recipients };
|
|
39
|
-
}
|
|
@@ -8,13 +8,52 @@ export type OutgoingMessage = {
|
|
|
8
8
|
message: ChatRuntimeMessage;
|
|
9
9
|
status: "sending" | "failed" | "sent";
|
|
10
10
|
confirmed?: boolean;
|
|
11
|
+
pendingState?: 0 | 1;
|
|
12
|
+
accessVersion?: number;
|
|
13
|
+
voiceBlob?: Blob;
|
|
14
|
+
voiceData?: {
|
|
15
|
+
bytes: ArrayBuffer;
|
|
16
|
+
type: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export type SendMessageInput = {
|
|
20
|
+
to: string;
|
|
21
|
+
body: ChatBody;
|
|
22
|
+
recipients?: string[];
|
|
23
|
+
voiceBlob?: Blob;
|
|
24
|
+
};
|
|
25
|
+
export type SenderScopeAccess = {
|
|
26
|
+
version: number;
|
|
27
|
+
allowed: boolean;
|
|
28
|
+
};
|
|
29
|
+
export type DurableSenderOptions = {
|
|
30
|
+
indexedDB?: IDBFactory;
|
|
31
|
+
name?: string;
|
|
32
|
+
access?: () => Promise<SenderScopeAccess>;
|
|
33
|
+
uploadVoice?: (blob: Blob, signal: AbortSignal) => Promise<string>;
|
|
11
34
|
};
|
|
12
35
|
/** Persists the exact wire message before sending; retries preserve its hash. */
|
|
13
36
|
export declare class DurableChatSender {
|
|
14
37
|
private client;
|
|
15
38
|
private onError;
|
|
39
|
+
private options;
|
|
16
40
|
private db;
|
|
17
41
|
private outbox;
|
|
42
|
+
private epochs;
|
|
43
|
+
private localUrls;
|
|
44
|
+
private localViews;
|
|
45
|
+
private uploads;
|
|
46
|
+
private uploadTail;
|
|
47
|
+
private uploadWaiting;
|
|
48
|
+
private confirmationQueue;
|
|
49
|
+
private confirmationTimer?;
|
|
50
|
+
private confirmationRunning?;
|
|
51
|
+
private confirmationSize;
|
|
52
|
+
private version;
|
|
53
|
+
private withdrawn;
|
|
54
|
+
private revoking?;
|
|
55
|
+
private channel?;
|
|
56
|
+
private accepted;
|
|
18
57
|
private scope;
|
|
19
58
|
private identity;
|
|
20
59
|
private values;
|
|
@@ -22,35 +61,45 @@ export declare class DurableChatSender {
|
|
|
22
61
|
private listeners;
|
|
23
62
|
private running;
|
|
24
63
|
private closed;
|
|
64
|
+
private closing;
|
|
65
|
+
private empty;
|
|
25
66
|
private offConnection;
|
|
26
67
|
private ready;
|
|
27
|
-
constructor(client: ChatClient, scope: MessageScope, onError: (error: unknown) => void, options?:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
68
|
+
constructor(client: ChatClient, scope: MessageScope, onError: (error: unknown) => void, options?: DurableSenderOptions);
|
|
69
|
+
/** Read only the selected durable rows, without restoring or delivering an outbox. */
|
|
70
|
+
static readSelected(scope: MessageScope, threadId: string, ids: readonly string[], version: number, options?: Pick<DurableSenderOptions, "indexedDB" | "name">): Promise<OutgoingMessage[]>;
|
|
71
|
+
private readAccess;
|
|
72
|
+
private restore;
|
|
73
|
+
private checkAccess;
|
|
74
|
+
private withEpoch;
|
|
75
|
+
private deleteRows;
|
|
76
|
+
/** Stop disclosure immediately; durable invalidation is coordinated separately. */
|
|
77
|
+
withdraw(): void;
|
|
78
|
+
revoke(targetVersion?: number): Promise<void>;
|
|
31
79
|
initialize(): Promise<void>;
|
|
32
80
|
subscribe(listener: () => void): () => void;
|
|
33
81
|
snapshot(threadId?: string): readonly OutgoingMessage[];
|
|
34
82
|
private emit;
|
|
35
83
|
/** Resolves after the exact wire message is durable, independently of network delivery. */
|
|
36
|
-
enqueue(threadId: string, input:
|
|
37
|
-
to: string;
|
|
38
|
-
body: ChatBody;
|
|
39
|
-
recipients?: string[];
|
|
40
|
-
}): Promise<ChatRuntimeMessage>;
|
|
84
|
+
enqueue(threadId: string, input: SendMessageInput): Promise<ChatRuntimeMessage>;
|
|
41
85
|
/** Compatibility API: resolves only after transport delivery. */
|
|
42
|
-
send(threadId: string, input:
|
|
43
|
-
to: string;
|
|
44
|
-
body: ChatBody;
|
|
45
|
-
recipients?: string[];
|
|
46
|
-
}): Promise<ChatRuntimeMessage>;
|
|
86
|
+
send(threadId: string, input: SendMessageInput): Promise<ChatRuntimeMessage>;
|
|
47
87
|
private persistOutgoing;
|
|
48
88
|
retry(hash: string): Promise<ChatRuntimeMessage>;
|
|
49
89
|
private transmit;
|
|
90
|
+
private releaseUrl;
|
|
91
|
+
private releaseUrls;
|
|
92
|
+
private localView;
|
|
93
|
+
private prepareVoice;
|
|
50
94
|
private save;
|
|
51
95
|
/** Confirm only after the authoritative message has been durably merged.
|
|
52
96
|
* A tombstone prevents another tab's late failure from recreating the outbox. */
|
|
53
97
|
confirm(hashes: string[]): Promise<void>;
|
|
98
|
+
private scheduleConfirmations;
|
|
99
|
+
private drainConfirmations;
|
|
100
|
+
private failConfirmations;
|
|
101
|
+
private flushConfirmations;
|
|
102
|
+
private confirmNow;
|
|
54
103
|
private resume;
|
|
55
104
|
close(): Promise<void>;
|
|
56
105
|
}
|