@corelicense/node 0.5.0 → 0.5.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.
@@ -0,0 +1,789 @@
1
+ # Tích hợp
2
+
3
+ Hướng dẫn chi tiết để tích hợp `@corelicense/node` vào hệ thống production — dành cho developer và AI agent generate code.
4
+
5
+ **Đọc trước:** [Cài đặt](./installation.md)
6
+
7
+ ---
8
+
9
+ ## Mục lục
10
+
11
+ 1. [Kiến trúc & nguyên tắc](#1-kiến-trúc--nguyên-tắc)
12
+ 2. [Vòng đời runtime](#2-vòng-đời-runtime)
13
+ 3. [Tích hợp Express (đầy đủ)](#3-tích-hợp-express-đầy-đủ)
14
+ 4. [Tích hợp Fastify](#4-tích-hợp-fastify)
15
+ 5. [Tích hợp NestJS](#5-tích-hợp-nestjs)
16
+ 6. [Feature gate, limit & quota](#6-feature-gate-limit--quota)
17
+ 7. [Worker & CLI guard](#7-worker--cli-guard)
18
+ 8. [Tích hợp sâu — Module Registry & DI](#8-tích-hợp-sâu--module-registry--di)
19
+ 9. [Pattern ứng dụng lớn](#9-pattern-ứng-dụng-lớn)
20
+ 10. [Xử lý lỗi](#10-xử-lý-lỗi)
21
+ 11. [Events & background sync](#11-events--background-sync)
22
+ 12. [Bảo mật & dữ liệu gửi server](#12-bảo-mật--dữ-liệu-gửi-server)
23
+ 13. [Production checklist](#13-production-checklist)
24
+ 14. [Troubleshooting](#14-troubleshooting)
25
+
26
+ ---
27
+
28
+ ## 1. Kiến trúc & nguyên tắc
29
+
30
+ ### Middleware không đủ
31
+
32
+ HTTP middleware chỉ chặn request. Attacker hoặc bug có thể gọi service/worker trực tiếp. **Tích hợp sâu** nghĩa là:
33
+
34
+ - Module/service **không được bind** nếu feature tắt trong policy
35
+ - Mỗi lần `get()` / `resolve()` **re-check** license (revoke chặn ngay)
36
+ - Worker/CLI có guard riêng
37
+ - Quota/limit enforce tại điểm tiêu thụ
38
+
39
+ ### Hai lớp bảo vệ
40
+
41
+ | Lớp | Cơ chế | Khi nào |
42
+ |-----|--------|---------|
43
+ | **HTTP** | `express()` / `fastifyPlugin()` / Nest guards | Mọi request API |
44
+ | **Runtime** | `requireFeature`, registry, DI, worker guard | Service, job, CLI |
45
+
46
+ ### Policy là nguồn sự thật
47
+
48
+ Policy token (JWT, EdDSA) chứa:
49
+
50
+ - `status`: `active` | `suspended` | `revoked` | ...
51
+ - `features`: map feature → boolean
52
+ - `limits`: số tối đa (users, workers, ...)
53
+ - `quotas` / usage local: tiêu thụ theo ngày
54
+
55
+ Admin đổi policy trên server → SDK refresh → runtime cập nhật. **Bind module/service** chỉ xảy ra lúc `bootstrap()` — đổi feature cần **restart app** để mount/unmount module; nhưng `get()`/`resolve()` vẫn chặn ngay khi revoke.
56
+
57
+ ---
58
+
59
+ ## 2. Vòng đời runtime
60
+
61
+ ```
62
+ constructor → boot() → [bootstrap()] → app chạy
63
+
64
+ background check (6h mặc định)
65
+
66
+ syncFromServer (mutex — không race)
67
+
68
+ requireActive() trên mọi gate quan trọng
69
+ ```
70
+
71
+ ### `boot()` — bắt buộc trước mọi thứ
72
+
73
+ ```ts
74
+ await license.boot();
75
+ ```
76
+
77
+ - Fail-closed v0.5: nếu license revoked/expired và không còn offline grace → **throw**, process nên exit
78
+ - Gọi `boot()` **một lần** khi process start (trước `listen()`)
79
+
80
+ ### Instance ID
81
+
82
+ ```ts
83
+ instance: {
84
+ strategy: 'install-id-domain-hash', // khuyến nghị production
85
+ domain: process.env.APP_DOMAIN, // ví dụ: app.customer.com
86
+ }
87
+ ```
88
+
89
+ - `install-id`: UUID lưu file trong `cachePath` — ổn định qua restart
90
+ - `domain-hash`: SHA-256 domain — phân biệt deploy theo domain
91
+ - `custom`: `instance.customId` — container/K8s pod id tùy chỉnh
92
+
93
+ Server đếm **max instances** theo license — mỗi cặp install-id + domain = một instance.
94
+
95
+ ---
96
+
97
+ ## 3. Tích hợp Express (đầy đủ)
98
+
99
+ ### App skeleton
100
+
101
+ ```ts
102
+ import express from 'express';
103
+ import { CoreLicense, loadOptionsFromEnv, registerLicensedRoutes } from '@corelicense/node';
104
+
105
+ const app = express();
106
+ app.use(express.json());
107
+
108
+ const license = new CoreLicense(
109
+ loadOptionsFromEnv({
110
+ cachePath: './storage/corelicense',
111
+ instance: { strategy: 'install-id-domain-hash', domain: process.env.APP_DOMAIN },
112
+ checkIntervalMs: 6 * 60 * 60 * 1000,
113
+ }),
114
+ );
115
+
116
+ async function main() {
117
+ await license.boot();
118
+
119
+ // --- Routes KHÔNG cần license ---
120
+ app.get('/health', (_req, res) => res.json({ ok: true }));
121
+ app.get('/license/status', async (_req, res) => {
122
+ res.json(await license.getStatus());
123
+ });
124
+
125
+ // --- Middleware license (toàn app) ---
126
+ app.use(
127
+ license.express({
128
+ allowPaths: ['/health', '/license/status', '/license/reactivate'],
129
+ mode: 'block',
130
+ }),
131
+ );
132
+
133
+ // --- Deep integration (tùy chọn) ---
134
+ const { modules, services, skipped } = await license.bootstrap({
135
+ modules: [
136
+ {
137
+ name: 'automation',
138
+ feature: 'automation',
139
+ factory: ({ license }) => new AutomationModule(license),
140
+ },
141
+ {
142
+ name: 'export',
143
+ feature: 'export',
144
+ factory: ({ license }) => new ExportModule(license),
145
+ },
146
+ ],
147
+ services: [
148
+ {
149
+ name: 'ExportService',
150
+ feature: 'export',
151
+ singleton: true,
152
+ factory: ({ license }) => new ExportService(license),
153
+ },
154
+ ],
155
+ });
156
+
157
+ console.log('Skipped (feature off):', skipped);
158
+
159
+ // Mount router CHỈ khi module đã registered
160
+ registerLicensedRoutes(app, modules, [
161
+ { path: '/api/automation', module: 'automation', router: automationRouter },
162
+ { path: '/api/export', module: 'export', router: exportRouter },
163
+ ]);
164
+
165
+ // --- Route thường (đã qua middleware requireActive) ---
166
+ app.get('/api/me', async (_req, res) => {
167
+ await license.requireFeature('dashboard');
168
+ res.json({ ok: true });
169
+ });
170
+
171
+ app.listen(process.env.PORT ?? 3000);
172
+ }
173
+
174
+ main().catch((err) => {
175
+ console.error('Boot failed:', err);
176
+ process.exit(1);
177
+ });
178
+ ```
179
+
180
+ ### `allowPaths`
181
+
182
+ Exact match `req.path`. Luôn whitelist:
183
+
184
+ - `/health` — load balancer
185
+ - `/license/status` — ops dashboard
186
+ - `/license/reactivate` — flow kích hoạt lại (nếu có)
187
+
188
+ ### Middleware response 403
189
+
190
+ ```json
191
+ {
192
+ "ok": false,
193
+ "code": "LICENSE_REVOKED",
194
+ "source": "core-license.requireActive",
195
+ "message": "..."
196
+ }
197
+ ```
198
+
199
+ Dùng `error.toClientPayload()` — không leak stack trace.
200
+
201
+ ### `registerLicensedRoutes`
202
+
203
+ - Chỉ `app.use(path, router)` nếu `modules.has(name)`
204
+ - Feature off → route **không tồn tại** (404), không phải 403 — khó discover API bị khóa
205
+
206
+ ---
207
+
208
+ ## 4. Tích hợp Fastify
209
+
210
+ ```ts
211
+ import Fastify from 'fastify';
212
+ import { CoreLicense, loadOptionsFromEnv } from '@corelicense/node';
213
+
214
+ const app = Fastify({ logger: true });
215
+
216
+ const license = new CoreLicense(loadOptionsFromEnv({ cachePath: './storage/corelicense' }));
217
+
218
+ async function main() {
219
+ await license.boot();
220
+
221
+ app.get('/health', async () => ({ ok: true }));
222
+
223
+ await app.register(
224
+ license.fastifyPlugin({
225
+ allowPaths: ['/health', '/license/status'],
226
+ }),
227
+ );
228
+
229
+ app.get('/api/data', async () => {
230
+ await license.requireFeature('api');
231
+ return { data: [] };
232
+ });
233
+
234
+ await app.listen({ port: 3000, host: '0.0.0.0' });
235
+ }
236
+
237
+ main().catch((err) => {
238
+ app.log.error(err);
239
+ process.exit(1);
240
+ });
241
+ ```
242
+
243
+ Import trực tiếp:
244
+
245
+ ```ts
246
+ import { createFastifyPlugin } from '@corelicense/node/fastify';
247
+ await app.register(createFastifyPlugin(license, { allowPaths: ['/health'] }));
248
+ ```
249
+
250
+ ---
251
+
252
+ ## 5. Tích hợp NestJS
253
+
254
+ ### `AppModule`
255
+
256
+ ```ts
257
+ // app.module.ts
258
+ import { Module } from '@nestjs/common';
259
+ import { CoreLicenseModule } from '@corelicense/node/nestjs';
260
+ import { loadOptionsFromEnv } from '@corelicense/node';
261
+ import { AutomationModule } from './automation/automation.module.js';
262
+
263
+ @Module({
264
+ imports: [
265
+ CoreLicenseModule.forRoot({
266
+ ...loadOptionsFromEnv({
267
+ cachePath: './storage/corelicense',
268
+ instance: { strategy: 'install-id-domain-hash', domain: process.env.APP_DOMAIN },
269
+ }),
270
+ }),
271
+ AutomationModule,
272
+ ],
273
+ })
274
+ export class AppModule {}
275
+ ```
276
+
277
+ `CoreLicenseModule.forRoot()`:
278
+
279
+ - Tạo `CoreLicense`, gọi `boot()` trong factory
280
+ - Đăng ký `CoreLicenseGuard`, `CoreLicenseFeatureGuard`
281
+ - Đăng ký `CoreLicenseExceptionFilter` → HTTP 403 + `toClientPayload()`
282
+
283
+ ### Controller — license active
284
+
285
+ ```ts
286
+ import { Controller, Get, UseGuards } from '@nestjs/common';
287
+ import { CoreLicense } from '@corelicense/node';
288
+ import { CoreLicenseGuard } from '@corelicense/node/nestjs';
289
+
290
+ @UseGuards(CoreLicenseGuard)
291
+ @Controller('api')
292
+ export class ApiController {
293
+ constructor(private readonly license: CoreLicense) {}
294
+
295
+ @Get('status')
296
+ async status() {
297
+ return this.license.getStatus();
298
+ }
299
+ }
300
+ ```
301
+
302
+ ### Controller — feature cụ thể
303
+
304
+ ```ts
305
+ import {
306
+ Controller,
307
+ Get,
308
+ UseGuards,
309
+ } from '@nestjs/common';
310
+ import {
311
+ CoreLicenseGuard,
312
+ CoreLicenseFeatureGuard,
313
+ RequireFeature,
314
+ } from '@corelicense/node/nestjs';
315
+
316
+ @RequireFeature('automation')
317
+ @UseGuards(CoreLicenseGuard, CoreLicenseFeatureGuard)
318
+ @Controller('automation')
319
+ export class AutomationController {
320
+ @Get()
321
+ list() {
322
+ return { items: [] };
323
+ }
324
+ }
325
+ ```
326
+
327
+ ### Health controller (không guard)
328
+
329
+ ```ts
330
+ @Controller('health')
331
+ export class HealthController {
332
+ @Get()
333
+ health() {
334
+ return { ok: true };
335
+ }
336
+ }
337
+ ```
338
+
339
+ Đặt `HealthController` **không** dùng `CoreLicenseGuard` — hoặc dùng global guard với `@SetMetadata` skip (tùy app).
340
+
341
+ ### Inject `CoreLicense` vào service
342
+
343
+ ```ts
344
+ import { Injectable } from '@nestjs/common';
345
+ import { CoreLicense, LicensedService } from '@corelicense/node';
346
+
347
+ @Injectable()
348
+ export class ExportService extends LicensedService {
349
+ constructor(license: CoreLicense) {
350
+ super(license);
351
+ }
352
+
353
+ async exportUsers() {
354
+ await this.require('export.users');
355
+ await this.license.consumeQuota('export.users', 1);
356
+ // business logic...
357
+ }
358
+ }
359
+ ```
360
+
361
+ ---
362
+
363
+ ## 6. Feature gate, limit & quota
364
+
365
+ ### Feature
366
+
367
+ ```ts
368
+ await license.requireFeature('automation');
369
+ await license.requireFeature('export.pdf');
370
+ await license.requireFeature('worker.video');
371
+ ```
372
+
373
+ Policy `features` ví dụ:
374
+
375
+ ```json
376
+ {
377
+ "automation": true,
378
+ "export": true,
379
+ "export.pdf": false,
380
+ "worker.video": true
381
+ }
382
+ ```
383
+
384
+ `requireFeature` → audit log `feature_allowed`. Throw `FeatureDisabledError` (`FEATURE_DISABLED`).
385
+
386
+ ### Limit (số hiện tại vs trần policy)
387
+
388
+ ```ts
389
+ const running = await jobQueue.countActive();
390
+ await license.checkLimit('maxConcurrentJobs', running);
391
+
392
+ const userCount = await db.users.count();
393
+ await license.checkLimit('maxUsers', userCount);
394
+ ```
395
+
396
+ Throw khi `currentValue >= limit` trong policy.
397
+
398
+ ### Quota (tiêu thụ tích lũy theo ngày, local cache)
399
+
400
+ ```ts
401
+ await license.consumeQuota('export.daily', 1);
402
+ await license.consumeQuota('api.calls', 10);
403
+ ```
404
+
405
+ - Usage lưu `usage.json` trong `cachePath`
406
+ - Reset theo ngày (UTC date string)
407
+ - `consumeQuota` / `checkLimit` / `getPolicy` đều gọi `requireActive()` trước
408
+
409
+ ### Runtime config
410
+
411
+ ```ts
412
+ const runtime = await license.getRuntimeConfig();
413
+ // {
414
+ // status: 'active',
415
+ // modules: { dashboard: true, automation: false, ... },
416
+ // limits: { maxWorkers: 2, maxUsers: 100 },
417
+ // policyVersion: 'v3',
418
+ // workers: { ... } // nếu policy định nghĩa
419
+ // }
420
+ ```
421
+
422
+ Dùng để **điều kiện hóa UI** hoặc config worker count — không thay thế `requireFeature` cho security.
423
+
424
+ ---
425
+
426
+ ## 7. Worker & CLI guard
427
+
428
+ ### BullMQ / queue worker
429
+
430
+ ```ts
431
+ import { Worker } from 'bullmq';
432
+
433
+ await license.boot();
434
+ await license.guardWorker('main-worker');
435
+
436
+ const worker = new Worker('jobs', async (job) => {
437
+ await license.requireFeature(`job.${job.name}`);
438
+ await license.consumeQuota(`job.${job.name}`, 1);
439
+ return processJob(job);
440
+ });
441
+ ```
442
+
443
+ `guardWorker(name)` kiểm tra policy cho phép worker name — throw `WORKER_NOT_ALLOWED`.
444
+
445
+ ### Cron / CLI
446
+
447
+ ```ts
448
+ async function main() {
449
+ await license.boot();
450
+ await license.guardCommand('nightly-export');
451
+ await runExport();
452
+ }
453
+
454
+ main().catch(console.error);
455
+ ```
456
+
457
+ Throw `COMMAND_NOT_ALLOWED` nếu feature/command không licensed.
458
+
459
+ ---
460
+
461
+ ## 8. Tích hợp sâu — Module Registry & DI
462
+
463
+ ### Khi nào dùng
464
+
465
+ - App có nhiều module lớn (automation, export, AI, ...)
466
+ - Muốn **không load code** module bị khóa license
467
+ - Muốn mount route / register provider có điều kiện
468
+
469
+ ### `bootstrap()` — một lần sau `boot()`
470
+
471
+ ```ts
472
+ const result = await license.bootstrap({
473
+ modules: ModuleDefinition[],
474
+ services: ServiceDefinition[],
475
+ });
476
+
477
+ // result.modules → LicensedModuleRegistry
478
+ // result.services → LicensedServiceContainer
479
+ // result.skipped → [{ name, feature, reason: 'feature_disabled' }]
480
+ ```
481
+
482
+ ### `ModuleDefinition`
483
+
484
+ ```ts
485
+ {
486
+ name: 'automation', // unique id
487
+ feature: 'automation', // key trong policy.features
488
+ factory: ({ license, runtime }) => new AutomationModule(license),
489
+ }
490
+ ```
491
+
492
+ ### `ServiceDefinition`
493
+
494
+ ```ts
495
+ {
496
+ name: 'ExportService',
497
+ feature: 'export',
498
+ singleton: true, // default true — factory chạy 1 lần
499
+ factory: ({ license }) => new ExportService(license),
500
+ }
501
+ ```
502
+
503
+ ### `modules.get(name)`
504
+
505
+ ```ts
506
+ const automation = await modules.get<AutomationModule>('automation');
507
+ await automation.runJob(payload);
508
+ ```
509
+
510
+ - Module chưa registered → `ModuleNotRegisteredError`
511
+ - Feature off lúc bootstrap → không registered → `ModuleNotLicensedError`
512
+ - **Mỗi `get()`** gọi `requireFeature` — revoke suspend chặn ngay
513
+
514
+ ### `services.resolve(name)`
515
+
516
+ ```ts
517
+ const svc = await services.resolve<ExportService>('ExportService');
518
+ await svc.exportUsers();
519
+ ```
520
+
521
+ - Singleton: instance cache nội bộ, nhưng **mỗi `resolve()` re-check license**
522
+ - Re-check **không** ghi audit (khác `license.requireFeature()`)
523
+
524
+ ### Tự tạo registry (advanced)
525
+
526
+ ```ts
527
+ const modules = license.createLicensedRegistry();
528
+ const services = license.createLicensedContainer();
529
+
530
+ const ctx = {
531
+ license,
532
+ runtime: await license.getRuntimeConfig(),
533
+ };
534
+
535
+ await modules.bootstrap(definitions, ctx);
536
+ ```
537
+
538
+ ### `LicensedService` base class
539
+
540
+ ```ts
541
+ import { LicensedService, CoreLicense } from '@corelicense/node';
542
+
543
+ export class ExportService extends LicensedService {
544
+ constructor(license: CoreLicense) {
545
+ super(license);
546
+ }
547
+
548
+ async exportUsers() {
549
+ await this.require('export.users');
550
+ await this.license.consumeQuota('export.users', 1);
551
+ // ...
552
+ }
553
+ }
554
+ ```
555
+
556
+ ---
557
+
558
+ ## 9. Pattern ứng dụng lớn
559
+
560
+ ### Cấu trúc thư mục gợi ý
561
+
562
+ ```
563
+ src/
564
+ bootstrap/
565
+ license.ts # khởi tạo CoreLicense + boot()
566
+ modules.ts # định nghĩa bootstrap modules/services
567
+ modules/
568
+ automation/
569
+ automation.module.ts
570
+ automation.router.ts
571
+ export/
572
+ export.service.ts
573
+ app.ts # express + registerLicensedRoutes
574
+ ```
575
+
576
+ ### `bootstrap/license.ts`
577
+
578
+ ```ts
579
+ import { CoreLicense, loadOptionsFromEnv } from '@corelicense/node';
580
+
581
+ let license: CoreLicense;
582
+
583
+ export async function initLicense() {
584
+ license = new CoreLicense(loadOptionsFromEnv({ cachePath: './storage/corelicense' }));
585
+ await license.boot();
586
+ return license;
587
+ }
588
+
589
+ export function getLicense() {
590
+ if (!license) throw new Error('License not initialized');
591
+ return license;
592
+ }
593
+ ```
594
+
595
+ ### `bootstrap/modules.ts`
596
+
597
+ ```ts
598
+ import type { BootstrapResult } from '@corelicense/node';
599
+ import { getLicense } from './license.js';
600
+ import { AutomationModule } from '../modules/automation/automation.module.js';
601
+ import { ExportService } from '../modules/export/export.service.js';
602
+
603
+ export async function bootstrapApp(): Promise<BootstrapResult> {
604
+ return getLicense().bootstrap({
605
+ modules: [
606
+ { name: 'automation', feature: 'automation', factory: ({ license }) => new AutomationModule(license) },
607
+ ],
608
+ services: [
609
+ { name: 'ExportService', feature: 'export', factory: ({ license }) => new ExportService(license) },
610
+ ],
611
+ });
612
+ }
613
+ ```
614
+
615
+ ### Graceful shutdown
616
+
617
+ ```ts
618
+ process.on('SIGTERM', () => {
619
+ license.stopBackgroundCheck();
620
+ process.exit(0);
621
+ });
622
+ ```
623
+
624
+ ### Docker
625
+
626
+ ```dockerfile
627
+ ENV CORELICENSE_KEY=...
628
+ ENV CORELICENSE_PRODUCT_ID=...
629
+ ENV APP_DOMAIN=app.customer.com
630
+ VOLUME ["/app/storage/corelicense"]
631
+ ```
632
+
633
+ Persist `cachePath` — mất volume = mất install-id → tính instance mới.
634
+
635
+ ### Multi-tenant
636
+
637
+ - **Một license / một deploy** (khuyến nghị): mỗi customer một deployment + key riêng
638
+ - Không share một `CoreLicense` instance cho nhiều tenant — instance ID và policy gắn một license
639
+
640
+ ---
641
+
642
+ ## 10. Xử lý lỗi
643
+
644
+ ### Type guard
645
+
646
+ ```ts
647
+ import {
648
+ isLicenseError,
649
+ formatLicenseError,
650
+ LicenseErrorCode,
651
+ } from '@corelicense/node';
652
+
653
+ try {
654
+ await license.boot();
655
+ } catch (error) {
656
+ if (isLicenseError(error)) {
657
+ console.error(error.code, error.source, error.message);
658
+ // CONFIG_MISSING_ENV, LICENSE_REVOKED, OFFLINE_GRACE_EXPIRED, ...
659
+ }
660
+ throw error;
661
+ }
662
+ ```
663
+
664
+ ### HTTP API
665
+
666
+ ```ts
667
+ if (isLicenseError(error)) {
668
+ res.status(403).json(error.toClientPayload());
669
+ }
670
+ ```
671
+
672
+ ### Mã lỗi thường gặp
673
+
674
+ | Code | Ý nghĩa |
675
+ |------|---------|
676
+ | `CONFIG_MISSING_ENV` | Thiếu `CORELICENSE_KEY` / `CORELICENSE_PRODUCT_ID` |
677
+ | `LICENSE_REVOKED` | Key bị revoke |
678
+ | `LICENSE_SUSPENDED` | Tạm ngưng |
679
+ | `LICENSE_EXPIRED` | Hết hạn |
680
+ | `OFFLINE_GRACE_EXPIRED` | Server unreachable quá lâu |
681
+ | `FEATURE_DISABLED` | Feature không trong policy |
682
+ | `MODULE_NOT_LICENSED` | Module không bind / feature off |
683
+ | `QUOTA_EXCEEDED` | Vượt quota ngày |
684
+ | `MAX_INSTANCES` | Vượt số instance |
685
+ | `INSTANCE_BLOCKED` | Instance bị block trên admin |
686
+ | `POLICY_SIGNATURE_ERROR` | Token không verify được |
687
+
688
+ Xem đầy đủ: [API Reference — Error codes](./api-reference.md#error-codes)
689
+
690
+ ---
691
+
692
+ ## 11. Events & background sync
693
+
694
+ ### Event bus
695
+
696
+ ```ts
697
+ license.on('licenseRevoked', (payload) => {
698
+ console.error('License revoked', payload);
699
+ process.exit(1);
700
+ });
701
+
702
+ license.on('policyUpdated', (payload) => {
703
+ console.log('Policy updated', payload);
704
+ });
705
+
706
+ license.on('offlineMode', (payload) => {
707
+ console.warn('Offline mode', payload);
708
+ });
709
+ ```
710
+
711
+ Events: `statusChanged`, `policyUpdated`, `licenseRevoked`, `licenseSuspended`, `offlineMode`
712
+
713
+ ### Background check
714
+
715
+ `boot()` tự start nếu `checkIntervalMs > 0`. Thủ công:
716
+
717
+ ```ts
718
+ license.startBackgroundCheck();
719
+ license.stopBackgroundCheck();
720
+ ```
721
+
722
+ `syncFromServer` có **mutex** — parallel refresh không race.
723
+
724
+ ### Manual refresh
725
+
726
+ ```ts
727
+ await license.refresh();
728
+ ```
729
+
730
+ Endpoint `refresh` trên License Server.
731
+
732
+ ---
733
+
734
+ ## 12. Bảo mật & dữ liệu gửi server
735
+
736
+ SDK **chỉ gửi**:
737
+
738
+ - `productId`, `licenseKey`, `instanceId`
739
+ - SDK metadata (name, version, runtime)
740
+ - `domainHash` (hash, không plain domain nếu cấu hình đúng)
741
+ - `currentPolicyHash` (khi check)
742
+
743
+ **Không gửi:** database, user data, business logs.
744
+
745
+ ### apiUrl pin (v0.5)
746
+
747
+ Server có thể trả `apiUrl` trong response. SDK **chỉ chấp nhận** URL trong whitelist (trusted apiUrl, env, embedded key). Redirect độc hại bị reject + audit `api_url_redirect_rejected`.
748
+
749
+ ### Public key
750
+
751
+ - Mặc định: fetch `GET /v1/public/products/{productId}/keys`
752
+ - Enterprise: pin `CORELICENSE_PUBLIC_KEY`
753
+
754
+ ---
755
+
756
+ ## 13. Production checklist
757
+
758
+ - [ ] `CORELICENSE_KEY` + `CORELICENSE_PRODUCT_ID` trong env (secret manager)
759
+ - [ ] `APP_DOMAIN` / `instance.domain` set đúng
760
+ - [ ] `cachePath` persist (volume)
761
+ - [ ] `boot()` fail → `process.exit(1)` — không start app “half-licensed”
762
+ - [ ] `/health` trong `allowPaths`
763
+ - [ ] License Server: `SDK_PUBLIC_URL=https://api.corelicense.net`
764
+ - [ ] Policy signer configured trên Go API
765
+ - [ ] Log audit path có rotate
766
+ - [ ] Test revoke: admin revoke → app chặn trong vòng refresh / ngay tại `requireFeature`
767
+
768
+ ---
769
+
770
+ ## 14. Troubleshooting
771
+
772
+ | Triệu chứng | Nguyên nhân | Xử lý |
773
+ |-------------|-------------|--------|
774
+ | `CONFIG_MISSING_ENV` | Thiếu env | Set `CORELICENSE_*` |
775
+ | `boot()` fail offline | Grace hết | Kết nối server hoặc tạm `offlineGrace: false` chỉ khi hiểu rủi ro |
776
+ | `MAX_INSTANCES` | Quá số instance | Xóa instance cũ trên admin hoặc tăng limit |
777
+ | `POLICY_SIGNATURE_ERROR` | Key rotation / sai pin | Cập nhật public key hoặc bỏ pin |
778
+ | Middleware 403 mọi route | Thiếu `allowPaths` | Thêm `/health`, ... |
779
+ | Module 404 | Feature off | Đúng thiết kế — `registerLicensedRoutes` skip |
780
+ | Nest 403 không JSON chuẩn | Thiếu filter | Dùng `CoreLicenseModule.forRoot()` (đã có filter) |
781
+
782
+ ---
783
+
784
+ ## Liên kết
785
+
786
+ - [Cài đặt](./installation.md)
787
+ - [API Reference](./api-reference.md)
788
+ - [Client API (License Server)](https://corelicense.net/docs/client-api)
789
+ - [README](../README.md)