@calimero-network/mero-js 26.11.0 → 27.0.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/README.md +73 -288
- package/dist/cloud-v2/attestation.d.ts +91 -0
- package/dist/cloud-v2/attestation.js +135 -0
- package/dist/cloud-v2/capabilities.d.ts +30 -0
- package/dist/cloud-v2/capabilities.js +31 -0
- package/dist/cloud-v2/client.d.ts +426 -0
- package/dist/cloud-v2/client.js +646 -0
- package/dist/cloud-v2/errors.d.ts +98 -0
- package/dist/cloud-v2/errors.js +165 -0
- package/dist/cloud-v2/index.d.ts +10 -0
- package/dist/cloud-v2/index.js +5 -0
- package/dist/cloud-v2/passkey.d.ts +27 -0
- package/dist/cloud-v2/passkey.js +73 -0
- package/dist/crypto/internal.d.ts +3 -37
- package/dist/crypto/internal.js +3 -72
- package/dist/index.browser.mjs +4 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +1750 -12772
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -56
- package/dist/index.js +14 -75
- package/dist/index.mjs +1750 -12772
- package/dist/index.mjs.map +4 -4
- package/dist/sealed/index.d.ts +4 -4
- package/dist/sealed/index.js +2 -2
- package/dist/sealed/release.d.ts +21 -76
- package/dist/sealed/release.js +24 -108
- package/dist/sealed/sealed.d.ts +8 -56
- package/dist/sealed/sealed.js +11 -89
- package/dist/sealed/verify.d.ts +7 -13
- package/dist/sealed/verify.js +3 -7
- package/package.json +8 -13
- package/dist/account/account-admin.d.ts +0 -80
- package/dist/account/account-admin.js +0 -346
- package/dist/account/account.d.ts +0 -196
- package/dist/account/account.js +0 -269
- package/dist/account/application-id.d.ts +0 -50
- package/dist/account/application-id.js +0 -83
- package/dist/account/bootstrap-from-invitation.d.ts +0 -110
- package/dist/account/bootstrap-from-invitation.js +0 -80
- package/dist/account/create-context.d.ts +0 -103
- package/dist/account/create-context.js +0 -267
- package/dist/account/govern.d.ts +0 -24
- package/dist/account/govern.js +0 -50
- package/dist/account/index.d.ts +0 -26
- package/dist/account/index.js +0 -15
- package/dist/account/join-as-account.d.ts +0 -23
- package/dist/account/join-as-account.js +0 -34
- package/dist/account/join-with-node.d.ts +0 -115
- package/dist/account/join-with-node.js +0 -161
- package/dist/account/node-admin.d.ts +0 -20
- package/dist/account/node-admin.js +0 -33
- package/dist/account/relay-from-invitation.d.ts +0 -137
- package/dist/account/relay-from-invitation.js +0 -180
- package/dist/account/relay-login-error.d.ts +0 -32
- package/dist/account/relay-login-error.js +0 -34
- package/dist/account/session.d.ts +0 -266
- package/dist/account/session.js +0 -915
- package/dist/account/wordlist.d.ts +0 -16
- package/dist/account/wordlist.js +0 -273
- package/dist/admin-api/admin-client.d.ts +0 -688
- package/dist/admin-api/admin-client.js +0 -1230
- package/dist/admin-api/admin-factory.d.ts +0 -14
- package/dist/admin-api/admin-factory.js +0 -22
- package/dist/admin-api/admin-types.d.ts +0 -2046
- package/dist/admin-api/admin-types.js +0 -12
- package/dist/admin-api/index.d.ts +0 -4
- package/dist/admin-api/index.js +0 -5
- package/dist/admin-api/redeem-invitation.d.ts +0 -101
- package/dist/admin-api/redeem-invitation.js +0 -200
- package/dist/auth/index.d.ts +0 -34
- package/dist/auth/index.js +0 -67
- package/dist/auth-api/auth-client.d.ts +0 -31
- package/dist/auth-api/auth-client.js +0 -117
- package/dist/auth-api/auth-factory.d.ts +0 -7
- package/dist/auth-api/auth-factory.js +0 -41
- package/dist/auth-api/auth-types.d.ts +0 -130
- package/dist/auth-api/auth-types.js +0 -2
- package/dist/auth-api/index.d.ts +0 -3
- package/dist/auth-api/index.js +0 -4
- package/dist/capabilities.d.ts +0 -84
- package/dist/capabilities.js +0 -117
- package/dist/cloud/account-ownership.d.ts +0 -63
- package/dist/cloud/account-ownership.js +0 -87
- package/dist/cloud/cloud-client.d.ts +0 -949
- package/dist/cloud/cloud-client.js +0 -1068
- package/dist/cloud/connect.d.ts +0 -213
- package/dist/cloud/connect.js +0 -260
- package/dist/cloud/enrol-redirect.d.ts +0 -181
- package/dist/cloud/enrol-redirect.js +0 -244
- package/dist/cloud/index.d.ts +0 -12
- package/dist/cloud/index.js +0 -10
- package/dist/cloud/link-redirect.d.ts +0 -95
- package/dist/cloud/link-redirect.js +0 -121
- package/dist/cloud/link-state.d.ts +0 -36
- package/dist/cloud/link-state.js +0 -104
- package/dist/cloud/routing-proof.d.ts +0 -113
- package/dist/cloud/routing-proof.js +0 -96
- package/dist/device-cert/device-cert.d.ts +0 -189
- package/dist/device-cert/device-cert.js +0 -239
- package/dist/device-cert/device-scope.d.ts +0 -58
- package/dist/device-cert/device-scope.js +0 -76
- package/dist/device-cert/index.d.ts +0 -4
- package/dist/device-cert/index.js +0 -3
- package/dist/ephemeral/codec.d.ts +0 -4
- package/dist/ephemeral/codec.js +0 -12
- package/dist/ephemeral/index.d.ts +0 -57
- package/dist/ephemeral/index.js +0 -101
- package/dist/ephemeral/types.d.ts +0 -27
- package/dist/ephemeral/types.js +0 -1
- package/dist/errors/classify.d.ts +0 -65
- package/dist/errors/classify.js +0 -117
- package/dist/errors/index.d.ts +0 -2
- package/dist/errors/index.js +0 -1
- package/dist/events/group.d.ts +0 -95
- package/dist/events/group.js +0 -10
- package/dist/events/index.d.ts +0 -5
- package/dist/events/index.js +0 -2
- package/dist/events/sse.d.ts +0 -127
- package/dist/events/sse.js +0 -469
- package/dist/events/ws.d.ts +0 -57
- package/dist/events/ws.js +0 -212
- package/dist/http-client/api-response.d.ts +0 -15
- package/dist/http-client/api-response.js +0 -1
- package/dist/http-client/http-factory.d.ts +0 -71
- package/dist/http-client/http-factory.js +0 -68
- package/dist/http-client/http-types.d.ts +0 -87
- package/dist/http-client/http-types.js +0 -1
- package/dist/http-client/index.d.ts +0 -7
- package/dist/http-client/index.js +0 -11
- package/dist/http-client/path-segment.d.ts +0 -1
- package/dist/http-client/path-segment.js +0 -8
- package/dist/http-client/retry.d.ts +0 -5
- package/dist/http-client/retry.js +0 -82
- package/dist/http-client/signal-utils.d.ts +0 -2
- package/dist/http-client/signal-utils.js +0 -41
- package/dist/http-client/web-client.d.ts +0 -80
- package/dist/http-client/web-client.js +0 -557
- package/dist/invitation/index.d.ts +0 -2
- package/dist/invitation/index.js +0 -2
- package/dist/invitation/invitation.d.ts +0 -106
- package/dist/invitation/invitation.js +0 -104
- package/dist/login/index.d.ts +0 -4
- package/dist/login/index.js +0 -3
- package/dist/login/login.d.ts +0 -111
- package/dist/login/login.js +0 -110
- package/dist/login/session.d.ts +0 -124
- package/dist/login/session.js +0 -141
- package/dist/mero-js.d.ts +0 -231
- package/dist/mero-js.js +0 -469
- package/dist/namespace-op/index.d.ts +0 -2
- package/dist/namespace-op/index.js +0 -3
- package/dist/namespace-op/namespace-op.d.ts +0 -150
- package/dist/namespace-op/namespace-op.js +0 -191
- package/dist/nodeDiscovery.d.ts +0 -47
- package/dist/nodeDiscovery.js +0 -101
- package/dist/owner-op/index.d.ts +0 -2
- package/dist/owner-op/index.js +0 -3
- package/dist/owner-op/owner-op.d.ts +0 -119
- package/dist/owner-op/owner-op.js +0 -214
- package/dist/presence/relay-presence.d.ts +0 -56
- package/dist/presence/relay-presence.js +0 -107
- package/dist/presence/statement.d.ts +0 -12
- package/dist/presence/statement.js +0 -17
- package/dist/relay/index.d.ts +0 -4
- package/dist/relay/index.js +0 -2
- package/dist/relay/nonce-source.d.ts +0 -72
- package/dist/relay/nonce-source.js +0 -111
- package/dist/relay/relay-client.d.ts +0 -472
- package/dist/relay/relay-client.js +0 -518
- package/dist/relay-attestation/attest-node-key.d.ts +0 -66
- package/dist/relay-attestation/attest-node-key.js +0 -110
- package/dist/relay-attestation/index.d.ts +0 -1
- package/dist/relay-attestation/index.js +0 -1
- package/dist/request/caller-proof.d.ts +0 -43
- package/dist/request/caller-proof.js +0 -36
- package/dist/request/index.d.ts +0 -3
- package/dist/request/index.js +0 -3
- package/dist/request/proof-signer.d.ts +0 -49
- package/dist/request/proof-signer.js +0 -59
- package/dist/request/request-sig.d.ts +0 -99
- package/dist/request/request-sig.js +0 -95
- package/dist/request-proof/authorizer.d.ts +0 -41
- package/dist/request-proof/authorizer.js +0 -60
- package/dist/request-proof/index.d.ts +0 -4
- package/dist/request-proof/index.js +0 -2
- package/dist/request-proof/request-proof.d.ts +0 -53
- package/dist/request-proof/request-proof.js +0 -95
- package/dist/rpc/index.d.ts +0 -64
- package/dist/rpc/index.js +0 -78
- package/dist/signer/index.d.ts +0 -2
- package/dist/signer/index.js +0 -3
- package/dist/signer/signer.d.ts +0 -92
- package/dist/signer/signer.js +0 -147
- package/dist/token-store/index.d.ts +0 -27
- package/dist/token-store/index.js +0 -62
- package/dist/transport/index.d.ts +0 -6
- package/dist/transport/index.js +0 -3
- package/dist/transport/mero-client.d.ts +0 -294
- package/dist/transport/mero-client.js +0 -386
- package/dist/transport/relay-observer.d.ts +0 -152
- package/dist/transport/relay-observer.js +0 -173
- package/dist/transport/relay-transport.d.ts +0 -73
- package/dist/transport/relay-transport.js +0 -94
- package/dist/transport/types.d.ts +0 -100
- package/dist/transport/types.js +0 -32
- package/dist/types/api-response.d.ts +0 -15
- package/dist/types/api-response.js +0 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/warrant/creation-warrant.d.ts +0 -163
- package/dist/warrant/creation-warrant.js +0 -194
- package/dist/warrant/governance-op.d.ts +0 -202
- package/dist/warrant/governance-op.js +0 -355
- package/dist/warrant/governance-warrant.d.ts +0 -124
- package/dist/warrant/governance-warrant.js +0 -164
- package/dist/warrant/index.d.ts +0 -8
- package/dist/warrant/index.js +0 -5
- package/dist/warrant/warrant.d.ts +0 -188
- package/dist/warrant/warrant.js +0 -202
package/dist/events/sse.d.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import type { GroupMembershipEventData, GroupMigrationEventData } from './group.js';
|
|
2
|
-
/** How long a subscription POST may take before it counts as failed. */
|
|
3
|
-
export declare const SUBSCRIPTION_TIMEOUT_MS = 10000;
|
|
4
|
-
export type { GroupMembershipEventData, GroupMigrationEventData };
|
|
5
|
-
export interface SseEventData {
|
|
6
|
-
contextId: string;
|
|
7
|
-
/** The context-event discriminator (core serializes it as a sibling of
|
|
8
|
-
* `data`, e.g. `"AppVersionChanged"`). Undefined for events without a tag. */
|
|
9
|
-
type?: string;
|
|
10
|
-
data: unknown;
|
|
11
|
-
}
|
|
12
|
-
/** Parsed `AppVersionChanged` context event (bundle-version flip; skew #2). */
|
|
13
|
-
export interface AppVersionChangedEvent {
|
|
14
|
-
contextId: string;
|
|
15
|
-
fromVersion?: string;
|
|
16
|
-
toVersion?: string;
|
|
17
|
-
}
|
|
18
|
-
/** Anything the `'event'` stream can deliver: context events plus the
|
|
19
|
-
* group-keyed membership and migration families. */
|
|
20
|
-
type SseEvent = SseEventData | GroupMembershipEventData | GroupMigrationEventData;
|
|
21
|
-
type SseEventHandler = (event: SseEvent) => void;
|
|
22
|
-
type SseConnectHandler = (sessionId: string) => void;
|
|
23
|
-
type SseErrorHandler = (error: Error) => void;
|
|
24
|
-
/**
|
|
25
|
-
* How one request to the SSE endpoints is authenticated.
|
|
26
|
-
*
|
|
27
|
-
* A bearer token is one value reused across requests; a request proof commits to
|
|
28
|
-
* the method, the path and the body, so it must be produced per request. Both
|
|
29
|
-
* reduce to the same thing from this client's point of view — headers — so the
|
|
30
|
-
* stream takes the general form and {@link SseClient} adapts `getAuthToken` to it.
|
|
31
|
-
*/
|
|
32
|
-
export type SseAuthorizer = (request: {
|
|
33
|
-
method: string;
|
|
34
|
-
path: string;
|
|
35
|
-
body?: string;
|
|
36
|
-
}) => Promise<Record<string, string>>;
|
|
37
|
-
export declare class SseClient {
|
|
38
|
-
private baseUrl;
|
|
39
|
-
private authorize;
|
|
40
|
-
private reconnectDelayMs;
|
|
41
|
-
private sessionId;
|
|
42
|
-
private abortController;
|
|
43
|
-
private reconnectTimer;
|
|
44
|
-
private subscribedContextIds;
|
|
45
|
-
private subscribedGroupIds;
|
|
46
|
-
/**
|
|
47
|
-
* The current presence of each subscribed context, as the node's replay and
|
|
48
|
-
* its live deltas left it: author → that author's last `Ephemeral` data and
|
|
49
|
-
* when it arrived. The node replays a context's presence once, when the
|
|
50
|
-
* context is first subscribed on this connection, so a presence listener
|
|
51
|
-
* that comes later would never see it; {@link presenceSeed} gives it this.
|
|
52
|
-
*/
|
|
53
|
-
private presence;
|
|
54
|
-
private closed;
|
|
55
|
-
private listeners;
|
|
56
|
-
private fetchImpl;
|
|
57
|
-
constructor(opts: {
|
|
58
|
-
baseUrl: string;
|
|
59
|
-
/** A bearer token provider. The historical form, and still the default. */
|
|
60
|
-
getAuthToken?: () => Promise<string>;
|
|
61
|
-
/**
|
|
62
|
-
* Per-request authorization, for a credential that commits to the request —
|
|
63
|
-
* a device cert signing a request proof. Takes precedence over
|
|
64
|
-
* `getAuthToken`; one of the two is required.
|
|
65
|
-
*/
|
|
66
|
-
authorize?: SseAuthorizer;
|
|
67
|
-
reconnectDelayMs?: number;
|
|
68
|
-
fetch?: typeof fetch;
|
|
69
|
-
});
|
|
70
|
-
on(event: 'connect', handler: SseConnectHandler): void;
|
|
71
|
-
on(event: 'event', handler: SseEventHandler): void;
|
|
72
|
-
on(event: 'error', handler: SseErrorHandler): void;
|
|
73
|
-
off(event: 'connect', handler: SseConnectHandler): void;
|
|
74
|
-
off(event: 'event', handler: SseEventHandler): void;
|
|
75
|
-
off(event: 'error', handler: SseErrorHandler): void;
|
|
76
|
-
/**
|
|
77
|
-
* Typed convenience over the generic `'event'` stream: invokes `handler` only
|
|
78
|
-
* for `AppVersionChanged` context events, with the payload already parsed.
|
|
79
|
-
* Returns an unsubscribe closure (so callers need not retain the listener).
|
|
80
|
-
*/
|
|
81
|
-
onAppVersionChanged(handler: (e: AppVersionChangedEvent) => void): () => void;
|
|
82
|
-
/**
|
|
83
|
-
* Typed convenience over the generic `'event'` stream: invokes `handler` only
|
|
84
|
-
* for group-migration events, narrowed on `type` so `data` is the matching
|
|
85
|
-
* payload. Returns an unsubscribe closure.
|
|
86
|
-
*
|
|
87
|
-
* A non-admin subscriber sees `MigrationStarted`, `MigrationProgress` and
|
|
88
|
-
* `MigrationCompleted` but never `CascadeProgress`, which core delivers to
|
|
89
|
-
* namespace admins only.
|
|
90
|
-
*/
|
|
91
|
-
onMigrationEvent(handler: (e: GroupMigrationEventData) => void): () => void;
|
|
92
|
-
private emit;
|
|
93
|
-
connect(): Promise<void>;
|
|
94
|
-
/**
|
|
95
|
-
* Whether `controller`'s connection is no longer the one this client holds:
|
|
96
|
-
* closed, or replaced by a later connect. Its abort lands asynchronously,
|
|
97
|
-
* after a `close()` + `connect()` has already cleared `closed`, so `closed`
|
|
98
|
-
* alone cannot tell; reporting it, and reconnecting for it, would abort the
|
|
99
|
-
* live connection, whose own abort would do the same, without end.
|
|
100
|
-
*/
|
|
101
|
-
private superseded;
|
|
102
|
-
private readStream;
|
|
103
|
-
private handleMessage;
|
|
104
|
-
subscribe(idsOrOpts: string[] | {
|
|
105
|
-
contextIds?: string[];
|
|
106
|
-
groupIds?: string[];
|
|
107
|
-
}): Promise<void>;
|
|
108
|
-
/** Whether this connection holds `contextId`'s events. */
|
|
109
|
-
isSubscribed(contextId: string): boolean;
|
|
110
|
-
/**
|
|
111
|
-
* The presence currently known for `contextId`, as `Ephemeral` event data,
|
|
112
|
-
* each entry's `ageMs` advanced by how long ago it arrived. Empty for a
|
|
113
|
-
* context this connection does not hold.
|
|
114
|
-
*/
|
|
115
|
-
presenceSeed(contextId: string): Array<Record<string, unknown>>;
|
|
116
|
-
private notePresence;
|
|
117
|
-
unsubscribe(idsOrOpts: string[] | {
|
|
118
|
-
contextIds?: string[];
|
|
119
|
-
groupIds?: string[];
|
|
120
|
-
}): Promise<void>;
|
|
121
|
-
private sendSubscription;
|
|
122
|
-
/** Sends one subscription change and returns why it failed, or `null`. */
|
|
123
|
-
private postSubscription;
|
|
124
|
-
private forceReconnect;
|
|
125
|
-
private scheduleReconnect;
|
|
126
|
-
close(): void;
|
|
127
|
-
}
|
package/dist/events/sse.js
DELETED
|
@@ -1,469 +0,0 @@
|
|
|
1
|
-
import { isGroupMigrationEvent } from './group.js';
|
|
2
|
-
import { AuthRevokedError, HTTPError } from '../http-client/index.js';
|
|
3
|
-
import { carriesProof, refuseProofRedirect } from '../http-client/web-client.js';
|
|
4
|
-
import { signedPath } from '../request-proof/authorizer.js';
|
|
5
|
-
/**
|
|
6
|
-
* `x-auth-error` reasons that mean the whole token family is gone, mirroring
|
|
7
|
-
* the request path in `web-client.ts`. Anything else — `token_expired` above
|
|
8
|
-
* all — is recoverable by refreshing and reconnecting.
|
|
9
|
-
*/
|
|
10
|
-
const TERMINAL_AUTH_ERRORS = new Set(['token_reuse', 'token_revoked']);
|
|
11
|
-
/**
|
|
12
|
-
* Statuses a terminal `x-auth-error` can arrive on.
|
|
13
|
-
*
|
|
14
|
-
* Measured against merod 0.11.0-rc.41: a revoked token family answers
|
|
15
|
-
* `GET /sse` and `POST /sse/subscription` with **403**, `x-auth-error:
|
|
16
|
-
* token_revoked` and `content-length: 0` — `token_expired` is the 401.
|
|
17
|
-
* Keying the check on 401 alone made the branch below unreachable for the one
|
|
18
|
-
* reason a stream actually sees, so a dead token family surfaced as a bare
|
|
19
|
-
* `HTTPError(403)` and reconnected on a timer forever.
|
|
20
|
-
*/
|
|
21
|
-
const TERMINAL_AUTH_STATUSES = new Set([401, 403]);
|
|
22
|
-
/** How long a subscription POST may take before it counts as failed. */
|
|
23
|
-
export const SUBSCRIPTION_TIMEOUT_MS = 10000;
|
|
24
|
-
/**
|
|
25
|
-
* Whether a failed response means the whole token family is gone, so the caller
|
|
26
|
-
* must re-authenticate and the client must stop reconnecting.
|
|
27
|
-
*
|
|
28
|
-
* The body carries nothing on this path (`content-length: 0`), so the header is
|
|
29
|
-
* the only evidence there is.
|
|
30
|
-
*/
|
|
31
|
-
function isTerminalAuthFailure(response) {
|
|
32
|
-
const authError = response.headers.get('x-auth-error');
|
|
33
|
-
if (!authError)
|
|
34
|
-
return null;
|
|
35
|
-
if (!TERMINAL_AUTH_STATUSES.has(response.status))
|
|
36
|
-
return null;
|
|
37
|
-
return TERMINAL_AUTH_ERRORS.has(authError) ? authError : null;
|
|
38
|
-
}
|
|
39
|
-
export class SseClient {
|
|
40
|
-
constructor(opts) {
|
|
41
|
-
this.sessionId = null;
|
|
42
|
-
this.abortController = null;
|
|
43
|
-
this.reconnectTimer = null;
|
|
44
|
-
this.subscribedContextIds = new Set();
|
|
45
|
-
this.subscribedGroupIds = new Set();
|
|
46
|
-
/**
|
|
47
|
-
* The current presence of each subscribed context, as the node's replay and
|
|
48
|
-
* its live deltas left it: author → that author's last `Ephemeral` data and
|
|
49
|
-
* when it arrived. The node replays a context's presence once, when the
|
|
50
|
-
* context is first subscribed on this connection, so a presence listener
|
|
51
|
-
* that comes later would never see it; {@link presenceSeed} gives it this.
|
|
52
|
-
*/
|
|
53
|
-
this.presence = new Map();
|
|
54
|
-
this.closed = false;
|
|
55
|
-
this.listeners = { connect: [], event: [], error: [] };
|
|
56
|
-
this.baseUrl = opts.baseUrl.replace(/\/+$/, '');
|
|
57
|
-
if (opts.authorize) {
|
|
58
|
-
this.authorize = opts.authorize;
|
|
59
|
-
}
|
|
60
|
-
else if (opts.getAuthToken) {
|
|
61
|
-
const getAuthToken = opts.getAuthToken;
|
|
62
|
-
this.authorize = async () => ({ Authorization: `Bearer ${await getAuthToken()}` });
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
throw new Error('SseClient needs either `getAuthToken` or `authorize`');
|
|
66
|
-
}
|
|
67
|
-
this.reconnectDelayMs = opts.reconnectDelayMs ?? 3000;
|
|
68
|
-
// globalThis.fetch must be called as a method on globalThis, not through a
|
|
69
|
-
// variable, or browsers throw "Illegal invocation".
|
|
70
|
-
this.fetchImpl = (url, init) => opts.fetch ? opts.fetch(url, init) : globalThis.fetch(url, init);
|
|
71
|
-
}
|
|
72
|
-
on(event, handler) {
|
|
73
|
-
const key = event;
|
|
74
|
-
if (key in this.listeners) {
|
|
75
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
76
|
-
const arr = this.listeners[key];
|
|
77
|
-
if (!arr.includes(handler))
|
|
78
|
-
arr.push(handler);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
off(event, handler) {
|
|
82
|
-
const key = event;
|
|
83
|
-
if (key in this.listeners) {
|
|
84
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
-
const arr = this.listeners[key];
|
|
86
|
-
const idx = arr.indexOf(handler);
|
|
87
|
-
if (idx !== -1)
|
|
88
|
-
arr.splice(idx, 1);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Typed convenience over the generic `'event'` stream: invokes `handler` only
|
|
93
|
-
* for `AppVersionChanged` context events, with the payload already parsed.
|
|
94
|
-
* Returns an unsubscribe closure (so callers need not retain the listener).
|
|
95
|
-
*/
|
|
96
|
-
onAppVersionChanged(handler) {
|
|
97
|
-
const listener = (ev) => {
|
|
98
|
-
// Core flattens the tagged payload: the event carries `type` as a sibling
|
|
99
|
-
// of `data`, and `data` is the `{ fromVersion?, toVersion? }` payload.
|
|
100
|
-
if (ev.type !== 'AppVersionChanged')
|
|
101
|
-
return;
|
|
102
|
-
const d = ev.data;
|
|
103
|
-
handler({ contextId: ev.contextId, fromVersion: d?.fromVersion, toVersion: d?.toVersion });
|
|
104
|
-
};
|
|
105
|
-
this.on('event', listener);
|
|
106
|
-
return () => this.off('event', listener);
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Typed convenience over the generic `'event'` stream: invokes `handler` only
|
|
110
|
-
* for group-migration events, narrowed on `type` so `data` is the matching
|
|
111
|
-
* payload. Returns an unsubscribe closure.
|
|
112
|
-
*
|
|
113
|
-
* A non-admin subscriber sees `MigrationStarted`, `MigrationProgress` and
|
|
114
|
-
* `MigrationCompleted` but never `CascadeProgress`, which core delivers to
|
|
115
|
-
* namespace admins only.
|
|
116
|
-
*/
|
|
117
|
-
onMigrationEvent(handler) {
|
|
118
|
-
const listener = (ev) => {
|
|
119
|
-
if (isGroupMigrationEvent(ev))
|
|
120
|
-
handler(ev);
|
|
121
|
-
};
|
|
122
|
-
this.on('event', listener);
|
|
123
|
-
return () => this.off('event', listener);
|
|
124
|
-
}
|
|
125
|
-
emit(event, arg) {
|
|
126
|
-
const key = event;
|
|
127
|
-
if (key in this.listeners) {
|
|
128
|
-
for (const handler of this.listeners[key]) {
|
|
129
|
-
try {
|
|
130
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
131
|
-
handler(arg);
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
// Swallow handler errors
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
async connect() {
|
|
140
|
-
// Already connected — don't reconnect
|
|
141
|
-
if (this.abortController && !this.closed) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
this.closed = false;
|
|
145
|
-
const controller = new AbortController();
|
|
146
|
-
this.abortController = controller;
|
|
147
|
-
try {
|
|
148
|
-
const url = `${this.baseUrl}/sse`;
|
|
149
|
-
const headers = {
|
|
150
|
-
...(await this.authorize({ method: 'GET', path: signedPath(url) })),
|
|
151
|
-
'Accept': 'text/event-stream',
|
|
152
|
-
};
|
|
153
|
-
const proofAttached = carriesProof(headers);
|
|
154
|
-
const response = await this.fetchImpl(url, {
|
|
155
|
-
headers,
|
|
156
|
-
signal: controller.signal,
|
|
157
|
-
...(proofAttached && { redirect: 'manual' }),
|
|
158
|
-
});
|
|
159
|
-
if (proofAttached)
|
|
160
|
-
refuseProofRedirect(response, url);
|
|
161
|
-
if (!response.ok) {
|
|
162
|
-
// Throw the same typed errors the request path throws. A bare
|
|
163
|
-
// `Error("SSE connection failed: 401")` forced callers to string-match
|
|
164
|
-
// the status and guess what it meant, and the honest guess — treat any
|
|
165
|
-
// 401 as fatal — logs the user out on a routine reconnect after the
|
|
166
|
-
// access token ages out, discarding a refresh token that was still
|
|
167
|
-
// good. The distinction is in the header; surface it.
|
|
168
|
-
const terminal = isTerminalAuthFailure(response);
|
|
169
|
-
const bodyText = await response.text().catch(() => undefined);
|
|
170
|
-
if (terminal) {
|
|
171
|
-
throw new AuthRevokedError(terminal, response.status, response.statusText, url, response.headers, bodyText);
|
|
172
|
-
}
|
|
173
|
-
throw new HTTPError(response.status, response.statusText, url, response.headers, bodyText);
|
|
174
|
-
}
|
|
175
|
-
if (!response.body) {
|
|
176
|
-
throw new Error('SSE response has no body');
|
|
177
|
-
}
|
|
178
|
-
this.readStream(response.body, controller).catch((err) => {
|
|
179
|
-
if (this.superseded(controller))
|
|
180
|
-
return;
|
|
181
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
182
|
-
this.emit('error', error);
|
|
183
|
-
this.scheduleReconnect();
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
catch (err) {
|
|
187
|
-
if (this.superseded(controller))
|
|
188
|
-
return;
|
|
189
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
190
|
-
this.emit('error', error);
|
|
191
|
-
// Reconnecting against a revoked token family is a timer that never
|
|
192
|
-
// succeeds: every attempt re-sends the same dead credential. The caller
|
|
193
|
-
// has to re-authenticate, and an AuthRevokedError is how it is told.
|
|
194
|
-
if (error instanceof AuthRevokedError)
|
|
195
|
-
return;
|
|
196
|
-
this.scheduleReconnect();
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Whether `controller`'s connection is no longer the one this client holds:
|
|
201
|
-
* closed, or replaced by a later connect. Its abort lands asynchronously,
|
|
202
|
-
* after a `close()` + `connect()` has already cleared `closed`, so `closed`
|
|
203
|
-
* alone cannot tell; reporting it, and reconnecting for it, would abort the
|
|
204
|
-
* live connection, whose own abort would do the same, without end.
|
|
205
|
-
*/
|
|
206
|
-
superseded(controller) {
|
|
207
|
-
return this.closed || controller.signal.aborted || this.abortController !== controller;
|
|
208
|
-
}
|
|
209
|
-
async readStream(body, controller) {
|
|
210
|
-
const reader = body.getReader();
|
|
211
|
-
const decoder = new TextDecoder();
|
|
212
|
-
let buffer = '';
|
|
213
|
-
try {
|
|
214
|
-
for (;;) {
|
|
215
|
-
const { done, value } = await reader.read();
|
|
216
|
-
if (done)
|
|
217
|
-
break;
|
|
218
|
-
buffer += decoder.decode(value, { stream: true });
|
|
219
|
-
const lines = buffer.split('\n');
|
|
220
|
-
// Keep the last partial line in the buffer
|
|
221
|
-
buffer = lines.pop() ?? '';
|
|
222
|
-
for (const line of lines) {
|
|
223
|
-
if (line.startsWith('data:')) {
|
|
224
|
-
const jsonStr = line.slice(5).trim();
|
|
225
|
-
if (jsonStr) {
|
|
226
|
-
this.handleMessage(jsonStr);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
// Flush remaining bytes from the decoder
|
|
232
|
-
buffer += decoder.decode();
|
|
233
|
-
if (buffer.startsWith('data:')) {
|
|
234
|
-
const jsonStr = buffer.slice(5).trim();
|
|
235
|
-
if (jsonStr) {
|
|
236
|
-
this.handleMessage(jsonStr);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
catch (err) {
|
|
241
|
-
if (this.superseded(controller))
|
|
242
|
-
return;
|
|
243
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
244
|
-
this.emit('error', error);
|
|
245
|
-
}
|
|
246
|
-
// Stream ended, reconnect
|
|
247
|
-
if (!this.superseded(controller)) {
|
|
248
|
-
this.scheduleReconnect();
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
handleMessage(jsonStr) {
|
|
252
|
-
try {
|
|
253
|
-
const msg = JSON.parse(jsonStr);
|
|
254
|
-
// Connection message
|
|
255
|
-
if (msg.type === 'connect' && msg.session_id) {
|
|
256
|
-
const sessionId = msg.session_id;
|
|
257
|
-
this.sessionId = sessionId;
|
|
258
|
-
// A new connection gets a new replay when it re-subscribes; what the
|
|
259
|
-
// old one said may name authors who have since gone.
|
|
260
|
-
this.presence.clear();
|
|
261
|
-
if (this.subscribedContextIds.size === 0 && this.subscribedGroupIds.size === 0) {
|
|
262
|
-
this.emit('connect', sessionId);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
// A new session starts subscribed to nothing, so announce it once the re-subscribe
|
|
266
|
-
// lands; a failed one still announces, then reports the error, as consumers saw before.
|
|
267
|
-
void this.postSubscription('subscribe', {
|
|
268
|
-
contextIds: [...this.subscribedContextIds],
|
|
269
|
-
groupIds: [...this.subscribedGroupIds],
|
|
270
|
-
}).then((failure) => {
|
|
271
|
-
if (this.closed || this.sessionId !== sessionId)
|
|
272
|
-
return;
|
|
273
|
-
this.emit('connect', sessionId);
|
|
274
|
-
if (failure)
|
|
275
|
-
this.emit('error', failure);
|
|
276
|
-
});
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
if (!msg.result)
|
|
280
|
-
return;
|
|
281
|
-
let eventData = msg.result.data;
|
|
282
|
-
// Decode byte-array data if needed
|
|
283
|
-
if (Array.isArray(eventData)) {
|
|
284
|
-
try {
|
|
285
|
-
const bytes = new Uint8Array(eventData);
|
|
286
|
-
const text = new TextDecoder().decode(bytes);
|
|
287
|
-
eventData = JSON.parse(text);
|
|
288
|
-
}
|
|
289
|
-
catch {
|
|
290
|
-
// Keep raw data
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
// Context event message
|
|
294
|
-
if (msg.result.contextId) {
|
|
295
|
-
if (msg.result.type === 'Ephemeral')
|
|
296
|
-
this.notePresence(msg.result.contextId, eventData);
|
|
297
|
-
this.emit('event', {
|
|
298
|
-
contextId: msg.result.contextId,
|
|
299
|
-
// Forward the event tag (sibling of `data` in core's flattened
|
|
300
|
-
// payload) so typed helpers like `onAppVersionChanged` can discriminate.
|
|
301
|
-
type: msg.result.type,
|
|
302
|
-
data: eventData,
|
|
303
|
-
});
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
// Group-keyed event message (untagged NodeEvent's membership and
|
|
307
|
-
// migration variants; the `type` tag tells them apart).
|
|
308
|
-
if (msg.result.groupId) {
|
|
309
|
-
this.emit('event', {
|
|
310
|
-
groupId: msg.result.groupId,
|
|
311
|
-
type: msg.result.type,
|
|
312
|
-
data: eventData,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
catch {
|
|
317
|
-
// Invalid JSON, ignore
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
async subscribe(idsOrOpts) {
|
|
321
|
-
const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;
|
|
322
|
-
const newContextIds = (opts.contextIds ?? []).filter(id => !this.subscribedContextIds.has(id));
|
|
323
|
-
const newGroupIds = (opts.groupIds ?? []).filter(id => !this.subscribedGroupIds.has(id));
|
|
324
|
-
for (const id of opts.contextIds ?? []) {
|
|
325
|
-
this.subscribedContextIds.add(id);
|
|
326
|
-
}
|
|
327
|
-
for (const id of opts.groupIds ?? []) {
|
|
328
|
-
this.subscribedGroupIds.add(id);
|
|
329
|
-
}
|
|
330
|
-
if ((newContextIds.length > 0 || newGroupIds.length > 0) && this.sessionId) {
|
|
331
|
-
await this.sendSubscription('subscribe', { contextIds: newContextIds, groupIds: newGroupIds });
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
/** Whether this connection holds `contextId`'s events. */
|
|
335
|
-
isSubscribed(contextId) {
|
|
336
|
-
return this.subscribedContextIds.has(contextId);
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* The presence currently known for `contextId`, as `Ephemeral` event data,
|
|
340
|
-
* each entry's `ageMs` advanced by how long ago it arrived. Empty for a
|
|
341
|
-
* context this connection does not hold.
|
|
342
|
-
*/
|
|
343
|
-
presenceSeed(contextId) {
|
|
344
|
-
const now = Date.now();
|
|
345
|
-
return [...(this.presence.get(contextId)?.values() ?? [])].map(({ data, at }) => ({
|
|
346
|
-
...data,
|
|
347
|
-
ageMs: (typeof data.ageMs === 'number' ? data.ageMs : 0) + (now - at),
|
|
348
|
-
}));
|
|
349
|
-
}
|
|
350
|
-
notePresence(contextId, data) {
|
|
351
|
-
if (!this.subscribedContextIds.has(contextId))
|
|
352
|
-
return;
|
|
353
|
-
const d = data;
|
|
354
|
-
if (!d || typeof d.author !== 'string')
|
|
355
|
-
return;
|
|
356
|
-
let byAuthor = this.presence.get(contextId);
|
|
357
|
-
if (!byAuthor)
|
|
358
|
-
this.presence.set(contextId, (byAuthor = new Map()));
|
|
359
|
-
if (d.removed)
|
|
360
|
-
byAuthor.delete(d.author);
|
|
361
|
-
else
|
|
362
|
-
byAuthor.set(d.author, { data: d, at: Date.now() });
|
|
363
|
-
}
|
|
364
|
-
async unsubscribe(idsOrOpts) {
|
|
365
|
-
const opts = Array.isArray(idsOrOpts) ? { contextIds: idsOrOpts } : idsOrOpts;
|
|
366
|
-
const hadContextIds = (opts.contextIds ?? []).filter(id => this.subscribedContextIds.has(id));
|
|
367
|
-
const hadGroupIds = (opts.groupIds ?? []).filter(id => this.subscribedGroupIds.has(id));
|
|
368
|
-
for (const id of opts.contextIds ?? []) {
|
|
369
|
-
this.subscribedContextIds.delete(id);
|
|
370
|
-
this.presence.delete(id);
|
|
371
|
-
}
|
|
372
|
-
for (const id of opts.groupIds ?? []) {
|
|
373
|
-
this.subscribedGroupIds.delete(id);
|
|
374
|
-
}
|
|
375
|
-
if ((hadContextIds.length > 0 || hadGroupIds.length > 0) && this.sessionId) {
|
|
376
|
-
await this.sendSubscription('unsubscribe', { contextIds: hadContextIds, groupIds: hadGroupIds });
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
async sendSubscription(method, ids) {
|
|
380
|
-
const failure = await this.postSubscription(method, ids);
|
|
381
|
-
if (failure)
|
|
382
|
-
this.emit('error', failure);
|
|
383
|
-
}
|
|
384
|
-
/** Sends one subscription change and returns why it failed, or `null`. */
|
|
385
|
-
async postSubscription(method, ids) {
|
|
386
|
-
const timeout = new AbortController();
|
|
387
|
-
const timer = setTimeout(() => timeout.abort(new DOMException(`SSE ${method} timed out`, 'TimeoutError')), SUBSCRIPTION_TIMEOUT_MS);
|
|
388
|
-
try {
|
|
389
|
-
const params = {};
|
|
390
|
-
if (ids.contextIds && ids.contextIds.length > 0)
|
|
391
|
-
params.contextIds = ids.contextIds;
|
|
392
|
-
if (ids.groupIds && ids.groupIds.length > 0)
|
|
393
|
-
params.groupIds = ids.groupIds;
|
|
394
|
-
const url = `${this.baseUrl}/sse/subscription`;
|
|
395
|
-
// Serialized once and both signed and sent, because a proof commits to the
|
|
396
|
-
// body: re-stringifying for the signature risks two byte sequences that
|
|
397
|
-
// differ in key order, which fails as a refused credential rather than as
|
|
398
|
-
// anything that names the real cause.
|
|
399
|
-
const body = JSON.stringify({ id: this.sessionId, method, params });
|
|
400
|
-
const headers = {
|
|
401
|
-
...(await this.authorize({ method: 'POST', path: signedPath(url), body })),
|
|
402
|
-
'Content-Type': 'application/json',
|
|
403
|
-
};
|
|
404
|
-
const proofAttached = carriesProof(headers);
|
|
405
|
-
const response = await this.fetchImpl(url, {
|
|
406
|
-
method: 'POST',
|
|
407
|
-
headers,
|
|
408
|
-
body,
|
|
409
|
-
signal: timeout.signal,
|
|
410
|
-
...(proofAttached && { redirect: 'manual' }),
|
|
411
|
-
});
|
|
412
|
-
if (proofAttached)
|
|
413
|
-
refuseProofRedirect(response, url);
|
|
414
|
-
if (!response.ok) {
|
|
415
|
-
// The subscribe path used to report only the status. On the one failure
|
|
416
|
-
// that matters it is the whole of what the caller got: a revoked family
|
|
417
|
-
// answers 403 with an empty body, so a bare `SSE subscribe failed: 403`
|
|
418
|
-
// discarded the only evidence of what went wrong — and a 403 here reads
|
|
419
|
-
// identically to the unrelated "not the session owner" refusal, which
|
|
420
|
-
// does carry a body.
|
|
421
|
-
const terminal = isTerminalAuthFailure(response);
|
|
422
|
-
const bodyText = await response.text().catch(() => undefined);
|
|
423
|
-
return terminal
|
|
424
|
-
? new AuthRevokedError(terminal, response.status, response.statusText, `${this.baseUrl}/sse/subscription`, response.headers, bodyText)
|
|
425
|
-
: new HTTPError(response.status, response.statusText, `${this.baseUrl}/sse/subscription`, response.headers, bodyText);
|
|
426
|
-
}
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
catch (err) {
|
|
430
|
-
return err instanceof Error ? err : new Error(`SSE ${method} failed`);
|
|
431
|
-
}
|
|
432
|
-
finally {
|
|
433
|
-
clearTimeout(timer);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
forceReconnect() {
|
|
437
|
-
if (this.abortController) {
|
|
438
|
-
this.abortController.abort();
|
|
439
|
-
this.abortController = null;
|
|
440
|
-
}
|
|
441
|
-
this.sessionId = null;
|
|
442
|
-
this.connect();
|
|
443
|
-
}
|
|
444
|
-
scheduleReconnect() {
|
|
445
|
-
if (this.closed)
|
|
446
|
-
return;
|
|
447
|
-
if (this.reconnectTimer) {
|
|
448
|
-
clearTimeout(this.reconnectTimer);
|
|
449
|
-
}
|
|
450
|
-
this.reconnectTimer = setTimeout(() => {
|
|
451
|
-
this.reconnectTimer = null;
|
|
452
|
-
this.forceReconnect();
|
|
453
|
-
}, this.reconnectDelayMs);
|
|
454
|
-
}
|
|
455
|
-
close() {
|
|
456
|
-
this.closed = true;
|
|
457
|
-
if (this.abortController) {
|
|
458
|
-
this.abortController.abort();
|
|
459
|
-
this.abortController = null;
|
|
460
|
-
}
|
|
461
|
-
if (this.reconnectTimer) {
|
|
462
|
-
clearTimeout(this.reconnectTimer);
|
|
463
|
-
this.reconnectTimer = null;
|
|
464
|
-
}
|
|
465
|
-
this.sessionId = null;
|
|
466
|
-
this.subscribedContextIds.clear();
|
|
467
|
-
this.subscribedGroupIds.clear();
|
|
468
|
-
}
|
|
469
|
-
}
|
package/dist/events/ws.d.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { GroupMembershipEventData, GroupMigrationEventData } from './group.js';
|
|
2
|
-
export type { GroupMembershipEventData, GroupMigrationEventData };
|
|
3
|
-
export interface WsEventData {
|
|
4
|
-
contextId: string;
|
|
5
|
-
/** The context-event discriminator (core serializes it as a sibling of
|
|
6
|
-
* `data`, e.g. `"AppVersionChanged"`). Undefined for events without a tag. */
|
|
7
|
-
type?: string;
|
|
8
|
-
data: unknown;
|
|
9
|
-
}
|
|
10
|
-
/** Anything the `'event'` stream can deliver: context events plus the
|
|
11
|
-
* group-keyed membership and migration families. */
|
|
12
|
-
type WsEvent = WsEventData | GroupMembershipEventData | GroupMigrationEventData;
|
|
13
|
-
type WsEventHandler = (event: WsEvent) => void;
|
|
14
|
-
type WsConnectHandler = () => void;
|
|
15
|
-
type WsErrorHandler = (error: Error) => void;
|
|
16
|
-
/**
|
|
17
|
-
* @experimental WebSocket event client. The SSE client (`SseClient`) is the
|
|
18
|
-
* recommended transport for production — use `MeroJs.events` instead of `MeroJs.ws`.
|
|
19
|
-
*/
|
|
20
|
-
export declare class WsClient {
|
|
21
|
-
private baseUrl;
|
|
22
|
-
private getAuthToken;
|
|
23
|
-
private ws;
|
|
24
|
-
private closed;
|
|
25
|
-
private reconnectAttempt;
|
|
26
|
-
private reconnectTimer;
|
|
27
|
-
private subscribedContextIds;
|
|
28
|
-
private subscribedGroupIds;
|
|
29
|
-
private listeners;
|
|
30
|
-
private static readonly MAX_BACKOFF_MS;
|
|
31
|
-
constructor(opts: {
|
|
32
|
-
baseUrl: string;
|
|
33
|
-
getAuthToken: () => Promise<string>;
|
|
34
|
-
});
|
|
35
|
-
on(event: 'connect', handler: WsConnectHandler): void;
|
|
36
|
-
on(event: 'event', handler: WsEventHandler): void;
|
|
37
|
-
on(event: 'error', handler: WsErrorHandler): void;
|
|
38
|
-
off(event: 'connect', handler: WsConnectHandler): void;
|
|
39
|
-
off(event: 'event', handler: WsEventHandler): void;
|
|
40
|
-
off(event: 'error', handler: WsErrorHandler): void;
|
|
41
|
-
private emit;
|
|
42
|
-
connect(): Promise<void>;
|
|
43
|
-
private handleMessage;
|
|
44
|
-
subscribe(idsOrOpts: string[] | {
|
|
45
|
-
contextIds?: string[];
|
|
46
|
-
groupIds?: string[];
|
|
47
|
-
}): void;
|
|
48
|
-
unsubscribe(idsOrOpts: string[] | {
|
|
49
|
-
contextIds?: string[];
|
|
50
|
-
groupIds?: string[];
|
|
51
|
-
}): void;
|
|
52
|
-
private resubscribeAfterReconnect;
|
|
53
|
-
private buildSubscriptionParams;
|
|
54
|
-
private sendMessage;
|
|
55
|
-
private scheduleReconnect;
|
|
56
|
-
close(): void;
|
|
57
|
-
}
|