@pellux/goodvibes-sdk 0.19.6 → 0.19.8

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 (169) hide show
  1. package/dist/_internal/contracts/index.d.ts +1 -0
  2. package/dist/_internal/contracts/index.d.ts.map +1 -1
  3. package/dist/_internal/contracts/index.js +2 -0
  4. package/dist/_internal/contracts/types.d.ts +4 -0
  5. package/dist/_internal/contracts/types.d.ts.map +1 -1
  6. package/dist/_internal/contracts/zod-schemas/accounts.d.ts +81 -0
  7. package/dist/_internal/contracts/zod-schemas/accounts.d.ts.map +1 -0
  8. package/dist/_internal/contracts/zod-schemas/accounts.js +47 -0
  9. package/dist/_internal/contracts/zod-schemas/auth.d.ts +42 -0
  10. package/dist/_internal/contracts/zod-schemas/auth.d.ts.map +1 -0
  11. package/dist/_internal/contracts/zod-schemas/auth.js +29 -0
  12. package/dist/_internal/contracts/zod-schemas/events.d.ts +37 -0
  13. package/dist/_internal/contracts/zod-schemas/events.d.ts.map +1 -0
  14. package/dist/_internal/contracts/zod-schemas/events.js +26 -0
  15. package/dist/_internal/contracts/zod-schemas/index.d.ts +9 -0
  16. package/dist/_internal/contracts/zod-schemas/index.d.ts.map +1 -0
  17. package/dist/_internal/contracts/zod-schemas/index.js +4 -0
  18. package/dist/_internal/contracts/zod-schemas/session.d.ts +22 -0
  19. package/dist/_internal/contracts/zod-schemas/session.d.ts.map +1 -0
  20. package/dist/_internal/contracts/zod-schemas/session.js +19 -0
  21. package/dist/_internal/daemon/api-router.d.ts.map +1 -1
  22. package/dist/_internal/daemon/api-router.js +0 -1
  23. package/dist/_internal/daemon/automation.d.ts.map +1 -1
  24. package/dist/_internal/daemon/channel-route-types.d.ts.map +1 -1
  25. package/dist/_internal/daemon/channel-routes.d.ts.map +1 -1
  26. package/dist/_internal/daemon/context.d.ts.map +1 -1
  27. package/dist/_internal/daemon/control-routes.d.ts.map +1 -1
  28. package/dist/_internal/daemon/http-policy.d.ts.map +1 -1
  29. package/dist/_internal/daemon/http-policy.js +0 -1
  30. package/dist/_internal/daemon/integration-route-types.d.ts.map +1 -1
  31. package/dist/_internal/daemon/integration-routes.d.ts.map +1 -1
  32. package/dist/_internal/daemon/knowledge-route-types.d.ts.map +1 -1
  33. package/dist/_internal/daemon/knowledge-routes.d.ts.map +1 -1
  34. package/dist/_internal/daemon/knowledge-routes.js +5 -4
  35. package/dist/_internal/daemon/media-route-types.d.ts.map +1 -1
  36. package/dist/_internal/daemon/media-routes.d.ts.map +1 -1
  37. package/dist/_internal/daemon/operator.d.ts.map +1 -1
  38. package/dist/_internal/daemon/remote-routes.d.ts.map +1 -1
  39. package/dist/_internal/daemon/remote.d.ts.map +1 -1
  40. package/dist/_internal/daemon/route-helpers.d.ts.map +1 -1
  41. package/dist/_internal/daemon/runtime-automation-routes.d.ts.map +1 -1
  42. package/dist/_internal/daemon/runtime-route-types.d.ts +14 -1
  43. package/dist/_internal/daemon/runtime-route-types.d.ts.map +1 -1
  44. package/dist/_internal/daemon/runtime-routes.d.ts.map +1 -1
  45. package/dist/_internal/daemon/runtime-session-routes.d.ts.map +1 -1
  46. package/dist/_internal/daemon/runtime-session-routes.js +0 -2
  47. package/dist/_internal/daemon/sessions.d.ts.map +1 -1
  48. package/dist/_internal/daemon/system-route-types.d.ts.map +1 -1
  49. package/dist/_internal/daemon/system-routes.d.ts.map +1 -1
  50. package/dist/_internal/daemon/tasks.d.ts.map +1 -1
  51. package/dist/_internal/daemon/telemetry-routes.d.ts.map +1 -1
  52. package/dist/_internal/errors/daemon-error-contract.d.ts.map +1 -1
  53. package/dist/_internal/errors/index.d.ts +2 -2
  54. package/dist/_internal/errors/index.js +2 -2
  55. package/dist/_internal/operator/client-core.d.ts.map +1 -1
  56. package/dist/_internal/operator/client-core.js +8 -2
  57. package/dist/_internal/operator/client.d.ts +7 -0
  58. package/dist/_internal/operator/client.d.ts.map +1 -1
  59. package/dist/_internal/operator/client.js +32 -1
  60. package/dist/_internal/peer/client-core.d.ts.map +1 -1
  61. package/dist/_internal/platform/agents/orchestrator.d.ts +7 -0
  62. package/dist/_internal/platform/agents/orchestrator.d.ts.map +1 -1
  63. package/dist/_internal/platform/agents/orchestrator.js +8 -0
  64. package/dist/_internal/platform/auth/android-keystore-token-store.d.ts +110 -0
  65. package/dist/_internal/platform/auth/android-keystore-token-store.d.ts.map +1 -0
  66. package/dist/_internal/platform/auth/android-keystore-token-store.js +164 -0
  67. package/dist/_internal/platform/auth/auto-refresh-middleware.d.ts +46 -0
  68. package/dist/_internal/platform/auth/auto-refresh-middleware.d.ts.map +1 -0
  69. package/dist/_internal/platform/auth/auto-refresh-middleware.js +155 -0
  70. package/dist/_internal/platform/auth/auto-refresh.d.ts +123 -0
  71. package/dist/_internal/platform/auth/auto-refresh.d.ts.map +1 -0
  72. package/dist/_internal/platform/auth/auto-refresh.js +236 -0
  73. package/dist/_internal/platform/auth/expo-secure-token-store.d.ts +82 -0
  74. package/dist/_internal/platform/auth/expo-secure-token-store.d.ts.map +1 -0
  75. package/dist/_internal/platform/auth/expo-secure-token-store.js +135 -0
  76. package/dist/_internal/platform/auth/index.d.ts +3 -0
  77. package/dist/_internal/platform/auth/index.d.ts.map +1 -1
  78. package/dist/_internal/platform/auth/index.js +2 -0
  79. package/dist/_internal/platform/auth/ios-keychain-token-store.d.ts +88 -0
  80. package/dist/_internal/platform/auth/ios-keychain-token-store.d.ts.map +1 -0
  81. package/dist/_internal/platform/auth/ios-keychain-token-store.js +147 -0
  82. package/dist/_internal/platform/auth/session-manager.d.ts +2 -0
  83. package/dist/_internal/platform/auth/session-manager.d.ts.map +1 -1
  84. package/dist/_internal/platform/auth/session-manager.js +9 -1
  85. package/dist/_internal/platform/auth/token-store.d.ts +13 -0
  86. package/dist/_internal/platform/auth/token-store.d.ts.map +1 -1
  87. package/dist/_internal/platform/auth/token-store.js +23 -0
  88. package/dist/_internal/platform/companion/companion-chat-manager.d.ts +64 -11
  89. package/dist/_internal/platform/companion/companion-chat-manager.d.ts.map +1 -1
  90. package/dist/_internal/platform/companion/companion-chat-manager.js +158 -12
  91. package/dist/_internal/platform/companion/companion-chat-persistence.d.ts +33 -0
  92. package/dist/_internal/platform/companion/companion-chat-persistence.d.ts.map +1 -0
  93. package/dist/_internal/platform/companion/companion-chat-persistence.js +115 -0
  94. package/dist/_internal/platform/companion/companion-chat-rate-limiter.d.ts +47 -0
  95. package/dist/_internal/platform/companion/companion-chat-rate-limiter.d.ts.map +1 -0
  96. package/dist/_internal/platform/companion/companion-chat-rate-limiter.js +117 -0
  97. package/dist/_internal/platform/companion/companion-chat-types.d.ts +2 -4
  98. package/dist/_internal/platform/companion/companion-chat-types.d.ts.map +1 -1
  99. package/dist/_internal/platform/companion/companion-chat-types.js +2 -4
  100. package/dist/_internal/platform/companion/index.d.ts +4 -0
  101. package/dist/_internal/platform/companion/index.d.ts.map +1 -1
  102. package/dist/_internal/platform/companion/index.js +2 -0
  103. package/dist/_internal/platform/daemon/facade-composition.d.ts.map +1 -1
  104. package/dist/_internal/platform/daemon/facade-composition.js +5 -0
  105. package/dist/_internal/platform/daemon/facade.d.ts.map +1 -1
  106. package/dist/_internal/platform/daemon/facade.js +3 -0
  107. package/dist/_internal/platform/daemon/http/runtime-route-types.d.ts +0 -7
  108. package/dist/_internal/platform/daemon/http/runtime-route-types.d.ts.map +1 -1
  109. package/dist/_internal/platform/state/db.d.ts.map +1 -1
  110. package/dist/_internal/platform/state/db.js +0 -1
  111. package/dist/_internal/platform/state/sqlite-store.d.ts.map +1 -1
  112. package/dist/_internal/platform/state/sqlite-store.js +0 -1
  113. package/dist/_internal/platform/version.js +1 -1
  114. package/dist/_internal/transport-core/client-transport.d.ts.map +1 -1
  115. package/dist/_internal/transport-core/event-envelope.d.ts.map +1 -1
  116. package/dist/_internal/transport-core/event-feeds.d.ts.map +1 -1
  117. package/dist/_internal/transport-core/index.d.ts +5 -0
  118. package/dist/_internal/transport-core/index.d.ts.map +1 -1
  119. package/dist/_internal/transport-core/index.js +3 -0
  120. package/dist/_internal/transport-core/middleware.d.ts +76 -0
  121. package/dist/_internal/transport-core/middleware.d.ts.map +1 -0
  122. package/dist/_internal/transport-core/middleware.js +67 -0
  123. package/dist/_internal/transport-core/observer.d.ts +53 -0
  124. package/dist/_internal/transport-core/observer.d.ts.map +1 -0
  125. package/dist/_internal/transport-core/observer.js +26 -0
  126. package/dist/_internal/transport-core/otel.d.ts +64 -0
  127. package/dist/_internal/transport-core/otel.d.ts.map +1 -0
  128. package/dist/_internal/transport-core/otel.js +149 -0
  129. package/dist/_internal/transport-direct/index.d.ts.map +1 -1
  130. package/dist/_internal/transport-direct/index.js +0 -1
  131. package/dist/_internal/transport-http/contract-client.d.ts +11 -1
  132. package/dist/_internal/transport-http/contract-client.d.ts.map +1 -1
  133. package/dist/_internal/transport-http/contract-client.js +18 -4
  134. package/dist/_internal/transport-http/http-core.d.ts +27 -1
  135. package/dist/_internal/transport-http/http-core.d.ts.map +1 -1
  136. package/dist/_internal/transport-http/http-core.js +180 -12
  137. package/dist/_internal/transport-http/http.d.ts +3 -3
  138. package/dist/_internal/transport-http/http.d.ts.map +1 -1
  139. package/dist/_internal/transport-http/http.js +2 -2
  140. package/dist/_internal/transport-http/index.d.ts +4 -2
  141. package/dist/_internal/transport-http/index.d.ts.map +1 -1
  142. package/dist/_internal/transport-http/index.js +2 -1
  143. package/dist/_internal/transport-http/paths.js +1 -1
  144. package/dist/_internal/transport-http/reconnect.d.ts +2 -0
  145. package/dist/_internal/transport-http/reconnect.d.ts.map +1 -1
  146. package/dist/_internal/transport-http/reconnect.js +4 -2
  147. package/dist/_internal/transport-http/retry.d.ts +15 -0
  148. package/dist/_internal/transport-http/retry.d.ts.map +1 -1
  149. package/dist/_internal/transport-http/retry.js +19 -0
  150. package/dist/_internal/transport-realtime/domain-events.d.ts.map +1 -1
  151. package/dist/_internal/transport-realtime/runtime-events.d.ts +10 -3
  152. package/dist/_internal/transport-realtime/runtime-events.d.ts.map +1 -1
  153. package/dist/_internal/transport-realtime/runtime-events.js +73 -8
  154. package/dist/auth.d.ts +38 -3
  155. package/dist/auth.d.ts.map +1 -1
  156. package/dist/auth.js +68 -3
  157. package/dist/client.d.ts +61 -2
  158. package/dist/client.d.ts.map +1 -1
  159. package/dist/client.js +64 -3
  160. package/dist/expo.d.ts +1 -0
  161. package/dist/expo.d.ts.map +1 -1
  162. package/dist/expo.js +1 -0
  163. package/dist/observer/index.d.ts +16 -25
  164. package/dist/observer/index.d.ts.map +1 -1
  165. package/dist/platform/runtime/transports/http.js +1 -1
  166. package/dist/react-native.d.ts +2 -0
  167. package/dist/react-native.d.ts.map +1 -1
  168. package/dist/react-native.js +2 -0
  169. package/package.json +16 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"android-keystore-token-store.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/auth/android-keystore-token-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAM5D;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAC5B,cAAc,GACd,iCAAiC,GACjC,sBAAsB,GACtB,yCAAyC,GACzC,iBAAiB,GACjB,sBAAsB,CAAC;AAE3B;;GAEG;AACH,MAAM,MAAM,iBAAiB,GACzB,eAAe,GACf,oBAAoB,GACpB,QAAQ,GACR,oCAAoC,GACpC,gCAAgC,GAChC,qCAAqC,GACrC,yBAAyB,CAAC;AAM9B,MAAM,WAAW,gCAAgC;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;;;;;OAQG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,oBAAoB,CAAC;IAE9C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,iBAAiB,CAAC;CACzC;AAkDD,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACpE;;;;OAIG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvE;;;OAGG;IACH,aAAa,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxE;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,GAAE,gCAAqC,EAC9C,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GACpC,yBAAyB,CAuF3B"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * android-keystore-token-store.ts
