@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.js CHANGED
@@ -1,6 +1,13 @@
1
+ // src/index.ts
2
+ import { Buffer as Buffer2 } from "buffer";
3
+ import process3 from "process";
4
+
1
5
  // src/client.ts
2
6
  import fetch from "cross-fetch";
3
7
 
8
+ // src/schemas.ts
9
+ import { z } from "zod";
10
+
4
11
  // src/types.ts
5
12
  var ActivationMethod = /* @__PURE__ */ ((ActivationMethod2) => {
6
13
  ActivationMethod2["Online"] = "Online";
@@ -9,7 +16,6 @@ var ActivationMethod = /* @__PURE__ */ ((ActivationMethod2) => {
9
16
  })(ActivationMethod || {});
10
17
 
11
18
  // src/schemas.ts
12
- import { z } from "zod";
13
19
  var activationRequestResponseSchema = z.object({
14
20
  id: z.string(),
15
21
  request: z.string(),
@@ -63,7 +69,7 @@ var LicenseClient = class {
63
69
  deviceName: await this.deviceIdResolver.resolveDeviceName(),
64
70
  deviceSignature: await this.deviceIdResolver.resolveDeviceId()
65
71
  };
66
- const response = await fetch(this.configuration.endpoint + `/api/client/activations/${this.configuration.productId}/request${this.buildQueryString()}`, {
72
+ const response = await fetch(`${this.configuration.endpoint}/api/client/activations/${this.configuration.productId}/request${this.buildQueryString()}`, {
67
73
  ...defaultFetchOptions,
68
74
  method: "POST",
69
75
  body: JSON.stringify(content)
@@ -85,7 +91,8 @@ var LicenseClient = class {
85
91
  }
86
92
  async getRequestedActivation(request) {
87
93
  const response = await fetch(request.request, defaultFetchOptions);
88
- if (response.status === 204 || response.status === 404) return null;
94
+ if (response.status === 204 || response.status === 404)
95
+ return null;
89
96
  return await this.handleLicenseResponse(response);
90
97
  }
91
98
  async requestTrial() {
@@ -93,7 +100,7 @@ var LicenseClient = class {
93
100
  deviceName: await this.deviceIdResolver.resolveDeviceName(),
94
101
  deviceSignature: await this.deviceIdResolver.resolveDeviceId()
95
102
  };
96
- const response = await fetch(this.configuration.endpoint + `/api/client/trials/${this.configuration.productId}/request${this.buildQueryString()}`, {
103
+ const response = await fetch(`${this.configuration.endpoint}/api/client/trials/${this.configuration.productId}/request${this.buildQueryString()}`, {
97
104
  ...defaultFetchOptions,
98
105
  method: "POST",
99
106
  headers: {
@@ -105,7 +112,7 @@ var LicenseClient = class {
105
112
  return await this.handleLicenseResponse(response);
106
113
  }
107
114
  async validateLicense(license) {
108
- const response = await fetch(this.configuration.endpoint + `/api/client/licenses/${this.configuration.productId}/validate${this.buildQueryString()}`, {
115
+ const response = await fetch(`${this.configuration.endpoint}/api/client/licenses/${this.configuration.productId}/validate${this.buildQueryString()}`, {
109
116
  ...defaultFetchOptions,
110
117
  method: "POST",
111
118
  headers: {
@@ -118,11 +125,12 @@ var LicenseClient = class {
118
125
  }
119
126
  async validateRawLicense(rawLicense) {
120
127
  const license = await this.licenseValidator.validateLicense(rawLicense.toString("utf8"));
121
- if (license.activationMethod === "Offline" /* Offline */) return license;
128
+ if (license.activationMethod === "Offline" /* Offline */)
129
+ return license;
122
130
  return await this.validateLicense(license);
123
131
  }
124
132
  async revokeLicense(license) {
125
- const response = await fetch(this.configuration.endpoint + `/api/client/licenses/${this.configuration.productId}/revoke?format=JWT`, {
133
+ const response = await fetch(`${this.configuration.endpoint}/api/client/licenses/${this.configuration.productId}/revoke?format=JWT`, {
126
134
  ...defaultFetchOptions,
127
135
  method: "POST",
128
136
  headers: {
@@ -144,11 +152,14 @@ var LicenseClient = class {
144
152
  buildQueryString() {
145
153
  const parts = ["format=JWT"];
146
154
  const { platform, appVersion, metadata } = this.configuration;
147
- if (platform) parts.push(`platform=${encodeURIComponent(platform)}`);
148
- if (appVersion) parts.push(`appVersion=${encodeURIComponent(appVersion)}`);
155
+ if (platform)
156
+ parts.push(`platform=${encodeURIComponent(platform)}`);
157
+ if (appVersion)
158
+ parts.push(`appVersion=${encodeURIComponent(appVersion)}`);
149
159
  if (metadata) {
150
160
  for (const [key, value] of Object.entries(metadata)) {
151
- if (value === null || value === void 0 || value === "") continue;
161
+ if (value === null || value === void 0 || value === "")
162
+ continue;
152
163
  parts.push(`meta[${encodeURIComponent(key)}]=${encodeURIComponent(value)}`);
153
164
  }
154
165
  }
@@ -157,32 +168,486 @@ var LicenseClient = class {
157
168
  };
158
169
 
159
170
  // src/deviceIdResolver.ts
171
+ import cp2 from "child_process";
172
+ import { createHash as createHash2 } from "crypto";
173
+ import os2 from "os";
174
+ import process2 from "process";
160
175
  import si from "systeminformation";
161
- import os from "os";
176
+
177
+ // src/errors.ts
178
+ var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
179
+ ErrorType2["None"] = "None";
180
+ ErrorType2["ApiError"] = "ApiError";
181
+ ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
182
+ ErrorType2["LicenseInvalid"] = "LicenseInvalid";
183
+ ErrorType2["LicenseRevoked"] = "LicenseRevoked";
184
+ ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
185
+ ErrorType2["LicenseExpired"] = "LicenseExpired";
186
+ ErrorType2["LicenseDeviceMismatch"] = "LicenseDeviceMismatch";
187
+ ErrorType2["DeviceIdentityUnavailable"] = "DeviceIdentityUnavailable";
188
+ return ErrorType2;
189
+ })(ErrorType || {});
190
+ var MoonbaseError = class extends Error {
191
+ constructor(title, detail, type, inner) {
192
+ super();
193
+ this.title = title;
194
+ this.detail = detail;
195
+ this.type = type;
196
+ this.inner = inner;
197
+ this.name = "MoonbaseError";
198
+ this.message = detail != null ? detail : title;
199
+ }
200
+ };
201
+ var InsufficientDeviceIdentityError = class extends MoonbaseError {
202
+ constructor(platform, reason = "no identity parameter could be read") {
203
+ super(
204
+ "No device identity",
205
+ `Could not identify this device (platform: ${platform}): ${reason}`,
206
+ "DeviceIdentityUnavailable" /* DeviceIdentityUnavailable */
207
+ );
208
+ this.platform = platform;
209
+ this.name = "InsufficientDeviceIdentityError";
210
+ }
211
+ };
212
+
213
+ // src/fingerprint.ts
214
+ import { Buffer } from "buffer";
162
215
  import cp from "child_process";
163
216
  import { createHash } from "crypto";
164
- var DefaultDeviceIdResolver = class {
217
+ import fs from "fs";
218
+ import os from "os";
219
+ import process from "process";
220
+ var FINGERPRINT_PREFIX = "moonbase:fingerprint:v2";
221
+ var FINGERPRINT_VERSION = 2;
222
+ var MAX_VALUE_LENGTH = 128;
223
+ var PRINTABLE_ASCII_MIN = 32;
224
+ var PRINTABLE_ASCII_MAX = 126;
225
+ var SOURCE_TAGS = {
226
+ identity: "",
227
+ deviceName: "n",
228
+ scoped: "s"
229
+ };
230
+ var DEVICE_ID_SOURCE_TAGS = Object.freeze({ ...SOURCE_TAGS });
231
+ var SOURCE_BY_TAG = new Map(
232
+ Object.entries(SOURCE_TAGS).map(([source, tag]) => [tag, source])
233
+ );
234
+ var SCOPED_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
235
+ function identitySource(platform) {
236
+ return SCOPED_PLATFORMS.has(platform) ? "scoped" : "identity";
237
+ }
238
+ var STAMP_PATTERN = /^mbd(\d+)([a-z]*)_([0-9a-f]{64})$/;
239
+ function canonicalizeValue(value) {
240
+ var _a;
241
+ let printable = "";
242
+ for (const char of value.normalize("NFC")) {
243
+ const code = (_a = char.codePointAt(0)) != null ? _a : 0;
244
+ if (code >= PRINTABLE_ASCII_MIN && code <= PRINTABLE_ASCII_MAX)
245
+ printable += char;
246
+ }
247
+ return printable.slice(0, MAX_VALUE_LENGTH).replace(/^ +| +$/g, "");
248
+ }
249
+ var PLATFORM_TAGS = /* @__PURE__ */ new Set(
250
+ ["mac", "ios", "windows", "android", "linux", "bsd", "unknown"]
251
+ );
252
+ function isPlatformTag(value) {
253
+ return PLATFORM_TAGS.has(value);
254
+ }
255
+ function platformTag(platform = process.platform) {
256
+ switch (platform) {
257
+ case "darwin":
258
+ return "mac";
259
+ case "win32":
260
+ return "windows";
261
+ case "android":
262
+ return "android";
263
+ case "linux":
264
+ return "linux";
265
+ case "freebsd":
266
+ case "openbsd":
267
+ case "netbsd":
268
+ return "bsd";
269
+ default:
270
+ return isPlatformTag(platform) ? platform : "unknown";
271
+ }
272
+ }
273
+ var IDENTIFYING_PARAMS = /* @__PURE__ */ new Set([
274
+ "ioPlatformUuid",
275
+ "machineId",
276
+ "systemUuid",
277
+ "baseboardSerialNumber",
278
+ "identifierForVendor",
279
+ "androidId",
280
+ "deviceName"
281
+ ]);
282
+ var IDENTIFYING_PARAM_NAMES = Object.freeze([...IDENTIFYING_PARAMS]);
283
+ var NOT_PROGRAMMED_VALUES = /* @__PURE__ */ new Set([
284
+ "to be filled by o.e.m.",
285
+ // The same filler without the dots, which plenty of firmware writes instead.
286
+ "to be filled by oem",
287
+ "default string",
288
+ "system serial number",
289
+ "base board serial number",
290
+ "chassis serial number",
291
+ "not specified",
292
+ "not applicable",
293
+ "not available",
294
+ "none",
295
+ "unknown",
296
+ "invalid",
297
+ "n/a",
298
+ "0123456789",
299
+ // `machine-id(5)`: the literal marker systemd writes to say "no id yet",
300
+ // e.g. in an initrd or a golden image awaiting first boot. Every machine
301
+ // deployed from such an image reads it, so it is the opposite of an identifier.
302
+ "uninitialized"
303
+ ]);
304
+ var IDENTITY_CONSTRAINTS = {
305
+ androidId: {
306
+ // Reading the *static field* `Settings.Secure.ANDROID_ID` instead of calling
307
+ // `getString` with it yields the key name "android_id", identical on every
308
+ // device, so a single activation would unlock a whole Android install base.
309
+ // That string is not hex, so this stops it reaching the material.
310
+ //
311
+ // The bound is 1,16 and not 16 because AOSP before 8.0 generated the value
312
+ // with `Long.toHexString`, which drops leading zeros — a strict 16 would
313
+ // reject legitimate ids on roughly one in sixteen pre-Oreo devices.
314
+ format: /^[0-9a-f]{1,16}$/,
315
+ // A real ANDROID_ID shared by a large batch of 2010-era devices whose
316
+ // `ro.serialno` was unset, seeding the generator identically on every unit.
317
+ // Valid hex, so the format rule cannot catch it.
318
+ rejected: /* @__PURE__ */ new Set(["9774d56d682e549c"])
319
+ }
320
+ };
321
+ function isNotProgrammed(value) {
322
+ return NOT_PROGRAMMED_VALUES.has(value.toLowerCase()) || /^0+$/.test(value) || /^f+$/i.test(value);
323
+ }
324
+ function isUsableIdentity(name, value) {
325
+ var _a, _b, _c;
326
+ if (isNotProgrammed(value))
327
+ return false;
328
+ const constraint = IDENTITY_CONSTRAINTS[name];
329
+ if (!constraint)
330
+ return true;
331
+ return ((_b = (_a = constraint.format) == null ? void 0 : _a.test(value)) != null ? _b : true) && !((_c = constraint.rejected) == null ? void 0 : _c.has(value.toLowerCase()));
332
+ }
333
+ function canonicalizeParams(params) {
334
+ const kept = [];
335
+ const seen = /* @__PURE__ */ new Set();
336
+ for (const [name, rawValue] of params) {
337
+ const value = canonicalizeValue(rawValue);
338
+ if (value.length === 0)
339
+ continue;
340
+ if (IDENTIFYING_PARAMS.has(name) && !isUsableIdentity(name, value))
341
+ continue;
342
+ if (seen.has(name))
343
+ throw new Error(`Duplicate fingerprint parameter name: ${name}`);
344
+ seen.add(name);
345
+ kept.push([name, value]);
346
+ }
347
+ return kept;
348
+ }
349
+ function buildFingerprintMaterial(platform, params) {
350
+ const kept = canonicalizeParams(params);
351
+ if (kept.length === 0)
352
+ throw new InsufficientDeviceIdentityError(platform, "no identity parameter could be read");
353
+ if (SCOPED_PLATFORMS.has(platform) && kept.some(([name]) => name === "deviceName")) {
354
+ throw new InsufficientDeviceIdentityError(
355
+ platform,
356
+ "the host-name fallback is not available on this platform, where the host name is the same on every device"
357
+ );
358
+ }
359
+ if (!kept.some(([name]) => IDENTIFYING_PARAMS.has(name))) {
360
+ throw new InsufficientDeviceIdentityError(
361
+ platform,
362
+ `only model-level parameters could be read (${kept.map(([name]) => name).join(", ")}), none of which identify this individual machine`
363
+ );
364
+ }
365
+ const lines = [FINGERPRINT_PREFIX, `platform=${platform}`];
366
+ for (const [name, value] of kept)
367
+ lines.push(`${name}=${value}`);
368
+ return lines.join("\n");
369
+ }
370
+ function fingerprintDigest(material) {
371
+ return createHash("sha256").update(material, "utf8").digest("hex");
372
+ }
373
+ function stampDeviceId(digest, source = "identity") {
374
+ return `mbd${FINGERPRINT_VERSION}${SOURCE_TAGS[source]}_${digest}`;
375
+ }
376
+ function fingerprintDeviceId(material, source = "identity") {
377
+ return stampDeviceId(fingerprintDigest(material), source);
378
+ }
379
+ function parseDeviceIdStamp(deviceId) {
380
+ var _a;
381
+ const match = STAMP_PATTERN.exec(deviceId);
382
+ if (!match)
383
+ return null;
384
+ return {
385
+ version: Number(match[1]),
386
+ sourceTag: match[2],
387
+ source: (_a = SOURCE_BY_TAG.get(match[2])) != null ? _a : null,
388
+ digest: match[3]
389
+ };
390
+ }
391
+ function commandOutput(command) {
392
+ try {
393
+ return cp.execSync(command, { encoding: "utf8" });
394
+ } catch (e) {
395
+ return "";
396
+ }
397
+ }
398
+ function readSysFile(path2) {
399
+ try {
400
+ return fs.readFileSync(path2, "utf8");
401
+ } catch (e) {
402
+ return "";
403
+ }
404
+ }
405
+ function parseIoregPlatformUuid(ioregOutput) {
406
+ const match = ioregOutput.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
407
+ return match ? match[1].replaceAll("-", "").toUpperCase() : "";
408
+ }
409
+ function readMacIdentity() {
410
+ const uuid = parseIoregPlatformUuid(commandOutput("ioreg -rd1 -c IOPlatformExpertDevice"));
411
+ const params = [["ioPlatformUuid", uuid]];
412
+ return { params, deviceName: os.hostname().replace(/\.local$/i, "") };
413
+ }
414
+ function selectMachineId(...sources) {
415
+ var _a;
416
+ return (_a = sources.map(canonicalizeValue).find((value) => /^[0-9a-f]{32}$/.test(value) && !isNotProgrammed(value))) != null ? _a : "";
417
+ }
418
+ function readMachineId() {
419
+ return selectMachineId(
420
+ readSysFile("/etc/machine-id"),
421
+ readSysFile("/var/lib/dbus/machine-id")
422
+ );
423
+ }
424
+ function readLinuxIdentity() {
425
+ const params = [
426
+ ["machineId", readMachineId()],
427
+ ["sysVendor", readSysFile("/sys/class/dmi/id/sys_vendor")],
428
+ ["productName", readSysFile("/sys/class/dmi/id/product_name")],
429
+ ["boardVendor", readSysFile("/sys/class/dmi/id/board_vendor")],
430
+ ["boardName", readSysFile("/sys/class/dmi/id/board_name")]
431
+ ];
432
+ return { params, deviceName: os.hostname() };
433
+ }
434
+ function parseSmbiosStructures(data) {
435
+ const structures = [];
436
+ let offset = 0;
437
+ while (offset + 4 <= data.length) {
438
+ const type = data[offset];
439
+ const length = data[offset + 1];
440
+ if (length < 4 || offset + length > data.length)
441
+ break;
442
+ const formatted = data.subarray(offset, offset + length);
443
+ const strings = [];
444
+ let p = offset + length;
445
+ if (p + 1 < data.length && data[p] === 0 && data[p + 1] === 0) {
446
+ p += 2;
447
+ } else {
448
+ while (p < data.length) {
449
+ let end = p;
450
+ while (end < data.length && data[end] !== 0)
451
+ end++;
452
+ strings.push(data.toString("latin1", p, end));
453
+ p = end + 1;
454
+ if (p < data.length && data[p] === 0) {
455
+ p += 1;
456
+ break;
457
+ }
458
+ }
459
+ }
460
+ structures.push({ type, formatted, strings });
461
+ if (type === 127)
462
+ break;
463
+ offset = p;
464
+ }
465
+ return structures;
466
+ }
467
+ function resolveSmbiosString(structure, fieldOffset) {
468
+ if (fieldOffset >= structure.formatted.length)
469
+ return "";
470
+ const index = structure.formatted[fieldOffset];
471
+ if (index === 0 || index > structure.strings.length)
472
+ return "";
473
+ return structure.strings[index - 1];
474
+ }
475
+ function formatSmbiosUuid(structure, fieldOffset) {
476
+ if (fieldOffset + 16 > structure.formatted.length)
477
+ return "";
478
+ const bytes = structure.formatted.subarray(fieldOffset, fieldOffset + 16);
479
+ if (bytes.every((byte) => byte === 0) || bytes.every((byte) => byte === 255))
480
+ return "";
481
+ let hex = "";
482
+ for (const byte of bytes)
483
+ hex += byte.toString(16).padStart(2, "0").toUpperCase();
484
+ return hex;
485
+ }
486
+ function parseSmbiosParams(smbiosData) {
487
+ const structures = parseSmbiosStructures(smbiosData);
488
+ const params = [];
489
+ const system = structures.find((structure) => structure.type === 1);
490
+ if (system) {
491
+ params.push(["systemManufacturer", resolveSmbiosString(system, 4)]);
492
+ params.push(["systemProductName", resolveSmbiosString(system, 5)]);
493
+ params.push(["systemUuid", formatSmbiosUuid(system, 8)]);
494
+ }
495
+ const baseboard = structures.find((structure) => structure.type === 2);
496
+ if (baseboard) {
497
+ params.push(["baseboardManufacturer", resolveSmbiosString(baseboard, 4)]);
498
+ params.push(["baseboardProduct", resolveSmbiosString(baseboard, 5)]);
499
+ params.push(["baseboardSerialNumber", resolveSmbiosString(baseboard, 7)]);
500
+ }
501
+ return params;
502
+ }
503
+ function readWindowsSmbios() {
504
+ const script = "[Convert]::ToBase64String((Get-CimInstance -Namespace root/wmi -ClassName MSSmBios_RawSMBiosTables).SMBiosData)";
505
+ const output = commandOutput(`powershell -NoProfile -NonInteractive -Command "${script}"`);
506
+ try {
507
+ return Buffer.from(output.trim(), "base64");
508
+ } catch (e) {
509
+ return Buffer.alloc(0);
510
+ }
511
+ }
512
+ function readWindowsIdentity() {
513
+ return { params: parseSmbiosParams(readWindowsSmbios()), deviceName: os.hostname() };
514
+ }
515
+ function defaultDeviceIdentityReader(platform = process.platform) {
516
+ const tag = platformTag(platform);
517
+ return {
518
+ read() {
519
+ switch (tag) {
520
+ case "mac":
521
+ return readMacIdentity();
522
+ case "linux":
523
+ return readLinuxIdentity();
524
+ case "windows":
525
+ return readWindowsIdentity();
526
+ // Everything else yields no identity params here, so it resolves to an
527
+ // insufficient-identity error unless the deviceName fallback is enabled.
528
+ // For bsd and unknown that matches the spec, which defines none. Android
529
+ // is different: the spec *does* define `androidId`, but it comes from
530
+ // `Settings.Secure.getString`, which is Android-framework API that a
531
+ // Node.js process cannot reach. Collecting it is the C++/.NET SDKs' job.
532
+ // A host that can bridge it should supply its own DeviceIdentityReader.
533
+ default:
534
+ return { params: [], deviceName: os.hostname() };
535
+ }
536
+ }
537
+ };
538
+ }
539
+
540
+ // src/deviceIdResolver.ts
541
+ var MoonbaseDeviceIdResolver = class {
542
+ constructor(options) {
543
+ var _a, _b, _c;
544
+ const platform = (_a = options == null ? void 0 : options.platform) != null ? _a : process2.platform;
545
+ this.platform = platformTag(platform);
546
+ this.reader = (_b = options == null ? void 0 : options.reader) != null ? _b : defaultDeviceIdentityReader(platform);
547
+ this.fallback = (_c = options == null ? void 0 : options.fallback) != null ? _c : "none";
548
+ }
549
+ async resolveDeviceName() {
550
+ return this.readIdentity().deviceName;
551
+ }
552
+ async resolveDeviceId() {
553
+ return this.compute().deviceId;
554
+ }
555
+ /**
556
+ * A fresh copy each call. The device binding is what activation sends and what
557
+ * validation compares, so handing out the resolver's own object would let a
558
+ * consumer that edits a diagnostic — or logs it through something that
559
+ * normalizes in place — silently change the id every later call returns.
560
+ */
561
+ async describeDevice() {
562
+ const described = this.compute();
563
+ return { ...described, paramNames: [...described.paramNames] };
564
+ }
565
+ compute() {
566
+ var _a;
567
+ (_a = this.described) != null ? _a : this.described = this.computeDescription();
568
+ return this.described;
569
+ }
570
+ /**
571
+ * Read identity at most once. Both halves of an activation request ask for it —
572
+ * the name and then the id — and a read can mean spawning `ioreg` or
573
+ * PowerShell, so reading per call would double the cost of every request and
574
+ * let the name and the id come from two different reads of the machine.
575
+ */
576
+ readIdentity() {
577
+ var _a;
578
+ (_a = this.identity) != null ? _a : this.identity = this.reader.read();
579
+ return this.identity;
580
+ }
581
+ computeDescription() {
582
+ const { params, deviceName } = this.readIdentity();
583
+ try {
584
+ return this.describe(params, identitySource(this.platform));
585
+ } catch (err) {
586
+ if (this.fallback !== "deviceName" || !(err instanceof InsufficientDeviceIdentityError))
587
+ throw err;
588
+ return this.describe([["deviceName", deviceName]], "deviceName");
589
+ }
590
+ }
591
+ describe(params, source) {
592
+ return {
593
+ deviceId: fingerprintDeviceId(buildFingerprintMaterial(this.platform, params), source),
594
+ version: FINGERPRINT_VERSION,
595
+ platform: this.platform,
596
+ source,
597
+ paramNames: canonicalizeParams(params).map(([name]) => name)
598
+ };
599
+ }
600
+ };
601
+ var MigratingDeviceIdResolver = class {
602
+ constructor(current, ...previous) {
603
+ this.current = current;
604
+ this.previous = previous;
605
+ const describe = current.describeDevice;
606
+ if (typeof describe === "function")
607
+ this.describeDevice = () => describe.call(current);
608
+ }
609
+ async resolveDeviceName() {
610
+ return this.current.resolveDeviceName();
611
+ }
612
+ async resolveDeviceId() {
613
+ return this.current.resolveDeviceId();
614
+ }
615
+ async acceptsDeviceId(deviceId) {
616
+ if (!this.previousIds) {
617
+ this.previousIds = Promise.all(this.previous.map(async (resolver) => {
618
+ try {
619
+ return await resolver.resolveDeviceId();
620
+ } catch (e) {
621
+ return "";
622
+ }
623
+ }));
624
+ }
625
+ return (await this.previousIds).some((previous) => previous.length > 0 && previous === deviceId);
626
+ }
627
+ };
628
+ var LegacyDeviceIdResolver = class {
165
629
  async resolveDeviceName() {
166
- switch (process.platform) {
630
+ switch (process2.platform) {
167
631
  case "darwin":
168
632
  try {
169
- return cp.execSync("scutil --get ComputerName").toString().trim();
633
+ return cp2.execSync("scutil --get ComputerName").toString().trim();
170
634
  } catch (e) {
171
- return os.hostname();
635
+ return os2.hostname();
172
636
  }
173
637
  case "win32":
174
- return process.env.COMPUTERNAME || os.hostname();
175
- case "linux":
176
- const prettyname = cp.execSync("hostnamectl --pretty").toString().trim();
177
- return prettyname || os.hostname();
638
+ return process2.env.COMPUTERNAME || os2.hostname();
639
+ case "linux": {
640
+ const prettyname = cp2.execSync("hostnamectl --pretty").toString().trim();
641
+ return prettyname || os2.hostname();
642
+ }
178
643
  default:
179
- return os.hostname();
644
+ return os2.hostname();
180
645
  }
181
646
  }
182
647
  async resolveDeviceId() {
183
648
  const [cpu, hw] = await Promise.all([si.cpu(), si.system()]);
184
649
  const parts = [
185
- os.hostname(),
650
+ os2.hostname(),
186
651
  hw.manufacturer,
187
652
  hw.model,
188
653
  hw.serial,
@@ -194,40 +659,15 @@ var DefaultDeviceIdResolver = class {
194
659
  cpu.family,
195
660
  cpu.model
196
661
  ];
197
- const hash = createHash("sha256").update(parts.join("")).digest();
662
+ const hash = createHash2("sha256").update(parts.join("")).digest();
198
663
  return hash.toString("base64").replaceAll("=", "");
199
664
  }
200
665
  };
201
666
 
202
667
  // src/store.ts
203
668
  import fsSync from "fs";
204
- import fs from "fs/promises";
669
+ import fs2 from "fs/promises";
205
670
  import path from "path";
206
-
207
- // src/errors.ts
208
- var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
209
- ErrorType2["None"] = "None";
210
- ErrorType2["ApiError"] = "ApiError";
211
- ErrorType2["NoEligibleLicense"] = "NoEligibleLicense";
212
- ErrorType2["LicenseInvalid"] = "LicenseInvalid";
213
- ErrorType2["LicenseRevoked"] = "LicenseRevoked";
214
- ErrorType2["LicenseActivationRevoked"] = "LicenseActivationRevoked";
215
- ErrorType2["LicenseExpired"] = "LicenseExpired";
216
- return ErrorType2;
217
- })(ErrorType || {});
218
- var MoonbaseError = class extends Error {
219
- constructor(title, detail, type, inner) {
220
- super();
221
- this.title = title;
222
- this.detail = detail;
223
- this.type = type;
224
- this.inner = inner;
225
- this.name = "MoonbaseError";
226
- this.message = detail != null ? detail : title;
227
- }
228
- };
229
-
230
- // src/store.ts
231
671
  var InMemoryLicenseStore = class {
232
672
  async loadLocalLicense() {
233
673
  var _a;
@@ -245,9 +685,10 @@ var FileLicenseStore = class {
245
685
  this.options = options;
246
686
  }
247
687
  async loadLocalLicense() {
248
- if (!fsSync.existsSync(this.path)) return null;
688
+ if (!fsSync.existsSync(this.path))
689
+ return null;
249
690
  try {
250
- const file = await fs.readFile(this.path, { encoding: "utf-8" });
691
+ const file = await fs2.readFile(this.path, { encoding: "utf-8" });
251
692
  const license = licenseSchema.parse(JSON.parse(file));
252
693
  return license;
253
694
  } catch (err) {
@@ -255,11 +696,12 @@ var FileLicenseStore = class {
255
696
  }
256
697
  }
257
698
  async storeLocalLicense(license) {
258
- await fs.writeFile(this.path, JSON.stringify(license), { encoding: "utf-8" });
699
+ await fs2.writeFile(this.path, JSON.stringify(license), { encoding: "utf-8" });
259
700
  }
260
701
  async deleteLocalLicense() {
261
- if (!fsSync.existsSync(this.path)) return;
262
- await fs.rm(this.path);
702
+ if (!fsSync.existsSync(this.path))
703
+ return;
704
+ await fs2.rm(this.path);
263
705
  }
264
706
  get path() {
265
707
  var _a, _b, _c, _d;
@@ -268,8 +710,8 @@ var FileLicenseStore = class {
268
710
  };
269
711
 
270
712
  // src/validator.ts
271
- import { z as z2 } from "zod";
272
713
  import jwt from "jsonwebtoken";
714
+ import { z as z2 } from "zod";
273
715
  var licenseClaimsSchema = z2.object({
274
716
  ["l:id" /* LicenseId */]: z2.string(),
275
717
  ["trial" /* Trial */]: z2.string().or(z2.boolean()).transform((v) => v === true || v === "true").pipe(z2.boolean()),
@@ -306,15 +748,20 @@ var LicenseValidator = class {
306
748
  );
307
749
  }
308
750
  const expectedSignature = await this.deviceIdResolver.resolveDeviceId();
309
- if (expectedSignature !== claims["sig" /* ComputerSignature */]) {
310
- throw new MoonbaseError(
311
- "License invalide",
312
- "This license is not for this device",
313
- "LicenseExpired" /* LicenseExpired */
314
- );
315
- }
751
+ const boundSignature = claims["sig" /* ComputerSignature */];
752
+ if (expectedSignature !== boundSignature && !await this.acceptsHistoricalDeviceId(boundSignature))
753
+ throw deviceMismatchError(expectedSignature, boundSignature);
316
754
  return license;
317
755
  }
756
+ /**
757
+ * Give a {@link IMigratingDeviceIdResolver} the chance to vouch for a device id
758
+ * this machine used to have. Only consulted after the fast path fails, so
759
+ * apps that have not opted into a migration pay nothing.
760
+ */
761
+ async acceptsHistoricalDeviceId(boundSignature) {
762
+ const accepts = this.deviceIdResolver.acceptsDeviceId;
763
+ return typeof accepts === "function" && await accepts.call(this.deviceIdResolver, boundSignature);
764
+ }
318
765
  parseLicenseToken(token) {
319
766
  return new Promise((resolve, reject) => {
320
767
  jwt.verify(token, this.configuration.publicKey, {
@@ -379,11 +826,57 @@ var LicenseValidator = class {
379
826
  });
380
827
  }
381
828
  };
829
+ function deviceMismatchError(expected, bound) {
830
+ const detail = "This license is not for this device";
831
+ const stampNote = describeStampDifference(expected, bound);
832
+ return new MoonbaseError(
833
+ "License is for another device",
834
+ stampNote ? `${detail}. ${stampNote}` : detail,
835
+ "LicenseDeviceMismatch" /* LicenseDeviceMismatch */
836
+ );
837
+ }
838
+ function describeStampDifference(expected, bound) {
839
+ const expectedStamp = parseDeviceIdStamp(expected);
840
+ if (!expectedStamp)
841
+ return null;
842
+ const boundStamp = parseDeviceIdStamp(bound);
843
+ if (!boundStamp || boundStamp.version !== expectedStamp.version)
844
+ return describeVersionDifference(expectedStamp, boundStamp);
845
+ if (boundStamp.sourceTag !== expectedStamp.sourceTag)
846
+ return describeSourceDifference(expectedStamp, boundStamp);
847
+ return null;
848
+ }
849
+ function describeVersionDifference(expected, bound) {
850
+ if (bound && bound.version > expected.version) {
851
+ 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.`;
852
+ }
853
+ const boundVersion = bound ? `device fingerprint v${bound.version}` : "an SDK predating versioned device fingerprints";
854
+ 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.`;
855
+ }
856
+ function describeSourceDifference(expected, bound) {
857
+ if (bound.source === "scoped") {
858
+ 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.";
859
+ }
860
+ if (expected.source === "scoped") {
861
+ 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.";
862
+ }
863
+ if (bound.source === null) {
864
+ 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.`;
865
+ }
866
+ if (expected.source === null) {
867
+ 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.`;
868
+ }
869
+ if (bound.source === "deviceName") {
870
+ 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.";
871
+ }
872
+ 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.";
873
+ }
382
874
 
383
875
  // src/index.ts
384
876
  function detectPlatform() {
385
- if (typeof process === "undefined") return void 0;
386
- switch (process.platform) {
877
+ if (typeof process3 === "undefined")
878
+ return void 0;
879
+ switch (process3.platform) {
387
880
  case "darwin":
388
881
  return "Mac";
389
882
  case "win32":
@@ -403,7 +896,7 @@ var MoonbaseLicensing = class {
403
896
  platform: configuration.platform === void 0 ? detectPlatform() : configuration.platform
404
897
  };
405
898
  this.store = (_a = configuration.licenseStore) != null ? _a : new InMemoryLicenseStore();
406
- this.deviceIdResolver = (_b = configuration.deivceIdResolver) != null ? _b : new DefaultDeviceIdResolver();
899
+ this.deviceIdResolver = (_b = configuration.deviceIdResolver) != null ? _b : new MoonbaseDeviceIdResolver();
407
900
  this.validator = new LicenseValidator(this.configuration, this.deviceIdResolver);
408
901
  this.client = new LicenseClient(this.configuration, this.deviceIdResolver, this.validator);
409
902
  }
@@ -415,7 +908,7 @@ var MoonbaseLicensing = class {
415
908
  format: "JWT"
416
909
  };
417
910
  const json = JSON.stringify(token);
418
- return Buffer.from(Buffer.from(json).toString("base64"));
911
+ return Buffer2.from(Buffer2.from(json).toString("base64"));
419
912
  }
420
913
  async readRawLicense(license) {
421
914
  return await this.client.validateRawLicense(license);
@@ -423,12 +916,33 @@ var MoonbaseLicensing = class {
423
916
  };
424
917
  export {
425
918
  ActivationMethod,
426
- DefaultDeviceIdResolver,
919
+ DEVICE_ID_SOURCE_TAGS,
427
920
  ErrorType,
921
+ FINGERPRINT_PREFIX,
922
+ FINGERPRINT_VERSION,
428
923
  FileLicenseStore,
924
+ IDENTIFYING_PARAM_NAMES,
429
925
  InMemoryLicenseStore,
926
+ InsufficientDeviceIdentityError,
927
+ LegacyDeviceIdResolver,
430
928
  LicenseClient,
431
929
  LicenseValidator,
930
+ MAX_VALUE_LENGTH,
931
+ MigratingDeviceIdResolver,
932
+ MoonbaseDeviceIdResolver,
432
933
  MoonbaseError,
433
- MoonbaseLicensing
934
+ MoonbaseLicensing,
935
+ buildFingerprintMaterial,
936
+ canonicalizeParams,
937
+ canonicalizeValue,
938
+ defaultDeviceIdentityReader,
939
+ fingerprintDeviceId,
940
+ fingerprintDigest,
941
+ identitySource,
942
+ parseDeviceIdStamp,
943
+ parseIoregPlatformUuid,
944
+ parseSmbiosParams,
945
+ platformTag,
946
+ selectMachineId,
947
+ stampDeviceId
434
948
  };