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