@licenseseat/js 0.1.0 โ†’ 0.2.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 CHANGED
@@ -1,168 +1,769 @@
1
- # ๐Ÿ’บ LicenseSeat - JavaScript SDK
1
+ # LicenseSeat JavaScript SDK
2
2
 
3
- Official JavaScript client for [LicenseSeat](https://licenseseat.com) โ€“ the simple, secure licensing platform for apps, games, and plugins.
3
+ Official JavaScript/TypeScript SDK for [LicenseSeat](https://licenseseat.com) โ€“ the simple, secure licensing platform for apps, games, and plugins.
4
4
 
5
- This SDK helps you integrate license activation, validation, offline caching, entitlement checks, and more into your JavaScript and browser-based apps.
5
+ [![CI](https://github.com/licenseseat/licenseseat-js/actions/workflows/ci.yml/badge.svg)](https://github.com/licenseseat/licenseseat-js/actions/workflows/ci.yml)
6
+ [![npm version](https://img.shields.io/npm/v/@licenseseat/js.svg)](https://www.npmjs.com/package/@licenseseat/js)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
8
 
7
9
  ---
8
10
 
9
- ## ๐Ÿš€ Installation
11
+ ## Features
10
12
 
11
- ```bash
12
- npm install @licenseseat/js
13
- ````
13
+ - **License activation & deactivation** โ€“ Activate licenses with automatic device fingerprinting
14
+ - **Online & offline validation** โ€“ Validate licenses with optional offline fallback
15
+ - **Entitlement checking** โ€“ Check feature access with `hasEntitlement()` and `checkEntitlement()`
16
+ - **Local caching** โ€“ Secure localStorage-based caching with clock tamper detection
17
+ - **Auto-retry with exponential backoff** โ€“ Resilient network handling
18
+ - **Event-driven architecture** โ€“ Subscribe to SDK lifecycle events
19
+ - **TypeScript support** โ€“ Full type definitions included (auto-generated from JSDoc)
20
+ - **Modern ESM package** โ€“ Native ES modules, tree-shakeable
21
+
22
+ ---
23
+
24
+ ## Installation
14
25
 
15
- Or via `yarn`:
26
+ ### npm / yarn / pnpm
16
27
 
17
28
  ```bash
29
+ # npm
30
+ npm install @licenseseat/js
31
+
32
+ # yarn
18
33
  yarn add @licenseseat/js
34
+
35
+ # pnpm
36
+ pnpm add @licenseseat/js
37
+ ```
38
+
39
+ ### CDN (Browser)
40
+
41
+ ```html
42
+ <!-- ESM via esm.sh -->
43
+ <script type="module">
44
+ import LicenseSeat from 'https://esm.sh/@licenseseat/js';
45
+
46
+ const sdk = new LicenseSeat({ apiKey: 'your-api-key' });
47
+ </script>
48
+
49
+ <!-- ESM via unpkg -->
50
+ <script type="module">
51
+ import LicenseSeat from 'https://unpkg.com/@licenseseat/js/dist/index.js';
52
+ </script>
53
+
54
+ <!-- ESM via jsDelivr -->
55
+ <script type="module">
56
+ import LicenseSeat from 'https://cdn.jsdelivr.net/npm/@licenseseat/js/dist/index.js';
57
+ </script>
19
58
  ```
20
59
 
21
60
  ---
22
61
 
23
- ## ๐Ÿงช Quickstart
62
+ ## Quick Start
24
63
 
25
- ```js
64
+ ### JavaScript (ESM)
65
+
66
+ ```javascript
26
67
  import LicenseSeat from '@licenseseat/js';
27
68
 
28
- const license = new LicenseSeat({
29
- publicKey: 'LS_PUBLIC_...',
30
- product: 'my-app-id',
69
+ // Create SDK instance
70
+ const sdk = new LicenseSeat({
71
+ apiKey: 'your-api-key',
31
72
  debug: true
32
73
  });
33
74
 
34
- // Activate a license key
35
- await license.activate('your-license-key');
75
+ // Activate a license
76
+ await sdk.activate('YOUR-LICENSE-KEY');
36
77
 
37
- // Check if the user is entitled to a feature
38
- if (license.hasEntitlement('pro')) {
39
- // unlock features
78
+ // Check entitlements (simple boolean)
79
+ if (sdk.hasEntitlement('pro')) {
80
+ // Enable pro features
40
81
  }
82
+
83
+ // Get current status
84
+ const status = sdk.getStatus();
85
+ console.log(status);
86
+ // { status: 'active', license: '...', entitlements: [...] }
41
87
  ```
42
88
 
43
- ---
89
+ ### TypeScript
44
90
 
45
- ## ๐ŸŒ Browser Support
91
+ ```typescript
92
+ import LicenseSeat, {
93
+ type LicenseSeatConfig,
94
+ type ValidationResult,
95
+ type EntitlementCheckResult,
96
+ type LicenseStatus
97
+ } from '@licenseseat/js';
46
98
 
47
- This SDK works natively in:
99
+ const config: LicenseSeatConfig = {
100
+ apiKey: 'your-api-key',
101
+ debug: true
102
+ };
103
+
104
+ const sdk = new LicenseSeat(config);
48
105
 
49
- * Modern browsers (`import` via ESM)
50
- * Any bundler (Vite, Webpack, Rollup)
51
- * Node.js (>= 18)
106
+ // Full type inference
107
+ const result: ValidationResult = await sdk.validateLicense('LICENSE-KEY');
108
+ const status: LicenseStatus = sdk.getStatus();
109
+ const hasPro: boolean = sdk.hasEntitlement('pro');
110
+ ```
52
111
 
53
- If you prefer `<script>` tag usage, you can use the [global bundle](#browser-global-usage) instead.
112
+ TypeScript users get full type support automatically โ€“ the package includes generated `.d.ts` declaration files.
54
113
 
55
114
  ---
56
115
 
57
- ## ๐Ÿ“ฆ Features
116
+ ## Configuration
58
117
 
59
- * ๐Ÿ” **License activation**
60
- * ๐Ÿ“ **Device fingerprinting**
61
- * ๐ŸŒ **Online & offline validation**
62
- * ๐ŸŽซ **Entitlements support**
63
- * ๐Ÿ“ฅ **Encrypted local caching**
64
- * ๐ŸŽฏ **Auto-retry on network failure**
65
- * ๐Ÿ“ก **Event emitters for reactive state**
66
- * โš™๏ธ **Fully ESM-compatible**
118
+ ```javascript
119
+ const sdk = new LicenseSeat({
120
+ // Required for authenticated operations
121
+ apiKey: 'your-api-key',
67
122
 
68
- ---
123
+ // API Configuration
124
+ apiBaseUrl: 'https://licenseseat.com/api', // Default
69
125
 
70
- ## ๐Ÿ“˜ API Reference
126
+ // Storage
127
+ storagePrefix: 'licenseseat_', // localStorage key prefix
71
128
 
72
- ### `new LicenseSeat(options)`
129
+ // Auto-Validation
130
+ autoValidateInterval: 3600000, // 1 hour (in ms)
131
+ autoInitialize: true, // Auto-validate cached license on init
73
132
 
74
- | Option | Type | Description |
75
- | ----------- | ------- | --------------------------------------------- |
76
- | `publicKey` | string | Your LicenseSeat public signing key |
77
- | `product` | string | Product identifier from LicenseSeat dashboard |
78
- | `debug` | boolean | Optional. Logs debug info to console |
133
+ // Offline Support
134
+ offlineFallbackEnabled: false, // Enable offline validation fallback
135
+ maxOfflineDays: 0, // Max days offline (0 = disabled)
136
+ offlineLicenseRefreshInterval: 259200000, // 72 hours
137
+ maxClockSkewMs: 300000, // 5 minutes
79
138
 
80
- ---
139
+ // Network
140
+ maxRetries: 3, // Retry attempts for failed requests
141
+ retryDelay: 1000, // Initial retry delay (ms)
142
+ networkRecheckInterval: 30000, // Check connectivity every 30s when offline
81
143
 
82
- ### `await license.activate(licenseKey)`
144
+ // Debug
145
+ debug: false // Enable console logging
146
+ });
147
+ ```
83
148
 
84
- Activates a license key and stores metadata.
149
+ ### Configuration Options
150
+
151
+ | Option | Type | Default | Description |
152
+ |--------|------|---------|-------------|
153
+ | `apiKey` | `string` | `null` | API key for authentication (required for most operations) |
154
+ | `apiBaseUrl` | `string` | `'https://licenseseat.com/api'` | API base URL |
155
+ | `storagePrefix` | `string` | `'licenseseat_'` | Prefix for localStorage keys |
156
+ | `autoValidateInterval` | `number` | `3600000` | Auto-validation interval in ms (1 hour) |
157
+ | `autoInitialize` | `boolean` | `true` | Auto-initialize and validate cached license |
158
+ | `offlineFallbackEnabled` | `boolean` | `false` | Enable offline validation on network errors |
159
+ | `maxOfflineDays` | `number` | `0` | Maximum days license works offline (0 = disabled) |
160
+ | `maxRetries` | `number` | `3` | Max retry attempts for failed API calls |
161
+ | `retryDelay` | `number` | `1000` | Initial retry delay in ms (exponential backoff) |
162
+ | `debug` | `boolean` | `false` | Enable debug logging to console |
85
163
 
86
164
  ---
87
165
 
88
- ### `license.hasEntitlement(entitlementName)`
166
+ ## API Reference
89
167
 
90
- Returns `true` if the current license includes the given entitlement.
168
+ ### Core Methods
91
169
 
92
- ---
170
+ #### `sdk.activate(licenseKey, options?)`
171
+
172
+ Activates a license key on this device.
173
+
174
+ ```javascript
175
+ const result = await sdk.activate('LICENSE-KEY', {
176
+ deviceIdentifier: 'custom-device-id', // Optional: auto-generated if not provided
177
+ softwareReleaseDate: '2024-01-15', // Optional: for version-aware licensing
178
+ metadata: { version: '1.0.0' } // Optional: custom metadata
179
+ });
180
+
181
+ console.log(result);
182
+ // {
183
+ // license_key: 'LICENSE-KEY',
184
+ // device_identifier: 'web-abc123-xyz',
185
+ // activated_at: '2024-01-15T10:30:00Z',
186
+ // activation: { ... }
187
+ // }
188
+ ```
189
+
190
+ #### `sdk.deactivate()`
191
+
192
+ Deactivates the current license and clears cached data.
93
193
 
94
- ### `await license.validate()`
194
+ ```javascript
195
+ await sdk.deactivate();
196
+ ```
197
+
198
+ #### `sdk.validateLicense(licenseKey, options?)`
199
+
200
+ Validates a license with the server.
201
+
202
+ ```javascript
203
+ const result = await sdk.validateLicense('LICENSE-KEY', {
204
+ deviceIdentifier: 'device-id', // Optional
205
+ productSlug: 'my-product' // Optional
206
+ });
207
+
208
+ console.log(result);
209
+ // {
210
+ // valid: true,
211
+ // active_entitlements: [
212
+ // { key: 'pro', name: 'Pro Features', expires_at: null },
213
+ // { key: 'beta', name: 'Beta Access', expires_at: '2024-12-31T23:59:59Z' }
214
+ // ]
215
+ // }
216
+ ```
217
+
218
+ ### Entitlement Methods
219
+
220
+ #### `sdk.hasEntitlement(key)`
95
221
 
96
- Force online validation of the current license (optional).
222
+ Check if an entitlement is active. Returns a simple boolean.
223
+
224
+ ```javascript
225
+ if (sdk.hasEntitlement('pro')) {
226
+ enableProFeatures();
227
+ }
228
+
229
+ if (sdk.hasEntitlement('beta')) {
230
+ showBetaUI();
231
+ }
232
+ ```
233
+
234
+ #### `sdk.checkEntitlement(key)`
235
+
236
+ Check entitlement with detailed information.
237
+
238
+ ```javascript
239
+ const result = sdk.checkEntitlement('pro');
240
+
241
+ if (result.active) {
242
+ console.log('Entitlement:', result.entitlement);
243
+ console.log('Expires:', result.entitlement.expires_at);
244
+ } else {
245
+ console.log('Reason:', result.reason);
246
+ // Possible reasons: 'no_license', 'not_found', 'expired'
247
+ }
248
+ ```
249
+
250
+ ### Status Methods
251
+
252
+ #### `sdk.getStatus()`
253
+
254
+ Get current license status.
255
+
256
+ ```javascript
257
+ const status = sdk.getStatus();
258
+
259
+ // Possible status values:
260
+ // - 'inactive': No license activated
261
+ // - 'pending': License pending validation
262
+ // - 'active': License valid (online)
263
+ // - 'invalid': License invalid
264
+ // - 'offline-valid': License valid (offline verification)
265
+ // - 'offline-invalid': License invalid (offline verification)
266
+
267
+ console.log(status);
268
+ // {
269
+ // status: 'active',
270
+ // license: 'LICENSE-KEY',
271
+ // device: 'web-abc123-xyz',
272
+ // activated_at: '2024-01-15T10:30:00Z',
273
+ // last_validated: '2024-01-15T11:30:00Z',
274
+ // entitlements: [...]
275
+ // }
276
+ ```
277
+
278
+ #### `sdk.testAuth()`
279
+
280
+ Test API authentication (useful for verifying API key).
281
+
282
+ ```javascript
283
+ try {
284
+ const result = await sdk.testAuth();
285
+ console.log('Authenticated:', result.authenticated);
286
+ } catch (error) {
287
+ console.error('Auth failed:', error);
288
+ }
289
+ ```
290
+
291
+ #### `sdk.reset()`
292
+
293
+ Clear all cached data and reset SDK state.
294
+
295
+ ```javascript
296
+ sdk.reset();
297
+ ```
298
+
299
+ #### `sdk.destroy()`
300
+
301
+ Destroy the SDK instance and release all resources. Call this when you no longer need the SDK to prevent memory leaks. After calling `destroy()`, the SDK instance should not be used.
302
+
303
+ ```javascript
304
+ // When unmounting a component or closing an app
305
+ sdk.destroy();
306
+ ```
307
+
308
+ #### `sdk.initialize()`
309
+
310
+ Manually initialize the SDK (only needed if `autoInitialize: false`).
311
+
312
+ ```javascript
313
+ const sdk = new LicenseSeat({
314
+ apiKey: 'key',
315
+ autoInitialize: false // Don't auto-initialize
316
+ });
317
+
318
+ // Later, when ready:
319
+ sdk.initialize();
320
+ ```
97
321
 
98
322
  ---
99
323
 
100
- ### `license.on(event, callback)`
324
+ ## Events
101
325
 
102
- Subscribe to SDK lifecycle events:
326
+ Subscribe to SDK lifecycle events for reactive UIs.
103
327
 
104
- ```js
105
- license.on('activated', (data) => {
328
+ ```javascript
329
+ // Subscribe
330
+ const unsubscribe = sdk.on('activation:success', (data) => {
106
331
  console.log('License activated:', data);
107
332
  });
108
333
 
109
- license.on('error', (err) => {
110
- console.error('LicenseSeat error:', err);
334
+ // Unsubscribe
335
+ unsubscribe();
336
+ // or
337
+ sdk.off('activation:success', handler);
338
+ ```
339
+
340
+ ### Available Events
341
+
342
+ | Event | Description | Data |
343
+ |-------|-------------|------|
344
+ | **Lifecycle** | | |
345
+ | `license:loaded` | Cached license loaded on init | `CachedLicense` |
346
+ | `sdk:reset` | SDK was reset | โ€“ |
347
+ | `sdk:destroyed` | SDK was destroyed | โ€“ |
348
+ | `sdk:error` | General SDK error | `{ message, error? }` |
349
+ | **Activation** | | |
350
+ | `activation:start` | Activation started | `{ licenseKey, deviceId }` |
351
+ | `activation:success` | Activation succeeded | `CachedLicense` |
352
+ | `activation:error` | Activation failed | `{ licenseKey, error }` |
353
+ | **Deactivation** | | |
354
+ | `deactivation:start` | Deactivation started | `CachedLicense` |
355
+ | `deactivation:success` | Deactivation succeeded | `Object` |
356
+ | `deactivation:error` | Deactivation failed | `{ error, license }` |
357
+ | **Validation** | | |
358
+ | `validation:start` | Validation started | `{ licenseKey }` |
359
+ | `validation:success` | Online validation succeeded | `ValidationResult` |
360
+ | `validation:failed` | Validation failed (invalid license) | `ValidationResult` |
361
+ | `validation:error` | Validation error (network, etc.) | `{ licenseKey, error }` |
362
+ | `validation:offline-success` | Offline validation succeeded | `ValidationResult` |
363
+ | `validation:offline-failed` | Offline validation failed | `ValidationResult` |
364
+ | `validation:auth-failed` | Auth failed during validation | `{ licenseKey, error, cached }` |
365
+ | **Auto-Validation** | | |
366
+ | `autovalidation:cycle` | Auto-validation scheduled | `{ nextRunAt: Date }` |
367
+ | `autovalidation:stopped` | Auto-validation stopped | โ€“ |
368
+ | **Network** | | |
369
+ | `network:online` | Network connectivity restored | โ€“ |
370
+ | `network:offline` | Network connectivity lost | `{ error }` |
371
+ | **Offline License** | | |
372
+ | `offlineLicense:fetching` | Fetching offline license | `{ licenseKey }` |
373
+ | `offlineLicense:fetched` | Offline license fetched | `{ licenseKey, data }` |
374
+ | `offlineLicense:fetchError` | Offline license fetch failed | `{ licenseKey, error }` |
375
+ | `offlineLicense:ready` | Offline assets synced | `{ kid, exp_at }` |
376
+ | `offlineLicense:verified` | Offline signature verified | `{ payload }` |
377
+ | `offlineLicense:verificationFailed` | Offline signature invalid | `{ payload }` |
378
+
379
+ ---
380
+
381
+ ## Singleton Pattern
382
+
383
+ For applications that need a shared SDK instance:
384
+
385
+ ```javascript
386
+ import { configure, getSharedInstance, resetSharedInstance } from '@licenseseat/js';
387
+
388
+ // Configure once at app startup
389
+ configure({ apiKey: 'your-key' });
390
+
391
+ // Use anywhere in your app
392
+ const sdk = getSharedInstance();
393
+ await sdk.activate('LICENSE-KEY');
394
+
395
+ // Reset if needed
396
+ resetSharedInstance();
397
+ ```
398
+
399
+ ---
400
+
401
+ ## Offline Support
402
+
403
+ The SDK supports offline license validation using cryptographically signed offline licenses (Ed25519).
404
+
405
+ ```javascript
406
+ const sdk = new LicenseSeat({
407
+ apiKey: 'your-key',
408
+ offlineFallbackEnabled: true, // Enable offline fallback
409
+ maxOfflineDays: 7 // Allow 7 days offline
111
410
  });
411
+
412
+ // After activation, offline assets are automatically synced
413
+ await sdk.activate('LICENSE-KEY');
414
+
415
+ // Later, even offline, validation will work using cached data
416
+ const result = await sdk.validateLicense('LICENSE-KEY');
417
+ if (result.offline) {
418
+ console.log('Validated offline');
419
+ }
112
420
  ```
113
421
 
114
- Supported events:
422
+ ### How Offline Validation Works
115
423
 
116
- * `activated`
117
- * `deactivated`
118
- * `error`
119
- * `offline`
120
- * `validated`
424
+ 1. On activation, the SDK fetches a signed offline license from the server
425
+ 2. The offline license contains the license data + Ed25519 signature
426
+ 3. When offline, the SDK verifies the signature locally
427
+ 4. Clock tamper detection prevents users from bypassing expiration
121
428
 
122
429
  ---
123
430
 
124
- ## ๐Ÿ’ป Browser Global Usage
431
+ ## Error Handling
432
+
433
+ The SDK exports custom error classes for precise error handling:
434
+
435
+ ```javascript
436
+ import LicenseSeat, {
437
+ APIError,
438
+ LicenseError,
439
+ ConfigurationError,
440
+ CryptoError
441
+ } from '@licenseseat/js';
442
+
443
+ try {
444
+ await sdk.activate('INVALID-KEY');
445
+ } catch (error) {
446
+ if (error instanceof APIError) {
447
+ console.log('HTTP Status:', error.status);
448
+ console.log('Response:', error.data);
449
+ } else if (error instanceof LicenseError) {
450
+ console.log('License error:', error.code);
451
+ }
452
+ }
453
+ ```
125
454
 
126
- To use without a build system:
455
+ ### Error Types
127
456
 
128
- ```html
129
- <script src="https://cdn.licenseseat.com/sdk/latest/index.global.js"></script>
130
- <script>
131
- const license = new LicenseSeat({
132
- publicKey: 'LS_PUBLIC_...',
133
- product: 'my-app-id'
134
- });
457
+ | Error | Description |
458
+ |-------|-------------|
459
+ | `APIError` | HTTP request failures (includes `status` and `data`) |
460
+ | `LicenseError` | License operation failures (includes `code`) |
461
+ | `ConfigurationError` | SDK misconfiguration |
462
+ | `CryptoError` | Cryptographic operation failures |
135
463
 
136
- license.activate('YOUR-LICENSE');
137
- </script>
464
+ ---
465
+
466
+ ## Browser Support
467
+
468
+ - **Modern browsers**: Chrome 80+, Firefox 75+, Safari 14+, Edge 80+
469
+ - **Bundlers**: Vite, Webpack, Rollup, esbuild, Parcel
470
+ - **Node.js**: 18+ (requires polyfills for `localStorage`, `document`)
471
+
472
+ ---
473
+
474
+ ## Usage Guide
475
+
476
+ ### For JavaScript Users
477
+
478
+ Simply import and use:
479
+
480
+ ```javascript
481
+ import LicenseSeat from '@licenseseat/js';
482
+
483
+ const sdk = new LicenseSeat({ apiKey: 'your-key' });
484
+ ```
485
+
486
+ ### For TypeScript Users
487
+
488
+ The package includes TypeScript declarations (`.d.ts` files) automatically. No additional `@types/` package needed.
489
+
490
+ ```typescript
491
+ import LicenseSeat from '@licenseseat/js';
492
+
493
+ // Types are automatically available
494
+ const sdk = new LicenseSeat({ apiKey: 'your-key' });
495
+
496
+ // Import specific types if needed
497
+ import type {
498
+ LicenseSeatConfig,
499
+ ValidationResult,
500
+ EntitlementCheckResult,
501
+ LicenseStatus,
502
+ Entitlement,
503
+ CachedLicense
504
+ } from '@licenseseat/js';
138
505
  ```
139
506
 
140
- *(CDN link optional โ€” you can also host your own global bundle.)*
507
+ ### For CDN/Browser Users
508
+
509
+ Use ES modules via CDN:
510
+
511
+ ```html
512
+ <!DOCTYPE html>
513
+ <html>
514
+ <head>
515
+ <title>LicenseSeat Demo</title>
516
+ </head>
517
+ <body>
518
+ <script type="module">
519
+ import LicenseSeat from 'https://esm.sh/@licenseseat/js';
520
+
521
+ const sdk = new LicenseSeat({
522
+ apiKey: 'your-api-key',
523
+ debug: true
524
+ });
525
+
526
+ // Check for existing license
527
+ const status = sdk.getStatus();
528
+ if (status.status === 'active') {
529
+ console.log('Already licensed!');
530
+ }
531
+
532
+ // Activate (example with user input)
533
+ document.getElementById('activate-btn').onclick = async () => {
534
+ const key = document.getElementById('license-key').value;
535
+ try {
536
+ await sdk.activate(key);
537
+ alert('License activated!');
538
+ } catch (e) {
539
+ alert('Activation failed: ' + e.message);
540
+ }
541
+ };
542
+ </script>
543
+
544
+ <input id="license-key" placeholder="Enter license key" />
545
+ <button id="activate-btn">Activate</button>
546
+ </body>
547
+ </html>
548
+ ```
141
549
 
142
550
  ---
143
551
 
144
- ## ๐Ÿ›  Development
552
+ ## Development
145
553
 
146
- Clone the SDK:
554
+ ### Setup
147
555
 
148
556
  ```bash
149
557
  git clone https://github.com/licenseseat/licenseseat-js.git
150
558
  cd licenseseat-js
151
559
  npm install
152
- npm run build
153
560
  ```
154
561
 
155
- This builds `dist/index.js` (ESM) and optionally `index.global.js` (IIFE).
562
+ ### Scripts
563
+
564
+ | Command | Description |
565
+ |---------|-------------|
566
+ | `npm run build` | Build JS bundle + TypeScript declarations |
567
+ | `npm run build:js` | Build JavaScript bundle only |
568
+ | `npm run build:types` | Generate TypeScript declarations |
569
+ | `npm run build:iife` | Build global/IIFE bundle |
570
+ | `npm run dev` | Watch mode for development |
571
+ | `npm test` | Run tests |
572
+ | `npm run test:watch` | Run tests in watch mode |
573
+ | `npm run test:coverage` | Run tests with coverage report |
574
+ | `npm run typecheck` | Type-check without emitting |
575
+
576
+ ### Project Structure
577
+
578
+ ```
579
+ licenseseat-js/
580
+ โ”œโ”€โ”€ src/
581
+ โ”‚ โ”œโ”€โ”€ index.js # Entry point, exports
582
+ โ”‚ โ”œโ”€โ”€ LicenseSeat.js # Main SDK class
583
+ โ”‚ โ”œโ”€โ”€ cache.js # LicenseCache (localStorage)
584
+ โ”‚ โ”œโ”€โ”€ errors.js # Error classes
585
+ โ”‚ โ”œโ”€โ”€ types.js # JSDoc type definitions
586
+ โ”‚ โ””โ”€โ”€ utils.js # Utility functions
587
+ โ”œโ”€โ”€ tests/
588
+ โ”‚ โ”œโ”€โ”€ setup.js # Test setup
589
+ โ”‚ โ”œโ”€โ”€ mocks/ # MSW handlers
590
+ โ”‚ โ”œโ”€โ”€ LicenseSeat.test.js
591
+ โ”‚ โ””โ”€โ”€ utils.test.js
592
+ โ”œโ”€โ”€ dist/ # Build output
593
+ โ”‚ โ”œโ”€โ”€ index.js # ESM bundle
594
+ โ”‚ โ””โ”€โ”€ types/ # TypeScript declarations
595
+ โ”œโ”€โ”€ package.json
596
+ โ”œโ”€โ”€ tsconfig.json
597
+ โ””โ”€โ”€ vitest.config.js
598
+ ```
599
+
600
+ ---
601
+
602
+ ## Publishing
603
+
604
+ ### Publishing to npm
605
+
606
+ 1. **Update version** in `package.json`:
607
+ ```bash
608
+ npm version patch # or minor, major
609
+ ```
610
+
611
+ 2. **Build the package**:
612
+ ```bash
613
+ npm run build
614
+ ```
615
+
616
+ 3. **Verify the build**:
617
+ ```bash
618
+ # Check what will be published
619
+ npm pack --dry-run
620
+
621
+ # Verify TypeScript types
622
+ ls dist/types/
623
+ ```
624
+
625
+ 4. **Publish**:
626
+ ```bash
627
+ # Login if needed
628
+ npm login
629
+
630
+ # Publish (public package)
631
+ npm publish --access public
632
+ ```
633
+
634
+ ### What Gets Published
635
+
636
+ The `files` field in `package.json` controls what's included:
637
+
638
+ ```json
639
+ {
640
+ "files": ["dist/", "src/"]
641
+ }
642
+ ```
643
+
644
+ Users receive:
645
+ - `dist/index.js` โ€“ ESM bundle (JavaScript)
646
+ - `dist/types/*.d.ts` โ€“ TypeScript declarations
647
+ - `src/*.js` โ€“ Source files (for debugging/reference)
648
+
649
+ ### Package Exports
650
+
651
+ ```json
652
+ {
653
+ "main": "dist/index.js",
654
+ "module": "dist/index.js",
655
+ "types": "dist/types/index.d.ts",
656
+ "exports": {
657
+ ".": {
658
+ "import": "./dist/index.js",
659
+ "types": "./dist/types/index.d.ts"
660
+ }
661
+ }
662
+ }
663
+ ```
664
+
665
+ This ensures:
666
+ - JavaScript users get `dist/index.js`
667
+ - TypeScript users get type definitions from `dist/types/index.d.ts`
668
+ - Both ESM `import` and bundlers work correctly
669
+
670
+ ### CDN Distribution
671
+
672
+ Once published to npm, the package is automatically available on CDNs:
673
+
674
+ | CDN | URL |
675
+ |-----|-----|
676
+ | **esm.sh** | `https://esm.sh/@licenseseat/js` |
677
+ | **unpkg** | `https://unpkg.com/@licenseseat/js/dist/index.js` |
678
+ | **jsDelivr** | `https://cdn.jsdelivr.net/npm/@licenseseat/js/dist/index.js` |
679
+ | **Skypack** | `https://cdn.skypack.dev/@licenseseat/js` |
680
+
681
+ **Version pinning** (recommended for production):
682
+ ```html
683
+ <script type="module">
684
+ import LicenseSeat from 'https://esm.sh/@licenseseat/js@0.2.0';
685
+ </script>
686
+ ```
687
+
688
+ ### Self-Hosting
689
+
690
+ To host the SDK yourself:
691
+
692
+ 1. Build the package:
693
+ ```bash
694
+ npm run build
695
+ ```
696
+
697
+ 2. Copy `dist/index.js` to your CDN/server
698
+
699
+ 3. Serve with correct MIME type (`application/javascript`) and CORS headers
700
+
701
+ ### Building an IIFE Bundle (Legacy Browsers)
702
+
703
+ For a global `LicenseSeat` variable (non-module script tags):
704
+
705
+ ```bash
706
+ npm run build:iife
707
+ ```
708
+
709
+ This creates `dist/index.global.js`:
710
+
711
+ ```html
712
+ <script src="/path/to/index.global.js"></script>
713
+ <script>
714
+ const sdk = new LicenseSeat({ apiKey: 'your-key' });
715
+ </script>
716
+ ```
717
+
718
+ ---
719
+
720
+ ## Versioning
721
+
722
+ This project follows [Semantic Versioning](https://semver.org/):
723
+
724
+ - **MAJOR** (1.0.0 โ†’ 2.0.0): Breaking changes
725
+ - **MINOR** (1.0.0 โ†’ 1.1.0): New features (backward compatible)
726
+ - **PATCH** (1.0.0 โ†’ 1.0.1): Bug fixes
727
+
728
+ ---
729
+
730
+ ## Migration from v0.1.x
731
+
732
+ ### Breaking Changes in v0.2.0
733
+
734
+ | Change | Before | After | Migration |
735
+ |--------|--------|-------|-----------|
736
+ | `apiBaseUrl` default | `/api` | `https://licenseseat.com/api` | Set `apiBaseUrl` explicitly if using a relative URL |
737
+ | `offlineFallbackEnabled` default | `true` | `false` | Set `offlineFallbackEnabled: true` if you need offline fallback |
738
+
739
+ ### New Features in v0.2.0
740
+
741
+ - `hasEntitlement(key)` method for simple boolean checks
742
+ - `autoInitialize` config option for lazy initialization
743
+ - Full TypeScript support with auto-generated `.d.ts` files
744
+ - Singleton pattern with `configure()` and `getSharedInstance()`
745
+ - New error classes: `LicenseError`, `ConfigurationError`, `CryptoError`
746
+
747
+ ---
748
+
749
+ ## License
750
+
751
+ MIT License โ€“ see [LICENSE](LICENSE) for details.
156
752
 
157
753
  ---
158
754
 
159
- ## ๐Ÿง  LicenseSeat Docs
755
+ ## Links
160
756
 
161
- * ๐Ÿ“˜ [Documentation](https://licenseseat.com/docs)
162
- * ๐Ÿ’ฌ [Contact support](https://licenseseat.com/contact)
757
+ - [LicenseSeat Website](https://licenseseat.com)
758
+ - [Documentation](https://licenseseat.com/docs)
759
+ - [API Reference](https://licenseseat.com/docs/api)
760
+ - [GitHub Repository](https://github.com/licenseseat/licenseseat-js)
761
+ - [npm Package](https://www.npmjs.com/package/@licenseseat/js)
762
+ - [Report Issues](https://github.com/licenseseat/licenseseat-js/issues)
163
763
 
164
764
  ---
165
765
 
166
- ## ๐Ÿชช License
766
+ ## Support
167
767
 
168
- MIT License ยฉ 2025 [LicenseSeat](https://licenseseat.com)
768
+ - **Email**: support@licenseseat.com
769
+ - **GitHub Issues**: [Report a bug](https://github.com/licenseseat/licenseseat-js/issues/new)