@crowdedkingdomstudios/crowdyjs 2.1.2 → 4.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.
Files changed (72) hide show
  1. package/MIGRATION.md +26 -0
  2. package/README.md +155 -749
  3. package/dist/auth-state.d.ts +6 -16
  4. package/dist/auth-state.d.ts.map +1 -1
  5. package/dist/auth-state.js +9 -26
  6. package/dist/client.d.ts +14 -5
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +24 -17
  9. package/dist/crowdy-client.d.ts +62 -16
  10. package/dist/crowdy-client.d.ts.map +1 -1
  11. package/dist/crowdy-client.js +70 -28
  12. package/dist/domains/apps.d.ts +48 -20
  13. package/dist/domains/apps.d.ts.map +1 -1
  14. package/dist/domains/apps.js +58 -35
  15. package/dist/domains/auth.d.ts +33 -22
  16. package/dist/domains/auth.d.ts.map +1 -1
  17. package/dist/domains/auth.js +51 -33
  18. package/dist/domains/serverStatus.d.ts +2 -1
  19. package/dist/domains/serverStatus.d.ts.map +1 -1
  20. package/dist/domains/serverStatus.js +5 -1
  21. package/dist/domains/udp.d.ts +28 -3
  22. package/dist/domains/udp.d.ts.map +1 -1
  23. package/dist/domains/udp.js +52 -1
  24. package/dist/domains/users.d.ts +19 -16
  25. package/dist/domains/users.d.ts.map +1 -1
  26. package/dist/domains/users.js +21 -39
  27. package/dist/errors.d.ts +42 -0
  28. package/dist/errors.d.ts.map +1 -0
  29. package/dist/errors.js +42 -0
  30. package/dist/generated/graphql.d.ts +1473 -11
  31. package/dist/generated/graphql.d.ts.map +1 -1
  32. package/dist/generated/graphql.js +17 -8
  33. package/dist/index.d.ts +37 -18
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +37 -20
  36. package/dist/logger.d.ts +8 -0
  37. package/dist/logger.d.ts.map +1 -0
  38. package/dist/logger.js +1 -0
  39. package/dist/realtime.d.ts +89 -0
  40. package/dist/realtime.d.ts.map +1 -0
  41. package/dist/realtime.js +273 -0
  42. package/dist/session.d.ts +27 -0
  43. package/dist/session.d.ts.map +1 -0
  44. package/dist/session.js +61 -0
  45. package/dist/subscriptions.d.ts +1 -48
  46. package/dist/subscriptions.d.ts.map +1 -1
  47. package/dist/subscriptions.js +1 -192
  48. package/dist/types.d.ts +2 -31
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js +5 -33
  51. package/dist/utils.d.ts +12 -0
  52. package/dist/utils.d.ts.map +1 -0
  53. package/dist/utils.js +50 -0
  54. package/dist/world.d.ts +44 -0
  55. package/dist/world.d.ts.map +1 -0
  56. package/dist/world.js +105 -0
  57. package/package.json +12 -3
  58. package/dist/domains/appAccess.d.ts +0 -23
  59. package/dist/domains/appAccess.d.ts.map +0 -1
  60. package/dist/domains/appAccess.js +0 -42
  61. package/dist/domains/billing.d.ts +0 -17
  62. package/dist/domains/billing.d.ts.map +0 -1
  63. package/dist/domains/billing.js +0 -31
  64. package/dist/domains/organizations.d.ts +0 -33
  65. package/dist/domains/organizations.d.ts.map +0 -1
  66. package/dist/domains/organizations.js +0 -90
  67. package/dist/domains/payments.d.ts +0 -20
  68. package/dist/domains/payments.d.ts.map +0 -1
  69. package/dist/domains/payments.js +0 -28
  70. package/dist/domains/quotas.d.ts +0 -20
  71. package/dist/domains/quotas.d.ts.map +0 -1
  72. package/dist/domains/quotas.js +0 -34
