@sidub-inc/licensing-client 1.5.57 → 1.5.72
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 +65 -99
- package/dist/{index.js → index.cjs} +550 -47
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +179 -13
- package/dist/index.esm.js +550 -48
- package/dist/index.esm.js.map +1 -1
- package/docs/MIGRATION.md +133 -0
- package/docs/REACT_GUIDE.md +45 -0
- package/package.json +14 -2
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install @sidub-inc/licensing-client
|
|
|
11
11
|
**Requirements:**
|
|
12
12
|
- Modern browsers with Web Crypto API support (Chrome, Firefox, Safari, Edge)
|
|
13
13
|
- Node.js 18+ (or polyfill for `fetch` and `crypto.subtle`)
|
|
14
|
-
- React 16.8+ (optional, for
|
|
14
|
+
- React 16.8+ (optional, for `LicensingProvider` / `useLicensingContext`)
|
|
15
15
|
|
|
16
16
|
## Configuration
|
|
17
17
|
|
|
@@ -136,13 +136,13 @@ const result = await client.pollCheckoutResult(session.sessionId, {
|
|
|
136
136
|
|
|
137
137
|
### 5. Report Access Check (Telemetry)
|
|
138
138
|
|
|
139
|
+
Telemetry-only: the event is always sent non-billable with a zero amount. Billable
|
|
140
|
+
metering flows exclusively through `performOperation()`.
|
|
141
|
+
|
|
139
142
|
```typescript
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
IsBillable: true,
|
|
144
|
-
Amount: 1
|
|
145
|
-
});
|
|
143
|
+
// All parameters optional — defaults come from the resolved context/config.
|
|
144
|
+
await client.reportAccessCheck(); // configured license + feature context
|
|
145
|
+
await client.reportAccessCheck('license-uuid', 'api-calls'); // explicit license + feature key
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
## Authorization Caching
|
|
@@ -230,9 +230,9 @@ const current = state.getConsumption(); // 2 (entries older than 60s are pruned)
|
|
|
230
230
|
`RateLimitAssertion` consults local feature state before server-reported consumption:
|
|
231
231
|
|
|
232
232
|
```typescript
|
|
233
|
-
//
|
|
234
|
-
client.setFeatureState('api-calls', state);
|
|
235
|
-
const stored = client.getFeatureState('api-calls');
|
|
233
|
+
// Feature state is stored per license + feature key
|
|
234
|
+
client.setFeatureState('license-uuid', 'api-calls', state);
|
|
235
|
+
const stored = client.getFeatureState('license-uuid', 'api-calls');
|
|
236
236
|
```
|
|
237
237
|
|
|
238
238
|
## Assertions
|
|
@@ -259,17 +259,34 @@ const hasAccess = client.assertLicense(assertion, authorization);
|
|
|
259
259
|
|
|
260
260
|
### RateLimitAssertion
|
|
261
261
|
|
|
262
|
+
Rate-limit assertions do NOT return `false` when the limit is exceeded — they **throw
|
|
263
|
+
`RateLimitError`** (and emit an unconditional `console.warn`), matching the .NET SDK's
|
|
264
|
+
`RateLimitException` contract. Handle the violation with try/catch:
|
|
265
|
+
|
|
262
266
|
```typescript
|
|
263
|
-
import { RateLimitAssertion } from '@sidub-inc/licensing-client';
|
|
267
|
+
import { RateLimitAssertion, RateLimitError } from '@sidub-inc/licensing-client';
|
|
264
268
|
|
|
265
269
|
const assertion = RateLimitAssertion.create({
|
|
266
270
|
featureId: 'api-calls',
|
|
267
271
|
rateLimit: 1000,
|
|
268
272
|
currentConsumption: 500
|
|
269
273
|
});
|
|
270
|
-
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
client.assertLicense(assertion, authorization); // true when within the limit
|
|
277
|
+
performTheRateLimitedWork();
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (error instanceof RateLimitError) {
|
|
280
|
+
showUpgradePrompt(); // limit exhausted — the throw IS the enforcement signal
|
|
281
|
+
} else {
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
271
285
|
```
|
|
272
286
|
|
|
287
|
+
The limit is inclusive: consumption equal to `rateLimit` still passes; the throw fires
|
|
288
|
+
once consumption exceeds it.
|
|
289
|
+
|
|
273
290
|
### Composite Assertions
|
|
274
291
|
|
|
275
292
|
```typescript
|
|
@@ -313,89 +330,37 @@ function App() {
|
|
|
313
330
|
}
|
|
314
331
|
```
|
|
315
332
|
|
|
316
|
-
###
|
|
317
|
-
|
|
318
|
-
```tsx
|
|
319
|
-
import { useLicenseAuthorization } from '@sidub-inc/licensing-client';
|
|
320
|
-
|
|
321
|
-
function LicensedComponent() {
|
|
322
|
-
const {
|
|
323
|
-
authorization, // LicenseAuthorization | null
|
|
324
|
-
loading, // boolean
|
|
325
|
-
error, // LicensingError | null
|
|
326
|
-
signatureValidated, // boolean
|
|
327
|
-
fetchAuthorization, // (licenseId?: string) => Promise<LicenseAuthorization>
|
|
328
|
-
clearAuthorization // () => void
|
|
329
|
-
} = useLicenseAuthorization();
|
|
330
|
-
|
|
331
|
-
if (loading) return <div>Loading...</div>;
|
|
332
|
-
if (error) return <div>Error: {error.message}</div>;
|
|
333
|
-
if (!authorization) return null;
|
|
334
|
-
|
|
335
|
-
return <div>License loaded {signatureValidated && '✓'}</div>;
|
|
336
|
-
}
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
### useAssertion
|
|
340
|
-
|
|
341
|
-
```tsx
|
|
342
|
-
import { useLicenseAuthorization, useAssertion, ServiceAccessAssertion, ServiceAccessLevel } from '@sidub-inc/licensing-client';
|
|
343
|
-
|
|
344
|
-
function FeatureGate() {
|
|
345
|
-
const { authorization } = useLicenseAuthorization();
|
|
346
|
-
const assertion = ServiceAccessAssertion.create('premium', ServiceAccessLevel.Allowed);
|
|
347
|
-
const hasPremium = useAssertion(assertion, authorization);
|
|
348
|
-
|
|
349
|
-
return hasPremium ? <PremiumFeature /> : <UpgradePrompt />;
|
|
350
|
-
}
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
### useLicenseFeature
|
|
333
|
+
### Using the Client from Context
|
|
354
334
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
return hasAnalytics ? <Analytics /> : null;
|
|
363
|
-
}
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### useLicenseValidity
|
|
367
|
-
|
|
368
|
-
```tsx
|
|
369
|
-
import { useLicenseAuthorization, useLicenseValidity } from '@sidub-inc/licensing-client';
|
|
370
|
-
|
|
371
|
-
function LicenseStatus() {
|
|
372
|
-
const { authorization } = useLicenseAuthorization();
|
|
373
|
-
const isValid = useLicenseValidity(authorization);
|
|
374
|
-
|
|
375
|
-
return <span>{isValid ? 'Active' : 'Expired'}</span>;
|
|
376
|
-
}
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
### useReportConsumption
|
|
335
|
+
The React surface is deliberately small: `LicensingProvider` owns one `LicensingClient`
|
|
336
|
+
for the tree, and `useLicensingContext()` hands it to components. Everything else —
|
|
337
|
+
authorization, assertions, consumption, checkout — is the same imperative client API
|
|
338
|
+
documented above. (Earlier releases shipped per-concern hooks such as
|
|
339
|
+
`useLicenseAuthorization`; they were removed in favor of the imperative client — see
|
|
340
|
+
[MIGRATION.md](docs/MIGRATION.md).)
|
|
380
341
|
|
|
381
342
|
```tsx
|
|
382
|
-
import { useLicensingContext,
|
|
343
|
+
import { useLicensingContext, FeatureExistsAssertion } from '@sidub-inc/licensing-client';
|
|
344
|
+
import { useEffect, useState } from 'react';
|
|
383
345
|
|
|
384
|
-
function
|
|
346
|
+
function FeatureGate({ children }: { children: React.ReactNode }) {
|
|
385
347
|
const client = useLicensingContext();
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
const handleUse = async () => {
|
|
389
|
-
await reportConsumption({
|
|
390
|
-
licenseId: 'license-uuid',
|
|
391
|
-
feature: { featureId: 'api-calls' },
|
|
392
|
-
operationType: 'increment',
|
|
393
|
-
quantity: 1,
|
|
394
|
-
timestamp: new Date()
|
|
395
|
-
});
|
|
396
|
-
};
|
|
348
|
+
const [hasPremium, setHasPremium] = useState<boolean | null>(null);
|
|
397
349
|
|
|
398
|
-
|
|
350
|
+
useEffect(() => {
|
|
351
|
+
let cancelled = false;
|
|
352
|
+
client.getAuthorization()
|
|
353
|
+
.then(authorization => {
|
|
354
|
+
if (cancelled) return;
|
|
355
|
+
const assertion = FeatureExistsAssertion.create('premium');
|
|
356
|
+
setHasPremium(client.assertLicense(assertion, authorization));
|
|
357
|
+
})
|
|
358
|
+
.catch(() => !cancelled && setHasPremium(false));
|
|
359
|
+
return () => { cancelled = true; };
|
|
360
|
+
}, [client]);
|
|
361
|
+
|
|
362
|
+
if (hasPremium === null) return <div>Loading…</div>;
|
|
363
|
+
return hasPremium ? <>{children}</> : <UpgradePrompt />;
|
|
399
364
|
}
|
|
400
365
|
```
|
|
401
366
|
|
|
@@ -456,19 +421,19 @@ function PurchaseButton({ offeringId }: { offeringId: string }) {
|
|
|
456
421
|
|
|
457
422
|
`pollCheckoutResult()` uses exponential backoff (1s initial, doubling to 15s cap, 60 max attempts ~15 min total). Pass an `AbortSignal` to cancel polling. Throws `LicensingError` on timeout or cancellation.
|
|
458
423
|
|
|
459
|
-
###
|
|
424
|
+
### Without the Provider
|
|
460
425
|
|
|
461
|
-
|
|
462
|
-
|
|
426
|
+
Outside React (Node services, plain scripts) or when a component tree does not need a
|
|
427
|
+
shared context, construct `LicensingClient` directly — the Provider is a convenience,
|
|
428
|
+
not a requirement:
|
|
463
429
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
licenseServiceUri: 'https://api.monaiq.com/licensing',
|
|
467
|
-
encodedCredential: 'SIDUB_LIC_...'
|
|
468
|
-
});
|
|
430
|
+
```typescript
|
|
431
|
+
import { LicensingClient } from '@sidub-inc/licensing-client';
|
|
469
432
|
|
|
470
|
-
|
|
471
|
-
|
|
433
|
+
const client = new LicensingClient({
|
|
434
|
+
licenseServiceUri: 'https://api.monaiq.com/licensing',
|
|
435
|
+
encodedCredential: 'SIDUB_LIC_...'
|
|
436
|
+
});
|
|
472
437
|
```
|
|
473
438
|
|
|
474
439
|
## Signature Validation
|
|
@@ -513,6 +478,7 @@ const decoded = decodeCredential(encoded); // LicensingCredential
|
|
|
513
478
|
| `LicensingError` | Base error for API, network, and timeout errors |
|
|
514
479
|
| `LicensingConfigurationException` | Missing required configuration fields (extends `LicensingError`) |
|
|
515
480
|
| `CryptoError` | Signature validation failures (`KEY_IMPORT_FAILED`, `VERIFICATION_FAILED`, `INVALID_SIGNATURE`) |
|
|
481
|
+
| `RateLimitError` | Thrown by `assertLicense` when a `RateLimitAssertion` limit is exceeded — catch it; it is the enforcement signal, not a failure |
|
|
516
482
|
|
|
517
483
|
```typescript
|
|
518
484
|
import { LicensingError, LicensingConfigurationException, CryptoError } from '@sidub-inc/licensing-client';
|