@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 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 hooks)
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
- await client.reportAccessCheck(authorization, {
141
- featureId: 'api-calls',
142
- featureKey: 'api-calls',
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
- // Store feature state on the client
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
- const withinLimit = client.assertLicense(assertion, authorization);
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
- ### useLicenseAuthorization
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
- ```tsx
356
- import { useLicenseAuthorization, useLicenseFeature } from '@sidub-inc/licensing-client';
357
-
358
- function Component() {
359
- const { authorization } = useLicenseAuthorization();
360
- const hasAnalytics = useLicenseFeature(authorization, 'analytics');
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, useReportConsumption } from '@sidub-inc/licensing-client';
343
+ import { useLicensingContext, FeatureExistsAssertion } from '@sidub-inc/licensing-client';
344
+ import { useEffect, useState } from 'react';
383
345
 
384
- function UsageTracker() {
346
+ function FeatureGate({ children }: { children: React.ReactNode }) {
385
347
  const client = useLicensingContext();
386
- const { reportConsumption, isReporting } = useReportConsumption(client);
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
- return <button onClick={handleUse} disabled={isReporting}>Track Usage</button>;
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
- ### Standalone Hook (without Provider)
424
+ ### Without the Provider
460
425
 
461
- ```tsx
462
- import { useLicensing } from '@sidub-inc/licensing-client';
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
- function Component() {
465
- const client = useLicensing({
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
- // Use client directly
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';