3
+ *
4
+ * Token store backed by `react-native-keychain` for bare React Native on Android.
5
+ *
6
+ * On Android, `react-native-keychain` routes to EncryptedSharedPreferences
7
+ * backed by the Android Keystore system — hardware-backed AES-256-GCM
8
+ * encryption where available (API 23+). This is the **recommended**
9
+ * implementation for proper hardware-backed security.
10
+ *
11
+ * Alternative: `@react-native-async-storage/async-storage` can store the
12
+ * token but is NOT hardware-backed. If you cannot use `react-native-keychain`,
13
+ * wrap async-storage with an additional encryption layer (e.g.
14
+ * `react-native-encrypted-storage`). The `react-native-keychain` path is
15
+ * strongly preferred for production apps.
16
+ *
17
+ * `react-native-keychain` is an **optional peer dependency** — this module
18
+ * does NOT import it at the top level.
19
+ *
20
+ * ## Installation
21
+ *
22
+ * ```sh
23
+ * npm install react-native-keychain
24
+ * # Android: auto-linked via Gradle; no manual step required
25
+ * ```
26
+ *
27
+ * Wave 6 three-part error messages: [what happened] · [why] · [what to do]
28
+ */
29
+ import { GoodVibesSdkError } from '../../errors/index.js';
30
+ let _mod = null;
31
+ async function loadKeychain() {
32
+ if (_mod !== null)
33
+ return _mod;
34
+ try {
35
+ _mod = await import('react-native-keychain');
36
+ return _mod;
37
+ }
38
+ catch {
39
+ throw new GoodVibesSdkError('react-native-keychain is not installed — the Android Keystore token store cannot be initialised. ' +
40
+ 'This optional peer dependency is required to persist tokens in Android Keystore-backed storage. ' +
41
+ 'Run `npm install react-native-keychain` and rebuild your app.', {
42
+ code: 'RN_KEYCHAIN_NOT_INSTALLED',
43
+ category: 'config',
44
+ source: 'config',
45
+ recoverable: false,
46
+ });
47
+ }
48
+ }
49
+ // ---------------------------------------------------------------------------
50
+ // Fixed username slot
51
+ // ---------------------------------------------------------------------------
52
+ const USERNAME_SLOT = 'goodvibes-sdk';
53
+ // ---------------------------------------------------------------------------
54
+ // Factory
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Create a `GoodVibesTokenStore` backed by the Android Keystore system via
58
+ * `react-native-keychain`.
59
+ *
60
+ * On Android, `react-native-keychain` uses `EncryptedSharedPreferences` backed
61
+ * by the Android Keystore — hardware-backed AES-256-GCM encryption where
62
+ * supported (API 23+). This is strongly preferred over plain AsyncStorage.
63
+ *
64
+ * Pass `accessControl: 'BIOMETRY_ANY'` or `'DEVICE_PASSCODE'` to require
65
+ * interactive user authentication before reading the stored credential.
66
+ *
67
+ * `react-native-keychain` is an **optional peer dependency** — install it with:
68
+ *
69
+ * ```sh
70
+ * npm install react-native-keychain
71
+ * # No manual Gradle/CocoaPods step required for Android
72
+ * ```
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * import { createAndroidKeystoreTokenStore, createReactNativeGoodVibesSdk } from '@pellux/goodvibes-sdk/react-native';
77
+ *
78
+ * const tokenStore = createAndroidKeystoreTokenStore({
79
+ * service: 'com.myapp.gv',
80
+ * accessControl: 'BIOMETRY_ANY',
81
+ * });
82
+ * const sdk = createReactNativeGoodVibesSdk({ baseUrl: 'https://daemon.example.com', tokenStore });
83
+ * ```
84
+ */
85
+ export function createAndroidKeystoreTokenStore(options = {}, __loadModule) {
86
+ const service = options.service ?? 'com.pellux.goodvibes-sdk';
87
+ const accessible = options.accessible ?? 'WHEN_UNLOCKED_THIS_DEVICE_ONLY';
88
+ const accessControl = options.accessControl;
89
+ async function resolveModule() {
90
+ if (__loadModule !== undefined) {
91
+ return __loadModule();
92
+ }
93
+ return loadKeychain();
94
+ }
95
+ function buildOptions(mod) {
96
+ const opts = { service };
97
+ const accessibleValue = mod.ACCESSIBLE[accessible];
98
+ if (accessibleValue !== undefined) {
99
+ opts['accessible'] = accessibleValue;
100
+ }
101
+ else if (options.accessible !== undefined) {
102
+ console.warn(`[pellux/goodvibes-sdk] react-native-keychain does not expose ACCESSIBLE.${accessible}; falling back to default`);
103
+ }
104
+ if (accessControl !== undefined) {
105
+ const acValue = mod.ACCESS_CONTROL[accessControl];
106
+ if (acValue !== undefined) {
107
+ opts['accessControl'] = acValue;
108
+ }
109
+ else {
110
+ console.warn(`[pellux/goodvibes-sdk] react-native-keychain does not expose ACCESS_CONTROL.${accessControl}; falling back to default`);
111
+ }
112
+ }
113
+ return opts;
114
+ }
115
+ async function readPayload() {
116
+ const mod = await resolveModule();
117
+ const result = await mod.getGenericPassword(buildOptions(mod));
118
+ if (result === false)
119
+ return null;
120
+ try {
121
+ return JSON.parse(result.password);
122
+ }
123
+ catch {
124
+ return null;
125
+ }
126
+ }
127
+ async function writePayload(payload) {
128
+ const mod = await resolveModule();
129
+ if (payload === null) {
130
+ await mod.resetGenericPassword(buildOptions(mod));
131
+ return;
132
+ }
133
+ await mod.setGenericPassword(USERNAME_SLOT, JSON.stringify(payload), buildOptions(mod));
134
+ }
135
+ return {
136
+ async getToken() {
137
+ const payload = await readPayload();
138
+ return payload?.token ?? null;
139
+ },
140
+ async setToken(token) {
141
+ if (token === null) {
142
+ await writePayload(null);
143
+ return;
144
+ }
145
+ await writePayload({ token, expiresAt: null });
146
+ },
147
+ async clearToken() {
148
+ await writePayload(null);
149
+ },
150
+ async setTokenEntry(token, expiresAt) {
151
+ if (token === null) {
152
+ await writePayload(null);
153
+ return;
154
+ }
155
+ await writePayload({ token, expiresAt: expiresAt ?? null });
156
+ },
157
+ async getTokenEntry() {
158
+ const payload = await readPayload();
159
+ if (payload === null)
160
+ return { token: null };
161
+ return { token: payload.token, expiresAt: payload.expiresAt ?? undefined };
162
+ },
163
+ };
164
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Auto-refresh transport middleware.
3
+ *
4
+ * Integrates `AutoRefreshCoordinator` at the transport boundary so that ALL
5
+ * typed operator/peer SDK calls benefit from silent token refresh — not only
6
+ * `auth.current()`.
7
+ *
8
+ * Responsibilities:
9
+ * 1. Pre-flight: call `coordinator.ensureFreshToken()` before dispatching the
10
+ * request. Updates `ctx.headers.Authorization` with the fresh token so
11
+ * consumer middleware (which runs after this one) sees the current token.
12
+ * 2. Reactive 401: if `next()` throws a 401-shaped error, trigger a refresh
13
+ * via `coordinator.refreshAndRetryOnce()` and set `ctx.response` to the
14
+ * retry result so the transport can return it to the caller.
15
+ * 3. Loop prevention: retry requests carry `__gv_ar_attempted: true` in their
16
+ * options. The middleware recognises this flag and passes through without
17
+ * additional refresh logic, preventing infinite recursion.
18
+ * 4. Error passthrough: terminal auth errors from the coordinator are placed
19
+ * directly onto `ctx.error` rather than thrown, so the transport's
20
+ * middleware-error-wrapping instrumentation does not re-wrap them as
21
+ * `kind:'unknown'`.
22
+ */
23
+ import type { TransportMiddleware } from '../../transport-core/index.js';
24
+ import type { HttpJsonTransport } from '../../transport-http/http-core.js';
25
+ import type { GoodVibesTokenStore } from '../../../auth.js';
26
+ import type { AutoRefreshCoordinator } from './auto-refresh.js';
27
+ /**
28
+ * Create a transport middleware that integrates the `AutoRefreshCoordinator`
29
+ * into every HTTP request.
30
+ *
31
+ * @param coordinator - The auto-refresh coordinator managing token lifecycle.
32
+ * @param transport - The HTTP JSON transport used to re-issue the request on
33
+ * reactive 401 retry. Must be the same transport instance
34
+ * whose middleware chain contains this middleware, so that
35
+ * the retry benefits from other middleware (e.g. logging,
36
+ * tracing) except for another auto-refresh cycle.
37
+ * @param tokenStore - The token store from which to read the fresh token after
38
+ * pre-flight refresh, so `ctx.headers.Authorization` can
39
+ * be updated before consumer middleware runs.
40
+ *
41
+ * @example
42
+ * const mw = createAutoRefreshMiddleware(coordinator, transport, tokenStore);
43
+ * transport.use(mw);
44
+ */
45
+ export declare function createAutoRefreshMiddleware(coordinator: AutoRefreshCoordinator, transport: Pick<HttpJsonTransport, 'requestJson'>, tokenStore: GoodVibesTokenStore): TransportMiddleware;
46
+ //# sourceMappingURL=auto-refresh-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-refresh-middleware.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/auth/auto-refresh-middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAoB,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAC3F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAKhE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,sBAAsB,EACnC,SAAS,EAAE,IAAI,CAAC,iBAAiB,EAAE,aAAa,CAAC,EACjD,UAAU,EAAE,mBAAmB,GAC9B,mBAAmB,CAyGrB"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Auto-refresh transport middleware.
3
+ *
4
+ * Integrates `AutoRefreshCoordinator` at the transport boundary so that ALL
5
+ * typed operator/peer SDK calls benefit from silent token refresh — not only
6
+ * `auth.current()`.
7
+ *
8
+ * Responsibilities:
9
+ * 1. Pre-flight: call `coordinator.ensureFreshToken()` before dispatching the
10
+ * request. Updates `ctx.headers.Authorization` with the fresh token so
11
+ * consumer middleware (which runs after this one) sees the current token.
12
+ * 2. Reactive 401: if `next()` throws a 401-shaped error, trigger a refresh
13
+ * via `coordinator.refreshAndRetryOnce()` and set `ctx.response` to the
14
+ * retry result so the transport can return it to the caller.
15
+ * 3. Loop prevention: retry requests carry `__gv_ar_attempted: true` in their
16
+ * options. The middleware recognises this flag and passes through without
17
+ * additional refresh logic, preventing infinite recursion.
18
+ * 4. Error passthrough: terminal auth errors from the coordinator are placed
19
+ * directly onto `ctx.error` rather than thrown, so the transport's
20
+ * middleware-error-wrapping instrumentation does not re-wrap them as
21
+ * `kind:'unknown'`.
22
+ */
23
+ /** Internal flag key — never conflicts with public HttpJsonRequestOptions fields. */
24
+ const ATTEMPTED_FLAG = '__gv_ar_attempted';
25
+ /**
26
+ * Create a transport middleware that integrates the `AutoRefreshCoordinator`
27
+ * into every HTTP request.
28
+ *
29
+ * @param coordinator - The auto-refresh coordinator managing token lifecycle.
30
+ * @param transport - The HTTP JSON transport used to re-issue the request on
31
+ * reactive 401 retry. Must be the same transport instance
32
+ * whose middleware chain contains this middleware, so that
33
+ * the retry benefits from other middleware (e.g. logging,
34
+ * tracing) except for another auto-refresh cycle.
35
+ * @param tokenStore - The token store from which to read the fresh token after
36
+ * pre-flight refresh, so `ctx.headers.Authorization` can
37
+ * be updated before consumer middleware runs.
38
+ *
39
+ * @example
40
+ * const mw = createAutoRefreshMiddleware(coordinator, transport, tokenStore);
41
+ * transport.use(mw);
42
+ */
43
+ export function createAutoRefreshMiddleware(coordinator, transport, tokenStore) {
44
+ return async function autoRefreshMiddleware(ctx, next) {
45
+ // ── Loop-prevention guard ────────────────────────────────────────────────
46
+ // Retry requests issued by this middleware carry the flag. When we see it,
47
+ // simply forward the request without any refresh logic.
48
+ // We catch and re-place errors onto ctx.error so they are NOT tagged as
49
+ // "middleware errors" by the transport's instrumented-chain wrapper — which
50
+ // would re-wrap them as GoodVibesSdkError{kind:'unknown'} and hide the
51
+ // original 401 status that refreshAndRetryOnce needs to detect.
52
+ if (ctx.options[ATTEMPTED_FLAG] === true) {
53
+ try {
54
+ await next();
55
+ }
56
+ catch (passthroughErr) {
57
+ ctx.error = passthroughErr;
58
+ }
59
+ return;
60
+ }
61
+ // ── Pre-flight refresh ───────────────────────────────────────────────────
62
+ // Silently refreshes the token when it is within the leeway window.
63
+ // After refresh, update ctx.headers.Authorization with the fresh token so
64
+ // consumer middleware (which runs after this one in the chain) sees the
65
+ // current Bearer token, not the stale one that was set when ctx was built.
66
+ await coordinator.ensureFreshToken();
67
+ const freshToken = await tokenStore.getToken();
68
+ if (freshToken) {
69
+ ctx.headers['Authorization'] = `Bearer ${freshToken}`;
70
+ }
71
+ // ── Dispatch request ─────────────────────────────────────────────────────
72
+ // We catch ALL errors from next() rather than re-throwing them directly.
73
+ // Re-throwing from within a middleware causes the transport's instrumented-
74
+ // chain wrapper to tag the error as "from this middleware" and re-wrap it
75
+ // as GoodVibesSdkError{kind:'unknown'}, even when the error originated
76
+ // from the real fetch (innerFetch). By placing non-401 errors onto
77
+ // ctx.error instead, they bypass the middleware-wrapping path and propagate
78
+ // with their original type and kind intact.
79
+ let caughtErr;
80
+ let nextSucceeded = false;
81
+ try {
82
+ await next();
83
+ nextSucceeded = true;
84
+ }
85
+ catch (err) {
86
+ caughtErr = err;
87
+ }
88
+ if (nextSucceeded) {
89
+ return; // success — nothing more to do
90
+ }
91
+ if (!is401Error(caughtErr)) {
92
+ // Non-401 error (e.g. 5xx, network) — place on ctx.error so it exits
93
+ // the middleware chain without triggering middleware-error wrapping.
94
+ ctx.error = caughtErr;
95
+ return;
96
+ }
97
+ // ── Reactive 401 retry ─────────────────────────────────────────────────
98
+ // Build retry options that preserve the original request attributes and
99
+ // carry the loop-prevention flag so the next pass through this middleware
100
+ // just calls next() without re-entering the refresh logic.
101
+ const retryOptions = {
102
+ method: ctx.method,
103
+ body: ctx.body,
104
+ signal: ctx.signal,
105
+ [ATTEMPTED_FLAG]: true,
106
+ };
107
+ // Delegate to coordinator.refreshAndRetryOnce — this refreshes the token
108
+ // exactly once and executes the retry fn once. If the retry also returns
109
+ // 401, it throws GoodVibesSdkError{kind:'auth'} (Wave 6 three-part message).
110
+ //
111
+ // IMPORTANT: we place terminal errors onto ctx.error instead of re-throwing
112
+ // them. This bypasses the transport's middleware-error-wrapping path
113
+ // (which would re-label them as kind:'unknown') and lets the transport's
114
+ // outer `if (ctx.error) throw ctx.error` propagate them cleanly.
115
+ let retryResult;
116
+ try {
117
+ retryResult = await coordinator.refreshAndRetryOnce(async () => {
118
+ // transport.requestJson goes through the full middleware chain; the
119
+ // ATTEMPTED_FLAG ensures this middleware is a passthrough on that call.
120
+ return transport.requestJson(ctx.url, retryOptions);
121
+ });
122
+ }
123
+ catch (retryErr) {
124
+ // Place onto ctx.error — transport's post-chain check will rethrow it
125
+ // without the middleware-wrapping treatment.
126
+ ctx.error = retryErr;
127
+ return;
128
+ }
129
+ // Put the retry result back onto ctx.response so the transport's outer
130
+ // requestJson can resolve it via `await ctx.response.json()`.
131
+ // Also clear ctx.error: the innerFetch path sets it when it throws (before
132
+ // re-throwing), so it reflects the original 401. Since we handled the 401
133
+ // successfully, we must clear it so the transport's `if (ctx.error) throw`
134
+ // guard does not re-raise the already-resolved error.
135
+ ctx.error = undefined;
136
+ ctx.response = new Response(JSON.stringify(retryResult), {
137
+ status: 200,
138
+ headers: { 'Content-Type': 'application/json' },
139
+ });
140
+ };
141
+ }
142
+ // ---------------------------------------------------------------------------
143
+ // Helpers
144
+ // ---------------------------------------------------------------------------
145
+ /** Detect a 401 response across the various error shapes the SDK produces. */
146
+ function is401Error(error) {
147
+ if (typeof error !== 'object' || error === null)
148
+ return false;
149
+ const e = error;
150
+ const status = e.status ??
151
+ e.transport?.status ??
152
+ e.response?.status ??
153
+ e.cause?.response?.status;
154
+ return status === 401;
155
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * AutoRefreshCoordinator — Silent token refresh with in-flight request queuing.
3
+ *
4
+ * Prevents user-visible 401s by:
5
+ * 1. Pre-flight leeway check: if the token expires within refreshLeewayMs,
6
+ * trigger a silent refresh before the request is dispatched.
7
+ * 2. Reactive 401 retry: if a request returns 401 and the token wasn't
8
+ * already known expired, trigger a refresh then retry the request once.
9
+ * 3. In-flight queuing: while a refresh is in progress, subsequent refresh
10
+ * attempts queue on the same promise — one refresh call for all waiters.
11
+ *
12
+ * When no refresh endpoint is available (the coordinator has no `refresh`
13
+ * function), the pre-flight check is a graceful no-op and the reactive path
14
+ * triggers a token re-read (which may succeed if the store was updated externally).
15
+ *
16
+ * Wave 6 three-part error messages:
17
+ * [what happened] · [why] · [what to do]
18
+ */
19
+ import type { GoodVibesTokenStore } from '../../../auth.js';
20
+ import type { SDKObserver } from '../../../observer/index.js';
21
+ export interface AutoRefreshOptions {
22
+ /**
23
+ * Enable or disable silent token refresh. Default: `true`.
24
+ * When `false`, 401 responses propagate immediately without retry.
25
+ */
26
+ readonly autoRefresh?: boolean;
27
+ /**
28
+ * Milliseconds before token expiry to trigger a silent refresh.
29
+ * Default: 60_000 (1 minute).
30
+ */
31
+ readonly refreshLeewayMs?: number;
32
+ /**
33
+ * Consumer-provided callback invoked to obtain a new token when the current
34
+ * token is near expiry (pre-flight leeway check) or a 401 is received
35
+ * (reactive retry path).
36
+ *
37
+ * The callback must return the new token string and, optionally, its expiry
38
+ * timestamp in Unix milliseconds. When provided, the coordinator calls this
39
+ * to perform the actual token refresh and persists the result via
40
+ * `setTokenEntry` (or `setToken` on stores that don't implement
41
+ * `setTokenEntry`).
42
+ *
43
+ * When absent, the coordinator's pre-flight check is a graceful no-op and
44
+ * the reactive 401 path re-reads the token store without making a network
45
+ * call (useful when an external party updates the store).
46
+ *
47
+ * @example
48
+ * const store = createMemoryTokenStore(initialToken);
49
+ * const sdk = createGoodVibesSdk({
50
+ * baseUrl: 'https://daemon.example.com',
51
+ * tokenStore: store,
52
+ * autoRefresh: {
53
+ * refresh: async () => {
54
+ * const res = await fetch('/api/auth/refresh', { method: 'POST' });
55
+ * const { token, expiresAt } = await res.json();
56
+ * return { token, expiresAt };
57
+ * },
58
+ * },
59
+ * });
60
+ */
61
+ readonly refresh?: () => Promise<{
62
+ token: string;
63
+ expiresAt?: number;
64
+ }>;
65
+ }
66
+ export interface AutoRefreshCoordinatorOptions {
67
+ readonly tokenStore: GoodVibesTokenStore;
68
+ readonly autoRefresh: boolean;
69
+ readonly refreshLeewayMs: number;
70
+ /**
71
+ * Optional refresh function. Called to acquire a new token when the current
72
+ * one is near expiry or a 401 was received.
73
+ *
74
+ * If undefined, the coordinator performs a graceful no-op (does not
75
+ * error) — in-flight queuing and leeway checks are still respected, but
76
+ * no network call is made. Reactive 401 retry still re-reads the token
77
+ * store in case an external party updated it.
78
+ */
79
+ readonly refresh?: () => Promise<{
80
+ token: string;
81
+ expiresAt?: number;
82
+ }>;
83
+ readonly observer?: SDKObserver;
84
+ }
85
+ export declare class AutoRefreshCoordinator {
86
+ #private;
87
+ constructor(options: AutoRefreshCoordinatorOptions);
88
+ /**
89
+ * Call before dispatching a request. If the token is near expiry (within
90
+ * `refreshLeewayMs`), silently refreshes before the request goes out.
91
+ *
92
+ * If `autoRefresh` is disabled, this is a no-op.
93
+ */
94
+ ensureFreshToken(): Promise<void>;
95
+ /**
96
+ * Execute `fn` and retry once on 401 after triggering a refresh.
97
+ *
98
+ * If `autoRefresh` is false, the first 401 is rethrown immediately.
99
+ * If the retry also returns 401, throws `GoodVibesSdkError{kind:'auth'}`.
100
+ *
101
+ * @param fn - The request function to execute. Must be side-effect-safe to
102
+ * call twice (called at most twice).
103
+ */
104
+ withRetryOn401<T>(fn: () => Promise<T>): Promise<T>;
105
+ /**
106
+ * Refresh the token immediately and execute `fn` exactly once as the retry.
107
+ *
108
+ * Unlike `withRetryOn401`, this method does NOT call `fn` before refreshing —
109
+ * it assumes the caller already received a 401 on the initial attempt. It
110
+ * refreshes the token (serialised via the shared promise, as with
111
+ * `withRetryOn401`) and then calls `fn` a single time.
112
+ *
113
+ * If `fn` throws a 401 on retry, a terminal `GoodVibesSdkError{kind:'auth'}`
114
+ * is thrown with the Wave 6 three-part message format.
115
+ *
116
+ * Used by `createAutoRefreshMiddleware` to avoid making an extra HTTP call
117
+ * when the middleware already observed the initial 401 from `next()`.
118
+ *
119
+ * @param fn - The retry request to execute after the refresh completes.
120
+ */
121
+ refreshAndRetryOnce<T>(fn: () => Promise<T>): Promise<T>;
122
+ }
123
+ //# sourceMappingURL=auto-refresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-refresh.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/auth/auto-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAO9D,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAE/B;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAElC;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzE;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;IACzC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,CAAC;CACjC;AAMD,qBAAa,sBAAsB;;gBAUrB,OAAO,EAAE,6BAA6B;IAsGlD;;;;;OAKG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAYvC;;;;;;;;OAQG;IACG,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA0CzD;;;;;;;;;;;;;;;OAeG;IACG,mBAAmB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CA2B/D"}