@sorisdk/web-audio 0.6.3 → 0.6.4

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 (3) hide show
  1. package/README.md +64 -3
  2. package/dist/index.js +24 -3
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -22,7 +22,7 @@ configuration, or Node-based asset server is required:
22
22
  ```html
23
23
  <script type="module">
24
24
  import { AudioRecognizer } from
25
- "https://cdn.iplateia.com/web/sorisdk/v0.6.3/sori-web-audio.mjs";
25
+ "https://cdn.iplateia.com/web/sorisdk/v0.6.4/sori-web-audio.mjs";
26
26
 
27
27
  const recognizer = new AudioRecognizer({
28
28
  appId: "YOUR_APP_ID",
@@ -49,7 +49,7 @@ configuration, or Node-based asset server is required:
49
49
  ```
50
50
 
51
51
  Static URL imports are valid inside `<script type="module">`. Dynamic
52
- `await import("https://cdn.iplateia.com/web/sorisdk/v0.6.3/sori-web-audio.mjs")`
52
+ `await import("https://cdn.iplateia.com/web/sorisdk/v0.6.4/sori-web-audio.mjs")`
53
53
  is an optional alternative when the SDK should be loaded conditionally.
54
54
 
55
55
  Pin an exact version in production. Do not construct a mutable `latest` URL.
@@ -93,7 +93,7 @@ An import map can give the standalone URL the npm package name:
93
93
  <script type="importmap">
94
94
  {
95
95
  "imports": {
96
- "@sorisdk/web-audio": "https://cdn.iplateia.com/web/sorisdk/v0.6.3/sori-web-audio.mjs"
96
+ "@sorisdk/web-audio": "https://cdn.iplateia.com/web/sorisdk/v0.6.4/sori-web-audio.mjs"
97
97
  }
98
98
  }
99
99
  </script>
@@ -159,6 +159,67 @@ await recognizer.stop();
159
159
  await recognizer.destroy();
