miakapi 3.0.31 → 4.0.0-alpha.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 (124) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +358 -0
  3. package/contracts/miakapp-v3.json +6 -0
  4. package/dist/access-token-provider.d.ts +8 -0
  5. package/dist/access-token-provider.d.ts.map +1 -0
  6. package/dist/access-token-provider.js +167 -0
  7. package/dist/access-token-provider.js.map +1 -0
  8. package/dist/api.d.ts +224 -0
  9. package/dist/api.d.ts.map +1 -0
  10. package/dist/api.js +47 -0
  11. package/dist/api.js.map +1 -0
  12. package/dist/browser-api.d.ts +113 -0
  13. package/dist/browser-api.d.ts.map +1 -0
  14. package/dist/browser-api.js +2 -0
  15. package/dist/browser-api.js.map +1 -0
  16. package/dist/browser-client.d.ts +3 -0
  17. package/dist/browser-client.d.ts.map +1 -0
  18. package/dist/browser-client.js +678 -0
  19. package/dist/browser-client.js.map +1 -0
  20. package/dist/browser-relay-credential-provider.d.ts +3 -0
  21. package/dist/browser-relay-credential-provider.d.ts.map +1 -0
  22. package/dist/browser-relay-credential-provider.js +243 -0
  23. package/dist/browser-relay-credential-provider.js.map +1 -0
  24. package/dist/browser.d.ts +4 -0
  25. package/dist/browser.d.ts.map +1 -0
  26. package/dist/browser.js +4 -0
  27. package/dist/browser.js.map +1 -0
  28. package/dist/coordinator.d.ts +3 -0
  29. package/dist/coordinator.d.ts.map +1 -0
  30. package/dist/coordinator.js +544 -0
  31. package/dist/coordinator.js.map +1 -0
  32. package/dist/index.d.ts +4 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +4 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/internal/browser-errors.d.ts +31 -0
  37. package/dist/internal/browser-errors.d.ts.map +1 -0
  38. package/dist/internal/browser-errors.js +69 -0
  39. package/dist/internal/browser-errors.js.map +1 -0
  40. package/dist/internal/browser-socket.d.ts +8 -0
  41. package/dist/internal/browser-socket.d.ts.map +1 -0
  42. package/dist/internal/browser-socket.js +204 -0
  43. package/dist/internal/browser-socket.js.map +1 -0
  44. package/dist/internal/calls.d.ts +33 -0
  45. package/dist/internal/calls.d.ts.map +1 -0
  46. package/dist/internal/calls.js +525 -0
  47. package/dist/internal/calls.js.map +1 -0
  48. package/dist/internal/control-plane-response.d.ts +10 -0
  49. package/dist/internal/control-plane-response.d.ts.map +1 -0
  50. package/dist/internal/control-plane-response.js +283 -0
  51. package/dist/internal/control-plane-response.js.map +1 -0
  52. package/dist/internal/declarations.d.ts +62 -0
  53. package/dist/internal/declarations.d.ts.map +1 -0
  54. package/dist/internal/declarations.js +427 -0
  55. package/dist/internal/declarations.js.map +1 -0
  56. package/dist/internal/errors.d.ts +31 -0
  57. package/dist/internal/errors.d.ts.map +1 -0
  58. package/dist/internal/errors.js +72 -0
  59. package/dist/internal/errors.js.map +1 -0
  60. package/dist/internal/events.d.ts +27 -0
  61. package/dist/internal/events.d.ts.map +1 -0
  62. package/dist/internal/events.js +207 -0
  63. package/dist/internal/events.js.map +1 -0
  64. package/dist/internal/presence.d.ts +15 -0
  65. package/dist/internal/presence.d.ts.map +1 -0
  66. package/dist/internal/presence.js +99 -0
  67. package/dist/internal/presence.js.map +1 -0
  68. package/dist/internal/resources.d.ts +33 -0
  69. package/dist/internal/resources.d.ts.map +1 -0
  70. package/dist/internal/resources.js +136 -0
  71. package/dist/internal/resources.js.map +1 -0
  72. package/dist/internal/runtime.d.ts +34 -0
  73. package/dist/internal/runtime.d.ts.map +1 -0
  74. package/dist/internal/runtime.js +17 -0
  75. package/dist/internal/runtime.js.map +1 -0
  76. package/dist/internal/session.d.ts +33 -0
  77. package/dist/internal/session.d.ts.map +1 -0
  78. package/dist/internal/session.js +199 -0
  79. package/dist/internal/session.js.map +1 -0
  80. package/dist/internal/socket.d.ts +6 -0
  81. package/dist/internal/socket.d.ts.map +1 -0
  82. package/dist/internal/socket.js +176 -0
  83. package/dist/internal/socket.js.map +1 -0
  84. package/dist/internal/state.d.ts +21 -0
  85. package/dist/internal/state.d.ts.map +1 -0
  86. package/dist/internal/state.js +133 -0
  87. package/dist/internal/state.js.map +1 -0
  88. package/dist/internal/user-calls.d.ts +27 -0
  89. package/dist/internal/user-calls.d.ts.map +1 -0
  90. package/dist/internal/user-calls.js +325 -0
  91. package/dist/internal/user-calls.js.map +1 -0
  92. package/dist/internal/user-session.d.ts +36 -0
  93. package/dist/internal/user-session.d.ts.map +1 -0
  94. package/dist/internal/user-session.js +250 -0
  95. package/dist/internal/user-session.js.map +1 -0
  96. package/dist/internal/user-state.d.ts +21 -0
  97. package/dist/internal/user-state.d.ts.map +1 -0
  98. package/dist/internal/user-state.js +240 -0
  99. package/dist/internal/user-state.js.map +1 -0
  100. package/dist/internal/validation.d.ts +32 -0
  101. package/dist/internal/validation.d.ts.map +1 -0
  102. package/dist/internal/validation.js +553 -0
  103. package/dist/internal/validation.js.map +1 -0
  104. package/dist/protocol/codec.d.ts +75 -0
  105. package/dist/protocol/codec.d.ts.map +1 -0
  106. package/dist/protocol/codec.js +1091 -0
  107. package/dist/protocol/codec.js.map +1 -0
  108. package/dist/protocol/session.d.ts +14 -0
  109. package/dist/protocol/session.d.ts.map +1 -0
  110. package/dist/protocol/session.js +125 -0
  111. package/dist/protocol/session.js.map +1 -0
  112. package/dist/protocol/user-session.d.ts +14 -0
  113. package/dist/protocol/user-session.d.ts.map +1 -0
  114. package/dist/protocol/user-session.js +116 -0
  115. package/dist/protocol/user-session.js.map +1 -0
  116. package/package.json +57 -9
  117. package/.eslintrc.js +0 -16
  118. package/.gitattributes +0 -2
  119. package/main.js +0 -305
  120. package/miakode.js +0 -56
  121. package/screenshots/bigGrid.jpg +0 -0
  122. package/screenshots/formExample.jpg +0 -0
  123. package/screenshots/properties&table.jpg +0 -0
  124. package/test.js +0 -45
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Mathieu Colmon
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,358 @@
1
+ # MiakAPI
2
+
3
+ MiakAPI is the typed SDK for running a trusted Bun/TypeScript coordinator and
4
+ connecting a first-party browser application to a Miakapp home. A coordinator
5
+ owns complete state, access, event, and function declarations for one
6
+ integration. The isolated browser entry point exposes the authenticated user role
7
+ without bundling server-runtime or coordinator-only dependencies.
8
+
9
+ Version 4 is a complete replacement for the legacy callback-based MiakAPI 3
10
+ client. It is currently an alpha while the Miakapp 4 stack is being completed.
11
+
12
+ **Building a home with a coding agent?** Read
13
+ [docs/agent-guide.md](docs/agent-guide.md) first. It covers the judgment the
14
+ reference below does not: what the coordinator must authorize, why component
15
+ requirements and coordinator grants intersect silently, and which failures must
16
+ never be retried. Start from [`templates/home`](templates/home).
17
+
18
+ ## Coordinator requirements
19
+
20
+ - Bun 1.2.23 or newer (primary coordinator runtime)
21
+ - Node.js 22.9 or newer when running compatibility or migration tooling
22
+ - A Miakapp Home Key or another approved short-lived access-token provider
23
+ - A Miakapp relay implementing wire protocol 1.0
24
+
25
+ The default `miakapi` entry point is server-side software. Do not ship
26
+ coordinator credentials, Home Keys, or coordinator access-token providers to a
27
+ browser or an untrusted plugin runtime.
28
+
29
+ ## Installation
30
+
31
+ ```sh
32
+ bun add miakapi@next
33
+ ```
34
+
35
+ Alpha releases use the `next` npm tag. The package is ESM-only and remains
36
+ Node-compatible so runtime-specific migration adapters can reuse the same SDK.
37
+
38
+ ## Quick start
39
+
40
+ ```ts
41
+ import {
42
+ ApplicationCallError,
43
+ EventDirection,
44
+ createCoordinator,
45
+ createHomeKeyAccessTokenProvider,
46
+ } from 'miakapi';
47
+
48
+ const homeKey = process.env.MIAKAPP_HOME_KEY;
49
+ if (homeKey === undefined) throw new Error('MIAKAPP_HOME_KEY is required');
50
+
51
+ const coordinator = createCoordinator({
52
+ name: 'home-assistant',
53
+ accessTokenProvider: createHomeKeyAccessTokenProvider({
54
+ exchangeEndpoint: 'https://control.miakapp.com/v1/access-tokens:exchange',
55
+ homeKey,
56
+ }),
57
+ });
58
+
59
+ coordinator.configure({
60
+ state: {
61
+ 'climate.living_room.temperature': 20,
62
+ },
63
+ stateAccess: [{
64
+ userId: 'user-id',
65
+ patterns: ['climate.living_room.*'],
66
+ }],
67
+ events: [{
68
+ topic: 'climate.living_room.changed',
69
+ directions:
70
+ EventDirection.acceptFromUsers |
71
+ EventDirection.publishToUsers,
72
+ }],
73
+ eventAccess: [{
74
+ userId: 'user-id',
75
+ publish: ['climate.living_room.changed'],
76
+ subscribe: ['climate.living_room.changed'],
77
+ }],
78
+ functions: {
79
+ async 'climate.living_room.set_target'(call) {
80
+ if (typeof call.arguments !== 'number') {
81
+ throw new ApplicationCallError(2001, 'Target must be a number');
82
+ }
83
+ await call.emit({ phase: 'applying' });
84
+ return { accepted: true, target: call.arguments };
85
+ },
86
+ },
87
+ });
88
+
89
+ coordinator.subscribe(({ current, reason }) => {
90
+ console.log('Miakapp coordinator status:', current, reason?.kind);
91
+ });
92
+
93
+ const session = await coordinator.start();
94
+ console.log('Ready in generation', session.generation);
95
+ ```
96
+
97
+ A complete minimal Bun coordinator matching the Miakapp V4 staging browser is
98
+ available in [`examples/synthetic-coordinator.ts`](examples/synthetic-coordinator.ts).
99
+ It publishes a small home state and implements one real `lighting.toggle` call.
100
+ Run it only in a trusted backend process:
101
+
102
+ ```sh
103
+ bun run build
104
+ bun run examples/synthetic-coordinator.ts
105
+ ```
106
+
107
+ The examples import `miakapi` the way an application does, so they resolve
108
+ through the package's own `exports` and need `dist` to exist. `bun run check`
109
+ builds before it tests for the same reason.
110
+
111
+ The process reads `MIAKAPP_HOME_KEY`,
112
+ `MIAKAPP_CONTROL_PLANE_EXCHANGE_ENDPOINT`, and `MIAKAPP_OWNER_USER_ID` from its
113
+ runtime environment. The Home Key is the only secret; the endpoint and Firebase
114
+ user ID are identifiers. Never pass any of them as command-line arguments.
115
+
116
+ The Home Key provider makes exactly one exchange request for each initial,
117
+ reauthentication, or reconnect demand from the SDK. It sends the Home Key only
118
+ to the configured HTTPS control-plane endpoint, rejects redirects and open or
119
+ overlong responses, and returns only the relay URL, compact access token, and
120
+ expiry to the coordinator core. It performs no independent retry; the
121
+ coordinator's single bounded reconnect schedule remains authoritative.
122
+
123
+ Keep the Home Key in the trusted coordinator backend. Do not place it in a web
124
+ bundle, browser storage, logs, URLs, or relay configuration. Applications with a
125
+ different approved credential store may continue to implement
126
+ `AccessTokenProvider` directly.
127
+
128
+ ### Running a coordinator as a service
129
+
130
+ [`examples/synthetic-coordinator-service.ts`](examples/synthetic-coordinator-service.ts)
131
+ wraps the same coordinator in the shape a container platform expects: an HTTP
132
+ listener that answers immediately, a `/healthz` probe that reports the live
133
+ lifecycle status, graceful `SIGTERM` draining, and a non-zero exit when the
134
+ coordinator stops without a shutdown signal so the platform restarts it.
135
+ [`examples/deploy/Dockerfile`](examples/deploy/Dockerfile) builds it from the
136
+ repository root.
137
+
138
+ Run exactly one coordinator per home. Two coordinators sharing a Home Key take
139
+ turns displacing each other on the relay; the reconnect schedule backs off so
140
+ the fight stays cheap, but only one of them holds the home.
141
+
142
+ `configure` supplies all five declaration slices as one desired snapshot. The
143
+ coordinator becomes `ready` only after the relay acknowledges them in order. A
144
+ later declaration call replaces its complete slice and temporarily returns the
145
+ coordinator to `synchronizing` until atomic activation.
146
+
147
+ ## State, events, and calls
148
+
149
+ State mutations are atomic and use acknowledged string paths:
150
+
151
+ ```ts
152
+ await coordinator.state.set([
153
+ { path: 'climate.living_room.temperature', value: 21.5 },
154
+ ]);
155
+ ```
156
+
157
+ Event publication returns a synchronous opaque ID and a transport-handoff
158
+ promise. `sent` is not a delivery receipt; a later correlated relay rejection is
159
+ reported through `coordinator.errors`.
160
+
161
+ ```ts
162
+ const event = coordinator.events.publish(
163
+ 'climate.living_room.changed',
164
+ { temperature: 21.5 },
165
+ );
166
+ await event.sent;
167
+
168
+ coordinator.errors.subscribe((failure) => {
169
+ if (failure.correlation?.localId === event.localId) {
170
+ console.error('The relay rejected the event:', failure.kind);
171
+ }
172
+ });
173
+ ```
174
+
175
+ Outgoing calls expose acceptance, pull-bounded progress, and one terminal
176
+ result:
177
+
178
+ ```ts
179
+ const call = coordinator.calls.start({
180
+ function: 'lighting.scene.activate',
181
+ arguments: { scene: 'evening' },
182
+ timeoutMs: 10_000,
183
+ idempotencyKey: 'intent-018f',
184
+ });
185
+
186
+ await call.accepted;
187
+ for await (const progress of call.stream) console.log(progress);
188
+ const result = await call.result;
189
+ ```
190
+
191
+ MiakAPI never retries state mutations, events, or calls. An idempotency key is
192
+ passed to the callee but does not enable hidden retries.
193
+
194
+ ## Trusted browser client
195
+
196
+ Use the isolated `miakapi/browser` entry point in the first-party Miakapp web
197
+ application. It relies on the browser's native WebSocket implementation and does
198
+ not expose coordinator declarations, Home Keys, or the Node.js `ws` transport.
199
+
200
+ ```ts
201
+ import {
202
+ createBrowserClient,
203
+ createControlPlaneBrowserRelayCredentialProvider,
204
+ } from 'miakapi/browser';
205
+
206
+ const credentialProvider = createControlPlaneBrowserRelayCredentialProvider({
207
+ exchangeEndpoint: 'https://control.example.com/v1/user-relay-tokens:exchange',
208
+ async getFirebaseIdToken({ signal }) {
209
+ if (signal.aborted) throw signal.reason;
210
+ const user = firebaseAuth.currentUser;
211
+ if (user === null) throw new Error('The user is signed out');
212
+ return user.getIdToken();
213
+ },
214
+ async getAppCheckToken({ signal }) {
215
+ if (signal.aborted) throw signal.reason;
216
+ return (await getToken(firebaseAppCheck)).token;
217
+ },
218
+ });
219
+
220
+ const client = createBrowserClient({
221
+ homeId: 'my-home',
222
+ credentialProvider,
223
+ });
224
+
225
+ await client.start();
226
+
227
+ const removeStateListener = client.state.subscribe((snapshot) => {
228
+ if (!snapshot.stale) renderHome(snapshot.values);
229
+ });
230
+
231
+ const removeHomeListener = client.home.subscribe((home) => {
232
+ renderAvailability(home.enrolled, home.coordinators, home.stale);
233
+ });
234
+
235
+ const call = client.calls.start({
236
+ function: 'lighting.scene.activate',
237
+ arguments: { scene: 'evening' },
238
+ timeoutMs: 10_000,
239
+ idempotencyKey: 'intent-018f',
240
+ });
241
+ await call.accepted;
242
+ const result = await call.result;
243
+
244
+ removeStateListener();
245
+ removeHomeListener();
246
+ await client.stop();
247
+ ```
248
+
249
+ This replaces the earlier alpha browser options `relayUrl` and
250
+ `idTokenProvider`. They are intentionally rejected: callers must not pair an
251
+ independently selected relay with a source or access token.
252
+
253
+ The credential provider is invoked for the initial connection, reauthentication,
254
+ and reconnects. Its Firebase ID and App Check callbacks run only inside the
255
+ trusted host and send those source tokens solely to the HTTPS control plane.
256
+ MiakAPI never places them in a relay URL, WebSocket subprotocol, persistent
257
+ browser storage, log, error, `HELLO`, or `REAUTH` frame.
258
+
259
+ The control plane returns an up-to-five-minute Miakapp access token atomically
260
+ with its authoritative relay URL. MiakAPI sends only that audience-bound token
261
+ to the returned relay. If a renewal selects a different relay, the client marks
262
+ the old session stale and closes it. Before any automatic replacement connection,
263
+ including recovery from a transport or protocol failure, the client waits for the
264
+ native transport close event. If closure is not confirmed within ten seconds, the
265
+ client stops fail-closed instead of opening overlapping relay sockets. A routing
266
+ handoff uses the already-issued credential; it does not expose the new token to
267
+ the old relay or repeat the exchange. Stop and discard the client immediately
268
+ when the Firebase user signs out or the selected home changes. Relay routing
269
+ changes arrive through credentials and do not require mutating the client options.
270
+
271
+ Audience binding limits credential replay; it does not encrypt home traffic from
272
+ the selected relay. Users should still choose an operator they trust with the
273
+ plaintext state and calls that transit through it.
274
+
275
+ Browser state snapshots are defensive copies and become `stale` immediately
276
+ when continuity is lost. Revision or dictionary mismatches trigger one
277
+ fail-closed resynchronization request. Browser calls target the home's default
278
+ coordinator by function name, have no progress stream, and are never replayed by
279
+ the SDK. Incoming calls are rejected with an application error because this
280
+ first user profile deliberately exposes no browser call handlers. An
281
+ `outcome_unknown` failure means an effect may already have happened.
282
+
283
+ Token acquisition, protocol welcome, bootstrap, and reauthentication each have
284
+ bounded deadlines. The browser transport also caps individual frames, its
285
+ outbound queue, and rolling inbound bytes and frame counts. A native WebSocket
286
+ still materializes a complete message before JavaScript can reject it, so these
287
+ limits are defense in depth rather than isolation from a malicious relay; a
288
+ Worker boundary remains an option for a later hardened browser profile.
289
+
290
+ ## Failure outcomes
291
+
292
+ Every `CoordinatorFailure` includes an `outcome`:
293
+
294
+ - `not_dispatched`: local validation, offline gating, or an explicit relay
295
+ terminal proves the operation did not dispatch.
296
+ - `sent`: an event frame reached the active transport; delivery is not implied.
297
+ - `accepted`: a call was accepted before its terminal failure.
298
+ - `applied`: a state mutation was acknowledged by the relay.
299
+ - `outcome_unknown`: transport loss, deadline, or post-accept cancellation means
300
+ an external effect may already have happened.
301
+
302
+ Treat `outcome_unknown` as uncertainty, never as rollback. MiakAPI does not turn
303
+ an uncertain physical effect into a safe automatic retry.
304
+
305
+ ## Lifecycle and cleanup
306
+
307
+ `start()` may be called once. `stop()` is idempotent and repeated calls return the
308
+ same terminal promise. It aborts token and handler work, settles pending
309
+ operations conservatively, removes listeners, and closes the owned socket.
310
+
311
+ ```ts
312
+ await coordinator.stop({ deadlineMs: 5_000 });
313
+ ```
314
+
315
+ `deadlineMs` bounds cleanup even when an injected dependency ignores its abort
316
+ signal.
317
+
318
+ Between `start()` and `stop()` the coordinator owns its own reconnect schedule:
319
+ full jitter under a ceiling that doubles from one second up to thirty. The
320
+ ceiling returns to one second only after a session has held for thirty seconds,
321
+ so a session that is torn down as soon as it becomes `ready` keeps backing off
322
+ instead of reconnecting in a tight loop.
323
+
324
+ ## Migration from MiakAPI 3
325
+
326
+ MiakAPI 4 removes the legacy `Miakapi(home, id, secret)` constructor, Firestore
327
+ lookup, mutable `home.variables`, UI callbacks, and notification helpers. Those
328
+ APIs depended on the retired Miakapp 3 transport and are not emulated.
329
+
330
+ Integrations now:
331
+
332
+ 1. obtain short-lived access material through an `AccessTokenProvider`;
333
+ 2. declare complete state, ACL, event, and function slices;
334
+ 3. wait for `start()` readiness before issuing effects;
335
+ 4. handle uncertainty explicitly through typed failures.
336
+
337
+ For UI automation and agent-driven homes, use [miakapp.com](https://miakapp.com/)
338
+ instead of building against the retired page-callback protocol.
339
+
340
+ ## Protocol and conformance
341
+
342
+ The public TypeScript API and wire codec are pinned to the Miakapp-V3 coordinator
343
+ contract at an immutable commit in [`contracts/miakapp-v3.json`](contracts/miakapp-v3.json).
344
+ The external conformance subject runs the real SDK against a deterministic relay
345
+ and must pass every scenario in the `sdk` profile.
346
+
347
+ ```sh
348
+ bun install --frozen-lockfile
349
+ bun run check
350
+ ```
351
+
352
+ The check includes strict type checking, unit and adversarial tests, Node.js and
353
+ browser-bundle smoke tests, canonical external conformance, and an npm package
354
+ dry run.
355
+
356
+ ## License
357
+
358
+ [ISC](LICENSE)
@@ -0,0 +1,6 @@
1
+ {
2
+ "repository": "https://github.com/Miakapp/Miakapp-V3.git",
3
+ "commit": "b927789691dd8cdebc91b673853cdc6711fe057d",
4
+ "profile": "sdk",
5
+ "schema": "miakapp.coordinator-contract/1"
6
+ }
@@ -0,0 +1,8 @@
1
+ import type { AccessTokenProvider } from './api.js';
2
+ export interface HomeKeyAccessTokenProviderOptions {
3
+ readonly exchangeEndpoint: string;
4
+ readonly homeKey: string;
5
+ readonly fetch?: (input: string, init: RequestInit) => Promise<Response>;
6
+ }
7
+ export declare function createHomeKeyAccessTokenProvider(value: HomeKeyAccessTokenProviderOptions): AccessTokenProvider;
8
+ //# sourceMappingURL=access-token-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-token-provider.d.ts","sourceRoot":"","sources":["../src/access-token-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,mBAAmB,EAEpB,MAAM,UAAU,CAAC;AAgBlB,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC1E;AAsGD,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,iCAAiC,GACvC,mBAAmB,CA4DrB"}
@@ -0,0 +1,167 @@
1
+ import { boundedResponseBody, boundedResponseString, cancelResponseBody, canonicalRelayUrl, parseResponseJson, } from './internal/control-plane-response.js';
2
+ const MAXIMUM_ACCESS_TOKEN_BYTES = 8_192;
3
+ const MAXIMUM_ACCESS_TOKEN_LIFETIME_MS = 330_000;
4
+ const HOME_KEY = /^mhk1_([A-Za-z0-9_-]{22})_([A-Za-z0-9_-]{43})$/;
5
+ const BASE64URL = /^[A-Za-z0-9_-]+$/;
6
+ const COORDINATOR_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
7
+ function exchangeFailure() {
8
+ throw new Error('Miakapp access-token exchange failed');
9
+ }
10
+ function exactRecord(value, required, optional) {
11
+ if (value === null || Array.isArray(value) || typeof value !== 'object')
12
+ return exchangeFailure();
13
+ const keys = Object.keys(value);
14
+ const allowed = new Set([...required, ...optional]);
15
+ if (required.some((key) => !Object.hasOwn(value, key))
16
+ || keys.some((key) => !allowed.has(key)))
17
+ return exchangeFailure();
18
+ return value;
19
+ }
20
+ function canonicalExchangeEndpoint(value) {
21
+ if (typeof value !== 'string' || value.length === 0 || value.length > 2_048) {
22
+ return exchangeFailure();
23
+ }
24
+ let parsed;
25
+ try {
26
+ parsed = new URL(value);
27
+ }
28
+ catch {
29
+ return exchangeFailure();
30
+ }
31
+ if (parsed.protocol !== 'https:'
32
+ || parsed.username !== ''
33
+ || parsed.password !== ''
34
+ || parsed.search !== ''
35
+ || parsed.hash !== ''
36
+ || parsed.pathname !== '/v1/access-tokens:exchange'
37
+ || parsed.href !== value)
38
+ return exchangeFailure();
39
+ return value;
40
+ }
41
+ function decodeCanonicalBase64URL(value, bytes) {
42
+ if (!BASE64URL.test(value))
43
+ return false;
44
+ const decoded = Buffer.from(value, 'base64url');
45
+ return decoded.byteLength === bytes && decoded.toString('base64url') === value;
46
+ }
47
+ function validHomeKey(value) {
48
+ if (typeof value !== 'string')
49
+ return exchangeFailure();
50
+ const match = HOME_KEY.exec(value);
51
+ if (match === null
52
+ || match[1] === undefined
53
+ || match[2] === undefined
54
+ || !decodeCanonicalBase64URL(match[1], 16)
55
+ || !decodeCanonicalBase64URL(match[2], 32))
56
+ return exchangeFailure();
57
+ return { value, keyId: match[1] };
58
+ }
59
+ function accessTokenResponse(value, keyId, now) {
60
+ const response = exactRecord(value, [
61
+ 'schema', 'access_token', 'token_type', 'expires_at_ms', 'relay_url', 'key',
62
+ ], []);
63
+ const key = exactRecord(response.key, ['id', 'label'], []);
64
+ const accessToken = boundedResponseString(response.access_token, 1, MAXIMUM_ACCESS_TOKEN_BYTES);
65
+ if (response.schema !== 'miakapp.access-token/1'
66
+ || response.token_type !== 'Bearer'
67
+ || accessToken.split('.').length !== 3
68
+ || !accessToken.split('.').every((segment) => BASE64URL.test(segment))
69
+ || key.id !== keyId)
70
+ return exchangeFailure();
71
+ boundedResponseString(key.label, 1, 64);
72
+ const expiresAtMs = response.expires_at_ms;
73
+ if (typeof expiresAtMs !== 'number'
74
+ || !Number.isSafeInteger(expiresAtMs)
75
+ || expiresAtMs <= now
76
+ || expiresAtMs > now + MAXIMUM_ACCESS_TOKEN_LIFETIME_MS)
77
+ return exchangeFailure();
78
+ return Object.freeze({
79
+ relayUrl: canonicalRelayUrl(response.relay_url),
80
+ token: accessToken,
81
+ expiresAtMs,
82
+ });
83
+ }
84
+ function validateTokenRequest(value) {
85
+ const request = exactRecord(value, ['coordinatorName', 'reason', 'signal'], ['relayHost']);
86
+ if (typeof request.coordinatorName !== 'string'
87
+ || !COORDINATOR_NAME.test(request.coordinatorName)
88
+ || (request.reason !== 'initial' && request.reason !== 'reauth' && request.reason !== 'reconnect')
89
+ || request.signal === null
90
+ || typeof request.signal !== 'object'
91
+ || !('aborted' in request.signal)
92
+ || !('addEventListener' in request.signal)
93
+ || (request.relayHost !== undefined
94
+ && (typeof request.relayHost !== 'string' || request.relayHost.length === 0 || request.relayHost.length > 255))) {
95
+ return exchangeFailure();
96
+ }
97
+ }
98
+ // createHomeKeyAccessTokenProvider maps the RFC 0004 Home Key exchange onto
99
+ // MiakAPI's three-field access-token boundary. It performs exactly one request
100
+ // per SDK demand and never exposes the Home Key to the WebSocket layer.
101
+ export function createHomeKeyAccessTokenProvider(value) {
102
+ const options = exactRecord(value, ['exchangeEndpoint', 'homeKey'], ['fetch']);
103
+ const endpoint = canonicalExchangeEndpoint(options.exchangeEndpoint);
104
+ const homeKey = validHomeKey(options.homeKey);
105
+ const fetcher = options.fetch ?? globalThis.fetch;
106
+ if (typeof fetcher !== 'function')
107
+ return exchangeFailure();
108
+ return Object.freeze({
109
+ async getAccessToken(request) {
110
+ validateTokenRequest(request);
111
+ let response;
112
+ try {
113
+ response = await fetcher(endpoint, {
114
+ method: 'POST',
115
+ headers: {
116
+ accept: 'application/json',
117
+ authorization: `Bearer ${homeKey.value}`,
118
+ 'content-type': 'application/json',
119
+ },
120
+ body: JSON.stringify({
121
+ purpose: 'relay',
122
+ role: 'coordinator',
123
+ coordinator_name: request.coordinatorName,
124
+ reason: request.reason,
125
+ }),
126
+ cache: 'no-store',
127
+ credentials: 'omit',
128
+ redirect: 'error',
129
+ referrerPolicy: 'no-referrer',
130
+ signal: request.signal,
131
+ });
132
+ }
133
+ catch {
134
+ if (request.signal.aborted)
135
+ throw request.signal.reason;
136
+ return exchangeFailure();
137
+ }
138
+ try {
139
+ if (request.signal.aborted) {
140
+ cancelResponseBody(response);
141
+ throw request.signal.reason;
142
+ }
143
+ if (response.status !== 200) {
144
+ cancelResponseBody(response);
145
+ return exchangeFailure();
146
+ }
147
+ if (response.headers.get('cache-control') !== 'no-store'
148
+ || response.headers.get('pragma') !== 'no-cache'
149
+ || response.headers.get('referrer-policy') !== 'no-referrer'
150
+ || response.headers.get('content-type')?.split(';', 1)[0]?.trim().toLowerCase() !== 'application/json') {
151
+ cancelResponseBody(response);
152
+ return exchangeFailure();
153
+ }
154
+ const body = await boundedResponseBody(response, request.signal);
155
+ if (request.signal.aborted)
156
+ throw request.signal.reason;
157
+ return accessTokenResponse(parseResponseJson(body), homeKey.keyId, Date.now());
158
+ }
159
+ catch {
160
+ if (request.signal.aborted)
161
+ throw request.signal.reason;
162
+ return exchangeFailure();
163
+ }
164
+ },
165
+ });
166
+ }
167
+ //# sourceMappingURL=access-token-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-token-provider.js","sourceRoot":"","sources":["../src/access-token-provider.ts"],"names":[],"mappings":"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"}