Changelog
Every release of the Kinesis.js packages, newest first — pulled straight from the library’s published versions.
- #28 Harden the OSRM client: a malformed routing
profileis now rejected before the request is issued (defense-in-depth on top of the existingencodeURIComponentescaping). The guard is syntactic ([A-Za-z0-9_-]+), so custom self-host profile names still work — only obviously-invalid values are rejected, surfacing as anINTERPOLATION_ERRORevent instead of a doomed network call.
- #24 fix: harden against untrusted input
- leaflet: the marker
divIconHTML now coerces numeric options (heading/speed/iconSize/…) to finite numbers and escapes interpolatedicon/colorvalues, so a malformed feed or crafted style option can no longer break out of an HTML attribute (DOM-XSS hardening). - core: non-finite
heading/speedare dropped on ingest, so malformed feed values never reach a render adapter. - route-aware: the OSRM
baseUrlmust now be anhttp(s)URL and the routing profile isencodeURIComponent-escaped before being placed in the request URL.
- leaflet: the marker
- Updated dependencies []:
- @kinesisjs/[email protected]
- @kinesisjs/[email protected]
- Updated dependencies []:
- @kinesisjs/[email protected]
#24 fix: harden against untrusted input
- leaflet: the marker
divIconHTML now coerces numeric options (heading/speed/iconSize/…) to finite numbers and escapes interpolatedicon/colorvalues, so a malformed feed or crafted style option can no longer break out of an HTML attribute (DOM-XSS hardening). - core: non-finite
heading/speedare dropped on ingest, so malformed feed values never reach a render adapter. - route-aware: the OSRM
baseUrlmust now be anhttp(s)URL and the routing profile isencodeURIComponent-escaped before being placed in the request URL.
- leaflet: the marker
Updated dependencies []:
- @kinesisjs/[email protected]
#24 fix: harden against untrusted input
- leaflet: the marker
divIconHTML now coerces numeric options (heading/speed/iconSize/…) to finite numbers and escapes interpolatedicon/colorvalues, so a malformed feed or crafted style option can no longer break out of an HTML attribute (DOM-XSS hardening). - core: non-finite
heading/speedare dropped on ingest, so malformed feed values never reach a render adapter. - route-aware: the OSRM
baseUrlmust now be anhttp(s)URL and the routing profile isencodeURIComponent-escaped before being placed in the request URL.
- leaflet: the marker
Updated dependencies []:
- @kinesisjs/[email protected]
#18 Add
TrackerOptions.playout— a per-vehicle queue that decouples display rate from arrival rate, so feeds with variable-period ingest (jitter, replay scrubbing, retry storms) render at a steady pace instead of speeding up and slowing down with each segment.Opt-in and non-breaking: without
playout, Tracker uses the existing classical real-time path; behaviour is byte-for-byte identical to v0.4.Two forms:
- Manual —
playout: { pace, bufferMs, maxQueue? }when you know your feed's worst-case gap. PickbufferMs ≥ worstCaseGapto avoid the queue underrunning (which would freeze the marker). - Auto —
playout: 'auto'. Tracker measures the last ~10 ingest gaps per vehicle and setspace = avg,bufferMs = max × 1.5. Behaves classically while gathering its first 5 samples, then engages playout. Each vehicle calibrates independently, so mixed fleets (1 Hz dispatch + jittery IoT) coexist cleanly.
Trade-off:
bufferMsof additional perceived latency for smooth motion. For most fleet/dispatch use cases (where "the marker is 2 s behind" beats "the marker stutters") this is the right exchange. Stable 1 Hz feeds shouldn't enable it.Composition: works on top of every
interpolationmode. Pairing with'smooth'(3-point Catmull-Rom) yields the maximum-pleasant render path for jittery feeds — smooth shapes the geometry, playout flattens the rhythm.Also adds
PlayoutOptionsandPlayoutQueueEntryto the public type surface.- Manual —
#22 Expose
[playout]@InputonKinesisMapDirective, mirroring theTrackerOptions.playoutfield that landed in@kinesisjs/[email protected].<div kinesisMap [positions]="positions" [interpolation]="'smooth'" [playout]="'auto'"></div>Forwarded to the underlying
Trackeronly when set, so omitting the input keeps the classical real-time path. Use'auto'for unknown feeds (Tracker self-calibrates from the gap history) or{ pace, bufferMs, maxQueue }when you know your worst-case gap.
#16 Add
interpolation: 'smooth'— a 3-point centripetal Catmull-Rom mode for jitter and variable-period feeds.The Tracker now keeps a third historical point per vehicle (
previous2) and routes smooth-mode ticks through a cubic spline overprevious2 → previous → current, with a mirror phantom for the trailing tangent. The marker glides through each waypoint instead of kinking, which is especially visible on irregular feeds (random arrival times, dead reckoning, replay scrubbing).Opt-in and conservative:
- Default stays
'linear'. Existing apps see no behavioural change. - Until the third ingest lands the spline falls back to linear — no spurious motion from incomplete history.
- If the
previous2 → previousgap exceedsmaxInterpolationGap, that control point is dropped (stale data shouldn't warp the curve). - All sanity checks (anomalous jump, sharp turn, render-lag warm-up) and the existing custom-interpolator path are untouched.
Also exposes
catmullRomLerpas a public math helper, alongsidelinearLerp/haversineDistance/shortestArcDiff, for authors of custom interpolators who want the same smoothing primitive.- Default stays
#20 Expose
[playout]@InputonKinesisMapDirective, mirroring theTrackerOptions.playoutfield that landed in@kinesisjs/[email protected].<div kinesisMap [positions]="positions" [interpolation]="'smooth'" [playout]="'auto'"></div>Forwarded to the underlying
Trackeronly when set, so omitting the input keeps the classical real-time path. Use'auto'for unknown feeds (Tracker self-calibrates from the gap history) or{ pace, bufferMs, maxQueue }when you know your worst-case gap.
- Updated dependencies []:
- @kinesisjs/[email protected]
- @kinesisjs/[email protected]
- Updated dependencies []:
- @kinesisjs/[email protected]
- @kinesisjs/[email protected]
- Updated dependencies []:
- @kinesisjs/[email protected]
- Updated dependencies []:
- @kinesisjs/[email protected]
- Updated dependencies []:
- @kinesisjs/[email protected]
- Updated dependencies []:
- @kinesisjs/[email protected]
- Updated dependencies []:
- @kinesisjs/[email protected]
- Updated dependencies []:
- @kinesisjs/[email protected]
Add opt-in Web Worker mode (
worker: trueorworker: { url }).The tick loop — interpolation, sanity checks, and the sweeper — can now run off the main thread inside a Web Worker, keeping the UI thread free for the actual map/DOM writes. The adapter stays on the main thread and is driven by messages the worker streams back, so existing adapters work unchanged.
worker: truespins the worker up from an inlined Blob (zero setup; adds ~2.4 KB gzip to the core bundle).worker: { url }loads the bundled worker script from a URL you control, avoiding the inline payload.
The public API is unchanged —
new Tracker({ worker: true })transparently returns a worker-backed tracker with the same surface.@kinesisjs/angular's[kinesisMap]directive exposes it via a new[worker]input.Caveats: a
CustomInterpolatorisn't supported in worker mode (functions can't cross the worker boundary; construction throws),updateOpacity-based fade animations degrade to snapping, andgetStats()returns a snapshot refreshed every ~30 ticks.
Add opt-in Web Worker mode (
worker: trueorworker: { url }).The tick loop — interpolation, sanity checks, and the sweeper — can now run off the main thread inside a Web Worker, keeping the UI thread free for the actual map/DOM writes. The adapter stays on the main thread and is driven by messages the worker streams back, so existing adapters work unchanged.
worker: truespins the worker up from an inlined Blob (zero setup; adds ~2.4 KB gzip to the core bundle).worker: { url }loads the bundled worker script from a URL you control, avoiding the inline payload.
The public API is unchanged —
new Tracker({ worker: true })transparently returns a worker-backed tracker with the same surface.@kinesisjs/angular's[kinesisMap]directive exposes it via a new[worker]input.Caveats: a
CustomInterpolatorisn't supported in worker mode (functions can't cross the worker boundary; construction throws),updateOpacity-based fade animations degrade to snapping, andgetStats()returns a snapshot refreshed every ~30 ticks.Updated dependencies []:
- @kinesisjs/[email protected]
- @kinesisjs/[email protected]
- Updated dependencies []:
- @kinesisjs/[email protected]
Release pipeline restored — npm Trusted Publishing now verified end-to-end.
No runtime changes. This patch only re-establishes the OIDC publish flow after the v0.1.2 / v0.2.0 / v0.2.1 release failures, by ensuring all three packages have valid Trusted Publisher rules on npmjs.com that match the release workflow.
Updated dependencies []:
- @kinesisjs/[email protected]
Release pipeline restored — npm Trusted Publishing now verified end-to-end.
No runtime changes. This patch only re-establishes the OIDC publish flow after the v0.1.2 / v0.2.0 / v0.2.1 release failures, by ensuring all three packages have valid Trusted Publisher rules on npmjs.com that match the release workflow.
Updated dependencies []:
- @kinesisjs/[email protected]
- @kinesisjs/[email protected]
Release pipeline restored — npm Trusted Publishing now verified end-to-end.
No runtime changes. This patch only re-establishes the OIDC publish flow after the v0.1.2 / v0.2.0 / v0.2.1 release failures, by ensuring all three packages have valid Trusted Publisher rules on npmjs.com that match the release workflow.
Trail layer was invisible by default in v0.2.0 — the layer's
zIndex: -1default placed it BELOW the standard OSM tile layer (zIndex 0), which then overdrew the trail. Reported by the first downstream consumer that enabledtrail: { enabled: true }without overriding zIndex.Fix:
- Trail layer is now added to the map BEFORE the adapter's vehicle layer. OpenLayers' natural render order (later-added on top) puts trails behind vehicles without needing zIndex tricks.
TrailRenderOptions.zIndexdefault is nowundefined(previously-1). The option remains available as an explicit override forexistingLayermode, where the user's own vehicle layer is already in the stack and trail-vs-vehicle order cannot be controlled by add sequence alone.
Two new tests lock in the fix:
adds the trail layer BEFORE the vehicle layer (trail renders below)honors explicit trail.zIndex when provided (existingLayer override)
No API surface change; users who were already setting
trail.zIndexexplicitly keep their behavior. Users on the default config get visible trails.
KinesisMapDirectivenow exposes thewarningOpacityadapter option as an optional@Input, completing the v0.2.0 gap-visualization story for directive users (previously reachable only via thekinesisTrackerfactory).<div kinesisMap [positions]="positions" [warningThreshold]="60000" [warningOpacity]="0.5"></div>Marker dims to 50% when a vehicle's idle exceeds
warningThreshold; restores to 1.0 on the next ingest or sweeper-detected recovery. Omit the input to keep the v0.2.0 behavior (no opacity change on warning).Updated dependencies []:
- @kinesisjs/[email protected]
TrackAdaptergains an optionalsetVehicleState(id, state)hook. Tracker calls it whenever a vehicle transitions between lifecycle states (active ↔ warning), so adapters can render gap-visualization treatment — fading, badging, dashed trails — without having to subscribe to the event bus externally.The hook fires:
- On warning (sweeper detects idle > warningThreshold)
- On recovery to active (fresh ingest, or sweeper after slot revives)
It does NOT fire for
staleorcompleted— those are followed immediately byremoveVehicle(id), and rendering a transient terminal state isn't useful.Backward compatible: the method is optional in the interface, and adapters that don't implement it (or instances on the existing v0.1.x API) keep working unchanged.
Gap visualization:
OpenLayersAdapternow implements the newsetVehicleStatehook.Every state change always writes a
vehicleStatefeature property (useful for external readers / popup labels). WhenOpenLayersAdapterOptions.warningOpacityis configured, the adapter additionally dims the marker onwarningand restores opacity 1 onactive:new OpenLayersAdapter(map, { style: vehicleStyle, warningOpacity: 0.5, // marker fades to 50% when warning threshold passes });staleandcompletedare handled byremoveVehicleand produce no opacity work here. WithoutwarningOpacity, only the property is set — no visual change (backward compatible default).Pairs naturally with the v0.2.0
trailrendering: the dimmed marker plus the still-rendered trail tell the user "we know the last position but haven't heard back" without removing the vehicle from the map.Per-vehicle trail rendering — fading polyline behind each marker showing recent positions.
Opt in via
OpenLayersAdapter:new OpenLayersAdapter(map, { style: vehicleStyle, trail: { enabled: true, maxPoints: 60, intervalMs: 100, width: 3, opacity: 0.5 }, });A separate
VectorLayer(name: 'kinesis-trails', defaultzIndex: -1) is added when enabled — trails always render below vehicle markers regardless of the vehicle layer's own zIndex. Each vehicle gets aFeature<LineString>with idtrail:<vehicleId>.Color resolution: explicit
trail.color→TrailPoint.meta.color(string) →trail.defaultColor→#3b82f6. Hex inputs (#rrggbb,#rgb) have the trail'sopacityapplied automatically as alpha; non-hex colors (named,rgb(),rgba()) are passed through unchanged so the caller controls alpha.Throttling:
intervalMs(default 100 ms) caps how often a tick is appended to a trail. The Tracker runs at ~60 fps, so without throttling a 60-point buffer fills in one second. The default samples at ~10 Hz, giving a ~6-second visible trail.Memory: per-trail overhead ≈ 64 bytes + 16 bytes per coordinate; reflected in
getMemoryEstimate(). Trail features are torn down withremoveVehicle(id)and the trail layer is removed from the map indestroy().Backward compatible — adapter behaves identically to v0.1.x when
trailis omitted or{ enabled: false }.Updated dependencies []:
- @kinesisjs/[email protected]
KinesisMapDirectivenow exposes the newtrailadapter option as an optional@Input.<div kinesisMap [positions]="positions" [trail]="{ enabled: true, maxPoints: 60, intervalMs: 100, color: '#3b82f6' }" ></div>Omit the input and the directive behaves identically to v0.1.2 (no trail layer created). See
@kinesisjs/openlayersTrailRenderOptionsfor the full option surface.Updated dependencies [,,]:
- @kinesisjs/[email protected]
- @kinesisjs/[email protected]
Lower
AdaptiveInterpolatordefaultminPeriodMsfrom 1000 to 500.At 1000 ms the default placed a typical 1 Hz GPS feed exactly on the boundary between the
noneandlinearadaptive zones, andsetInterval/interval(1000)jitter routinely produced sub-1000 ms periods. Each clipped tick fell into thenonezone and teleported the marker — visible micro-skipping with the otherwise smoothrenderLagMsbuffer.The new 500 ms default keeps 1 Hz feeds firmly inside
linearregardless of jitter. Sub-second feeds that explicitly want thenonebehavior can opt in:new Tracker({ adapter, interpolation: 'adaptive', adaptive: { minPeriodMs: 1000 } });No API change, only the default value.
- Updated dependencies []:
- @kinesisjs/[email protected]
KinesisMapDirectivenow exposes four advancedTrackerOptionsas optional@Inputs. Previously these were only reachable via the lower-levelkinesisTracker(...)factory.[renderLagMs]— real-time interpolation buffer size (default1000)[adaptive]— adaptive zone thresholds object[fadeAnimation]— duration / easing for the adaptivefadezone[initialPositionBehavior]—'show-immediately' | 'wait-for-second' | 'fade-in'
Example:
<div kinesisMap [positions]="positions" [interpolation]="'adaptive'" [renderLagMs]="800" [adaptive]="{ minPeriodMs: 200, fadeThresholdMs: 30000 }" [fadeAnimation]="{ duration: 400, easing: 'linear' }" [initialPositionBehavior]="'fade-in'" ></div>All four inputs are optional — omitting them keeps the tracker defaults.
Updated dependencies []:
- @kinesisjs/[email protected]
- @kinesisjs/[email protected]
Fix two critical issues discovered while building the first downstream Angular demo:
@kinesisjs/core— real-time interpolation now actually runs. v0.1.0'sTracker.tick()always took the snap-to-current branch: at the moment a position was ingested,now == current.receivedAt, soelapsed = now − previous.receivedAt ≥ periodimmediately and stayed true, making interpolation unreachable outside fake-timer tests that rewindDate.now(). AddedTrackerOptions.renderLagMs(default 1000 ms), the standard interpolation-buffer pattern from real-time networking: tick computesrenderTime = now − renderLagMsand uses that for elapsed/ratio. With the default, a 1 Hz feed slides the marker smoothly from the previous to the current point over each second. PassrenderLagMs: 0to restore the legacy snap-on-ingest behavior. Added two new tests covering both modes; the existing custom-interpolator tests no longer rely onvi.setSystemTimerewinding.@kinesisjs/angular— built with ng-packagr, finally importable by Angular AOT consumers. v0.1.0 was bundled with tsup, which preserved raw TS decorator output (__decorate([Directive({...})], cls)). Angular AOT consumers compile against Ivy partial-Ivy metadata (ɵdir,ɵfac,ɵngDeclareDirective,ɵngDeclareClassMetadata), which tsup does not emit — soimports: [KinesisMapDirective]in any consuming standalone component failed at AOT compile time with "Component imports must be standalone components, directives, pipes, or must be NgModules." Migrated build tong-packagr(FESM2022 + partial-Ivy.d.ts); the package now compiles cleanly into Angular 17+ apps. No source-level API changes.@kinesisjs/openlayers— patch bump for monorepo cohesion only; no behavior change.
Fix two critical issues discovered while building the first downstream Angular demo:
@kinesisjs/core— real-time interpolation now actually runs. v0.1.0'sTracker.tick()always took the snap-to-current branch: at the moment a position was ingested,now == current.receivedAt, soelapsed = now − previous.receivedAt ≥ periodimmediately and stayed true, making interpolation unreachable outside fake-timer tests that rewindDate.now(). AddedTrackerOptions.renderLagMs(default 1000 ms), the standard interpolation-buffer pattern from real-time networking: tick computesrenderTime = now − renderLagMsand uses that for elapsed/ratio. With the default, a 1 Hz feed slides the marker smoothly from the previous to the current point over each second. PassrenderLagMs: 0to restore the legacy snap-on-ingest behavior. Added two new tests covering both modes; the existing custom-interpolator tests no longer rely onvi.setSystemTimerewinding.@kinesisjs/angular— built with ng-packagr, finally importable by Angular AOT consumers. v0.1.0 was bundled with tsup, which preserved raw TS decorator output (__decorate([Directive({...})], cls)). Angular AOT consumers compile against Ivy partial-Ivy metadata (ɵdir,ɵfac,ɵngDeclareDirective,ɵngDeclareClassMetadata), which tsup does not emit — soimports: [KinesisMapDirective]in any consuming standalone component failed at AOT compile time with "Component imports must be standalone components, directives, pipes, or must be NgModules." Migrated build tong-packagr(FESM2022 + partial-Ivy.d.ts); the package now compiles cleanly into Angular 17+ apps. No source-level API changes.@kinesisjs/openlayers— patch bump for monorepo cohesion only; no behavior change.Updated dependencies []:
- @kinesisjs/[email protected]
Fix two critical issues discovered while building the first downstream Angular demo:
@kinesisjs/core— real-time interpolation now actually runs. v0.1.0'sTracker.tick()always took the snap-to-current branch: at the moment a position was ingested,now == current.receivedAt, soelapsed = now − previous.receivedAt ≥ periodimmediately and stayed true, making interpolation unreachable outside fake-timer tests that rewindDate.now(). AddedTrackerOptions.renderLagMs(default 1000 ms), the standard interpolation-buffer pattern from real-time networking: tick computesrenderTime = now − renderLagMsand uses that for elapsed/ratio. With the default, a 1 Hz feed slides the marker smoothly from the previous to the current point over each second. PassrenderLagMs: 0to restore the legacy snap-on-ingest behavior. Added two new tests covering both modes; the existing custom-interpolator tests no longer rely onvi.setSystemTimerewinding.@kinesisjs/angular— built with ng-packagr, finally importable by Angular AOT consumers. v0.1.0 was bundled with tsup, which preserved raw TS decorator output (__decorate([Directive({...})], cls)). Angular AOT consumers compile against Ivy partial-Ivy metadata (ɵdir,ɵfac,ɵngDeclareDirective,ɵngDeclareClassMetadata), which tsup does not emit — soimports: [KinesisMapDirective]in any consuming standalone component failed at AOT compile time with "Component imports must be standalone components, directives, pipes, or must be NgModules." Migrated build tong-packagr(FESM2022 + partial-Ivy.d.ts); the package now compiles cleanly into Angular 17+ apps. No source-level API changes.@kinesisjs/openlayers— patch bump for monorepo cohesion only; no behavior change.Updated dependencies []:
- @kinesisjs/[email protected]
- @kinesisjs/[email protected]
Initial public release.
Trackerorchestrator with validation, throttling, and configurable initial-position behaviour (show-immediately/wait-for-second/fade-in)Interpolatormodes:linear,cubic,geodesic,noneAdaptiveInterpolator— period-aware four-zone classifierSweeper— multi-state vehicle lifecycle (active/warning/stale/completed)CustomInterpolatorinterface with sync/async support- Tick-loop sanity checks: anomalous-jump (haversine + speed) and sharp-turn (heading)
- Event-based error handling — public methods never throw
- Performance telemetry — tick history percentiles, dropped ticks, ingest rate, memory breakdown
- Public utilities:
haversineDistance,shortestArcDiff,linearLerp - Benchmarked: 1000 vehicles ≈ 0.15 ms per tick
Initial public release.
- Full
TrackAdapterimplementation for OpenLayers managedFeatureIdsoption to coexist safely with non-vehicle features inside a sharedVectorLayerupdateOpacitycapability for fade animationsgetMemoryEstimatecapability for accurate statscreateVehicleStylehelper with Icon / Circle modes, heading rotation, and speed colour bandscolorForSpeedexported standalone for custom style factories
Initial public release.
KinesisMapDirective— standalone directive for one-line setupkinesisTrackerfactory for programmatic use in services and route resolvers- Automatic
DestroyRefcleanup Signal<Position[]>andObservable<Position[]>both supported as the input source- Peer dependency: Angular 17+
feat(leaflet): add the Leaflet map adapter
New
@kinesisjs/leafletpackage — aTrackAdapterfor Leaflet, on par with@kinesisjs/openlayers: per-vehicleL.Markerlifecycle, a built-in heading-aware rotatable marker (plus static/dynamic icon factories and thecreateVehicleStylehelper with speed-band colouring),managedFeatureIds,updateOpacity,setVehicleState+warningOpacitygap visualisation, and optional per-vehicle trail rendering.leafletis a peer dependency (>=1.7).
#12 feat(route-aware): add the road-snapping CustomInterpolator package
New
@kinesisjs/route-aware— aCustomInterpolatorfor@kinesisjs/corethat asks an OSRM server for the real road between two GPS points and walks that polyline at constant arc-length speed. Markers follow the actual street network instead of cutting straight lines across buildings.Highlights:
OSRMInterpolatordrops intonew Tracker({ interpolation: ri })— no changes to the existing engine, no map adapter coupling.- The tick is never blocked:
compute()is always synchronous.prepare()warms the cache in the background; cache misses fall back to a linear lerp this tick and snap to the road on the next. - LRU + coordinate-grid hashing → high cache hit rate (a 500-vehicle fleet on recurring routes typically generates tens of unique segment fetches).
- Coalesces concurrent fetches for the same segment hash.
- Detour guard (default
2.5×straight-line) rejects implausible routes — segment keeps using linear fallback rather than misleading the operator. dispose()clears cache + in-flight set ontracker.destroy().
Defaults point at the public
router.project-osrm.orgdemo endpoint for evaluation; production fleets should self-host (see README + PRD §22).