@designbasekorea/figma-license-core 0.1.0 → 0.3.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/README.md +75 -12
- package/dist/designbaseAccountAuth.d.ts +65 -0
- package/dist/designbaseAccountAuth.d.ts.map +1 -0
- package/dist/fetchLicenseTransport.d.ts +8 -0
- package/dist/fetchLicenseTransport.d.ts.map +1 -1
- package/dist/figmaStatusManager.d.ts +13 -0
- package/dist/figmaStatusManager.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +826 -59
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +829 -58
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +829 -58
- package/dist/index.umd.js.map +1 -1
- package/dist/licenseCore.d.ts +16 -5
- package/dist/licenseCore.d.ts.map +1 -1
- package/dist/licenseErrorMapper.d.ts.map +1 -1
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/PLUGIN_INTEGRATION_KO.md +331 -0
- package/package.json +5 -4
package/dist/index.umd.js
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
(function (LicenseErrorCode) {
|
|
9
9
|
LicenseErrorCode["ACTIVATION_LIMIT_REACHED"] = "ACTIVATION_LIMIT_REACHED";
|
|
10
10
|
LicenseErrorCode["INVALID_LICENSE"] = "INVALID_LICENSE";
|
|
11
|
+
LicenseErrorCode["LICENSE_EXPIRED"] = "LICENSE_EXPIRED";
|
|
12
|
+
LicenseErrorCode["LICENSE_REVOKED"] = "LICENSE_REVOKED";
|
|
13
|
+
LicenseErrorCode["RATE_LIMITED"] = "RATE_LIMITED";
|
|
11
14
|
LicenseErrorCode["PRODUCT_MISMATCH"] = "PRODUCT_MISMATCH";
|
|
12
15
|
LicenseErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
13
16
|
LicenseErrorCode["STORAGE_ERROR"] = "STORAGE_ERROR";
|
|
@@ -31,10 +34,18 @@
|
|
|
31
34
|
if (typeof input === 'string') {
|
|
32
35
|
return input;
|
|
33
36
|
}
|
|
34
|
-
if (typeof input === 'object'
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
return
|
|
37
|
+
if (typeof input === 'object') {
|
|
38
|
+
const objectInput = input;
|
|
39
|
+
if (objectInput.raw) {
|
|
40
|
+
return asText(objectInput.raw);
|
|
41
|
+
}
|
|
42
|
+
if (objectInput.body) {
|
|
43
|
+
return asText(objectInput.body);
|
|
44
|
+
}
|
|
45
|
+
const fields = [objectInput.message, objectInput.errorCode, objectInput.code, objectInput.status]
|
|
46
|
+
.filter((value) => typeof value === 'string' && value.trim() !== '');
|
|
47
|
+
if (fields.length > 0) {
|
|
48
|
+
return fields.join(' ');
|
|
38
49
|
}
|
|
39
50
|
}
|
|
40
51
|
try {
|
|
@@ -52,13 +63,32 @@
|
|
|
52
63
|
|| normalized.includes('already activated on maximum')) {
|
|
53
64
|
return exports.LicenseErrorCode.ACTIVATION_LIMIT_REACHED;
|
|
54
65
|
}
|
|
66
|
+
if (normalized.includes('rate limited')
|
|
67
|
+
|| normalized.includes('rate_limited')
|
|
68
|
+
|| normalized.includes('too many requests')
|
|
69
|
+
|| normalized.includes('too_many_requests')
|
|
70
|
+
|| normalized.includes('429')) {
|
|
71
|
+
return exports.LicenseErrorCode.RATE_LIMITED;
|
|
72
|
+
}
|
|
73
|
+
if (normalized.includes('expired') || normalized.includes('expiration') || normalized.includes('license_expired')) {
|
|
74
|
+
return exports.LicenseErrorCode.LICENSE_EXPIRED;
|
|
75
|
+
}
|
|
76
|
+
if (normalized.includes('revoked')
|
|
77
|
+
|| normalized.includes('license_revoked')
|
|
78
|
+
|| normalized.includes('cancelled')
|
|
79
|
+
|| normalized.includes('canceled')) {
|
|
80
|
+
return exports.LicenseErrorCode.LICENSE_REVOKED;
|
|
81
|
+
}
|
|
55
82
|
if (normalized.includes('invalid license')
|
|
56
83
|
|| normalized.includes('not found')
|
|
57
84
|
|| normalized.includes('not valid')
|
|
58
85
|
|| normalized.includes('license key is invalid')) {
|
|
59
86
|
return exports.LicenseErrorCode.INVALID_LICENSE;
|
|
60
87
|
}
|
|
61
|
-
if (normalized.includes('product mismatch')
|
|
88
|
+
if (normalized.includes('product mismatch')
|
|
89
|
+
|| normalized.includes('product_mismatch')
|
|
90
|
+
|| normalized.includes('product_not_allowed')
|
|
91
|
+
|| normalized.includes('wrong product')) {
|
|
62
92
|
return exports.LicenseErrorCode.PRODUCT_MISMATCH;
|
|
63
93
|
}
|
|
64
94
|
if (normalized.includes('network')
|
|
@@ -79,7 +109,9 @@
|
|
|
79
109
|
const text = asText(error);
|
|
80
110
|
const message = text.trim() || fallbackMessage;
|
|
81
111
|
const code = inferCode(message);
|
|
82
|
-
return new LicenseCoreError(code, message, error, code === exports.LicenseErrorCode.NETWORK_ERROR
|
|
112
|
+
return new LicenseCoreError(code, message, error, code === exports.LicenseErrorCode.NETWORK_ERROR
|
|
113
|
+
|| code === exports.LicenseErrorCode.SERVER_ERROR
|
|
114
|
+
|| code === exports.LicenseErrorCode.RATE_LIMITED);
|
|
83
115
|
}
|
|
84
116
|
function isActivationLimitReached(activationLimit, activationUsage) {
|
|
85
117
|
if (activationLimit <= 0) {
|
|
@@ -91,10 +123,19 @@
|
|
|
91
123
|
const STORAGE_KEYS = {
|
|
92
124
|
paymentStatus: 'paymentStatus',
|
|
93
125
|
licenseKey: 'licenseKey',
|
|
126
|
+
instanceId: 'instanceId',
|
|
94
127
|
activationLimit: 'activationLimit',
|
|
95
128
|
activationUsage: 'activationUsage',
|
|
96
129
|
usageCount: 'usageCount',
|
|
130
|
+
previousUsageCount: 'previousUsageCount',
|
|
131
|
+
usageWindowKey: 'usageWindowKey',
|
|
132
|
+
planTier: 'planTier',
|
|
97
133
|
lastVerifiedAt: 'lastVerifiedAt',
|
|
134
|
+
licenseStatus: 'licenseStatus',
|
|
135
|
+
licenseSource: 'licenseSource',
|
|
136
|
+
accessScope: 'accessScope',
|
|
137
|
+
expiresAt: 'expiresAt',
|
|
138
|
+
validationMessage: 'validationMessage',
|
|
98
139
|
};
|
|
99
140
|
const DEFAULT_USAGE_LIMIT = 20;
|
|
100
141
|
const MASTER_CODE_ACTIVATION_LIMIT = 999999;
|
|
@@ -113,6 +154,31 @@
|
|
|
113
154
|
function toText(value) {
|
|
114
155
|
return typeof value === 'string' ? value : '';
|
|
115
156
|
}
|
|
157
|
+
function validationMessage(result) {
|
|
158
|
+
if (result.message) {
|
|
159
|
+
return result.message;
|
|
160
|
+
}
|
|
161
|
+
const status = (result.licenseStatus || result.errorCode || '').toLowerCase();
|
|
162
|
+
switch (status) {
|
|
163
|
+
case 'revoked':
|
|
164
|
+
case 'license_revoked':
|
|
165
|
+
return '이 멤버십 라이선스는 철회되었습니다.';
|
|
166
|
+
case 'expired':
|
|
167
|
+
case 'license_expired':
|
|
168
|
+
return '멤버십 라이선스가 만료되었습니다. YouTube 커뮤니티에서 이번 달 키를 확인해 주세요.';
|
|
169
|
+
case 'not_started':
|
|
170
|
+
return '이 멤버십 라이선스는 아직 시작되지 않았습니다.';
|
|
171
|
+
case 'rate_limited':
|
|
172
|
+
case 'rate_limited_error':
|
|
173
|
+
return '라이선스 확인 요청이 너무 많습니다. 잠시 후 다시 시도해 주세요.';
|
|
174
|
+
case 'product_not_allowed':
|
|
175
|
+
return '이 라이선스는 현재 플러그인에서 사용할 수 없습니다.';
|
|
176
|
+
case 'network_error':
|
|
177
|
+
return '라이선스 서버에 연결할 수 없습니다. 잠시 후 다시 시도해 주세요.';
|
|
178
|
+
default:
|
|
179
|
+
return '라이선스가 더 이상 유효하지 않습니다.';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
116
182
|
function toPaymentStatus(value) {
|
|
117
183
|
if (value === 'PAID' || value === 'UNPAID' || value === 'FREE') {
|
|
118
184
|
return value;
|
|
@@ -122,10 +188,77 @@
|
|
|
122
188
|
function normalizeProductId(productId) {
|
|
123
189
|
return productId.trim().toLowerCase();
|
|
124
190
|
}
|
|
191
|
+
function normalizeTier(tier) {
|
|
192
|
+
return tier.trim() || 'FREE';
|
|
193
|
+
}
|
|
194
|
+
function normalizeUsageReset(reset) {
|
|
195
|
+
if (reset === 'daily' || reset === 'weekly' || reset === 'monthly' || reset === 'none') {
|
|
196
|
+
return reset;
|
|
197
|
+
}
|
|
198
|
+
return 'none';
|
|
199
|
+
}
|
|
200
|
+
function toDateKey(date, period) {
|
|
201
|
+
if (period === 'none') {
|
|
202
|
+
return 'lifetime';
|
|
203
|
+
}
|
|
204
|
+
const year = date.getUTCFullYear();
|
|
205
|
+
const month = `${date.getUTCMonth() + 1}`.padStart(2, '0');
|
|
206
|
+
const day = `${date.getUTCDate()}`.padStart(2, '0');
|
|
207
|
+
if (period === 'monthly') {
|
|
208
|
+
return `${year}-${month}`;
|
|
209
|
+
}
|
|
210
|
+
if (period === 'weekly') {
|
|
211
|
+
// ISO weeks start on Monday and keep the week-year at the Thursday of
|
|
212
|
+
// the current week. Using UTC avoids a reset moving at local DST changes.
|
|
213
|
+
const dayOfWeek = date.getUTCDay() || 7;
|
|
214
|
+
const thursday = new Date(Date.UTC(year, date.getUTCMonth(), date.getUTCDate()));
|
|
215
|
+
thursday.setUTCDate(thursday.getUTCDate() + (4 - dayOfWeek));
|
|
216
|
+
const weekYear = thursday.getUTCFullYear();
|
|
217
|
+
const firstThursday = new Date(Date.UTC(weekYear, 0, 4));
|
|
218
|
+
const firstDay = firstThursday.getUTCDay() || 7;
|
|
219
|
+
firstThursday.setUTCDate(firstThursday.getUTCDate() + (4 - firstDay));
|
|
220
|
+
const week = 1 + Math.round((thursday.getTime() - firstThursday.getTime()) / 604800000);
|
|
221
|
+
return `${weekYear}-W${String(week).padStart(2, '0')}`;
|
|
222
|
+
}
|
|
223
|
+
return `${year}-${month}-${day}`;
|
|
224
|
+
}
|
|
225
|
+
function normalizePolicy(config) {
|
|
226
|
+
const rawPolicy = config.policy || {};
|
|
227
|
+
const freeTier = normalizeTier(rawPolicy.freeTier || 'FREE');
|
|
228
|
+
const paidTier = normalizeTier(rawPolicy.paidTier || 'PAID');
|
|
229
|
+
const defaultTier = normalizeTier(rawPolicy.defaultTier || freeTier);
|
|
230
|
+
const usageByTier = {
|
|
231
|
+
[freeTier]: {
|
|
232
|
+
limit: Math.max(config.usageLimit ?? DEFAULT_USAGE_LIMIT, 0),
|
|
233
|
+
reset: 'none',
|
|
234
|
+
},
|
|
235
|
+
[paidTier]: {
|
|
236
|
+
limit: 'unlimited',
|
|
237
|
+
reset: 'none',
|
|
238
|
+
},
|
|
239
|
+
...(rawPolicy.usageByTier || {}),
|
|
240
|
+
};
|
|
241
|
+
Object.keys(usageByTier).forEach((tier) => {
|
|
242
|
+
usageByTier[tier] = {
|
|
243
|
+
limit: usageByTier[tier].limit === 'unlimited'
|
|
244
|
+
? 'unlimited'
|
|
245
|
+
: Math.max(toNonNegativeNumber(usageByTier[tier].limit, 0), 0),
|
|
246
|
+
reset: normalizeUsageReset(usageByTier[tier].reset),
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
defaultTier,
|
|
251
|
+
freeTier,
|
|
252
|
+
paidTier,
|
|
253
|
+
usageByTier,
|
|
254
|
+
featuresByTier: rawPolicy.featuresByTier || {},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
125
257
|
class LicenseCore {
|
|
126
258
|
constructor(config, deps) {
|
|
127
259
|
this.config = config;
|
|
128
260
|
this.deps = deps;
|
|
261
|
+
this.usageMutation = Promise.resolve();
|
|
129
262
|
if (!config.productId.trim()) {
|
|
130
263
|
throw new Error('LicenseConfig.productId is required.');
|
|
131
264
|
}
|
|
@@ -135,14 +268,117 @@
|
|
|
135
268
|
if (!config.instanceName.trim()) {
|
|
136
269
|
throw new Error('LicenseConfig.instanceName is required.');
|
|
137
270
|
}
|
|
138
|
-
this.
|
|
271
|
+
this.policy = normalizePolicy(config);
|
|
139
272
|
this.storagePrefix = config.storagePrefix?.trim() || `${config.instanceName}:${config.productId}`;
|
|
140
273
|
}
|
|
141
274
|
async initialize() {
|
|
142
|
-
|
|
275
|
+
let state = await this.getStatus();
|
|
276
|
+
if (state.paymentStatus === 'PAID' && state.licenseKey && state.licenseKey !== this.config.masterCode) {
|
|
277
|
+
try {
|
|
278
|
+
const validateResult = this.deps.transport.validate
|
|
279
|
+
? await this.deps.transport.validate({
|
|
280
|
+
licenseKey: state.licenseKey,
|
|
281
|
+
productId: this.config.productId,
|
|
282
|
+
instanceName: this.config.instanceName,
|
|
283
|
+
instanceId: state.instanceId,
|
|
284
|
+
})
|
|
285
|
+
: await this.deps.transport.verify({
|
|
286
|
+
licenseKey: state.licenseKey,
|
|
287
|
+
productId: this.config.productId,
|
|
288
|
+
instanceName: this.config.instanceName,
|
|
289
|
+
});
|
|
290
|
+
if (!validateResult.valid) {
|
|
291
|
+
state = await this.markValidationFailure(state, validationMessage(validateResult), validateResult.errorCode || validateResult.licenseStatus);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
state = await this.applyValidationResult(state, validateResult);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
const mappedError = mapLicenseError(error, '라이선스 검증 요청에 실패했습니다.');
|
|
299
|
+
state = await this.markValidationFailure(state, mappedError.message, mappedError.code);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return this.applyUsageWindowResetIfNeeded(state);
|
|
303
|
+
}
|
|
304
|
+
async resetLicenseState() {
|
|
305
|
+
const current = await this.getStatus();
|
|
306
|
+
const freeReset = this.getUsagePolicy(this.policy.freeTier).reset;
|
|
307
|
+
const previousUsageCount = current.previousUsageCount ?? 0;
|
|
308
|
+
const nextState = {
|
|
309
|
+
...current,
|
|
310
|
+
paymentStatus: 'FREE',
|
|
311
|
+
planTier: this.policy.freeTier,
|
|
312
|
+
usageWindowKey: toDateKey(new Date(), freeReset),
|
|
313
|
+
licenseKey: '',
|
|
314
|
+
instanceId: '',
|
|
315
|
+
activationLimit: 0,
|
|
316
|
+
activationUsage: 0,
|
|
317
|
+
usageCount: previousUsageCount,
|
|
318
|
+
lastVerifiedAt: undefined,
|
|
319
|
+
licenseStatus: undefined,
|
|
320
|
+
licenseSource: undefined,
|
|
321
|
+
accessScope: undefined,
|
|
322
|
+
expiresAt: undefined,
|
|
323
|
+
validationMessage: undefined,
|
|
324
|
+
};
|
|
325
|
+
await this.persistState(nextState);
|
|
326
|
+
return nextState;
|
|
327
|
+
}
|
|
328
|
+
async applyValidationResult(current, result) {
|
|
329
|
+
const nextState = {
|
|
330
|
+
...current,
|
|
331
|
+
paymentStatus: 'PAID',
|
|
332
|
+
activationLimit: result.activationLimit ?? current.activationLimit,
|
|
333
|
+
activationUsage: result.activationUsage ?? current.activationUsage,
|
|
334
|
+
licenseStatus: result.licenseStatus || 'active',
|
|
335
|
+
licenseSource: result.licenseSource || current.licenseSource,
|
|
336
|
+
accessScope: result.accessScope || current.accessScope,
|
|
337
|
+
expiresAt: result.expiresAt ?? current.expiresAt,
|
|
338
|
+
validationMessage: result.message || undefined,
|
|
339
|
+
lastVerifiedAt: new Date().toISOString(),
|
|
340
|
+
};
|
|
341
|
+
await this.persistState(nextState);
|
|
342
|
+
return nextState;
|
|
343
|
+
}
|
|
344
|
+
async markValidationFailure(current, message, errorCode) {
|
|
345
|
+
const freeReset = this.getUsagePolicy(this.policy.freeTier).reset;
|
|
346
|
+
const nextState = {
|
|
347
|
+
...current,
|
|
348
|
+
paymentStatus: 'UNPAID',
|
|
349
|
+
planTier: this.policy.freeTier,
|
|
350
|
+
usageWindowKey: toDateKey(new Date(), freeReset),
|
|
351
|
+
activationLimit: 0,
|
|
352
|
+
activationUsage: 0,
|
|
353
|
+
usageCount: current.previousUsageCount ?? current.usageCount,
|
|
354
|
+
licenseStatus: errorCode || 'invalid',
|
|
355
|
+
accessScope: undefined,
|
|
356
|
+
expiresAt: undefined,
|
|
357
|
+
validationMessage: message,
|
|
358
|
+
lastVerifiedAt: new Date().toISOString(),
|
|
359
|
+
};
|
|
360
|
+
await this.persistState(nextState);
|
|
361
|
+
return nextState;
|
|
143
362
|
}
|
|
144
363
|
async getStatus() {
|
|
145
|
-
|
|
364
|
+
const state = await this.loadState();
|
|
365
|
+
return this.applyUsageWindowResetIfNeeded(state);
|
|
366
|
+
}
|
|
367
|
+
async getLicenseSummary() {
|
|
368
|
+
const state = await this.getStatus();
|
|
369
|
+
return {
|
|
370
|
+
paymentStatus: state.paymentStatus,
|
|
371
|
+
planTier: this.resolveTier(state),
|
|
372
|
+
licenseKey: state.licenseKey,
|
|
373
|
+
hasLicenseKey: Boolean(state.licenseKey),
|
|
374
|
+
isLicenseRegistered: this.isLicenseRegisteredState(state),
|
|
375
|
+
licenseStatus: state.licenseStatus,
|
|
376
|
+
licenseSource: state.licenseSource,
|
|
377
|
+
accessScope: state.accessScope,
|
|
378
|
+
expiresAt: state.expiresAt,
|
|
379
|
+
lastVerifiedAt: state.lastVerifiedAt,
|
|
380
|
+
validationMessage: state.validationMessage,
|
|
381
|
+
};
|
|
146
382
|
}
|
|
147
383
|
async verifyLicense(licenseKey) {
|
|
148
384
|
const trimmedLicenseKey = licenseKey.trim();
|
|
@@ -150,18 +386,27 @@
|
|
|
150
386
|
throw new LicenseCoreError(exports.LicenseErrorCode.INVALID_LICENSE, '라이선스 키를 입력해 주세요.');
|
|
151
387
|
}
|
|
152
388
|
if (this.config.masterCode && trimmedLicenseKey === this.config.masterCode) {
|
|
153
|
-
const current = await this.
|
|
389
|
+
const current = await this.getStatus();
|
|
154
390
|
const nextState = {
|
|
155
391
|
...current,
|
|
156
392
|
paymentStatus: 'PAID',
|
|
393
|
+
planTier: this.policy.paidTier,
|
|
394
|
+
usageWindowKey: toDateKey(new Date(), this.getUsagePolicy(this.policy.paidTier).reset),
|
|
157
395
|
licenseKey: trimmedLicenseKey,
|
|
396
|
+
instanceId: 'master-instance',
|
|
158
397
|
activationLimit: MASTER_CODE_ACTIVATION_LIMIT,
|
|
159
398
|
activationUsage: 0,
|
|
399
|
+
previousUsageCount: current.usageCount,
|
|
160
400
|
lastVerifiedAt: new Date().toISOString(),
|
|
401
|
+
licenseStatus: 'active',
|
|
402
|
+
licenseSource: 'master',
|
|
403
|
+
accessScope: 'all-products',
|
|
404
|
+
expiresAt: undefined,
|
|
405
|
+
validationMessage: undefined,
|
|
161
406
|
};
|
|
162
407
|
await this.persistState(nextState);
|
|
163
408
|
await this.notify('마스터 코드로 라이선스가 활성화되었습니다.');
|
|
164
|
-
return nextState;
|
|
409
|
+
return this.applyUsageWindowResetIfNeeded(nextState);
|
|
165
410
|
}
|
|
166
411
|
let verifyResult;
|
|
167
412
|
try {
|
|
@@ -175,7 +420,7 @@
|
|
|
175
420
|
throw mapLicenseError(error, '라이선스 검증 요청에 실패했습니다.');
|
|
176
421
|
}
|
|
177
422
|
if (!verifyResult.valid) {
|
|
178
|
-
throw mapLicenseError(verifyResult.raw ?? verifyResult.message, '유효하지 않은 라이선스 키입니다.');
|
|
423
|
+
throw mapLicenseError(verifyResult.raw ?? { message: verifyResult.message, errorCode: verifyResult.errorCode }, '유효하지 않은 라이선스 키입니다.');
|
|
179
424
|
}
|
|
180
425
|
if (verifyResult.productId
|
|
181
426
|
&& normalizeProductId(verifyResult.productId) !== normalizeProductId(this.config.productId)) {
|
|
@@ -186,21 +431,32 @@
|
|
|
186
431
|
if (isActivationLimitReached(activationLimit, activationUsage)) {
|
|
187
432
|
throw new LicenseCoreError(exports.LicenseErrorCode.ACTIVATION_LIMIT_REACHED, '활성화 가능한 기기 수를 모두 사용했습니다. 기존 기기를 비활성화한 뒤 다시 시도해 주세요.', verifyResult);
|
|
188
433
|
}
|
|
189
|
-
const current = await this.
|
|
434
|
+
const current = await this.getStatus();
|
|
435
|
+
const planTier = normalizeTier(verifyResult.planTier || this.policy.paidTier);
|
|
436
|
+
const usageReset = this.getUsagePolicy(planTier).reset;
|
|
190
437
|
const nextState = {
|
|
191
438
|
...current,
|
|
192
439
|
paymentStatus: 'PAID',
|
|
193
|
-
|
|
440
|
+
planTier,
|
|
441
|
+
usageWindowKey: toDateKey(new Date(), usageReset),
|
|
442
|
+
licenseKey: verifyResult.licenseKey || trimmedLicenseKey,
|
|
443
|
+
instanceId: verifyResult.instanceId || '',
|
|
194
444
|
activationLimit,
|
|
195
445
|
activationUsage,
|
|
446
|
+
previousUsageCount: current.usageCount,
|
|
196
447
|
lastVerifiedAt: new Date().toISOString(),
|
|
448
|
+
licenseStatus: verifyResult.licenseStatus || 'active',
|
|
449
|
+
licenseSource: verifyResult.licenseSource,
|
|
450
|
+
accessScope: verifyResult.accessScope,
|
|
451
|
+
expiresAt: verifyResult.expiresAt,
|
|
452
|
+
validationMessage: verifyResult.message || undefined,
|
|
197
453
|
};
|
|
198
454
|
await this.persistState(nextState);
|
|
199
455
|
await this.notify(verifyResult.message || '라이선스가 활성화되었습니다.');
|
|
200
|
-
return nextState;
|
|
456
|
+
return this.applyUsageWindowResetIfNeeded(nextState);
|
|
201
457
|
}
|
|
202
458
|
async deactivateLicense() {
|
|
203
|
-
const current = await this.
|
|
459
|
+
const current = await this.getStatus();
|
|
204
460
|
if (!current.licenseKey) {
|
|
205
461
|
return current;
|
|
206
462
|
}
|
|
@@ -208,6 +464,7 @@
|
|
|
208
464
|
licenseKey: current.licenseKey,
|
|
209
465
|
productId: this.config.productId,
|
|
210
466
|
instanceName: this.config.instanceName,
|
|
467
|
+
instanceId: current.instanceId,
|
|
211
468
|
};
|
|
212
469
|
if (!this.config.masterCode || current.licenseKey !== this.config.masterCode) {
|
|
213
470
|
try {
|
|
@@ -220,24 +477,19 @@
|
|
|
220
477
|
throw mapLicenseError(error, '라이선스 비활성화 요청에 실패했습니다.');
|
|
221
478
|
}
|
|
222
479
|
}
|
|
223
|
-
const nextState =
|
|
224
|
-
...current,
|
|
225
|
-
paymentStatus: 'FREE',
|
|
226
|
-
licenseKey: '',
|
|
227
|
-
activationLimit: 0,
|
|
228
|
-
activationUsage: 0,
|
|
229
|
-
lastVerifiedAt: undefined,
|
|
230
|
-
};
|
|
231
|
-
await this.persistState(nextState);
|
|
480
|
+
const nextState = await this.resetLicenseState();
|
|
232
481
|
await this.notify('라이선스가 비활성화되었습니다.');
|
|
233
|
-
return nextState;
|
|
482
|
+
return this.applyUsageWindowResetIfNeeded(nextState);
|
|
234
483
|
}
|
|
235
484
|
async incrementUsage(amount = 1) {
|
|
236
|
-
const current = await this.
|
|
485
|
+
const current = await this.getStatus();
|
|
237
486
|
if (current.paymentStatus === 'PAID') {
|
|
238
487
|
return current;
|
|
239
488
|
}
|
|
240
|
-
const increasedAmount = Math.max(0, amount);
|
|
489
|
+
const increasedAmount = Number.isFinite(amount) ? Math.max(0, amount) : 0;
|
|
490
|
+
if (increasedAmount === 0) {
|
|
491
|
+
return current;
|
|
492
|
+
}
|
|
241
493
|
const nextState = {
|
|
242
494
|
...current,
|
|
243
495
|
usageCount: current.usageCount + increasedAmount,
|
|
@@ -245,64 +497,198 @@
|
|
|
245
497
|
await this.persistState(nextState);
|
|
246
498
|
return nextState;
|
|
247
499
|
}
|
|
248
|
-
async canUseFeature() {
|
|
249
|
-
const state = await this.
|
|
250
|
-
return this.resolveUsageAccess(state);
|
|
500
|
+
async canUseFeature(featureKey, amount = 1) {
|
|
501
|
+
const state = await this.getStatus();
|
|
502
|
+
return this.resolveUsageAccess(state, featureKey, amount);
|
|
251
503
|
}
|
|
252
|
-
|
|
253
|
-
|
|
504
|
+
async consumeUsage(amount = 1, featureKey) {
|
|
505
|
+
const execute = async () => {
|
|
506
|
+
const state = await this.getStatus();
|
|
507
|
+
const requestedAmount = Number.isFinite(amount) ? Math.max(0, amount) : 0;
|
|
508
|
+
const access = this.resolveUsageAccess(state, featureKey, requestedAmount);
|
|
509
|
+
if (!access.allowed || requestedAmount <= 0) {
|
|
510
|
+
return { state, access, consumed: false };
|
|
511
|
+
}
|
|
512
|
+
const updatedState = await this.incrementUsage(requestedAmount);
|
|
513
|
+
const updatedAccess = this.resolveUsageAccess(updatedState, featureKey, requestedAmount);
|
|
254
514
|
return {
|
|
255
|
-
|
|
515
|
+
state: updatedState,
|
|
516
|
+
access: updatedAccess,
|
|
517
|
+
consumed: true,
|
|
518
|
+
};
|
|
519
|
+
};
|
|
520
|
+
// Figma can deliver duplicate UI events while a panel remounts. Serialize
|
|
521
|
+
// the check + persist pair so two concurrent requests cannot overspend the
|
|
522
|
+
// same local allowance.
|
|
523
|
+
const pending = this.usageMutation.then(execute, execute);
|
|
524
|
+
this.usageMutation = pending.then(() => undefined, () => undefined);
|
|
525
|
+
return pending;
|
|
526
|
+
}
|
|
527
|
+
resolveUsageAccess(state, featureKey, amount = 1) {
|
|
528
|
+
const tier = this.resolveTier(state);
|
|
529
|
+
const usagePolicy = this.getUsagePolicy(tier);
|
|
530
|
+
const featureAllowed = this.isFeatureAllowed(tier, featureKey);
|
|
531
|
+
const isLicensed = this.isLicenseRegisteredState(state);
|
|
532
|
+
const requested = Number.isFinite(amount) && amount > 0 ? amount : 1;
|
|
533
|
+
const used = state.usageCount;
|
|
534
|
+
if (usagePolicy.limit === 'unlimited') {
|
|
535
|
+
return {
|
|
536
|
+
allowed: featureAllowed,
|
|
256
537
|
remaining: Number.POSITIVE_INFINITY,
|
|
538
|
+
used,
|
|
539
|
+
limit: 'unlimited',
|
|
540
|
+
reset: usagePolicy.reset,
|
|
541
|
+
requested,
|
|
542
|
+
tier,
|
|
543
|
+
hasLicenseKey: Boolean(state.licenseKey),
|
|
544
|
+
isLicenseRegistered: isLicensed,
|
|
545
|
+
featureAllowed,
|
|
546
|
+
featureKey,
|
|
547
|
+
reason: featureAllowed ? undefined : 'FEATURE_NOT_ALLOWED',
|
|
257
548
|
};
|
|
258
549
|
}
|
|
259
|
-
const remaining = Math.max(
|
|
550
|
+
const remaining = Math.max(usagePolicy.limit - used, 0);
|
|
551
|
+
const usageAllowed = remaining >= requested;
|
|
552
|
+
const allowed = featureAllowed && usageAllowed;
|
|
260
553
|
return {
|
|
261
|
-
allowed
|
|
554
|
+
allowed,
|
|
262
555
|
remaining,
|
|
556
|
+
used,
|
|
557
|
+
limit: usagePolicy.limit,
|
|
558
|
+
reset: usagePolicy.reset,
|
|
559
|
+
requested,
|
|
560
|
+
tier,
|
|
561
|
+
hasLicenseKey: Boolean(state.licenseKey),
|
|
562
|
+
isLicenseRegistered: isLicensed,
|
|
563
|
+
featureAllowed,
|
|
564
|
+
featureKey,
|
|
565
|
+
reason: featureAllowed ? (usageAllowed ? undefined : 'USAGE_LIMIT_REACHED') : 'FEATURE_NOT_ALLOWED',
|
|
263
566
|
};
|
|
264
567
|
}
|
|
265
|
-
getUsageLimit() {
|
|
266
|
-
|
|
568
|
+
getUsageLimit(state) {
|
|
569
|
+
const tier = state ? this.resolveTier(state) : this.policy.freeTier;
|
|
570
|
+
return this.getUsagePolicy(tier).limit;
|
|
267
571
|
}
|
|
268
572
|
getStoragePrefix() {
|
|
269
573
|
return this.storagePrefix;
|
|
270
574
|
}
|
|
575
|
+
resolveTier(state) {
|
|
576
|
+
const storedTier = normalizeTier(state.planTier || '');
|
|
577
|
+
{
|
|
578
|
+
return storedTier;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
getUsagePolicy(tier) {
|
|
582
|
+
return this.policy.usageByTier[tier]
|
|
583
|
+
|| this.policy.usageByTier[this.policy.defaultTier]
|
|
584
|
+
|| { limit: 'unlimited', reset: 'none' };
|
|
585
|
+
}
|
|
586
|
+
isFeatureAllowed(tier, featureKey) {
|
|
587
|
+
if (!featureKey) {
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
const policy = this.policy.featuresByTier[tier];
|
|
591
|
+
if (!policy) {
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
if (policy.deny?.includes(featureKey)) {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
if (policy.allow && policy.allow.length > 0) {
|
|
598
|
+
return policy.allow.includes(featureKey);
|
|
599
|
+
}
|
|
600
|
+
return policy.defaultAllowed ?? true;
|
|
601
|
+
}
|
|
602
|
+
isLicenseRegisteredState(state) {
|
|
603
|
+
const status = state.licenseStatus?.toLowerCase();
|
|
604
|
+
const invalidStatuses = ['invalid', 'inactive', 'expired', 'revoked', 'unpaid'];
|
|
605
|
+
return state.paymentStatus === 'PAID'
|
|
606
|
+
&& Boolean(state.licenseKey)
|
|
607
|
+
&& !invalidStatuses.includes(status || '');
|
|
608
|
+
}
|
|
271
609
|
storageKey(name) {
|
|
272
610
|
return `${this.storagePrefix}:${STORAGE_KEYS[name]}`;
|
|
273
611
|
}
|
|
274
612
|
async loadState() {
|
|
275
613
|
try {
|
|
276
|
-
const [paymentStatus, licenseKey, activationLimit, activationUsage, usageCount, lastVerifiedAt,] = await Promise.all([
|
|
614
|
+
const [paymentStatus, licenseKey, instanceId, activationLimit, activationUsage, usageCount, previousUsageCount, usageWindowKey, planTier, lastVerifiedAt, licenseStatus, licenseSource, accessScope, expiresAt, validationMessage,] = await Promise.all([
|
|
277
615
|
this.deps.storage.get(this.storageKey('paymentStatus')),
|
|
278
616
|
this.deps.storage.get(this.storageKey('licenseKey')),
|
|
617
|
+
this.deps.storage.get(this.storageKey('instanceId')),
|
|
279
618
|
this.deps.storage.get(this.storageKey('activationLimit')),
|
|
280
619
|
this.deps.storage.get(this.storageKey('activationUsage')),
|
|
281
620
|
this.deps.storage.get(this.storageKey('usageCount')),
|
|
621
|
+
this.deps.storage.get(this.storageKey('previousUsageCount')),
|
|
622
|
+
this.deps.storage.get(this.storageKey('usageWindowKey')),
|
|
623
|
+
this.deps.storage.get(this.storageKey('planTier')),
|
|
282
624
|
this.deps.storage.get(this.storageKey('lastVerifiedAt')),
|
|
625
|
+
this.deps.storage.get(this.storageKey('licenseStatus')),
|
|
626
|
+
this.deps.storage.get(this.storageKey('licenseSource')),
|
|
627
|
+
this.deps.storage.get(this.storageKey('accessScope')),
|
|
628
|
+
this.deps.storage.get(this.storageKey('expiresAt')),
|
|
629
|
+
this.deps.storage.get(this.storageKey('validationMessage')),
|
|
283
630
|
]);
|
|
631
|
+
const normalizedPaymentStatus = toPaymentStatus(paymentStatus);
|
|
632
|
+
const resolvedTier = normalizeTier(toText(planTier)
|
|
633
|
+
|| (normalizedPaymentStatus === 'PAID' ? this.policy.paidTier : this.policy.freeTier));
|
|
634
|
+
const usageReset = this.getUsagePolicy(resolvedTier).reset;
|
|
284
635
|
return {
|
|
285
|
-
paymentStatus:
|
|
636
|
+
paymentStatus: normalizedPaymentStatus,
|
|
286
637
|
licenseKey: toText(licenseKey),
|
|
638
|
+
instanceId: toText(instanceId),
|
|
287
639
|
activationLimit: toNonNegativeNumber(activationLimit, 0),
|
|
288
640
|
activationUsage: toNonNegativeNumber(activationUsage, 0),
|
|
289
641
|
usageCount: toNonNegativeNumber(usageCount, 0),
|
|
642
|
+
previousUsageCount: toNonNegativeNumber(previousUsageCount, 0),
|
|
643
|
+
usageWindowKey: toText(usageWindowKey) || toDateKey(new Date(), usageReset),
|
|
644
|
+
planTier: resolvedTier,
|
|
290
645
|
lastVerifiedAt: toText(lastVerifiedAt) || undefined,
|
|
646
|
+
licenseStatus: toText(licenseStatus) || undefined,
|
|
647
|
+
licenseSource: toText(licenseSource) || undefined,
|
|
648
|
+
accessScope: toText(accessScope) || undefined,
|
|
649
|
+
expiresAt: toText(expiresAt) || undefined,
|
|
650
|
+
validationMessage: toText(validationMessage) || undefined,
|
|
291
651
|
};
|
|
292
652
|
}
|
|
293
653
|
catch (error) {
|
|
294
654
|
throw new LicenseCoreError(exports.LicenseErrorCode.STORAGE_ERROR, '라이선스 상태를 불러오지 못했습니다.', error, true);
|
|
295
655
|
}
|
|
296
656
|
}
|
|
657
|
+
async applyUsageWindowResetIfNeeded(state) {
|
|
658
|
+
const tier = this.resolveTier(state);
|
|
659
|
+
const usageReset = this.getUsagePolicy(tier).reset;
|
|
660
|
+
const expectedWindowKey = toDateKey(new Date(), usageReset);
|
|
661
|
+
const requiresUsageReset = usageReset !== 'none' && state.usageWindowKey !== expectedWindowKey;
|
|
662
|
+
const requiresWindowMigration = state.usageWindowKey !== expectedWindowKey;
|
|
663
|
+
if (!requiresUsageReset && !requiresWindowMigration) {
|
|
664
|
+
return state;
|
|
665
|
+
}
|
|
666
|
+
const nextState = {
|
|
667
|
+
...state,
|
|
668
|
+
usageWindowKey: expectedWindowKey,
|
|
669
|
+
usageCount: requiresUsageReset ? 0 : state.usageCount,
|
|
670
|
+
};
|
|
671
|
+
await this.persistState(nextState);
|
|
672
|
+
return nextState;
|
|
673
|
+
}
|
|
297
674
|
async persistState(state) {
|
|
298
675
|
try {
|
|
299
676
|
await Promise.all([
|
|
300
677
|
this.deps.storage.set(this.storageKey('paymentStatus'), state.paymentStatus),
|
|
301
678
|
this.deps.storage.set(this.storageKey('licenseKey'), state.licenseKey),
|
|
679
|
+
this.deps.storage.set(this.storageKey('instanceId'), state.instanceId || ''),
|
|
302
680
|
this.deps.storage.set(this.storageKey('activationLimit'), state.activationLimit),
|
|
303
681
|
this.deps.storage.set(this.storageKey('activationUsage'), state.activationUsage),
|
|
304
682
|
this.deps.storage.set(this.storageKey('usageCount'), state.usageCount),
|
|
683
|
+
this.deps.storage.set(this.storageKey('previousUsageCount'), state.previousUsageCount || 0),
|
|
684
|
+
this.deps.storage.set(this.storageKey('usageWindowKey'), state.usageWindowKey),
|
|
685
|
+
this.deps.storage.set(this.storageKey('planTier'), state.planTier),
|
|
305
686
|
this.deps.storage.set(this.storageKey('lastVerifiedAt'), state.lastVerifiedAt || ''),
|
|
687
|
+
this.deps.storage.set(this.storageKey('licenseStatus'), state.licenseStatus || ''),
|
|
688
|
+
this.deps.storage.set(this.storageKey('licenseSource'), state.licenseSource || ''),
|
|
689
|
+
this.deps.storage.set(this.storageKey('accessScope'), state.accessScope || ''),
|
|
690
|
+
this.deps.storage.set(this.storageKey('expiresAt'), state.expiresAt || ''),
|
|
691
|
+
this.deps.storage.set(this.storageKey('validationMessage'), state.validationMessage || ''),
|
|
306
692
|
]);
|
|
307
693
|
}
|
|
308
694
|
catch (error) {
|
|
@@ -352,18 +738,47 @@
|
|
|
352
738
|
}
|
|
353
739
|
return undefined;
|
|
354
740
|
}
|
|
355
|
-
function
|
|
741
|
+
function readBooleanField(raw, keys) {
|
|
742
|
+
if (!raw || typeof raw !== 'object') {
|
|
743
|
+
return undefined;
|
|
744
|
+
}
|
|
745
|
+
for (const key of keys) {
|
|
746
|
+
const value = raw[key];
|
|
747
|
+
if (typeof value === 'boolean') {
|
|
748
|
+
return value;
|
|
749
|
+
}
|
|
750
|
+
if (typeof value === 'string') {
|
|
751
|
+
const normalized = value.trim().toLowerCase();
|
|
752
|
+
if (['true', '1', 'yes', 'active', 'valid', 'success'].includes(normalized)) {
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
if (['false', '0', 'no', 'inactive', 'invalid', 'revoked', 'expired', 'error'].includes(normalized)) {
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return undefined;
|
|
761
|
+
}
|
|
762
|
+
function asRecord$1(value) {
|
|
356
763
|
return value && typeof value === 'object' ? value : {};
|
|
357
764
|
}
|
|
358
765
|
function defaultNormalizeVerifyResponse(raw) {
|
|
359
|
-
const body = asRecord(raw);
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
766
|
+
const body = asRecord$1(raw);
|
|
767
|
+
const licenseStatus = readTextField(body, ['licenseStatus', 'license_status', 'status'])
|
|
768
|
+
|| readTextField(asRecord$1(body.meta), ['license_status', 'status']);
|
|
769
|
+
const statusValid = typeof licenseStatus === 'string'
|
|
770
|
+
? licenseStatus.toLowerCase() === 'active'
|
|
771
|
+
: undefined;
|
|
772
|
+
const valid = readBooleanField(body, ['valid', 'success', 'active'])
|
|
773
|
+
?? statusValid
|
|
774
|
+
?? false;
|
|
364
775
|
const message = readTextField(body, ['message', 'error', 'detail']);
|
|
365
|
-
const nestedLicenseKey = asRecord(body.license_key);
|
|
366
|
-
const nestedMeta = asRecord(body.meta);
|
|
776
|
+
const nestedLicenseKey = asRecord$1(body.license_key);
|
|
777
|
+
const nestedMeta = asRecord$1(body.meta);
|
|
778
|
+
const nestedInstance = asRecord$1(body.instance);
|
|
779
|
+
const instanceId = readTextField(body, ['instanceId', 'instance_id'])
|
|
780
|
+
|| readTextField(nestedInstance, ['id'])
|
|
781
|
+
|| readTextField(nestedLicenseKey, ['instance_id']);
|
|
367
782
|
const productId = readTextField(body, ['productId', 'product_id'])
|
|
368
783
|
|| readTextField(nestedMeta, ['product_id'])
|
|
369
784
|
|| readTextField(nestedLicenseKey, ['product_id']);
|
|
@@ -373,17 +788,37 @@
|
|
|
373
788
|
const activationUsage = readNumberField(body, ['activationUsage', 'activation_usage'])
|
|
374
789
|
?? readNumberField(nestedMeta, ['activation_usage'])
|
|
375
790
|
?? readNumberField(nestedLicenseKey, ['activation_usage']);
|
|
791
|
+
const planTier = readTextField(body, ['planTier', 'tier', 'plan'])
|
|
792
|
+
|| readTextField(nestedMeta, ['tier', 'plan']);
|
|
793
|
+
const licenseSource = readTextField(body, ['licenseSource', 'license_source', 'source'])
|
|
794
|
+
|| readTextField(nestedMeta, ['license_source', 'source'])
|
|
795
|
+
|| readTextField(nestedLicenseKey, ['license_source', 'source']);
|
|
796
|
+
const accessScope = readTextField(body, ['accessScope', 'access_scope', 'scope'])
|
|
797
|
+
|| readTextField(nestedMeta, ['access_scope', 'scope'])
|
|
798
|
+
|| readTextField(nestedLicenseKey, ['access_scope', 'scope']);
|
|
799
|
+
const expiresAt = readTextField(body, ['expiresAt', 'expires_at', 'expiration', 'expiration_date'])
|
|
800
|
+
|| readTextField(nestedMeta, ['expires_at', 'expiration', 'expiration_date'])
|
|
801
|
+
|| readTextField(nestedLicenseKey, ['expires_at', 'expiration', 'expiration_date']);
|
|
802
|
+
const errorCode = readTextField(body, ['errorCode', 'error_code', 'code'])
|
|
803
|
+
|| readTextField(nestedMeta, ['error_code', 'code']);
|
|
376
804
|
return {
|
|
377
805
|
valid,
|
|
378
806
|
message,
|
|
379
807
|
productId,
|
|
380
808
|
activationLimit,
|
|
381
809
|
activationUsage,
|
|
810
|
+
instanceId,
|
|
811
|
+
planTier,
|
|
812
|
+
licenseStatus,
|
|
813
|
+
licenseSource,
|
|
814
|
+
accessScope,
|
|
815
|
+
expiresAt,
|
|
816
|
+
errorCode,
|
|
382
817
|
raw,
|
|
383
818
|
};
|
|
384
819
|
}
|
|
385
820
|
function defaultNormalizeDeactivateResponse(raw) {
|
|
386
|
-
const body = asRecord(raw);
|
|
821
|
+
const body = asRecord$1(raw);
|
|
387
822
|
const success = Boolean(body.success
|
|
388
823
|
?? body.deactivated
|
|
389
824
|
?? body.valid
|
|
@@ -394,6 +829,14 @@
|
|
|
394
829
|
raw,
|
|
395
830
|
};
|
|
396
831
|
}
|
|
832
|
+
class LicenseTransportHttpError extends Error {
|
|
833
|
+
constructor(status, body, message) {
|
|
834
|
+
super(message);
|
|
835
|
+
this.status = status;
|
|
836
|
+
this.body = body;
|
|
837
|
+
this.name = 'LicenseTransportHttpError';
|
|
838
|
+
}
|
|
839
|
+
}
|
|
397
840
|
class FetchLicenseTransport {
|
|
398
841
|
constructor(config) {
|
|
399
842
|
this.config = config;
|
|
@@ -407,6 +850,15 @@
|
|
|
407
850
|
const normalize = this.config.normalizeVerifyResponse ?? defaultNormalizeVerifyResponse;
|
|
408
851
|
return normalize(raw);
|
|
409
852
|
}
|
|
853
|
+
async validate(payload) {
|
|
854
|
+
const url = this.config.validateUrl || this.config.verifyUrl;
|
|
855
|
+
const body = this.config.buildValidateBody
|
|
856
|
+
? this.config.buildValidateBody(payload)
|
|
857
|
+
: payload;
|
|
858
|
+
const raw = await this.postJson(url, body);
|
|
859
|
+
const normalize = this.config.normalizeVerifyResponse ?? defaultNormalizeVerifyResponse;
|
|
860
|
+
return normalize(raw);
|
|
861
|
+
}
|
|
410
862
|
async deactivate(payload) {
|
|
411
863
|
const url = this.config.deactivateUrl || this.config.verifyUrl;
|
|
412
864
|
const body = this.config.buildDeactivateBody
|
|
@@ -436,8 +888,143 @@
|
|
|
436
888
|
signal: abortController?.signal,
|
|
437
889
|
});
|
|
438
890
|
if (!response.ok) {
|
|
439
|
-
|
|
440
|
-
|
|
891
|
+
let errorBody;
|
|
892
|
+
try {
|
|
893
|
+
errorBody = await response.json();
|
|
894
|
+
}
|
|
895
|
+
catch {
|
|
896
|
+
errorBody = await response.text();
|
|
897
|
+
}
|
|
898
|
+
const errorRecord = asRecord$1(errorBody);
|
|
899
|
+
const errorMessage = readTextField(errorRecord, ['message', 'error', 'detail'])
|
|
900
|
+
|| (typeof errorBody === 'string' ? errorBody : '')
|
|
901
|
+
|| response.statusText;
|
|
902
|
+
throw new LicenseTransportHttpError(response.status, errorBody, `HTTP ${response.status}: ${errorMessage}`);
|
|
903
|
+
}
|
|
904
|
+
return await response.json();
|
|
905
|
+
}
|
|
906
|
+
finally {
|
|
907
|
+
if (timeoutId) {
|
|
908
|
+
clearTimeout(timeoutId);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function asRecord(value) {
|
|
915
|
+
return value && typeof value === 'object' ? value : {};
|
|
916
|
+
}
|
|
917
|
+
function readText(raw, keys) {
|
|
918
|
+
const body = asRecord(raw);
|
|
919
|
+
for (const key of keys) {
|
|
920
|
+
const value = body[key];
|
|
921
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
922
|
+
return value;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return '';
|
|
926
|
+
}
|
|
927
|
+
function readNumber(raw, keys) {
|
|
928
|
+
const body = asRecord(raw);
|
|
929
|
+
for (const key of keys) {
|
|
930
|
+
const value = body[key];
|
|
931
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
932
|
+
return value;
|
|
933
|
+
}
|
|
934
|
+
if (typeof value === 'string' && value.trim() !== '') {
|
|
935
|
+
const numeric = Number(value);
|
|
936
|
+
if (Number.isFinite(numeric)) {
|
|
937
|
+
return numeric;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return undefined;
|
|
942
|
+
}
|
|
943
|
+
function joinUrl(baseUrl, path) {
|
|
944
|
+
return `${baseUrl.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
|
|
945
|
+
}
|
|
946
|
+
class DesignbaseAccountAuthClient {
|
|
947
|
+
constructor(config) {
|
|
948
|
+
this.config = config;
|
|
949
|
+
if (!config.baseUrl.trim()) {
|
|
950
|
+
throw new Error('DesignbaseAccountAuthConfig.baseUrl is required.');
|
|
951
|
+
}
|
|
952
|
+
this.fetchFn = config.fetchFn ?? fetch;
|
|
953
|
+
}
|
|
954
|
+
async start(payload) {
|
|
955
|
+
const raw = await this.postJson(joinUrl(this.config.baseUrl, this.config.startPath || '/wp-json/custom/v1/figma/auth/start'), {
|
|
956
|
+
plugin_code: payload.pluginCode,
|
|
957
|
+
device_id: payload.deviceId,
|
|
958
|
+
return_url: payload.returnUrl || '',
|
|
959
|
+
});
|
|
960
|
+
return {
|
|
961
|
+
sessionId: readText(raw, ['session_id', 'sessionId']),
|
|
962
|
+
pollToken: readText(raw, ['poll_token', 'pollToken']),
|
|
963
|
+
authUrl: readText(raw, ['auth_url', 'authUrl']),
|
|
964
|
+
expiresIn: readNumber(raw, ['expires_in', 'expiresIn']) ?? 600,
|
|
965
|
+
pollInterval: readNumber(raw, ['poll_interval', 'pollInterval']) ?? 3,
|
|
966
|
+
raw,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
async poll(sessionId, pollToken) {
|
|
970
|
+
const raw = await this.postJson(joinUrl(this.config.baseUrl, this.config.pollPath || '/wp-json/custom/v1/figma/auth/poll'), {
|
|
971
|
+
session_id: sessionId,
|
|
972
|
+
poll_token: pollToken,
|
|
973
|
+
});
|
|
974
|
+
const status = readText(raw, ['status']) || 'pending';
|
|
975
|
+
const allowed = Boolean(asRecord(raw).allowed || status === 'active');
|
|
976
|
+
const tokens = asRecord(asRecord(raw).tokens);
|
|
977
|
+
const actions = asRecord(asRecord(raw).actions);
|
|
978
|
+
return {
|
|
979
|
+
status,
|
|
980
|
+
allowed,
|
|
981
|
+
pluginCode: readText(raw, ['plugin_code', 'pluginCode']),
|
|
982
|
+
productId: readText(raw, ['product_id', 'productId']) || undefined,
|
|
983
|
+
planTier: readText(raw, ['plan_tier', 'planTier']) || undefined,
|
|
984
|
+
licenseKey: readText(raw, ['license_key', 'licenseKey']) || undefined,
|
|
985
|
+
accessToken: readText(tokens, ['access_token', 'accessToken']) || undefined,
|
|
986
|
+
refreshToken: readText(tokens, ['refresh_token', 'refreshToken']) || undefined,
|
|
987
|
+
expiresIn: readNumber(tokens, ['expires_in', 'expiresIn']),
|
|
988
|
+
message: readText(raw, ['message']) || undefined,
|
|
989
|
+
actions,
|
|
990
|
+
raw,
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
async saveTokens(result) {
|
|
994
|
+
if (!this.config.storage || !result.accessToken) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
const prefix = this.config.storagePrefix || 'designbase-account-auth';
|
|
998
|
+
await Promise.all([
|
|
999
|
+
this.config.storage.set(`${prefix}:accessToken`, result.accessToken),
|
|
1000
|
+
this.config.storage.set(`${prefix}:refreshToken`, result.refreshToken || ''),
|
|
1001
|
+
this.config.storage.set(`${prefix}:expiresAt`, Date.now() + ((result.expiresIn || 1800) * 1000)),
|
|
1002
|
+
]);
|
|
1003
|
+
}
|
|
1004
|
+
async getAccessToken() {
|
|
1005
|
+
if (!this.config.storage) {
|
|
1006
|
+
return '';
|
|
1007
|
+
}
|
|
1008
|
+
const prefix = this.config.storagePrefix || 'designbase-account-auth';
|
|
1009
|
+
const token = await this.config.storage.get(`${prefix}:accessToken`);
|
|
1010
|
+
return typeof token === 'string' ? token : '';
|
|
1011
|
+
}
|
|
1012
|
+
async postJson(url, body) {
|
|
1013
|
+
const timeoutMs = this.config.timeoutMs ?? 15000;
|
|
1014
|
+
const abortController = typeof AbortController !== 'undefined' ? new AbortController() : undefined;
|
|
1015
|
+
const timeoutId = abortController ? setTimeout(() => abortController.abort(), timeoutMs) : undefined;
|
|
1016
|
+
try {
|
|
1017
|
+
const response = await this.fetchFn(url, {
|
|
1018
|
+
method: 'POST',
|
|
1019
|
+
headers: {
|
|
1020
|
+
'Content-Type': 'application/json',
|
|
1021
|
+
},
|
|
1022
|
+
body: JSON.stringify(body),
|
|
1023
|
+
signal: abortController?.signal,
|
|
1024
|
+
});
|
|
1025
|
+
if (!response.ok) {
|
|
1026
|
+
const text = await response.text();
|
|
1027
|
+
throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);
|
|
441
1028
|
}
|
|
442
1029
|
return await response.json();
|
|
443
1030
|
}
|
|
@@ -448,6 +1035,94 @@
|
|
|
448
1035
|
}
|
|
449
1036
|
}
|
|
450
1037
|
}
|
|
1038
|
+
class DesignbaseAccountLicenseTransport {
|
|
1039
|
+
constructor(config) {
|
|
1040
|
+
this.config = config;
|
|
1041
|
+
this.client = new DesignbaseAccountAuthClient(config);
|
|
1042
|
+
this.fetchFn = config.fetchFn ?? fetch;
|
|
1043
|
+
}
|
|
1044
|
+
async verify(payload) {
|
|
1045
|
+
return this.validate(payload);
|
|
1046
|
+
}
|
|
1047
|
+
async validate(payload) {
|
|
1048
|
+
const token = await this.resolveAccessToken();
|
|
1049
|
+
if (!token) {
|
|
1050
|
+
return {
|
|
1051
|
+
valid: false,
|
|
1052
|
+
message: 'Designbase account access token is missing.',
|
|
1053
|
+
raw: { status: 'not_logged_in' },
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
const url = new URL(joinUrl(this.config.baseUrl, this.config.statusPath || '/wp-json/custom/v1/product/status'));
|
|
1057
|
+
url.searchParams.set('plugin_code', payload.productId);
|
|
1058
|
+
const raw = await this.getJson(url.toString(), token);
|
|
1059
|
+
const status = readText(raw, ['status']);
|
|
1060
|
+
const allowed = Boolean(asRecord(raw).allowed || status === 'active');
|
|
1061
|
+
return {
|
|
1062
|
+
valid: allowed,
|
|
1063
|
+
message: readText(raw, ['message']) || undefined,
|
|
1064
|
+
productId: readText(raw, ['plugin_code', 'pluginCode']) || payload.productId,
|
|
1065
|
+
instanceId: payload.instanceId,
|
|
1066
|
+
activationLimit: readNumber(raw, ['activation_limit', 'activationLimit']) ?? 999999,
|
|
1067
|
+
activationUsage: readNumber(raw, ['activation_usage', 'activationUsage']) ?? 0,
|
|
1068
|
+
planTier: readText(raw, ['plan_tier', 'planTier']) || (allowed ? 'PRO' : 'FREE'),
|
|
1069
|
+
licenseKey: payload.licenseKey,
|
|
1070
|
+
raw,
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
async deactivate(payload) {
|
|
1074
|
+
const token = await this.resolveAccessToken();
|
|
1075
|
+
if (!token) {
|
|
1076
|
+
return { success: true, message: 'Already signed out.' };
|
|
1077
|
+
}
|
|
1078
|
+
const raw = await this.postJson(joinUrl(this.config.baseUrl, this.config.deactivatePath || '/wp-json/custom/v1/product/deactivate'), {
|
|
1079
|
+
license_key: payload.licenseKey,
|
|
1080
|
+
device_id: payload.instanceId || payload.instanceName,
|
|
1081
|
+
plugin_code: payload.productId,
|
|
1082
|
+
}, token);
|
|
1083
|
+
const status = readText(raw, ['status']);
|
|
1084
|
+
return {
|
|
1085
|
+
success: Boolean(asRecord(raw).success || status === 'ok' || status === 'inactive'),
|
|
1086
|
+
message: readText(raw, ['message']) || undefined,
|
|
1087
|
+
raw,
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
async resolveAccessToken() {
|
|
1091
|
+
if (this.config.getAccessToken) {
|
|
1092
|
+
const token = await this.config.getAccessToken();
|
|
1093
|
+
return typeof token === 'string' ? token : '';
|
|
1094
|
+
}
|
|
1095
|
+
return this.client.getAccessToken();
|
|
1096
|
+
}
|
|
1097
|
+
async getJson(url, token) {
|
|
1098
|
+
const response = await this.fetchFn(url, {
|
|
1099
|
+
method: 'GET',
|
|
1100
|
+
headers: {
|
|
1101
|
+
Authorization: `Bearer ${token}`,
|
|
1102
|
+
},
|
|
1103
|
+
});
|
|
1104
|
+
if (!response.ok) {
|
|
1105
|
+
const text = await response.text();
|
|
1106
|
+
throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);
|
|
1107
|
+
}
|
|
1108
|
+
return response.json();
|
|
1109
|
+
}
|
|
1110
|
+
async postJson(url, body, token) {
|
|
1111
|
+
const response = await this.fetchFn(url, {
|
|
1112
|
+
method: 'POST',
|
|
1113
|
+
headers: {
|
|
1114
|
+
Authorization: `Bearer ${token}`,
|
|
1115
|
+
'Content-Type': 'application/json',
|
|
1116
|
+
},
|
|
1117
|
+
body: JSON.stringify(body),
|
|
1118
|
+
});
|
|
1119
|
+
if (!response.ok) {
|
|
1120
|
+
const text = await response.text();
|
|
1121
|
+
throw new Error(`HTTP ${response.status}: ${text || response.statusText}`);
|
|
1122
|
+
}
|
|
1123
|
+
return response.json();
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
451
1126
|
|
|
452
1127
|
class FigmaStorageAdapter {
|
|
453
1128
|
constructor(figma) {
|
|
@@ -478,25 +1153,38 @@
|
|
|
478
1153
|
}
|
|
479
1154
|
}
|
|
480
1155
|
}
|
|
481
|
-
const
|
|
1156
|
+
const DEFAULT_LICENSE_MESSAGE_TYPES = {
|
|
482
1157
|
initialize: 'initialize',
|
|
483
1158
|
verifyLicense: 'verify-license',
|
|
484
1159
|
deactivateLicense: 'deactivate-license',
|
|
485
1160
|
updateStatus: 'update-plugin-status',
|
|
486
1161
|
verifyResult: 'license-verification-result',
|
|
487
1162
|
deactivateResult: 'license-deactivation-result',
|
|
1163
|
+
checkFeatureAccess: 'check-feature-access',
|
|
1164
|
+
consumeUsage: 'consume-usage',
|
|
1165
|
+
featureAccessResult: 'feature-access-result',
|
|
1166
|
+
usageConsumeResult: 'usage-consume-result',
|
|
488
1167
|
};
|
|
489
1168
|
class FigmaLicenseStatusManager {
|
|
490
1169
|
constructor(options) {
|
|
491
1170
|
this.options = options;
|
|
492
1171
|
this.messageTypes = {
|
|
493
|
-
...
|
|
1172
|
+
...DEFAULT_LICENSE_MESSAGE_TYPES,
|
|
494
1173
|
...(options.messageTypes || {}),
|
|
495
1174
|
};
|
|
496
1175
|
this.notifier = options.notifier;
|
|
497
1176
|
}
|
|
498
1177
|
async initialize() {
|
|
499
|
-
|
|
1178
|
+
if (!this.initializationPromise) {
|
|
1179
|
+
this.initializationPromise = (async () => {
|
|
1180
|
+
const state = await this.options.core.initialize();
|
|
1181
|
+
await this.postStatusUpdate(state);
|
|
1182
|
+
})().catch((error) => {
|
|
1183
|
+
this.initializationPromise = undefined;
|
|
1184
|
+
throw error;
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
await this.initializationPromise;
|
|
500
1188
|
}
|
|
501
1189
|
async handleMessage(pluginMessage) {
|
|
502
1190
|
if (!pluginMessage || typeof pluginMessage !== 'object') {
|
|
@@ -508,7 +1196,7 @@
|
|
|
508
1196
|
return false;
|
|
509
1197
|
}
|
|
510
1198
|
if (type === this.messageTypes.initialize) {
|
|
511
|
-
await this.
|
|
1199
|
+
await this.initialize();
|
|
512
1200
|
return true;
|
|
513
1201
|
}
|
|
514
1202
|
if (type === this.messageTypes.verifyLicense) {
|
|
@@ -520,6 +1208,22 @@
|
|
|
520
1208
|
await this.handleDeactivateLicense();
|
|
521
1209
|
return true;
|
|
522
1210
|
}
|
|
1211
|
+
if (type === this.messageTypes.checkFeatureAccess) {
|
|
1212
|
+
const featureKey = typeof message.featureKey === 'string' ? message.featureKey : undefined;
|
|
1213
|
+
const amount = typeof message.amount === 'number' && Number.isFinite(message.amount) && message.amount > 0
|
|
1214
|
+
? message.amount
|
|
1215
|
+
: 1;
|
|
1216
|
+
await this.handleCheckFeatureAccess(featureKey, amount);
|
|
1217
|
+
return true;
|
|
1218
|
+
}
|
|
1219
|
+
if (type === this.messageTypes.consumeUsage) {
|
|
1220
|
+
const amount = typeof message.amount === 'number' && Number.isFinite(message.amount) && message.amount > 0
|
|
1221
|
+
? message.amount
|
|
1222
|
+
: 1;
|
|
1223
|
+
const featureKey = typeof message.featureKey === 'string' ? message.featureKey : undefined;
|
|
1224
|
+
await this.handleConsumeUsage(amount, featureKey);
|
|
1225
|
+
return true;
|
|
1226
|
+
}
|
|
523
1227
|
return false;
|
|
524
1228
|
}
|
|
525
1229
|
bindToOnMessage(fallbackHandler) {
|
|
@@ -531,18 +1235,40 @@
|
|
|
531
1235
|
}
|
|
532
1236
|
};
|
|
533
1237
|
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Check a feature from main code using the same policy as the UI messages.
|
|
1240
|
+
* This is the preferred guard for operations handled entirely in main.
|
|
1241
|
+
*/
|
|
1242
|
+
async canUseFeature(featureKey, amount = 1) {
|
|
1243
|
+
const state = await this.options.core.getStatus();
|
|
1244
|
+
const safeAmount = Number.isFinite(amount) && amount > 0 ? amount : 1;
|
|
1245
|
+
return this.options.core.resolveUsageAccess(state, featureKey, safeAmount);
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Consume usage from main code and publish the refreshed status to the UI.
|
|
1249
|
+
*/
|
|
1250
|
+
async consumeUsage(amount = 1, featureKey) {
|
|
1251
|
+
const result = await this.options.core.consumeUsage(amount, featureKey);
|
|
1252
|
+
await this.postStatusUpdate(result.state);
|
|
1253
|
+
return result;
|
|
1254
|
+
}
|
|
534
1255
|
async handleVerifyLicense(licenseKey) {
|
|
535
1256
|
try {
|
|
536
1257
|
const state = await this.options.core.verifyLicense(licenseKey);
|
|
537
1258
|
const access = this.options.core.resolveUsageAccess(state);
|
|
1259
|
+
const summary = await this.options.core.getLicenseSummary();
|
|
538
1260
|
this.postMessage({
|
|
539
1261
|
type: this.messageTypes.verifyResult,
|
|
540
1262
|
success: true,
|
|
541
1263
|
message: '라이선스가 검증되었습니다.',
|
|
542
1264
|
...state,
|
|
543
|
-
|
|
1265
|
+
...summary,
|
|
1266
|
+
usageLimit: this.options.core.getUsageLimit(state),
|
|
544
1267
|
canUseFeature: access.allowed,
|
|
545
1268
|
remainingUsage: access.remaining,
|
|
1269
|
+
usageUsed: access.used,
|
|
1270
|
+
usageReset: access.reset,
|
|
1271
|
+
usageTier: access.tier,
|
|
546
1272
|
});
|
|
547
1273
|
await this.postStatusUpdate(state);
|
|
548
1274
|
}
|
|
@@ -555,20 +1281,26 @@
|
|
|
555
1281
|
message: mappedError.message,
|
|
556
1282
|
errorCode: mappedError.code,
|
|
557
1283
|
});
|
|
1284
|
+
await this.postStatusUpdate();
|
|
558
1285
|
}
|
|
559
1286
|
}
|
|
560
1287
|
async handleDeactivateLicense() {
|
|
561
1288
|
try {
|
|
562
1289
|
const state = await this.options.core.deactivateLicense();
|
|
563
1290
|
const access = this.options.core.resolveUsageAccess(state);
|
|
1291
|
+
const summary = await this.options.core.getLicenseSummary();
|
|
564
1292
|
this.postMessage({
|
|
565
1293
|
type: this.messageTypes.deactivateResult,
|
|
566
1294
|
success: true,
|
|
567
1295
|
message: '라이선스가 비활성화되었습니다.',
|
|
568
1296
|
...state,
|
|
569
|
-
|
|
1297
|
+
...summary,
|
|
1298
|
+
usageLimit: this.options.core.getUsageLimit(state),
|
|
570
1299
|
canUseFeature: access.allowed,
|
|
571
1300
|
remainingUsage: access.remaining,
|
|
1301
|
+
usageUsed: access.used,
|
|
1302
|
+
usageReset: access.reset,
|
|
1303
|
+
usageTier: access.tier,
|
|
572
1304
|
});
|
|
573
1305
|
await this.postStatusUpdate(state);
|
|
574
1306
|
}
|
|
@@ -586,13 +1318,47 @@
|
|
|
586
1318
|
async postStatusUpdate(initialState) {
|
|
587
1319
|
const state = initialState ?? await this.options.core.getStatus();
|
|
588
1320
|
const access = this.options.core.resolveUsageAccess(state);
|
|
1321
|
+
const summary = await this.options.core.getLicenseSummary();
|
|
589
1322
|
this.postMessage({
|
|
590
1323
|
type: this.messageTypes.updateStatus,
|
|
591
1324
|
...state,
|
|
592
|
-
|
|
1325
|
+
...summary,
|
|
1326
|
+
usageLimit: this.options.core.getUsageLimit(state),
|
|
593
1327
|
canUseFeature: access.allowed,
|
|
594
1328
|
remainingUsage: access.remaining,
|
|
1329
|
+
usageUsed: access.used,
|
|
1330
|
+
usageReset: access.reset,
|
|
1331
|
+
usageTier: access.tier,
|
|
1332
|
+
usageReason: access.reason,
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
async handleCheckFeatureAccess(featureKey, amount = 1) {
|
|
1336
|
+
const state = await this.options.core.getStatus();
|
|
1337
|
+
const access = this.options.core.resolveUsageAccess(state, featureKey, amount);
|
|
1338
|
+
const summary = await this.options.core.getLicenseSummary();
|
|
1339
|
+
this.postMessage({
|
|
1340
|
+
type: this.messageTypes.featureAccessResult,
|
|
1341
|
+
success: true,
|
|
1342
|
+
featureKey,
|
|
1343
|
+
amount,
|
|
1344
|
+
...summary,
|
|
1345
|
+
...access,
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
async handleConsumeUsage(amount, featureKey) {
|
|
1349
|
+
const result = await this.options.core.consumeUsage(amount, featureKey);
|
|
1350
|
+
const summary = await this.options.core.getLicenseSummary();
|
|
1351
|
+
this.postMessage({
|
|
1352
|
+
type: this.messageTypes.usageConsumeResult,
|
|
1353
|
+
success: result.consumed,
|
|
1354
|
+
featureKey,
|
|
1355
|
+
amount,
|
|
1356
|
+
consumed: result.consumed,
|
|
1357
|
+
...summary,
|
|
1358
|
+
...result.access,
|
|
1359
|
+
usageCount: result.state.usageCount,
|
|
595
1360
|
});
|
|
1361
|
+
await this.postStatusUpdate(result.state);
|
|
596
1362
|
}
|
|
597
1363
|
postMessage(message) {
|
|
598
1364
|
this.options.figma.ui.postMessage(message);
|
|
@@ -620,6 +1386,7 @@
|
|
|
620
1386
|
const notifier = options.notifier ?? new FigmaNotifierAdapter(figmaApi);
|
|
621
1387
|
const transport = options.transport ?? new FetchLicenseTransport({
|
|
622
1388
|
verifyUrl: options.config.endpoint,
|
|
1389
|
+
validateUrl: options.config.validateEndpoint,
|
|
623
1390
|
deactivateUrl: options.config.deactivateEndpoint,
|
|
624
1391
|
headers: options.config.requestHeaders,
|
|
625
1392
|
timeoutMs: options.config.requestTimeoutMs,
|
|
@@ -639,12 +1406,16 @@
|
|
|
639
1406
|
});
|
|
640
1407
|
}
|
|
641
1408
|
|
|
1409
|
+
exports.DEFAULT_LICENSE_MESSAGE_TYPES = DEFAULT_LICENSE_MESSAGE_TYPES;
|
|
1410
|
+
exports.DesignbaseAccountAuthClient = DesignbaseAccountAuthClient;
|
|
1411
|
+
exports.DesignbaseAccountLicenseTransport = DesignbaseAccountLicenseTransport;
|
|
642
1412
|
exports.FetchLicenseTransport = FetchLicenseTransport;
|
|
643
1413
|
exports.FigmaLicenseStatusManager = FigmaLicenseStatusManager;
|
|
644
1414
|
exports.FigmaNotifierAdapter = FigmaNotifierAdapter;
|
|
645
1415
|
exports.FigmaStorageAdapter = FigmaStorageAdapter;
|
|
646
1416
|
exports.LicenseCore = LicenseCore;
|
|
647
1417
|
exports.LicenseCoreError = LicenseCoreError;
|
|
1418
|
+
exports.LicenseTransportHttpError = LicenseTransportHttpError;
|
|
648
1419
|
exports.createFigmaLicenseStatusManager = createFigmaLicenseStatusManager;
|
|
649
1420
|
exports.defaultNormalizeDeactivateResponse = defaultNormalizeDeactivateResponse;
|
|
650
1421
|
exports.defaultNormalizeVerifyResponse = defaultNormalizeVerifyResponse;
|