@moonbase.sh/licensing 2.0.1 → 3.1.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.cjs CHANGED
@@ -31,20 +31,46 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  ActivationMethod: () => ActivationMethod,
34
- DefaultDeviceIdResolver: () => DefaultDeviceIdResolver,
34
+ DEVICE_ID_SOURCE_TAGS: () => DEVICE_ID_SOURCE_TAGS,
35
35
  ErrorType: () => ErrorType,
36
+ FINGERPRINT_PREFIX: () => FINGERPRINT_PREFIX,
37
+ FINGERPRINT_VERSION: () => FINGERPRINT_VERSION,
36
38
  FileLicenseStore: () => FileLicenseStore,
39
+ IDENTIFYING_PARAM_NAMES: () => IDENTIFYING_PARAM_NAMES,
37
40
  InMemoryLicenseStore: () => InMemoryLicenseStore,
41
+ InsufficientDeviceIdentityError: () => InsufficientDeviceIdentityError,
42
+ LegacyDeviceIdResolver: () => LegacyDeviceIdResolver,
38
43
  LicenseClient: () => LicenseClient,
39
44
  LicenseValidator: () => LicenseValidator,
45
+ MAX_VALUE_LENGTH: () => MAX_VALUE_LENGTH,
46
+ MigratingDeviceIdResolver: () => MigratingDeviceIdResolver,
47
+ MoonbaseDeviceIdResolver: () => MoonbaseDeviceIdResolver,
40
48
  MoonbaseError: () => MoonbaseError,
41
- MoonbaseLicensing: () => MoonbaseLicensing
49
+ MoonbaseLicensing: () => MoonbaseLicensing,
50
+ buildFingerprintMaterial: () => buildFingerprintMaterial,
51
+ canonicalizeParams: () => canonicalizeParams,
52
+ canonicalizeValue: () => canonicalizeValue,
53
+ defaultDeviceIdentityReader: () => defaultDeviceIdentityReader,
54
+ fingerprintDeviceId: () => fingerprintDeviceId,
55
+ fingerprintDigest: () => fingerprintDigest,
56
+ identitySource: () => identitySource,
57
+ parseDeviceIdStamp: () => parseDeviceIdStamp,
58
+ parseIoregPlatformUuid: () => parseIoregPlatformUuid,
59
+ parseSmbiosParams: () => parseSmbiosParams,
60
+ platformTag: () => platformTag,
61
+ selectMachineId: () => selectMachineId,
62
+ stampDeviceId: () => stampDeviceId
42
63
  });
43
64
  module.exports = __toCommonJS(index_exports);
65
+ var import_node_buffer2 = require("buffer");
66
+ var import_node_process3 = __toESM(require("process"), 1);
44
67
 
45
68
  // src/client.ts
46
69
  var import_cross_fetch = __toESM(require("cross-fetch"), 1);
47
70
 
71
+ // src/schemas.ts
72
+ var import_zod = require("zod");
73
+
48
74
  // src/types.ts
