Skip to content

Commit 864ee26

Browse files
HF-329: re-port the license key reader to the entitlement key format
Upstream handsontable/license-key 4.0.0 (DEV-2512) deleted src/typed-key/ and replaced the tagged key format with the entitlement key format: <prose>, blank line, [<base64url-payload><sha512-checksum>]. The tagged format was never issued to anyone (its 3.5.0 carrier was never released), so the old reader is removed rather than kept alongside. Re-vendored from src/entitlement-key/ at tag 4.0.0: detect-format and extract-key-data are new ports; sha512 and utils are byte-identical upstream and carry over. The reader is schema-free by upstream design, so default-schema is no longer vendored and TIER_TO_CAPABILITY_TOKEN (the tagged format's tier adapter) is gone with the format that fed it. Resolution reads HyperFormula's own product entry only: capabilities verbatim, exactly one of usage_until/release_until (the reader enforces the shape), notice/grace, flags (trial + the three silent spellings). Legacy 25-character keys and the literals are untouched; the invariant stands - only a VALID entitlement key may restrict the entitlement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pxNP45obT2LZfjitaCv9o
1 parent 9933ba3 commit 864ee26

14 files changed

Lines changed: 423 additions & 584 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1515
### Changed
1616

1717
- Changed `getAvailableFunctions()` and `getFunctionDetails()` to describe only the functions the instance's license key includes, so they no longer advertise a function that would evaluate to a `#LIC!` error. A missing, invalid, or expired key does not shorten the list. [#1731](https://github.com/handsontable/hyperformula/pull/1731)
18+
- Changed the parser for the new proprietary license keys to the entitlement key format (a human-readable text ending with a machine-readable block in square brackets), following its upstream specification. This replaces the tagged key format, which was never issued to anyone. Classic 25-character license keys and `gpl-v3` are unaffected.
1819

1920
## [3.4.0] - 2026-08-10
2021

docs/guide/license-key.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,30 @@ const options = {
2929
}
3030
```
3131

32+
### Proprietary license key formats
33+
34+
Your proprietary license key is in one of two formats, and both work the same way:
35+
36+
* A classic key: 25 characters, for example `xxxx-xxxx-xxxx-xxxx-xxxx`.
37+
* An entitlement key: a short, human-readable license text that ends with a machine-readable
38+
block in square brackets. Assign the whole text to the `licenseKey` option, or just the
39+
bracketed block — the block is the only part HyperFormula reads, so both work. The text may be
40+
re-wrapped or re-formatted on its way to you (for example, by an email client) without
41+
affecting the key.
42+
3243
### Proprietary license key validation
3344

3445
::: tip
3546
HyperFormula doesn't use an internet connection to validate your proprietary license key.
3647
:::
3748

3849
To determine whether a user is still entitled to use a particular
39-
version of the software, HyperFormula compares the time between
40-
two dates:
41-
* The HyperFormula build date
42-
* The date in your proprietary license key
50+
version of the software, HyperFormula compares the date in your
51+
proprietary license key against one of two references, depending on
52+
the license you purchased:
53+
* The HyperFormula build date, when the key ends maintenance on a set
54+
date (versions released before that date keep working indefinitely)
55+
* The current date (in UTC), when the key ends usage on a set date
4356

4457
This process doesn't require any connection to the server.
4558

src/Config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ export class Config implements ConfigParams, ParserConfig {
341341

342342
/**
343343
* Whether gate B (the entitlement check in the interpreter) needs to run at all for this
344-
* config. `false` — the common case, for `gpl-v3`, legacy keys, and an unrestricted typed
344+
* config. `false` — the common case, for `gpl-v3`, legacy keys, and an unrestricted entitlement
345345
* key — is a single boolean read, cheaper than the string-enum comparison it replaces.
346346
*
347347
* @internal

src/helpers/licenseKeyValidator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export function resetLicenseKeyNotificationForTests(): void {
7474
/**
7575
* Prints the console message for a non-valid license key state, at most once per page load.
7676
*
77-
* Extracted so the typed-key path in `src/license/licenseResolution.ts` reports the same states
77+
* Extracted so the entitlement-key path in `src/license/licenseResolution.ts` reports the same states
7878
* with the same wording and the same once-only behaviour, without duplicating the message table
7979
* or getting a second `_notified` flag of its own — two flags would let a page print two
8080
* warnings for one key.
@@ -95,7 +95,7 @@ export function notifyLicenseKeyState(state: LicenseKeyValidityState, keyValidit
9595
}
9696

9797
/**
98-
* Prints a one-time notice that a VALID typed key's usage-until expiry is approaching, at most
98+
* Prints a one-time notice that a VALID entitlement key's usage-until expiry is approaching, at most
9999
* once per distinct license key string.
100100
*
101101
* Called from `src/license/licenseResolution.ts`'s `resolveLicense`, alongside
@@ -154,7 +154,7 @@ export function checkLicenseKeyValidity(licenseKey: string): LicenseKeyValidityS
154154
* Formats a Date instance to hard-coded format MMMM DD, YYYY.
155155
*
156156
* Read in UTC, not local time. Every date reaching this function is built at UTC midnight — the
157-
* legacy path from a whole number of days since the epoch, the typed-key path from a calendar
157+
* legacy path from a whole number of days since the epoch, the entitlement-key path from a calendar
158158
* date in the payload — so local getters shifted the day backwards for anyone west of UTC and
159159
* printed an expiry one day earlier than the one the key actually carries.
160160
*

src/license/LicenseEntitlement.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export interface LicenseEntitlement {
8484
/**
8585
* The unrestricted entitlement: legacy keys and `gpl-v3` resolve to this today.
8686
*
87-
* HF-307 decision D3 (fail-closed, silent) means a typed key whose tokens this library version
87+
* HF-307 decision D3 (fail-closed, silent) means an entitlement key whose tokens this library version
8888
* does not recognize at all no longer maps here — it resolves to an entitlement with an empty,
8989
* silent capability set instead of falling back to unrestricted access. Do not reuse this
9090
* function for that case.

src/license/capabilities.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ export const NAMED_EXPRESSIONS_FEATURE_TOKEN = 'feat:named_expressions'
2727
export const BATCHING_FEATURE_TOKEN = 'feat:batching'
2828

2929
/**
30-
* Every feature token, in one list, for the shipped-shape adapter: the shipped key vocabulary
31-
* predates feature tokens entirely, so a commercial tier is translated into its functions token
32-
* PLUS all of these — see `licenseTermsOf` for the reasoning.
30+
* Every feature token, in one list, for the opt-in rule in `licenseTermsOf`: a key naming no
31+
* `feat:*` token at all is granted all of these, because no key vocabulary in circulation can
32+
* express "no features" — see that function for the reasoning.
3333
*/
3434
export const ALL_FEATURE_TOKENS = [
3535
CRUD_FEATURE_TOKEN, UNDO_REDO_FEATURE_TOKEN, CLIPBOARD_FEATURE_TOKEN,
@@ -79,7 +79,7 @@ const OPERATOR_FUNCTIONS = [
7979
]
8080

8181
// An earlier revision granted all five features from CORE_TOKEN, which made feature gating inert
82-
// by construction: no typed key could ever lose an API area. Kuba's call (task comment, 12.08):
82+
// by construction: no restricted key could ever lose an API area. Kuba's call (task comment, 12.08):
8383
// "Feature gating should work, but the legacy keys should grant all feat:* capabilities" — legacy
8484
// keys already resolve to the unrestricted entitlement, so the carve-out costs nothing, and the
8585
// five features moved onto their own `feat:*` tokens below.
@@ -201,9 +201,9 @@ const functions4Grant: CapabilityGrant = {
201201
* in `unit/license/capability-registry.spec.ts` fails on.
202202
*
203203
* The five `feat:*` tokens carry the gated API areas, one feature each, spelled after the draft
204-
* vocabulary in the task. A rev-5 key states them explicitly; the shipped-shape adapter grants
205-
* all five alongside the tier (that vocabulary predates feature tokens); legacy keys resolve to
206-
* the unrestricted entitlement and never consult this table.
204+
* vocabulary in the task. A key may state them explicitly; a key naming none is granted all five
205+
* (the opt-in rule in `licenseTermsOf`); legacy keys resolve to the unrestricted entitlement and
206+
* never consult this table.
207207
*
208208
* The two add-on tokens, wired per the 2026-08-12 packages meeting: `spreadsheet` backs the
209209
* 'Spreadsheet Bundle' add-on and grants {@link FeatureId.Crud}, {@link FeatureId.UndoRedo},

0 commit comments

Comments
 (0)