@kehto/services 0.6.0 → 0.8.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/dist/index.d.ts +243 -58
- package/dist/index.js +279 -159
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -231,156 +231,6 @@ function createNotificationService(options) {
|
|
|
231
231
|
|
|
232
232
|
// src/identity-service.ts
|
|
233
233
|
var IDENTITY_SERVICE_VERSION = "1.0.0";
|
|
234
|
-
var DECRYPT_ERROR_CODES = [
|
|
235
|
-
"class-forbidden",
|
|
236
|
-
"signer-denied",
|
|
237
|
-
"signer-unavailable",
|
|
238
|
-
"decrypt-failed",
|
|
239
|
-
"malformed-wrap",
|
|
240
|
-
"impersonation",
|
|
241
|
-
"unsupported-encryption",
|
|
242
|
-
"policy-denied"
|
|
243
|
-
];
|
|
244
|
-
var DECRYPT_ERROR_CODE_SET = new Set(DECRYPT_ERROR_CODES);
|
|
245
|
-
function isDecryptErrorCode(value) {
|
|
246
|
-
return typeof value === "string" && DECRYPT_ERROR_CODE_SET.has(value);
|
|
247
|
-
}
|
|
248
|
-
function normalizeDecryptError(error) {
|
|
249
|
-
if (isDecryptErrorCode(error)) return error;
|
|
250
|
-
if (typeof error === "object" && error !== null) {
|
|
251
|
-
const candidate = error;
|
|
252
|
-
if (isDecryptErrorCode(candidate.code)) return candidate.code;
|
|
253
|
-
if (isDecryptErrorCode(candidate.error)) return candidate.error;
|
|
254
|
-
if (isDecryptErrorCode(candidate.message)) return candidate.message;
|
|
255
|
-
}
|
|
256
|
-
return "decrypt-failed";
|
|
257
|
-
}
|
|
258
|
-
function sendDecryptError(id, error, send) {
|
|
259
|
-
const result = {
|
|
260
|
-
type: "identity.decrypt.error",
|
|
261
|
-
id,
|
|
262
|
-
error
|
|
263
|
-
};
|
|
264
|
-
send(result);
|
|
265
|
-
}
|
|
266
|
-
function isStringArrayArray(value) {
|
|
267
|
-
return Array.isArray(value) && value.every(
|
|
268
|
-
(tag) => Array.isArray(tag) && tag.every((part) => typeof part === "string")
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
function isNostrEvent(value) {
|
|
272
|
-
const event = value;
|
|
273
|
-
return typeof event === "object" && event !== null && typeof event.id === "string" && typeof event.pubkey === "string" && typeof event.created_at === "number" && typeof event.kind === "number" && isStringArrayArray(event.tags) && typeof event.content === "string" && typeof event.sig === "string";
|
|
274
|
-
}
|
|
275
|
-
function isRumor(value) {
|
|
276
|
-
const rumor = value;
|
|
277
|
-
return typeof rumor === "object" && rumor !== null && typeof rumor.id === "string" && typeof rumor.pubkey === "string" && typeof rumor.created_at === "number" && typeof rumor.kind === "number" && isStringArrayArray(rumor.tags) && typeof rumor.content === "string";
|
|
278
|
-
}
|
|
279
|
-
function isGiftWrapDecryptResult(value) {
|
|
280
|
-
const result = value;
|
|
281
|
-
return typeof result === "object" && result !== null && isNostrEvent(result.seal) && isRumor(result.rumor);
|
|
282
|
-
}
|
|
283
|
-
function firstDecodedByte(content) {
|
|
284
|
-
const trimmed = content.trim();
|
|
285
|
-
if (trimmed.length === 0) return null;
|
|
286
|
-
const normalized = trimmed.replace(/-/g, "+").replace(/_/g, "/");
|
|
287
|
-
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
288
|
-
try {
|
|
289
|
-
const decoded = atob(padded);
|
|
290
|
-
return decoded.length > 0 ? decoded.charCodeAt(0) : null;
|
|
291
|
-
} catch {
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
function detectEncryptionMode(event) {
|
|
296
|
-
if (event.kind === 4) return "nip04";
|
|
297
|
-
if (event.kind === 1059) return "nip17";
|
|
298
|
-
if (event.kind === 14 || firstDecodedByte(event.content) === 2) {
|
|
299
|
-
return "nip44-direct";
|
|
300
|
-
}
|
|
301
|
-
return null;
|
|
302
|
-
}
|
|
303
|
-
function rumorFromSignedEvent(event, content) {
|
|
304
|
-
return {
|
|
305
|
-
id: event.id,
|
|
306
|
-
pubkey: event.pubkey,
|
|
307
|
-
kind: event.kind,
|
|
308
|
-
tags: event.tags,
|
|
309
|
-
created_at: event.created_at,
|
|
310
|
-
content
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
async function handleDecrypt(id, message, send, options) {
|
|
314
|
-
const event = message.event;
|
|
315
|
-
if (!isNostrEvent(event)) {
|
|
316
|
-
sendDecryptError(id, "malformed-wrap", send);
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
const verifyEvent = options.verifyEvent ?? (() => true);
|
|
320
|
-
let verified;
|
|
321
|
-
try {
|
|
322
|
-
verified = await Promise.resolve(verifyEvent(event));
|
|
323
|
-
} catch {
|
|
324
|
-
sendDecryptError(id, "malformed-wrap", send);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
if (!verified) {
|
|
328
|
-
sendDecryptError(id, "malformed-wrap", send);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
const mode = detectEncryptionMode(event);
|
|
332
|
-
if (!mode) {
|
|
333
|
-
sendDecryptError(id, "unsupported-encryption", send);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const decryptor = options.getDecryptor?.() ?? null;
|
|
337
|
-
if (!decryptor) {
|
|
338
|
-
sendDecryptError(id, "signer-unavailable", send);
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
try {
|
|
342
|
-
if (mode === "nip04") {
|
|
343
|
-
const plaintext = await decryptor.nip04Decrypt(event.pubkey, event.content);
|
|
344
|
-
const result2 = {
|
|
345
|
-
type: "identity.decrypt.result",
|
|
346
|
-
id,
|
|
347
|
-
rumor: rumorFromSignedEvent(event, plaintext),
|
|
348
|
-
sender: event.pubkey
|
|
349
|
-
};
|
|
350
|
-
send(result2);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (mode === "nip44-direct") {
|
|
354
|
-
const plaintext = await decryptor.nip44Decrypt(event.pubkey, event.content);
|
|
355
|
-
const result2 = {
|
|
356
|
-
type: "identity.decrypt.result",
|
|
357
|
-
id,
|
|
358
|
-
rumor: rumorFromSignedEvent(event, plaintext),
|
|
359
|
-
sender: event.pubkey
|
|
360
|
-
};
|
|
361
|
-
send(result2);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
const unwrapped = await decryptor.unwrapGiftWrap(event);
|
|
365
|
-
if (!isGiftWrapDecryptResult(unwrapped)) {
|
|
366
|
-
sendDecryptError(id, "malformed-wrap", send);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
if (unwrapped.seal.pubkey !== unwrapped.rumor.pubkey) {
|
|
370
|
-
sendDecryptError(id, "impersonation", send);
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
const result = {
|
|
374
|
-
type: "identity.decrypt.result",
|
|
375
|
-
id,
|
|
376
|
-
rumor: unwrapped.rumor,
|
|
377
|
-
sender: unwrapped.seal.pubkey
|
|
378
|
-
};
|
|
379
|
-
send(result);
|
|
380
|
-
} catch (error) {
|
|
381
|
-
sendDecryptError(id, normalizeDecryptError(error), send);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
234
|
function createIdentityService(options) {
|
|
385
235
|
return {
|
|
386
236
|
descriptor: {
|
|
@@ -496,10 +346,6 @@ function createIdentityService(options) {
|
|
|
496
346
|
send(result);
|
|
497
347
|
return;
|
|
498
348
|
}
|
|
499
|
-
case "identity.decrypt": {
|
|
500
|
-
void handleDecrypt(id, message, send, options);
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
349
|
default:
|
|
504
350
|
sendError(message.type, `Unknown identity method: ${message.type}`);
|
|
505
351
|
}
|
|
@@ -2278,6 +2124,278 @@ function normalizePublishResult(res, relayUrls) {
|
|
|
2278
2124
|
return out;
|
|
2279
2125
|
}
|
|
2280
2126
|
|
|
2127
|
+
// src/upload-service.ts
|
|
2128
|
+
var UPLOAD_SERVICE_VERSION = "1.0.0";
|
|
2129
|
+
var UPLOAD_DESCRIPTOR = {
|
|
2130
|
+
name: "upload",
|
|
2131
|
+
version: UPLOAD_SERVICE_VERSION,
|
|
2132
|
+
description: "NAP-UPLOAD shell-mediated file/blob upload \u2014 upload/status with progress pushes"
|
|
2133
|
+
};
|
|
2134
|
+
function createUploadService(options) {
|
|
2135
|
+
if (!options || typeof options.uploader !== "object" || options.uploader === null) {
|
|
2136
|
+
throw new Error("createUploadService: options.uploader is required");
|
|
2137
|
+
}
|
|
2138
|
+
const { uploader } = options;
|
|
2139
|
+
const generateId2 = options.generateId ?? (() => crypto.randomUUID());
|
|
2140
|
+
const now = options.now ?? (() => Date.now());
|
|
2141
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2142
|
+
function handleUpload(windowId, msg, send) {
|
|
2143
|
+
const m = msg;
|
|
2144
|
+
const id = m.id ?? "";
|
|
2145
|
+
const request = m.request;
|
|
2146
|
+
if (!request || typeof request !== "object" || request.data == null) {
|
|
2147
|
+
send({ type: "upload.upload.result", id, error: "invalid request" });
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
const uploadId = generateId2();
|
|
2151
|
+
const key = `${windowId}:${uploadId}`;
|
|
2152
|
+
entries.set(key, { uploadId });
|
|
2153
|
+
const ctx = {
|
|
2154
|
+
uploadId,
|
|
2155
|
+
windowId,
|
|
2156
|
+
onStatus: (status) => {
|
|
2157
|
+
const stamped = { ...status, uploadId, updatedAt: status.updatedAt || now() };
|
|
2158
|
+
const entry = entries.get(key);
|
|
2159
|
+
if (entry) entry.status = stamped;
|
|
2160
|
+
send({ type: "upload.status.changed", status: stamped });
|
|
2161
|
+
}
|
|
2162
|
+
};
|
|
2163
|
+
void uploader.upload(request, ctx).then((result) => {
|
|
2164
|
+
const stamped = { ...result, uploadId };
|
|
2165
|
+
const entry = entries.get(key);
|
|
2166
|
+
if (entry) entry.status = { ...stamped, updatedAt: now() };
|
|
2167
|
+
send({ type: "upload.upload.result", id, result: stamped });
|
|
2168
|
+
}).catch((err) => {
|
|
2169
|
+
entries.delete(key);
|
|
2170
|
+
send({ type: "upload.upload.result", id, error: toErrorMessage2(err) });
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
function handleStatus(windowId, msg, send) {
|
|
2174
|
+
const m = msg;
|
|
2175
|
+
const id = m.id ?? "";
|
|
2176
|
+
const uploadId = m.uploadId;
|
|
2177
|
+
if (typeof uploadId !== "string" || uploadId.length === 0) {
|
|
2178
|
+
send({ type: "upload.status.result", id, error: "invalid uploadId" });
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
const tracked = entries.get(`${windowId}:${uploadId}`)?.status;
|
|
2182
|
+
if (tracked) {
|
|
2183
|
+
send({ type: "upload.status.result", id, status: tracked });
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
if (uploader.status) {
|
|
2187
|
+
void uploader.status(uploadId).then(
|
|
2188
|
+
(status) => send(
|
|
2189
|
+
status ? { type: "upload.status.result", id, status } : { type: "upload.status.result", id, error: "unknown upload" }
|
|
2190
|
+
)
|
|
2191
|
+
).catch(
|
|
2192
|
+
(err) => send({ type: "upload.status.result", id, error: toErrorMessage2(err) })
|
|
2193
|
+
);
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
send({ type: "upload.status.result", id, error: "unknown upload" });
|
|
2197
|
+
}
|
|
2198
|
+
return {
|
|
2199
|
+
descriptor: UPLOAD_DESCRIPTOR,
|
|
2200
|
+
handleMessage(windowId, message, send) {
|
|
2201
|
+
switch (message.type) {
|
|
2202
|
+
case "upload.upload":
|
|
2203
|
+
handleUpload(windowId, message, send);
|
|
2204
|
+
return;
|
|
2205
|
+
case "upload.status":
|
|
2206
|
+
handleStatus(windowId, message, send);
|
|
2207
|
+
return;
|
|
2208
|
+
default:
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
},
|
|
2212
|
+
onWindowDestroyed(windowId) {
|
|
2213
|
+
const prefix = `${windowId}:`;
|
|
2214
|
+
for (const [key, entry] of entries) {
|
|
2215
|
+
if (key.startsWith(prefix)) {
|
|
2216
|
+
uploader.cancel?.(entry.uploadId);
|
|
2217
|
+
entries.delete(key);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
function toErrorMessage2(err) {
|
|
2224
|
+
if (err instanceof Error) return err.message;
|
|
2225
|
+
if (typeof err === "string") return err;
|
|
2226
|
+
return "upload request failed";
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
// src/http-uploader.ts
|
|
2230
|
+
var KIND_NIP98 = 27235;
|
|
2231
|
+
var KIND_BLOSSOM_AUTH = 24242;
|
|
2232
|
+
var BLOSSOM_AUTH_TTL_S = 3600;
|
|
2233
|
+
function createHttpUploader(options) {
|
|
2234
|
+
if (!options || typeof options.signEvent !== "function") {
|
|
2235
|
+
throw new Error("createHttpUploader: options.signEvent is required");
|
|
2236
|
+
}
|
|
2237
|
+
const rails = options.rails ?? {};
|
|
2238
|
+
const signEvent = options.signEvent;
|
|
2239
|
+
const fetchFn = options.fetch ?? fetch;
|
|
2240
|
+
const digest = options.digestSha256 ?? defaultDigestSha256;
|
|
2241
|
+
const nowS = options.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
2242
|
+
const defaultRail = options.defaultRail ?? firstConfiguredRail(rails);
|
|
2243
|
+
async function upload(request, ctx) {
|
|
2244
|
+
const rail = request.rail ?? defaultRail;
|
|
2245
|
+
const config = rail === "nip96" ? rails.nip96 : rail === "blossom" ? rails.blossom : void 0;
|
|
2246
|
+
if (rail !== "nip96" && rail !== "blossom") {
|
|
2247
|
+
return failed(ctx.uploadId, rail ?? "unknown", "unsupported rail");
|
|
2248
|
+
}
|
|
2249
|
+
const server = config?.servers?.[0];
|
|
2250
|
+
if (!server) {
|
|
2251
|
+
return failed(ctx.uploadId, rail, "no server configured");
|
|
2252
|
+
}
|
|
2253
|
+
const bytes = await toBytes(request.data);
|
|
2254
|
+
const sha256 = await digest(bytes);
|
|
2255
|
+
try {
|
|
2256
|
+
return rail === "nip96" ? await uploadNip96({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS }) : await uploadBlossom({ request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS });
|
|
2257
|
+
} catch (err) {
|
|
2258
|
+
return failed(ctx.uploadId, rail, toErrorMessage3(err), sha256);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
return { upload };
|
|
2262
|
+
}
|
|
2263
|
+
async function uploadNip96(args) {
|
|
2264
|
+
const { request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS } = args;
|
|
2265
|
+
const auth = await signEvent({
|
|
2266
|
+
kind: KIND_NIP98,
|
|
2267
|
+
created_at: nowS(),
|
|
2268
|
+
content: "",
|
|
2269
|
+
tags: [
|
|
2270
|
+
["u", server],
|
|
2271
|
+
["method", "POST"],
|
|
2272
|
+
["payload", sha256]
|
|
2273
|
+
]
|
|
2274
|
+
});
|
|
2275
|
+
const form = new FormData();
|
|
2276
|
+
form.append("file", new Blob([bytesToArrayBuffer(bytes)], { type: request.mimeType }), request.filename ?? "file");
|
|
2277
|
+
if (request.caption !== void 0) form.append("caption", request.caption);
|
|
2278
|
+
if (request.mimeType !== void 0) form.append("content_type", request.mimeType);
|
|
2279
|
+
if (request.noTransform) form.append("no_transform", "true");
|
|
2280
|
+
const res = await fetchFn(server, {
|
|
2281
|
+
method: "POST",
|
|
2282
|
+
headers: { Authorization: nostrAuthHeader(auth) },
|
|
2283
|
+
body: form
|
|
2284
|
+
});
|
|
2285
|
+
if (!res.ok) {
|
|
2286
|
+
return failed(ctx.uploadId, "nip96", `server rejected (HTTP ${res.status})`, sha256);
|
|
2287
|
+
}
|
|
2288
|
+
const body = await res.json();
|
|
2289
|
+
if (body.status === "error") {
|
|
2290
|
+
return failed(ctx.uploadId, "nip96", body.message ?? "upload failed", sha256);
|
|
2291
|
+
}
|
|
2292
|
+
const tags = body.nip94_event?.tags ?? [];
|
|
2293
|
+
return fromNip94Tags(ctx.uploadId, "nip96", tags, bytes.byteLength, sha256);
|
|
2294
|
+
}
|
|
2295
|
+
function fromNip94Tags(uploadId, rail, tags, fallbackSize, fallbackSha) {
|
|
2296
|
+
const get = (name) => tags.find((t) => t[0] === name)?.[1];
|
|
2297
|
+
const url = get("url");
|
|
2298
|
+
const result = {
|
|
2299
|
+
ok: Boolean(url),
|
|
2300
|
+
uploadId,
|
|
2301
|
+
status: url ? "complete" : "failed",
|
|
2302
|
+
rail,
|
|
2303
|
+
sha256: get("x") ?? fallbackSha,
|
|
2304
|
+
nip94: tags
|
|
2305
|
+
};
|
|
2306
|
+
if (url) result.url = url;
|
|
2307
|
+
const ox = get("ox");
|
|
2308
|
+
if (ox) result.originalSha256 = ox;
|
|
2309
|
+
const size = get("size");
|
|
2310
|
+
result.size = size ? Number(size) : fallbackSize;
|
|
2311
|
+
const m = get("m");
|
|
2312
|
+
if (m) result.mimeType = m;
|
|
2313
|
+
const dim = parseDimensions(get("dim"));
|
|
2314
|
+
if (dim) result.dimensions = dim;
|
|
2315
|
+
const blurhash = get("blurhash");
|
|
2316
|
+
if (blurhash) result.blurhash = blurhash;
|
|
2317
|
+
if (!url) result.error = "server returned no url";
|
|
2318
|
+
return result;
|
|
2319
|
+
}
|
|
2320
|
+
async function uploadBlossom(args) {
|
|
2321
|
+
const { request, ctx, server, bytes, sha256, signEvent, fetchFn, nowS } = args;
|
|
2322
|
+
const auth = await signEvent({
|
|
2323
|
+
kind: KIND_BLOSSOM_AUTH,
|
|
2324
|
+
created_at: nowS(),
|
|
2325
|
+
content: `Upload ${request.filename ?? "file"}`,
|
|
2326
|
+
tags: [
|
|
2327
|
+
["t", "upload"],
|
|
2328
|
+
["x", sha256],
|
|
2329
|
+
["expiration", String(nowS() + BLOSSOM_AUTH_TTL_S)]
|
|
2330
|
+
]
|
|
2331
|
+
});
|
|
2332
|
+
const endpoint = `${trimTrailingSlash(server)}/upload`;
|
|
2333
|
+
const headers = { Authorization: nostrAuthHeader(auth) };
|
|
2334
|
+
if (request.mimeType) headers["Content-Type"] = request.mimeType;
|
|
2335
|
+
const res = await fetchFn(endpoint, {
|
|
2336
|
+
method: "PUT",
|
|
2337
|
+
headers,
|
|
2338
|
+
body: bytesToArrayBuffer(bytes)
|
|
2339
|
+
});
|
|
2340
|
+
if (!res.ok) {
|
|
2341
|
+
return failed(ctx.uploadId, "blossom", `server rejected (HTTP ${res.status})`, sha256);
|
|
2342
|
+
}
|
|
2343
|
+
const blob = await res.json();
|
|
2344
|
+
if (!blob.url) {
|
|
2345
|
+
return failed(ctx.uploadId, "blossom", "server returned no url", sha256);
|
|
2346
|
+
}
|
|
2347
|
+
const result = {
|
|
2348
|
+
ok: true,
|
|
2349
|
+
uploadId: ctx.uploadId,
|
|
2350
|
+
status: "complete",
|
|
2351
|
+
rail: "blossom",
|
|
2352
|
+
url: blob.url,
|
|
2353
|
+
sha256: blob.sha256 ?? sha256,
|
|
2354
|
+
size: blob.size ?? bytes.byteLength
|
|
2355
|
+
};
|
|
2356
|
+
if (blob.type) result.mimeType = blob.type;
|
|
2357
|
+
return result;
|
|
2358
|
+
}
|
|
2359
|
+
function failed(uploadId, rail, error, sha256) {
|
|
2360
|
+
return { ok: false, uploadId, status: "failed", rail, error, ...sha256 ? { sha256 } : {} };
|
|
2361
|
+
}
|
|
2362
|
+
function firstConfiguredRail(rails) {
|
|
2363
|
+
if (rails.nip96?.servers?.length) return "nip96";
|
|
2364
|
+
if (rails.blossom?.servers?.length) return "blossom";
|
|
2365
|
+
return void 0;
|
|
2366
|
+
}
|
|
2367
|
+
function nostrAuthHeader(event) {
|
|
2368
|
+
return `Nostr ${base64Utf8(JSON.stringify(event))}`;
|
|
2369
|
+
}
|
|
2370
|
+
function base64Utf8(s) {
|
|
2371
|
+
return btoa(String.fromCharCode(...new TextEncoder().encode(s)));
|
|
2372
|
+
}
|
|
2373
|
+
function parseDimensions(dim) {
|
|
2374
|
+
if (!dim) return void 0;
|
|
2375
|
+
const m = /^(\d+)x(\d+)$/.exec(dim);
|
|
2376
|
+
if (!m) return void 0;
|
|
2377
|
+
return { width: Number(m[1]), height: Number(m[2]) };
|
|
2378
|
+
}
|
|
2379
|
+
async function toBytes(data) {
|
|
2380
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
2381
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
2382
|
+
}
|
|
2383
|
+
function bytesToArrayBuffer(bytes) {
|
|
2384
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
2385
|
+
}
|
|
2386
|
+
function trimTrailingSlash(url) {
|
|
2387
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
2388
|
+
}
|
|
2389
|
+
async function defaultDigestSha256(bytes) {
|
|
2390
|
+
const buf = await crypto.subtle.digest("SHA-256", bytesToArrayBuffer(bytes));
|
|
2391
|
+
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2392
|
+
}
|
|
2393
|
+
function toErrorMessage3(err) {
|
|
2394
|
+
if (err instanceof Error) return err.message;
|
|
2395
|
+
if (typeof err === "string") return err;
|
|
2396
|
+
return "upload failed";
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2281
2399
|
// src/cvm-service.ts
|
|
2282
2400
|
var CVM_SERVICE_VERSION = "1.0.0";
|
|
2283
2401
|
var CVM_DESCRIPTOR = {
|
|
@@ -2320,7 +2438,7 @@ function createCvmService(options) {
|
|
|
2320
2438
|
const m = msg;
|
|
2321
2439
|
const id = m.id ?? "";
|
|
2322
2440
|
void transport.discover(m.query).then((servers) => send({ type: "cvm.discover.result", id, servers })).catch(
|
|
2323
|
-
(err) => send({ type: "cvm.discover.result", id, servers: [], error:
|
|
2441
|
+
(err) => send({ type: "cvm.discover.result", id, servers: [], error: toErrorMessage4(err) })
|
|
2324
2442
|
);
|
|
2325
2443
|
}
|
|
2326
2444
|
function handleRequest(windowId, msg, send) {
|
|
@@ -2336,7 +2454,7 @@ function createCvmService(options) {
|
|
|
2336
2454
|
}
|
|
2337
2455
|
openSession(windowId, m.server, send);
|
|
2338
2456
|
void transport.request(m.server, m.message, m.options).then((message) => send({ type: "cvm.request.result", id, message })).catch(
|
|
2339
|
-
(err) => send({ type: "cvm.request.result", id, error:
|
|
2457
|
+
(err) => send({ type: "cvm.request.result", id, error: toErrorMessage4(err) })
|
|
2340
2458
|
);
|
|
2341
2459
|
}
|
|
2342
2460
|
function handleClose(windowId, msg, send) {
|
|
@@ -2347,7 +2465,7 @@ function createCvmService(options) {
|
|
|
2347
2465
|
return;
|
|
2348
2466
|
}
|
|
2349
2467
|
closeSession(windowId, m.server.pubkey);
|
|
2350
|
-
void transport.close(m.server).then(() => send({ type: "cvm.close.result", id })).catch((err) => send({ type: "cvm.close.result", id, error:
|
|
2468
|
+
void transport.close(m.server).then(() => send({ type: "cvm.close.result", id })).catch((err) => send({ type: "cvm.close.result", id, error: toErrorMessage4(err) }));
|
|
2351
2469
|
}
|
|
2352
2470
|
return {
|
|
2353
2471
|
descriptor: CVM_DESCRIPTOR,
|
|
@@ -2378,7 +2496,7 @@ function createCvmService(options) {
|
|
|
2378
2496
|
}
|
|
2379
2497
|
};
|
|
2380
2498
|
}
|
|
2381
|
-
function
|
|
2499
|
+
function toErrorMessage4(err) {
|
|
2382
2500
|
if (err instanceof Error) return err.message;
|
|
2383
2501
|
if (typeof err === "string") return err;
|
|
2384
2502
|
return "cvm request failed";
|
|
@@ -2390,6 +2508,7 @@ export {
|
|
|
2390
2508
|
createConfigService,
|
|
2391
2509
|
createCoordinatedRelay,
|
|
2392
2510
|
createCvmService,
|
|
2511
|
+
createHttpUploader,
|
|
2393
2512
|
createIdentityService,
|
|
2394
2513
|
createKeysService,
|
|
2395
2514
|
createMediaService,
|
|
@@ -2399,6 +2518,7 @@ export {
|
|
|
2399
2518
|
createRelayPoolOutboxRouter,
|
|
2400
2519
|
createRelayPoolService,
|
|
2401
2520
|
createResourceService,
|
|
2402
|
-
createThemeService
|
|
2521
|
+
createThemeService,
|
|
2522
|
+
createUploadService
|
|
2403
2523
|
};
|
|
2404
2524
|
//# sourceMappingURL=index.js.map
|