slybrowser 0.2.1 → 0.3.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 +8 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -26
- package/dist/cli.js.map +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +54 -8
- package/dist/installer.js.map +1 -1
- package/dist/licensed.d.ts.map +1 -1
- package/dist/licensed.js +0 -3
- package/dist/licensed.js.map +1 -1
- package/dist/service.d.ts +2 -13
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +33 -288
- package/dist/service.js.map +1 -1
- package/package.json +1 -1
package/dist/service.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
1
|
+
import { createPublicKey, randomUUID, verify as verifySignature } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
4
3
|
import { resolve } from "node:path";
|
|
5
4
|
import { gunzipSync } from "node:zlib";
|
|
6
5
|
import { canonicalJson, decodeBase64Url } from "./canonical.js";
|
|
@@ -107,21 +106,6 @@ const LICENSE_FILE_TOP_LEVEL_FIELDS = new Set([
|
|
|
107
106
|
"tag",
|
|
108
107
|
"signature",
|
|
109
108
|
]);
|
|
110
|
-
const LICENSE_FILE_SECRET_FIELDS = new Set([
|
|
111
|
-
"schemaVersion",
|
|
112
|
-
"type",
|
|
113
|
-
"audience",
|
|
114
|
-
"licenseId",
|
|
115
|
-
"fileId",
|
|
116
|
-
"serviceUrl",
|
|
117
|
-
"channel",
|
|
118
|
-
"licenseKey",
|
|
119
|
-
"secretVersion",
|
|
120
|
-
"createdAt",
|
|
121
|
-
"expiresAt",
|
|
122
|
-
"nonce",
|
|
123
|
-
"scope",
|
|
124
|
-
]);
|
|
125
109
|
const USER_LICENSE_FILE_FORMAT = "slybrowser-license-compact-v1";
|
|
126
110
|
const USER_LICENSE_FILE_FIELDS = new Set(["schemaVersion", "type", "format", "license"]);
|
|
127
111
|
const USER_LICENSE_FILE_MAX_COMPRESSED_BYTES = 64 * 1024;
|
|
@@ -214,7 +198,7 @@ function trustedKeyMap(keys) {
|
|
|
214
198
|
const entries = keys instanceof Map ? keys.entries() : Object.entries(keys ?? {});
|
|
215
199
|
return new Map([...entries].map(([key, value]) => [key, Buffer.from(value)]));
|
|
216
200
|
}
|
|
217
|
-
function
|
|
201
|
+
function validateEncryptedLicenseFile(value, options) {
|
|
218
202
|
const document = requiredObject(unwrapUserLicenseFile(value));
|
|
219
203
|
if (Object.keys(document).some((name) => !LICENSE_FILE_TOP_LEVEL_FIELDS.has(name)) ||
|
|
220
204
|
Object.keys(document).length !== LICENSE_FILE_TOP_LEVEL_FIELDS.size ||
|
|
@@ -266,70 +250,16 @@ function parseEncryptedLicenseFile(value, options) {
|
|
|
266
250
|
const verified = verifySignature(null, signedBody, createPublicKey({ key: Buffer.concat([ED25519_SPKI_PREFIX, publicKey]), format: "der", type: "spki" }), decodeBase64Url(requiredText(signature, "signature", 256), 64));
|
|
267
251
|
if (!verified)
|
|
268
252
|
fail("license_file_signature_invalid", "License file signature is invalid", 401);
|
|
269
|
-
|
|
270
|
-
if (typeof passphrase !== "string" || passphrase.length < 12) {
|
|
271
|
-
fail("license_file_locked", "License file passphrase is missing or too short", 401);
|
|
272
|
-
}
|
|
273
|
-
const salt = decodeBase64Url(requiredText(kdf, "salt", 64), 16);
|
|
274
|
-
if (salt.length !== 16)
|
|
275
|
-
fail("authorization_invalid", "License file salt length is invalid");
|
|
276
|
-
const nonce = decodeBase64Url(requiredText(encryption, "nonce", 64), 12);
|
|
277
|
-
if (nonce.length !== 12)
|
|
278
|
-
fail("authorization_invalid", "License file nonce length is invalid");
|
|
279
|
-
const decipher = createDecipheriv("aes-256-gcm", scryptSync(passphrase, salt, 32, { N: 16_384, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }), nonce);
|
|
280
|
-
decipher.setAAD(canonicalJson(publicLicenseFileHeader({ ...document, serviceUrl })));
|
|
281
|
-
decipher.setAuthTag(decodeBase64Url(requiredText(document, "tag", 64), 16));
|
|
282
|
-
let payload;
|
|
283
|
-
try {
|
|
284
|
-
const plaintext = Buffer.concat([
|
|
285
|
-
decipher.update(decodeBase64Url(requiredText(document, "ciphertext", 64 * 1024), 64 * 1024)),
|
|
286
|
-
decipher.final(),
|
|
287
|
-
]);
|
|
288
|
-
payload = requiredObject(JSON.parse(plaintext.toString("utf8")));
|
|
289
|
-
}
|
|
290
|
-
catch (error) {
|
|
291
|
-
if (error instanceof LicenseServiceError)
|
|
292
|
-
throw error;
|
|
293
|
-
fail("license_file_locked", "License file cannot be decrypted", 401);
|
|
294
|
-
}
|
|
295
|
-
if (Object.keys(payload).some((name) => !LICENSE_FILE_SECRET_FIELDS.has(name)) ||
|
|
296
|
-
Object.keys(payload).length !== LICENSE_FILE_SECRET_FIELDS.size) {
|
|
297
|
-
fail("license_file_payload_invalid", "License file payload contains unsupported claims");
|
|
298
|
-
}
|
|
299
|
-
const licenseKey = requiredText(payload, "licenseKey", 256);
|
|
300
|
-
if (payload.schemaVersion !== 2 ||
|
|
301
|
-
payload.type !== "slybrowser-license-secret" ||
|
|
302
|
-
payload.audience !== document.audience ||
|
|
303
|
-
payload.licenseId !== licenseId ||
|
|
304
|
-
payload.fileId !== document.fileId ||
|
|
305
|
-
normalizeServiceUrl(requiredText(payload, "serviceUrl"), options.allowInsecureLocalhost ?? false) !== serviceUrl ||
|
|
306
|
-
payload.channel !== "stable" ||
|
|
307
|
-
payload.secretVersion !== 1 ||
|
|
308
|
-
payload.expiresAt !== document.expiresAt ||
|
|
309
|
-
payload.scope !== expectedScope ||
|
|
310
|
-
!/^sly_live_[0-9a-f-]{36}\.[A-Za-z0-9_-]{40,}$/.test(licenseKey) ||
|
|
311
|
-
!licenseKey.startsWith(`sly_live_${licenseId}.`)) {
|
|
312
|
-
fail("license_file_payload_invalid", "License file payload does not match its public header");
|
|
313
|
-
}
|
|
314
|
-
return { schemaVersion: 1, serviceUrl, licenseKey, channel: "stable" };
|
|
315
|
-
}
|
|
316
|
-
function localLicenseFileUnlockRequested(options) {
|
|
317
|
-
return options.licenseFilePassphrase !== undefined;
|
|
253
|
+
return { serviceUrl, licenseId };
|
|
318
254
|
}
|
|
319
255
|
async function resolveEncryptedLicenseFileViaService(value, options) {
|
|
320
|
-
const
|
|
321
|
-
if (document.schemaVersion !== 2 || document.type !== "slybrowser-license" ||
|
|
322
|
-
document.audience !== "slybrowser-license-file" || document.channel !== "stable") {
|
|
323
|
-
fail("authorization_invalid", "License file fields are invalid");
|
|
324
|
-
}
|
|
325
|
-
const documentServiceUrl = normalizeServiceUrl(requiredText(document, "serviceUrl"), options.allowInsecureLocalhost ?? false);
|
|
326
|
-
assertTrustedAuthorizationOrigin(documentServiceUrl, options);
|
|
327
|
-
const resolutionServiceUrl = normalizeServiceUrl(options.licenseFileResolutionServiceUrl ?? documentServiceUrl, options.allowInsecureLocalhost ?? false);
|
|
256
|
+
const { serviceUrl: documentServiceUrl, licenseId } = validateEncryptedLicenseFile(value, options);
|
|
328
257
|
const fetchImplementation = options.fetch ?? fetch;
|
|
329
258
|
let response;
|
|
330
259
|
try {
|
|
331
|
-
response = await fetchImplementation(`${
|
|
260
|
+
response = await fetchImplementation(`${documentServiceUrl}/v1/license-files/authorization`, {
|
|
332
261
|
method: "POST",
|
|
262
|
+
redirect: "error",
|
|
333
263
|
headers: {
|
|
334
264
|
"content-type": "application/json",
|
|
335
265
|
"user-agent": "SlyBrowserNodeSDK/license-file",
|
|
@@ -362,205 +292,12 @@ async function resolveEncryptedLicenseFileViaService(value, options) {
|
|
|
362
292
|
throw new LicenseServiceError(message, code, response.status, details);
|
|
363
293
|
}
|
|
364
294
|
const authorization = parseAuthorization(parsed, options.allowInsecureLocalhost ?? false);
|
|
365
|
-
if (authorization.serviceUrl !== documentServiceUrl
|
|
366
|
-
|
|
295
|
+
if (authorization.serviceUrl !== documentServiceUrl ||
|
|
296
|
+
!authorization.licenseKey.startsWith(`sly_live_${licenseId}.`)) {
|
|
297
|
+
fail("license_service_invalid_response", "License-file authorization does not match the signed file", response.status);
|
|
367
298
|
}
|
|
368
299
|
return authorization;
|
|
369
300
|
}
|
|
370
|
-
const SEALED_LICENSE_TOP_LEVEL_FIELDS = new Set([
|
|
371
|
-
"schemaVersion",
|
|
372
|
-
"type",
|
|
373
|
-
"audience",
|
|
374
|
-
"serviceUrl",
|
|
375
|
-
"channel",
|
|
376
|
-
"sealedAt",
|
|
377
|
-
"licenseKeySha256",
|
|
378
|
-
"protection",
|
|
379
|
-
"ciphertext",
|
|
380
|
-
]);
|
|
381
|
-
function licenseKeySha256(licenseKey) {
|
|
382
|
-
return createHash("sha256").update(licenseKey, "utf8").digest("hex");
|
|
383
|
-
}
|
|
384
|
-
function sealedLicenseEntropy(document) {
|
|
385
|
-
return [
|
|
386
|
-
"slybrowser-sealed-license-v1",
|
|
387
|
-
requiredText(document, "serviceUrl", 2048),
|
|
388
|
-
requiredText(document, "channel", 16),
|
|
389
|
-
requiredText(document, "licenseKeySha256", 64),
|
|
390
|
-
].join("\0");
|
|
391
|
-
}
|
|
392
|
-
function assertTrustedAuthorizationOrigin(serviceUrl, options) {
|
|
393
|
-
const trustedServiceUrls = (options.trustedServiceUrls ?? ["https://api.slybrowser.com"])
|
|
394
|
-
.map((url) => normalizeServiceUrl(url, options.allowInsecureLocalhost ?? false));
|
|
395
|
-
if (!trustedServiceUrls.includes(serviceUrl)) {
|
|
396
|
-
fail("license_file_untrusted_origin", "License file service URL is not trusted", 403);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
async function windowsDpapi(operation, data, entropy) {
|
|
400
|
-
if (process.platform !== "win32") {
|
|
401
|
-
fail("sealed_license_unsupported", "Windows DPAPI sealed license files are only supported on Windows", 400);
|
|
402
|
-
}
|
|
403
|
-
const script = `
|
|
404
|
-
$ErrorActionPreference = 'Stop'
|
|
405
|
-
[System.Reflection.Assembly]::LoadWithPartialName('System.Security') | Out-Null
|
|
406
|
-
$request = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
407
|
-
$data = [Convert]::FromBase64String([string]$request.data)
|
|
408
|
-
$entropy = [Convert]::FromBase64String([string]$request.entropy)
|
|
409
|
-
$scope = [System.Security.Cryptography.DataProtectionScope]::CurrentUser
|
|
410
|
-
if ([string]$request.operation -eq 'protect') {
|
|
411
|
-
$output = [System.Security.Cryptography.ProtectedData]::Protect($data, $entropy, $scope)
|
|
412
|
-
} elseif ([string]$request.operation -eq 'unprotect') {
|
|
413
|
-
$output = [System.Security.Cryptography.ProtectedData]::Unprotect($data, $entropy, $scope)
|
|
414
|
-
} else {
|
|
415
|
-
throw 'Unsupported DPAPI operation'
|
|
416
|
-
}
|
|
417
|
-
[Console]::Out.Write([Convert]::ToBase64String($output))
|
|
418
|
-
`;
|
|
419
|
-
const encodedCommand = Buffer.from(script, "utf16le").toString("base64");
|
|
420
|
-
const request = JSON.stringify({
|
|
421
|
-
operation,
|
|
422
|
-
data: data.toString("base64"),
|
|
423
|
-
entropy: Buffer.from(entropy, "utf8").toString("base64"),
|
|
424
|
-
});
|
|
425
|
-
return await new Promise((resolvePromise, reject) => {
|
|
426
|
-
const child = spawn("powershell.exe", [
|
|
427
|
-
"-NoLogo",
|
|
428
|
-
"-NoProfile",
|
|
429
|
-
"-NonInteractive",
|
|
430
|
-
"-ExecutionPolicy",
|
|
431
|
-
"Bypass",
|
|
432
|
-
"-EncodedCommand",
|
|
433
|
-
encodedCommand,
|
|
434
|
-
], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
435
|
-
const stdout = [];
|
|
436
|
-
const stderr = [];
|
|
437
|
-
let settled = false;
|
|
438
|
-
const rejectOnce = (error) => {
|
|
439
|
-
if (settled)
|
|
440
|
-
return;
|
|
441
|
-
settled = true;
|
|
442
|
-
reject(error);
|
|
443
|
-
};
|
|
444
|
-
child.stdout.on("data", (chunk) => {
|
|
445
|
-
stdout.push(chunk);
|
|
446
|
-
if (Buffer.concat(stdout).length > 16 * 1024) {
|
|
447
|
-
child.kill();
|
|
448
|
-
rejectOnce(new LicenseServiceError("Windows DPAPI returned an oversized response", "sealed_license_invalid", 400));
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
child.stderr.on("data", (chunk) => {
|
|
452
|
-
stderr.push(chunk);
|
|
453
|
-
if (Buffer.concat(stderr).length > 8 * 1024)
|
|
454
|
-
child.kill();
|
|
455
|
-
});
|
|
456
|
-
child.on("error", () => {
|
|
457
|
-
rejectOnce(new LicenseServiceError("Windows DPAPI is unavailable", "sealed_license_unsupported", 400));
|
|
458
|
-
});
|
|
459
|
-
child.on("close", (code) => {
|
|
460
|
-
if (settled)
|
|
461
|
-
return;
|
|
462
|
-
settled = true;
|
|
463
|
-
if (code !== 0) {
|
|
464
|
-
reject(new LicenseServiceError("Sealed license file cannot be decrypted on this Windows user or machine", "sealed_license_locked", 401));
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
try {
|
|
468
|
-
resolvePromise(Buffer.from(Buffer.concat(stdout).toString("utf8").trim(), "base64"));
|
|
469
|
-
}
|
|
470
|
-
catch {
|
|
471
|
-
reject(new LicenseServiceError("Windows DPAPI returned an invalid sealed license response", "sealed_license_invalid", 400));
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
child.stdin.end(request);
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
async function parseSealedLicenseFile(value, options) {
|
|
478
|
-
const document = requiredObject(value);
|
|
479
|
-
if (Object.keys(document).some((name) => !SEALED_LICENSE_TOP_LEVEL_FIELDS.has(name)) ||
|
|
480
|
-
Object.keys(document).length !== SEALED_LICENSE_TOP_LEVEL_FIELDS.size ||
|
|
481
|
-
document.schemaVersion !== 3 ||
|
|
482
|
-
document.type !== "slybrowser-sealed-authorization" ||
|
|
483
|
-
document.audience !== "slybrowser") {
|
|
484
|
-
fail("sealed_license_invalid", "Sealed license file fields are invalid");
|
|
485
|
-
}
|
|
486
|
-
const serviceUrl = normalizeServiceUrl(requiredText(document, "serviceUrl"), options.allowInsecureLocalhost ?? false);
|
|
487
|
-
assertTrustedAuthorizationOrigin(serviceUrl, options);
|
|
488
|
-
if (requiredText(document, "channel", 16) !== "stable")
|
|
489
|
-
fail("sealed_license_invalid", "Sealed license channel is invalid");
|
|
490
|
-
const sealedAt = Date.parse(requiredText(document, "sealedAt", 64));
|
|
491
|
-
if (!Number.isFinite(sealedAt))
|
|
492
|
-
fail("sealed_license_invalid", "Sealed license timestamp is invalid");
|
|
493
|
-
const sha256 = requiredText(document, "licenseKeySha256", 64);
|
|
494
|
-
if (!/^[0-9a-f]{64}$/.test(sha256))
|
|
495
|
-
fail("sealed_license_invalid", "Sealed license fingerprint is invalid");
|
|
496
|
-
const protection = requiredObject(document.protection);
|
|
497
|
-
if (Object.keys(protection).sort().join(",") !== "provider,scope" ||
|
|
498
|
-
protection.provider !== "windows-dpapi" ||
|
|
499
|
-
protection.scope !== "current-user") {
|
|
500
|
-
fail("sealed_license_unsupported", "Sealed license protection is not supported", 400);
|
|
501
|
-
}
|
|
502
|
-
const ciphertext = decodeBase64Url(requiredText(document, "ciphertext", 64 * 1024), 64 * 1024);
|
|
503
|
-
let parsed;
|
|
504
|
-
try {
|
|
505
|
-
const plaintext = await windowsDpapi("unprotect", ciphertext, sealedLicenseEntropy({ ...document, serviceUrl }));
|
|
506
|
-
parsed = JSON.parse(plaintext.toString("utf8"));
|
|
507
|
-
}
|
|
508
|
-
catch (error) {
|
|
509
|
-
if (error instanceof LicenseServiceError)
|
|
510
|
-
throw error;
|
|
511
|
-
fail("sealed_license_locked", "Sealed license file cannot be decrypted on this Windows user or machine", 401);
|
|
512
|
-
}
|
|
513
|
-
const authorization = parseAuthorization(parsed, options.allowInsecureLocalhost ?? false);
|
|
514
|
-
if (authorization.serviceUrl !== serviceUrl || authorization.channel !== "stable" ||
|
|
515
|
-
licenseKeySha256(authorization.licenseKey) !== sha256) {
|
|
516
|
-
fail("sealed_license_mismatch", "Sealed license payload does not match its public header", 400);
|
|
517
|
-
}
|
|
518
|
-
return authorization;
|
|
519
|
-
}
|
|
520
|
-
export async function importLicenseFileToSealedAuthorization(inputPath, outputPath, options = {}) {
|
|
521
|
-
const raw = await readFile(resolve(inputPath));
|
|
522
|
-
if (raw.length > 64 * 1024)
|
|
523
|
-
fail("authorization_invalid", "Authorization file is too large");
|
|
524
|
-
let parsed;
|
|
525
|
-
try {
|
|
526
|
-
parsed = JSON.parse(raw.toString("utf8"));
|
|
527
|
-
}
|
|
528
|
-
catch {
|
|
529
|
-
fail("authorization_invalid", "Authorization file is not valid JSON");
|
|
530
|
-
}
|
|
531
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.schemaVersion !== 2) {
|
|
532
|
-
fail("license_file_invalid", "Only encrypted v2 SlyBrowser license files can be imported", 400);
|
|
533
|
-
}
|
|
534
|
-
const authorization = parseEncryptedLicenseFile(parsed, options);
|
|
535
|
-
assertTrustedAuthorizationOrigin(authorization.serviceUrl, options);
|
|
536
|
-
const fingerprint = licenseKeySha256(authorization.licenseKey);
|
|
537
|
-
const sealedAt = new Date().toISOString();
|
|
538
|
-
const document = {
|
|
539
|
-
schemaVersion: 3,
|
|
540
|
-
type: "slybrowser-sealed-authorization",
|
|
541
|
-
audience: "slybrowser",
|
|
542
|
-
serviceUrl: authorization.serviceUrl,
|
|
543
|
-
channel: authorization.channel,
|
|
544
|
-
sealedAt,
|
|
545
|
-
licenseKeySha256: fingerprint,
|
|
546
|
-
protection: {
|
|
547
|
-
provider: "windows-dpapi",
|
|
548
|
-
scope: "current-user",
|
|
549
|
-
},
|
|
550
|
-
ciphertext: "",
|
|
551
|
-
};
|
|
552
|
-
const ciphertext = await windowsDpapi("protect", Buffer.from(JSON.stringify(authorization), "utf8"), sealedLicenseEntropy(document));
|
|
553
|
-
document.ciphertext = ciphertext.toString("base64url");
|
|
554
|
-
const output = resolve(outputPath);
|
|
555
|
-
await writeFile(output, `${JSON.stringify(document, null, 2)}\n`, { flag: "wx", mode: 0o600 });
|
|
556
|
-
return {
|
|
557
|
-
output,
|
|
558
|
-
serviceUrl: authorization.serviceUrl,
|
|
559
|
-
channel: "stable",
|
|
560
|
-
protection: "windows-dpapi-current-user",
|
|
561
|
-
licenseKeySha256: fingerprint,
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
301
|
export async function readLicenseAuthorization(path, options = {}) {
|
|
565
302
|
const raw = await readFile(resolve(path));
|
|
566
303
|
if (raw.length > 64 * 1024)
|
|
@@ -578,14 +315,7 @@ export async function readLicenseAuthorization(path, options = {}) {
|
|
|
578
315
|
: parsed;
|
|
579
316
|
if (document && typeof document === "object" && !Array.isArray(document) &&
|
|
580
317
|
document.schemaVersion === 2) {
|
|
581
|
-
|
|
582
|
-
return await resolveEncryptedLicenseFileViaService(parsed, options);
|
|
583
|
-
}
|
|
584
|
-
return parseEncryptedLicenseFile(parsed, options);
|
|
585
|
-
}
|
|
586
|
-
if (document && typeof document === "object" && !Array.isArray(document) &&
|
|
587
|
-
document.schemaVersion === 3) {
|
|
588
|
-
return await parseSealedLicenseFile(parsed, options);
|
|
318
|
+
return await resolveEncryptedLicenseFileViaService(parsed, options);
|
|
589
319
|
}
|
|
590
320
|
return parseAuthorization(parsed, options.allowInsecureLocalhost ?? false);
|
|
591
321
|
}
|
|
@@ -708,7 +438,7 @@ export class LicenseServiceClient {
|
|
|
708
438
|
async licenseInfo(options = {}) {
|
|
709
439
|
const platform = options.platform ?? currentPlatform();
|
|
710
440
|
const arch = options.arch ?? currentArch();
|
|
711
|
-
const sdkVersion = options.sdkVersion ?? "0.
|
|
441
|
+
const sdkVersion = options.sdkVersion ?? "0.3.0";
|
|
712
442
|
const versionPolicy = options.versionPolicy ?? (options.browserVersion === undefined ? "latest" : "exact");
|
|
713
443
|
if (versionPolicy === "latest" && options.browserVersion !== undefined) {
|
|
714
444
|
fail("version_policy_invalid", "Latest selection cannot include a requested browser version");
|
|
@@ -814,7 +544,7 @@ export class LicenseServiceClient {
|
|
|
814
544
|
async createSession(options = {}) {
|
|
815
545
|
const platform = options.platform ?? currentPlatform();
|
|
816
546
|
const arch = options.arch ?? currentArch();
|
|
817
|
-
const sdkVersion = options.sdkVersion ?? "0.
|
|
547
|
+
const sdkVersion = options.sdkVersion ?? "0.3.0";
|
|
818
548
|
const versionPolicy = options.versionPolicy ?? (options.browserVersion === undefined ? "latest" : "exact");
|
|
819
549
|
if (versionPolicy === "latest" && options.browserVersion !== undefined) {
|
|
820
550
|
fail("version_policy_invalid", "Latest selection cannot include a requested browser version");
|
|
@@ -940,7 +670,7 @@ export class LicenseServiceClient {
|
|
|
940
670
|
async createRuntimeSession(options = {}) {
|
|
941
671
|
const platform = options.platform ?? currentPlatform();
|
|
942
672
|
const arch = options.arch ?? currentArch();
|
|
943
|
-
const sdkVersion = options.sdkVersion ?? "0.
|
|
673
|
+
const sdkVersion = options.sdkVersion ?? "0.3.0";
|
|
944
674
|
const startupId = options.startupId ?? newStartupId();
|
|
945
675
|
if (!/^st_[A-Za-z0-9_-]{16,120}$/.test(startupId)) {
|
|
946
676
|
fail("startup_id_invalid", "Runtime startup ID is invalid");
|
|
@@ -1170,18 +900,32 @@ export class LicenseServiceClient {
|
|
|
1170
900
|
authorization: `Bootstrap ${grant.bootstrapToken}`,
|
|
1171
901
|
});
|
|
1172
902
|
}
|
|
1173
|
-
async downloadArtifact(grant) {
|
|
1174
|
-
|
|
903
|
+
async downloadArtifact(grant, signal) {
|
|
904
|
+
let artifactUrl;
|
|
905
|
+
try {
|
|
906
|
+
artifactUrl = new URL(grant.artifact.url);
|
|
907
|
+
}
|
|
908
|
+
catch {
|
|
909
|
+
fail("license_service_invalid_response", "Artifact URL is invalid");
|
|
910
|
+
}
|
|
911
|
+
const response = await this.#fetch(artifactUrl.toString(), {
|
|
1175
912
|
method: "GET",
|
|
1176
913
|
headers: { authorization: `Session ${grant.sessionToken}` },
|
|
1177
914
|
redirect: "error",
|
|
915
|
+
...(signal === undefined ? {} : { signal }),
|
|
1178
916
|
});
|
|
1179
917
|
if (!response.ok || !response.body)
|
|
1180
918
|
await this.#throwResponse(response);
|
|
1181
919
|
return response;
|
|
1182
920
|
}
|
|
1183
|
-
async downloadRuntimeArtifact(grant) {
|
|
1184
|
-
|
|
921
|
+
async downloadRuntimeArtifact(grant, signal) {
|
|
922
|
+
let artifactUrl;
|
|
923
|
+
try {
|
|
924
|
+
artifactUrl = new URL(grant.downloadTicket.artifactUrl);
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
fail("license_service_invalid_response", "Runtime artifact URL is invalid");
|
|
928
|
+
}
|
|
1185
929
|
if (artifactUrl.pathname.startsWith("/v1/releases/artifacts/")) {
|
|
1186
930
|
artifactUrl.pathname = artifactUrl.pathname.replace("/v1/releases/artifacts/", "/v2/runtime/artifacts/");
|
|
1187
931
|
}
|
|
@@ -1192,6 +936,7 @@ export class LicenseServiceClient {
|
|
|
1192
936
|
method: "GET",
|
|
1193
937
|
headers: { authorization: `Download ${grant.downloadTicket.token}` },
|
|
1194
938
|
redirect: "error",
|
|
939
|
+
...(signal === undefined ? {} : { signal }),
|
|
1195
940
|
});
|
|
1196
941
|
if (!response.ok || !response.body)
|
|
1197
942
|
await this.#throwResponse(response);
|