uidex 0.5.2 → 0.7.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 +3 -3
- package/dist/cli/cli.cjs +1542 -1227
- package/dist/cli/cli.cjs.map +1 -1
- package/dist/cloud/index.cjs +385 -175
- package/dist/cloud/index.cjs.map +1 -1
- package/dist/cloud/index.d.cts +192 -4
- package/dist/cloud/index.d.ts +192 -4
- package/dist/cloud/index.js +377 -177
- package/dist/cloud/index.js.map +1 -1
- package/dist/headless/index.cjs +116 -251
- package/dist/headless/index.cjs.map +1 -1
- package/dist/headless/index.d.cts +6 -11
- package/dist/headless/index.d.ts +6 -11
- package/dist/headless/index.js +116 -253
- package/dist/headless/index.js.map +1 -1
- package/dist/index.cjs +776 -1055
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +152 -160
- package/dist/index.d.ts +152 -160
- package/dist/index.js +792 -1066
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +801 -1019
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +102 -86
- package/dist/react/index.d.ts +102 -86
- package/dist/react/index.js +821 -1038
- package/dist/react/index.js.map +1 -1
- package/dist/scan/index.cjs +1550 -1220
- package/dist/scan/index.cjs.map +1 -1
- package/dist/scan/index.d.cts +210 -12
- package/dist/scan/index.d.ts +210 -12
- package/dist/scan/index.js +1547 -1219
- package/dist/scan/index.js.map +1 -1
- package/package.json +22 -21
- package/templates/claude/SKILL.md +71 -0
- package/templates/claude/references/audit.md +43 -0
- package/templates/claude/{rules.md → references/conventions.md} +25 -28
- package/templates/claude/audit.md +0 -43
- /package/templates/claude/{api.md → references/api.md} +0 -0
package/dist/cloud/index.cjs
CHANGED
|
@@ -22,96 +22,61 @@ var cloud_exports = {};
|
|
|
22
22
|
__export(cloud_exports, {
|
|
23
23
|
CloudError: () => CloudError,
|
|
24
24
|
DEFAULT_CLOUD_ENDPOINT: () => DEFAULT_CLOUD_ENDPOINT,
|
|
25
|
+
DEFAULT_RPC_TIMEOUT_MS: () => DEFAULT_RPC_TIMEOUT_MS,
|
|
26
|
+
RPC_METHODS: () => RPC_METHODS,
|
|
27
|
+
SCREENSHOT_CHUNK_BYTES: () => SCREENSHOT_CHUNK_BYTES,
|
|
28
|
+
SCREENSHOT_INLINE_MAX_BYTES: () => SCREENSHOT_INLINE_MAX_BYTES,
|
|
25
29
|
cloud: () => cloud,
|
|
26
|
-
createRealtimeChannel: () => createRealtimeChannel
|
|
30
|
+
createRealtimeChannel: () => createRealtimeChannel,
|
|
31
|
+
createRpcClient: () => createRpcClient,
|
|
32
|
+
isRpcMethod: () => isRpcMethod,
|
|
33
|
+
parseRpcResponseFrame: () => parseRpcResponseFrame
|
|
27
34
|
});
|
|
28
35
|
module.exports = __toCommonJS(cloud_exports);
|
|
29
36
|
|
|
30
|
-
// src/cloud/
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
url: "/api/ingest/config",
|
|
67
|
-
...options
|
|
68
|
-
});
|
|
69
|
-
};
|
|
70
|
-
var listIngestReports = (options) => {
|
|
71
|
-
return (options?.client ?? client).get({
|
|
72
|
-
security: [
|
|
73
|
-
{
|
|
74
|
-
scheme: "bearer",
|
|
75
|
-
type: "http"
|
|
76
|
-
}
|
|
77
|
-
],
|
|
78
|
-
url: "/api/ingest/reports",
|
|
79
|
-
...options
|
|
80
|
-
});
|
|
81
|
-
};
|
|
82
|
-
var listPins = (options) => {
|
|
83
|
-
return (options?.client ?? client).get({
|
|
84
|
-
security: [
|
|
85
|
-
{
|
|
86
|
-
scheme: "bearer",
|
|
87
|
-
type: "http"
|
|
88
|
-
}
|
|
89
|
-
],
|
|
90
|
-
url: "/api/ingest/pins",
|
|
91
|
-
...options
|
|
92
|
-
});
|
|
93
|
-
};
|
|
94
|
-
var archivePin = (options) => {
|
|
95
|
-
return (options.client ?? client).post({
|
|
96
|
-
security: [
|
|
97
|
-
{
|
|
98
|
-
scheme: "bearer",
|
|
99
|
-
type: "http"
|
|
100
|
-
}
|
|
101
|
-
],
|
|
102
|
-
url: "/api/ingest/pins/archive",
|
|
103
|
-
...options,
|
|
104
|
-
headers: {
|
|
105
|
-
"Content-Type": "application/json",
|
|
106
|
-
...options?.headers
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
};
|
|
37
|
+
// src/cloud/protocol.ts
|
|
38
|
+
var RPC_METHODS = [
|
|
39
|
+
"reports.submit",
|
|
40
|
+
"reports.list",
|
|
41
|
+
"config.get",
|
|
42
|
+
"pins.list",
|
|
43
|
+
"pins.screenshot",
|
|
44
|
+
"pins.archive",
|
|
45
|
+
"screenshot.chunk"
|
|
46
|
+
];
|
|
47
|
+
var SCREENSHOT_INLINE_MAX_BYTES = 20 * 1024 * 1024;
|
|
48
|
+
var SCREENSHOT_CHUNK_BYTES = 8 * 1024 * 1024;
|
|
49
|
+
function isRpcMethod(value) {
|
|
50
|
+
return typeof value === "string" && RPC_METHODS.includes(value);
|
|
51
|
+
}
|
|
52
|
+
function parseRpcResponseFrame(msg) {
|
|
53
|
+
if (!msg || typeof msg !== "object") return null;
|
|
54
|
+
const m = msg;
|
|
55
|
+
if (m.type !== "rpc:res" || typeof m.id !== "string") return null;
|
|
56
|
+
if (m.ok === true) {
|
|
57
|
+
return { type: "rpc:res", id: m.id, ok: true, data: m.data };
|
|
58
|
+
}
|
|
59
|
+
if (m.ok === false) {
|
|
60
|
+
if (typeof m.status !== "number" || typeof m.error !== "string") return null;
|
|
61
|
+
return {
|
|
62
|
+
type: "rpc:res",
|
|
63
|
+
id: m.id,
|
|
64
|
+
ok: false,
|
|
65
|
+
status: m.status,
|
|
66
|
+
error: m.error,
|
|
67
|
+
retryAfter: typeof m.retryAfter === "number" ? m.retryAfter : void 0,
|
|
68
|
+
details: m.details
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
110
73
|
|
|
111
74
|
// src/cloud/realtime.ts
|
|
112
75
|
var RECONNECT_INITIAL_MS = 1e3;
|
|
113
76
|
var RECONNECT_MAX_MS = 3e4;
|
|
77
|
+
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
114
78
|
var CLOSE_CODE_AUTH_FAILED = 4001;
|
|
79
|
+
var CLOSE_CODE_SYNTHETIC = 1006;
|
|
115
80
|
function emit(listeners, value) {
|
|
116
81
|
for (const cb of listeners) {
|
|
117
82
|
try {
|
|
@@ -130,14 +95,28 @@ function resolveWebSocket(override) {
|
|
|
130
95
|
);
|
|
131
96
|
}
|
|
132
97
|
function createRealtimeChannel(options) {
|
|
133
|
-
|
|
98
|
+
let WS = options.WebSocketImpl ?? null;
|
|
134
99
|
const presenceListeners = /* @__PURE__ */ new Set();
|
|
135
100
|
const pinListeners = /* @__PURE__ */ new Set();
|
|
101
|
+
const pinArchivedListeners = /* @__PURE__ */ new Set();
|
|
102
|
+
const openListeners = /* @__PURE__ */ new Set();
|
|
103
|
+
const closeListeners = /* @__PURE__ */ new Set();
|
|
104
|
+
const rpcListeners = /* @__PURE__ */ new Set();
|
|
136
105
|
let ws = null;
|
|
137
106
|
let state = "disconnected";
|
|
138
107
|
let disposed = false;
|
|
139
108
|
let reconnectAttempts = 0;
|
|
140
109
|
let reconnectTimer = null;
|
|
110
|
+
let identity = null;
|
|
111
|
+
let route = null;
|
|
112
|
+
function emitClose(code, willReconnect) {
|
|
113
|
+
for (const cb of closeListeners) {
|
|
114
|
+
try {
|
|
115
|
+
cb(code, willReconnect);
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
141
120
|
function clearReconnectTimer() {
|
|
142
121
|
if (reconnectTimer !== null) {
|
|
143
122
|
clearTimeout(reconnectTimer);
|
|
@@ -168,6 +147,11 @@ function createRealtimeChannel(options) {
|
|
|
168
147
|
}
|
|
169
148
|
if (!parsed || typeof parsed !== "object") return;
|
|
170
149
|
const msg = parsed;
|
|
150
|
+
if (msg.type === "rpc:res") {
|
|
151
|
+
const frame = parseRpcResponseFrame(parsed);
|
|
152
|
+
if (frame) emit(rpcListeners, frame);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
171
155
|
if (msg.type === "presence") {
|
|
172
156
|
const p = msg;
|
|
173
157
|
if (!Array.isArray(p.users)) return;
|
|
@@ -185,15 +169,21 @@ function createRealtimeChannel(options) {
|
|
|
185
169
|
emit(presenceListeners, users);
|
|
186
170
|
return;
|
|
187
171
|
}
|
|
172
|
+
if (msg.type === "pin:archived") {
|
|
173
|
+
const p = msg;
|
|
174
|
+
if (typeof p.reportId !== "string") return;
|
|
175
|
+
emit(pinArchivedListeners, p.reportId);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
188
178
|
if (msg.type === "pin") {
|
|
189
179
|
const p = msg;
|
|
190
|
-
if (typeof p.
|
|
180
|
+
if (typeof p.reportId !== "string" || !p.elementRef || typeof p.elementRef !== "object" || typeof p.elementRef.kind !== "string" || typeof p.elementRef.id !== "string" || !p.author || typeof p.author !== "object" || typeof p.body !== "string" || typeof p.reportType !== "string" || typeof p.reportSeverity !== "string" || typeof p.createdAt !== "string") {
|
|
191
181
|
return;
|
|
192
182
|
}
|
|
193
183
|
const author = p.author;
|
|
194
184
|
const elRef = p.elementRef;
|
|
195
185
|
const pin = {
|
|
196
|
-
id: p.
|
|
186
|
+
id: p.reportId,
|
|
197
187
|
entity: `${elRef.kind}:${elRef.id}`,
|
|
198
188
|
reporter: {
|
|
199
189
|
name: typeof author.name === "string" ? author.name : void 0,
|
|
@@ -210,20 +200,45 @@ function createRealtimeChannel(options) {
|
|
|
210
200
|
return;
|
|
211
201
|
}
|
|
212
202
|
}
|
|
203
|
+
function sendRaw(frame) {
|
|
204
|
+
if (!ws || !WS || ws.readyState !== WS.OPEN) return false;
|
|
205
|
+
try {
|
|
206
|
+
ws.send(JSON.stringify(frame));
|
|
207
|
+
return true;
|
|
208
|
+
} catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
213
212
|
function openSocket() {
|
|
214
213
|
if (disposed) return;
|
|
215
214
|
state = "connecting";
|
|
215
|
+
if (!WS) {
|
|
216
|
+
try {
|
|
217
|
+
WS = resolveWebSocket(options.WebSocketImpl);
|
|
218
|
+
} catch {
|
|
219
|
+
state = "disconnected";
|
|
220
|
+
emitClose(CLOSE_CODE_SYNTHETIC, false);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
216
224
|
let url;
|
|
217
225
|
try {
|
|
218
226
|
url = options.buildUrl();
|
|
219
227
|
} catch {
|
|
220
228
|
state = "disconnected";
|
|
229
|
+
emitClose(CLOSE_CODE_SYNTHETIC, false);
|
|
221
230
|
return;
|
|
222
231
|
}
|
|
223
232
|
let socket;
|
|
224
233
|
try {
|
|
225
234
|
socket = new WS(url);
|
|
226
235
|
} catch {
|
|
236
|
+
if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
237
|
+
state = "disconnected";
|
|
238
|
+
emitClose(CLOSE_CODE_SYNTHETIC, false);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
emitClose(CLOSE_CODE_SYNTHETIC, true);
|
|
227
242
|
scheduleReconnect();
|
|
228
243
|
return;
|
|
229
244
|
}
|
|
@@ -232,6 +247,18 @@ function createRealtimeChannel(options) {
|
|
|
232
247
|
if (ws !== socket) return;
|
|
233
248
|
state = "connected";
|
|
234
249
|
reconnectAttempts = 0;
|
|
250
|
+
if (identity) {
|
|
251
|
+
sendRaw({
|
|
252
|
+
type: "identify",
|
|
253
|
+
userId: identity.id,
|
|
254
|
+
...identity.name ? { name: identity.name } : {},
|
|
255
|
+
...identity.avatar ? { avatar: identity.avatar } : {}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
if (route !== null) {
|
|
259
|
+
sendRaw({ type: "join", route });
|
|
260
|
+
}
|
|
261
|
+
emit(openListeners, void 0);
|
|
235
262
|
});
|
|
236
263
|
socket.addEventListener("message", (event) => {
|
|
237
264
|
if (ws !== socket) return;
|
|
@@ -241,10 +268,12 @@ function createRealtimeChannel(options) {
|
|
|
241
268
|
if (ws !== socket) return;
|
|
242
269
|
ws = null;
|
|
243
270
|
const code = event.code;
|
|
244
|
-
if (disposed || code === CLOSE_CODE_AUTH_FAILED) {
|
|
271
|
+
if (disposed || code === CLOSE_CODE_AUTH_FAILED || reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
245
272
|
state = "disconnected";
|
|
273
|
+
emitClose(code, false);
|
|
246
274
|
return;
|
|
247
275
|
}
|
|
276
|
+
emitClose(code, true);
|
|
248
277
|
scheduleReconnect();
|
|
249
278
|
});
|
|
250
279
|
socket.addEventListener("error", () => {
|
|
@@ -263,22 +292,35 @@ function createRealtimeChannel(options) {
|
|
|
263
292
|
disposed = true;
|
|
264
293
|
clearReconnectTimer();
|
|
265
294
|
state = "disconnected";
|
|
295
|
+
identity = null;
|
|
296
|
+
route = null;
|
|
266
297
|
if (ws) {
|
|
298
|
+
const socket = ws;
|
|
299
|
+
ws = null;
|
|
267
300
|
try {
|
|
268
|
-
|
|
301
|
+
socket.close(1e3);
|
|
269
302
|
} catch {
|
|
270
303
|
}
|
|
271
|
-
ws = null;
|
|
272
304
|
}
|
|
305
|
+
emitClose(CLOSE_CODE_SYNTHETIC, false);
|
|
273
306
|
}
|
|
274
|
-
function
|
|
307
|
+
function clearSession() {
|
|
308
|
+
identity = null;
|
|
309
|
+
route = null;
|
|
310
|
+
}
|
|
311
|
+
function identify(user) {
|
|
312
|
+
identity = user;
|
|
313
|
+
sendRaw({
|
|
314
|
+
type: "identify",
|
|
315
|
+
userId: user.id,
|
|
316
|
+
...user.name ? { name: user.name } : {},
|
|
317
|
+
...user.avatar ? { avatar: user.avatar } : {}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function joinRoute(nextRoute) {
|
|
321
|
+
route = nextRoute;
|
|
275
322
|
emit(presenceListeners, []);
|
|
276
|
-
|
|
277
|
-
try {
|
|
278
|
-
ws.send(JSON.stringify({ type: "join", route }));
|
|
279
|
-
} catch {
|
|
280
|
-
}
|
|
281
|
-
}
|
|
323
|
+
sendRaw({ type: "join", route: nextRoute });
|
|
282
324
|
}
|
|
283
325
|
function onPresence(cb) {
|
|
284
326
|
presenceListeners.add(cb);
|
|
@@ -292,15 +334,46 @@ function createRealtimeChannel(options) {
|
|
|
292
334
|
pinListeners.delete(cb);
|
|
293
335
|
};
|
|
294
336
|
}
|
|
337
|
+
function onPinArchived(cb) {
|
|
338
|
+
pinArchivedListeners.add(cb);
|
|
339
|
+
return () => {
|
|
340
|
+
pinArchivedListeners.delete(cb);
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function onOpen(cb) {
|
|
344
|
+
openListeners.add(cb);
|
|
345
|
+
return () => {
|
|
346
|
+
openListeners.delete(cb);
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function onClose(cb) {
|
|
350
|
+
closeListeners.add(cb);
|
|
351
|
+
return () => {
|
|
352
|
+
closeListeners.delete(cb);
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function onRpcResponse(cb) {
|
|
356
|
+
rpcListeners.add(cb);
|
|
357
|
+
return () => {
|
|
358
|
+
rpcListeners.delete(cb);
|
|
359
|
+
};
|
|
360
|
+
}
|
|
295
361
|
return {
|
|
296
362
|
get state() {
|
|
297
363
|
return state;
|
|
298
364
|
},
|
|
299
365
|
connect,
|
|
300
366
|
disconnect,
|
|
367
|
+
identify,
|
|
368
|
+
clearSession,
|
|
301
369
|
joinRoute,
|
|
370
|
+
sendFrame: sendRaw,
|
|
302
371
|
onPresence,
|
|
303
|
-
onPin
|
|
372
|
+
onPin,
|
|
373
|
+
onPinArchived,
|
|
374
|
+
onOpen,
|
|
375
|
+
onClose,
|
|
376
|
+
onRpcResponse
|
|
304
377
|
};
|
|
305
378
|
}
|
|
306
379
|
|
|
@@ -319,29 +392,111 @@ var CloudError = class extends Error {
|
|
|
319
392
|
}
|
|
320
393
|
};
|
|
321
394
|
|
|
395
|
+
// src/cloud/rpc.ts
|
|
396
|
+
var DEFAULT_RPC_TIMEOUT_MS = 3e4;
|
|
397
|
+
var SUBMIT_RPC_TIMEOUT_MS = 12e4;
|
|
398
|
+
function authFailedError() {
|
|
399
|
+
return new CloudError("WebSocket authentication failed", { status: 401 });
|
|
400
|
+
}
|
|
401
|
+
function createRpcClient(channel, options) {
|
|
402
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_RPC_TIMEOUT_MS;
|
|
403
|
+
const idSuffix = Math.random().toString(36).slice(2, 8);
|
|
404
|
+
let idCounter = 0;
|
|
405
|
+
const pending = /* @__PURE__ */ new Map();
|
|
406
|
+
const queue = [];
|
|
407
|
+
let terminalError = null;
|
|
408
|
+
function settlePending(err) {
|
|
409
|
+
for (const entry of pending.values()) {
|
|
410
|
+
clearTimeout(entry.timer);
|
|
411
|
+
entry.reject(err);
|
|
412
|
+
}
|
|
413
|
+
pending.clear();
|
|
414
|
+
}
|
|
415
|
+
channel.onOpen(() => {
|
|
416
|
+
while (queue.length > 0) {
|
|
417
|
+
const next = queue[0];
|
|
418
|
+
if (!channel.sendFrame(next.frame)) return;
|
|
419
|
+
queue.shift();
|
|
420
|
+
pending.set(next.frame.id, next.entry);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
function rejectQueue(err) {
|
|
424
|
+
for (const queued of queue) {
|
|
425
|
+
clearTimeout(queued.entry.timer);
|
|
426
|
+
queued.entry.reject(err);
|
|
427
|
+
}
|
|
428
|
+
queue.length = 0;
|
|
429
|
+
}
|
|
430
|
+
channel.onClose((code, willReconnect) => {
|
|
431
|
+
if (code === CLOSE_CODE_AUTH_FAILED) {
|
|
432
|
+
const err = authFailedError();
|
|
433
|
+
terminalError = err;
|
|
434
|
+
settlePending(err);
|
|
435
|
+
rejectQueue(err);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
settlePending(new CloudError("Connection lost", { status: 0 }));
|
|
439
|
+
if (!willReconnect) {
|
|
440
|
+
rejectQueue(new CloudError("Connection lost", { status: 0 }));
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
channel.onRpcResponse((frame) => {
|
|
444
|
+
const entry = pending.get(frame.id);
|
|
445
|
+
if (!entry) return;
|
|
446
|
+
pending.delete(frame.id);
|
|
447
|
+
clearTimeout(entry.timer);
|
|
448
|
+
if (frame.ok) {
|
|
449
|
+
entry.resolve(frame.data);
|
|
450
|
+
} else {
|
|
451
|
+
entry.reject(
|
|
452
|
+
new CloudError(frame.error, {
|
|
453
|
+
status: frame.status,
|
|
454
|
+
retryAfter: frame.retryAfter,
|
|
455
|
+
details: frame.details
|
|
456
|
+
})
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
function call(method, params, opts) {
|
|
461
|
+
if (terminalError) return Promise.reject(terminalError);
|
|
462
|
+
const callTimeoutMs = opts?.timeoutMs ?? timeoutMs;
|
|
463
|
+
return new Promise((resolve, reject) => {
|
|
464
|
+
idCounter += 1;
|
|
465
|
+
const id = `${idCounter.toString(36)}-${idSuffix}`;
|
|
466
|
+
const frame = {
|
|
467
|
+
type: "rpc",
|
|
468
|
+
id,
|
|
469
|
+
method,
|
|
470
|
+
...params !== void 0 ? { params } : {}
|
|
471
|
+
};
|
|
472
|
+
const timer = setTimeout(() => {
|
|
473
|
+
pending.delete(id);
|
|
474
|
+
const queuedIndex = queue.findIndex((q) => q.frame.id === id);
|
|
475
|
+
if (queuedIndex !== -1) queue.splice(queuedIndex, 1);
|
|
476
|
+
reject(new CloudError("Request timed out", { status: 0 }));
|
|
477
|
+
}, callTimeoutMs);
|
|
478
|
+
const entry = {
|
|
479
|
+
resolve,
|
|
480
|
+
reject,
|
|
481
|
+
timer
|
|
482
|
+
};
|
|
483
|
+
if (channel.sendFrame(frame)) {
|
|
484
|
+
pending.set(id, entry);
|
|
485
|
+
} else {
|
|
486
|
+
queue.push({ frame, entry });
|
|
487
|
+
channel.connect();
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
return { call };
|
|
492
|
+
}
|
|
493
|
+
|
|
322
494
|
// src/cloud/client.ts
|
|
323
495
|
function trimEndpoint(endpoint) {
|
|
324
496
|
return endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
|
|
325
497
|
}
|
|
326
|
-
function
|
|
327
|
-
|
|
328
|
-
const status = result.response.status;
|
|
329
|
-
const retryAfter = status === 429 ? parseRetryAfter(result.response.headers.get("Retry-After")) : void 0;
|
|
330
|
-
const msg = result.error && typeof result.error === "object" && "error" in result.error && typeof result.error.error === "string" ? result.error.error : `Request failed (${status})`;
|
|
331
|
-
throw new CloudError(msg, { status, retryAfter, details: result.error });
|
|
332
|
-
}
|
|
333
|
-
return result.data;
|
|
334
|
-
}
|
|
335
|
-
function parseRetryAfter(header) {
|
|
336
|
-
if (!header) return void 0;
|
|
337
|
-
const seconds = Number(header);
|
|
338
|
-
if (Number.isFinite(seconds) && seconds >= 0) return seconds;
|
|
339
|
-
const date = Date.parse(header);
|
|
340
|
-
if (Number.isFinite(date)) {
|
|
341
|
-
const delta = Math.ceil((date - Date.now()) / 1e3);
|
|
342
|
-
return delta > 0 ? delta : 0;
|
|
343
|
-
}
|
|
344
|
-
return void 0;
|
|
498
|
+
function nextUploadId() {
|
|
499
|
+
return `up-${crypto.randomUUID()}`;
|
|
345
500
|
}
|
|
346
501
|
function cloud(options) {
|
|
347
502
|
let cachedConfig = null;
|
|
@@ -352,13 +507,18 @@ function cloud(options) {
|
|
|
352
507
|
}
|
|
353
508
|
const endpoint = trimEndpoint(options.endpoint ?? DEFAULT_CLOUD_ENDPOINT);
|
|
354
509
|
const git = options.git;
|
|
355
|
-
|
|
356
|
-
(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
510
|
+
function buildWsUrl() {
|
|
511
|
+
const httpToWs = endpoint.replace(/^http:/, "ws:").replace(/^https:/, "wss:");
|
|
512
|
+
const params = new URLSearchParams();
|
|
513
|
+
params.set("key", projectKey);
|
|
514
|
+
return `${httpToWs}/ws?${params.toString()}`;
|
|
515
|
+
}
|
|
516
|
+
const channel = createRealtimeChannel({
|
|
517
|
+
buildUrl: buildWsUrl,
|
|
518
|
+
...options.WebSocketImpl ? { WebSocketImpl: options.WebSocketImpl } : {}
|
|
519
|
+
});
|
|
520
|
+
const rpc = createRpcClient(channel);
|
|
521
|
+
const eagerConnect = options.WebSocketImpl !== void 0 || typeof window !== "undefined" && typeof WebSocket === "function";
|
|
362
522
|
async function submit(payload) {
|
|
363
523
|
const enriched = git?.branch || git?.commit ? {
|
|
364
524
|
...payload,
|
|
@@ -370,103 +530,153 @@ function cloud(options) {
|
|
|
370
530
|
}
|
|
371
531
|
}
|
|
372
532
|
} : payload;
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
533
|
+
const screenshot = enriched.screenshot;
|
|
534
|
+
if (typeof screenshot === "string" && screenshot.length > SCREENSHOT_INLINE_MAX_BYTES) {
|
|
535
|
+
const uploadId = nextUploadId();
|
|
536
|
+
const total = Math.ceil(screenshot.length / SCREENSHOT_CHUNK_BYTES);
|
|
537
|
+
for (let seq = 0; seq < total; seq++) {
|
|
538
|
+
const data = screenshot.slice(
|
|
539
|
+
seq * SCREENSHOT_CHUNK_BYTES,
|
|
540
|
+
(seq + 1) * SCREENSHOT_CHUNK_BYTES
|
|
541
|
+
);
|
|
542
|
+
await rpc.call(
|
|
543
|
+
"screenshot.chunk",
|
|
544
|
+
{ uploadId, seq, total, data },
|
|
545
|
+
{ timeoutMs: SUBMIT_RPC_TIMEOUT_MS }
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
return rpc.call(
|
|
549
|
+
"reports.submit",
|
|
550
|
+
{ ...enriched, screenshot: void 0, screenshotUploadId: uploadId },
|
|
551
|
+
{ timeoutMs: SUBMIT_RPC_TIMEOUT_MS }
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
return rpc.call("reports.submit", enriched, {
|
|
555
|
+
timeoutMs: SUBMIT_RPC_TIMEOUT_MS
|
|
376
556
|
});
|
|
377
|
-
return unwrap(result);
|
|
378
|
-
}
|
|
379
|
-
async function fetchConfig() {
|
|
380
|
-
const result = await getIngestConfig({ client: apiClient });
|
|
381
|
-
return unwrap(result);
|
|
382
557
|
}
|
|
383
558
|
function startFetch() {
|
|
384
|
-
const promise =
|
|
559
|
+
const promise = rpc.call("config.get", void 0);
|
|
560
|
+
cachedConfig = promise;
|
|
385
561
|
promise.then(
|
|
386
562
|
(config) => {
|
|
387
563
|
resolvedConfig = config;
|
|
388
564
|
},
|
|
389
565
|
() => {
|
|
566
|
+
if (cachedConfig === promise) cachedConfig = null;
|
|
390
567
|
}
|
|
391
568
|
);
|
|
392
569
|
return promise;
|
|
393
570
|
}
|
|
394
|
-
|
|
571
|
+
if (eagerConnect) {
|
|
572
|
+
channel.connect();
|
|
573
|
+
startFetch();
|
|
574
|
+
}
|
|
395
575
|
function getConfig() {
|
|
396
|
-
return cachedConfig ??
|
|
576
|
+
return cachedConfig ?? startFetch();
|
|
397
577
|
}
|
|
398
578
|
function getCachedConfig() {
|
|
399
579
|
return resolvedConfig;
|
|
400
580
|
}
|
|
401
|
-
function realtimeUrl(route, user) {
|
|
402
|
-
const httpToWs = endpoint.replace(/^http:/, "ws:").replace(/^https:/, "wss:");
|
|
403
|
-
const params = new URLSearchParams();
|
|
404
|
-
params.set("key", projectKey);
|
|
405
|
-
params.set("route", route);
|
|
406
|
-
params.set("userId", user.id);
|
|
407
|
-
if (user.name) params.set("name", user.name);
|
|
408
|
-
if (user.avatar) params.set("avatar", user.avatar);
|
|
409
|
-
return `${httpToWs}/ws?${params.toString()}`;
|
|
410
|
-
}
|
|
411
581
|
function connectRealtime(opts) {
|
|
412
582
|
if (!opts || !opts.user || typeof opts.user.id !== "string") {
|
|
413
583
|
throw new TypeError("uidex/cloud: realtime.connect requires `user.id`");
|
|
414
584
|
}
|
|
415
585
|
let currentRoute = opts.route;
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
586
|
+
let detached = false;
|
|
587
|
+
const registrations = /* @__PURE__ */ new Set();
|
|
588
|
+
function register(attach) {
|
|
589
|
+
const reg = { attach, detach: detached ? null : attach() };
|
|
590
|
+
registrations.add(reg);
|
|
591
|
+
return () => {
|
|
592
|
+
registrations.delete(reg);
|
|
593
|
+
reg.detach?.();
|
|
594
|
+
reg.detach = null;
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function arm() {
|
|
598
|
+
channel.identify(opts.user);
|
|
599
|
+
channel.joinRoute(currentRoute);
|
|
600
|
+
channel.connect();
|
|
601
|
+
}
|
|
602
|
+
arm();
|
|
603
|
+
return {
|
|
421
604
|
get state() {
|
|
422
|
-
return channel.state;
|
|
605
|
+
return detached ? "disconnected" : channel.state;
|
|
606
|
+
},
|
|
607
|
+
// Re-arms identity/route and re-attaches this facade's listeners so an
|
|
608
|
+
// explicit disconnect() -> connect() restores presence and pin events.
|
|
609
|
+
connect: () => {
|
|
610
|
+
if (detached) {
|
|
611
|
+
detached = false;
|
|
612
|
+
for (const reg of registrations) reg.detach ??= reg.attach();
|
|
613
|
+
}
|
|
614
|
+
arm();
|
|
615
|
+
},
|
|
616
|
+
// Soft leave: drop presence and the stored identity (so a later
|
|
617
|
+
// reopen/revival stays anonymous) but keep the adapter's shared socket
|
|
618
|
+
// alive — in-flight and future RPCs (reports.submit, pins.*) must
|
|
619
|
+
// survive a surface unmount. Socket teardown is reserved for
|
|
620
|
+
// CloudAdapter.dispose().
|
|
621
|
+
disconnect: () => {
|
|
622
|
+
detached = true;
|
|
623
|
+
channel.sendFrame({ type: "leave" });
|
|
624
|
+
channel.clearSession();
|
|
625
|
+
for (const reg of registrations) {
|
|
626
|
+
reg.detach?.();
|
|
627
|
+
reg.detach = null;
|
|
628
|
+
}
|
|
423
629
|
},
|
|
424
|
-
connect: () => channel.connect(),
|
|
425
|
-
disconnect: () => channel.disconnect(),
|
|
426
630
|
joinRoute: (route) => {
|
|
427
631
|
currentRoute = route;
|
|
428
|
-
|
|
632
|
+
channel.joinRoute(route);
|
|
429
633
|
},
|
|
430
|
-
onPresence: (cb) => channel.onPresence(cb),
|
|
431
|
-
onPin: (cb) => channel.onPin(cb)
|
|
634
|
+
onPresence: (cb) => register(() => channel.onPresence(cb)),
|
|
635
|
+
onPin: (cb) => register(() => channel.onPin(cb)),
|
|
636
|
+
onPinArchived: (cb) => register(() => channel.onPinArchived(cb))
|
|
432
637
|
};
|
|
433
|
-
channel.connect();
|
|
434
|
-
return wrapped;
|
|
435
638
|
}
|
|
436
|
-
async function
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
639
|
+
async function listPins(params) {
|
|
640
|
+
const entities = params.entities ? params.entities.split(",").filter(Boolean) : void 0;
|
|
641
|
+
const result = await rpc.call("pins.list", {
|
|
642
|
+
...params.route !== void 0 ? { route: params.route } : {},
|
|
643
|
+
...entities ? { entities } : {}
|
|
440
644
|
});
|
|
441
|
-
|
|
442
|
-
return data.pins;
|
|
645
|
+
return result.pins;
|
|
443
646
|
}
|
|
444
|
-
async function
|
|
445
|
-
const result = await
|
|
446
|
-
|
|
447
|
-
body: { reportId, ...reason ? { reason } : {} }
|
|
448
|
-
});
|
|
449
|
-
unwrap(result);
|
|
647
|
+
async function getPinScreenshot(reportId) {
|
|
648
|
+
const result = await rpc.call("pins.screenshot", { reportId });
|
|
649
|
+
return result.screenshot;
|
|
450
650
|
}
|
|
451
|
-
async function
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
});
|
|
456
|
-
|
|
651
|
+
async function closePin(reportId, reason) {
|
|
652
|
+
await rpc.call("pins.archive", { reportId, ...reason ? { reason } : {} });
|
|
653
|
+
}
|
|
654
|
+
function listReports(opts) {
|
|
655
|
+
return rpc.call("reports.list", opts ?? {});
|
|
656
|
+
}
|
|
657
|
+
function dispose() {
|
|
658
|
+
channel.disconnect();
|
|
457
659
|
}
|
|
458
660
|
return {
|
|
459
661
|
reports: { submit, list: listReports },
|
|
460
662
|
integrations: { getConfig, getCachedConfig },
|
|
461
663
|
realtime: { connect: connectRealtime },
|
|
462
|
-
pins: { list:
|
|
664
|
+
pins: { list: listPins, screenshot: getPinScreenshot, close: closePin },
|
|
665
|
+
dispose
|
|
463
666
|
};
|
|
464
667
|
}
|
|
465
668
|
// Annotate the CommonJS export names for ESM import in node:
|
|
466
669
|
0 && (module.exports = {
|
|
467
670
|
CloudError,
|
|
468
671
|
DEFAULT_CLOUD_ENDPOINT,
|
|
672
|
+
DEFAULT_RPC_TIMEOUT_MS,
|
|
673
|
+
RPC_METHODS,
|
|
674
|
+
SCREENSHOT_CHUNK_BYTES,
|
|
675
|
+
SCREENSHOT_INLINE_MAX_BYTES,
|
|
469
676
|
cloud,
|
|
470
|
-
createRealtimeChannel
|
|
677
|
+
createRealtimeChannel,
|
|
678
|
+
createRpcClient,
|
|
679
|
+
isRpcMethod,
|
|
680
|
+
parseRpcResponseFrame
|
|
471
681
|
});
|
|
472
682
|
//# sourceMappingURL=index.cjs.map
|