160
160
  ```
161
161
 
162
+ ## Session identifiers
163
+
164
+ `AudioRecognizer` creates one pseudonymous session identifier per application
165
+ and stores it in `localStorage` under
166
+ `sorisdk:web-audio:session:<appId>`. The value persists until the origin's
167
+ storage is cleared or the session manager's `clear()` method is called. An
168
+ upgrade does not rotate an existing non-empty value, including identifiers
169
+ created by older SDK versions.
170
+
171
+ New identifiers use `crypto.randomUUID()`. Browsers without `randomUUID()` use
172
+ `crypto.getRandomValues()` to create an RFC 4122 UUID v4. If neither secure API
173
+ is available, session creation fails instead of falling back to predictable
174
+ randomness. A host for such an environment must provide a cryptographically
175
+ secure generator explicitly:
176
+
177
+ ```ts
178
+ import {
179
+ AudioRecognizer,
180
+ createLocalStorageSessionManager
181
+ } from "@sorisdk/web-audio";
182
+
183
+ const sessionManager = createLocalStorageSessionManager({
184
+ key: "sorisdk:web-audio:session:YOUR_APP_ID",
185
+ generateSessionId: () => secureSessionIdFromYourRuntime()
186
+ });
187
+
188
+ const recognizer = new AudioRecognizer({
189
+ appId: "YOUR_APP_ID",
190
+ ephemeralKey: fetchEphemeralKeyFromYourServer,
191
+ sessionManager
192
+ });
193
+
194
+ // Stop recognition before intentionally rotating the identifier.
195
+ await recognizer.destroy();
196
+ await sessionManager.clear?.();
197
+ ```
198
+
199
+ Treat the identifier as persistent pseudonymous data: do not include personal
200
+ information in a custom value, and do not log or expose it unnecessarily.
201
+ Clearing it breaks device and activity continuity; the next authentication
202
+ creates a new server-side device identity.
203
+
204
+ ### SORI service contract
205
+
206
+ The current SORI service uses the identifier at these boundaries:
207
+
208
+ | Boundary | Use of `sessionId` | Security contract |
209
+ | --- | --- | --- |
210
+ | Authentication request | Debounces repeated requests and upserts an account-scoped device record | A valid application ID plus secret or ephemeral key is still required |
211
+ | Session token | Stored as the signed device claim | Possession of the raw identifier does not create or validate a token |
212
+ | Recognition activity | Attributes impressions, clicks, campaign links, and campaign webhooks to a device | Authorization comes from the signed token, not the identifier |
213
+ | Monitoring | Binds stored health and transition data to the token's device claim | A submitted device ID must match the signed claim |
214
+ | Authentication webhook | Populates the webhook `device_id` | The value is correlation data, not a webhook credential |
215
+ | Quota and throttling | Selects the authentication debounce bucket only | It is not a standalone billing or usage quota key |
216
+ | AudioPack caching | Not used as a cache partition | The browser cache remains application-scoped |
217
+ | Replay protection | No use | The stable identifier provides neither freshness nor replay protection |
218
+
219
+ Authorization, quota enforcement, cache isolation, and replay controls must
220
+ remain bound to authenticated server-side state rather than possession or
221
+ unpredictability of `sessionId`.
222
+
162
223
  ## Advanced wasm loading overrides
163
224
 
164
225
  If you need to pin explicit generated modules, use the nested `wasm` options:
package/dist/index.js CHANGED
@@ -201,6 +201,7 @@ var WrappedFingerprintMatchWindow = class {
201
201
  constructor(inner) {
202
202
  this.inner = inner;
203
203
  }
204
+ inner;
204
205
  appendFingerprint(bytes) {
205
206
  const fn = getCallable(this.inner, ["appendFingerprint", "append_fingerprint"]);
206
207
  if (!fn) {
@@ -1025,11 +1026,31 @@ function resolveStorage2(storage) {
1025
1026
  }
1026
1027
  return globalThis.localStorage;
1027
1028
  }
1029
+ function formatUuidV4(bytes) {
1030
+ bytes[6] = bytes[6] & 15 | 64;
1031
+ bytes[8] = bytes[8] & 63 | 128;
1032
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
1033
+ return [
1034
+ hex.slice(0, 8),
1035
+ hex.slice(8, 12),
1036
+ hex.slice(12, 16),
1037
+ hex.slice(16, 20),
1038
+ hex.slice(20)
1039
+ ].join("-");
1040
+ }
1028
1041
  function defaultGenerateSessionId() {
1029
- if (typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.randomUUID === "function") {
1030
- return globalThis.crypto.randomUUID();
1042
+ const crypto = globalThis.crypto;
1043
+ if (typeof crypto !== "undefined") {
1044
+ if (typeof crypto.randomUUID === "function") {
1045
+ return crypto.randomUUID();
1046
+ }
1047
+ if (typeof crypto.getRandomValues === "function") {
1048
+ return formatUuidV4(crypto.getRandomValues(new Uint8Array(16)));
1049
+ }
1031
1050
  }
1032
- return `session-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
1051
+ throw new Error(
1052
+ "Secure random number generation is unavailable; provide a cryptographically secure `generateSessionId`"
1053
+ );
1033
1054
  }
1034
1055
  function createLocalStorageSessionManager(options) {
1035
1056
  const storage = resolveStorage2(options.storage);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sorisdk/web-audio",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "Web SDK for browser-based audio recognition with SORI API",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,13 +34,13 @@
34
34
  "license": "SEE LICENSE IN LICENSE.md",
35
35
  "homepage": "https://docs.soriapi.com/ko/integration/web",
36
36
  "dependencies": {
37
- "@sorisdk/matcher": "0.6.3",
38
- "@sorisdk/afpgen": "0.6.3"
37
+ "@sorisdk/matcher": "0.6.4",
38
+ "@sorisdk/afpgen": "0.6.4"
39
39
  },
40
40
  "devDependencies": {
41
41
  "tsup": "^8.5.1",
42
42
  "typescript": "^5.8.3",
43
- "vitest": "^3.2.4"
43
+ "vitest": "^3.2.6"
44
44
  },
45
45
  "scripts": {
46
46
  "build:wasm": "node ../../scripts/build-wasm-package.mjs web-audio",