@buoy-gg/license 1.7.2
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/lib/commonjs/LicenseManager.js +887 -0
- package/lib/commonjs/api.js +198 -0
- package/lib/commonjs/config.js +26 -0
- package/lib/commonjs/device-info.js +93 -0
- package/lib/commonjs/fingerprint.js +30 -0
- package/lib/commonjs/hooks.js +216 -0
- package/lib/commonjs/index.js +103 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/types.js +1 -0
- package/lib/module/LicenseManager.js +884 -0
- package/lib/module/api.js +187 -0
- package/lib/module/config.js +22 -0
- package/lib/module/device-info.js +87 -0
- package/lib/module/fingerprint.js +10 -0
- package/lib/module/hooks.js +208 -0
- package/lib/module/index.js +43 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +1 -0
- package/lib/typescript/commonjs/LicenseManager.d.ts +132 -0
- package/lib/typescript/commonjs/LicenseManager.d.ts.map +1 -0
- package/lib/typescript/commonjs/api.d.ts +38 -0
- package/lib/typescript/commonjs/api.d.ts.map +1 -0
- package/lib/typescript/commonjs/config.d.ts +13 -0
- package/lib/typescript/commonjs/config.d.ts.map +1 -0
- package/lib/typescript/commonjs/device-info.d.ts +23 -0
- package/lib/typescript/commonjs/device-info.d.ts.map +1 -0
- package/lib/typescript/commonjs/fingerprint.d.ts +8 -0
- package/lib/typescript/commonjs/fingerprint.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks.d.ts +68 -0
- package/lib/typescript/commonjs/hooks.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +13 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/types.d.ts +200 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -0
- package/lib/typescript/module/LicenseManager.d.ts +132 -0
- package/lib/typescript/module/LicenseManager.d.ts.map +1 -0
- package/lib/typescript/module/api.d.ts +38 -0
- package/lib/typescript/module/api.d.ts.map +1 -0
- package/lib/typescript/module/config.d.ts +13 -0
- package/lib/typescript/module/config.d.ts.map +1 -0
- package/lib/typescript/module/device-info.d.ts +23 -0
- package/lib/typescript/module/device-info.d.ts.map +1 -0
- package/lib/typescript/module/fingerprint.d.ts +8 -0
- package/lib/typescript/module/fingerprint.d.ts.map +1 -0
- package/lib/typescript/module/hooks.d.ts +68 -0
- package/lib/typescript/module/hooks.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +13 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/types.d.ts +200 -0
- package/lib/typescript/module/types.d.ts.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keygen REST API client for React Buoy
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { KEYGEN_CONFIG } from "./config.js";
|
|
8
|
+
const {
|
|
9
|
+
accountId,
|
|
10
|
+
productToken,
|
|
11
|
+
apiUrl
|
|
12
|
+
} = KEYGEN_CONFIG;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Base headers for Keygen API requests (without auth)
|
|
16
|
+
*/
|
|
17
|
+
function getBaseHeaders() {
|
|
18
|
+
return {
|
|
19
|
+
"Content-Type": "application/vnd.api+json",
|
|
20
|
+
Accept: "application/vnd.api+json"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Headers for license-authenticated API requests (machine operations)
|
|
26
|
+
* Uses the license key itself for authentication
|
|
27
|
+
*/
|
|
28
|
+
function getLicenseAuthHeaders(licenseKey) {
|
|
29
|
+
return {
|
|
30
|
+
...getBaseHeaders(),
|
|
31
|
+
Authorization: `License ${licenseKey}`
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate a license key with Keygen
|
|
37
|
+
*/
|
|
38
|
+
export async function validateLicense(licenseKey, fingerprint) {
|
|
39
|
+
// Include license.machines to get the machine count in the response
|
|
40
|
+
const url = `${apiUrl}/${accountId}/licenses/actions/validate-key?include=license.machines`;
|
|
41
|
+
const body = {
|
|
42
|
+
meta: {
|
|
43
|
+
key: licenseKey
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Include fingerprint if provided (for machine-scoped validation)
|
|
48
|
+
if (fingerprint) {
|
|
49
|
+
body.meta.scope = {
|
|
50
|
+
fingerprint
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Note: validate-key endpoint doesn't need Authorization header
|
|
55
|
+
// The license key in the body is the authentication
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: getBaseHeaders(),
|
|
59
|
+
body: JSON.stringify(body)
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const errorData = await response.json();
|
|
63
|
+
const errorMessage = errorData.errors?.[0]?.detail || `HTTP ${response.status}`;
|
|
64
|
+
throw new Error(errorMessage);
|
|
65
|
+
}
|
|
66
|
+
return await response.json();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Activate a machine for a license
|
|
71
|
+
*/
|
|
72
|
+
export async function activateMachine(licenseId, licenseKey, fingerprint, name, metadata) {
|
|
73
|
+
const url = `${apiUrl}/${accountId}/machines`;
|
|
74
|
+
const body = {
|
|
75
|
+
data: {
|
|
76
|
+
type: "machines",
|
|
77
|
+
attributes: {
|
|
78
|
+
fingerprint,
|
|
79
|
+
name,
|
|
80
|
+
metadata: metadata || {}
|
|
81
|
+
},
|
|
82
|
+
relationships: {
|
|
83
|
+
license: {
|
|
84
|
+
data: {
|
|
85
|
+
type: "licenses",
|
|
86
|
+
id: licenseId
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const response = await fetch(url, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: getLicenseAuthHeaders(licenseKey),
|
|
95
|
+
body: JSON.stringify(body)
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const errorData = await response.json();
|
|
99
|
+
const errorCode = errorData.errors?.[0]?.code;
|
|
100
|
+
const errorMessage = errorData.errors?.[0]?.detail || `HTTP ${response.status}`;
|
|
101
|
+
|
|
102
|
+
// Check if machine limit reached
|
|
103
|
+
if (errorCode === "MACHINE_LIMIT_EXCEEDED" || errorMessage.includes("machine limit")) {
|
|
104
|
+
throw new Error("MACHINE_LIMIT_EXCEEDED");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check if fingerprint already exists
|
|
108
|
+
if (errorCode === "FINGERPRINT_TAKEN" || errorMessage.includes("fingerprint")) {
|
|
109
|
+
throw new Error("FINGERPRINT_TAKEN");
|
|
110
|
+
}
|
|
111
|
+
throw new Error(errorMessage);
|
|
112
|
+
}
|
|
113
|
+
return response.json();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Deactivate a machine from a license
|
|
118
|
+
*/
|
|
119
|
+
export async function deactivateMachine(machineId, licenseKey) {
|
|
120
|
+
const url = `${apiUrl}/${accountId}/machines/${machineId}`;
|
|
121
|
+
const response = await fetch(url, {
|
|
122
|
+
method: "DELETE",
|
|
123
|
+
headers: getLicenseAuthHeaders(licenseKey)
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok && response.status !== 404) {
|
|
126
|
+
const errorData = await response.json();
|
|
127
|
+
const errorMessage = errorData.errors?.[0]?.detail || `HTTP ${response.status}`;
|
|
128
|
+
throw new Error(errorMessage);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get all machines for a license
|
|
134
|
+
*/
|
|
135
|
+
export async function getLicenseMachines(licenseId, licenseKey) {
|
|
136
|
+
const url = `${apiUrl}/${accountId}/licenses/${licenseId}/machines`;
|
|
137
|
+
const response = await fetch(url, {
|
|
138
|
+
method: "GET",
|
|
139
|
+
headers: getLicenseAuthHeaders(licenseKey)
|
|
140
|
+
});
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
const errorData = await response.json();
|
|
143
|
+
const errorMessage = errorData.errors?.[0]?.detail || `HTTP ${response.status}`;
|
|
144
|
+
throw new Error(errorMessage);
|
|
145
|
+
}
|
|
146
|
+
const data = await response.json();
|
|
147
|
+
return data.data || [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Parse validation response into a simpler result
|
|
152
|
+
* Note: response.data can be null when license is invalid (e.g., NOT_FOUND)
|
|
153
|
+
*/
|
|
154
|
+
export function parseValidationResult(response) {
|
|
155
|
+
const {
|
|
156
|
+
meta,
|
|
157
|
+
data
|
|
158
|
+
} = response;
|
|
159
|
+
return {
|
|
160
|
+
valid: meta.valid,
|
|
161
|
+
code: meta.code,
|
|
162
|
+
detail: meta.detail,
|
|
163
|
+
// data can be null for invalid licenses
|
|
164
|
+
metadata: data?.attributes?.metadata
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check if a validation code indicates the license needs machine activation
|
|
170
|
+
*/
|
|
171
|
+
export function needsMachineActivation(code) {
|
|
172
|
+
return code === "NO_MACHINE" || code === "NO_MACHINES";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if a validation code indicates too many machines
|
|
177
|
+
*/
|
|
178
|
+
export function hasTooManyMachines(code) {
|
|
179
|
+
return code === "TOO_MANY_MACHINES";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if a validation code indicates a fatal error (license invalid)
|
|
184
|
+
*/
|
|
185
|
+
export function isFatalLicenseError(code) {
|
|
186
|
+
return ["NOT_FOUND", "SUSPENDED", "EXPIRED", "BANNED", "PRODUCT_SCOPE_MISMATCH"].includes(code);
|
|
187
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keygen configuration for React Buoy
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const KEYGEN_CONFIG = {
|
|
8
|
+
accountId: "b0eba0e8-e904-4906-b644-38f9aab57bed",
|
|
9
|
+
productId: "1037431d-b2fe-48db-aca9-10c5e9c4d944",
|
|
10
|
+
// Product token - read-only, safe to embed in SDK
|
|
11
|
+
productToken: "prod-4467c14b75b715e91884945cd3e69705846ccbce7dd5e380236a1ab3bc03139fv3",
|
|
12
|
+
apiUrl: "https://api.keygen.sh/v1/accounts"
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Cache duration: 30 days in milliseconds
|
|
16
|
+
export const LICENSE_CACHE_DURATION = 30 * 24 * 60 * 60 * 1000;
|
|
17
|
+
|
|
18
|
+
// How often to revalidate when app is active (4 hours)
|
|
19
|
+
export const LICENSE_REVALIDATION_INTERVAL = 4 * 60 * 60 * 1000;
|
|
20
|
+
|
|
21
|
+
// Storage key for cached license
|
|
22
|
+
export const LICENSE_STORAGE_KEY = "@buoy-gg/license";
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure JS device info utilities
|
|
5
|
+
*
|
|
6
|
+
* No native module dependencies - uses only React Native core APIs.
|
|
7
|
+
* Device fingerprint is a generated UUID stored alongside license data.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Platform } from "react-native";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate a UUID v4
|
|
14
|
+
*/
|
|
15
|
+
export function generateUUID() {
|
|
16
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
|
|
17
|
+
const r = Math.random() * 16 | 0;
|
|
18
|
+
const v = c === "x" ? r : r & 0x3 | 0x8;
|
|
19
|
+
return v.toString(16);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check if running on a simulator/emulator using Platform constants
|
|
25
|
+
*/
|
|
26
|
+
export function isSimulator() {
|
|
27
|
+
if (Platform.OS === "ios") {
|
|
28
|
+
// On iOS simulator, we can check various indicators
|
|
29
|
+
const constants = Platform.constants;
|
|
30
|
+
|
|
31
|
+
// Check for simulator interface idiom
|
|
32
|
+
if (constants?.interfaceIdiom === "simulator") {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if running in a testing environment
|
|
37
|
+
if ("isTesting" in Platform && Platform.isTesting) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (Platform.OS === "android") {
|
|
43
|
+
const constants = Platform.constants;
|
|
44
|
+
const brand = String(constants?.Brand || "").toLowerCase();
|
|
45
|
+
const model = String(constants?.Model || "").toLowerCase();
|
|
46
|
+
const fingerprint = String(constants?.Fingerprint || "").toLowerCase();
|
|
47
|
+
const isEmulator = brand.includes("generic") || model.includes("sdk") || model.includes("emulator") || model.includes("android sdk") || fingerprint.includes("generic");
|
|
48
|
+
return isEmulator;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get basic device metadata from Platform APIs
|
|
55
|
+
*/
|
|
56
|
+
export function getDeviceMetadata() {
|
|
57
|
+
const metadata = {
|
|
58
|
+
platform: Platform.OS,
|
|
59
|
+
osVersion: String(Platform.Version),
|
|
60
|
+
isSimulator: String(isSimulator())
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Add Android-specific constants if available
|
|
64
|
+
if (Platform.OS === "android") {
|
|
65
|
+
const constants = Platform.constants;
|
|
66
|
+
if (constants?.Brand) metadata.brand = String(constants.Brand);
|
|
67
|
+
if (constants?.Model) metadata.model = String(constants.Model);
|
|
68
|
+
if (constants?.Manufacturer) metadata.manufacturer = String(constants.Manufacturer);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Add iOS-specific constants if available
|
|
72
|
+
if (Platform.OS === "ios") {
|
|
73
|
+
const constants = Platform.constants;
|
|
74
|
+
if (constants?.systemName) metadata.systemName = String(constants.systemName);
|
|
75
|
+
if (constants?.interfaceIdiom) metadata.interfaceIdiom = String(constants.interfaceIdiom);
|
|
76
|
+
}
|
|
77
|
+
return metadata;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get a default device name suggestion based on platform
|
|
82
|
+
*/
|
|
83
|
+
export function getDefaultDeviceName() {
|
|
84
|
+
const platformName = Platform.OS === "ios" ? "iOS" : Platform.OS === "android" ? "Android" : Platform.OS;
|
|
85
|
+
const sim = isSimulator() ? " Simulator" : "";
|
|
86
|
+
return `My ${platformName} Device${sim}`;
|
|
87
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Device fingerprint utilities for license activation
|
|
5
|
+
*
|
|
6
|
+
* The fingerprint is a generated UUID that is stored alongside the license.
|
|
7
|
+
* It's created when the user registers their device and persists with the license data.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { generateUUID, isSimulator, getDeviceMetadata, getDefaultDeviceName } from "./device-info.js";
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* React hooks for license management
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useEffect, useCallback, useRef } from "react";
|
|
8
|
+
import { LicenseManager } from "./LicenseManager.js";
|
|
9
|
+
/**
|
|
10
|
+
* Hook to access the current license state
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* function MyComponent() {
|
|
15
|
+
* const { isPro, isValidating, error } = useLicense();
|
|
16
|
+
*
|
|
17
|
+
* if (isValidating) return <Loading />;
|
|
18
|
+
* if (isPro) return <ProFeature />;
|
|
19
|
+
* return <UpgradePrompt />;
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function useLicense() {
|
|
24
|
+
const [state, setState] = useState(LicenseManager.getState());
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
// Initialize on first mount
|
|
27
|
+
LicenseManager.initialize();
|
|
28
|
+
|
|
29
|
+
// Subscribe to license changes
|
|
30
|
+
const unsubscribe = LicenseManager.subscribe(event => {
|
|
31
|
+
setState(LicenseManager.getState());
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Get initial state after initialization
|
|
35
|
+
setState(LicenseManager.getState());
|
|
36
|
+
return unsubscribe;
|
|
37
|
+
}, []);
|
|
38
|
+
const setLicenseKey = useCallback(async key => {
|
|
39
|
+
const result = await LicenseManager.setLicenseKey(key);
|
|
40
|
+
setState(LicenseManager.getState());
|
|
41
|
+
return result;
|
|
42
|
+
}, []);
|
|
43
|
+
const validateLicenseKey = useCallback(async key => {
|
|
44
|
+
const result = await LicenseManager.validateLicenseKey(key);
|
|
45
|
+
const newState = LicenseManager.getState();
|
|
46
|
+
setState(newState);
|
|
47
|
+
return result;
|
|
48
|
+
}, []);
|
|
49
|
+
const registerDevice = useCallback(async deviceName => {
|
|
50
|
+
const result = await LicenseManager.registerDevice(deviceName);
|
|
51
|
+
setState(LicenseManager.getState());
|
|
52
|
+
return result;
|
|
53
|
+
}, []);
|
|
54
|
+
const claimDevice = useCallback(async (machineId, existingDevices) => {
|
|
55
|
+
const result = await LicenseManager.claimDevice(machineId, existingDevices);
|
|
56
|
+
setState(LicenseManager.getState());
|
|
57
|
+
return result;
|
|
58
|
+
}, []);
|
|
59
|
+
const clearLicense = useCallback(async () => {
|
|
60
|
+
await LicenseManager.clearLicense();
|
|
61
|
+
setState(LicenseManager.getState());
|
|
62
|
+
}, []);
|
|
63
|
+
const clearCacheAndRevalidate = useCallback(async () => {
|
|
64
|
+
const result = await LicenseManager.clearCacheAndRevalidate();
|
|
65
|
+
setState(LicenseManager.getState());
|
|
66
|
+
return result;
|
|
67
|
+
}, []);
|
|
68
|
+
return {
|
|
69
|
+
...state,
|
|
70
|
+
setLicenseKey,
|
|
71
|
+
validateLicenseKey,
|
|
72
|
+
registerDevice,
|
|
73
|
+
claimDevice,
|
|
74
|
+
clearLicense,
|
|
75
|
+
clearCacheAndRevalidate
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Hook to check if a feature is available based on license
|
|
81
|
+
*/
|
|
82
|
+
export function useFeatureAccess(featureCode) {
|
|
83
|
+
const {
|
|
84
|
+
isPro,
|
|
85
|
+
isInitialized,
|
|
86
|
+
isValidating
|
|
87
|
+
} = useLicense();
|
|
88
|
+
const hasAccess = isPro;
|
|
89
|
+
const isLoading = !isInitialized || isValidating;
|
|
90
|
+
return {
|
|
91
|
+
hasAccess,
|
|
92
|
+
isPro,
|
|
93
|
+
isLoading
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Hook to get seat/machine information
|
|
99
|
+
*/
|
|
100
|
+
export function useSeats() {
|
|
101
|
+
const [seats, setSeats] = useState(LicenseManager.getSeatsInfo());
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
const unsubscribe = LicenseManager.subscribe(() => {
|
|
104
|
+
setSeats(LicenseManager.getSeatsInfo());
|
|
105
|
+
});
|
|
106
|
+
return unsubscribe;
|
|
107
|
+
}, []);
|
|
108
|
+
return {
|
|
109
|
+
...seats,
|
|
110
|
+
remaining: seats.total !== null ? seats.total - seats.used : null
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Hook that returns true only when Pro and not loading
|
|
116
|
+
*/
|
|
117
|
+
export function useIsPro() {
|
|
118
|
+
const {
|
|
119
|
+
isPro,
|
|
120
|
+
isInitialized
|
|
121
|
+
} = useLicense();
|
|
122
|
+
return isInitialized && isPro;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Hook for device management
|
|
127
|
+
*/
|
|
128
|
+
export function useDevices() {
|
|
129
|
+
const [devices, setDevices] = useState([]);
|
|
130
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
131
|
+
const [error, setError] = useState(null);
|
|
132
|
+
const {
|
|
133
|
+
isPro,
|
|
134
|
+
machineId
|
|
135
|
+
} = useLicense();
|
|
136
|
+
const refreshDevices = useCallback(async () => {
|
|
137
|
+
setIsLoading(true);
|
|
138
|
+
setError(null);
|
|
139
|
+
try {
|
|
140
|
+
const deviceList = await LicenseManager.getRegisteredDevices();
|
|
141
|
+
setDevices(deviceList);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
setError(err instanceof Error ? err.message : "Failed to load devices");
|
|
144
|
+
} finally {
|
|
145
|
+
setIsLoading(false);
|
|
146
|
+
}
|
|
147
|
+
}, []);
|
|
148
|
+
|
|
149
|
+
// Use ref to avoid infinite loop risk if refreshDevices dependencies change
|
|
150
|
+
const refreshDevicesRef = useRef(refreshDevices);
|
|
151
|
+
refreshDevicesRef.current = refreshDevices;
|
|
152
|
+
const registerDevice = useCallback(async deviceName => {
|
|
153
|
+
setIsLoading(true);
|
|
154
|
+
setError(null);
|
|
155
|
+
try {
|
|
156
|
+
const result = await LicenseManager.registerDevice(deviceName);
|
|
157
|
+
if (result.success) {
|
|
158
|
+
await refreshDevices();
|
|
159
|
+
} else if (result.error) {
|
|
160
|
+
setError(result.error);
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
} catch (err) {
|
|
164
|
+
const error = err instanceof Error ? err.message : "Failed to register device";
|
|
165
|
+
setError(error);
|
|
166
|
+
return {
|
|
167
|
+
success: false,
|
|
168
|
+
error
|
|
169
|
+
};
|
|
170
|
+
} finally {
|
|
171
|
+
setIsLoading(false);
|
|
172
|
+
}
|
|
173
|
+
}, [refreshDevices]);
|
|
174
|
+
const deactivateDevice = useCallback(async machineIdToDeactivate => {
|
|
175
|
+
setIsLoading(true);
|
|
176
|
+
setError(null);
|
|
177
|
+
try {
|
|
178
|
+
const result = await LicenseManager.deactivateDevice(machineIdToDeactivate);
|
|
179
|
+
if (result) {
|
|
180
|
+
await refreshDevices();
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
} catch (err) {
|
|
184
|
+
setError(err instanceof Error ? err.message : "Failed to deactivate device");
|
|
185
|
+
return false;
|
|
186
|
+
} finally {
|
|
187
|
+
setIsLoading(false);
|
|
188
|
+
}
|
|
189
|
+
}, [refreshDevices]);
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (isPro) {
|
|
192
|
+
refreshDevicesRef.current();
|
|
193
|
+
} else {
|
|
194
|
+
setDevices([]);
|
|
195
|
+
}
|
|
196
|
+
}, [isPro]);
|
|
197
|
+
return {
|
|
198
|
+
devices,
|
|
199
|
+
isLoading,
|
|
200
|
+
error,
|
|
201
|
+
refreshDevices,
|
|
202
|
+
registerDevice,
|
|
203
|
+
deactivateDevice,
|
|
204
|
+
isCurrentDeviceRegistered: machineId !== null
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Re-export types for convenience
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @buoy-gg/license
|
|
5
|
+
*
|
|
6
|
+
* License validation for React Buoy Pro features
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Main manager
|
|
10
|
+
export { LicenseManager } from "./LicenseManager.js";
|
|
11
|
+
|
|
12
|
+
// React hooks
|
|
13
|
+
export { useLicense, useFeatureAccess, useSeats, useIsPro, useDevices } from "./hooks.js";
|
|
14
|
+
|
|
15
|
+
// Types
|
|
16
|
+
|
|
17
|
+
// Utilities (for advanced use cases)
|
|
18
|
+
export { generateUUID, isSimulator, getDeviceMetadata, getDefaultDeviceName } from "./fingerprint.js";
|
|
19
|
+
|
|
20
|
+
// Convenience function for setting license key
|
|
21
|
+
export async function setLicenseKey(key) {
|
|
22
|
+
const {
|
|
23
|
+
LicenseManager: LM
|
|
24
|
+
} = await import("./LicenseManager");
|
|
25
|
+
await LM.initialize();
|
|
26
|
+
return LM.setLicenseKey(key);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Convenience function for checking Pro status
|
|
30
|
+
export function isPro() {
|
|
31
|
+
const {
|
|
32
|
+
LicenseManager: LM
|
|
33
|
+
} = require("./LicenseManager");
|
|
34
|
+
return LM.isPro();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Convenience function for checking entitlement
|
|
38
|
+
export function hasEntitlement(code) {
|
|
39
|
+
const {
|
|
40
|
+
LicenseManager: LM
|
|
41
|
+
} = require("./LicenseManager");
|
|
42
|
+
return LM.hasEntitlement(code);
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LicenseManager - Singleton for managing React Buoy Pro licenses
|
|
3
|
+
*
|
|
4
|
+
* Handles license validation, machine activation, caching, and state management.
|
|
5
|
+
*
|
|
6
|
+
* New flow:
|
|
7
|
+
* 1. validateLicenseKey() - Check if license is valid, returns existing devices
|
|
8
|
+
* 2. registerDevice(deviceName) - Register new device with user-provided name
|
|
9
|
+
* 3. claimDevice(machineId) - Re-claim an existing device after storage clear
|
|
10
|
+
*/
|
|
11
|
+
import type { LicenseState, LicenseListener, RegisteredDevice } from "./types";
|
|
12
|
+
/**
|
|
13
|
+
* Result of license validation - indicates what action is needed
|
|
14
|
+
*/
|
|
15
|
+
export interface LicenseValidationResult {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
needsDeviceRegistration: boolean;
|
|
18
|
+
existingDevices: RegisteredDevice[];
|
|
19
|
+
error?: string;
|
|
20
|
+
maxDevices?: number;
|
|
21
|
+
currentDeviceCount?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Result of device registration
|
|
25
|
+
*/
|
|
26
|
+
export interface DeviceRegistrationResult {
|
|
27
|
+
success: boolean;
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
declare class LicenseManagerImpl {
|
|
31
|
+
private state;
|
|
32
|
+
private listeners;
|
|
33
|
+
private revalidationTimer;
|
|
34
|
+
private cachedLicense;
|
|
35
|
+
private pendingLicenseId;
|
|
36
|
+
private validationInProgress;
|
|
37
|
+
private registrationInProgress;
|
|
38
|
+
private initPromise;
|
|
39
|
+
/**
|
|
40
|
+
* Initialize the license manager and load cached license
|
|
41
|
+
*/
|
|
42
|
+
initialize(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Internal initialization logic
|
|
45
|
+
*/
|
|
46
|
+
private _doInitialize;
|
|
47
|
+
/**
|
|
48
|
+
* Validate a license key and check what action is needed
|
|
49
|
+
* Returns info about whether device registration is needed
|
|
50
|
+
*/
|
|
51
|
+
validateLicenseKey(licenseKey: string): Promise<LicenseValidationResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Register a new device with a user-provided name
|
|
54
|
+
*/
|
|
55
|
+
registerDevice(deviceName: string): Promise<DeviceRegistrationResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Claim an existing device (use its fingerprint)
|
|
58
|
+
* This is for when the user wants to re-use a device slot after clearing storage
|
|
59
|
+
*/
|
|
60
|
+
claimDevice(machineId: string, existingDevices: RegisteredDevice[]): Promise<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Legacy method - validates and auto-registers if needed (for backward compatibility)
|
|
63
|
+
* Note: This won't work well with the new flow - use validateLicenseKey + registerDevice instead
|
|
64
|
+
*/
|
|
65
|
+
setLicenseKey(licenseKey: string): Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Check if an error is a network error (not an API validation error)
|
|
68
|
+
*/
|
|
69
|
+
private isNetworkError;
|
|
70
|
+
/**
|
|
71
|
+
* Clear the current license
|
|
72
|
+
*/
|
|
73
|
+
clearLicense(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Check if the user has Pro features
|
|
76
|
+
*/
|
|
77
|
+
isPro(): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Check if a specific entitlement is available
|
|
80
|
+
*/
|
|
81
|
+
hasEntitlement(code: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Get current license state
|
|
84
|
+
*/
|
|
85
|
+
getState(): Readonly<LicenseState>;
|
|
86
|
+
/**
|
|
87
|
+
* Get seat information
|
|
88
|
+
*/
|
|
89
|
+
getSeatsInfo(): {
|
|
90
|
+
used: number;
|
|
91
|
+
total: number | null;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Subscribe to license events
|
|
95
|
+
*/
|
|
96
|
+
subscribe(listener: LicenseListener): () => void;
|
|
97
|
+
/**
|
|
98
|
+
* Get all registered devices for the current license
|
|
99
|
+
*/
|
|
100
|
+
getRegisteredDevices(): Promise<RegisteredDevice[]>;
|
|
101
|
+
/**
|
|
102
|
+
* Deactivate a specific device by machine ID
|
|
103
|
+
*/
|
|
104
|
+
deactivateDevice(machineId: string): Promise<boolean>;
|
|
105
|
+
/**
|
|
106
|
+
* Register the current device for the license (legacy - use registerDevice instead)
|
|
107
|
+
*/
|
|
108
|
+
registerCurrentDevice(): Promise<boolean>;
|
|
109
|
+
/**
|
|
110
|
+
* Check if the current device is registered
|
|
111
|
+
*/
|
|
112
|
+
isCurrentDeviceRegistered(): boolean;
|
|
113
|
+
private emit;
|
|
114
|
+
private countMachines;
|
|
115
|
+
private isCacheValid;
|
|
116
|
+
private loadCachedLicense;
|
|
117
|
+
private saveLicenseToCache;
|
|
118
|
+
private clearCachedLicense;
|
|
119
|
+
private validateInBackground;
|
|
120
|
+
private startRevalidationTimer;
|
|
121
|
+
/**
|
|
122
|
+
* Stop the revalidation timer
|
|
123
|
+
*/
|
|
124
|
+
stopRevalidationTimer(): void;
|
|
125
|
+
/**
|
|
126
|
+
* Clear cache and revalidate
|
|
127
|
+
*/
|
|
128
|
+
clearCacheAndRevalidate(): Promise<boolean>;
|
|
129
|
+
}
|
|
130
|
+
export declare const LicenseManager: LicenseManagerImpl;
|
|
131
|
+
export {};
|
|
132
|
+
//# sourceMappingURL=LicenseManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LicenseManager.d.ts","sourceRoot":"","sources":["../../../src/LicenseManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA0DH,OAAO,KAAK,EAEV,YAAY,EAEZ,eAAe,EAEf,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,uBAAuB,EAAE,OAAO,CAAC;IACjC,eAAe,EAAE,gBAAgB,EAAE,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,cAAM,kBAAkB;IACtB,OAAO,CAAC,KAAK,CAUX;IAEF,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,iBAAiB,CAA+C;IACxE,OAAO,CAAC,aAAa,CAA8B;IAGnD,OAAO,CAAC,gBAAgB,CAAuB;IAG/C,OAAO,CAAC,oBAAoB,CAAS;IAGrC,OAAO,CAAC,sBAAsB,CAAS;IAGvC,OAAO,CAAC,WAAW,CAA8B;IAEjD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAUjC;;OAEG;YACW,aAAa;IAoC3B;;;OAGG;IACG,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAoM9E;;OAEG;IACG,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAwF3E;;;OAGG;IACG,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAyD3F;;;OAGG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBzD;;OAEG;IACH,OAAO,CAAC,cAAc;IAkBtB;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBnC;;OAEG;IACH,KAAK,IAAI,OAAO;IAIhB;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAOrC;;OAEG;IACH,QAAQ,IAAI,QAAQ,CAAC,YAAY,CAAC;IAIlC;;OAEG;IACH,YAAY,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;IAOtD;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;IAKhD;;OAEG;IACG,oBAAoB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IA4BzD;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkD3D;;OAEG;IACG,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC;IAU/C;;OAEG;IACH,yBAAyB,IAAI,OAAO;IAMpC,OAAO,CAAC,IAAI;IAYZ,OAAO,CAAC,aAAa;IAqBrB,OAAO,CAAC,YAAY;YAkBN,iBAAiB;YAajB,kBAAkB;YAoClB,kBAAkB;IAWhC,OAAO,CAAC,oBAAoB;IAsB5B,OAAO,CAAC,sBAAsB;IAiB9B;;OAEG;IACH,qBAAqB,IAAI,IAAI;IAO7B;;OAEG;IACG,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;CAalD;AAGD,eAAO,MAAM,cAAc,oBAA2B,CAAC"}
|