@camera.ui/transport 0.0.5 → 0.0.7
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 +1 -1
- package/dist/effects/reconnectWatchdog.d.ts +10 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +40 -1
- package/dist/transports/socketio.d.ts +1 -0
- package/dist/transports/socketio.js +48 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Kernel } from '../core/kernel.js';
|
|
2
|
+
export type Detach = () => void;
|
|
3
|
+
export interface ReconnectWatchdogOptions {
|
|
4
|
+
readonly kernel: Kernel;
|
|
5
|
+
readonly escalateAfterMs?: number;
|
|
6
|
+
readonly setTimer?: (cb: () => void, ms: number) => unknown;
|
|
7
|
+
readonly clearTimer?: (handle: unknown) => void;
|
|
8
|
+
readonly onEscalate?: (attempt: number) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function attachReconnectWatchdog(options: ReconnectWatchdogOptions): Detach;
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { attachNetworkChange } from './effects/networkChange.js';
|
|
|
7
7
|
export { attachPersistence, localStorageAdapter, memoryStorageAdapter } from './effects/persistence.js';
|
|
8
8
|
export { attachPresence, defaultOnNetworkOnline } from './effects/presence.js';
|
|
9
9
|
export { attachProbeLoop, isProbeFailure, makeProbeFailure } from './effects/probeLoop.js';
|
|
10
|
+
export { attachReconnectWatchdog } from './effects/reconnectWatchdog.js';
|
|
10
11
|
export { attachTokenLifecycle } from './effects/tokenLifecycle.js';
|
|
11
12
|
export { attachTransportSync } from './effects/transportSync.js';
|
|
12
13
|
export { attachTransportWatchdog } from './effects/transportWatchdog.js';
|
|
@@ -21,6 +22,7 @@ export type { NetworkChangeOptions, NetworkChangeSource } from './effects/networ
|
|
|
21
22
|
export type { PersistedTarget, Persistence, PersistenceOptions, StorageAdapter } from './effects/persistence.js';
|
|
22
23
|
export type { PresenceCallback, PresenceOptions, VisibilitySource } from './effects/presence.js';
|
|
23
24
|
export type { ProbeContext, ProbeFailure, ProbeFailureKind, ProbeLoopOptions } from './effects/probeLoop.js';
|
|
25
|
+
export type { ReconnectWatchdogOptions } from './effects/reconnectWatchdog.js';
|
|
24
26
|
export type { Detach, RefreshReason, TokenLifecycle, TokenLifecycleOptions } from './effects/tokenLifecycle.js';
|
|
25
27
|
export type { TransportSyncOptions } from './effects/transportSync.js';
|
|
26
28
|
export type { TransportWatchdogOptions, WatchdogClearReason } from './effects/transportWatchdog.js';
|
package/dist/index.js
CHANGED
|
@@ -717,6 +717,45 @@ function attachProbeLoop(options) {
|
|
|
717
717
|
};
|
|
718
718
|
}
|
|
719
719
|
//#endregion
|
|
720
|
+
//#region src/effects/reconnectWatchdog.ts
|
|
721
|
+
var DEFAULT_ESCALATE_AFTER_MS = 12e3;
|
|
722
|
+
function attachReconnectWatchdog(options) {
|
|
723
|
+
const escalateAfterMs = options.escalateAfterMs ?? DEFAULT_ESCALATE_AFTER_MS;
|
|
724
|
+
const setTimer = options.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
725
|
+
const clearTimer = options.clearTimer ?? ((h) => clearTimeout(h));
|
|
726
|
+
let timer;
|
|
727
|
+
let attempt = 0;
|
|
728
|
+
let detached = false;
|
|
729
|
+
function cancelTimer() {
|
|
730
|
+
if (timer !== void 0) {
|
|
731
|
+
clearTimer(timer);
|
|
732
|
+
timer = void 0;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
function arm() {
|
|
736
|
+
cancelTimer();
|
|
737
|
+
timer = setTimer(() => {
|
|
738
|
+
timer = void 0;
|
|
739
|
+
if (detached) return;
|
|
740
|
+
if (options.kernel.phase.kind !== "reconnecting") return;
|
|
741
|
+
attempt += 1;
|
|
742
|
+
options.onEscalate?.(attempt);
|
|
743
|
+
options.kernel.dispatch({ type: "USER_RETRY" });
|
|
744
|
+
}, escalateAfterMs);
|
|
745
|
+
}
|
|
746
|
+
const unsubKernel = options.kernel.subscribe((next, prev) => {
|
|
747
|
+
if (next.kind === "reconnecting" && prev.kind !== "reconnecting") arm();
|
|
748
|
+
else if (next.kind !== "reconnecting" && prev.kind === "reconnecting") cancelTimer();
|
|
749
|
+
if (next.kind === "online" || next.kind === "idle") attempt = 0;
|
|
750
|
+
});
|
|
751
|
+
if (options.kernel.phase.kind === "reconnecting") arm();
|
|
752
|
+
return () => {
|
|
753
|
+
detached = true;
|
|
754
|
+
cancelTimer();
|
|
755
|
+
unsubKernel();
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
//#endregion
|
|
720
759
|
//#region src/effects/tokenLifecycle.ts
|
|
721
760
|
var DEFAULT_GRACE_MS$1 = 5e3;
|
|
722
761
|
var DEFAULT_MAX_TRANSIENT_RETRIES = 3;
|
|
@@ -1098,4 +1137,4 @@ function attachWorkerBridge(options) {
|
|
|
1098
1137
|
};
|
|
1099
1138
|
}
|
|
1100
1139
|
//#endregion
|
|
1101
|
-
export { DEFAULT_RACE_TIMEOUT_BY_MODE, RaceFirstError, TransportEmitter, attachBackoff, attachCrossTab, attachNetworkChange, attachPersistence, attachPresence, attachProbeLoop, attachTokenLifecycle, attachTransportSync, attachTransportWatchdog, attachWorkerBridge, createKernel, defaultOnNetworkOnline, endpointKey, isEndpointChange, isProbeFailure, isSameEndpoint, isSameTarget, isTokenOnlyChange, localStorageAdapter, makeProbeFailure, memoryStorageAdapter, raceFirst, reducer, sortByPriority };
|
|
1140
|
+
export { DEFAULT_RACE_TIMEOUT_BY_MODE, RaceFirstError, TransportEmitter, attachBackoff, attachCrossTab, attachNetworkChange, attachPersistence, attachPresence, attachProbeLoop, attachReconnectWatchdog, attachTokenLifecycle, attachTransportSync, attachTransportWatchdog, attachWorkerBridge, createKernel, defaultOnNetworkOnline, endpointKey, isEndpointChange, isProbeFailure, isSameEndpoint, isSameTarget, isTokenOnlyChange, localStorageAdapter, makeProbeFailure, memoryStorageAdapter, raceFirst, reducer, sortByPriority };
|
|
@@ -15,5 +15,6 @@ export interface SocketioTransport extends Transport {
|
|
|
15
15
|
readonly manager: Manager | null;
|
|
16
16
|
socket(namespace?: string): Socket | null;
|
|
17
17
|
ensureSocket(namespace: string): Socket | null;
|
|
18
|
+
reviveDeadSockets(): void;
|
|
18
19
|
}
|
|
19
20
|
export declare function createSocketioTransport(options?: SocketioTransportOptions): SocketioTransport;
|
|
@@ -47,17 +47,46 @@ function createSocketioTransport(options = {}) {
|
|
|
47
47
|
};
|
|
48
48
|
emitter.emit("down", { reason });
|
|
49
49
|
}
|
|
50
|
+
function isAuthError(msg) {
|
|
51
|
+
const m = msg.toLowerCase();
|
|
52
|
+
return m.includes("auth") || m.includes("unauthorized");
|
|
53
|
+
}
|
|
54
|
+
function bindCommonAuthEvents(socket) {
|
|
55
|
+
let rotatedOut = false;
|
|
56
|
+
socket.on("unauthenticated", () => {
|
|
57
|
+
const fresh = currentTarget ? buildAuth(currentTarget) : null;
|
|
58
|
+
const current = socket.auth?.token;
|
|
59
|
+
if (fresh && current !== fresh.token) {
|
|
60
|
+
socket.auth = fresh;
|
|
61
|
+
rotatedOut = true;
|
|
62
|
+
} else emitter.emit("auth-error", { message: "unauthenticated" });
|
|
63
|
+
});
|
|
64
|
+
socket.on("disconnect", () => {
|
|
65
|
+
if (rotatedOut) {
|
|
66
|
+
rotatedOut = false;
|
|
67
|
+
socket.connect();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
50
71
|
function bindMainSocketEvents(socket) {
|
|
51
72
|
socket.on("connect", () => markUp());
|
|
52
73
|
socket.on("disconnect", (reason) => markDown(reason));
|
|
53
74
|
socket.on("connect_error", (err) => {
|
|
54
75
|
const msg = err?.message ?? "connect_error";
|
|
55
|
-
if (
|
|
76
|
+
if (isAuthError(msg)) {
|
|
56
77
|
emitter.emit("auth-error", { message: msg });
|
|
57
78
|
return;
|
|
58
79
|
}
|
|
59
80
|
markDown(msg);
|
|
60
81
|
});
|
|
82
|
+
bindCommonAuthEvents(socket);
|
|
83
|
+
}
|
|
84
|
+
function bindSecondarySocketEvents(socket) {
|
|
85
|
+
socket.on("connect_error", (err) => {
|
|
86
|
+
const msg = err?.message ?? "connect_error";
|
|
87
|
+
if (isAuthError(msg)) emitter.emit("auth-error", { message: msg });
|
|
88
|
+
});
|
|
89
|
+
bindCommonAuthEvents(socket);
|
|
61
90
|
}
|
|
62
91
|
function openSocket(namespace, target) {
|
|
63
92
|
const sock = io(`${target.endpoint.url}${namespace}`, {
|
|
@@ -101,7 +130,10 @@ function createSocketioTransport(options = {}) {
|
|
|
101
130
|
}
|
|
102
131
|
function rebindAuth(target) {
|
|
103
132
|
const auth = buildAuth(target);
|
|
104
|
-
for (const sock of sockets.values())
|
|
133
|
+
for (const sock of sockets.values()) {
|
|
134
|
+
sock.auth = auth;
|
|
135
|
+
if (!sock.connected) sock.connect();
|
|
136
|
+
}
|
|
105
137
|
}
|
|
106
138
|
async function apply(target) {
|
|
107
139
|
if (disposed) throw new Error("socketio-transport disposed");
|
|
@@ -138,9 +170,20 @@ function createSocketioTransport(options = {}) {
|
|
|
138
170
|
function ensureSocket(namespace) {
|
|
139
171
|
if (!currentTarget) return null;
|
|
140
172
|
let sock = sockets.get(namespace);
|
|
141
|
-
if (!sock)
|
|
173
|
+
if (!sock) {
|
|
174
|
+
sock = openSocket(namespace, currentTarget);
|
|
175
|
+
bindSecondarySocketEvents(sock);
|
|
176
|
+
}
|
|
142
177
|
return sock;
|
|
143
178
|
}
|
|
179
|
+
function reviveDeadSockets() {
|
|
180
|
+
if (!currentTarget) return;
|
|
181
|
+
const auth = buildAuth(currentTarget);
|
|
182
|
+
for (const sock of sockets.values()) if (!sock.connected) {
|
|
183
|
+
sock.auth = auth;
|
|
184
|
+
sock.connect();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
144
187
|
return {
|
|
145
188
|
get spec() {
|
|
146
189
|
return spec;
|
|
@@ -153,7 +196,8 @@ function createSocketioTransport(options = {}) {
|
|
|
153
196
|
on,
|
|
154
197
|
dispose,
|
|
155
198
|
socket,
|
|
156
|
-
ensureSocket
|
|
199
|
+
ensureSocket,
|
|
200
|
+
reviveDeadSockets
|
|
157
201
|
};
|
|
158
202
|
}
|
|
159
203
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camera.ui/transport",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "camera.ui transport layer — framework-agnostic connection kernel, reducer state, pluggable transports (HTTP/WS/Socket.IO/NATS), lifecycle effects and worker bridge",
|
|
5
5
|
"author": "seydx (https://github.com/cameraui/clients)",
|
|
6
6
|
"type": "module",
|
|
@@ -57,21 +57,21 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@camera.ui/rpc": ">=1.0.4",
|
|
60
|
-
"axios": ">=1.18.
|
|
60
|
+
"axios": ">=1.18.1",
|
|
61
61
|
"socket.io-client": ">=4.8.3"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@eslint/js": "9.39.4",
|
|
65
65
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
66
|
-
"@typescript-eslint/parser": "^8.
|
|
66
|
+
"@typescript-eslint/parser": "^8.62.0",
|
|
67
67
|
"@vue/language-core": "^3.3.5",
|
|
68
68
|
"eslint": "9.39.2",
|
|
69
|
-
"globals": "^17.
|
|
69
|
+
"globals": "^17.7.0",
|
|
70
70
|
"jiti": "^2.7.0",
|
|
71
71
|
"prettier": "^3.8.4",
|
|
72
72
|
"rimraf": "^6.1.3",
|
|
73
73
|
"typescript": "5.9.3",
|
|
74
|
-
"typescript-eslint": "^8.
|
|
74
|
+
"typescript-eslint": "^8.62.0",
|
|
75
75
|
"unplugin-dts": "^1.0.2",
|
|
76
76
|
"updates": "^17.18.0",
|
|
77
77
|
"vite": "^8.0.16",
|