Skip to content

v0.2.0

Choose a tag to compare

@Halloweedev Halloweedev released this 03 Sep 16:47
· 6 commits to main since this release

[0.2.0] - 2026-09-04

Added

  • A keyless heartbeat, on by default. reportKeylessState used to go out
    only when the app called it, which in practice is once at startup — so an
    Electron app or a resident Node service reported itself once and then looked
    dead to the dashboard for as long as it ran: last_seen never moved past
    first_seen, and the reported app version froze at whatever shipped that day.

    checkOnLaunch() now starts a cadence (keylessHeartbeatMs, default 6h to
    match the Worker's server-side gate on keyless writes); startKeylessHeartbeat()
    is public for apps that never call checkOnLaunch. Each tick beacons only
    when the device is keyless — a licensed device sends nothing and reports
    liveness through /validate — and the timer keeps running across that
    boundary so a lapsed license resumes on its own.

    Nothing to change in your app. reportKeylessState still debounces to one
    request per 24h, so the cadence costs nothing extra on the wire. The timer is
    unref'd on Node/Bun so a six-hour interval can't keep a one-shot script
    alive, and stopKeylessHeartbeat() disposes it. Pass
    keylessHeartbeatMs: null to opt out.

  • Device dimensions on the telemetry fields: arch and os_version.
    Requests that already carry telemetry (activate, validate, the keyless
    beacon) now also report the CPU architecture as a canonical token (arm64 /
    x86_64; anything else is omitted) and the host OS's release version reduced
    to its dotted-numeric form (a Linux kernel release like 6.8.0-45-generic is
    sent as 6.8.0; on macOS it is the marketing version from sw_vers, e.g.
    26.1, matching what the Swift and Rust SDKs report — not the Darwin kernel
    version, which would place the same macOS release in a different bucket. The
    sw_vers call runs at most once per process, and where it cannot run the
    version is omitted rather than reported in the other vocabulary). Both fields
    are optional on the wire and only sent where a host OS exists to ask: under
    Node, Bun, Electron and Deno — on Deno the macOS version needs --allow-run,
    and is omitted rather than prompting for it. Browser pages and edge isolates
    send neither — there
    is no reliable source there that isn't also a fingerprinting surface, and
    this SDK keeps its stance of not reading one. device_class is never sent;
    the backend derives it from the OS. No app code changes.

  • Coarse device capacity on the same telemetry fields: cpu_cores and
    memory.
    Requests that already carry telemetry report which bucket the
    machine falls in — 1-2, 3-4, 5-8, 9-16, 17+ cores, and <4GB,
    4-8GB, 8-16GB, 16-32GB, 32-64GB, 64GB+ of RAM. The exact core count
    and the exact byte figure never leave the machine: a licensing SDK reporting
    precise hardware specs reads as fingerprinting, so only the bucket is sent.
    Bucket edges match the other SDKs exactly, so one machine population never
    splits across two buckets. Both fields are optional on the wire and only sent
    where a host OS exists to ask (Node, Bun, Electron, Deno). Browser pages and
    edge isolates send neither: navigator.hardwareConcurrency and
    navigator.deviceMemory are documented fingerprinting surfaces and this SDK
    declines to read them, the same stance it takes on arch. No app code
    changes.