@dejadex/web 0.0.0-stage → 0.1.0

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.
Files changed (113) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +214 -2
  3. package/dist/errors.d.ts +8 -0
  4. package/dist/errors.d.ts.map +1 -0
  5. package/dist/errors.js +9 -0
  6. package/dist/errors.js.map +1 -0
  7. package/dist/geometry/homography.d.ts +75 -0
  8. package/dist/geometry/homography.d.ts.map +1 -0
  9. package/dist/geometry/homography.js +146 -0
  10. package/dist/geometry/homography.js.map +1 -0
  11. package/dist/identify/assets.d.ts +39 -0
  12. package/dist/identify/assets.d.ts.map +1 -0
  13. package/dist/identify/assets.js +167 -0
  14. package/dist/identify/assets.js.map +1 -0
  15. package/dist/identify/decode.d.ts +6 -0
  16. package/dist/identify/decode.d.ts.map +1 -0
  17. package/dist/identify/decode.js +53 -0
  18. package/dist/identify/decode.js.map +1 -0
  19. package/dist/identify/engine.d.ts +32 -0
  20. package/dist/identify/engine.d.ts.map +1 -0
  21. package/dist/identify/engine.js +127 -0
  22. package/dist/identify/engine.js.map +1 -0
  23. package/dist/identify/identifier.d.ts +81 -0
  24. package/dist/identify/identifier.d.ts.map +1 -0
  25. package/dist/identify/identifier.js +327 -0
  26. package/dist/identify/identifier.js.map +1 -0
  27. package/dist/identify/manifest.d.ts +60 -0
  28. package/dist/identify/manifest.d.ts.map +1 -0
  29. package/dist/identify/manifest.js +211 -0
  30. package/dist/identify/manifest.js.map +1 -0
  31. package/dist/identify/preprocess.d.ts +52 -0
  32. package/dist/identify/preprocess.d.ts.map +1 -0
  33. package/dist/identify/preprocess.js +195 -0
  34. package/dist/identify/preprocess.js.map +1 -0
  35. package/dist/identify/router.d.ts +60 -0
  36. package/dist/identify/router.d.ts.map +1 -0
  37. package/dist/identify/router.js +83 -0
  38. package/dist/identify/router.js.map +1 -0
  39. package/dist/identify/runtime.d.ts +69 -0
  40. package/dist/identify/runtime.d.ts.map +1 -0
  41. package/dist/identify/runtime.js +140 -0
  42. package/dist/identify/runtime.js.map +1 -0
  43. package/dist/identify/search.d.ts +35 -0
  44. package/dist/identify/search.d.ts.map +1 -0
  45. package/dist/identify/search.js +99 -0
  46. package/dist/identify/search.js.map +1 -0
  47. package/dist/identify/server.d.ts +60 -0
  48. package/dist/identify/server.d.ts.map +1 -0
  49. package/dist/identify/server.js +149 -0
  50. package/dist/identify/server.js.map +1 -0
  51. package/dist/identify/shard.d.ts +31 -0
  52. package/dist/identify/shard.d.ts.map +1 -0
  53. package/dist/identify/shard.js +146 -0
  54. package/dist/identify/shard.js.map +1 -0
  55. package/dist/identify/types.d.ts +107 -0
  56. package/dist/identify/types.d.ts.map +1 -0
  57. package/dist/identify/types.js +7 -0
  58. package/dist/identify/types.js.map +1 -0
  59. package/dist/identify/worker.d.ts +2 -0
  60. package/dist/identify/worker.d.ts.map +1 -0
  61. package/dist/identify/worker.js +13 -0
  62. package/dist/identify/worker.js.map +1 -0
  63. package/dist/identify/workerProtocol.d.ts +54 -0
  64. package/dist/identify/workerProtocol.d.ts.map +1 -0
  65. package/dist/identify/workerProtocol.js +113 -0
  66. package/dist/identify/workerProtocol.js.map +1 -0
  67. package/dist/index.d.ts +20 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +16 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/lease/verify.d.ts +69 -0
  72. package/dist/lease/verify.d.ts.map +1 -0
  73. package/dist/lease/verify.js +235 -0
  74. package/dist/lease/verify.js.map +1 -0
  75. package/dist/scanner.d.ts +10 -0
  76. package/dist/scanner.d.ts.map +1 -0
  77. package/dist/scanner.js +58 -0
  78. package/dist/scanner.js.map +1 -0
  79. package/dist/types.d.ts +180 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +2 -0
  82. package/dist/types.js.map +1 -0
  83. package/dist/usage/meter.d.ts +83 -0
  84. package/dist/usage/meter.d.ts.map +1 -0
  85. package/dist/usage/meter.js +111 -0
  86. package/dist/usage/meter.js.map +1 -0
  87. package/dist/usage/store.d.ts +52 -0
  88. package/dist/usage/store.d.ts.map +1 -0
  89. package/dist/usage/store.js +198 -0
  90. package/dist/usage/store.js.map +1 -0
  91. package/package.json +64 -4
  92. package/src/errors.ts +24 -0
  93. package/src/geometry/homography.ts +178 -0
  94. package/src/identify/assets.ts +189 -0
  95. package/src/identify/decode.ts +54 -0
  96. package/src/identify/engine.ts +167 -0
  97. package/src/identify/identifier.ts +419 -0
  98. package/src/identify/manifest.ts +240 -0
  99. package/src/identify/preprocess.ts +227 -0
  100. package/src/identify/router.ts +115 -0
  101. package/src/identify/runtime.ts +197 -0
  102. package/src/identify/search.ts +115 -0
  103. package/src/identify/server.ts +167 -0
  104. package/src/identify/shard.ts +154 -0
  105. package/src/identify/types.ts +122 -0
  106. package/src/identify/worker.ts +13 -0
  107. package/src/identify/workerProtocol.ts +136 -0
  108. package/src/index.ts +71 -0
  109. package/src/lease/verify.ts +252 -0
  110. package/src/scanner.ts +56 -0
  111. package/src/types.ts +196 -0
  112. package/src/usage/meter.ts +165 -0
  113. package/src/usage/store.ts +245 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@dejadex/web`. Versions follow semver; before 1.0 a
4
+ minor version may break the client contract (`src/types.ts`), and says so here.
5
+
6
+ ## 0.1.0 — first published release
7
+
8
+ - `createIdentifier`: card crops in, catalogue ids out, on the device
9
+ (onnxruntime-web: WebGPU, else WASM SIMD, threaded when the page is
10
+ cross-origin isolated) or through `POST /v1/identify` with the lease.
11
+ - Leases verified on the device with pinned Ed25519 keys (`leasePublicKeys`).
12
+ - Model and index downloads carry the lease's signature: the manifest URL's
13
+ Cloud CDN prefix signature (`URLPrefix`, `Expires`, `KeyName`, `Signature`)
14
+ is copied onto every file URL under that prefix, and onto no other.
15
+ - Usage counted on the device per distinct card per scan session, queued in
16
+ IndexedDB and handed to the host by `drainUsage`.
17
+ - **Contract:** usage is `UsageReport` (`reportId`, `leaseId`, `seq`,
18
+ `sessionId`, `distinctCards`, `cardIds?`, `calls?`, `at?`) in
19
+ `UsageBatch.reports`; it replaced the earlier per-identification
20
+ `UsageEvent`, before any release.
21
+ - `createScanner` and the live pipeline keep their final shape but are still stubs.
package/README.md CHANGED
@@ -1,3 +1,215 @@
1
- # Temporary Holding Version
1
+ # @dejadex/web
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Framework-free browser SDK for trading-card identification. Real today:
4
+
5
+ - **`createIdentifier`**: card crops in, catalogue ids out. Identification runs
6
+ on the device (onnxruntime-web: WebGPU, else WASM with SIMD, threaded when the
7
+ page is cross-origin isolated) or through `POST /v1/identify`. The answer has
8
+ the same shape either way. Leases are verified on the device. Usage is counted
9
+ on the device, one per distinct card per session.
10
+ - **The homography maths** (`geometry/`).
11
+
12
+ Still a stub, with its final shape: `createScanner`, the live pipeline
13
+ (detection, tracking, AR). See `openspec/changes/web-sdk-pipeline`.
14
+
15
+ ESM only. Published on the public npm registry, so it installs with no token:
16
+
17
+ ```
18
+ npm install @dejadex/web
19
+ ```
20
+
21
+ What protects the platform is the lease and the signed asset URLs, not the
22
+ SDK's code, which runs in browsers anyway. Releases are cut from a
23
+ `web-v<version>` tag by `.github/workflows/publish-web-sdk.yml` (npm trusted
24
+ publishing, no stored token); see `CHANGELOG.md`.
25
+
26
+ ## Usage
27
+
28
+ ```ts
29
+ import { createIdentifier } from '@dejadex/web'
30
+
31
+ const identifier = createIdentifier({
32
+ domain: 'tcg01_en',
33
+ // The service's lease-signing keys (its JWKS), pinned in the host's build.
34
+ leasePublicKeys: [{ kty: 'OKP', crv: 'Ed25519', x: '…', kid: '…' }],
35
+ // The identify route's base URL; omit for device-only.
36
+ serverUrl: 'https://api.dejadex.com',
37
+ identification: 'auto', // 'local' never sends an image; 'server' prefers the server
38
+ })
39
+
40
+ // The host's backend mints the lease (POST /v1/leases with its secret key)
41
+ // and passes { token, manifestUrl } through unchanged.
42
+ await identifier.setLease(await myBackend.getLease())
43
+
44
+ const outcome = await identifier.identify(crops /* ≤ 9 JPEG Blobs, one card each */, {
45
+ session: scanSessionId, // one per visit to the scanner; usage counts each card once in it
46
+ purpose: 'live', // 'live' | 'still' | 'retry' (retries are never charged)
47
+ top: 5,
48
+ signal,
49
+ })
50
+ if (outcome.ok) {
51
+ outcome.modelVersion // the model the thresholds belong to
52
+ for (const r of outcome.results) {
53
+ r.index // the crop's position
54
+ r.kind // 'card' | 'not_a_card'
55
+ r.confident // top1 − top2 ≥ the manifest's confidentMargin; never for not_a_card
56
+ r.candidates // [{ cardId, score }], best first
57
+ }
58
+ } else {
59
+ outcome.reason // 'unavailable' | 'timeout' | 'rejected'
60
+ outcome.detail // starts with a stable code where there is one: LEASE_EXPIRED, ALLOWANCE_EXHAUSTED, …
61
+ }
62
+
63
+ // While online, forward usage through the host's backend (it calls POST /v1/usage).
64
+ // A batch leaves the IndexedDB queue only when `deliver` resolves.
65
+ await identifier.drainUsage((batch) => myBackend.postUsage(batch))
66
+ ```
67
+
68
+ `identify` never throws. Other members: `prepare()` downloads and warms the
69
+ device engine and resolves its `DeviceEngineInfo` (backend, precision, threads,
70
+ measured `perCropMs`). `status()` reports the lease, the device engine and why
71
+ the server route is paused. `leaseRequest()` is the renewal body (see Usage).
72
+ `newSession()`, `pendingUsage()` and `destroy()` are what their names say.
73
+
74
+ ### Where a call runs
75
+
76
+ | `identification` | Device fast (WebGPU, or ≤ `slowDeviceBudgetMs` = 300 ms per crop) | Device slow, or model still downloading |
77
+ |---|---|---|
78
+ | `auto` | device | server, then device |
79
+ | `server` | server, then device | server, then device |
80
+ | `local` | device | device (waits for the model) |
81
+
82
+ The server is skipped, going straight to the device:
83
+
84
+ - while `navigator.onLine` is false;
85
+ - for the rest of a lease the server refused (401/403/409);
86
+ - until a 429's `Retry-After`;
87
+ - for 30 s after three failures in a row.
88
+
89
+ A server attempt waits at most the device's own time for the same crops, kept
90
+ between 300 ms and 3 s. While the device engine is still loading, it waits up
91
+ to `serverTimeoutMs` (8 s). After that the same crops are identified on the
92
+ device, and a late answer is ignored. A server answer from another model
93
+ version than the lease's is not used.
94
+
95
+ ### What the device engine does
96
+
97
+ It reads the manifest from the lease's `manifestUrl`, whose query is a Cloud CDN
98
+ prefix signature over the lease's model/index directory. The engine copies that
99
+ query onto each file the manifest lists under the signed prefix, on the same
100
+ origin, and onto no other URL. The manifest must match
101
+ the lease's model version, index version and shard. The engine then downloads
102
+ the index shard, its ids and the model file for this device: fp16 on WebGPU
103
+ with `shader-f16`, fp32 on WebGPU without it, and on WASM `runtime.wasmPrecision`,
104
+ fp16 by default. Every file is checked against its SHA-256 and size and cached
105
+ by digest in the Cache API (`dejadex-assets-v1`). Each crop is then:
106
+
107
+ 1. decoded without colour management;
108
+ 2. resized to 224 × 308 with a bit-exact port of Pillow's bilinear filter (the
109
+ gallery's preprocessing), and normalised with ImageNet mean and std;
110
+ 3. embedded at 0° and 180°, taking the L2-normalised CLS token;
111
+ 4. scored against every row by brute-force cosine, keeping the better of the
112
+ two orientations.
113
+
114
+ `kind` and `confident` come from the manifest's `notACardBelow` and
115
+ `confidentMargin`.
116
+
117
+ By default the engine runs in a dedicated module Worker:
118
+ `new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })`.
119
+ Vite, webpack 5 and Rollup bundle that pattern. With Vite, exclude the package
120
+ from pre-bundling (`optimizeDeps.exclude: ['@dejadex/web', 'onnxruntime-web']`),
121
+ or onnxruntime-web loses track of its WASM files. To build the Worker yourself,
122
+ point it at `@dejadex/web/identify-worker` and pass `engine: () => worker`.
123
+ `engine: 'inline'` runs the engine on the calling thread.
124
+
125
+ `runtime.wasmPaths` tells onnxruntime-web where its `.wasm` and `.mjs` files
126
+ are served, e.g. the dev server's `/ort/`.
127
+
128
+ ### Leases
129
+
130
+ `verifyLease(token, { publicKeys, origin, domains })` mirrors the service's
131
+ `service/src/leases/jws.ts`. It accepts an Ed25519 compact JWS with header
132
+ `{ alg: 'EdDSA', typ: 'dejadex-lease+jwt', kid? }` and refuses everything
133
+ else, `alg: none` included. It checks, in order, the signature, the claims'
134
+ shape and 24 h lifetime, expiry (with 60 s of clock skew for `iat`), the page
135
+ origin and the shard. Failures are `LeaseVerificationError`s, which are
136
+ `ScannerError`s whose `reason` is the service's own code: `LEASE_BAD_SIGNATURE`,
137
+ `LEASE_EXPIRED`, `LEASE_ORIGIN_NOT_ALLOWED`, `LEASE_DOMAIN_NOT_ALLOWED`, ….
138
+
139
+ Signatures are checked with WebCrypto Ed25519 where the browser has it
140
+ (Chrome 137+, Safari 17+, Firefox 129+). Elsewhere they fall back to
141
+ `@noble/ed25519` 3.2.0, which is MIT-licensed, audited and has no
142
+ dependencies. It is loaded only when needed.
143
+
144
+ ### Usage
145
+
146
+ Charged: a `kind: 'card'`, `confident` answer whose top `cardId` is new to the
147
+ session. Free: the same card again in the session, `not_a_card`, unsure
148
+ answers and `purpose: 'retry'`. An answer carrying an `identificationId` (the
149
+ service charged it itself) is left out of the report, but still counts toward
150
+ the lease's `allowance`. Once the allowance is used up, `identify` answers
151
+ `rejected` with `ALLOWANCE_EXHAUSTED`.
152
+
153
+ Each call is tallied per (lease, session) in IndexedDB (`dejadex-usage`) before
154
+ it returns. `drainUsage` turns the tallies into `UsageReport`s
155
+ (`{ reportId, leaseId, seq, sessionId, distinctCards, cardIds, calls, at }`,
156
+ with `seq` running per lease from 1). It hands them over in batches of at most
157
+ 500 as `{ deviceId, reports }`, and removes a batch only when `deliver` resolves.
158
+
159
+ Every lease gets at least one report, an empty one if nothing was identified
160
+ under it, because the service renews a lease only once the previous one has
161
+ reported. To renew, drain first, then send `await identifier.leaseRequest()`
162
+ (`{ previousLeaseId, usageReportedThrough }`) to the host's backend.
163
+ `leaseRequest(id)` names a lease the host stored but has not set (it expired,
164
+ or this page never identified); `leaseRequest(null)` asks as a new device.
165
+
166
+ ### Host requirements
167
+
168
+ - **CSP:** `script-src 'wasm-unsafe-eval'`, a `worker-src` for the SDK's
169
+ Worker, and a `connect-src` for the model CDN and the identify route.
170
+ - **Cross-origin isolation:** COOP `same-origin` plus COEP, needed for WASM
171
+ threads. Without it, WASM runs on one thread, which is several times slower.
172
+ Every cross-origin file must then carry CORP or CORS; the dev server sends both.
173
+ - **Privacy notice:** "The camera feed never leaves the device; on devices that
174
+ need it, the cropped image of a single card is sent for identification and
175
+ is not stored."
176
+
177
+ ## Development
178
+
179
+ ```
180
+ npm run build # tsc → dist/
181
+ npm test # vitest
182
+ npm run lint && npm run typecheck
183
+ npm run assets:dev # model + shard + ORT + dev leases on http://127.0.0.1:5350
184
+ npm run measure:identify # time identification on this machine (Node + headless Chromium)
185
+ ```
186
+
187
+ `assets:dev` serves the folder written by `ml/scripts/phase0_export_shard.py`
188
+ (default `ml/artifacts/phase0/shard`). Run that script first. The server provides:
189
+
190
+ - `GET /manifests/<model>/<index>/manifest.json`, plus the shard, ids and `.onnx`
191
+ files it lists, **only with a valid prefix signature**, checked as Cloud CDN
192
+ checks it: an unsigned, expired or wrongly signed request, or one outside the
193
+ lease's pair directory, gets 403 `SIGNATURE_REFUSED`. A URL that lost its
194
+ signature fails here as it would in production;
195
+ - `GET /ort/<file>`, onnxruntime-web's dist, for `runtime.wasmPaths`;
196
+ - `POST /dev/lease { origin }`, which answers `{ token, manifestUrl }`: a lease
197
+ signed with a development key, kept in `.secrets/dev-lease-ed25519.pem`
198
+ (gitignored), and the manifest URL prefix-signed until the lease's `exp`;
199
+ - `GET /dev/lease-key`, the development public key as a JWKS, which a host pins
200
+ in its development configuration.
201
+
202
+ The URL key is the development service's (`service/.secrets/manifest-url-dev.key`,
203
+ name `dev-manifest-key`, from `npm run dev:setup` there; created on first
204
+ start if missing, never printed), so leases from the development service with
205
+ `MODEL_CDN_BASE_URL=http://127.0.0.1:5350` load here as well. `--url-key` and
206
+ `--url-key-name` point elsewhere.
207
+
208
+ Every response carries CORS and `Cross-Origin-Resource-Policy: cross-origin`.
209
+ Private Network Access preflights are answered. To reach it from a phone, use
210
+ `adb reverse tcp:5350 tcp:5350`.
211
+
212
+ The preprocessing parity fixtures in `src/identify/__fixtures__/` come from
213
+ `ml/scripts/phase0_sdk_fixtures.py`: synthetic crops, never card images. The
214
+ embedding half of that test runs only where the gitignored model files are on
215
+ disk. `DEJADEX_REQUIRE_MODELS=1` turns their absence into a failure.
@@ -0,0 +1,8 @@
1
+ /** Machine-readable reasons the scanner refuses or stops. Stable; hosts branch on them. */
2
+ export type ScannerErrorCode = 'INVALID_OPTIONS' | 'NOT_IMPLEMENTED' | 'LEASE_INVALID' | 'LEASE_EXPIRED' | 'ALLOWANCE_EXHAUSTED' | 'ORIGIN_NOT_ALLOWED' | 'DOMAIN_NOT_ALLOWED' | 'UNSUPPORTED_DEVICE' | 'MODEL_INTEGRITY' | 'NETWORK';
3
+ export declare class ScannerError extends Error {
4
+ readonly code: ScannerErrorCode;
5
+ readonly name = "ScannerError";
6
+ constructor(code: ScannerErrorCode, message: string, options?: ErrorOptions);
7
+ }
8
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,eAAe,GACf,qBAAqB,GACrB,oBAAoB,GACpB,oBAAoB,GACpB,oBAAoB,GACpB,iBAAiB,GACjB,SAAS,CAAA;AAEb,qBAAa,YAAa,SAAQ,KAAK;IAInC,QAAQ,CAAC,IAAI,EAAE,gBAAgB;IAHjC,SAAkB,IAAI,kBAAiB;gBAG5B,IAAI,EAAE,gBAAgB,EAC/B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAIzB"}
package/dist/errors.js ADDED
@@ -0,0 +1,9 @@
1
+ export class ScannerError extends Error {
2
+ code;
3
+ name = 'ScannerError';
4
+ constructor(code, message, options) {
5
+ super(message, options);
6
+ this.code = code;
7
+ }
8
+ }
9
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAaA,MAAM,OAAO,YAAa,SAAQ,KAAK;IAI1B;IAHO,IAAI,GAAG,cAAc,CAAA;IAEvC,YACW,IAAsB,EAC/B,OAAe,EACf,OAAsB;QAEtB,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAJd,SAAI,GAAJ,IAAI,CAAkB;IAKjC,CAAC;CACF"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Planar homographies for AR labels: the projective map from a card's own
3
+ * unit rectangle to the four corners the detector found on screen, and its
4
+ * CSS `matrix3d` form, so a host page can lay a DOM element on the card's
5
+ * plane with nothing but `transform`.
6
+ *
7
+ * Conventions, fixed for every consumer:
8
+ *
9
+ * - Card space is the unit square: (0,0) top-left, (1,0) top-right, (1,1)
10
+ * bottom-right, (0,1) bottom-left of the card as printed (portrait).
11
+ * - Screen space is CSS pixels of the element the corners were measured in,
12
+ * y pointing down.
13
+ * - A `Quad` lists the screen corners in that same order: TL, TR, BR, BL.
14
+ * - A `Homography` is a 3×3 matrix in ROW-MAJOR order, normalised so the
15
+ * bottom-right entry is 1. It maps (u, v, 1) to (x·w, y·w, w).
16
+ *
17
+ * The square-to-quad solution is the closed form from P. Heckbert,
18
+ * "Fundamentals of Texture Mapping and Image Warping" (1989), §2.2.3: no
19
+ * linear solve, exact for any convex quad, and cheap enough to run per track
20
+ * per frame.
21
+ */
22
+ export interface Point {
23
+ readonly x: number;
24
+ readonly y: number;
25
+ }
26
+ /** Screen corners in card order: top-left, top-right, bottom-right, bottom-left. */
27
+ export type Quad = readonly [Point, Point, Point, Point];
28
+ /** Row-major 3×3 matrix, `[a, b, c, d, e, f, g, h, i]` with i = 1 once normalised. */
29
+ export type Homography = readonly [number, number, number, number, number, number, number, number, number];
30
+ export declare const IDENTITY: Homography;
31
+ /** The unit card rectangle's corners, in `Quad` order. */
32
+ export declare const UNIT_QUAD: Quad;
33
+ /**
34
+ * The homography that maps the unit card rectangle onto `quad`.
35
+ *
36
+ * Throws `RangeError` when no proper homography exists: a non-finite corner,
37
+ * three collinear corners (zero area), or a quad that is not convex — a
38
+ * concave or self-crossing quad would need the projective plane to fold, and
39
+ * the label would be drawn through the horizon.
40
+ */
41
+ export declare function unitSquareToQuad(quad: Quad): Homography;
42
+ /** Maps a point through `m`. Throws `RangeError` for a point on the vanishing line. */
43
+ export declare function applyHomography(m: Homography, p: Point): Point;
44
+ /** `a · b` (apply `b` first, then `a`), normalised. */
45
+ export declare function multiplyHomography(a: Homography, b: Homography): Homography;
46
+ /** The inverse map, normalised. Throws `RangeError` for a singular matrix. */
47
+ export declare function invertHomography(m: Homography): Homography;
48
+ /** Scales `m` so its bottom-right entry is 1. */
49
+ export declare function normalise(m: readonly number[]): Homography;
50
+ export interface ElementSize {
51
+ /** The label element's layout width in CSS pixels (its `offsetWidth`). */
52
+ readonly width: number;
53
+ /** The label element's layout height in CSS pixels (its `offsetHeight`). */
54
+ readonly height: number;
55
+ }
56
+ /**
57
+ * The CSS `matrix3d(...)` that draws an element of `size` onto the quad that
58
+ * `m` maps the unit card to.
59
+ *
60
+ * The element must be absolutely positioned at the origin of the coordinate
61
+ * space the corners were measured in, with `transform-origin: 0 0`. Its own
62
+ * box (0..width, 0..height) is first scaled to the unit square, then sent
63
+ * through `m`. The 3×3 homography becomes the 4×4
64
+ *
65
+ * | a b 0 c |
66
+ * | d e 0 f |
67
+ * | 0 0 1 0 |
68
+ * | g h 0 i |
69
+ *
70
+ * which leaves z alone, and CSS takes it in COLUMN-major order.
71
+ */
72
+ export declare function toCssMatrix3d(m: Homography, size?: ElementSize): string;
73
+ /** Convenience for the common case: corners in, CSS transform out. */
74
+ export declare function quadToCssMatrix3d(quad: Quad, size?: ElementSize): string;
75
+ //# sourceMappingURL=homography.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"homography.d.ts","sourceRoot":"","sources":["../../src/geometry/homography.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,oFAAoF;AACpF,MAAM,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;AAExD,sFAAsF;AACtF,MAAM,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAE1G,eAAO,MAAM,QAAQ,EAAE,UAAwC,CAAA;AAE/D,0DAA0D;AAC1D,eAAO,MAAM,SAAS,EAAE,IAKvB,CAAA;AAOD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAwCvD;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,GAAG,KAAK,CAK9D;AAED,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,UAAU,CAI3E;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,UAAU,GAAG,UAAU,CAY1D;AAED,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAI1D;AAED,MAAM,WAAW,WAAW;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,UAAU,EAAE,IAAI,GAAE,WAAqC,GAAG,MAAM,CAMhG;AAUD,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,MAAM,CAExE"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Planar homographies for AR labels: the projective map from a card's own
3
+ * unit rectangle to the four corners the detector found on screen, and its
4
+ * CSS `matrix3d` form, so a host page can lay a DOM element on the card's
5
+ * plane with nothing but `transform`.
6
+ *
7
+ * Conventions, fixed for every consumer:
8
+ *
9
+ * - Card space is the unit square: (0,0) top-left, (1,0) top-right, (1,1)
10
+ * bottom-right, (0,1) bottom-left of the card as printed (portrait).
11
+ * - Screen space is CSS pixels of the element the corners were measured in,
12
+ * y pointing down.
13
+ * - A `Quad` lists the screen corners in that same order: TL, TR, BR, BL.
14
+ * - A `Homography` is a 3×3 matrix in ROW-MAJOR order, normalised so the
15
+ * bottom-right entry is 1. It maps (u, v, 1) to (x·w, y·w, w).
16
+ *
17
+ * The square-to-quad solution is the closed form from P. Heckbert,
18
+ * "Fundamentals of Texture Mapping and Image Warping" (1989), §2.2.3: no
19
+ * linear solve, exact for any convex quad, and cheap enough to run per track
20
+ * per frame.
21
+ */
22
+ export const IDENTITY = [1, 0, 0, 0, 1, 0, 0, 0, 1];
23
+ /** The unit card rectangle's corners, in `Quad` order. */
24
+ export const UNIT_QUAD = [
25
+ { x: 0, y: 0 },
26
+ { x: 1, y: 0 },
27
+ { x: 1, y: 1 },
28
+ { x: 0, y: 1 },
29
+ ];
30
+ /** Relative tolerance below which a quad or a matrix counts as degenerate. */
31
+ const DEGENERATE_EPS = 1e-12;
32
+ const isFiniteQuad = (q) => q.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y));
33
+ /**
34
+ * The homography that maps the unit card rectangle onto `quad`.
35
+ *
36
+ * Throws `RangeError` when no proper homography exists: a non-finite corner,
37
+ * three collinear corners (zero area), or a quad that is not convex — a
38
+ * concave or self-crossing quad would need the projective plane to fold, and
39
+ * the label would be drawn through the horizon.
40
+ */
41
+ export function unitSquareToQuad(quad) {
42
+ if (!isFiniteQuad(quad))
43
+ throw new RangeError('quad has a non-finite corner');
44
+ const [p0, p1, p2, p3] = quad;
45
+ const dx1 = p1.x - p2.x;
46
+ const dx2 = p3.x - p2.x;
47
+ const dy1 = p1.y - p2.y;
48
+ const dy2 = p3.y - p2.y;
49
+ const sx = p0.x - p1.x + p2.x - p3.x;
50
+ const sy = p0.y - p1.y + p2.y - p3.y;
51
+ const span = Math.max(...quad.map((p) => Math.abs(p.x - p0.x)), ...quad.map((p) => Math.abs(p.y - p0.y)));
52
+ const den = dx1 * dy2 - dx2 * dy1;
53
+ if (span === 0 || Math.abs(den) <= DEGENERATE_EPS * span * span) {
54
+ throw new RangeError('quad is degenerate (zero area or collinear corners)');
55
+ }
56
+ const g = (sx * dy2 - dx2 * sy) / den;
57
+ const h = (dx1 * sy - sx * dy1) / den;
58
+ // w at each corner: (0,0) → 1, (1,0) → 1+g, (1,1) → 1+g+h, (0,1) → 1+h.
59
+ // All must share a sign, or the quad is not the image of a convex square.
60
+ if (1 + g <= DEGENERATE_EPS || 1 + h <= DEGENERATE_EPS || 1 + g + h <= DEGENERATE_EPS) {
61
+ throw new RangeError('quad is not convex');
62
+ }
63
+ return [
64
+ p1.x - p0.x + g * p1.x,
65
+ p3.x - p0.x + h * p3.x,
66
+ p0.x,
67
+ p1.y - p0.y + g * p1.y,
68
+ p3.y - p0.y + h * p3.y,
69
+ p0.y,
70
+ g,
71
+ h,
72
+ 1,
73
+ ];
74
+ }
75
+ /** Maps a point through `m`. Throws `RangeError` for a point on the vanishing line. */
76
+ export function applyHomography(m, p) {
77
+ const [a, b, c, d, e, f, g, h, i] = m;
78
+ const w = g * p.x + h * p.y + i;
79
+ if (Math.abs(w) <= DEGENERATE_EPS)
80
+ throw new RangeError('point maps to infinity');
81
+ return { x: (a * p.x + b * p.y + c) / w, y: (d * p.x + e * p.y + f) / w };
82
+ }
83
+ /** `a · b` (apply `b` first, then `a`), normalised. */
84
+ export function multiplyHomography(a, b) {
85
+ const r = (row, col) => a[row * 3] * b[col] + a[row * 3 + 1] * b[3 + col] + a[row * 3 + 2] * b[6 + col];
86
+ return normalise([r(0, 0), r(0, 1), r(0, 2), r(1, 0), r(1, 1), r(1, 2), r(2, 0), r(2, 1), r(2, 2)]);
87
+ }
88
+ /** The inverse map, normalised. Throws `RangeError` for a singular matrix. */
89
+ export function invertHomography(m) {
90
+ const [a, b, c, d, e, f, g, h, i] = m;
91
+ const A = e * i - f * h;
92
+ const B = -(d * i - f * g);
93
+ const C = d * h - e * g;
94
+ const det = a * A + b * B + c * C;
95
+ const scale = Math.max(...m.map(Math.abs));
96
+ if (!Number.isFinite(det) || Math.abs(det) <= DEGENERATE_EPS * scale * scale * scale) {
97
+ throw new RangeError('homography is singular');
98
+ }
99
+ // Adjugate (transposed cofactors); the 1/det factor cancels in normalise().
100
+ return normalise([A, -(b * i - c * h), b * f - c * e, B, a * i - c * g, -(a * f - c * d), C, -(a * h - b * g), a * e - b * d]);
101
+ }
102
+ /** Scales `m` so its bottom-right entry is 1. */
103
+ export function normalise(m) {
104
+ const k = m[8];
105
+ if (Math.abs(k) <= DEGENERATE_EPS)
106
+ throw new RangeError('homography cannot be normalised (h33 = 0)');
107
+ return m.map((v) => v / k);
108
+ }
109
+ /**
110
+ * The CSS `matrix3d(...)` that draws an element of `size` onto the quad that
111
+ * `m` maps the unit card to.
112
+ *
113
+ * The element must be absolutely positioned at the origin of the coordinate
114
+ * space the corners were measured in, with `transform-origin: 0 0`. Its own
115
+ * box (0..width, 0..height) is first scaled to the unit square, then sent
116
+ * through `m`. The 3×3 homography becomes the 4×4
117
+ *
118
+ * | a b 0 c |
119
+ * | d e 0 f |
120
+ * | 0 0 1 0 |
121
+ * | g h 0 i |
122
+ *
123
+ * which leaves z alone, and CSS takes it in COLUMN-major order.
124
+ */
125
+ export function toCssMatrix3d(m, size = { width: 1, height: 1 }) {
126
+ if (!(size.width > 0) || !(size.height > 0))
127
+ throw new RangeError('element size must be positive');
128
+ const scaled = multiplyHomography(m, [1 / size.width, 0, 0, 0, 1 / size.height, 0, 0, 0, 1]);
129
+ const [a, b, c, d, e, f, g, h, i] = scaled;
130
+ const columnMajor = [a, d, 0, g, b, e, 0, h, 0, 0, 1, 0, c, f, 0, i];
131
+ return `matrix3d(${columnMajor.map(formatCssNumber).join(', ')})`;
132
+ }
133
+ /** Fixed precision, no `-0`, no exponent: what every engine parses identically. */
134
+ function formatCssNumber(v) {
135
+ if (!Number.isFinite(v))
136
+ throw new RangeError('non-finite matrix entry');
137
+ const fixed = Number(v.toFixed(12));
138
+ if (fixed === 0)
139
+ return '0';
140
+ return fixed.toFixed(12).replace(/\.?0+$/, '');
141
+ }
142
+ /** Convenience for the common case: corners in, CSS transform out. */
143
+ export function quadToCssMatrix3d(quad, size) {
144
+ return toCssMatrix3d(unitSquareToQuad(quad), size);
145
+ }
146
+ //# sourceMappingURL=homography.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"homography.js","sourceRoot":"","sources":["../../src/geometry/homography.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { ManifestFile } from './manifest.js';
2
+ /** The Cache API cache the SDK owns. Bump the suffix if the key scheme changes. */
3
+ export declare const CACHE_NAME = "dejadex-assets-v1";
4
+ export declare const MANIFEST_TIMEOUT_MS = 10000;
5
+ export type FetchLike = (input: string, init: {
6
+ signal: AbortSignal;
7
+ cache?: RequestCache;
8
+ }) => Promise<Response>;
9
+ /** The subset of `CacheStorage` used, so tests and hosts without one can pass their own (or none). */
10
+ export interface CacheLike {
11
+ match(key: string): Promise<Response | undefined>;
12
+ put(key: string, response: Response): Promise<void>;
13
+ delete(key: string): Promise<boolean>;
14
+ keys(): Promise<readonly {
15
+ readonly url: string;
16
+ }[]>;
17
+ }
18
+ export interface AssetStoreOptions {
19
+ readonly fetch: FetchLike;
20
+ /** null: no cache (every start downloads). */
21
+ readonly cache: CacheLike | null;
22
+ /** Overrides the per-file bound (ms); default from the size, at ≥ 100 kB/s. */
23
+ readonly fileTimeoutMs?: number;
24
+ }
25
+ export interface AssetStore {
26
+ /** The file's bytes, from the cache or the network, verified. */
27
+ get(file: ManifestFile, signal?: AbortSignal): Promise<ArrayBuffer>;
28
+ /** The manifest document from `url`; offline, the last one that loaded from it. */
29
+ manifest(url: string, signal?: AbortSignal): Promise<unknown>;
30
+ /** Drops cached files whose digest is not in `keep` (after a new manifest verified). */
31
+ evictExcept(keep: readonly string[]): Promise<number>;
32
+ }
33
+ export declare function sha256Hex(data: ArrayBuffer): Promise<string>;
34
+ /** `AbortSignal.any` where the runtime has it, else a controller following both. */
35
+ export declare function anySignal(signals: readonly (AbortSignal | undefined)[]): AbortSignal;
36
+ export declare function createAssetStore(options: AssetStoreOptions): AssetStore;
37
+ /** The page's Cache API cache, or null where there is none (or it is blocked). */
38
+ export declare function openDefaultCache(): Promise<CacheLike | null>;
39
+ //# sourceMappingURL=assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/identify/assets.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEjD,mFAAmF;AACnF,eAAO,MAAM,UAAU,sBAAsB,CAAA;AAO7C,eAAO,MAAM,mBAAmB,QAAS,CAAA;AAEzC,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,KAAK,CAAC,EAAE,YAAY,CAAA;CAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAEjH,sGAAsG;AACtG,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAA;IACjD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACrC,IAAI,IAAI,OAAO,CAAC,SAAS;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAA;CACrD;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAChC;AAED,MAAM,WAAW,UAAU;IACzB,iEAAiE;IACjE,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IACnE,mFAAmF;IACnF,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC7D,wFAAwF;IACxF,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACtD;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlE;AASD,oFAAoF;AACpF,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,SAAS,CAAC,EAAE,GAAG,WAAW,CAapF;AAID,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CA0FvE;AAED,kFAAkF;AAClF,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAalE"}