@@ -0,0 +1,89 @@
1
+ import type { SessionStore } from './session.js';
2
+ import type { CrowdyLogger } from './logger.js';
3
+ import { CrowdyRealtimeError } from './errors.js';
4
+ import { type UdpNotificationsSubscription } from './generated/graphql.js';
5
+ export type RealtimeStatus = 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'disconnected' | 'failed';
6
+ export type UdpNotification = NonNullable<UdpNotificationsSubscription['udpNotifications']>;
7
+ export type SpatialNotification = Extract<UdpNotification, {
8
+ sequenceNumber: number;
9
+ }>;
10
+ export interface UdpNotificationHandlers {
11
+ actorUpdate?: (notification: Extract<UdpNotification, {
12
+ __typename?: 'ActorUpdateNotification';
13
+ }>) => void;
14
+ actorUpdateResponse?: (notification: Extract<UdpNotification, {
15
+ __typename?: 'ActorUpdateResponse';
16
+ }>) => void;
17
+ voxelUpdate?: (notification: Extract<UdpNotification, {
18
+ __typename?: 'VoxelUpdateNotification';
19
+ }>) => void;
20
+ voxelUpdateResponse?: (notification: Extract<UdpNotification, {
21
+ __typename?: 'VoxelUpdateResponse';
22
+ }>) => void;
23
+ audio?: (notification: Extract<UdpNotification, {
24
+ __typename?: 'ClientAudioNotification';
25
+ }>) => void;
26
+ text?: (notification: Extract<UdpNotification, {
27
+ __typename?: 'ClientTextNotification';
28
+ }>) => void;
29
+ clientEvent?: (notification: Extract<UdpNotification, {
30
+ __typename?: 'ClientEventNotification';
31
+ }>) => void;
32
+ serverEvent?: (notification: Extract<UdpNotification, {
33
+ __typename?: 'ServerEventNotification';
34
+ }>) => void;
35
+ singleActorMessage?: (notification: Extract<UdpNotification, {
36
+ __typename?: 'SingleActorMessageNotification';
37
+ }>) => void;
38
+ genericError?: (notification: Extract<UdpNotification, {
39
+ __typename?: 'GenericErrorResponse';
40
+ }>) => void;
41
+ connectionEvent?: (notification: Extract<UdpNotification, {
42
+ __typename?: 'RealtimeConnectionEvent';
43
+ }>) => void;
44
+ error?: (error: CrowdyRealtimeError) => void;
45
+ any?: (notification: UdpNotification) => void;
46
+ }
47
+ export interface RealtimeConfig {
48
+ wsUrl?: string;
49
+ wsEndpoint?: string;
50
+ retryAttempts?: number;
51
+ retryInitialDelayMs?: number;
52
+ retryMaxDelayMs?: number;
53
+ waitTimeoutMs?: number;
54
+ logger?: CrowdyLogger;
55
+ }
56
+ export declare class RealtimeClient {
57
+ private readonly session;
58
+ private readonly wsUrl;
59
+ private readonly logger;
60
+ private readonly retryAttempts;
61
+ private readonly retryInitialDelayMs;
62
+ private readonly retryMaxDelayMs;
63
+ private readonly waitTimeoutMs;
64
+ private client;
65
+ private release;
66
+ private desired;
67
+ private statusValue;
68
+ private readonly statusListeners;
69
+ private readonly subscribers;
70
+ private readonly pending;
71
+ private nextSubscriberId;
72
+ constructor(config: RealtimeConfig | undefined, session: SessionStore);
73
+ status(): RealtimeStatus;
74
+ onStatus(listener: (status: RealtimeStatus) => void): () => void;
75
+ connect(): void;
76
+ disconnect(): void;
77
+ close(): void;
78
+ subscribe(handlers: UdpNotificationHandlers): () => void;
79
+ waitForSequence(sequenceNumber: number, timeoutMs?: number): Promise<SpatialNotification>;
80
+ private ensureSubscription;
81
+ private restart;
82
+ private dispatch;
83
+ private resolvePending;
84
+ private removePending;
85
+ private rejectAllPending;
86
+ private dispatchError;
87
+ private setStatus;
88
+ }
89
+ //# sourceMappingURL=realtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,273 @@
1
+ import { print } from 'graphql';
2
+ import { createClient } from 'graphql-ws';
3
+ import { silentLogger } from './logger.js';
4
+ import { CrowdyRealtimeError } from './errors.js';
5
+ import { UdpNotificationsDocument, } from './generated/graphql.js';
6
+ export class RealtimeClient {
7
+ constructor(config = {}, session) {
8
+ this.session = session;
9
+ this.client = null;
10
+ this.release = null;
11
+ this.desired = false;
12
+ this.statusValue = 'idle';
13
+ this.statusListeners = new Set();
14
+ this.subscribers = new Map();
15
+ this.pending = new Map();
16
+ this.nextSubscriberId = 1;
17
+ this.wsUrl = config.wsUrl || config.wsEndpoint || 'ws://localhost:3000/graphql';
18
+ this.logger = config.logger ?? silentLogger;
19
+ this.retryAttempts = config.retryAttempts ?? 8;
20
+ this.retryInitialDelayMs = config.retryInitialDelayMs ?? 250;
21
+ this.retryMaxDelayMs = config.retryMaxDelayMs ?? 5000;
22
+ this.waitTimeoutMs = config.waitTimeoutMs ?? 5000;
23
+ this.session.onChange((token) => {
24
+ if (!this.desired)
25
+ return;
26
+ if (!token) {
27
+ this.disconnect();
28
+ this.dispatchError(new CrowdyRealtimeError('Realtime disconnected because the session token was cleared', {
29
+ code: 'AUTH_CLEARED',
30
+ retryable: false,
31
+ }));
32
+ return;
33
+ }
34
+ this.restart();
35
+ });
36
+ }
37
+ status() {
38
+ return this.statusValue;
39
+ }
40
+ onStatus(listener) {
41
+ this.statusListeners.add(listener);
42
+ listener(this.statusValue);
43
+ return () => {
44
+ this.statusListeners.delete(listener);
45
+ };
46
+ }
47
+ connect() {
48
+ this.desired = true;
49
+ this.ensureSubscription();
50
+ }
51
+ disconnect() {
52
+ this.desired = false;
53
+ this.release?.();
54
+ this.release = null;
55
+ this.client?.dispose();
56
+ this.client = null;
57
+ this.setStatus('disconnected');
58
+ }
59
+ close() {
60
+ this.disconnect();
61
+ this.subscribers.clear();
62
+ this.rejectAllPending(new CrowdyRealtimeError('Realtime client closed', { retryable: false }));
63
+ }
64
+ subscribe(handlers) {
65
+ const id = `s${this.nextSubscriberId++}`;
66
+ this.subscribers.set(id, handlers);
67
+ this.connect();
68
+ return () => {
69
+ this.subscribers.delete(id);
70
+ if (this.subscribers.size === 0 && this.desired) {
71
+ this.disconnect();
72
+ }
73
+ };
74
+ }
75
+ waitForSequence(sequenceNumber, timeoutMs = this.waitTimeoutMs) {
76
+ return new Promise((resolve, reject) => {
77
+ const timer = setTimeout(() => {
78
+ this.removePending(sequenceNumber, wait);
79
+ reject(new CrowdyRealtimeError(`Timed out waiting for UDP response sequence ${sequenceNumber}`, { code: 'UDP_SEQUENCE_TIMEOUT', retryable: true }));
80
+ }, timeoutMs);
81
+ const wait = { resolve, reject, timer };
82
+ const waits = this.pending.get(sequenceNumber) ?? [];
83
+ waits.push(wait);
84
+ this.pending.set(sequenceNumber, waits);
85
+ });
86
+ }
87
+ ensureSubscription() {
88
+ if (this.release)
89
+ return;
90
+ const token = this.session.getToken();
91
+ if (!token) {
92
+ const error = new CrowdyRealtimeError('Must be authenticated to subscribe', {
93
+ code: 'AUTH_REQUIRED',
94
+ retryable: false,
95
+ });
96
+ this.setStatus('failed');
97
+ this.dispatchError(error);
98
+ throw error;
99
+ }
100
+ this.setStatus('connecting');
101
+ this.client = createClient({
102
+ url: this.wsUrl,
103
+ lazy: true,
104
+ retryAttempts: this.retryAttempts,
105
+ connectionParams: () => {
106
+ const currentToken = this.session.getToken();
107
+ return currentToken ? { Authorization: `Bearer ${currentToken}` } : {};
108
+ },
109
+ retryWait: async (retries) => {
110
+ this.setStatus('reconnecting');
111
+ const delay = Math.min(this.retryMaxDelayMs, this.retryInitialDelayMs * 2 ** retries);
112
+ const jitter = Math.floor(Math.random() * this.retryInitialDelayMs);
113
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
114
+ },
115
+ on: {
116
+ connected: () => this.setStatus('connected'),
117
+ closed: () => {
118
+ if (this.desired) {
119
+ this.setStatus('reconnecting');
120
+ }
121
+ else {
122
+ this.setStatus('disconnected');
123
+ }
124
+ },
125
+ error: (error) => {
126
+ this.logger.error?.('Realtime WebSocket error', error);
127
+ this.dispatchError(new CrowdyRealtimeError('Realtime WebSocket error', {
128
+ code: 'WEBSOCKET_ERROR',
129
+ retryable: true,
130
+ cause: error,
131
+ }));
132
+ },
133
+ },
134
+ });
135
+ this.release = this.client.subscribe({ query: print(UdpNotificationsDocument) }, {
136
+ next: (message) => {
137
+ const data = message.data;
138
+ const notification = data?.udpNotifications;
139
+ if (notification)
140
+ this.dispatch(notification);
141
+ if (message.errors?.length) {
142
+ this.dispatchError(new CrowdyRealtimeError(message.errors[0]?.message ?? 'Subscription error', {
143
+ code: 'SUBSCRIPTION_ERROR',
144
+ retryable: true,
145
+ cause: message.errors,
146
+ }));
147
+ }
148
+ },
149
+ error: (error) => {
150
+ this.setStatus('failed');
151
+ this.dispatchError(new CrowdyRealtimeError('Realtime subscription failed', {
152
+ code: 'SUBSCRIPTION_FAILED',
153
+ retryable: true,
154
+ cause: error,
155
+ }));
156
+ },
157
+ complete: () => {
158
+ this.release = null;
159
+ if (this.desired) {
160
+ this.setStatus('reconnecting');
161
+ this.ensureSubscription();
162
+ }
163
+ },
164
+ });
165
+ }
166
+ restart() {
167
+ this.release?.();
168
+ this.release = null;
169
+ this.client?.dispose();
170
+ this.client = null;
171
+ this.ensureSubscription();
172
+ }
173
+ dispatch(notification) {
174
+ this.resolvePending(notification);
175
+ for (const handlers of [...this.subscribers.values()]) {
176
+ try {
177
+ handlers.any?.(notification);
178
+ switch (notification.__typename) {
179
+ case 'ActorUpdateNotification':
180
+ handlers.actorUpdate?.(notification);
181
+ break;
182
+ case 'ActorUpdateResponse':
183
+ handlers.actorUpdateResponse?.(notification);
184
+ break;
185
+ case 'VoxelUpdateNotification':
186
+ handlers.voxelUpdate?.(notification);
187
+ break;
188
+ case 'VoxelUpdateResponse':
189
+ handlers.voxelUpdateResponse?.(notification);
190
+ break;
191
+ case 'ClientAudioNotification':
192
+ handlers.audio?.(notification);
193
+ break;
194
+ case 'ClientTextNotification':
195
+ handlers.text?.(notification);
196
+ break;
197
+ case 'ClientEventNotification':
198
+ handlers.clientEvent?.(notification);
199
+ break;
200
+ case 'ServerEventNotification':
201
+ handlers.serverEvent?.(notification);
202
+ break;
203
+ case 'SingleActorMessageNotification':
204
+ handlers.singleActorMessage?.(notification);
205
+ break;
206
+ case 'GenericErrorResponse':
207
+ handlers.genericError?.(notification);
208
+ break;
209
+ case 'RealtimeConnectionEvent':
210
+ handlers.connectionEvent?.(notification);
211
+ break;
212
+ }
213
+ }
214
+ catch (error) {
215
+ this.logger.error?.('Realtime notification handler threw', error);
216
+ }
217
+ }
218
+ }
219
+ resolvePending(notification) {
220
+ if (!('sequenceNumber' in notification))
221
+ return;
222
+ const waits = this.pending.get(notification.sequenceNumber);
223
+ if (!waits?.length)
224
+ return;
225
+ this.pending.delete(notification.sequenceNumber);
226
+ for (const wait of waits) {
227
+ clearTimeout(wait.timer);
228
+ if (notification.__typename === 'GenericErrorResponse') {
229
+ wait.reject(new CrowdyRealtimeError(`UDP request failed: ${notification.errorCode}`, {
230
+ code: notification.errorCode,
231
+ retryable: false,
232
+ }));
233
+ }
234
+ else {
235
+ wait.resolve(notification);
236
+ }
237
+ }
238
+ }
239
+ removePending(sequenceNumber, wait) {
240
+ const waits = this.pending.get(sequenceNumber);
241
+ if (!waits)
242
+ return;
243
+ const next = waits.filter((candidate) => candidate !== wait);
244
+ if (next.length) {
245
+ this.pending.set(sequenceNumber, next);
246
+ }
247
+ else {
248
+ this.pending.delete(sequenceNumber);
249
+ }
250
+ }
251
+ rejectAllPending(error) {
252
+ for (const waits of this.pending.values()) {
253
+ for (const wait of waits) {
254
+ clearTimeout(wait.timer);
255
+ wait.reject(error);
256
+ }
257
+ }
258
+ this.pending.clear();
259
+ }
260
+ dispatchError(error) {
261
+ for (const handlers of [...this.subscribers.values()]) {
262
+ handlers.error?.(error);
263
+ }
264
+ }
265
+ setStatus(status) {
266
+ if (status === this.statusValue)
267
+ return;
268
+ this.statusValue = status;
269
+ for (const listener of [...this.statusListeners]) {
270
+ listener(status);
271
+ }
272
+ }
273
+ }
@@ -0,0 +1,27 @@
1
+ export type SessionListener = (token: string | null) => void;
2
+ export interface TokenStore {
3
+ get(): string | null | Promise<string | null>;
4
+ set(token: string): void | Promise<void>;
5
+ clear(): void | Promise<void>;
6
+ }
7
+ export declare class BrowserLocalStorageTokenStore implements TokenStore {
8
+ private readonly key;
9
+ constructor(key?: string);
10
+ get(): string | null;
11
+ set(token: string): void;
12
+ clear(): void;
13
+ }
14
+ export declare class SessionStore {
15
+ private readonly tokenStore?;
16
+ private token;
17
+ private readonly listeners;
18
+ constructor(tokenStore?: TokenStore | undefined);
19
+ restore(): Promise<string | null>;
20
+ getToken(): string | null;
21
+ setToken(token: string | null, options?: {
22
+ persist?: boolean;
23
+ }): void;
24
+ clear(): void;
25
+ onChange(listener: SessionListener): () => void;
26
+ }
27
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,GAAG,IAAI,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,KAAK,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,qBAAa,6BAA8B,YAAW,UAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,SAAmB;IAEnD,GAAG,IAAI,MAAM,GAAG,IAAI;IAKpB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAKxB,KAAK,IAAI,IAAI;CAId;AAED,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAHxC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;gBAE3B,UAAU,CAAC,EAAE,UAAU,YAAA;IAE9C,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAMvC,QAAQ,IAAI,MAAM,GAAG,IAAI;IAIzB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,IAAI;IAiBzE,KAAK,IAAI,IAAI;IAIb,QAAQ,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;CAOhD"}
@@ -0,0 +1,61 @@
1
+ export class BrowserLocalStorageTokenStore {
2
+ constructor(key = 'crowdyjs:token') {
3
+ this.key = key;
4
+ }
5
+ get() {
6
+ if (typeof localStorage === 'undefined')
7
+ return null;
8
+ return localStorage.getItem(this.key);
9
+ }
10
+ set(token) {
11
+ if (typeof localStorage === 'undefined')
12
+ return;
13
+ localStorage.setItem(this.key, token);
14
+ }
15
+ clear() {
16
+ if (typeof localStorage === 'undefined')
17
+ return;
18
+ localStorage.removeItem(this.key);
19
+ }
20
+ }
21
+ export class SessionStore {
22
+ constructor(tokenStore) {
23
+ this.tokenStore = tokenStore;
24
+ this.token = null;
25
+ this.listeners = new Set();
26
+ }
27
+ async restore() {
28
+ const token = (await this.tokenStore?.get()) ?? null;
29
+ this.setToken(token, { persist: false });
30
+ return token;
31
+ }
32
+ getToken() {
33
+ return this.token;
34
+ }
35
+ setToken(token, options = {}) {
36
+ if (token === this.token)
37
+ return;
38
+ this.token = token;
39
+ if (options.persist !== false) {
40
+ if (token) {
41
+ void this.tokenStore?.set(token);
42
+ }
43
+ else {
44
+ void this.tokenStore?.clear();
45
+ }
46
+ }
47
+ for (const listener of [...this.listeners]) {
48
+ listener(token);
49
+ }
50
+ }
51
+ clear() {
52
+ this.setToken(null);
53
+ }
54
+ onChange(listener) {
55
+ this.listeners.add(listener);
56
+ listener(this.token);
57
+ return () => {
58
+ this.listeners.delete(listener);
59
+ };
60
+ }
61
+ }
@@ -1,49 +1,2 @@
1
- /**
2
- * WebSocket subscription manager for the udpNotifications stream.
3
- *
4
- * Owns one shared `graphql-transport-ws` socket and dispatches incoming
5
- * `udpNotifications` payloads to per-typename handler arrays. Reads its
6
- * bearer token from `AuthState` so HTTP and WS auth can never drift.
7
- *
8
- * Public API is now `subscribe(handlers)` which returns an unsubscribe
9
- * function; the per-handler `onActorUpdate` etc. shims are gone.
10
- */
11
- import { AuthState } from './auth-state.js';
12
- import type { ActorUpdateHandler, ActorUpdateResponseHandler, VoxelUpdateHandler, VoxelUpdateResponseHandler, ClientAudioHandler, ClientTextHandler, ClientEventHandler, ServerEventHandler, GenericErrorHandler, UnsubscribeFn } from './types.js';
13
- export interface UdpNotificationHandlers {
14
- onActorUpdate?: ActorUpdateHandler;
15
- onActorUpdateResponse?: ActorUpdateResponseHandler;
16
- onVoxelUpdate?: VoxelUpdateHandler;
17
- onVoxelUpdateResponse?: VoxelUpdateResponseHandler;
18
- onClientAudio?: ClientAudioHandler;
19
- onClientText?: ClientTextHandler;
20
- onClientEvent?: ClientEventHandler;
21
- onServerEvent?: ServerEventHandler;
22
- onGenericError?: GenericErrorHandler;
23
- }
24
- export interface SubscriptionManagerConfig {
25
- wsEndpoint?: string;
26
- }
27
- export declare class SubscriptionManager {
28
- private readonly wsEndpoint;
29
- private readonly authState;
30
- private wsClient;
31
- private wsUnsubscribe;
32
- private subscriptionId;
33
- private subscribers;
34
- private nextSubscriberId;
35
- constructor(config: SubscriptionManagerConfig | undefined, authState: AuthState);
36
- /**
37
- * Register handlers for the udpNotifications stream. The first call opens
38
- * the shared socket (provided we have a token); subsequent calls reuse it.
39
- * The returned function detaches just this set of handlers and closes the
40
- * socket once the last subscriber leaves.
41
- */
42
- subscribe(handlers: UdpNotificationHandlers): UnsubscribeFn;
43
- private ensureSubscription;
44
- private startSubscription;
45
- private dispatch;
46
- private checkIfShouldUnsubscribe;
47
- close(): void;
48
- }
1
+ export { RealtimeClient as SubscriptionManager, type RealtimeConfig as SubscriptionManagerConfig, type UdpNotificationHandlers, } from './realtime.js';
49
2
  //# sourceMappingURL=subscriptions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../src/subscriptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAEV,kBAAkB,EAClB,0BAA0B,EAC1B,kBAAkB,EAClB,0BAA0B,EAC1B,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,qBAAqB,CAAC,EAAE,0BAA0B,CAAC;IACnD,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,qBAAqB,CAAC,EAAE,0BAA0B,CAAC;IACnD,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,cAAc,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAOD,MAAM,WAAW,yBAAyB;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,WAAW,CAA6C;IAChE,OAAO,CAAC,gBAAgB,CAAK;gBAEjB,MAAM,EAAE,yBAAyB,YAAK,EAAE,SAAS,EAAE,SAAS;IAKxE;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,uBAAuB,GAAG,aAAa;IAU3D,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,iBAAiB;IAsFzB,OAAO,CAAC,QAAQ;IA6ChB,OAAO,CAAC,wBAAwB;IAOhC,KAAK,IAAI,IAAI;CAMd"}
1
+ {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../src/subscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,IAAI,mBAAmB,EACrC,KAAK,cAAc,IAAI,yBAAyB,EAChD,KAAK,uBAAuB,GAC7B,MAAM,eAAe,CAAC"}