sello 0.1.0 → 0.1.2
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 +2 -0
- package/dist/cbor.js +337 -0
- package/dist/cli/bench.js +389 -0
- package/dist/cli/demo.js +113 -0
- package/dist/cli/sello.js +515 -0
- package/dist/cose/protected-header.js +210 -0
- package/dist/cose/sign1.js +124 -0
- package/dist/crypto/ed25519.js +117 -0
- package/dist/crypto/identifiers.js +64 -0
- package/dist/hpke/base.js +349 -0
- package/dist/hpke/receipt.js +79 -0
- package/dist/index.js +15 -0
- package/dist/log/canonical-url.js +168 -0
- package/dist/log/mock-log.js +147 -0
- package/dist/log/rekor.js +120 -0
- package/dist/log/types.js +0 -0
- package/dist/mcp/middleware.js +162 -0
- package/dist/owner/verify.js +271 -0
- package/dist/receipt/body.js +210 -0
- package/dist/registry/json-registry.js +233 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/keys.js +191 -0
- package/dist/sdk/logs.js +196 -0
- package/dist/sdk/publisher.js +106 -0
- package/dist/sdk/service.js +561 -0
- package/dist/service/create-receipt.js +174 -0
- package/dist/token/jws-profile.js +174 -0
- package/docs/decisions.md +2 -2
- package/docs/release-checklist.md +4 -3
- package/docs/sdk-quickstart.md +2 -0
- package/package.json +10 -6
- package/src/cli/sello.ts +5 -3
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import { buildReceipt } from "../service/create-receipt.js";
|
|
2
|
+
import { verifySelloJwsToken } from "../token/jws-profile.js";
|
|
3
|
+
import { assertCanonicalLogUrl, logUrlsEqual } from "../log/canonical-url.js";
|
|
4
|
+
import { canonicalJsonBytes } from "../mcp/middleware.js";
|
|
5
|
+
import {
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
decodeBase64url,
|
|
9
|
+
normalizeEd25519PublicKey,
|
|
10
|
+
normalizeServiceKey,
|
|
11
|
+
} from "./keys.js";
|
|
12
|
+
import { http } from "./logs.js";
|
|
13
|
+
import {
|
|
14
|
+
BackgroundReceiptPublisher,
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
} from "./publisher.js";
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
export function createSelloService(input ) {
|
|
99
|
+
const deferred = resolveDeferredConfig(input, process.env);
|
|
100
|
+
const publisherOptions = resolvePublisherOptions(input, process.env);
|
|
101
|
+
let loaded ;
|
|
102
|
+
let publisher ;
|
|
103
|
+
|
|
104
|
+
async function config() {
|
|
105
|
+
loaded ??= loadDeferredConfig(deferred);
|
|
106
|
+
return await loaded;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function publisherFor(resolved ) {
|
|
110
|
+
publisher ??= new BackgroundReceiptPublisher({
|
|
111
|
+
log: resolved.log,
|
|
112
|
+
...publisherOptions,
|
|
113
|
+
});
|
|
114
|
+
return publisher;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
tool (
|
|
119
|
+
actionType ,
|
|
120
|
+
handler ,
|
|
121
|
+
options = {},
|
|
122
|
+
) {
|
|
123
|
+
if (typeof actionType !== "string" || actionType.length === 0) {
|
|
124
|
+
throw new TypeError("Sello action type must be a non-empty string");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return async (request ) => {
|
|
128
|
+
const resolved = await config();
|
|
129
|
+
const authorizationToken = resolveValue(
|
|
130
|
+
options.authorizationToken ?? defaultAuthorizationToken,
|
|
131
|
+
request,
|
|
132
|
+
);
|
|
133
|
+
const tokenIssuerPublicKey = await resolveTokenIssuerPublicKey(resolved.tokenIssuer);
|
|
134
|
+
const verifiedToken = verifySelloJwsToken({
|
|
135
|
+
authorizationToken,
|
|
136
|
+
issuerPublicKey: tokenIssuerPublicKey,
|
|
137
|
+
});
|
|
138
|
+
const selloLogs = selectSelloLogs(
|
|
139
|
+
verifiedToken.selloLogs,
|
|
140
|
+
resolved.fallbackSelloLogs,
|
|
141
|
+
resolved.log.logUrl,
|
|
142
|
+
);
|
|
143
|
+
const base = {
|
|
144
|
+
authorizationTokenBytes: verifiedToken.authorizationTokenBytes,
|
|
145
|
+
ownerHpkePublicKey: verifiedToken.ownerHpkePublicKey,
|
|
146
|
+
selloLogs,
|
|
147
|
+
serviceKid: resolved.serviceKid,
|
|
148
|
+
servicePrivateKey: resolved.servicePrivateKey,
|
|
149
|
+
serviceIdentifier: resolved.service,
|
|
150
|
+
logUrl: resolved.log.logUrl,
|
|
151
|
+
actionType,
|
|
152
|
+
actionInputBytes: (options.canonicalizeInput ?? canonicalJsonBytes)(request),
|
|
153
|
+
timestamp: resolved.now(),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
if (options.isDenied && (await options.isDenied(request))) {
|
|
157
|
+
const response = options.deniedResponse
|
|
158
|
+
? await options.deniedResponse(request)
|
|
159
|
+
: undefined;
|
|
160
|
+
const receipt = emitReceipt({
|
|
161
|
+
...base,
|
|
162
|
+
actionOutputBytes: new Uint8Array(),
|
|
163
|
+
resultStatus: "denied",
|
|
164
|
+
});
|
|
165
|
+
resolved.onReceipt?.({ resultStatus: "denied", receipt, response });
|
|
166
|
+
await submit(resolved, receipt, base.timestamp);
|
|
167
|
+
|
|
168
|
+
if (options.deniedResponse) {
|
|
169
|
+
return response ;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
throw new SelloDeniedError(receipt);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let response ;
|
|
176
|
+
try {
|
|
177
|
+
response = await handler(request);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const receipt = emitReceipt({
|
|
180
|
+
...base,
|
|
181
|
+
actionOutputBytes: (options.canonicalizeError ?? canonicalErrorBytes)(
|
|
182
|
+
error,
|
|
183
|
+
),
|
|
184
|
+
resultStatus: "error",
|
|
185
|
+
});
|
|
186
|
+
resolved.onReceipt?.({ resultStatus: "error", receipt, error });
|
|
187
|
+
await submit(resolved, receipt, base.timestamp);
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const receipt = emitReceipt({
|
|
192
|
+
...base,
|
|
193
|
+
actionOutputBytes: (options.canonicalizeOutput ?? canonicalJsonBytes)(
|
|
194
|
+
response,
|
|
195
|
+
),
|
|
196
|
+
resultStatus: "success",
|
|
197
|
+
});
|
|
198
|
+
resolved.onReceipt?.({ resultStatus: "success", receipt, response });
|
|
199
|
+
await submit(resolved, receipt, base.timestamp);
|
|
200
|
+
return response;
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
async flush() {
|
|
204
|
+
await publisher?.flush();
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
async function submit(
|
|
209
|
+
resolved ,
|
|
210
|
+
receipt ,
|
|
211
|
+
integratedTime ,
|
|
212
|
+
) {
|
|
213
|
+
const currentPublisher = publisherFor(resolved);
|
|
214
|
+
if (currentPublisher.mode === "await") {
|
|
215
|
+
await currentPublisher.publish({
|
|
216
|
+
envelope: receipt.envelope,
|
|
217
|
+
integratedTime,
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
currentPublisher.publishBackground({
|
|
223
|
+
envelope: receipt.envelope,
|
|
224
|
+
integratedTime,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export class SelloDeniedError extends Error {
|
|
230
|
+
receipt ;
|
|
231
|
+
|
|
232
|
+
constructor(receipt ) {
|
|
233
|
+
super("Sello request denied");
|
|
234
|
+
this.name = "SelloDeniedError";
|
|
235
|
+
this.receipt = receipt;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function emitReceipt(input ) {
|
|
240
|
+
return buildReceipt(input);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function resolveDeferredConfig(
|
|
244
|
+
input ,
|
|
245
|
+
env ,
|
|
246
|
+
) {
|
|
247
|
+
const objectInput =
|
|
248
|
+
typeof input === "object" && input !== null ? input : {};
|
|
249
|
+
const serviceOverride = typeof input === "string" ? input : objectInput.service;
|
|
250
|
+
const hostedSecret = env.SELLO_SECRET_KEY;
|
|
251
|
+
|
|
252
|
+
if (
|
|
253
|
+
hostedSecret &&
|
|
254
|
+
objectInput.serviceKey === undefined &&
|
|
255
|
+
env.SELLO_SERVICE_KEY === undefined
|
|
256
|
+
) {
|
|
257
|
+
return {
|
|
258
|
+
type: "hosted",
|
|
259
|
+
secretKey: hostedSecret,
|
|
260
|
+
configUrl:
|
|
261
|
+
env.SELLO_HOSTED_CONFIG_URL ?? "https://sello.build/api/sdk/config",
|
|
262
|
+
now: objectInput.now ?? nowUtcSeconds,
|
|
263
|
+
onReceipt: objectInput.onReceipt,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
type: "resolved",
|
|
269
|
+
config: resolveSelfHostedConfig(objectInput, serviceOverride, env),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function resolvePublisherOptions(
|
|
274
|
+
input ,
|
|
275
|
+
env ,
|
|
276
|
+
) {
|
|
277
|
+
const objectInput =
|
|
278
|
+
typeof input === "object" && input !== null ? input : {};
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
submit: objectInput.submit ?? envSubmitMode(env),
|
|
282
|
+
onSubmitError: objectInput.onSubmitError,
|
|
283
|
+
onDrop: objectInput.onDrop,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function resolveSelfHostedConfig(
|
|
288
|
+
input ,
|
|
289
|
+
serviceOverride ,
|
|
290
|
+
env ,
|
|
291
|
+
) {
|
|
292
|
+
const service = serviceOverride ?? env.SELLO_SERVICE_ID;
|
|
293
|
+
if (!service) {
|
|
294
|
+
throw new TypeError(
|
|
295
|
+
"Sello setup missing SELLO_SERVICE_ID. Set SELLO_SERVICE_ID or call sello.service(\"service-id\").",
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const serviceKey = normalizeServiceKey(
|
|
300
|
+
input.serviceKey ?? env.SELLO_SERVICE_KEY,
|
|
301
|
+
input.serviceKid ?? env.SELLO_SERVICE_KID,
|
|
302
|
+
);
|
|
303
|
+
const log =
|
|
304
|
+
input.log ??
|
|
305
|
+
(env.SELLO_LOG_URL
|
|
306
|
+
? http(env.SELLO_LOG_URL, { endpoint: env.SELLO_LOG_ENDPOINT })
|
|
307
|
+
: undefined);
|
|
308
|
+
|
|
309
|
+
if (!log) {
|
|
310
|
+
throw new TypeError(
|
|
311
|
+
"Sello setup missing SELLO_LOG_URL. Set SELLO_LOG_URL or pass log explicitly.",
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
service,
|
|
317
|
+
serviceKid: serviceKey.kid,
|
|
318
|
+
servicePrivateKey: serviceKey.privateKey,
|
|
319
|
+
tokenIssuer: normalizeTokenIssuer(input, env),
|
|
320
|
+
log,
|
|
321
|
+
fallbackSelloLogs: input.fallbackSelloLogs ?? [log.logUrl],
|
|
322
|
+
now: input.now ?? nowUtcSeconds,
|
|
323
|
+
onReceipt: input.onReceipt,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function loadDeferredConfig(
|
|
328
|
+
deferred ,
|
|
329
|
+
) {
|
|
330
|
+
if (deferred.type === "resolved") {
|
|
331
|
+
return deferred.config;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const response = await fetch(deferred.configUrl, {
|
|
335
|
+
method: "GET",
|
|
336
|
+
headers: {
|
|
337
|
+
authorization: `Bearer ${deferred.secretKey}`,
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
if (!response.ok) {
|
|
342
|
+
throw new TypeError(`sello.build config fetch failed with HTTP ${response.status}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const config = await response.json();
|
|
346
|
+
if (!isRecord(config)) {
|
|
347
|
+
throw new TypeError("sello.build config response must be an object");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const service = readString(config.service, "hosted service");
|
|
351
|
+
const logUrl = readString(config.logUrl, "hosted logUrl");
|
|
352
|
+
const hostedServiceKey = normalizeServiceKey(
|
|
353
|
+
readString(config.serviceKey, "hosted serviceKey"),
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
service,
|
|
358
|
+
serviceKid: hostedServiceKey.kid,
|
|
359
|
+
servicePrivateKey: hostedServiceKey.privateKey,
|
|
360
|
+
tokenIssuer: {
|
|
361
|
+
type: "public-key",
|
|
362
|
+
publicKey: normalizeEd25519PublicKey(
|
|
363
|
+
readString(config.tokenIssuerPublicKey, "hosted tokenIssuerPublicKey"),
|
|
364
|
+
"hosted tokenIssuerPublicKey",
|
|
365
|
+
),
|
|
366
|
+
},
|
|
367
|
+
log: http(logUrl, {
|
|
368
|
+
endpoint:
|
|
369
|
+
typeof config.logEndpoint === "string" ? config.logEndpoint : undefined,
|
|
370
|
+
}),
|
|
371
|
+
fallbackSelloLogs: [http(logUrl).logUrl],
|
|
372
|
+
now: deferred.now,
|
|
373
|
+
onReceipt: deferred.onReceipt,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function normalizeTokenIssuer(
|
|
378
|
+
input ,
|
|
379
|
+
env ,
|
|
380
|
+
) {
|
|
381
|
+
const tokenIssuer = input.tokenIssuer;
|
|
382
|
+
|
|
383
|
+
if (tokenIssuer instanceof Uint8Array || typeof tokenIssuer === "string") {
|
|
384
|
+
return {
|
|
385
|
+
type: "public-key",
|
|
386
|
+
publicKey: normalizeEd25519PublicKey(tokenIssuer, "tokenIssuer"),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (tokenIssuer && typeof tokenIssuer === "object") {
|
|
391
|
+
if (tokenIssuer.publicKey) {
|
|
392
|
+
return {
|
|
393
|
+
type: "public-key",
|
|
394
|
+
publicKey: normalizeEd25519PublicKey(
|
|
395
|
+
tokenIssuer.publicKey,
|
|
396
|
+
"tokenIssuer.publicKey",
|
|
397
|
+
),
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (tokenIssuer.jwksUrl) {
|
|
402
|
+
return { type: "jwks", jwksUrl: tokenIssuer.jwksUrl };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (input.tokenIssuerPublicKey || env.SELLO_TOKEN_ISSUER_PUBLIC_KEY) {
|
|
407
|
+
return {
|
|
408
|
+
type: "public-key",
|
|
409
|
+
publicKey: normalizeEd25519PublicKey(
|
|
410
|
+
input.tokenIssuerPublicKey ?? env.SELLO_TOKEN_ISSUER_PUBLIC_KEY ,
|
|
411
|
+
"SELLO_TOKEN_ISSUER_PUBLIC_KEY",
|
|
412
|
+
),
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const jwksUrl = input.tokenIssuerJwks ?? env.SELLO_TOKEN_ISSUER_JWKS;
|
|
417
|
+
if (jwksUrl) {
|
|
418
|
+
return { type: "jwks", jwksUrl };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
throw new TypeError(
|
|
422
|
+
"Sello setup missing token issuer. Set SELLO_TOKEN_ISSUER_PUBLIC_KEY, SELLO_TOKEN_ISSUER_JWKS, or pass tokenIssuer.",
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function resolveTokenIssuerPublicKey(
|
|
427
|
+
issuer ,
|
|
428
|
+
) {
|
|
429
|
+
if (issuer.type === "public-key") {
|
|
430
|
+
return issuer.publicKey;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
issuer.publicKey ??= await fetchEd25519JwksKey(issuer.jwksUrl);
|
|
434
|
+
return issuer.publicKey;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function fetchEd25519JwksKey(jwksUrl ) {
|
|
438
|
+
const response = await fetch(jwksUrl);
|
|
439
|
+
if (!response.ok) {
|
|
440
|
+
throw new TypeError(`token issuer JWKS fetch failed with HTTP ${response.status}`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const jwks = await response.json();
|
|
444
|
+
if (!isRecord(jwks) || !Array.isArray(jwks.keys)) {
|
|
445
|
+
throw new TypeError("token issuer JWKS must contain keys");
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const key = jwks.keys.find(
|
|
449
|
+
(candidate) =>
|
|
450
|
+
isRecord(candidate) &&
|
|
451
|
+
candidate.kty === "OKP" &&
|
|
452
|
+
candidate.crv === "Ed25519" &&
|
|
453
|
+
typeof candidate.x === "string",
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
if (!key || !isRecord(key) || typeof key.x !== "string") {
|
|
457
|
+
throw new TypeError("token issuer JWKS must contain an Ed25519 OKP key");
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return normalizeEd25519PublicKey(decodeBase64url(key.x, "JWKS x"), "JWKS x");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function selectSelloLogs(
|
|
464
|
+
tokenLogs ,
|
|
465
|
+
fallbackLogs ,
|
|
466
|
+
logUrl ,
|
|
467
|
+
) {
|
|
468
|
+
const selloLogs = tokenLogs ?? fallbackLogs ?? [];
|
|
469
|
+
if (selloLogs.length === 0) {
|
|
470
|
+
throw new TypeError("Sello token did not provide owner-trusted logs");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
for (const entry of selloLogs) {
|
|
474
|
+
assertCanonicalLogUrl(entry, "sello_logs entry");
|
|
475
|
+
if (logUrlsEqual(entry, logUrl)) {
|
|
476
|
+
return selloLogs;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
throw new TypeError("Sello log must be listed in the token's sello_logs");
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function defaultAuthorizationToken(request ) {
|
|
484
|
+
if (isRecord(request)) {
|
|
485
|
+
const direct = request.authorizationToken ?? request.authorization;
|
|
486
|
+
if (typeof direct === "string" || direct instanceof Uint8Array) {
|
|
487
|
+
return stripBearer(direct);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const headers = request.headers;
|
|
491
|
+
if (isRecord(headers)) {
|
|
492
|
+
const header = headers.authorization ?? headers.Authorization;
|
|
493
|
+
if (typeof header === "string") {
|
|
494
|
+
return stripBearer(header);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
throw new TypeError(
|
|
500
|
+
"Sello authorization token not found. Pass authorizationToken or include request.authorizationToken.",
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function stripBearer(value ) {
|
|
505
|
+
if (typeof value !== "string") {
|
|
506
|
+
return value;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return value.startsWith("Bearer ") ? value.slice("Bearer ".length) : value;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function canonicalErrorBytes(error ) {
|
|
513
|
+
if (error instanceof Error) {
|
|
514
|
+
return canonicalJsonBytes({
|
|
515
|
+
name: error.name,
|
|
516
|
+
message: error.message,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return canonicalJsonBytes({ error: String(error) });
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function resolveValue (
|
|
524
|
+
value ,
|
|
525
|
+
request ,
|
|
526
|
+
) {
|
|
527
|
+
if (typeof value === "function") {
|
|
528
|
+
return (value )(request);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return value;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function envSubmitMode(env ) {
|
|
535
|
+
const mode = env.SELLO_SUBMIT_MODE;
|
|
536
|
+
if (mode === undefined) {
|
|
537
|
+
return undefined;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (mode !== "background" && mode !== "await") {
|
|
541
|
+
throw new TypeError("SELLO_SUBMIT_MODE must be background or await");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return mode;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function nowUtcSeconds() {
|
|
548
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function isRecord(value ) {
|
|
552
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function readString(value , name ) {
|
|
556
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
557
|
+
throw new TypeError(`${name} must be a non-empty string`);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return value;
|
|
561
|
+
}
|