@voltro/plugin-licensing 0.85.0 → 0.87.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/CHANGELOG.md +5987 -0
- package/README.md +44 -0
- package/THIRD-PARTY-NOTICES.md +864 -253
- package/dist/cloud.d.ts +31 -0
- package/dist/cloud.js +61 -0
- package/dist/games.d.ts +39 -0
- package/dist/games.js +65 -0
- package/dist/gate.d.ts +7 -0
- package/dist/gate.js +28 -0
- package/dist/index.d.ts +28 -7
- package/dist/index.js +86 -121
- package/dist/inspect.d.ts +56 -0
- package/dist/inspect.js +48 -0
- package/dist/resolver.js +9 -0
- package/dist/snapshot.js +33 -0
- package/dist/verify.js +88 -0
- package/package.json +26 -6
package/dist/cloud.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { VoidIfEmpty } from 'effect/Types';
|
|
3
|
+
import { YieldableError } from 'effect/Cause';
|
|
4
|
+
|
|
5
|
+
/** Authenticated JSON retrieval from the existing Cloud /licenses route.
|
|
6
|
+
* HTTPS, or literal local loopback HTTP for private development/fixtures.
|
|
7
|
+
* Redirects refuse before credential forwarding. No issuer or verification key. */
|
|
8
|
+
export declare const cloudLicenseSnapshot: (options: CloudLicenseSnapshotOptions) => LicenseSnapshotSource;
|
|
9
|
+
|
|
10
|
+
export declare interface CloudLicenseSnapshotOptions {
|
|
11
|
+
readonly url: string;
|
|
12
|
+
/** Read the actual license:read credential at refresh time. Never a signing key. */
|
|
13
|
+
readonly bearerToken: () => Effect.Effect<string, LicenseSnapshotFetchError>;
|
|
14
|
+
readonly timeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Redacted transport refusal. No response body, URL, token or credential cause. */
|
|
18
|
+
export declare class LicenseSnapshotFetchError extends LicenseSnapshotFetchError_base<{
|
|
19
|
+
readonly reason: 'credentials' | 'transport' | 'status' | 'response';
|
|
20
|
+
readonly status?: number;
|
|
21
|
+
}> {
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare const LicenseSnapshotFetchError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
|
|
25
|
+
readonly _tag: "LicenseSnapshotFetchError";
|
|
26
|
+
} & Readonly<A>;
|
|
27
|
+
|
|
28
|
+
/** Swappable server transport; verification and installation remain with Licensing. */
|
|
29
|
+
export declare type LicenseSnapshotSource = () => Effect.Effect<string, LicenseSnapshotFetchError>;
|
|
30
|
+
|
|
31
|
+
export { }
|
package/dist/cloud.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Data as e, Effect as t } from "effect";
|
|
2
|
+
//#region src/cloud.ts
|
|
3
|
+
var n = class extends e.TaggedError("LicenseSnapshotFetchError") {}, r = 131072, i = 65536, a = (e) => {
|
|
4
|
+
let a = new URL(e.url), o = a.hostname === "127.0.0.1" || a.hostname === "[::1]";
|
|
5
|
+
if (a.username !== "" || a.password !== "" || a.hash !== "" || a.protocol !== "https:" && !(a.protocol === "http:" && o)) throw Error("Cloud license retrieval requires HTTPS or literal local loopback HTTP, without URL credentials or fragments");
|
|
6
|
+
let s = a.href, c = e.bearerToken, l = e.timeoutMs ?? 3e4;
|
|
7
|
+
if (typeof c != "function" || !Number.isSafeInteger(l) || l <= 0 || l > 3e5) throw Error("Cloud license retrieval requires an actual credential port and bounded positive timeout");
|
|
8
|
+
return () => t.gen(function* () {
|
|
9
|
+
let e = yield* c();
|
|
10
|
+
return typeof e != "string" || e.length === 0 || e.length > i || !/^[\x21-\x7e]+$/.test(e) ? yield* t.fail(new n({ reason: "credentials" })) : yield* t.tryPromise({
|
|
11
|
+
try: async (t) => {
|
|
12
|
+
let a = new AbortController(), o = () => {
|
|
13
|
+
a.abort();
|
|
14
|
+
};
|
|
15
|
+
t.addEventListener("abort", o, { once: !0 }), t.aborted && a.abort();
|
|
16
|
+
let c = setTimeout(o, l);
|
|
17
|
+
try {
|
|
18
|
+
let t = await fetch(s, {
|
|
19
|
+
headers: { authorization: `Bearer ${e}` },
|
|
20
|
+
redirect: "error",
|
|
21
|
+
signal: a.signal
|
|
22
|
+
});
|
|
23
|
+
if (!t.ok) throw await t.body?.cancel(), new n({
|
|
24
|
+
reason: "status",
|
|
25
|
+
status: t.status
|
|
26
|
+
});
|
|
27
|
+
if (!/^application\/json(?:\s*;|\s*$)/i.test(t.headers.get("content-type") ?? "") || t.body === null) throw await t.body?.cancel(), new n({ reason: "response" });
|
|
28
|
+
let o = t.headers.get("content-length");
|
|
29
|
+
if (o !== null && (!/^\d+$/.test(o) || Number(o) > r)) throw await t.body.cancel(), new n({ reason: "response" });
|
|
30
|
+
let c = t.body.getReader(), l = [], u = 0;
|
|
31
|
+
try {
|
|
32
|
+
for (;;) {
|
|
33
|
+
let e = await c.read();
|
|
34
|
+
if (e.done) break;
|
|
35
|
+
if (u += e.value.byteLength, u > r) throw await c.cancel(), new n({ reason: "response" });
|
|
36
|
+
l.push(e.value);
|
|
37
|
+
}
|
|
38
|
+
} finally {
|
|
39
|
+
c.releaseLock();
|
|
40
|
+
}
|
|
41
|
+
let d = new Uint8Array(u), f = 0;
|
|
42
|
+
for (let e of l) d.set(e, f), f += e.byteLength;
|
|
43
|
+
let p;
|
|
44
|
+
try {
|
|
45
|
+
p = JSON.parse(new TextDecoder("utf-8", { fatal: !0 }).decode(d));
|
|
46
|
+
} catch {
|
|
47
|
+
throw new n({ reason: "response" });
|
|
48
|
+
}
|
|
49
|
+
let m = typeof p == "object" && p && !Array.isArray(p) && "token" in p ? p.token : void 0;
|
|
50
|
+
if (typeof m != "string" || m.length > i || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(m)) throw new n({ reason: "response" });
|
|
51
|
+
return m;
|
|
52
|
+
} finally {
|
|
53
|
+
clearTimeout(c), t.removeEventListener("abort", o);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
catch: (e) => e instanceof n ? e : new n({ reason: "transport" })
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
export { n as LicenseSnapshotFetchError, a as cloudLicenseSnapshot };
|
package/dist/games.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
|
|
3
|
+
/** A numeric cap, or `'unlimited'` for no cap. */
|
|
4
|
+
declare type EntitlementLimit = number | 'unlimited';
|
|
5
|
+
|
|
6
|
+
/** License names, not package installation or accepted capability claims. */
|
|
7
|
+
export declare const GAMES_PLUGIN_GRANTS: readonly ["rooms", "matchmaking", "leaderboards", "social", "chat", "guilds", "inventory", "economy", "progression", "liveops", "players", "saves", "sanctions", "gm"];
|
|
8
|
+
|
|
9
|
+
export declare const GamesLicenseEdition: Schema.Literals<readonly ["games", "games-enterprise"]>;
|
|
10
|
+
|
|
11
|
+
export declare type GamesLicenseEdition = typeof GamesLicenseEdition.Type;
|
|
12
|
+
|
|
13
|
+
export declare interface GamesSignLicenseInput extends Omit<SignLicenseInput, 'edition'> {
|
|
14
|
+
readonly edition: GamesLicenseEdition;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
declare interface SignLicenseInput {
|
|
18
|
+
/** Scope the license to one tenant id; omit for a deployment-wide license. */
|
|
19
|
+
readonly tenant?: string;
|
|
20
|
+
/** Edition label (e.g. `'pro'`, `'enterprise'`). */
|
|
21
|
+
readonly edition: string;
|
|
22
|
+
/** Per-key numeric limits (`'unlimited'` for no cap). */
|
|
23
|
+
readonly entitlements: Readonly<Record<string, EntitlementLimit>>;
|
|
24
|
+
/** Boolean feature flags to grant. */
|
|
25
|
+
readonly features?: ReadonlyArray<string>;
|
|
26
|
+
/** Plugins licensed for production use (names without the
|
|
27
|
+
* `@voltro/plugin-` prefix, e.g. `['storage']`). */
|
|
28
|
+
readonly plugins?: ReadonlyArray<string>;
|
|
29
|
+
/** Seconds from `now` until the license expires. */
|
|
30
|
+
readonly ttlSeconds: number;
|
|
31
|
+
/** Key id stamped into the JWS header (public-key rotation). */
|
|
32
|
+
readonly kid?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Issuer-side composition. Retains actual caller-owned base grants, limits,
|
|
36
|
+
* seats and TTL. It signs nothing and invents no price, key or entitlement. */
|
|
37
|
+
export declare const withGamesPluginGrants: (input: GamesSignLicenseInput) => SignLicenseInput;
|
|
38
|
+
|
|
39
|
+
export { }
|
package/dist/games.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Schema as e } from "effect";
|
|
2
|
+
//#region src/games.ts
|
|
3
|
+
var t = e.Literals(["games", "games-enterprise"]), n = Object.freeze([
|
|
4
|
+
"rooms",
|
|
5
|
+
"matchmaking",
|
|
6
|
+
"leaderboards",
|
|
7
|
+
"social",
|
|
8
|
+
"chat",
|
|
9
|
+
"guilds",
|
|
10
|
+
"inventory",
|
|
11
|
+
"economy",
|
|
12
|
+
"progression",
|
|
13
|
+
"liveops",
|
|
14
|
+
"players",
|
|
15
|
+
"saves",
|
|
16
|
+
"sanctions",
|
|
17
|
+
"gm"
|
|
18
|
+
]), r = (r) => {
|
|
19
|
+
if (!r || Object.getPrototypeOf(r) !== Object.prototype || Object.getOwnPropertySymbols(r).length) throw TypeError("Invalid Games license input");
|
|
20
|
+
let i = Object.getOwnPropertyDescriptors(r);
|
|
21
|
+
if (Object.entries(i).some(([e, t]) => ![
|
|
22
|
+
"tenant",
|
|
23
|
+
"edition",
|
|
24
|
+
"entitlements",
|
|
25
|
+
"features",
|
|
26
|
+
"plugins",
|
|
27
|
+
"ttlSeconds",
|
|
28
|
+
"kid"
|
|
29
|
+
].includes(e) || !t.enumerable || !("value" in t))) throw TypeError("Invalid Games license input");
|
|
30
|
+
let a = Object.fromEntries(Object.entries(i).map(([e, t]) => [e, t.value]));
|
|
31
|
+
e.decodeUnknownSync(e.toType(t))(a.edition);
|
|
32
|
+
let o = (e) => typeof e == "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/.test(e);
|
|
33
|
+
if (a.tenant !== void 0 && !o(a.tenant) || a.kid !== void 0 && !o(a.kid) || !Number.isSafeInteger(a.ttlSeconds) || a.ttlSeconds <= 0) throw TypeError("Invalid Games license input");
|
|
34
|
+
let s = (e) => {
|
|
35
|
+
if (e === void 0) return Object.freeze([]);
|
|
36
|
+
if (!Array.isArray(e) || e.length > 256 || Reflect.ownKeys(e).length !== e.length + 1) throw TypeError("Invalid Games license grants");
|
|
37
|
+
let t = [];
|
|
38
|
+
for (let n = 0; n < e.length; n++) {
|
|
39
|
+
let r = Object.getOwnPropertyDescriptor(e, String(n));
|
|
40
|
+
if (!r?.enumerable || !("value" in r) || !o(r.value)) throw TypeError("Invalid Games license grants");
|
|
41
|
+
t.push(r.value);
|
|
42
|
+
}
|
|
43
|
+
return Object.freeze([...new Set(t)]);
|
|
44
|
+
}, c = a.entitlements;
|
|
45
|
+
if (!c || Object.getPrototypeOf(c) !== Object.prototype || Reflect.ownKeys(c).length > 256) throw TypeError("Invalid Games license limits");
|
|
46
|
+
let l = {};
|
|
47
|
+
for (let e of Reflect.ownKeys(c)) {
|
|
48
|
+
let t = Object.getOwnPropertyDescriptor(c, e);
|
|
49
|
+
if (!o(e) || !t?.enumerable || !("value" in t)) throw TypeError("Invalid Games license limits");
|
|
50
|
+
let n = t.value;
|
|
51
|
+
if (n !== "unlimited" && (typeof n != "number" || !Number.isFinite(n) || n < 0)) throw TypeError("Invalid Games license limits");
|
|
52
|
+
Object.defineProperty(l, e, {
|
|
53
|
+
value: n,
|
|
54
|
+
enumerable: !0
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return Object.freeze({
|
|
58
|
+
...a,
|
|
59
|
+
entitlements: Object.freeze(l),
|
|
60
|
+
features: s(a.features),
|
|
61
|
+
plugins: Object.freeze([.../* @__PURE__ */ new Set([...s(a.plugins), ...n])])
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
export { n as GAMES_PLUGIN_GRANTS, t as GamesLicenseEdition, r as withGamesPluginGrants };
|
package/dist/gate.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { VoltroPlugin } from '@voltro/protocol';
|
|
2
|
+
|
|
3
|
+
/** Server-only factory admission. The ordinary lifecycle still owns order,
|
|
4
|
+
* hook execution and cleanup; no descriptor/transport/store/tick contribution. */
|
|
5
|
+
export declare const defineLicensedGamePlugin: (descriptor: VoltroPlugin) => VoltroPlugin;
|
|
6
|
+
|
|
7
|
+
export { }
|
package/dist/gate.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { GAMES_PLUGIN_GRANTS as e } from "./games.js";
|
|
2
|
+
import { PluginNotLicensed as t, licenseStatus as n, requireLicensedPlugin as r } from "./index.js";
|
|
3
|
+
import { Effect as i } from "effect";
|
|
4
|
+
import { definePlugin as a } from "@voltro/protocol";
|
|
5
|
+
//#region src/gate.ts
|
|
6
|
+
var o = (o) => {
|
|
7
|
+
let s = o.baseName ?? o.name, c = e.find((e) => s === `@voltro/plugin-${e}`);
|
|
8
|
+
if (c === void 0) throw TypeError("Game license admission requires an actual canonical Games plugin identity");
|
|
9
|
+
let l = o.onActivate;
|
|
10
|
+
return a({
|
|
11
|
+
...o,
|
|
12
|
+
onActivate: (e) => i.gen(function* () {
|
|
13
|
+
let a = (e.env.NODE_ENV ?? process.env.NODE_ENV ?? "production").toLowerCase();
|
|
14
|
+
if (a !== "development" && a !== "test") {
|
|
15
|
+
let e = n();
|
|
16
|
+
if (e.state !== "active" && e.state !== "grace" || e.tenant !== void 0) return yield* i.fail(new t({ plugin: c }));
|
|
17
|
+
yield* r(c);
|
|
18
|
+
}
|
|
19
|
+
let o = l?.(e);
|
|
20
|
+
i.isEffect(o) ? yield* o : o !== void 0 && (yield* i.tryPromise({
|
|
21
|
+
try: () => Promise.resolve(o),
|
|
22
|
+
catch: (e) => e
|
|
23
|
+
}));
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { o as defineLicensedGamePlugin };
|
package/dist/index.d.ts
CHANGED
|
@@ -67,6 +67,20 @@ export declare interface LicenseSnapshot {
|
|
|
67
67
|
readonly state: () => 'active' | 'grace' | 'expired';
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/** Redacted transport refusal. No response body, URL, token or credential cause. */
|
|
71
|
+
export declare class LicenseSnapshotFetchError extends LicenseSnapshotFetchError_base<{
|
|
72
|
+
readonly reason: 'credentials' | 'transport' | 'status' | 'response';
|
|
73
|
+
readonly status?: number;
|
|
74
|
+
}> {
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
declare const LicenseSnapshotFetchError_base: new <A extends Record<string, any> = {}>(args: VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => YieldableError & {
|
|
78
|
+
readonly _tag: "LicenseSnapshotFetchError";
|
|
79
|
+
} & Readonly<A>;
|
|
80
|
+
|
|
81
|
+
/** Swappable server transport; verification and installation remain with Licensing. */
|
|
82
|
+
export declare type LicenseSnapshotSource = () => Effect.Effect<string, LicenseSnapshotFetchError>;
|
|
83
|
+
|
|
70
84
|
/** The service handlers can `yield*` to read the current license directly (for
|
|
71
85
|
* feature checks beyond billing's metered entitlements). */
|
|
72
86
|
/**
|
|
@@ -111,6 +125,13 @@ declare const LicenseVerifyError_base: new <A extends Record<string, any> = {}>(
|
|
|
111
125
|
readonly _tag: "LicenseVerifyError";
|
|
112
126
|
} & Readonly<A>;
|
|
113
127
|
|
|
128
|
+
export declare interface LicenseVerifyOptions {
|
|
129
|
+
/** Explicit expiry-only grace. Signature, not-before and claim checks remain strict. */
|
|
130
|
+
readonly graceSeconds?: number;
|
|
131
|
+
/** Trusted server/test clock in whole Unix seconds. Defaults to the actual clock. */
|
|
132
|
+
readonly nowSeconds?: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
114
135
|
export declare const licensingPlugin: (options?: LicensingPluginOptions) => VoltroPlugin;
|
|
115
136
|
|
|
116
137
|
export declare interface LicensingPluginOptions {
|
|
@@ -119,11 +140,10 @@ export declare interface LicensingPluginOptions {
|
|
|
119
140
|
readonly publicKey?: string;
|
|
120
141
|
/** The signed license token. Falls back to `VOLTRO_LICENSE_KEY`. */
|
|
121
142
|
readonly licenseKey?: string;
|
|
122
|
-
/**
|
|
123
|
-
*
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
/** Refresh interval (ms) for `snapshotUrl`. Default 15 minutes. */
|
|
143
|
+
/** Actual server transport port for periodically retrieving a signed token.
|
|
144
|
+
* Verification remains offline; use ./cloud for authenticated JSON retrieval. */
|
|
145
|
+
readonly fetchSnapshot?: LicenseSnapshotSource;
|
|
146
|
+
/** Refresh interval (ms) for `fetchSnapshot`. Default 15 minutes. */
|
|
127
147
|
readonly refreshMs?: number;
|
|
128
148
|
/** Seconds an expired license is still honored (grace) before it goes dark.
|
|
129
149
|
* Default 0. */
|
|
@@ -150,8 +170,9 @@ export declare const requireLicensedPlugin: (plugin: string) => Effect.Effect<vo
|
|
|
150
170
|
/**
|
|
151
171
|
* Verify a license token against an Ed25519 public key (SPKI/PEM), OFFLINE.
|
|
152
172
|
* Returns the decoded {@link LicenseClaims} or a typed {@link LicenseVerifyError}
|
|
153
|
-
* (expiry is enforced by `jose
|
|
173
|
+
* (expiry is enforced by `jose`, with explicit authenticated expiry-only grace).
|
|
174
|
+
* No network access. Grace never changes the clock tolerance for other claims.
|
|
154
175
|
*/
|
|
155
|
-
export declare const verifyLicense: (token: string, publicKeyPem: string) => Effect.Effect<LicenseClaims, LicenseVerifyError>;
|
|
176
|
+
export declare const verifyLicense: (token: string, publicKeyPem: string, options?: LicenseVerifyOptions) => Effect.Effect<LicenseClaims, LicenseVerifyError>;
|
|
156
177
|
|
|
157
178
|
export { }
|
package/dist/index.js
CHANGED
|
@@ -1,84 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
message: t
|
|
14
|
-
});
|
|
15
|
-
}, d = (e) => e === "unlimited" || typeof e == "number" && Number.isFinite(e) && e >= 0, f = (e, n) => {
|
|
16
|
-
let r = e.edition, i = e.entitlements, a = e.features;
|
|
17
|
-
if (typeof r != "string" || typeof i != "object" || !i) return t.fail(new c({
|
|
18
|
-
reason: "malformed",
|
|
19
|
-
message: "missing edition/entitlements"
|
|
20
|
-
}));
|
|
21
|
-
let o = {};
|
|
22
|
-
for (let [e, n] of Object.entries(i)) {
|
|
23
|
-
if (!d(n)) return t.fail(new c({
|
|
24
|
-
reason: "malformed",
|
|
25
|
-
message: `bad entitlement '${e}'`
|
|
26
|
-
}));
|
|
27
|
-
o[e] = n;
|
|
28
|
-
}
|
|
29
|
-
let s = Array.isArray(a) ? a.filter((e) => typeof e == "string") : [], l = e.plugins, u = Array.isArray(l) ? l.filter((e) => typeof e == "string") : [];
|
|
30
|
-
return t.succeed({
|
|
31
|
-
...typeof e.tenant == "string" ? { tenant: e.tenant } : {},
|
|
32
|
-
edition: r,
|
|
33
|
-
entitlements: o,
|
|
34
|
-
features: s,
|
|
35
|
-
plugins: u,
|
|
36
|
-
exp: typeof e.exp == "number" ? e.exp : 0,
|
|
37
|
-
...typeof e.iat == "number" ? { iat: e.iat } : {},
|
|
38
|
-
...n === void 0 ? {} : { kid: n }
|
|
39
|
-
});
|
|
40
|
-
}, p = (e, n) => t.tryPromise({
|
|
41
|
-
try: () => o(n, "EdDSA"),
|
|
42
|
-
catch: (e) => new c({
|
|
43
|
-
reason: "key",
|
|
44
|
-
message: e instanceof Error ? e.message : String(e)
|
|
45
|
-
})
|
|
46
|
-
}).pipe(t.flatMap((n) => t.tryPromise({
|
|
47
|
-
try: async () => {
|
|
48
|
-
let { payload: t, protectedHeader: r } = await s(e, n, { algorithms: ["EdDSA"] });
|
|
49
|
-
return {
|
|
50
|
-
payload: t,
|
|
51
|
-
kid: r.kid
|
|
52
|
-
};
|
|
53
|
-
},
|
|
54
|
-
catch: u
|
|
55
|
-
})), t.flatMap(({ payload: e, kid: t }) => f(e, t))), m = (e, t = {}) => {
|
|
56
|
-
let n = new Set(e.features), r = new Set(e.plugins), i = t.graceSeconds ?? 0, a = t.now ?? (() => Date.now() / 1e3), o = () => e.exp <= 0 || a() <= e.exp + i;
|
|
57
|
-
return {
|
|
58
|
-
claims: e,
|
|
59
|
-
state: () => e.exp <= 0 || a() <= e.exp ? "active" : o() ? "grace" : "expired",
|
|
60
|
-
limitFor: (t, n) => {
|
|
61
|
-
if (!o() || e.tenant !== void 0 && e.tenant !== t) return;
|
|
62
|
-
let r = e.entitlements[n];
|
|
63
|
-
if (r !== void 0) return r === "unlimited" ? Infinity : r;
|
|
64
|
-
},
|
|
65
|
-
hasFeature: (e) => o() && n.has(e),
|
|
66
|
-
allowsPlugin: (e) => o() && r.has(e)
|
|
67
|
-
};
|
|
68
|
-
}, h, g = (e) => {
|
|
69
|
-
h = e;
|
|
70
|
-
}, _ = () => h, v, y = (e) => {
|
|
71
|
-
v = e;
|
|
72
|
-
}, b = () => v, x = (e, n) => t.sync(() => {
|
|
73
|
-
let t = _()?.limitFor(e, n);
|
|
74
|
-
return t === void 0 ? null : t;
|
|
75
|
-
}), S = () => {
|
|
76
|
-
let e = _(), t = e === void 0 ? void 0 : {
|
|
1
|
+
import { GAMES_PLUGIN_GRANTS as e } from "./games.js";
|
|
2
|
+
import { LicensingStatusSchema as t } from "./inspect.js";
|
|
3
|
+
import { LicenseVerifyError as n, verifyLicense as r } from "./verify.js";
|
|
4
|
+
import { getRejection as i, getSnapshot as a, makeSnapshot as o, setRejection as s, setSnapshot as c } from "./snapshot.js";
|
|
5
|
+
import { entitlementResolver as l } from "./resolver.js";
|
|
6
|
+
import { LicenseSnapshotFetchError as u } from "./cloud.js";
|
|
7
|
+
import { Data as d, Effect as f, Result as p, Schema as m } from "effect";
|
|
8
|
+
import { definePlugin as h, definePluginService as g, pluginInstanceName as _ } from "@voltro/protocol";
|
|
9
|
+
import { pluginEnv as v } from "@voltro/env";
|
|
10
|
+
//#region src/index.ts
|
|
11
|
+
var y = () => {
|
|
12
|
+
let e = a(), t = e === void 0 ? void 0 : {
|
|
77
13
|
state: e.state(),
|
|
78
14
|
edition: e.claims.edition,
|
|
79
15
|
expiresAt: e.claims.exp,
|
|
80
16
|
...e.claims.tenant === void 0 ? {} : { tenant: e.claims.tenant }
|
|
81
|
-
}, n =
|
|
17
|
+
}, n = i();
|
|
82
18
|
return n === void 0 ? t ?? { state: "none" } : {
|
|
83
19
|
state: "rejected",
|
|
84
20
|
reason: n.reason,
|
|
@@ -89,7 +25,7 @@ var c = class extends e.TaggedError("LicenseVerifyError") {}, l = (e) => typeof
|
|
|
89
25
|
expiresAt: t.expiresAt
|
|
90
26
|
} }
|
|
91
27
|
};
|
|
92
|
-
},
|
|
28
|
+
}, b = v([{
|
|
93
29
|
name: "VOLTRO_LICENSE_PUBLIC_KEY",
|
|
94
30
|
required: !1,
|
|
95
31
|
secret: !1,
|
|
@@ -97,68 +33,97 @@ var c = class extends e.TaggedError("LicenseVerifyError") {}, l = (e) => typeof
|
|
|
97
33
|
}, {
|
|
98
34
|
name: "VOLTRO_LICENSE_KEY",
|
|
99
35
|
required: !1,
|
|
100
|
-
secret: !
|
|
101
|
-
description: "The signed EdDSA license token, verified offline at boot."
|
|
102
|
-
}]), { Tag:
|
|
103
|
-
snapshot: () =>
|
|
104
|
-
status: () =>
|
|
105
|
-
limitFor: (e, t) =>
|
|
106
|
-
hasFeature: (e) =>
|
|
107
|
-
allowsPlugin: (e) =>
|
|
108
|
-
edition: () =>
|
|
109
|
-
}),
|
|
110
|
-
let
|
|
36
|
+
secret: !0,
|
|
37
|
+
description: "The signed EdDSA license token, verified offline at boot; private local custody."
|
|
38
|
+
}]), { Tag: x, Live: S } = g("@voltro/plugin-licensing/LicenseService", {
|
|
39
|
+
snapshot: () => a(),
|
|
40
|
+
status: () => y(),
|
|
41
|
+
limitFor: (e, t) => a()?.limitFor(e, t),
|
|
42
|
+
hasFeature: (e) => a()?.hasFeature(e) ?? !1,
|
|
43
|
+
allowsPlugin: (e) => a()?.allowsPlugin(e) ?? !1,
|
|
44
|
+
edition: () => a()?.claims.edition
|
|
45
|
+
}), C = class extends d.TaggedError("PluginNotLicensed") {}, w = (e) => a()?.allowsPlugin(e) ?? !1, T = (e) => w(e) ? f.void : f.fail(new C({ plugin: e })), E = (n = {}) => {
|
|
46
|
+
let l = b.read("VOLTRO_LICENSE_PUBLIC_KEY", n.publicKey), u = b.read("VOLTRO_LICENSE_KEY", n.licenseKey), d = n.graceSeconds ?? 0, g = n.refreshMs ?? 9e5, v = n.fetchSnapshot;
|
|
47
|
+
if (!Number.isSafeInteger(g) || g <= 0 || !Number.isSafeInteger(d) || d < 0 || v !== void 0 && typeof v != "function") throw Error("Licensing requires positive whole-millisecond refresh, nonnegative whole-second grace and a callable snapshot port");
|
|
48
|
+
let y, x = 0, C = !1, w = (e, t, n) => r(e, t, { graceSeconds: d }).pipe(f.match({
|
|
111
49
|
onSuccess: (e) => {
|
|
112
|
-
|
|
50
|
+
C && x === n && (c(o(e, { graceSeconds: d })), s(void 0), y?.info("license active", {
|
|
113
51
|
edition: e.edition,
|
|
114
52
|
entitlements: Object.keys(e.entitlements).length,
|
|
115
53
|
exp: e.exp
|
|
116
|
-
});
|
|
54
|
+
}));
|
|
117
55
|
},
|
|
118
56
|
onFailure: (e) => {
|
|
119
|
-
|
|
57
|
+
C && x === n && (s({
|
|
120
58
|
reason: e.reason,
|
|
121
59
|
message: e.message
|
|
122
|
-
}),
|
|
60
|
+
}), y?.warn("license verification failed — entitlements fall back to static plan", {
|
|
123
61
|
reason: e.reason,
|
|
124
62
|
message: e.message
|
|
125
|
-
});
|
|
63
|
+
}));
|
|
126
64
|
}
|
|
127
65
|
}));
|
|
128
|
-
return
|
|
129
|
-
name:
|
|
66
|
+
return h({
|
|
67
|
+
name: _({
|
|
130
68
|
base: "@voltro/plugin-licensing",
|
|
131
|
-
instance:
|
|
69
|
+
instance: n.name
|
|
132
70
|
}),
|
|
71
|
+
baseName: "@voltro/plugin-licensing",
|
|
72
|
+
permissions: ["inspect:read"],
|
|
73
|
+
inspectEndpoints: [{
|
|
74
|
+
method: "GET",
|
|
75
|
+
path: "/status",
|
|
76
|
+
scope: "process",
|
|
77
|
+
description: "Redacted current-process license status; no credentials, tenant IDs or entitlement values.",
|
|
78
|
+
handler: () => f.try({
|
|
79
|
+
try: () => {
|
|
80
|
+
let n = a(), r = i(), o = n?.state(), s = n === void 0 ? null : {
|
|
81
|
+
state: o,
|
|
82
|
+
edition: n.claims.edition,
|
|
83
|
+
expiresAt: n.claims.exp,
|
|
84
|
+
scope: n.claims.tenant === void 0 ? "deployment" : "tenant",
|
|
85
|
+
gamesGrants: o === "expired" ? [] : e.filter((e) => n.allowsPlugin(e))
|
|
86
|
+
};
|
|
87
|
+
return {
|
|
88
|
+
kind: "json",
|
|
89
|
+
data: m.decodeUnknownSync(t)({
|
|
90
|
+
observedAt: Date.now(),
|
|
91
|
+
activated: C,
|
|
92
|
+
state: r === void 0 ? o ?? "none" : "rejected",
|
|
93
|
+
installed: s,
|
|
94
|
+
rejection: r?.reason ?? null,
|
|
95
|
+
graceSeconds: d,
|
|
96
|
+
refreshIntervalMs: v === void 0 || l === void 0 ? null : g
|
|
97
|
+
})
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
catch: () => /* @__PURE__ */ Error("License inspection unavailable")
|
|
101
|
+
})
|
|
102
|
+
}],
|
|
133
103
|
description: "Offline-verified EdDSA license keys + entitlement snapshots; feeds @voltro/plugin-billing via entitlementResolver.",
|
|
134
|
-
declaredEnv:
|
|
135
|
-
services:
|
|
136
|
-
onActivate: (e) =>
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
yield* l(a, r);
|
|
104
|
+
declaredEnv: b.declared,
|
|
105
|
+
services: S,
|
|
106
|
+
onActivate: (e) => f.gen(function* () {
|
|
107
|
+
y = e.logger, C = !0;
|
|
108
|
+
let t = ++x;
|
|
109
|
+
c(void 0), s(void 0), l === void 0 || u === void 0 ? e.logger.debug("licensing: no license configured — static plan entitlements apply") : yield* w(u, l, t);
|
|
142
110
|
}),
|
|
143
|
-
bindDataStore: (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
} catch (e) {
|
|
154
|
-
c?.warn("license refresh failed", { message: e instanceof Error ? e.message : String(e) });
|
|
155
|
-
}
|
|
111
|
+
bindDataStore: (e, t) => {
|
|
112
|
+
if (v === void 0 || l === void 0 || t?.scheduleCoordinated === void 0) return;
|
|
113
|
+
let n = l;
|
|
114
|
+
t.scheduleCoordinated("licensing.refresh", g, async () => {
|
|
115
|
+
if (!C) return;
|
|
116
|
+
let e = x, t = await f.runPromise(f.result(v()));
|
|
117
|
+
C && x === e && (p.isFailure(t) ? y?.warn("license refresh failed — retaining the last verified snapshot", {
|
|
118
|
+
reason: t.failure.reason,
|
|
119
|
+
...t.failure.status === void 0 ? {} : { status: t.failure.status }
|
|
120
|
+
}) : await f.runPromise(w(t.success, n, e)));
|
|
156
121
|
});
|
|
157
122
|
},
|
|
158
|
-
onDeactivate: () =>
|
|
159
|
-
|
|
123
|
+
onDeactivate: () => f.sync(() => {
|
|
124
|
+
C = !1, x++, c(void 0), s(void 0);
|
|
160
125
|
})
|
|
161
126
|
});
|
|
162
127
|
};
|
|
163
128
|
//#endregion
|
|
164
|
-
export {
|
|
129
|
+
export { x as LicenseService, u as LicenseSnapshotFetchError, n as LicenseVerifyError, C as PluginNotLicensed, l as entitlementResolver, w as licenseAllowsPlugin, y as licenseStatus, E as licensingPlugin, T as requireLicensedPlugin, r as verifyLicense };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
|
|
3
|
+
declare const base: Schema.Struct<{
|
|
4
|
+
observedAt: Schema.Number;
|
|
5
|
+
activated: Schema.Boolean;
|
|
6
|
+
state: Schema.Literals<readonly ["none", "active", "grace", "expired", "rejected"]>;
|
|
7
|
+
installed: Schema.NullOr<Schema.Struct<{
|
|
8
|
+
readonly state: Schema.Literals<readonly ["active", "grace", "expired"]>;
|
|
9
|
+
readonly edition: Schema.String;
|
|
10
|
+
readonly expiresAt: Schema.Number;
|
|
11
|
+
readonly scope: Schema.Literals<readonly ["deployment", "tenant"]>;
|
|
12
|
+
readonly gamesGrants: Schema.$Array<Schema.Literals<readonly ["rooms", "matchmaking", "leaderboards", "social", "chat", "guilds", "inventory", "economy", "progression", "liveops", "players", "saves", "sanctions", "gm"]>>;
|
|
13
|
+
}>>;
|
|
14
|
+
rejection: Schema.NullOr<Schema.Literals<readonly ["expired", "invalid", "key", "malformed"]>>;
|
|
15
|
+
graceSeconds: Schema.Number;
|
|
16
|
+
refreshIntervalMs: Schema.NullOr<Schema.Number>;
|
|
17
|
+
}>;
|
|
18
|
+
|
|
19
|
+
export declare const isLicensingStatusConsistent: (value: typeof base.Type) => boolean;
|
|
20
|
+
|
|
21
|
+
export declare type LicensingStatus = typeof LicensingStatusSchema.Type;
|
|
22
|
+
|
|
23
|
+
export declare const LicensingStatusFields: {
|
|
24
|
+
observedAt: Schema.Number;
|
|
25
|
+
activated: Schema.Boolean;
|
|
26
|
+
state: Schema.Literals<readonly ["none", "active", "grace", "expired", "rejected"]>;
|
|
27
|
+
installed: Schema.NullOr<Schema.Struct<{
|
|
28
|
+
readonly state: Schema.Literals<readonly ["active", "grace", "expired"]>;
|
|
29
|
+
readonly edition: Schema.String;
|
|
30
|
+
readonly expiresAt: Schema.Number;
|
|
31
|
+
readonly scope: Schema.Literals<readonly ["deployment", "tenant"]>;
|
|
32
|
+
readonly gamesGrants: Schema.$Array<Schema.Literals<readonly ["rooms", "matchmaking", "leaderboards", "social", "chat", "guilds", "inventory", "economy", "progression", "liveops", "players", "saves", "sanctions", "gm"]>>;
|
|
33
|
+
}>>;
|
|
34
|
+
rejection: Schema.NullOr<Schema.Literals<readonly ["expired", "invalid", "key", "malformed"]>>;
|
|
35
|
+
graceSeconds: Schema.Number;
|
|
36
|
+
refreshIntervalMs: Schema.NullOr<Schema.Number>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Browser-safe redacted process observation, never a token or grant authority. */
|
|
40
|
+
export declare const LicensingStatusSchema: Schema.Struct<{
|
|
41
|
+
observedAt: Schema.Number;
|
|
42
|
+
activated: Schema.Boolean;
|
|
43
|
+
state: Schema.Literals<readonly ["none", "active", "grace", "expired", "rejected"]>;
|
|
44
|
+
installed: Schema.NullOr<Schema.Struct<{
|
|
45
|
+
readonly state: Schema.Literals<readonly ["active", "grace", "expired"]>;
|
|
46
|
+
readonly edition: Schema.String;
|
|
47
|
+
readonly expiresAt: Schema.Number;
|
|
48
|
+
readonly scope: Schema.Literals<readonly ["deployment", "tenant"]>;
|
|
49
|
+
readonly gamesGrants: Schema.$Array<Schema.Literals<readonly ["rooms", "matchmaking", "leaderboards", "social", "chat", "guilds", "inventory", "economy", "progression", "liveops", "players", "saves", "sanctions", "gm"]>>;
|
|
50
|
+
}>>;
|
|
51
|
+
rejection: Schema.NullOr<Schema.Literals<readonly ["expired", "invalid", "key", "malformed"]>>;
|
|
52
|
+
graceSeconds: Schema.Number;
|
|
53
|
+
refreshIntervalMs: Schema.NullOr<Schema.Number>;
|
|
54
|
+
}>;
|
|
55
|
+
|
|
56
|
+
export { }
|
package/dist/inspect.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { GAMES_PLUGIN_GRANTS as e } from "./games.js";
|
|
2
|
+
import { Schema as t } from "effect";
|
|
3
|
+
//#region src/inspect.ts
|
|
4
|
+
var n = t.Number.pipe(t.check(t.isInt()), t.check(t.isBetween({
|
|
5
|
+
minimum: 0,
|
|
6
|
+
maximum: 2 ** 53 - 1
|
|
7
|
+
}))), r = t.Literals([
|
|
8
|
+
"active",
|
|
9
|
+
"grace",
|
|
10
|
+
"expired"
|
|
11
|
+
]), i = {
|
|
12
|
+
observedAt: t.Number.pipe(t.check(t.isInt()), t.check(t.isBetween({
|
|
13
|
+
minimum: 0,
|
|
14
|
+
maximum: 864e13
|
|
15
|
+
}))),
|
|
16
|
+
activated: t.Boolean,
|
|
17
|
+
state: t.Literals([
|
|
18
|
+
"none",
|
|
19
|
+
"active",
|
|
20
|
+
"grace",
|
|
21
|
+
"expired",
|
|
22
|
+
"rejected"
|
|
23
|
+
]),
|
|
24
|
+
installed: t.NullOr(t.Struct({
|
|
25
|
+
state: r,
|
|
26
|
+
edition: t.String.pipe(t.check(t.isMinLength(1)), t.check(t.isMaxLength(256))),
|
|
27
|
+
expiresAt: t.Number.pipe(t.check(t.isInt()), t.check(t.isBetween({
|
|
28
|
+
minimum: 0,
|
|
29
|
+
maximum: 864e10
|
|
30
|
+
}))),
|
|
31
|
+
scope: t.Literals(["deployment", "tenant"]),
|
|
32
|
+
gamesGrants: t.Array(t.Literals(e))
|
|
33
|
+
})),
|
|
34
|
+
rejection: t.NullOr(t.Literals([
|
|
35
|
+
"expired",
|
|
36
|
+
"invalid",
|
|
37
|
+
"key",
|
|
38
|
+
"malformed"
|
|
39
|
+
])),
|
|
40
|
+
graceSeconds: n,
|
|
41
|
+
refreshIntervalMs: t.NullOr(n.pipe(t.check(t.isGreaterThan(0))))
|
|
42
|
+
}, a = t.Struct(i), o = (e) => {
|
|
43
|
+
if (e.state === "rejected" != (e.rejection !== null) || e.state === "none" && e.installed !== null || e.state !== "none" && e.state !== "rejected" && e.installed?.state !== e.state) return !1;
|
|
44
|
+
let t = e.installed;
|
|
45
|
+
return !(t !== null && (new Set(t.gamesGrants).size !== t.gamesGrants.length || t.state === "expired" && t.gamesGrants.length > 0 || t.state === "grace" && (e.graceSeconds === 0 || t.expiresAt === 0)));
|
|
46
|
+
}, s = a.pipe(t.check(t.makeFilter(o)));
|
|
47
|
+
//#endregion
|
|
48
|
+
export { i as LicensingStatusFields, s as LicensingStatusSchema, o as isLicensingStatusConsistent };
|