@carchalabs/seal-electron 0.0.0-stage → 0.5.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/LICENSE +21 -0
- package/README.md +35 -2
- package/dist/fingerprint.d.ts +33 -0
- package/dist/fingerprint.js +97 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +524 -0
- package/dist/index.js.map +1 -0
- package/package.json +24 -4
- package/src/fingerprint.ts +103 -0
- package/src/index.ts +623 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bugra Omer Aydemir
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @carchalabs/seal-electron
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The [Carcha Seal](https://seal.carchalabs.com) (formerly LicenseKit) SDK. It runs in the Electron main process or in plain Node. It activates a license key on this device, saves the signed lease, and verifies it offline with your public keys.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
const { Seal } = require('@carchalabs/seal-electron');
|
|
7
|
+
|
|
8
|
+
const kit = new Seal({
|
|
9
|
+
serverUrl: 'https://licenses.example.com',
|
|
10
|
+
product: 'example-studio',
|
|
11
|
+
publicKey: [currentKeyPem, nextKeyPem],
|
|
12
|
+
storageDir: app.getPath('userData'),
|
|
13
|
+
secureStore: {
|
|
14
|
+
encrypt: (s) => safeStorage.encryptString(s),
|
|
15
|
+
decrypt: (b) => safeStorage.decryptString(b),
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const state = await kit.check();
|
|
20
|
+
if (state.status === 'unlicensed') await kit.activate(userEnteredKey);
|
|
21
|
+
if (Seal.hasFeature(state, 'export-pdf')) exportPdf();
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`check()` returns one of `valid`, `grace`, `invalid`, `unlicensed` or `clock_suspect`, with `offline` and `storageProtected` flags. A refusal from the server (suspended, expired, device removed, update required) is kept while offline and cleared by the next successful check-in. `notices` carries what the server asks your app to act on, such as `sdk_update_recommended` or `app_update_recommended` with a `minimum` version and a `requiredFrom` date; the SDK shows nothing itself. `activate(key, machineName)` sends the device name you pass; the default is the computer's host name. `deactivate()` releases the seat.
|
|
25
|
+
|
|
26
|
+
Code can be protected the same way (Envelope): encrypt a CommonJS module or a folder of them with `seal-encrypt --feature pro dist/premium dist/premium`, and load it with `await kit.requireEncrypted('pro', path.join(__dirname, 'premium/index.js.lke'))`. Without the feature in the license the code cannot be read; a licensed user can still read it from memory.
|
|
27
|
+
|
|
28
|
+
Computers that never reach the server use offline activation: `offlineRequest(key)` returns a request file, the license server turns it into a license file (its `/offline` page, `POST /v1/offline/activate` or the console), and `activateOffline(file)` loads it; it works only on that computer, for the policy's offline days. `deactivateOffline()` removes the license and returns a deactivation file the vendor uploads to free the seat. See the [documentation](https://seal.carchalabs.com/docs/#concepts).
|
|
29
|
+
|
|
30
|
+
The SDK sends the license key, a per-product SHA-256 hash of the operating system's machine ID, the device name, the platform, your app version and its own version. The raw machine ID does not leave the computer.
|
|
31
|
+
|
|
32
|
+
**Feature data.** `await kit.decrypt('pro', data)` returns data you encrypted for a feature (`encryptForFeature` in `@carchalabs/seal-core`, with the feature's content key from the console). It works offline from the stored lease and only on a device whose license includes the feature; without a license the data stays encrypted, so removing a license check from your app does not unlock it. With `@carchalabs/seal-hwid` 0.2 the native module does the decryption and the key never reaches JavaScript.
|
|
33
|
+
|
|
34
|
+
**Hardware components.** When `@carchalabs/seal-hwid` is installed, the SDK also sends one keyed hash per hardware part (motherboard, disk, network card, OS install, CPU and graphics models) and whether the computer is a virtual machine. The license server then recognises the computer after a new disk, network card or operating system install, and refuses a copied disk or system image on other hardware. Raw serial numbers never leave the computer. Without the package, or on a platform it has no module for, nothing extra is sent. `hardware: false` turns it off. Bundlers must leave the package to Node (mark it external); electron-builder unpacks its native module from the asar archive.
|
|
35
|
+
|
|
36
|
+
Integration guide (activate once, check at start, put feature code behind `decrypt`, ship the native module, harden the app): [seal.carchalabs.com/docs/#integration](https://seal.carchalabs.com/docs/#integration). Full reference: [seal.carchalabs.com/docs](https://seal.carchalabs.com/docs/#sdk). Requires Node 22.13 or later. MIT License.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OS tools are run by absolute path, never looked up through PATH: an app started from a terminal inherits the
|
|
3
|
+
* user's PATH, and a fake `ioreg` or `reg` placed first in it could report another computer's id.
|
|
4
|
+
*/
|
|
5
|
+
export declare const IOREG = "/usr/sbin/ioreg";
|
|
6
|
+
export declare const REG_EXE: string[];
|
|
7
|
+
export declare const LINUX_ID_FILES: string[];
|
|
8
|
+
export interface MachineIdDeps {
|
|
9
|
+
platform?: NodeJS.Platform;
|
|
10
|
+
/** Attempts to read the OS id before giving up (default 3). */
|
|
11
|
+
attempts?: number;
|
|
12
|
+
/** Pause between attempts (default 250 ms, doubled each time). */
|
|
13
|
+
delayMs?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Use hostname + CPU + MAC when the OS id cannot be read. Off by default: that value differs from the OS id, so the
|
|
16
|
+
* same computer would look like a new device and take another seat.
|
|
17
|
+
*/
|
|
18
|
+
allowFallback?: boolean;
|
|
19
|
+
run?: (file: string, args: string[], options: {
|
|
20
|
+
timeout: number;
|
|
21
|
+
windowsHide: boolean;
|
|
22
|
+
}) => Promise<{
|
|
23
|
+
stdout: string;
|
|
24
|
+
}>;
|
|
25
|
+
readText?: (file: string) => Promise<string>;
|
|
26
|
+
}
|
|
27
|
+
/** The OS id could not be read; licensing on this computer cannot continue until it can. */
|
|
28
|
+
export declare class MachineIdUnavailableError extends Error {
|
|
29
|
+
readonly code = "machine_id_unavailable";
|
|
30
|
+
constructor();
|
|
31
|
+
}
|
|
32
|
+
/** Reads a stable, OS-provided machine id, retrying a few times. Throws MachineIdUnavailableError unless allowFallback. */
|
|
33
|
+
export declare function readMachineId(deps?: MachineIdDeps): Promise<string>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MachineIdUnavailableError = exports.LINUX_ID_FILES = exports.REG_EXE = exports.IOREG = void 0;
|
|
4
|
+
exports.readMachineId = readMachineId;
|
|
5
|
+
const node_child_process_1 = require("node:child_process");
|
|
6
|
+
const promises_1 = require("node:fs/promises");
|
|
7
|
+
const node_os_1 = require("node:os");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const node_util_1 = require("node:util");
|
|
10
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
11
|
+
/**
|
|
12
|
+
* The OS tools are run by absolute path, never looked up through PATH: an app started from a terminal inherits the
|
|
13
|
+
* user's PATH, and a fake `ioreg` or `reg` placed first in it could report another computer's id.
|
|
14
|
+
*/
|
|
15
|
+
exports.IOREG = '/usr/sbin/ioreg';
|
|
16
|
+
exports.REG_EXE = [
|
|
17
|
+
'C:\\Windows\\System32\\reg.exe',
|
|
18
|
+
node_path_1.win32.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'reg.exe'),
|
|
19
|
+
].filter((file, index, all) => all.indexOf(file) === index);
|
|
20
|
+
exports.LINUX_ID_FILES = ['/etc/machine-id', '/var/lib/dbus/machine-id'];
|
|
21
|
+
/** The OS id could not be read; licensing on this computer cannot continue until it can. */
|
|
22
|
+
class MachineIdUnavailableError extends Error {
|
|
23
|
+
code = 'machine_id_unavailable';
|
|
24
|
+
constructor() {
|
|
25
|
+
super('This computer\'s id could not be read. Try again; if it keeps failing, contact the app\'s support.');
|
|
26
|
+
this.name = 'MachineIdUnavailableError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.MachineIdUnavailableError = MachineIdUnavailableError;
|
|
30
|
+
/** Reads a stable, OS-provided machine id, retrying a few times. Throws MachineIdUnavailableError unless allowFallback. */
|
|
31
|
+
async function readMachineId(deps = {}) {
|
|
32
|
+
const attempts = Math.max(1, deps.attempts ?? 3);
|
|
33
|
+
let delay = deps.delayMs ?? 250;
|
|
34
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
35
|
+
const id = await readOsMachineId(deps);
|
|
36
|
+
if (id)
|
|
37
|
+
return id;
|
|
38
|
+
if (attempt < attempts) {
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
40
|
+
delay *= 2;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (deps.allowFallback)
|
|
44
|
+
return `fallback:${fallbackTraits()}`;
|
|
45
|
+
throw new MachineIdUnavailableError();
|
|
46
|
+
}
|
|
47
|
+
/** One attempt; null when the OS id is not available. */
|
|
48
|
+
async function readOsMachineId(deps) {
|
|
49
|
+
const os = deps.platform ?? (0, node_os_1.platform)();
|
|
50
|
+
const run = deps.run ?? ((file, args, options) => execFileAsync(file, args, options));
|
|
51
|
+
const readText = deps.readText ?? ((file) => (0, promises_1.readFile)(file, 'utf8'));
|
|
52
|
+
try {
|
|
53
|
+
if (os === 'darwin') {
|
|
54
|
+
const { stdout } = await run(exports.IOREG, ['-rd1', '-c', 'IOPlatformExpertDevice'], { timeout: 3000, windowsHide: true });
|
|
55
|
+
const m = stdout.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
56
|
+
if (m)
|
|
57
|
+
return `mac:${m[1]}`;
|
|
58
|
+
}
|
|
59
|
+
else if (os === 'win32') {
|
|
60
|
+
for (const reg of exports.REG_EXE) {
|
|
61
|
+
try {
|
|
62
|
+
// /reg:64 reads the 64-bit registry view, so a 32-bit build sees the same MachineGuid.
|
|
63
|
+
const { stdout } = await run(reg, ['query', 'HKLM\\SOFTWARE\\Microsoft\\Cryptography', '/v', 'MachineGuid', '/reg:64'], {
|
|
64
|
+
timeout: 3000,
|
|
65
|
+
windowsHide: true,
|
|
66
|
+
});
|
|
67
|
+
const m = stdout.match(/MachineGuid\s+REG_SZ\s+([\w-]+)/i);
|
|
68
|
+
if (m)
|
|
69
|
+
return `win:${m[1]}`;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* try the next location */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
for (const f of exports.LINUX_ID_FILES) {
|
|
78
|
+
const id = (await readText(f).catch(() => '')).trim();
|
|
79
|
+
if (id)
|
|
80
|
+
return `linux:${id}`;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
/* fall through */
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
function fallbackTraits() {
|
|
90
|
+
const macs = Object.values((0, node_os_1.networkInterfaces)())
|
|
91
|
+
.flat()
|
|
92
|
+
.filter((i) => i && !i.internal && i.mac && i.mac !== '00:00:00:00:00:00')
|
|
93
|
+
.map((i) => i.mac)
|
|
94
|
+
.sort();
|
|
95
|
+
return [(0, node_os_1.hostname)(), (0, node_os_1.cpus)()[0]?.model ?? '', macs[0] ?? ''].join('|');
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { MachineIdUnavailableError, readMachineId } from './fingerprint';
|
|
2
|
+
export { MachineIdUnavailableError, readMachineId };
|
|
3
|
+
/** This SDK's version, sent to the server so vendors can see (and require) which SDK each device runs. */
|
|
4
|
+
export declare const SDK_VERSION: string;
|
|
5
|
+
export type LicenseStatus = 'valid' | 'grace' | 'invalid' | 'unlicensed' | 'clock_suspect';
|
|
6
|
+
/**
|
|
7
|
+
* Something the server asks the app to act on: sdk_update_recommended (update the Carcha Seal SDK in the app) or
|
|
8
|
+
* app_update_recommended (the vendor's minimum app version), each with minimum and requiredFrom.
|
|
9
|
+
*/
|
|
10
|
+
export interface LicenseNotice {
|
|
11
|
+
code: string;
|
|
12
|
+
minimum?: string;
|
|
13
|
+
/** ISO date from which an older SDK is refused; null when the update is only recommended. */
|
|
14
|
+
requiredFrom?: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface LicenseState {
|
|
17
|
+
status: LicenseStatus;
|
|
18
|
+
offline: boolean;
|
|
19
|
+
storageProtected: boolean;
|
|
20
|
+
reason?: string;
|
|
21
|
+
message?: string;
|
|
22
|
+
customer?: string;
|
|
23
|
+
features: string[];
|
|
24
|
+
expiresAt: string | null;
|
|
25
|
+
maintenanceUntil?: string | null;
|
|
26
|
+
revalidateBy?: string;
|
|
27
|
+
keyHint?: string;
|
|
28
|
+
/** From the last successful check-in; kept while offline. The app decides how to show them. */
|
|
29
|
+
notices?: LicenseNotice[];
|
|
30
|
+
/** True when the license came from an offline license file (activateOffline); renew it with a new file. */
|
|
31
|
+
offlineActivated?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Hashed hardware components (see @carchalabs/seal-hwid); the server matches a device by them. */
|
|
34
|
+
export interface HardwareReport {
|
|
35
|
+
components: {
|
|
36
|
+
kind: string;
|
|
37
|
+
hash: string;
|
|
38
|
+
}[];
|
|
39
|
+
/** True when the device runs as a virtual machine. */
|
|
40
|
+
vm: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface SecureStore {
|
|
43
|
+
encrypt(plain: string): Buffer | Promise<Buffer>;
|
|
44
|
+
decrypt(cipher: Buffer): string | Promise<string>;
|
|
45
|
+
isProtected?(): boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface SealOptions {
|
|
48
|
+
serverUrl: string;
|
|
49
|
+
product: string;
|
|
50
|
+
publicKey: string | string[];
|
|
51
|
+
storageDir: string;
|
|
52
|
+
appVersion?: string;
|
|
53
|
+
graceDays?: number;
|
|
54
|
+
secureStore?: SecureStore;
|
|
55
|
+
requestTimeoutMs?: number;
|
|
56
|
+
fetchImpl?: typeof fetch;
|
|
57
|
+
machineId?: () => Promise<string>;
|
|
58
|
+
/** Use hostname + CPU + MAC when the OS machine id cannot be read (default false; see readMachineId). */
|
|
59
|
+
allowFallbackFingerprint?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Hardware components sent with each request, so the device keeps its seat after a new disk, network
|
|
62
|
+
* card or OS install. Default: read with @carchalabs/seal-hwid when it is installed; nothing is
|
|
63
|
+
* sent when it is not (or cannot load on this platform). false: never send them. A function: your own.
|
|
64
|
+
*/
|
|
65
|
+
hardware?: false | (() => HardwareReport | null | Promise<HardwareReport | null>);
|
|
66
|
+
now?: () => number;
|
|
67
|
+
}
|
|
68
|
+
export declare class LicenseError extends Error {
|
|
69
|
+
readonly code: string;
|
|
70
|
+
readonly status?: number | undefined;
|
|
71
|
+
constructor(code: string, message: string, status?: number | undefined);
|
|
72
|
+
}
|
|
73
|
+
export declare class Seal {
|
|
74
|
+
private readonly opts;
|
|
75
|
+
private readonly file;
|
|
76
|
+
private readonly keys;
|
|
77
|
+
private fingerprintCache?;
|
|
78
|
+
private hardwareCache?;
|
|
79
|
+
/** The native lease verifier, when it trusts every key this app was given (undefined = not looked up yet). */
|
|
80
|
+
private nativeLease?;
|
|
81
|
+
private deviceKeyCache?;
|
|
82
|
+
constructor(opts: SealOptions);
|
|
83
|
+
private get fetch();
|
|
84
|
+
private nowSec;
|
|
85
|
+
private get storageProtected();
|
|
86
|
+
fingerprint(): Promise<string>;
|
|
87
|
+
/** Read once per instance: hardware does not change while the app runs. Never throws. */
|
|
88
|
+
private hardware;
|
|
89
|
+
private load;
|
|
90
|
+
private save;
|
|
91
|
+
private clear;
|
|
92
|
+
private post;
|
|
93
|
+
private emptyState;
|
|
94
|
+
/**
|
|
95
|
+
* The device's public key, sent with activation and check-ins so the server wraps feature keys for
|
|
96
|
+
* this machine only. Only when the native module verifies for this app (it then also decrypts, with
|
|
97
|
+
* the matching secret it derives itself) and hardware is not turned off. Never throws.
|
|
98
|
+
*/
|
|
99
|
+
private deviceKey;
|
|
100
|
+
/**
|
|
101
|
+
* The native verifier, used only when its compiled-in keys include every key this app was given:
|
|
102
|
+
* an app that trusts other keys (a self-hosted server) is checked by the JavaScript verifier alone.
|
|
103
|
+
*/
|
|
104
|
+
private native;
|
|
105
|
+
/**
|
|
106
|
+
* Verifies a lease twice, in JavaScript and in the native module, when the native verifier applies.
|
|
107
|
+
* Both follow the same rules, so a different answer means one of them was tampered with: the lease
|
|
108
|
+
* is then treated as modified.
|
|
109
|
+
*/
|
|
110
|
+
private verify;
|
|
111
|
+
private stateFromLease;
|
|
112
|
+
activate(licenseKey: string, machineName?: string): Promise<LicenseState>;
|
|
113
|
+
check(): Promise<LicenseState>;
|
|
114
|
+
/**
|
|
115
|
+
* Decrypts data you encrypted for a feature (encryptForFeature in @carchalabs/seal-core, with the
|
|
116
|
+
* feature's content key from the console). Works offline: it uses the saved lease, which must be valid
|
|
117
|
+
* (or in its offline grace period), grant the feature and carry its key, and the saved license key.
|
|
118
|
+
* The native module does the work when it trusts this app's keys, so the content key never reaches
|
|
119
|
+
* JavaScript; without it the JavaScript implementation is used. Throws a LicenseError:
|
|
120
|
+
* `unlicensed`, a lease failure reason, `clock_suspect`, `feature_not_licensed` or `decrypt_failed`.
|
|
121
|
+
*/
|
|
122
|
+
decrypt(feature: string, data: Uint8Array): Promise<Buffer>;
|
|
123
|
+
/**
|
|
124
|
+
* Envelope: loads a CommonJS module you encrypted for a feature (seal-encrypt, one file or a
|
|
125
|
+
* folder) and returns its exports, like require(). The code is decrypted with the lease's feature key
|
|
126
|
+
* (see decrypt) and compiled from memory; it never exists as a plain file. Relative requires inside
|
|
127
|
+
* it load encrypted neighbours (`./x` finds `x.lke`, `x.js.lke` or `x/index.js.lke`), anything else
|
|
128
|
+
* goes to Node as usual. Loaded once per process, like require(). Throws the same LicenseErrors as
|
|
129
|
+
* decrypt. A licensed user can still read the code from memory: this keeps an app with the license
|
|
130
|
+
* check removed from having the feature, it does not hide the code from its licensed users.
|
|
131
|
+
*/
|
|
132
|
+
requireEncrypted(feature: string, file: string): Promise<any>;
|
|
133
|
+
private readonly encryptedModules;
|
|
134
|
+
private loadEncrypted;
|
|
135
|
+
/**
|
|
136
|
+
* Checks the saved lease once for a feature and returns a synchronous decrypter bound to it: decrypt
|
|
137
|
+
* uses it once, requireEncrypted for a module and the encrypted modules it requires.
|
|
138
|
+
*/
|
|
139
|
+
private opener;
|
|
140
|
+
/**
|
|
141
|
+
* Offline activation, step 1 on a machine that cannot reach the server: the request file (JSON text)
|
|
142
|
+
* the user takes to a connected device. The license server turns it into a license file at
|
|
143
|
+
* POST /v1/offline/activate (or its offline activation page). It holds what activate() would send:
|
|
144
|
+
* the license key, this device's fingerprint and hashed hardware parts, never raw serial numbers.
|
|
145
|
+
*/
|
|
146
|
+
offlineRequest(licenseKey: string, machineName?: string): Promise<string>;
|
|
147
|
+
/**
|
|
148
|
+
* Offline activation, step 2: loads the license file the server issued for this machine's request.
|
|
149
|
+
* It is verified like an online lease (signature, product, this device) and then saved; from then on
|
|
150
|
+
* check() works without the server until the lease's renewal date plus the offline grace. Throws a
|
|
151
|
+
* LicenseError: `invalid_file`, `wrong_product`, or a lease failure reason such as `wrong_device`.
|
|
152
|
+
*/
|
|
153
|
+
activateOffline(licenseFile: string): Promise<LicenseState>;
|
|
154
|
+
/**
|
|
155
|
+
* Gives an offline machine's seat back: removes the license from this device and returns the
|
|
156
|
+
* deactivation file (JSON text) for the vendor, who frees the seat by uploading it in the console.
|
|
157
|
+
* Throws `unlicensed` when there is no license to remove.
|
|
158
|
+
*/
|
|
159
|
+
deactivateOffline(): Promise<string>;
|
|
160
|
+
deactivate(): Promise<void>;
|
|
161
|
+
static hasFeature(state: LicenseState, feature: string): boolean;
|
|
162
|
+
}
|
|
163
|
+
/** The SDK class under its earlier name (LicenseKit). Same class; use Seal in new code. */
|
|
164
|
+
export { Seal as LicenseKit };
|
|
165
|
+
/** @deprecated The options under the class's earlier name; use SealOptions. */
|
|
166
|
+
export type LicenseKitOptions = SealOptions;
|