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