@designbasekorea/figma-license-core 0.2.1 → 0.3.1
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 +29 -1
- package/dist/figmaStatusManager.d.ts +10 -2
- package/dist/figmaStatusManager.d.ts.map +1 -1
- package/dist/index.esm.js +105 -44
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +105 -43
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +105 -43
- package/dist/index.umd.js.map +1 -1
- package/dist/licenseCore.d.ts +6 -8
- package/dist/licenseCore.d.ts.map +1 -1
- package/dist/types.d.ts +12 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/PLUGIN_INTEGRATION_KO.md +21 -5
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Figma 플러그인의 라이선스 상태/사용량 정책/기능 제한을 공
|
|
|
8
8
|
- `hasLicenseKey`: 라이선스 키가 저장되어 있는지
|
|
9
9
|
- `isLicenseRegistered`: 실제 유효 등록(PAID + key) 상태인지
|
|
10
10
|
- 티어별 사용량 정책
|
|
11
|
-
|
|
11
|
+
- 일간 리셋 (`daily`), 주간 리셋 (`weekly`), 월간 리셋 (`monthly`), 리셋 없음 (`none`)
|
|
12
12
|
- `limit: number | 'unlimited'`
|
|
13
13
|
- 티어별 기능 제한
|
|
14
14
|
- `allow`/`deny` 기반 기능 접근 제어
|
|
@@ -65,6 +65,32 @@ await statusManager.initialize();
|
|
|
65
65
|
- `check-feature-access` (`featureKey` 선택)
|
|
66
66
|
- `consume-usage` (`amount`, `featureKey` 선택)
|
|
67
67
|
|
|
68
|
+
`amount`가 남은 수량보다 크면 소비되지 않습니다. 마지막 1회를 소비한 응답도
|
|
69
|
+
`success: true`, `remaining: 0`으로 반환되므로 실행 성공 여부와 다음 차단 상태를 혼동하지 않습니다.
|
|
70
|
+
|
|
71
|
+
## 플러그인 공통 사용 규칙
|
|
72
|
+
|
|
73
|
+
모든 플러그인은 기능 실행 직전에 같은 순서를 사용합니다.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const access = await statusManager.canUseFeature('bulk_apply', 1);
|
|
77
|
+
if (!access.allowed) {
|
|
78
|
+
// access.reason === 'FEATURE_NOT_ALLOWED' 또는 'USAGE_LIMIT_REACHED'
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const result = await statusManager.consumeUsage(1, 'bulk_apply');
|
|
83
|
+
if (!result.consumed) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await runBulkApply();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
UI 메시지를 사용하는 경우에는 `check-feature-access`로 미리 확인한 뒤
|
|
91
|
+
`consume-usage`를 실행 직전에 전송합니다. 서버에서 최종 권한을 판정하는 제품은
|
|
92
|
+
동일한 feature key를 서버와 코어 설정에 사용해야 합니다.
|
|
93
|
+
|
|
68
94
|
### Main -> UI
|
|
69
95
|
|
|
70
96
|
- `update-plugin-status`
|
|
@@ -88,6 +114,8 @@ await statusManager.initialize();
|
|
|
88
114
|
- 운영 배포에서는 `masterCode`를 사용하지 마세요.
|
|
89
115
|
- 최종 권한 판정은 서버 검증 결과 기준으로 처리하세요.
|
|
90
116
|
- 클라이언트 상태(`paymentStatus`, `usageCount`)는 변조 가능하다고 가정하세요.
|
|
117
|
+
- 모든 플러그인의 리셋 단위·무료 한도·차단 기능은 `policy`에서만 선언하고,
|
|
118
|
+
플러그인별 임의 카운터를 추가하지 마세요.
|
|
91
119
|
|
|
92
120
|
## 문서
|
|
93
121
|
|
|
@@ -32,6 +32,7 @@ export declare class FigmaNotifierAdapter implements LicenseNotifierAdapter {
|
|
|
32
32
|
timeout?: number;
|
|
33
33
|
}): void;
|
|
34
34
|
}
|
|
35
|
+
export declare const DEFAULT_LICENSE_MESSAGE_TYPES: Required<LicenseManagerMessageTypes>;
|
|
35
36
|
export interface FigmaLicenseStatusManagerOptions {
|
|
36
37
|
figma: FigmaApiLike;
|
|
37
38
|
core: LicenseCore;
|
|
@@ -46,13 +47,20 @@ export declare class FigmaLicenseStatusManager {
|
|
|
46
47
|
private readonly options;
|
|
47
48
|
private readonly messageTypes;
|
|
48
49
|
private readonly notifier?;
|
|
49
|
-
private readonly featureAccessMessageType;
|
|
50
|
-
private readonly consumeUsageMessageType;
|
|
51
50
|
private initializationPromise?;
|
|
52
51
|
constructor(options: FigmaLicenseStatusManagerOptions);
|
|
53
52
|
initialize(): Promise<void>;
|
|
54
53
|
handleMessage(pluginMessage: unknown): Promise<boolean>;
|
|
55
54
|
bindToOnMessage(fallbackHandler?: (pluginMessage: unknown) => void | Promise<void>): void;
|
|
55
|
+
/**
|
|
56
|
+
* Check a feature from main code using the same policy as the UI messages.
|
|
57
|
+
* This is the preferred guard for operations handled entirely in main.
|
|
58
|
+
*/
|
|
59
|
+
canUseFeature(featureKey?: string, amount?: number): Promise<import("./types").UsageAccessResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Consume usage from main code and publish the refreshed status to the UI.
|
|
62
|
+
*/
|
|
63
|
+
consumeUsage(amount?: number, featureKey?: string): Promise<import("./types").UsageConsumptionResult>;
|
|
56
64
|
private handleVerifyLicense;
|
|
57
65
|
private handleDeactivateLicense;
|
|
58
66
|
private postStatusUpdate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"figmaStatusManager.d.ts","sourceRoot":"","sources":["../src/figmaStatusManager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAA2B,MAAM,eAAe,CAAC;AACrE,OAAO,EACL,aAAa,EAEb,0BAA0B,EAC1B,sBAAsB,EAEtB,qBAAqB,EACrB,gBAAgB,EACjB,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,WAAW,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,SAAS,CAAC,EAAE,CAAC,aAAa,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,sBAAsB,CAAC;IACtC,EAAE,EAAE,WAAW,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACrF;AAED,qBAAa,mBAAoB,YAAW,qBAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,YAAY;IAE1C,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAKrD,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAItD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAQzC;AAED,qBAAa,oBAAqB,YAAW,sBAAsB;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,YAAY;IAEhD,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;CAK/E;
|
|
1
|
+
{"version":3,"file":"figmaStatusManager.d.ts","sourceRoot":"","sources":["../src/figmaStatusManager.ts"],"names":[],"mappings":"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"}
|
package/dist/index.esm.js
CHANGED
|
@@ -122,6 +122,7 @@ const STORAGE_KEYS = {
|
|
|
122
122
|
activationUsage: 'activationUsage',
|
|
123
123
|
usageCount: 'usageCount',
|
|
124
124
|
previousUsageCount: 'previousUsageCount',
|
|
125
|
+
previousUsageWindowKey: 'previousUsageWindowKey',
|
|
125
126
|
usageWindowKey: 'usageWindowKey',
|
|
126
127
|
planTier: 'planTier',
|
|
127
128
|
lastVerifiedAt: 'lastVerifiedAt',
|
|
@@ -186,7 +187,7 @@ function normalizeTier(tier) {
|
|
|
186
187
|
return tier.trim() || 'FREE';
|
|
187
188
|
}
|
|
188
189
|
function normalizeUsageReset(reset) {
|
|
189
|
-
if (reset === 'daily' || reset === 'monthly' || reset === 'none') {
|
|
190
|
+
if (reset === 'daily' || reset === 'weekly' || reset === 'monthly' || reset === 'none') {
|
|
190
191
|
return reset;
|
|
191
192
|
}
|
|
192
193
|
return 'none';
|
|
@@ -201,6 +202,19 @@ function toDateKey(date, period) {
|
|
|
201
202
|
if (period === 'monthly') {
|
|
202
203
|
return `${year}-${month}`;
|
|
203
204
|
}
|
|
205
|
+
if (period === 'weekly') {
|
|
206
|
+
// ISO weeks start on Monday and keep the week-year at the Thursday of
|
|
207
|
+
// the current week. Using UTC avoids a reset moving at local DST changes.
|
|
208
|
+
const dayOfWeek = date.getUTCDay() || 7;
|
|
209
|
+
const thursday = new Date(Date.UTC(year, date.getUTCMonth(), date.getUTCDate()));
|
|
210
|
+
thursday.setUTCDate(thursday.getUTCDate() + (4 - dayOfWeek));
|
|
211
|
+
const weekYear = thursday.getUTCFullYear();
|
|
212
|
+
const firstThursday = new Date(Date.UTC(weekYear, 0, 4));
|
|
213
|
+
const firstDay = firstThursday.getUTCDay() || 7;
|
|
214
|
+
firstThursday.setUTCDate(firstThursday.getUTCDate() + (4 - firstDay));
|
|
215
|
+
const week = 1 + Math.round((thursday.getTime() - firstThursday.getTime()) / 604800000);
|
|
216
|
+
return `${weekYear}-W${String(week).padStart(2, '0')}`;
|
|
217
|
+
}
|
|
204
218
|
return `${year}-${month}-${day}`;
|
|
205
219
|
}
|
|
206
220
|
function normalizePolicy(config) {
|
|
@@ -239,6 +253,7 @@ class LicenseCore {
|
|
|
239
253
|
constructor(config, deps) {
|
|
240
254
|
this.config = config;
|
|
241
255
|
this.deps = deps;
|
|
256
|
+
this.usageMutation = Promise.resolve();
|
|
242
257
|
if (!config.productId.trim()) {
|
|
243
258
|
throw new Error('LicenseConfig.productId is required.');
|
|
244
259
|
}
|
|
@@ -283,13 +298,15 @@ class LicenseCore {
|
|
|
283
298
|
}
|
|
284
299
|
async resetLicenseState() {
|
|
285
300
|
const current = await this.getStatus();
|
|
286
|
-
const freeReset = this.getUsagePolicy(this.policy.freeTier).reset;
|
|
287
301
|
const previousUsageCount = current.previousUsageCount ?? 0;
|
|
302
|
+
const previousUsageWindowKey = current.previousUsageWindowKey || '';
|
|
288
303
|
const nextState = {
|
|
289
304
|
...current,
|
|
290
305
|
paymentStatus: 'FREE',
|
|
291
306
|
planTier: this.policy.freeTier,
|
|
292
|
-
|
|
307
|
+
// 유료 전환 전에 사용하던 무료 사용량 창을 복원한다. 날짜가 바뀌었다면
|
|
308
|
+
// applyUsageWindowResetIfNeeded()가 아래 카운트를 0으로 초기화한다.
|
|
309
|
+
usageWindowKey: previousUsageWindowKey,
|
|
293
310
|
licenseKey: '',
|
|
294
311
|
instanceId: '',
|
|
295
312
|
activationLimit: 0,
|
|
@@ -309,6 +326,7 @@ class LicenseCore {
|
|
|
309
326
|
const nextState = {
|
|
310
327
|
...current,
|
|
311
328
|
paymentStatus: 'PAID',
|
|
329
|
+
planTier: normalizeTier(result.planTier || current.planTier || this.policy.paidTier),
|
|
312
330
|
activationLimit: result.activationLimit ?? current.activationLimit,
|
|
313
331
|
activationUsage: result.activationUsage ?? current.activationUsage,
|
|
314
332
|
licenseStatus: result.licenseStatus || 'active',
|
|
@@ -322,12 +340,12 @@ class LicenseCore {
|
|
|
322
340
|
return nextState;
|
|
323
341
|
}
|
|
324
342
|
async markValidationFailure(current, message, errorCode) {
|
|
325
|
-
const
|
|
343
|
+
const previousUsageWindowKey = current.previousUsageWindowKey || '';
|
|
326
344
|
const nextState = {
|
|
327
345
|
...current,
|
|
328
346
|
paymentStatus: 'UNPAID',
|
|
329
347
|
planTier: this.policy.freeTier,
|
|
330
|
-
usageWindowKey:
|
|
348
|
+
usageWindowKey: previousUsageWindowKey,
|
|
331
349
|
activationLimit: 0,
|
|
332
350
|
activationUsage: 0,
|
|
333
351
|
usageCount: current.previousUsageCount ?? current.usageCount,
|
|
@@ -377,6 +395,7 @@ class LicenseCore {
|
|
|
377
395
|
activationLimit: MASTER_CODE_ACTIVATION_LIMIT,
|
|
378
396
|
activationUsage: 0,
|
|
379
397
|
previousUsageCount: current.usageCount,
|
|
398
|
+
previousUsageWindowKey: current.usageWindowKey,
|
|
380
399
|
lastVerifiedAt: new Date().toISOString(),
|
|
381
400
|
licenseStatus: 'active',
|
|
382
401
|
licenseSource: 'master',
|
|
@@ -424,6 +443,7 @@ class LicenseCore {
|
|
|
424
443
|
activationLimit,
|
|
425
444
|
activationUsage,
|
|
426
445
|
previousUsageCount: current.usageCount,
|
|
446
|
+
previousUsageWindowKey: current.usageWindowKey,
|
|
427
447
|
lastVerifiedAt: new Date().toISOString(),
|
|
428
448
|
licenseStatus: verifyResult.licenseStatus || 'active',
|
|
429
449
|
licenseSource: verifyResult.licenseSource,
|
|
@@ -466,7 +486,7 @@ class LicenseCore {
|
|
|
466
486
|
if (current.paymentStatus === 'PAID') {
|
|
467
487
|
return current;
|
|
468
488
|
}
|
|
469
|
-
const increasedAmount = Math.max(0, amount);
|
|
489
|
+
const increasedAmount = Number.isFinite(amount) ? Math.max(0, amount) : 0;
|
|
470
490
|
if (increasedAmount === 0) {
|
|
471
491
|
return current;
|
|
472
492
|
}
|
|
@@ -477,28 +497,39 @@ class LicenseCore {
|
|
|
477
497
|
await this.persistState(nextState);
|
|
478
498
|
return nextState;
|
|
479
499
|
}
|
|
480
|
-
async canUseFeature(featureKey) {
|
|
500
|
+
async canUseFeature(featureKey, amount = 1) {
|
|
481
501
|
const state = await this.getStatus();
|
|
482
|
-
return this.resolveUsageAccess(state, featureKey);
|
|
502
|
+
return this.resolveUsageAccess(state, featureKey, amount);
|
|
483
503
|
}
|
|
484
504
|
async consumeUsage(amount = 1, featureKey) {
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
505
|
+
const execute = async () => {
|
|
506
|
+
const state = await this.getStatus();
|
|
507
|
+
const requestedAmount = Number.isFinite(amount) ? Math.max(0, amount) : 0;
|
|
508
|
+
const access = this.resolveUsageAccess(state, featureKey, requestedAmount);
|
|
509
|
+
if (!access.allowed || requestedAmount <= 0) {
|
|
510
|
+
return { state, access, consumed: false };
|
|
511
|
+
}
|
|
512
|
+
const updatedState = await this.incrementUsage(requestedAmount);
|
|
513
|
+
const updatedAccess = this.resolveUsageAccess(updatedState, featureKey, requestedAmount);
|
|
514
|
+
return {
|
|
515
|
+
state: updatedState,
|
|
516
|
+
access: updatedAccess,
|
|
517
|
+
consumed: true,
|
|
518
|
+
};
|
|
495
519
|
};
|
|
496
|
-
|
|
497
|
-
|
|
520
|
+
// Figma can deliver duplicate UI events while a panel remounts. Serialize
|
|
521
|
+
// the check + persist pair so two concurrent requests cannot overspend the
|
|
522
|
+
// same local allowance.
|
|
523
|
+
const pending = this.usageMutation.then(execute, execute);
|
|
524
|
+
this.usageMutation = pending.then(() => undefined, () => undefined);
|
|
525
|
+
return pending;
|
|
526
|
+
}
|
|
527
|
+
resolveUsageAccess(state, featureKey, amount = 1) {
|
|
498
528
|
const tier = this.resolveTier(state);
|
|
499
529
|
const usagePolicy = this.getUsagePolicy(tier);
|
|
500
530
|
const featureAllowed = this.isFeatureAllowed(tier, featureKey);
|
|
501
531
|
const isLicensed = this.isLicenseRegisteredState(state);
|
|
532
|
+
const requested = Number.isFinite(amount) && amount > 0 ? amount : 1;
|
|
502
533
|
const used = state.usageCount;
|
|
503
534
|
if (usagePolicy.limit === 'unlimited') {
|
|
504
535
|
return {
|
|
@@ -507,6 +538,7 @@ class LicenseCore {
|
|
|
507
538
|
used,
|
|
508
539
|
limit: 'unlimited',
|
|
509
540
|
reset: usagePolicy.reset,
|
|
541
|
+
requested,
|
|
510
542
|
tier,
|
|
511
543
|
hasLicenseKey: Boolean(state.licenseKey),
|
|
512
544
|
isLicenseRegistered: isLicensed,
|
|
@@ -516,7 +548,7 @@ class LicenseCore {
|
|
|
516
548
|
};
|
|
517
549
|
}
|
|
518
550
|
const remaining = Math.max(usagePolicy.limit - used, 0);
|
|
519
|
-
const usageAllowed = remaining
|
|
551
|
+
const usageAllowed = remaining >= requested;
|
|
520
552
|
const allowed = featureAllowed && usageAllowed;
|
|
521
553
|
return {
|
|
522
554
|
allowed,
|
|
@@ -524,6 +556,7 @@ class LicenseCore {
|
|
|
524
556
|
used,
|
|
525
557
|
limit: usagePolicy.limit,
|
|
526
558
|
reset: usagePolicy.reset,
|
|
559
|
+
requested,
|
|
527
560
|
tier,
|
|
528
561
|
hasLicenseKey: Boolean(state.licenseKey),
|
|
529
562
|
isLicenseRegistered: isLicensed,
|
|
@@ -532,19 +565,18 @@ class LicenseCore {
|
|
|
532
565
|
reason: featureAllowed ? (usageAllowed ? undefined : 'USAGE_LIMIT_REACHED') : 'FEATURE_NOT_ALLOWED',
|
|
533
566
|
};
|
|
534
567
|
}
|
|
535
|
-
getUsageLimit() {
|
|
536
|
-
const
|
|
537
|
-
return
|
|
568
|
+
getUsageLimit(state) {
|
|
569
|
+
const tier = state ? this.resolveTier(state) : this.policy.freeTier;
|
|
570
|
+
return this.getUsagePolicy(tier).limit;
|
|
538
571
|
}
|
|
539
572
|
getStoragePrefix() {
|
|
540
573
|
return this.storagePrefix;
|
|
541
574
|
}
|
|
542
575
|
resolveTier(state) {
|
|
543
576
|
const storedTier = normalizeTier(state.planTier || '');
|
|
544
|
-
|
|
577
|
+
{
|
|
545
578
|
return storedTier;
|
|
546
579
|
}
|
|
547
|
-
return state.paymentStatus === 'PAID' ? this.policy.paidTier : this.policy.freeTier;
|
|
548
580
|
}
|
|
549
581
|
getUsagePolicy(tier) {
|
|
550
582
|
return this.policy.usageByTier[tier]
|
|
@@ -579,7 +611,7 @@ class LicenseCore {
|
|
|
579
611
|
}
|
|
580
612
|
async loadState() {
|
|
581
613
|
try {
|
|
582
|
-
const [paymentStatus, licenseKey, instanceId, activationLimit, activationUsage, usageCount, previousUsageCount, usageWindowKey, planTier, lastVerifiedAt, licenseStatus, licenseSource, accessScope, expiresAt, validationMessage,] = await Promise.all([
|
|
614
|
+
const [paymentStatus, licenseKey, instanceId, activationLimit, activationUsage, usageCount, previousUsageCount, previousUsageWindowKey, usageWindowKey, planTier, lastVerifiedAt, licenseStatus, licenseSource, accessScope, expiresAt, validationMessage,] = await Promise.all([
|
|
583
615
|
this.deps.storage.get(this.storageKey('paymentStatus')),
|
|
584
616
|
this.deps.storage.get(this.storageKey('licenseKey')),
|
|
585
617
|
this.deps.storage.get(this.storageKey('instanceId')),
|
|
@@ -587,6 +619,7 @@ class LicenseCore {
|
|
|
587
619
|
this.deps.storage.get(this.storageKey('activationUsage')),
|
|
588
620
|
this.deps.storage.get(this.storageKey('usageCount')),
|
|
589
621
|
this.deps.storage.get(this.storageKey('previousUsageCount')),
|
|
622
|
+
this.deps.storage.get(this.storageKey('previousUsageWindowKey')),
|
|
590
623
|
this.deps.storage.get(this.storageKey('usageWindowKey')),
|
|
591
624
|
this.deps.storage.get(this.storageKey('planTier')),
|
|
592
625
|
this.deps.storage.get(this.storageKey('lastVerifiedAt')),
|
|
@@ -608,6 +641,7 @@ class LicenseCore {
|
|
|
608
641
|
activationUsage: toNonNegativeNumber(activationUsage, 0),
|
|
609
642
|
usageCount: toNonNegativeNumber(usageCount, 0),
|
|
610
643
|
previousUsageCount: toNonNegativeNumber(previousUsageCount, 0),
|
|
644
|
+
previousUsageWindowKey: toText(previousUsageWindowKey) || undefined,
|
|
611
645
|
usageWindowKey: toText(usageWindowKey) || toDateKey(new Date(), usageReset),
|
|
612
646
|
planTier: resolvedTier,
|
|
613
647
|
lastVerifiedAt: toText(lastVerifiedAt) || undefined,
|
|
@@ -649,6 +683,7 @@ class LicenseCore {
|
|
|
649
683
|
this.deps.storage.set(this.storageKey('activationUsage'), state.activationUsage),
|
|
650
684
|
this.deps.storage.set(this.storageKey('usageCount'), state.usageCount),
|
|
651
685
|
this.deps.storage.set(this.storageKey('previousUsageCount'), state.previousUsageCount || 0),
|
|
686
|
+
this.deps.storage.set(this.storageKey('previousUsageWindowKey'), state.previousUsageWindowKey || ''),
|
|
652
687
|
this.deps.storage.set(this.storageKey('usageWindowKey'), state.usageWindowKey),
|
|
653
688
|
this.deps.storage.set(this.storageKey('planTier'), state.planTier),
|
|
654
689
|
this.deps.storage.set(this.storageKey('lastVerifiedAt'), state.lastVerifiedAt || ''),
|
|
@@ -1121,21 +1156,23 @@ class FigmaNotifierAdapter {
|
|
|
1121
1156
|
}
|
|
1122
1157
|
}
|
|
1123
1158
|
}
|
|
1124
|
-
const
|
|
1159
|
+
const DEFAULT_LICENSE_MESSAGE_TYPES = {
|
|
1125
1160
|
initialize: 'initialize',
|
|
1126
1161
|
verifyLicense: 'verify-license',
|
|
1127
1162
|
deactivateLicense: 'deactivate-license',
|
|
1128
1163
|
updateStatus: 'update-plugin-status',
|
|
1129
1164
|
verifyResult: 'license-verification-result',
|
|
1130
1165
|
deactivateResult: 'license-deactivation-result',
|
|
1166
|
+
checkFeatureAccess: 'check-feature-access',
|
|
1167
|
+
consumeUsage: 'consume-usage',
|
|
1168
|
+
featureAccessResult: 'feature-access-result',
|
|
1169
|
+
usageConsumeResult: 'usage-consume-result',
|
|
1131
1170
|
};
|
|
1132
1171
|
class FigmaLicenseStatusManager {
|
|
1133
1172
|
constructor(options) {
|
|
1134
1173
|
this.options = options;
|
|
1135
|
-
this.featureAccessMessageType = 'feature-access-result';
|
|
1136
|
-
this.consumeUsageMessageType = 'usage-consume-result';
|
|
1137
1174
|
this.messageTypes = {
|
|
1138
|
-
...
|
|
1175
|
+
...DEFAULT_LICENSE_MESSAGE_TYPES,
|
|
1139
1176
|
...(options.messageTypes || {}),
|
|
1140
1177
|
};
|
|
1141
1178
|
this.notifier = options.notifier;
|
|
@@ -1174,13 +1211,18 @@ class FigmaLicenseStatusManager {
|
|
|
1174
1211
|
await this.handleDeactivateLicense();
|
|
1175
1212
|
return true;
|
|
1176
1213
|
}
|
|
1177
|
-
if (type ===
|
|
1214
|
+
if (type === this.messageTypes.checkFeatureAccess) {
|
|
1178
1215
|
const featureKey = typeof message.featureKey === 'string' ? message.featureKey : undefined;
|
|
1179
|
-
|
|
1216
|
+
const amount = typeof message.amount === 'number' && Number.isFinite(message.amount) && message.amount > 0
|
|
1217
|
+
? message.amount
|
|
1218
|
+
: 1;
|
|
1219
|
+
await this.handleCheckFeatureAccess(featureKey, amount);
|
|
1180
1220
|
return true;
|
|
1181
1221
|
}
|
|
1182
|
-
if (type ===
|
|
1183
|
-
const amount = typeof message.amount === 'number'
|
|
1222
|
+
if (type === this.messageTypes.consumeUsage) {
|
|
1223
|
+
const amount = typeof message.amount === 'number' && Number.isFinite(message.amount) && message.amount > 0
|
|
1224
|
+
? message.amount
|
|
1225
|
+
: 1;
|
|
1184
1226
|
const featureKey = typeof message.featureKey === 'string' ? message.featureKey : undefined;
|
|
1185
1227
|
await this.handleConsumeUsage(amount, featureKey);
|
|
1186
1228
|
return true;
|
|
@@ -1196,6 +1238,23 @@ class FigmaLicenseStatusManager {
|
|
|
1196
1238
|
}
|
|
1197
1239
|
};
|
|
1198
1240
|
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Check a feature from main code using the same policy as the UI messages.
|
|
1243
|
+
* This is the preferred guard for operations handled entirely in main.
|
|
1244
|
+
*/
|
|
1245
|
+
async canUseFeature(featureKey, amount = 1) {
|
|
1246
|
+
const state = await this.options.core.getStatus();
|
|
1247
|
+
const safeAmount = Number.isFinite(amount) && amount > 0 ? amount : 1;
|
|
1248
|
+
return this.options.core.resolveUsageAccess(state, featureKey, safeAmount);
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Consume usage from main code and publish the refreshed status to the UI.
|
|
1252
|
+
*/
|
|
1253
|
+
async consumeUsage(amount = 1, featureKey) {
|
|
1254
|
+
const result = await this.options.core.consumeUsage(amount, featureKey);
|
|
1255
|
+
await this.postStatusUpdate(result.state);
|
|
1256
|
+
return result;
|
|
1257
|
+
}
|
|
1199
1258
|
async handleVerifyLicense(licenseKey) {
|
|
1200
1259
|
try {
|
|
1201
1260
|
const state = await this.options.core.verifyLicense(licenseKey);
|
|
@@ -1207,7 +1266,7 @@ class FigmaLicenseStatusManager {
|
|
|
1207
1266
|
message: '라이선스가 검증되었습니다.',
|
|
1208
1267
|
...state,
|
|
1209
1268
|
...summary,
|
|
1210
|
-
usageLimit: this.options.core.getUsageLimit(),
|
|
1269
|
+
usageLimit: this.options.core.getUsageLimit(state),
|
|
1211
1270
|
canUseFeature: access.allowed,
|
|
1212
1271
|
remainingUsage: access.remaining,
|
|
1213
1272
|
usageUsed: access.used,
|
|
@@ -1239,7 +1298,7 @@ class FigmaLicenseStatusManager {
|
|
|
1239
1298
|
message: '라이선스가 비활성화되었습니다.',
|
|
1240
1299
|
...state,
|
|
1241
1300
|
...summary,
|
|
1242
|
-
usageLimit: this.options.core.getUsageLimit(),
|
|
1301
|
+
usageLimit: this.options.core.getUsageLimit(state),
|
|
1243
1302
|
canUseFeature: access.allowed,
|
|
1244
1303
|
remainingUsage: access.remaining,
|
|
1245
1304
|
usageUsed: access.used,
|
|
@@ -1267,7 +1326,7 @@ class FigmaLicenseStatusManager {
|
|
|
1267
1326
|
type: this.messageTypes.updateStatus,
|
|
1268
1327
|
...state,
|
|
1269
1328
|
...summary,
|
|
1270
|
-
usageLimit: this.options.core.getUsageLimit(),
|
|
1329
|
+
usageLimit: this.options.core.getUsageLimit(state),
|
|
1271
1330
|
canUseFeature: access.allowed,
|
|
1272
1331
|
remainingUsage: access.remaining,
|
|
1273
1332
|
usageUsed: access.used,
|
|
@@ -1276,14 +1335,15 @@ class FigmaLicenseStatusManager {
|
|
|
1276
1335
|
usageReason: access.reason,
|
|
1277
1336
|
});
|
|
1278
1337
|
}
|
|
1279
|
-
async handleCheckFeatureAccess(featureKey) {
|
|
1338
|
+
async handleCheckFeatureAccess(featureKey, amount = 1) {
|
|
1280
1339
|
const state = await this.options.core.getStatus();
|
|
1281
|
-
const access = this.options.core.resolveUsageAccess(state, featureKey);
|
|
1340
|
+
const access = this.options.core.resolveUsageAccess(state, featureKey, amount);
|
|
1282
1341
|
const summary = await this.options.core.getLicenseSummary();
|
|
1283
1342
|
this.postMessage({
|
|
1284
|
-
type: this.
|
|
1343
|
+
type: this.messageTypes.featureAccessResult,
|
|
1285
1344
|
success: true,
|
|
1286
1345
|
featureKey,
|
|
1346
|
+
amount,
|
|
1287
1347
|
...summary,
|
|
1288
1348
|
...access,
|
|
1289
1349
|
});
|
|
@@ -1292,10 +1352,11 @@ class FigmaLicenseStatusManager {
|
|
|
1292
1352
|
const result = await this.options.core.consumeUsage(amount, featureKey);
|
|
1293
1353
|
const summary = await this.options.core.getLicenseSummary();
|
|
1294
1354
|
this.postMessage({
|
|
1295
|
-
type: this.
|
|
1296
|
-
success: result.
|
|
1355
|
+
type: this.messageTypes.usageConsumeResult,
|
|
1356
|
+
success: result.consumed,
|
|
1297
1357
|
featureKey,
|
|
1298
1358
|
amount,
|
|
1359
|
+
consumed: result.consumed,
|
|
1299
1360
|
...summary,
|
|
1300
1361
|
...result.access,
|
|
1301
1362
|
usageCount: result.state.usageCount,
|
|
@@ -1348,5 +1409,5 @@ function createFigmaLicenseStatusManager(options) {
|
|
|
1348
1409
|
});
|
|
1349
1410
|
}
|
|
1350
1411
|
|
|
1351
|
-
export { DesignbaseAccountAuthClient, DesignbaseAccountLicenseTransport, FetchLicenseTransport, FigmaLicenseStatusManager, FigmaNotifierAdapter, FigmaStorageAdapter, LicenseCore, LicenseCoreError, LicenseErrorCode, LicenseTransportHttpError, createFigmaLicenseStatusManager, defaultNormalizeDeactivateResponse, defaultNormalizeVerifyResponse, isActivationLimitReached, mapLicenseError };
|
|
1412
|
+
export { DEFAULT_LICENSE_MESSAGE_TYPES, DesignbaseAccountAuthClient, DesignbaseAccountLicenseTransport, FetchLicenseTransport, FigmaLicenseStatusManager, FigmaNotifierAdapter, FigmaStorageAdapter, LicenseCore, LicenseCoreError, LicenseErrorCode, LicenseTransportHttpError, createFigmaLicenseStatusManager, defaultNormalizeDeactivateResponse, defaultNormalizeVerifyResponse, isActivationLimitReached, mapLicenseError };
|
|
1352
1413
|
//# sourceMappingURL=index.esm.js.map
|