@lemoncloud/chatic-sockets-lib 0.1.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 (46) hide show
  1. package/README.md +44 -0
  2. package/dist/client-socket-v2/common.d.ts +4 -0
  3. package/dist/client-socket-v2/common.js +21 -0
  4. package/dist/client-socket-v2/connection-rotation-controller.d.ts +26 -0
  5. package/dist/client-socket-v2/connection-rotation-controller.js +79 -0
  6. package/dist/client-socket-v2/create-client-socket-v2.d.ts +2 -0
  7. package/dist/client-socket-v2/create-client-socket-v2.js +164 -0
  8. package/dist/client-socket-v2/create-device-runtime.d.ts +19 -0
  9. package/dist/client-socket-v2/create-device-runtime.js +53 -0
  10. package/dist/client-socket-v2/domain-sync-plan.d.ts +1 -0
  11. package/dist/client-socket-v2/domain-sync-plan.js +2 -0
  12. package/dist/client-socket-v2/gateways/create-domain-gateway.d.ts +6 -0
  13. package/dist/client-socket-v2/gateways/create-domain-gateway.js +19 -0
  14. package/dist/client-socket-v2/gateways/device-gateway.d.ts +8 -0
  15. package/dist/client-socket-v2/gateways/device-gateway.js +13 -0
  16. package/dist/client-socket-v2/index.d.ts +23 -0
  17. package/dist/client-socket-v2/index.js +41 -0
  18. package/dist/client-socket-v2/keep-alive-loop.d.ts +29 -0
  19. package/dist/client-socket-v2/keep-alive-loop.js +93 -0
  20. package/dist/client-socket-v2/message-router.d.ts +9 -0
  21. package/dist/client-socket-v2/message-router.js +45 -0
  22. package/dist/client-socket-v2/pending-request-store.d.ts +23 -0
  23. package/dist/client-socket-v2/pending-request-store.js +85 -0
  24. package/dist/client-socket-v2/plans/device-sync-plan.d.ts +30 -0
  25. package/dist/client-socket-v2/plans/device-sync-plan.js +64 -0
  26. package/dist/client-socket-v2/reconnect-controller.d.ts +32 -0
  27. package/dist/client-socket-v2/reconnect-controller.js +120 -0
  28. package/dist/client-socket-v2/shared-timer-scheduler.d.ts +17 -0
  29. package/dist/client-socket-v2/shared-timer-scheduler.js +72 -0
  30. package/dist/client-socket-v2/socket-runtime.d.ts +32 -0
  31. package/dist/client-socket-v2/socket-runtime.js +52 -0
  32. package/dist/client-socket-v2/socket-transport.d.ts +18 -0
  33. package/dist/client-socket-v2/socket-transport.js +124 -0
  34. package/dist/client-socket-v2/sync-scheduler.d.ts +36 -0
  35. package/dist/client-socket-v2/sync-scheduler.js +189 -0
  36. package/dist/client-socket-v2/types.d.ts +135 -0
  37. package/dist/client-socket-v2/types.js +2 -0
  38. package/dist/lib/device/contracts.d.ts +36 -0
  39. package/dist/lib/device/contracts.js +7 -0
  40. package/dist/lib/device/types.d.ts +55 -0
  41. package/dist/lib/device/types.js +2 -0
  42. package/dist/lib/sockets/packets.d.ts +67 -0
  43. package/dist/lib/sockets/packets.js +2 -0
  44. package/dist/lib/types.d.ts +122 -0
  45. package/dist/lib/types.js +11 -0
  46. package/package.json +31 -0
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.KeepAliveLoop = void 0;
13
+ class KeepAliveLoop {
14
+ constructor(options) {
15
+ var _a, _b, _c;
16
+ this.options = options;
17
+ this.unsubs = [];
18
+ this.running = false;
19
+ this.start = () => {
20
+ this.running = true;
21
+ if (this.options.client.state === 'connected')
22
+ this.scheduleNow();
23
+ };
24
+ this.stop = () => {
25
+ this.running = false;
26
+ this.clearTimer();
27
+ };
28
+ this.destroy = () => {
29
+ this.stop();
30
+ this.unsubs.splice(0).forEach(unsub => unsub());
31
+ };
32
+ this.scheduleNow = () => {
33
+ this.clearTimer();
34
+ if (this.timerScheduler) {
35
+ this.timerScheduler.schedule(this.timerKey, 0, () => this.tick());
36
+ return;
37
+ }
38
+ this.timer = setTimeout(() => void this.tick(), 0);
39
+ };
40
+ this.scheduleNext = () => {
41
+ this.clearTimer();
42
+ if (!this.running || this.options.client.state !== 'connected')
43
+ return;
44
+ if (this.timerScheduler) {
45
+ this.timerScheduler.schedule(this.timerKey, this.intervalMs, () => this.tick());
46
+ return;
47
+ }
48
+ this.timer = setTimeout(() => void this.tick(), this.intervalMs);
49
+ };
50
+ this.clearTimer = () => {
51
+ var _a;
52
+ (_a = this.timerScheduler) === null || _a === void 0 ? void 0 : _a.cancel(this.timerKey);
53
+ if (this.timer)
54
+ clearTimeout(this.timer);
55
+ this.timer = undefined;
56
+ };
57
+ this.tick = () => __awaiter(this, void 0, void 0, function* () {
58
+ var _d, _e, _f;
59
+ if (!this.running || this.options.client.state !== 'connected')
60
+ return;
61
+ if (this.inFlight)
62
+ return;
63
+ const payload = (_f = (_e = (_d = this.options).buildPayload) === null || _e === void 0 ? void 0 : _e.call(_d)) !== null && _f !== void 0 ? _f : null;
64
+ this.inFlight = Promise.resolve()
65
+ .then(() => __awaiter(this, void 0, void 0, function* () {
66
+ if (this.mode === 'send') {
67
+ this.options.client.send('system.ping', payload);
68
+ return;
69
+ }
70
+ yield this.options.client.request('system.ping', payload, {
71
+ timeoutMs: this.options.timeoutMs,
72
+ });
73
+ }))
74
+ .catch(() => undefined)
75
+ .then(() => {
76
+ this.inFlight = undefined;
77
+ this.scheduleNext();
78
+ });
79
+ yield this.inFlight;
80
+ });
81
+ this.intervalMs = (_a = options.intervalMs) !== null && _a !== void 0 ? _a : 30000;
82
+ this.mode = (_b = options.mode) !== null && _b !== void 0 ? _b : 'request';
83
+ this.timerScheduler = options.timerScheduler;
84
+ this.timerKey = (_c = options.timerKey) !== null && _c !== void 0 ? _c : 'keepalive';
85
+ this.unsubs.push(options.client.onState(event => {
86
+ if (event.next === 'connected' && this.running)
87
+ this.scheduleNow();
88
+ if (event.next === 'closing' || event.next === 'closed')
89
+ this.clearTimer();
90
+ }));
91
+ }
92
+ }
93
+ exports.KeepAliveLoop = KeepAliveLoop;
@@ -0,0 +1,9 @@
1
+ import type { SocketMessage } from '../lib/types';
2
+ export declare type MessageListener<T = any> = (message: SocketMessage<T>) => void;
3
+ export declare class MessageRouter {
4
+ private readonly anyListeners;
5
+ private readonly typeListeners;
6
+ onAny: (listener: MessageListener) => (() => void);
7
+ onType: <T = any>(type: string, listener: MessageListener<T>) => (() => void);
8
+ route: (message: SocketMessage<any>) => number;
9
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MessageRouter = void 0;
4
+ class MessageRouter {
5
+ constructor() {
6
+ this.anyListeners = new Set();
7
+ this.typeListeners = new Map();
8
+ this.onAny = (listener) => {
9
+ this.anyListeners.add(listener);
10
+ return () => this.anyListeners.delete(listener);
11
+ };
12
+ this.onType = (type, listener) => {
13
+ var _a;
14
+ const key = `${type !== null && type !== void 0 ? type : ''}`.trim();
15
+ if (!key)
16
+ throw new Error(`@type (string) is required - MessageRouter.onType()`);
17
+ const bucket = (_a = this.typeListeners.get(key)) !== null && _a !== void 0 ? _a : new Set();
18
+ bucket.add(listener);
19
+ this.typeListeners.set(key, bucket);
20
+ return () => {
21
+ bucket.delete(listener);
22
+ if (!bucket.size)
23
+ this.typeListeners.delete(key);
24
+ };
25
+ };
26
+ this.route = (message) => {
27
+ var _a;
28
+ const key = `${(_a = message === null || message === void 0 ? void 0 : message.type) !== null && _a !== void 0 ? _a : ''}`.trim();
29
+ let count = 0;
30
+ this.anyListeners.forEach(listener => {
31
+ listener(message);
32
+ count += 1;
33
+ });
34
+ if (!key)
35
+ return count;
36
+ const bucket = this.typeListeners.get(key);
37
+ bucket === null || bucket === void 0 ? void 0 : bucket.forEach(listener => {
38
+ listener(message);
39
+ count += 1;
40
+ });
41
+ return count;
42
+ };
43
+ }
44
+ }
45
+ exports.MessageRouter = MessageRouter;
@@ -0,0 +1,23 @@
1
+ import type { SocketMessage } from '../lib/types';
2
+ import type { SharedTimerScheduler } from './types';
3
+ export interface PendingRequestStoreOptions {
4
+ timerScheduler?: SharedTimerScheduler;
5
+ timerPrefix?: string;
6
+ }
7
+ export declare class PendingRequestStore {
8
+ private readonly items;
9
+ private readonly timerScheduler?;
10
+ private readonly timerPrefix;
11
+ constructor(options?: PendingRequestStoreOptions);
12
+ size: () => number;
13
+ has: (mid: string) => boolean;
14
+ create<T = any>(mid: string, options?: {
15
+ timeoutMs?: number;
16
+ onTimeout?: (mid: string) => Error;
17
+ }): Promise<T>;
18
+ resolve: <T = any>(mid: string, value: T) => boolean;
19
+ reject: (mid: string, error: unknown) => boolean;
20
+ clear: (reason?: unknown) => number;
21
+ settle: (message: SocketMessage<any>) => boolean;
22
+ private toTimerKey;
23
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PendingRequestStore = void 0;
4
+ class PendingRequestStore {
5
+ constructor(options = {}) {
6
+ var _a;
7
+ this.items = new Map();
8
+ this.size = () => this.items.size;
9
+ this.has = (mid) => this.items.has(mid);
10
+ this.resolve = (mid, value) => {
11
+ var _a;
12
+ const item = this.items.get(mid);
13
+ if (!item)
14
+ return false;
15
+ this.items.delete(mid);
16
+ (_a = this.timerScheduler) === null || _a === void 0 ? void 0 : _a.cancel(this.toTimerKey(mid));
17
+ item.timer && clearTimeout(item.timer);
18
+ item.resolve(value);
19
+ return true;
20
+ };
21
+ this.reject = (mid, error) => {
22
+ var _a;
23
+ const item = this.items.get(mid);
24
+ if (!item)
25
+ return false;
26
+ this.items.delete(mid);
27
+ (_a = this.timerScheduler) === null || _a === void 0 ? void 0 : _a.cancel(this.toTimerKey(mid));
28
+ item.timer && clearTimeout(item.timer);
29
+ item.reject(error);
30
+ return true;
31
+ };
32
+ this.clear = (reason) => {
33
+ const mids = [...this.items.keys()];
34
+ mids.forEach(mid => this.reject(mid, reason !== null && reason !== void 0 ? reason : new Error(`499 CLIENT CLOSED REQUEST - pending[${mid}]`)));
35
+ return mids.length;
36
+ };
37
+ this.settle = (message) => {
38
+ var _a, _b, _c;
39
+ const mid = `${(_a = message === null || message === void 0 ? void 0 : message.mid) !== null && _a !== void 0 ? _a : ''}`;
40
+ if (!mid)
41
+ return false;
42
+ if ((_b = message === null || message === void 0 ? void 0 : message.type) === null || _b === void 0 ? void 0 : _b.endsWith(':ok'))
43
+ return this.resolve(mid, message.data);
44
+ if ((_c = message === null || message === void 0 ? void 0 : message.type) === null || _c === void 0 ? void 0 : _c.endsWith(':error'))
45
+ return this.reject(mid, new Error(`${(message === null || message === void 0 ? void 0 : message.error) || 'socket request failed'} - ${message === null || message === void 0 ? void 0 : message.type}`));
46
+ return false;
47
+ };
48
+ this.toTimerKey = (mid) => `${this.timerPrefix}${mid}`;
49
+ this.timerScheduler = options.timerScheduler;
50
+ this.timerPrefix = (_a = options.timerPrefix) !== null && _a !== void 0 ? _a : 'pending:';
51
+ }
52
+ create(mid, options) {
53
+ if (!mid)
54
+ throw new Error(`@mid (string) is required - PendingRequestStore.create()`);
55
+ if (this.items.has(mid))
56
+ throw new Error(`409 CONFLICT - pending request[${mid}] already exists`);
57
+ return new Promise((resolve, reject) => {
58
+ const item = {
59
+ resolve,
60
+ reject,
61
+ timer: (options === null || options === void 0 ? void 0 : options.timeoutMs) && options.timeoutMs > 0 && !this.timerScheduler
62
+ ? setTimeout(() => {
63
+ var _a, _b;
64
+ this.items.delete(mid);
65
+ const error = (_b = (_a = options === null || options === void 0 ? void 0 : options.onTimeout) === null || _a === void 0 ? void 0 : _a.call(options, mid)) !== null && _b !== void 0 ? _b : new Error(`408 REQUEST TIMEOUT - pending[${mid}]`);
66
+ reject(error);
67
+ }, options.timeoutMs)
68
+ : undefined,
69
+ };
70
+ this.items.set(mid, item);
71
+ if ((options === null || options === void 0 ? void 0 : options.timeoutMs) && options.timeoutMs > 0 && this.timerScheduler) {
72
+ this.timerScheduler.schedule(this.toTimerKey(mid), options.timeoutMs, () => {
73
+ var _a, _b;
74
+ const current = this.items.get(mid);
75
+ if (!current)
76
+ return;
77
+ this.items.delete(mid);
78
+ const error = (_b = (_a = options === null || options === void 0 ? void 0 : options.onTimeout) === null || _a === void 0 ? void 0 : _a.call(options, mid)) !== null && _b !== void 0 ? _b : new Error(`408 REQUEST TIMEOUT - pending[${mid}]`);
79
+ reject(error);
80
+ });
81
+ }
82
+ });
83
+ }
84
+ }
85
+ exports.PendingRequestStore = PendingRequestStore;
@@ -0,0 +1,30 @@
1
+ import type { DeviceGetResponseData } from '../../lib/device/types';
2
+ import type { SocketMessage } from '../../lib/types';
3
+ import type { DomainSyncContext, DomainSyncPlan, SyncTargetDescriptor } from '../types';
4
+ export interface DeviceSyncTarget extends SyncTargetDescriptor {
5
+ type: 'device';
6
+ }
7
+ export interface DeviceSyncSnapshot {
8
+ id?: string;
9
+ tick?: number;
10
+ lastAppliedTick?: number;
11
+ view?: DeviceGetResponseData;
12
+ }
13
+ export interface DeviceSyncPlanOptions {
14
+ intervalMs?: number;
15
+ sendSyncHint?: boolean;
16
+ resetSnapshotOnConnected?: boolean;
17
+ onUpdate?: (target: DeviceSyncTarget, view: DeviceGetResponseData, previous?: DeviceSyncSnapshot) => void;
18
+ }
19
+ export declare class DeviceSyncPlan implements DomainSyncPlan<DeviceSyncTarget> {
20
+ private readonly options;
21
+ readonly domain = "device";
22
+ constructor(options?: DeviceSyncPlanOptions);
23
+ supports: (target: SyncTargetDescriptor) => target is DeviceSyncTarget;
24
+ getKey: (target: DeviceSyncTarget) => string;
25
+ getIntervalMs: (target: DeviceSyncTarget) => number;
26
+ onConnected: (target: DeviceSyncTarget, ctx: DomainSyncContext) => void;
27
+ run: (target: DeviceSyncTarget, ctx: DomainSyncContext) => Promise<void>;
28
+ onTrigger: (target: DeviceSyncTarget, message: SocketMessage<any>, ctx: DomainSyncContext) => Promise<void>;
29
+ updateLocalState: (target: DeviceSyncTarget, snapshot: unknown, ctx: DomainSyncContext) => void;
30
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.DeviceSyncPlan = void 0;
13
+ class DeviceSyncPlan {
14
+ constructor(options = {}) {
15
+ this.options = options;
16
+ this.domain = 'device';
17
+ this.supports = (target) => (target === null || target === void 0 ? void 0 : target.type) === 'device';
18
+ this.getKey = (target) => { var _a; return `device:${(_a = target === null || target === void 0 ? void 0 : target.id) !== null && _a !== void 0 ? _a : 'current'}`; };
19
+ this.getIntervalMs = (target) => { var _a, _b; return (_b = (_a = target.intervalMs) !== null && _a !== void 0 ? _a : this.options.intervalMs) !== null && _b !== void 0 ? _b : 2000; };
20
+ this.onConnected = (target, ctx) => {
21
+ if (this.options.resetSnapshotOnConnected === false)
22
+ return;
23
+ ctx.writeSnapshot(target, undefined);
24
+ };
25
+ this.run = (target, ctx) => __awaiter(this, void 0, void 0, function* () {
26
+ var _a, _b, _c, _d;
27
+ const prev = ctx.readSnapshot(target);
28
+ if (this.options.sendSyncHint !== false) {
29
+ const syncData = Object.assign(Object.assign({}, (target.id ? { id: target.id } : {})), (typeof (prev === null || prev === void 0 ? void 0 : prev.tick) === 'number' ? { tick: prev.tick } : {}));
30
+ ctx.client.send('device.sync', Object.keys(syncData).length ? syncData : null);
31
+ }
32
+ const input = target.id ? { id: target.id } : null;
33
+ const view = yield ctx.client.request('device.read', input);
34
+ const nextTick = typeof (view === null || view === void 0 ? void 0 : view.tick) === 'number' ? view.tick : undefined;
35
+ const prevTick = typeof (prev === null || prev === void 0 ? void 0 : prev.tick) === 'number' ? prev.tick : undefined;
36
+ const lastAppliedTick = typeof (prev === null || prev === void 0 ? void 0 : prev.lastAppliedTick) === 'number' ? prev.lastAppliedTick : undefined;
37
+ if (lastAppliedTick !== undefined && nextTick !== undefined && nextTick < lastAppliedTick)
38
+ return;
39
+ if (prevTick === nextTick && (prev === null || prev === void 0 ? void 0 : prev.view))
40
+ return;
41
+ const next = {
42
+ id: `${(_b = (_a = view === null || view === void 0 ? void 0 : view.id) !== null && _a !== void 0 ? _a : target.id) !== null && _b !== void 0 ? _b : ''}` || undefined,
43
+ tick: nextTick,
44
+ lastAppliedTick: nextTick,
45
+ view,
46
+ };
47
+ ctx.writeSnapshot(target, next);
48
+ (_d = (_c = this.options).onUpdate) === null || _d === void 0 ? void 0 : _d.call(_c, target, view, prev);
49
+ });
50
+ this.onTrigger = (target, message, ctx) => __awaiter(this, void 0, void 0, function* () {
51
+ const data = ((message === null || message === void 0 ? void 0 : message.data) || {});
52
+ if (target.id && (data === null || data === void 0 ? void 0 : data.id) && target.id !== data.id)
53
+ return;
54
+ yield this.run(target, ctx);
55
+ });
56
+ this.updateLocalState = (target, snapshot, ctx) => {
57
+ var _a, _b, _c;
58
+ const prev = ctx.readSnapshot(target);
59
+ const patch = (snapshot || {});
60
+ ctx.writeSnapshot(target, Object.assign(Object.assign(Object.assign({}, prev), patch), { view: (_a = patch.view) !== null && _a !== void 0 ? _a : prev === null || prev === void 0 ? void 0 : prev.view, tick: (_b = patch.tick) !== null && _b !== void 0 ? _b : prev === null || prev === void 0 ? void 0 : prev.tick, lastAppliedTick: (_c = patch.lastAppliedTick) !== null && _c !== void 0 ? _c : prev === null || prev === void 0 ? void 0 : prev.lastAppliedTick }));
61
+ };
62
+ }
63
+ }
64
+ exports.DeviceSyncPlan = DeviceSyncPlan;
@@ -0,0 +1,32 @@
1
+ import type { ClientSocketV2, ReconnectController, SharedTimerScheduler } from './types';
2
+ export interface AutoReconnectControllerOptions {
3
+ client: ClientSocketV2;
4
+ minDelayMs?: number;
5
+ maxDelayMs?: number;
6
+ factor?: number;
7
+ timerScheduler?: SharedTimerScheduler;
8
+ timerKey?: string;
9
+ }
10
+ export declare class AutoReconnectController implements ReconnectController {
11
+ private readonly options;
12
+ private readonly minDelayMs;
13
+ private readonly maxDelayMs;
14
+ private readonly factor;
15
+ private readonly timerScheduler?;
16
+ private readonly timerKey;
17
+ private readonly unsubs;
18
+ private timer?;
19
+ private active;
20
+ private manualStop;
21
+ private restarting;
22
+ private attempt;
23
+ private connecting?;
24
+ constructor(options: AutoReconnectControllerOptions);
25
+ start: () => Promise<void>;
26
+ stop: () => Promise<void>;
27
+ restart: () => Promise<void>;
28
+ destroy: () => void;
29
+ private scheduleReconnect;
30
+ private tryConnect;
31
+ private clearTimer;
32
+ }
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AutoReconnectController = void 0;
13
+ class AutoReconnectController {
14
+ constructor(options) {
15
+ var _a, _b, _c, _d;
16
+ this.options = options;
17
+ this.unsubs = [];
18
+ this.active = false;
19
+ this.manualStop = false;
20
+ this.restarting = false;
21
+ this.attempt = 0;
22
+ this.start = () => __awaiter(this, void 0, void 0, function* () {
23
+ this.active = true;
24
+ this.manualStop = false;
25
+ this.clearTimer();
26
+ if (this.options.client.state !== 'connected') {
27
+ yield this.tryConnect();
28
+ }
29
+ });
30
+ this.stop = () => __awaiter(this, void 0, void 0, function* () {
31
+ this.active = false;
32
+ this.manualStop = true;
33
+ this.restarting = false;
34
+ this.clearTimer();
35
+ this.attempt = 0;
36
+ yield this.options.client.disconnect();
37
+ });
38
+ this.restart = () => __awaiter(this, void 0, void 0, function* () {
39
+ if (!this.active) {
40
+ yield this.start();
41
+ return;
42
+ }
43
+ this.clearTimer();
44
+ this.attempt = 0;
45
+ this.restarting = true;
46
+ try {
47
+ if (this.options.client.state === 'connected' || this.options.client.state === 'connecting') {
48
+ yield this.options.client.disconnect(1012, 'service-restart');
49
+ }
50
+ yield this.tryConnect();
51
+ }
52
+ finally {
53
+ this.restarting = false;
54
+ }
55
+ });
56
+ this.destroy = () => {
57
+ this.clearTimer();
58
+ this.unsubs.splice(0).forEach(unsub => unsub());
59
+ };
60
+ this.scheduleReconnect = () => {
61
+ if (!this.active || this.manualStop)
62
+ return;
63
+ if (this.timer || this.connecting)
64
+ return;
65
+ const delayMs = Math.min(this.minDelayMs * Math.pow(this.factor, this.attempt), this.maxDelayMs);
66
+ if (this.timerScheduler) {
67
+ this.timerScheduler.schedule(this.timerKey, delayMs, () => {
68
+ void this.tryConnect();
69
+ });
70
+ this.attempt += 1;
71
+ return;
72
+ }
73
+ this.timer = setTimeout(() => {
74
+ this.timer = undefined;
75
+ void this.tryConnect();
76
+ }, delayMs);
77
+ this.attempt += 1;
78
+ };
79
+ this.tryConnect = () => __awaiter(this, void 0, void 0, function* () {
80
+ if (!this.active || this.manualStop)
81
+ return;
82
+ if (this.connecting)
83
+ return this.connecting;
84
+ this.connecting = this.options.client
85
+ .connect()
86
+ .catch(() => {
87
+ this.scheduleReconnect();
88
+ })
89
+ .then(() => {
90
+ this.connecting = undefined;
91
+ });
92
+ return this.connecting;
93
+ });
94
+ this.clearTimer = () => {
95
+ var _a;
96
+ (_a = this.timerScheduler) === null || _a === void 0 ? void 0 : _a.cancel(this.timerKey);
97
+ if (this.timer)
98
+ clearTimeout(this.timer);
99
+ this.timer = undefined;
100
+ };
101
+ this.minDelayMs = (_a = options.minDelayMs) !== null && _a !== void 0 ? _a : 500;
102
+ this.maxDelayMs = (_b = options.maxDelayMs) !== null && _b !== void 0 ? _b : 10000;
103
+ this.factor = (_c = options.factor) !== null && _c !== void 0 ? _c : 2;
104
+ this.timerScheduler = options.timerScheduler;
105
+ this.timerKey = (_d = options.timerKey) !== null && _d !== void 0 ? _d : 'reconnect';
106
+ this.unsubs.push(options.client.onState(event => {
107
+ if (!this.active)
108
+ return;
109
+ if (event.next === 'connected') {
110
+ this.attempt = 0;
111
+ this.clearTimer();
112
+ this.connecting = undefined;
113
+ }
114
+ if (event.next === 'closed' && !this.manualStop && !this.restarting) {
115
+ this.scheduleReconnect();
116
+ }
117
+ }));
118
+ }
119
+ }
120
+ exports.AutoReconnectController = AutoReconnectController;
@@ -0,0 +1,17 @@
1
+ import type { SharedTimerScheduler } from './types';
2
+ export interface SharedTimerSchedulerOptions {
3
+ now?: () => number;
4
+ }
5
+ export declare class InMemorySharedTimerScheduler implements SharedTimerScheduler {
6
+ private readonly tasks;
7
+ private readonly now;
8
+ private timer?;
9
+ constructor(options?: SharedTimerSchedulerOptions);
10
+ schedule: (key: string, delayMs: number, task: () => void | Promise<void>) => void;
11
+ cancel: (key: string) => void;
12
+ cancelAll: (prefix?: string) => void;
13
+ size: () => number;
14
+ private rearm;
15
+ private getNextTask;
16
+ private flush;
17
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.InMemorySharedTimerScheduler = void 0;
13
+ class InMemorySharedTimerScheduler {
14
+ constructor(options = {}) {
15
+ var _a;
16
+ this.tasks = new Map();
17
+ this.schedule = (key, delayMs, task) => {
18
+ const safeDelayMs = Math.max(0, delayMs || 0);
19
+ this.tasks.set(key, {
20
+ key,
21
+ dueAt: this.now() + safeDelayMs,
22
+ task,
23
+ });
24
+ this.rearm();
25
+ };
26
+ this.cancel = (key) => {
27
+ this.tasks.delete(key);
28
+ this.rearm();
29
+ };
30
+ this.cancelAll = (prefix) => {
31
+ if (!prefix) {
32
+ this.tasks.clear();
33
+ this.rearm();
34
+ return;
35
+ }
36
+ [...this.tasks.keys()].forEach(key => {
37
+ if (key.startsWith(prefix))
38
+ this.tasks.delete(key);
39
+ });
40
+ this.rearm();
41
+ };
42
+ this.size = () => this.tasks.size;
43
+ this.rearm = () => {
44
+ if (this.timer)
45
+ clearTimeout(this.timer);
46
+ this.timer = undefined;
47
+ const next = this.getNextTask();
48
+ if (!next)
49
+ return;
50
+ const delayMs = Math.max(0, next.dueAt - this.now());
51
+ this.timer = setTimeout(() => void this.flush(), delayMs);
52
+ };
53
+ this.getNextTask = () => {
54
+ let next;
55
+ this.tasks.forEach(task => {
56
+ if (!next || task.dueAt < next.dueAt)
57
+ next = task;
58
+ });
59
+ return next;
60
+ };
61
+ this.flush = () => __awaiter(this, void 0, void 0, function* () {
62
+ this.timer = undefined;
63
+ const now = this.now();
64
+ const due = [...this.tasks.values()].filter(task => task.dueAt <= now);
65
+ due.forEach(task => this.tasks.delete(task.key));
66
+ this.rearm();
67
+ yield Promise.all(due.map(task => Promise.resolve(task.task()).catch(() => undefined)));
68
+ });
69
+ this.now = (_a = options.now) !== null && _a !== void 0 ? _a : (() => Date.now());
70
+ }
71
+ }
72
+ exports.InMemorySharedTimerScheduler = InMemorySharedTimerScheduler;
@@ -0,0 +1,32 @@
1
+ import { AutoReconnectController } from './reconnect-controller';
2
+ import { ConnectionRotationController } from './connection-rotation-controller';
3
+ import { KeepAliveLoop } from './keep-alive-loop';
4
+ import type { ClientSocketRuntime, ClientSocketV2, DomainSyncPlan, KeepAliveLoopControl, ReconnectController, SharedTimerScheduler, SyncScheduler, SyncTargetDescriptor } from './types';
5
+ export interface ClientSocketRuntimeOptions {
6
+ client: ClientSocketV2;
7
+ scheduler?: SyncScheduler;
8
+ keepAlive?: KeepAliveLoopControl;
9
+ reconnect?: ReconnectController;
10
+ rotation?: ConnectionRotationController;
11
+ timerScheduler?: SharedTimerScheduler;
12
+ syncPlans?: DomainSyncPlan<any>[];
13
+ keepAliveOptions?: Omit<ConstructorParameters<typeof KeepAliveLoop>[0], 'client' | 'timerScheduler'>;
14
+ reconnectOptions?: Omit<ConstructorParameters<typeof AutoReconnectController>[0], 'client'>;
15
+ rotationOptions?: Omit<ConstructorParameters<typeof ConnectionRotationController>[0], 'client' | 'reconnect'>;
16
+ }
17
+ export declare class SocketRuntime implements ClientSocketRuntime {
18
+ private readonly options;
19
+ readonly scheduler: SyncScheduler;
20
+ readonly keepAlive: KeepAliveLoopControl;
21
+ readonly reconnect: ReconnectController;
22
+ readonly rotation: ConnectionRotationController;
23
+ readonly timerScheduler: SharedTimerScheduler;
24
+ constructor(options: ClientSocketRuntimeOptions);
25
+ start: () => Promise<void>;
26
+ stop: () => Promise<void>;
27
+ startSync: (target: SyncTargetDescriptor) => void;
28
+ stopSync: (target: SyncTargetDescriptor) => void;
29
+ stopAllSync: () => void;
30
+ listSyncTargets: () => SyncTargetDescriptor[];
31
+ updateLocalSnapshot: (target: SyncTargetDescriptor, snapshot: unknown) => void;
32
+ }