49
75
  var ActivationMethod = /* @__PURE__ */ ((ActivationMethod2) => {
50
76
  ActivationMethod2["Online"] = "Online";
@@ -53,7 +79,6 @@ var ActivationMethod = /* @__PURE__ */ ((ActivationMethod2) => {
53
79
  })(ActivationMethod || {});
54
80
 
55
81
  // src/schemas.ts
56
- var import_zod = require("zod");
57
82
  var activationRequestResponseSchema = import_zod.z.object({
58
83
  id: import_zod.z.string(),
59
84
  request: import_zod.z.string(),
@@ -107,7 +132,7 @@ var LicenseClient = class {
107
132
  deviceName: await this.deviceIdResolver.resolveDeviceName(),
108
133
  deviceSignature: await this.deviceIdResolver.resolveDeviceId()
109
134
  };
110
- const response = await (0, import_cross_fetch.default)(this.configuration.endpoint + `/api/client/activations/${this.configuration.productId}/request${this.buildQueryString()}`, {
135
+ const response = await (0, import_cross_fetch.default)(`${this.configuration.endpoint}/api/client/activations/${this.configuration.productId}/request${this.buildQueryString()}`, {
111
136
  ...defaultFetchOptions,
112
137
  method: "POST",
113
138
  body: JSON.stringify(content)
@@ -129,7 +154,8 @@ var LicenseClient = class {
129
154
  }
130
155
  async getRequestedActivation(request) {
131
156
  const response = await (0, import_cross_fetch.default)(request.request, defaultFetchOptions);
132
- if (response.status === 204 || response.status === 404) return null;
157
+ if (response.status === 204 || response.status === 404)
158
+ return null;
133
159
  return await this.handleLicenseResponse(response);
134
160
  }
135
161
  async requestTrial() {
@@ -137,7 +163,7 @@ var LicenseClient = class {
137
163
  deviceName: await this.deviceIdResolver.resolveDeviceName(),
138
164
  deviceSignature: await this.deviceIdResolver.resolveDeviceId()
139
165
  };
140
- const response = await (0, import_cross_fetch.default)(this.configuration.endpoint + `/api/client/trials/${this.configuration.productId}/request${this.buildQueryString()}`, {
166
+ const response = await (0, import_cross_fetch.default)(`${this.configuration.endpoint}/api/client/trials/${this.configuration.productId}/request${this.buildQueryString()}`, {
141
167
  ...defaultFetchOptions,
142
168
  method: "POST",
143
169
  headers: {
@@ -149,7 +175,7 @@ var LicenseClient = class {
149
175
  return await this.handleLicenseResponse(response);
150
176
  }
151
177
  async validateLicense(license) {
152
- const response = await (0, import_cross_fetch.default)(this.configuration.endpoint + `/api/client/licenses/${this.configuration.productId}/validate${this.buildQueryString()}`, {
178
+ const response = await (0, import_cross_fetch.default)(`${this.configuration.endpoint}/api/client/licenses/${this.configuration.productId}/validate${this.buildQueryString()}`, {
153
179
  ...defaultFetchOptions,
154
180
  method: "POST",
155
181
  headers: {
@@ -162,11 +188,12 @@ var LicenseClient = class {
162
188
  }
163
189
  async validateRawLicense(rawLicense) {
164
190
  const license = await this.licenseValidator.validateLicense(rawLicense.toString("utf8"));
165
- if (license.activationMethod === "Offline" /* Offline */) return license;
191
+ if (license.activationMethod === "Offline" /* Offline */)
192
+ return license;
166
193
  return await this.validateLicense(license);
167
194
  }
168
195
  async revokeLicense(license) {
169
- const response = await (0, import_cross_fetch.default)(this.configuration.endpoint + `/api/client/licenses/${this.configuration.productId}/revoke?format=JWT`, {
196
+ const response = await (0, import_cross_fetch.default)(`${this.configuration.endpoint}/api/client/licenses/${this.configuration.productId}/revoke?format=JWT`, {
170
197
  ...defaultFetchOptions,
171
198
  method: "POST",
172
199
  headers: {
@@ -188,11 +215,14 @@ var LicenseClient = class {
188
215
  buildQueryString() {
189
216
  const parts = ["format=JWT"];
190
217
  const { platform, appVersion, metadata } = this.configuration;
191
- if (platform) parts.push(`platform=${encodeURIComponent(platform)}`);
192
- if (appVersion) parts.push(`appVersion=${encodeURIComponent(appVersion)}`);
218
+ if (platform)
219
+ parts.push(`platform=${encodeURIComponent(platform)}`);
220
+ if (appVersion)
221
+ parts.push(`appVersion=${encodeURIComponent(appVersion)}`);
193
222
  if (metadata) {
194
223
  for (const [key, value] of Object.entries(metadata)) {
195
- if (value === null || value === void 0 || value === "") continue;
224
+ if (value === null || value === void 0 || value === "")
225
+ continue;
196
226
  parts.push(`meta[${encodeURIComponent(key)}]=${encodeURIComponent(value)}`);
197
227
  }
198
228
  }
@@ -201,32 +231,486 @@ var LicenseClient = class {
201
231
  };
202
232
 
203
233
  // src/deviceIdResolver.ts
234
+ var import_node_child_process2 = __toESM(require("child_process"), 1);
235
+ var import_node_crypto2 = require("crypto");
236
+ var import_node_os2 = __toESM(require("os"), 1);
237
+ var import_node_process2 = __toESM(require("process"), 1);
204
238
  var import_systeminformation = __toESM(require("systeminformation"), 1);
205
- var import_node_os = __toESM(require("os"), 1);
239
+
240
+ // src/errors.ts
241
+ var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
242
+ ErrorType2["None"] = "None";
243
+ ErrorType2["ApiError"] = "ApiError";
244
+ ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
245
+ ErrorType2["LicenseInvalid"] = "LicenseInvalid";
246
+ ErrorType2["LicenseRevoked"] = "LicenseRevoked";
247
+ ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
248
+ ErrorType2["LicenseExpired"] = "LicenseExpired";
249
+ ErrorType2["LicenseDeviceMismatch"] = "LicenseDeviceMismatch";
250
+ ErrorType2["DeviceIdentityUnavailable"] = "DeviceIdentityUnavailable";
251
+ return ErrorType2;
252
+ })(ErrorType || {});
253
+ var MoonbaseError = class extends Error {
254
+ constructor(title, detail, type, inner) {
255
+ super();
256
+ this.title = title;
257
+ this.detail = detail;
258
+ this.type = type;
259
+ this.inner = inner;
260
+ this.name = "MoonbaseError";
261
+ this.message = detail != null ? detail : title;
262
+ }
263
+ };
264
+ var InsufficientDeviceIdentityError = class extends MoonbaseError {
265
+ constructor(platform, reason = "no identity parameter could be read") {
266
+ super(
267
+ "No device identity",
268
+ `Could not identify this device (platform: ${platform}): ${reason}`,
269
+ "DeviceIdentityUnavailable" /* DeviceIdentityUnavailable */
270
+ );
271
+ this.platform = platform;
272
+ this.name = "InsufficientDeviceIdentityError";
273
+ }
274
+ };
275
+
276
+ // src/fingerprint.ts
277
+ var import_node_buffer = require("buffer");
206
278
  var import_node_child_process = __toESM(require("child_process"), 1);
207
279
  var import_node_crypto = require("crypto");
208
- var DefaultDeviceIdResolver = class {
280
+ var import_node_fs = __toESM(require("fs"), 1);
281
+ var import_node_os = __toESM(require("os"), 1);
282
+ var import_node_process = __toESM(require("process"), 1);
283
+ var FINGERPRINT_PREFIX = "moonbase:fingerprint:v2";
284
+ var FINGERPRINT_VERSION = 2;
285
+ var MAX_VALUE_LENGTH = 128;
286
+ var PRINTABLE_ASCII_MIN = 32;
287
+ var PRINTABLE_ASCII_MAX = 126;
288
+ var SOURCE_TAGS = {
289
+ identity: "",
290
+ deviceName: "n",
291
+ scoped: "s"
292
+ };
293
+ var DEVICE_ID_SOURCE_TAGS = Object.freeze({ ...SOURCE_TAGS });
294
+ var SOURCE_BY_TAG = new Map(
295
+ Object.entries(SOURCE_TAGS).map(([source, tag]) => [tag, source])
296
+ );
297
+ var SCOPED_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
298
+ function identitySource(platform) {
299
+ return SCOPED_PLATFORMS.has(platform) ? "scoped" : "identity";
300
+ }
301
+ var STAMP_PATTERN = /^mbd(\d+)([a-z]*)_([0-9a-f]{64})$/;
302
+ function canonicalizeValue(value) {
303
+ var _a;
304
+ let printable = "";
305
+ for (const char of value.normalize("NFC")) {
306
+ const code = (_a = char.codePointAt(0)) != null ? _a : 0;
307
+ if (code >= PRINTABLE_ASCII_MIN && code <= PRINTABLE_ASCII_MAX)
308
+ printable += char;
309
+ }
310
+ return printable.slice(0, MAX_VALUE_LENGTH).replace(/^ +| +$/g, "");
311
+ }
312
+ var PLATFORM_TAGS = /* @__PURE__ */ new Set(
313
+ ["mac", "ios", "windows", "android", "linux", "bsd", "unknown"]
314
+ );
315
+ function isPlatformTag(value) {
316
+ return PLATFORM_TAGS.has(value);
317
+ }
318
+ function platformTag(platform = import_node_process.default.platform) {
319
+ switch (platform) {
320
+ case "darwin":
321
+ return "mac";
322
+ case "win32":
323
+ return "windows";
324
+ case "android":
325
+ return "android";
326
+ case "linux":
327
+ return "linux";
328
+ case "freebsd":
329
+ case "openbsd":
330
+ case "netbsd":
331
+ return "bsd";
332
+ default:
333
+ return isPlatformTag(platform) ? platform : "unknown";
334
+ }
335
+ }
336
+ var IDENTIFYING_PARAMS = /* @__PURE__ */ new Set([
337
+ "ioPlatformUuid",
338
+ "machineId",
339
+ "systemUuid",
340
+ "baseboardSerialNumber",
341
+ "identifierForVendor",
342
+ "androidId",
343
+ "deviceName"
344
+ ]);
345
+ var IDENTIFYING_PARAM_NAMES = Object.freeze([...IDENTIFYING_PARAMS]);
346
+ var NOT_PROGRAMMED_VALUES = /* @__PURE__ */ new Set([
347
+ "to be filled by o.e.m.",
348
+ // The same filler without the dots, which plenty of firmware writes instead.
349
+ "to be filled by oem",
350
+ "default string",
351
+ "system serial number",
352
+ "base board serial number",
353
+ "chassis serial number",
354
+ "not specified",
355
+ "not applicable",
356
+ "not available",
357
+ "none",
358
+ "unknown",
359
+ "invalid",
360
+ "n/a",
361
+ "0123456789",
362
+ // `machine-id(5)`: the literal marker systemd writes to say "no id yet",
363
+ // e.g. in an initrd or a golden image awaiting first boot. Every machine
364
+ // deployed from such an image reads it, so it is the opposite of an identifier.
365
+ "uninitialized"
366
+ ]);
367
+ var IDENTITY_CONSTRAINTS = {
368
+ androidId: {
369
+ // Reading the *static field* `Settings.Secure.ANDROID_ID` instead of calling
370
+ // `getString` with it yields the key name "android_id", identical on every
371
+ // device, so a single activation would unlock a whole Android install base.
372
+ // That string is not hex, so this stops it reaching the material.
373
+ //
374
+ // The bound is 1,16 and not 16 because AOSP before 8.0 generated the value
375
+ // with `Long.toHexString`, which drops leading zeros — a strict 16 would
376
+ // reject legitimate ids on roughly one in sixteen pre-Oreo devices.
377
+ format: /^[0-9a-f]{1,16}$/,
378
+ // A real ANDROID_ID shared by a large batch of 2010-era devices whose
379
+ // `ro.serialno` was unset, seeding the generator identically on every unit.
380
+ // Valid hex, so the format rule cannot catch it.
381
+ rejected: /* @__PURE__ */ new Set(["9774d56d682e549c"])
382
+ }
383
+ };
384
+ function isNotProgrammed(value) {
385
+ return NOT_PROGRAMMED_VALUES.has(value.toLowerCase()) || /^0+$/.test(value) || /^f+$/i.test(value);
386
+ }
387
+ function isUsableIdentity(name, value) {
388
+ var _a, _b, _c;
389
+ if (isNotProgrammed(value))
390
+ return false;
391
+ const constraint = IDENTITY_CONSTRAINTS[name];
392
+ if (!constraint)
393
+ return true;
394
+ return ((_b = (_a = constraint.format) == null ? void 0 : _a.test(value)) != null ? _b : true) && !((_c = constraint.rejected) == null ? void 0 : _c.has(value.toLowerCase()));
395
+ }
396
+ function canonicalizeParams(params) {
397
+ const kept = [];
398
+ const seen = /* @__PURE__ */ new Set();
399
+ for (const [name, rawValue] of params) {
400
+ const value = canonicalizeValue(rawValue);
401
+ if (value.length === 0)
402
+ continue;
403
+ if (IDENTIFYING_PARAMS.has(name) && !isUsableIdentity(name, value))
404
+ continue;
405
+ if (seen.has(name))
406
+ throw new Error(`Duplicate fingerprint parameter name: ${name}`);
407
+ seen.add(name);
408
+ kept.push([name, value]);
409
+ }
410
+ return kept;
411
+ }
412
+ function buildFingerprintMaterial(platform, params) {
413
+ const kept = canonicalizeParams(params);
414
+ if (kept.length === 0)
415
+ throw new InsufficientDeviceIdentityError(platform, "no identity parameter could be read");
416
+ if (SCOPED_PLATFORMS.has(platform) && kept.some(([name]) => name === "deviceName")) {
417
+ throw new InsufficientDeviceIdentityError(
418
+ platform,
419
+ "the host-name fallback is not available on this platform, where the host name is the same on every device"
420
+ );
421
+ }
422
+ if (!kept.some(([name]) => IDENTIFYING_PARAMS.has(name))) {
423
+ throw new InsufficientDeviceIdentityError(
424
+ platform,
425
+ `only model-level parameters could be read (${kept.map(([name]) => name).join(", ")}), none of which identify this individual machine`
426
+ );
427
+ }
428
+ const lines = [FINGERPRINT_PREFIX, `platform=${platform}`];
429
+ for (const [name, value] of kept)
430
+ lines.push(`${name}=${value}`);
431
+ return lines.join("\n");
432
+ }
433
+ function fingerprintDigest(material) {
434
+ return (0, import_node_crypto.createHash)("sha256").update(material, "utf8").digest("hex");
435
+ }
436
+ function stampDeviceId(digest, source = "identity") {
437
+ return `mbd${FINGERPRINT_VERSION}${SOURCE_TAGS[source]}_${digest}`;
438
+ }
439
+ function fingerprintDeviceId(material, source = "identity") {
440
+ return stampDeviceId(fingerprintDigest(material), source);
441
+ }
442
+ function parseDeviceIdStamp(deviceId) {
443
+ var _a;
444
+ const match = STAMP_PATTERN.exec(deviceId);
445
+ if (!match)
446
+ return null;
447
+ return {
448
+ version: Number(match[1]),
449
+ sourceTag: match[2],
450
+ source: (_a = SOURCE_BY_TAG.get(match[2])) != null ? _a : null,
451
+ digest: match[3]
452
+ };
453
+ }
454
+ function commandOutput(command) {
455
+ try {
456
+ return import_node_child_process.default.execSync(command, { encoding: "utf8" });
457
+ } catch (e) {
458
+ return "";
459
+ }
460
+ }
461
+ function readSysFile(path2) {
462
+ try {
463
+ return import_node_fs.default.readFileSync(path2, "utf8");
464
+ } catch (e) {
465
+ return "";
466
+ }
467
+ }
468
+ function parseIoregPlatformUuid(ioregOutput) {
469
+ const match = ioregOutput.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
470
+ return match ? match[1].replaceAll("-", "").toUpperCase() : "";
471
+ }
472
+ function readMacIdentity() {
473
+ const uuid = parseIoregPlatformUuid(commandOutput("ioreg -rd1 -c IOPlatformExpertDevice"));
474
+ const params = [["ioPlatformUuid", uuid]];
475
+ return { params, deviceName: import_node_os.default.hostname().replace(/\.local$/i, "") };
476
+ }
477
+ function selectMachineId(...sources) {
478
+ var _a;
479
+ return (_a = sources.map(canonicalizeValue).find((value) => /^[0-9a-f]{32}$/.test(value) && !isNotProgrammed(value))) != null ? _a : "";
480
+ }
481
+ function readMachineId() {
482
+ return selectMachineId(
483
+ readSysFile("/etc/machine-id"),
484
+ readSysFile("/var/lib/dbus/machine-id")
485
+ );
486
+ }
487
+ function readLinuxIdentity() {
488
+ const params = [
489
+ ["machineId", readMachineId()],
490
+ ["sysVendor", readSysFile("/sys/class/dmi/id/sys_vendor")],
491
+ ["productName", readSysFile("/sys/class/dmi/id/product_name")],
492
+ ["boardVendor", readSysFile("/sys/class/dmi/id/board_vendor")],
493
+ ["boardName", readSysFile("/sys/class/dmi/id/board_name")]
494
+ ];
495
+ return { params, deviceName: import_node_os.default.hostname() };
496
+ }
497
+ function parseSmbiosStructures(data) {
498
+ const structures = [];
499
+ let offset = 0;
500
+ while (offset + 4 <= data.length) {
501
+ const type = data[offset];
502
+ const length = data[offset + 1];
503
+ if (length < 4 || offset + length > data.length)
504
+ break;
505
+ const formatted = data.subarray(offset, offset + length);
506
+ const strings = [];
507
+ let p = offset + length;
508
+ if (p + 1 < data.length && data[p] === 0 && data[p + 1] === 0) {
509
+ p += 2;
510
+ } else {
511
+ while (p < data.length) {
512
+ let end = p;
513
+ while (end < data.length && data[end] !== 0)
514
+ end++;
515
+ strings.push(data.toString("latin1", p, end));
516
+ p = end + 1;
517
+ if (p < data.length && data[p] === 0) {
518
+ p += 1;
519
+ break;
520
+ }
521
+ }
522
+ }
523
+ structures.push({ type, formatted, strings });
524
+ if (type === 127)
525
+ break;
526
+ offset = p;
527
+ }
528
+ return structures;
529
+ }
530
+ function resolveSmbiosString(structure, fieldOffset) {
531
+ if (fieldOffset >= structure.formatted.length)
532
+ return "";
533
+ const index = structure.formatted[fieldOffset];
534
+ if (index === 0 || index > structure.strings.length)
535
+ return "";
536
+ return structure.strings[index - 1];
537
+ }
538
+ function formatSmbiosUuid(structure, fieldOffset) {
539
+ if (fieldOffset + 16 > structure.formatted.length)
540
+ return "";
541
+ const bytes = structure.formatted.subarray(fieldOffset, fieldOffset + 16);
542
+ if (bytes.every((byte) => byte === 0) || bytes.every((byte) => byte === 255))
543
+ return "";
544
+ let hex = "";
545
+ for (const byte of bytes)
546
+ hex += byte.toString(16).padStart(2, "0").toUpperCase();
547
+ return hex;
548
+ }
549
+ function parseSmbiosParams(smbiosData) {
550
+ const structures = parseSmbiosStructures(smbiosData);
551
+ const params = [];
552
+ const system = structures.find((structure) => structure.type === 1);
553
+ if (system) {
554
+ params.push(["systemManufacturer", resolveSmbiosString(system, 4)]);
555
+ params.push(["systemProductName", resolveSmbiosString(system, 5)]);
556
+ params.push(["systemUuid", formatSmbiosUuid(system, 8)]);
557
+ }
558
+ const baseboard = structures.find((structure) => structure.type === 2);
559
+ if (baseboard) {
560
+ params.push(["baseboardManufacturer", resolveSmbiosString(baseboard, 4)]);
561
+ params.push(["baseboardProduct", resolveSmbiosString(baseboard, 5)]);
562
+ params.push(["baseboardSerialNumber", resolveSmbiosString(baseboard, 7)]);
563
+ }
564
+ return params;
565
+ }
566
+ function readWindowsSmbios() {
567
+ const script = "[Convert]::ToBase64String((Get-CimInstance -Namespace root/wmi -ClassName MSSmBios_RawSMBiosTables).SMBiosData)";
568
+ const output = commandOutput(`powershell -NoProfile -NonInteractive -Command "${script}"`);
569
+ try {
570
+ return import_node_buffer.Buffer.from(output.trim(), "base64");
571
+ } catch (e) {
572
+ return import_node_buffer.Buffer.alloc(0);
573
+ }
574
+ }
575
+ function readWindowsIdentity() {
576
+ return { params: parseSmbiosParams(readWindowsSmbios()), deviceName: import_node_os.default.hostname() };
577
+ }
578
+ function defaultDeviceIdentityReader(platform = import_node_process.default.platform) {
579
+ const tag = platformTag(platform);
580
+ return {
581
+ read() {
582
+ switch (tag) {
583
+ case "mac":
584
+ return readMacIdentity();
585
+ case "linux":
586
+ return readLinuxIdentity();
587
+ case "windows":
588
+ return readWindowsIdentity();
589
+ // Everything else yields no identity params here, so it resolves to an
590
+ // insufficient-identity error unless the deviceName fallback is enabled.
591
+ // For bsd and unknown that matches the spec, which defines none. Android
592
+ // is different: the spec *does* define `androidId`, but it comes from
593
+ // `Settings.Secure.getString`, which is Android-framework API that a
594
+ // Node.js process cannot reach. Collecting it is the C++/.NET SDKs' job.
595
+ // A host that can bridge it should supply its own DeviceIdentityReader.
596
+ default:
597
+ return { params: [], deviceName: import_node_os.default.hostname() };
598
+ }
599
+ }
600
+ };
601
+ }
602
+
603
+ // src/deviceIdResolver.ts
604
+ var MoonbaseDeviceIdResolver = class {
605
+ constructor(options) {
606
+ var _a, _b, _c;
607
+ const platform = (_a = options == null ? void 0 : options.platform) != null ? _a : import_node_process2.default.platform;
608
+ this.platform = platformTag(platform);
609
+ this.reader = (_b = options == null ? void 0 : options.reader) != null ? _b : defaultDeviceIdentityReader(platform);
610
+ this.fallback = (_c = options == null ? void 0 : options.fallback) != null ? _c : "none";
611
+ }
612
+ async resolveDeviceName() {
613
+ return this.readIdentity().deviceName;
614
+ }
615
+ async resolveDeviceId() {
616
+ return this.compute().deviceId;
617
+ }
618
+ /**
619
+ * A fresh copy each call. The device binding is what activation sends and what
620
+ * validation compares, so handing out the resolver's own object would let a
621
+ * consumer that edits a diagnostic — or logs it through something that
622
+ * normalizes in place — silently change the id every later call returns.
623
+ */
624
+ async describeDevice() {
625
+ const described = this.compute();
626
+ return { ...described, paramNames: [...described.paramNames] };
627
+ }
628
+ compute() {
629
+ var _a;
630
+ (_a = this.described) != null ? _a : this.described = this.computeDescription();
631
+ return this.described;
632
+ }
633
+ /**
634
+ * Read identity at most once. Both halves of an activation request ask for it —
635
+ * the name and then the id — and a read can mean spawning `ioreg` or
636
+ * PowerShell, so reading per call would double the cost of every request and
637
+ * let the name and the id come from two different reads of the machine.
638
+ */
639
+ readIdentity() {
640
+ var _a;
641
+ (_a = this.identity) != null ? _a : this.identity = this.reader.read();
642
+ return this.identity;
643
+ }
644
+ computeDescription() {
645
+ const { params, deviceName } = this.readIdentity();
646
+ try {
647
+ return this.describe(params, identitySource(this.platform));
648
+ } catch (err) {
649
+ if (this.fallback !== "deviceName" || !(err instanceof InsufficientDeviceIdentityError))
650
+ throw err;
651
+ return this.describe([["deviceName", deviceName]], "deviceName");
652
+ }
653
+ }
654
+ describe(params, source) {
655
+ return {
656
+ deviceId: fingerprintDeviceId(buildFingerprintMaterial(this.platform, params), source),
657
+ version: FINGERPRINT_VERSION,
658
+ platform: this.platform,
659
+ source,
660
+ paramNames: canonicalizeParams(params).map(([name]) => name)
661
+ };
662
+ }
663
+ };
664
+ var MigratingDeviceIdResolver = class {
665
+ constructor(current, ...previous) {
666
+ this.current = current;
667
+ this.previous = previous;
668
+ const describe = current.describeDevice;
669
+ if (typeof describe === "function")
670
+ this.describeDevice = () => describe.call(current);
671
+ }
672
+ async resolveDeviceName() {
673
+ return this.current.resolveDeviceName();
674
+ }
675
+ async resolveDeviceId() {
676
+ return this.current.resolveDeviceId();
677
+ }
678
+ async acceptsDeviceId(deviceId) {
679
+ if (!this.previousIds) {
680
+ this.previousIds = Promise.all(this.previous.map(async (resolver) => {
681
+ try {
682
+ return await resolver.resolveDeviceId();
683
+ } catch (e) {
684
+ return "";
685
+ }
686
+ }));
687
+ }
688
+ return (await this.previousIds).some((previous) => previous.length > 0 && previous === deviceId);
689
+ }
690
+ };
691
+ var LegacyDeviceIdResolver = class {
209
692
  async resolveDeviceName() {
210
- switch (process.platform) {
693
+ switch (import_node_process2.default.platform) {
211
694
  case "darwin":
212
695
  try {
213
- return import_node_child_process.default.execSync("scutil --get ComputerName").toString().trim();
696
+ return import_node_child_process2.default.execSync("scutil --get ComputerName").toString().trim();
214
697
  } catch (e) {
215
- return import_node_os.default.hostname();
698
+ return import_node_os2.default.hostname();
216
699
  }
217
700
  case "win32":
218
- return process.env.COMPUTERNAME || import_node_os.default.hostname();
219
- case "linux":
220
- const prettyname = import_node_child_process.default.execSync("hostnamectl --pretty").toString().trim();
221
- return prettyname || import_node_os.default.hostname();
701
+ return import_node_process2.default.env.COMPUTERNAME || import_node_os2.default.hostname();
702
+ case "linux": {
703
+ const prettyname = import_node_child_process2.default.execSync("hostnamectl --pretty").toString().trim();
704
+ return prettyname || import_node_os2.default.hostname();
705
+ }
222
706
  default:
223
- return import_node_os.default.hostname();
707
+ return import_node_os2.default.hostname();
224
708
  }
225
709
  }
226
710
  async resolveDeviceId() {
227
711
  const [cpu, hw] = await Promise.all([import_systeminformation.default.cpu(), import_systeminformation.default.system()]);
228
712
  const parts = [
229
- import_node_os.default.hostname(),
713
+ import_node_os2.default.hostname(),
230
714
  hw.manufacturer,
231
715
  hw.model,
232
716
  hw.serial,
@@ -238,40 +722,15 @@ var DefaultDeviceIdResolver = class {
238
722
  cpu.family,
239
723
  cpu.model
240
724
  ];
241
- const hash = (0, import_node_crypto.createHash)("sha256").update(parts.join("")).digest();
725
+ const hash = (0, import_node_crypto2.createHash)("sha256").update(parts.join("")).digest();
242
726
  return hash.toString("base64").replaceAll("=", "");
243
727
  }
244
728
  };
245
729
 
246
730
  // src/store.ts
247
- var import_node_fs = __toESM(require("fs"), 1);
731
+ var import_node_fs2 = __toESM(require("fs"), 1);
248
732
  var import_promises = __toESM(require("fs/promises"), 1);
249
733
  var import_node_path = __toESM(require("path"), 1);
250
-
251
- // src/errors.ts
252
- var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
253
- ErrorType2["None"] = "None";
254
- ErrorType2["ApiError"] = "ApiError";
255
- ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
256
- ErrorType2["LicenseInvalid"] = "LicenseInvalid";
257
- ErrorType2["LicenseRevoked"] = "LicenseRevoked";
258
- ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
259
- ErrorType2["LicenseExpired"] = "LicenseExpired";
260
- return ErrorType2;
261
- })(ErrorType || {});
262
- var MoonbaseError = class extends Error {
263
- constructor(title, detail, type, inner) {
264
- super();
265
- this.title = title;
266
- this.detail = detail;
267
- this.type = type;
268
- this.inner = inner;
269
- this.name = "MoonbaseError";
270
- this.message = detail != null ? detail : title;
271
- }
272
- };
273
-
274
- // src/store.ts
275
734
  var InMemoryLicenseStore = class {
276
735
  async loadLocalLicense() {
277
736
  var _a;
@@ -289,7 +748,8 @@ var FileLicenseStore = class {
289
748
  this.options = options;
290
749
  }
291
750
  async loadLocalLicense() {
292
- if (!import_node_fs.default.existsSync(this.path)) return null;
751
+ if (!import_node_fs2.default.existsSync(this.path))
752
+ return null;
293
753
  try {
294
754
  const file = await import_promises.default.readFile(this.path, { encoding: "utf-8" });
295
755
  const license = licenseSchema.parse(JSON.parse(file));
@@ -302,7 +762,8 @@ var FileLicenseStore = class {
302
762
  await import_promises.default.writeFile(this.path, JSON.stringify(license), { encoding: "utf-8" });
303
763
  }
304
764
  async deleteLocalLicense() {
305
- if (!import_node_fs.default.existsSync(this.path)) return;
765
+ if (!import_node_fs2.default.existsSync(this.path))
766
+ return;
306
767
  await import_promises.default.rm(this.path);
307
768
  }
308
769
  get path() {
@@ -312,8 +773,8 @@ var FileLicenseStore = class {
312
773
  };
313
774
 
314
775
  // src/validator.ts
315
- var import_zod2 = require("zod");
316
776
  var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
777
+ var import_zod2 = require("zod");
317
778
  var licenseClaimsSchema = import_zod2.z.object({
318
779
  ["l:id" /* LicenseId */]: import_zod2.z.string(),
319
780
  ["trial" /* Trial */]: import_zod2.z.string().or(import_zod2.z.boolean()).transform((v) => v === true || v === "true").pipe(import_zod2.z.boolean()),
@@ -350,15 +811,20 @@ var LicenseValidator = class {
350
811
  );
351
812
  }
352
813
  const expectedSignature = await this.deviceIdResolver.resolveDeviceId();
353
- if (expectedSignature !== claims["sig" /* ComputerSignature */]) {
354
- throw new MoonbaseError(
355
- "License invalide",
356
- "This license is not for this device",
357
- "LicenseExpired" /* LicenseExpired */
358
- );
359
- }
814
+ const boundSignature = claims["sig" /* ComputerSignature */];
815
+ if (expectedSignature !== boundSignature && !await this.acceptsHistoricalDeviceId(boundSignature))
816
+ throw deviceMismatchError(expectedSignature, boundSignature);
360
817
  return license;
361
818
  }
819
+ /**
820
+ * Give a {@link IMigratingDeviceIdResolver} the chance to vouch for a device id
821
+ * this machine used to have. Only consulted after the fast path fails, so
822
+ * apps that have not opted into a migration pay nothing.
823
+ */
824
+ async acceptsHistoricalDeviceId(boundSignature) {
825
+ const accepts = this.deviceIdResolver.acceptsDeviceId;
826
+ return typeof accepts === "function" && await accepts.call(this.deviceIdResolver, boundSignature);
827
+ }
362
828
  parseLicenseToken(token) {
363
829
  return new Promise((resolve, reject) => {
364
830
  import_jsonwebtoken.default.verify(token, this.configuration.publicKey, {
@@ -423,11 +889,57 @@ var LicenseValidator = class {
423
889
  });
424
890
  }
425
891
  };
892
+ function deviceMismatchError(expected, bound) {
893
+ const detail = "This license is not for this device";
894
+ const stampNote = describeStampDifference(expected, bound);
895
+ return new MoonbaseError(
896
+ "License is for another device",
897
+ stampNote ? `${detail}. ${stampNote}` : detail,
898
+ "LicenseDeviceMismatch" /* LicenseDeviceMismatch */
899
+ );
900
+ }
901
+ function describeStampDifference(expected, bound) {
902
+ const expectedStamp = parseDeviceIdStamp(expected);
903
+ if (!expectedStamp)
904
+ return null;
905
+ const boundStamp = parseDeviceIdStamp(bound);
906
+ if (!boundStamp || boundStamp.version !== expectedStamp.version)
907
+ return describeVersionDifference(expectedStamp, boundStamp);
908
+ if (boundStamp.sourceTag !== expectedStamp.sourceTag)
909
+ return describeSourceDifference(expectedStamp, boundStamp);
910
+ return null;
911
+ }
912
+ function describeVersionDifference(expected, bound) {
913
+ if (bound && bound.version > expected.version) {
914
+ return `The binding was created by device fingerprint v${bound.version}, which is newer than the v${expected.version} this SDK computes \u2014 update the SDK rather than re-activating, which would rebind the device to the older algorithm.`;
915
+ }
916
+ const boundVersion = bound ? `device fingerprint v${bound.version}` : "an SDK predating versioned device fingerprints";
917
+ return `The binding was created by ${boundVersion}, while this SDK computes v${expected.version}, so this may instead be the same machine bound under the older algorithm \u2014 re-activate to find out, or configure a MigratingDeviceIdResolver to keep accepting the previous id.`;
918
+ }
919
+ function describeSourceDifference(expected, bound) {
920
+ if (bound.source === "scoped") {
921
+ return "The binding uses an app-scoped device identity, which cannot be compared with the id this SDK computes \u2014 not even on the same device. Re-activate here to bind this build.";
922
+ }
923
+ if (expected.source === "scoped") {
924
+ return "This SDK computes an app-scoped device identity, which cannot be compared with the one the binding carries \u2014 not even on the same device. Re-activate here to bind this app.";
925
+ }
926
+ if (bound.source === null) {
927
+ return `The binding carries the device identity tag "${bound.sourceTag}", which this SDK does not recognise \u2014 it was created by a newer Moonbase SDK, so update rather than re-activating.`;
928
+ }
929
+ if (expected.source === null) {
930
+ return `The id this SDK computes carries the device identity tag "${expected.sourceTag}", which the spec does not define \u2014 it came from a custom device id resolver, so check that resolver rather than the binding.`;
931
+ }
932
+ if (bound.source === "deviceName") {
933
+ return "The binding was created from the host-name fallback, while this SDK reads hardware identity, so this may instead be the same machine bound while no hardware identity could be read \u2014 re-activate to find out.";
934
+ }
935
+ return "The binding was created from hardware identity, while this SDK has fallen back to the host name \u2014 check why hardware identity cannot be read here rather than re-activating, which would rebind the device to the weaker id.";
936
+ }
426
937
 
427
938
  // src/index.ts
428
939
  function detectPlatform() {
429
- if (typeof process === "undefined") return void 0;
430
- switch (process.platform) {
940
+ if (typeof import_node_process3.default === "undefined")
941
+ return void 0;
942
+ switch (import_node_process3.default.platform) {
431
943
  case "darwin":
432
944
  return "Mac";
433
945
  case "win32":
@@ -447,7 +959,7 @@ var MoonbaseLicensing = class {
447
959
  platform: configuration.platform === void 0 ? detectPlatform() : configuration.platform
448
960
  };
449
961
  this.store = (_a = configuration.licenseStore) != null ? _a : new InMemoryLicenseStore();
450
- this.deviceIdResolver = (_b = configuration.deivceIdResolver) != null ? _b : new DefaultDeviceIdResolver();
962
+ this.deviceIdResolver = (_b = configuration.deviceIdResolver) != null ? _b : new MoonbaseDeviceIdResolver();
451
963
  this.validator = new LicenseValidator(this.configuration, this.deviceIdResolver);
452
964
  this.client = new LicenseClient(this.configuration, this.deviceIdResolver, this.validator);
453
965
  }
@@ -459,7 +971,7 @@ var MoonbaseLicensing = class {
459
971
  format: "JWT"
460
972
  };
461
973
  const json = JSON.stringify(token);
462
- return Buffer.from(Buffer.from(json).toString("base64"));
974
+ return import_node_buffer2.Buffer.from(import_node_buffer2.Buffer.from(json).toString("base64"));
463
975
  }
464
976
  async readRawLicense(license) {
465
977
  return await this.client.validateRawLicense(license);
@@ -468,12 +980,33 @@ var MoonbaseLicensing = class {
468
980
  // Annotate the CommonJS export names for ESM import in node:
469
981
  0 && (module.exports = {
470
982
  ActivationMethod,
471
- DefaultDeviceIdResolver,
983
+ DEVICE_ID_SOURCE_TAGS,
472
984
  ErrorType,
985
+ FINGERPRINT_PREFIX,
986
+ FINGERPRINT_VERSION,
473
987
  FileLicenseStore,
988
+ IDENTIFYING_PARAM_NAMES,
474
989
  InMemoryLicenseStore,
990
+ InsufficientDeviceIdentityError,
991
+ LegacyDeviceIdResolver,
475
992
  LicenseClient,
476
993
  LicenseValidator,
994
+ MAX_VALUE_LENGTH,
995
+ MigratingDeviceIdResolver,
996
+ MoonbaseDeviceIdResolver,
477
997
  MoonbaseError,
478
- MoonbaseLicensing
998
+ MoonbaseLicensing,
999
+ buildFingerprintMaterial,
1000
+ canonicalizeParams,
1001
+ canonicalizeValue,
1002
+ defaultDeviceIdentityReader,
1003
+ fingerprintDeviceId,
1004
+ fingerprintDigest,
1005
+ identitySource,
1006
+ parseDeviceIdStamp,
1007
+ parseIoregPlatformUuid,
1008
+ parseSmbiosParams,
1009
+ platformTag,
1010
+ selectMachineId,
1011
+ stampDeviceId
479
1012
  });