@camera.ui/transport 0.0.16 → 0.0.18

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.
@@ -1,3 +1,4 @@
1
+ import { Logger } from '@camera.ui/logger';
1
2
  import { AxiosInstance, AxiosRequestConfig } from 'axios';
2
3
  import { TransportSpec } from '../core/types.js';
3
4
  import { Transport } from './contract.js';
@@ -6,6 +7,7 @@ export interface HttpTransportOptions {
6
7
  readonly timeoutMs?: number;
7
8
  readonly targetWaitMs?: number;
8
9
  readonly spec?: Partial<TransportSpec>;
10
+ readonly logger?: Logger;
9
11
  }
10
12
  export interface HttpTransport extends Transport {
11
13
  readonly client: AxiosInstance;
@@ -13,6 +13,7 @@ function createHttpTransport(options = {}) {
13
13
  };
14
14
  const apiPrefix = options.apiPrefix ?? "/api";
15
15
  const targetWaitMs = options.targetWaitMs ?? 15e3;
16
+ const logger = options.logger;
16
17
  const emitter = new TransportEmitter();
17
18
  let currentTarget = null;
18
19
  let status = { up: false };
@@ -63,6 +64,7 @@ function createHttpTransport(options = {}) {
63
64
  client.interceptors.response.use((response) => {
64
65
  if (!status.up) {
65
66
  status = { up: true };
67
+ logger?.debug("markUp");
66
68
  emitter.emit("up", void 0);
67
69
  }
68
70
  return response;
@@ -72,10 +74,13 @@ function createHttpTransport(options = {}) {
72
74
  markDown(error.message ?? "network");
73
75
  return Promise.reject(error);
74
76
  }
75
- if (error.response.status === 401) emitter.emit("auth-error", {
76
- status: 401,
77
- message: extractMessage(error)
78
- });
77
+ if (error.response.status === 401) {
78
+ logger?.debug(`auth-error 401 (${error.config?.url ?? "unknown-url"})`);
79
+ emitter.emit("auth-error", {
80
+ status: 401,
81
+ message: extractMessage(error)
82
+ });
83
+ }
79
84
  return Promise.reject(error);
80
85
  });
81
86
  function markDown(reason) {
@@ -84,6 +89,7 @@ function createHttpTransport(options = {}) {
84
89
  up: false,
85
90
  lastError: reason
86
91
  };
92
+ logger?.debug(`markDown (${reason})`);
87
93
  emitter.emit("down", { reason });
88
94
  }
89
95
  }
@@ -1,3 +1,4 @@
1
+ import { Logger } from '@camera.ui/logger';
1
2
  import { RPCClient } from '@camera.ui/rpc';
2
3
  import { TransportSpec } from '../core/types.js';
3
4
  import { Transport, Unsubscribe } from './contract.js';
@@ -11,9 +12,11 @@ export interface NatsTransportOptions {
11
12
  readonly reconnectionDelayMax?: number;
12
13
  readonly reconnectionRandomizationFactor?: number;
13
14
  readonly timeout?: number;
15
+ readonly connectTimeout?: number;
14
16
  readonly pingInterval?: number;
15
17
  readonly pingTimeout?: number;
16
18
  readonly maxPingOut?: number;
19
+ readonly logger?: Logger;
17
20
  }
18
21
  export type NatsClientListener = (client: RPCClient | null) => void;
19
22
  export interface NatsTransport extends Transport {
@@ -30,9 +30,11 @@ function createNatsTransport(options = {}) {
30
30
  const reconnectionDelayMax = options.reconnectionDelayMax ?? 5e3;
31
31
  const reconnectionRandomizationFactor = options.reconnectionRandomizationFactor ?? .5;
32
32
  const timeout = options.timeout ?? 1e4;
33
+ const connectTimeout = options.connectTimeout ?? 5e3;
33
34
  const pingInterval = options.pingInterval ?? 25e3;
34
35
  const pingTimeout = options.pingTimeout ?? 2e4;
35
36
  const maxPingOut = options.maxPingOut ?? 1;
37
+ const logger = options.logger;
36
38
  const emitter = new TransportEmitter();
37
39
  const clientListeners = /* @__PURE__ */ new Set();
38
40
  let proxy = null;
@@ -61,6 +63,7 @@ function createNatsTransport(options = {}) {
61
63
  function markUp() {
62
64
  if (status.up) return;
63
65
  status = { up: true };
66
+ logger?.debug("markUp");
64
67
  emitter.emit("up", void 0);
65
68
  }
66
69
  function markDown(reason) {
@@ -69,6 +72,7 @@ function createNatsTransport(options = {}) {
69
72
  up: false,
70
73
  lastError: reason
71
74
  };
75
+ logger?.debug(`markDown (${reason})`);
72
76
  emitter.emit("down", { reason });
73
77
  }
74
78
  function stopStatusMonitor() {
@@ -84,6 +88,7 @@ function createNatsTransport(options = {}) {
84
88
  if (!iter) return;
85
89
  for await (const event of iter) {
86
90
  if (abort.signal.aborted) break;
91
+ logger?.debug(`status: ${event.type}${"server" in event && event.server ? ` server=${String(event.server)}` : ""}`);
87
92
  switch (event.type) {
88
93
  case "reconnect":
89
94
  markUp();
@@ -114,6 +119,8 @@ function createNatsTransport(options = {}) {
114
119
  markDown(message);
115
120
  }
116
121
  async function rebuildClient(target, epoch) {
122
+ logger?.debug(`rebuildClient start (epoch=${epoch})`);
123
+ const t0 = Date.now();
117
124
  stopStatusMonitor();
118
125
  pendingConnectAbort?.abort();
119
126
  if (proxy) {
@@ -138,6 +145,7 @@ function createNatsTransport(options = {}) {
138
145
  reconnectionDelayMax,
139
146
  reconnectionRandomizationFactor,
140
147
  timeout,
148
+ connectTimeout,
141
149
  pingInterval,
142
150
  pingTimeout,
143
151
  maxPingOut,
@@ -152,7 +160,9 @@ function createNatsTransport(options = {}) {
152
160
  next.abortClose();
153
161
  } catch {}
154
162
  if (disposed || epoch !== applyEpoch) return;
155
- markDown(err instanceof Error ? err.message : String(err));
163
+ const msg = err instanceof Error ? err.message : String(err);
164
+ logger?.debug(`rebuildClient connect FAILED after ${Date.now() - t0}ms: ${msg}`);
165
+ markDown(msg);
156
166
  throw err;
157
167
  } finally {
158
168
  if (pendingConnectAbort === connectAbort) pendingConnectAbort = null;
@@ -164,6 +174,7 @@ function createNatsTransport(options = {}) {
164
174
  return;
165
175
  }
166
176
  proxy = next;
177
+ logger?.debug(`rebuildClient connected in ${Date.now() - t0}ms`);
167
178
  markUp();
168
179
  notifyClient();
169
180
  startStatusMonitor(next);
@@ -237,11 +248,24 @@ function createNatsTransport(options = {}) {
237
248
  }
238
249
  async function probeAlive(timeoutMs = 5e3) {
239
250
  if (!proxy) throw new Error("nats-transport: no client");
240
- await proxy.flush(timeoutMs);
251
+ const t0 = Date.now();
252
+ try {
253
+ await proxy.flush(timeoutMs);
254
+ logger?.debug(`probeAlive OK in ${Date.now() - t0}ms`);
255
+ } catch (err) {
256
+ logger?.debug(`probeAlive FAILED after ${Date.now() - t0}ms: ${err instanceof Error ? err.message : String(err)}`);
257
+ throw err;
258
+ }
241
259
  }
242
260
  async function forceReconnect() {
243
- if (!proxy) return;
261
+ if (!proxy) {
262
+ logger?.debug("forceReconnect: no client");
263
+ return;
264
+ }
265
+ logger?.debug(`forceReconnect: issuing (up=${status.up} stale=${proxy.isStale})`);
266
+ const t0 = Date.now();
244
267
  await proxy.forceReconnect();
268
+ logger?.debug(`forceReconnect: returned after ${Date.now() - t0}ms (up=${status.up})`);
245
269
  }
246
270
  return {
247
271
  spec,
@@ -1,4 +1,5 @@
1
1
  import { Manager, Socket } from 'socket.io-client';
2
+ import { Logger } from '@camera.ui/logger';
2
3
  import { TransportSpec } from '../core/types.js';
3
4
  import { Transport } from './contract.js';
4
5
  export type { Socket };
@@ -10,6 +11,7 @@ export interface SocketioTransportOptions {
10
11
  readonly reconnectionDelay?: number;
11
12
  readonly reconnectionDelayMax?: number;
12
13
  readonly timeout?: number;
14
+ readonly logger?: Logger;
13
15
  }
14
16
  export interface SocketioTransport extends Transport {
15
17
  readonly manager: Manager | null;
@@ -18,6 +18,7 @@ function createSocketioTransport(options = {}) {
18
18
  const reconnectionDelay = options.reconnectionDelay ?? 1e3;
19
19
  const reconnectionDelayMax = options.reconnectionDelayMax ?? 5e3;
20
20
  const timeout = options.timeout ?? 2e4;
21
+ const logger = options.logger;
21
22
  const emitter = new TransportEmitter();
22
23
  const sockets = /* @__PURE__ */ new Map();
23
24
  let manager = null;
@@ -37,6 +38,7 @@ function createSocketioTransport(options = {}) {
37
38
  function markUp() {
38
39
  if (status.up) return;
39
40
  status = { up: true };
41
+ logger?.debug("markUp");
40
42
  emitter.emit("up", void 0);
41
43
  }
42
44
  function markDown(reason) {
@@ -45,6 +47,7 @@ function createSocketioTransport(options = {}) {
45
47
  up: false,
46
48
  lastError: reason
47
49
  };
50
+ logger?.debug(`markDown (${reason})`);
48
51
  emitter.emit("down", { reason });
49
52
  }
50
53
  function isAuthError(msg) {
@@ -73,6 +76,7 @@ function createSocketioTransport(options = {}) {
73
76
  socket.on("disconnect", (reason) => markDown(reason));
74
77
  socket.on("connect_error", (err) => {
75
78
  const msg = err?.message ?? "connect_error";
79
+ logger?.debug(`connect_error (main): ${msg}`);
76
80
  if (isAuthError(msg)) {
77
81
  emitter.emit("auth-error", { message: msg });
78
82
  return;
@@ -179,10 +183,13 @@ function createSocketioTransport(options = {}) {
179
183
  function reviveDeadSockets() {
180
184
  if (!currentTarget) return;
181
185
  const auth = buildAuth(currentTarget);
186
+ let revived = 0;
182
187
  for (const sock of sockets.values()) if (!sock.connected) {
183
188
  sock.auth = auth;
184
189
  sock.connect();
190
+ revived++;
185
191
  }
192
+ logger?.debug(`reviveDeadSockets: ${revived}/${sockets.size} reconnecting`);
186
193
  }
187
194
  return {
188
195
  get spec() {
@@ -1,3 +1,4 @@
1
+ import { Logger } from '@camera.ui/logger';
1
2
  import { TransportSpec } from '../core/types.js';
2
3
  import { PerResourceTransport, Unsubscribe } from './contract.js';
3
4
  export interface WsHandleSpec {
@@ -27,6 +28,7 @@ export interface WsTransportOptions {
27
28
  readonly webSocketCtor?: typeof WebSocket;
28
29
  readonly tokenParam?: string;
29
30
  readonly sessionParam?: string;
31
+ readonly logger?: Logger;
30
32
  }
31
33
  export interface WsTransport extends PerResourceTransport<WsHandle, WsHandleSpec> {
32
34
  readonly handleCount: number;
@@ -16,6 +16,7 @@ function createWsTransport(options = {}) {
16
16
  const tokenParam = options.tokenParam ?? "token";
17
17
  const sessionParam = options.sessionParam ?? "session";
18
18
  const WsCtor = options.webSocketCtor ?? (typeof WebSocket !== "undefined" ? WebSocket : void 0);
19
+ const logger = options.logger;
19
20
  const emitter = new TransportEmitter();
20
21
  const handles = /* @__PURE__ */ new Set();
21
22
  const closeDelivered = /* @__PURE__ */ new WeakSet();
@@ -45,6 +46,7 @@ function createWsTransport(options = {}) {
45
46
  function bindWs(handle, ws) {
46
47
  ws.onopen = () => {
47
48
  if (handle.disposed || handle.ws !== ws) return;
49
+ logger?.debug(`open ${handle.spec.path}`);
48
50
  if (!status.up) {
49
51
  status = { up: true };
50
52
  emitter.emit("up", void 0);
@@ -60,6 +62,7 @@ function createWsTransport(options = {}) {
60
62
  if (closeDelivered.has(ws)) return;
61
63
  closeDelivered.add(ws);
62
64
  if (!isCurrent) return;
65
+ logger?.debug(`close ${handle.spec.path} code=${event.code} clean=${event.wasClean}${event.reason ? ` reason=${event.reason}` : ""}`);
63
66
  if (isAuthCloseEvent(event)) emitter.emit("auth-error", { message: event.reason || `ws close code ${event.code}` });
64
67
  const info = {
65
68
  code: event.code,
@@ -112,6 +115,7 @@ function createWsTransport(options = {}) {
112
115
  }
113
116
  function recycleAll(code, reason) {
114
117
  const snapshot = [...handles];
118
+ logger?.debug(`recycleAll (${reason}) handles=${snapshot.length}`);
115
119
  for (const handle of snapshot) closeWs(handle, code, reason);
116
120
  if (status.up) {
117
121
  status = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camera.ui/transport",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
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",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@camera.ui/logger": ">=0.0.3",
59
- "@camera.ui/rpc": ">=1.0.8",
59
+ "@camera.ui/rpc": ">=1.0.9",
60
60
  "axios": ">=1.18.1",
61
61
  "socket.io-client": ">=4.8.3"
62
62
  },