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