@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.
- package/dist/_internal/contracts/index.d.ts +1 -0
- package/dist/_internal/contracts/index.d.ts.map +1 -1
- package/dist/_internal/contracts/index.js +2 -0
- package/dist/_internal/contracts/types.d.ts +4 -0
- package/dist/_internal/contracts/types.d.ts.map +1 -1
- package/dist/_internal/contracts/zod-schemas/accounts.d.ts +81 -0
- package/dist/_internal/contracts/zod-schemas/accounts.d.ts.map +1 -0
- package/dist/_internal/contracts/zod-schemas/accounts.js +47 -0
- package/dist/_internal/contracts/zod-schemas/auth.d.ts +42 -0
- package/dist/_internal/contracts/zod-schemas/auth.d.ts.map +1 -0
- package/dist/_internal/contracts/zod-schemas/auth.js +29 -0
- package/dist/_internal/contracts/zod-schemas/events.d.ts +37 -0
- package/dist/_internal/contracts/zod-schemas/events.d.ts.map +1 -0
- package/dist/_internal/contracts/zod-schemas/events.js +26 -0
- package/dist/_internal/contracts/zod-schemas/index.d.ts +9 -0
- package/dist/_internal/contracts/zod-schemas/index.d.ts.map +1 -0
- package/dist/_internal/contracts/zod-schemas/index.js +4 -0
- package/dist/_internal/contracts/zod-schemas/session.d.ts +22 -0
- package/dist/_internal/contracts/zod-schemas/session.d.ts.map +1 -0
- package/dist/_internal/contracts/zod-schemas/session.js +19 -0
- package/dist/_internal/daemon/api-router.d.ts.map +1 -1
- package/dist/_internal/daemon/api-router.js +0 -1
- package/dist/_internal/daemon/automation.d.ts.map +1 -1
- package/dist/_internal/daemon/channel-route-types.d.ts.map +1 -1
- package/dist/_internal/daemon/channel-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/context.d.ts.map +1 -1
- package/dist/_internal/daemon/control-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/http-policy.d.ts.map +1 -1
- package/dist/_internal/daemon/http-policy.js +0 -1
- package/dist/_internal/daemon/integration-route-types.d.ts.map +1 -1
- package/dist/_internal/daemon/integration-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/knowledge-route-types.d.ts.map +1 -1
- package/dist/_internal/daemon/knowledge-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/knowledge-routes.js +5 -4
- package/dist/_internal/daemon/media-route-types.d.ts.map +1 -1
- package/dist/_internal/daemon/media-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/operator.d.ts.map +1 -1
- package/dist/_internal/daemon/remote-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/remote.d.ts.map +1 -1
- package/dist/_internal/daemon/route-helpers.d.ts.map +1 -1
- package/dist/_internal/daemon/runtime-automation-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/runtime-route-types.d.ts +14 -1
- package/dist/_internal/daemon/runtime-route-types.d.ts.map +1 -1
- package/dist/_internal/daemon/runtime-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/runtime-session-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/runtime-session-routes.js +0 -2
- package/dist/_internal/daemon/sessions.d.ts.map +1 -1
- package/dist/_internal/daemon/system-route-types.d.ts.map +1 -1
- package/dist/_internal/daemon/system-routes.d.ts.map +1 -1
- package/dist/_internal/daemon/tasks.d.ts.map +1 -1
- package/dist/_internal/daemon/telemetry-routes.d.ts.map +1 -1
- package/dist/_internal/errors/daemon-error-contract.d.ts.map +1 -1
- package/dist/_internal/errors/index.d.ts +2 -2
- package/dist/_internal/errors/index.js +2 -2
- package/dist/_internal/operator/client-core.d.ts.map +1 -1
- package/dist/_internal/operator/client-core.js +8 -2
- package/dist/_internal/operator/client.d.ts +7 -0
- package/dist/_internal/operator/client.d.ts.map +1 -1
- package/dist/_internal/operator/client.js +32 -1
- package/dist/_internal/peer/client-core.d.ts.map +1 -1
- package/dist/_internal/platform/agents/orchestrator.d.ts +7 -0
- package/dist/_internal/platform/agents/orchestrator.d.ts.map +1 -1
- package/dist/_internal/platform/agents/orchestrator.js +8 -0
- package/dist/_internal/platform/auth/android-keystore-token-store.d.ts +110 -0
- package/dist/_internal/platform/auth/android-keystore-token-store.d.ts.map +1 -0
- package/dist/_internal/platform/auth/android-keystore-token-store.js +164 -0
- package/dist/_internal/platform/auth/auto-refresh-middleware.d.ts +46 -0
- package/dist/_internal/platform/auth/auto-refresh-middleware.d.ts.map +1 -0
- package/dist/_internal/platform/auth/auto-refresh-middleware.js +155 -0
- package/dist/_internal/platform/auth/auto-refresh.d.ts +123 -0
- package/dist/_internal/platform/auth/auto-refresh.d.ts.map +1 -0
- package/dist/_internal/platform/auth/auto-refresh.js +236 -0
- package/dist/_internal/platform/auth/expo-secure-token-store.d.ts +82 -0
- package/dist/_internal/platform/auth/expo-secure-token-store.d.ts.map +1 -0
- package/dist/_internal/platform/auth/expo-secure-token-store.js +135 -0
- package/dist/_internal/platform/auth/index.d.ts +3 -0
- package/dist/_internal/platform/auth/index.d.ts.map +1 -1
- package/dist/_internal/platform/auth/index.js +2 -0
- package/dist/_internal/platform/auth/ios-keychain-token-store.d.ts +88 -0
- package/dist/_internal/platform/auth/ios-keychain-token-store.d.ts.map +1 -0
- package/dist/_internal/platform/auth/ios-keychain-token-store.js +147 -0
- package/dist/_internal/platform/auth/session-manager.d.ts +2 -0
- package/dist/_internal/platform/auth/session-manager.d.ts.map +1 -1
- package/dist/_internal/platform/auth/session-manager.js +9 -1
- package/dist/_internal/platform/auth/token-store.d.ts +13 -0
- package/dist/_internal/platform/auth/token-store.d.ts.map +1 -1
- package/dist/_internal/platform/auth/token-store.js +23 -0
- package/dist/_internal/platform/companion/companion-chat-manager.d.ts +64 -11
- package/dist/_internal/platform/companion/companion-chat-manager.d.ts.map +1 -1
- package/dist/_internal/platform/companion/companion-chat-manager.js +158 -12
- package/dist/_internal/platform/companion/companion-chat-persistence.d.ts +33 -0
- package/dist/_internal/platform/companion/companion-chat-persistence.d.ts.map +1 -0
- package/dist/_internal/platform/companion/companion-chat-persistence.js +115 -0
- package/dist/_internal/platform/companion/companion-chat-rate-limiter.d.ts +47 -0
- package/dist/_internal/platform/companion/companion-chat-rate-limiter.d.ts.map +1 -0
- package/dist/_internal/platform/companion/companion-chat-rate-limiter.js +117 -0
- package/dist/_internal/platform/companion/companion-chat-types.d.ts +2 -4
- package/dist/_internal/platform/companion/companion-chat-types.d.ts.map +1 -1
- package/dist/_internal/platform/companion/companion-chat-types.js +2 -4
- package/dist/_internal/platform/companion/index.d.ts +4 -0
- package/dist/_internal/platform/companion/index.d.ts.map +1 -1
- package/dist/_internal/platform/companion/index.js +2 -0
- package/dist/_internal/platform/daemon/facade-composition.d.ts.map +1 -1
- package/dist/_internal/platform/daemon/facade-composition.js +5 -0
- package/dist/_internal/platform/daemon/facade.d.ts.map +1 -1
- package/dist/_internal/platform/daemon/facade.js +3 -0
- package/dist/_internal/platform/daemon/http/runtime-route-types.d.ts +0 -7
- package/dist/_internal/platform/daemon/http/runtime-route-types.d.ts.map +1 -1
- package/dist/_internal/platform/state/db.d.ts.map +1 -1
- package/dist/_internal/platform/state/db.js +0 -1
- package/dist/_internal/platform/state/sqlite-store.d.ts.map +1 -1
- package/dist/_internal/platform/state/sqlite-store.js +0 -1
- package/dist/_internal/platform/version.js +1 -1
- package/dist/_internal/transport-core/client-transport.d.ts.map +1 -1
- package/dist/_internal/transport-core/event-envelope.d.ts.map +1 -1
- package/dist/_internal/transport-core/event-feeds.d.ts.map +1 -1
- package/dist/_internal/transport-core/index.d.ts +5 -0
- package/dist/_internal/transport-core/index.d.ts.map +1 -1
- package/dist/_internal/transport-core/index.js +3 -0
- package/dist/_internal/transport-core/middleware.d.ts +76 -0
- package/dist/_internal/transport-core/middleware.d.ts.map +1 -0
- package/dist/_internal/transport-core/middleware.js +67 -0
- package/dist/_internal/transport-core/observer.d.ts +53 -0
- package/dist/_internal/transport-core/observer.d.ts.map +1 -0
- package/dist/_internal/transport-core/observer.js +26 -0
- package/dist/_internal/transport-core/otel.d.ts +64 -0
- package/dist/_internal/transport-core/otel.d.ts.map +1 -0
- package/dist/_internal/transport-core/otel.js +149 -0
- package/dist/_internal/transport-direct/index.d.ts.map +1 -1
- package/dist/_internal/transport-direct/index.js +0 -1
- package/dist/_internal/transport-http/contract-client.d.ts +11 -1
- package/dist/_internal/transport-http/contract-client.d.ts.map +1 -1
- package/dist/_internal/transport-http/contract-client.js +18 -4
- package/dist/_internal/transport-http/http-core.d.ts +27 -1
- package/dist/_internal/transport-http/http-core.d.ts.map +1 -1
- package/dist/_internal/transport-http/http-core.js +180 -12
- package/dist/_internal/transport-http/http.d.ts +3 -3
- package/dist/_internal/transport-http/http.d.ts.map +1 -1
- package/dist/_internal/transport-http/http.js +2 -2
- package/dist/_internal/transport-http/index.d.ts +4 -2
- package/dist/_internal/transport-http/index.d.ts.map +1 -1
- package/dist/_internal/transport-http/index.js +2 -1
- package/dist/_internal/transport-http/paths.js +1 -1
- package/dist/_internal/transport-http/reconnect.d.ts +2 -0
- package/dist/_internal/transport-http/reconnect.d.ts.map +1 -1
- package/dist/_internal/transport-http/reconnect.js +4 -2
- package/dist/_internal/transport-http/retry.d.ts +15 -0
- package/dist/_internal/transport-http/retry.d.ts.map +1 -1
- package/dist/_internal/transport-http/retry.js +19 -0
- package/dist/_internal/transport-realtime/domain-events.d.ts.map +1 -1
- package/dist/_internal/transport-realtime/runtime-events.d.ts +10 -3
- package/dist/_internal/transport-realtime/runtime-events.d.ts.map +1 -1
- package/dist/_internal/transport-realtime/runtime-events.js +73 -8
- package/dist/auth.d.ts +38 -3
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +68 -3
- package/dist/client.d.ts +61 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +64 -3
- package/dist/expo.d.ts +1 -0
- package/dist/expo.d.ts.map +1 -1
- package/dist/expo.js +1 -0
- package/dist/observer/index.d.ts +16 -25
- package/dist/observer/index.d.ts.map +1 -1
- package/dist/platform/runtime/transports/http.js +1 -1
- package/dist/react-native.d.ts +2 -0
- package/dist/react-native.d.ts.map +1 -1
- package/dist/react-native.js +2 -0
- package/package.json +16 -3
|
@@ -0,0 +1,236 @@
|
|
|
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 { GoodVibesSdkError } from '../../errors/index.js';
|
|
20
|
+
import { invokeObserver } from '../../../observer/index.js';
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Coordinator implementation
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
export class AutoRefreshCoordinator {
|
|
25
|
+
#tokenStore;
|
|
26
|
+
#autoRefresh;
|
|
27
|
+
#refreshLeewayMs;
|
|
28
|
+
#refresh;
|
|
29
|
+
#observer;
|
|
30
|
+
/** Promise shared across all waiters during an active refresh. */
|
|
31
|
+
#refreshingPromise = null;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.#tokenStore = options.tokenStore;
|
|
34
|
+
this.#autoRefresh = options.autoRefresh;
|
|
35
|
+
this.#refreshLeewayMs = options.refreshLeewayMs;
|
|
36
|
+
this.#refresh = options.refresh;
|
|
37
|
+
this.#observer = options.observer;
|
|
38
|
+
}
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Token expiry helpers
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
/** Read the raw token entry, including optional expiresAt. */
|
|
43
|
+
async #readEntry() {
|
|
44
|
+
const store = this.#tokenStore;
|
|
45
|
+
if (typeof store.getTokenEntry === 'function') {
|
|
46
|
+
return store.getTokenEntry();
|
|
47
|
+
}
|
|
48
|
+
// Fall back to token-only stores that don't expose expiresAt.
|
|
49
|
+
const token = await this.#tokenStore.getToken();
|
|
50
|
+
return { token };
|
|
51
|
+
}
|
|
52
|
+
/** Return true if the token is within the leeway window of expiry. */
|
|
53
|
+
async #isNearExpiry() {
|
|
54
|
+
const { expiresAt } = await this.#readEntry();
|
|
55
|
+
if (expiresAt === undefined)
|
|
56
|
+
return false;
|
|
57
|
+
return Date.now() + this.#refreshLeewayMs >= expiresAt;
|
|
58
|
+
}
|
|
59
|
+
/** Return true if the token is definitively expired (past expiresAt). */
|
|
60
|
+
async #isExpired() {
|
|
61
|
+
const { expiresAt } = await this.#readEntry();
|
|
62
|
+
if (expiresAt === undefined)
|
|
63
|
+
return false;
|
|
64
|
+
return Date.now() >= expiresAt;
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Core refresh logic (serialised via shared promise)
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
/**
|
|
70
|
+
* Trigger a refresh. If one is already in progress, all callers wait on the
|
|
71
|
+
* same promise — no duplicate refresh network calls.
|
|
72
|
+
*
|
|
73
|
+
* After a successful refresh, emits `onAuthTransition` reason='refresh'.
|
|
74
|
+
* After a failed refresh, emits `onAuthTransition` reason='expire' and
|
|
75
|
+
* clears the token (falls back to anonymous).
|
|
76
|
+
*/
|
|
77
|
+
async #doRefresh() {
|
|
78
|
+
if (this.#refreshingPromise) {
|
|
79
|
+
return this.#refreshingPromise;
|
|
80
|
+
}
|
|
81
|
+
const promise = (async () => {
|
|
82
|
+
if (!this.#refresh) {
|
|
83
|
+
// No refresh endpoint — graceful no-op.
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const { token, expiresAt } = await this.#refresh();
|
|
88
|
+
const store = this.#tokenStore;
|
|
89
|
+
if (typeof store.setTokenEntry === 'function') {
|
|
90
|
+
await store.setTokenEntry(token, expiresAt);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
await this.#tokenStore.setToken(token);
|
|
94
|
+
}
|
|
95
|
+
invokeObserver(() => this.#observer?.onAuthTransition?.({
|
|
96
|
+
from: 'token',
|
|
97
|
+
to: 'token',
|
|
98
|
+
reason: 'refresh',
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Refresh failed — fall back to anonymous.
|
|
103
|
+
await this.#tokenStore.clearToken();
|
|
104
|
+
invokeObserver(() => this.#observer?.onAuthTransition?.({
|
|
105
|
+
from: 'token',
|
|
106
|
+
to: 'anonymous',
|
|
107
|
+
reason: 'expire',
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
})();
|
|
111
|
+
this.#refreshingPromise = promise.finally(() => {
|
|
112
|
+
this.#refreshingPromise = null;
|
|
113
|
+
});
|
|
114
|
+
return this.#refreshingPromise;
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Pre-flight check
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
/**
|
|
120
|
+
* Call before dispatching a request. If the token is near expiry (within
|
|
121
|
+
* `refreshLeewayMs`), silently refreshes before the request goes out.
|
|
122
|
+
*
|
|
123
|
+
* If `autoRefresh` is disabled, this is a no-op.
|
|
124
|
+
*/
|
|
125
|
+
async ensureFreshToken() {
|
|
126
|
+
if (!this.#autoRefresh)
|
|
127
|
+
return;
|
|
128
|
+
const nearExpiry = await this.#isNearExpiry();
|
|
129
|
+
if (nearExpiry) {
|
|
130
|
+
await this.#doRefresh();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// Reactive 401 retry wrapper
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
/**
|
|
137
|
+
* Execute `fn` and retry once on 401 after triggering a refresh.
|
|
138
|
+
*
|
|
139
|
+
* If `autoRefresh` is false, the first 401 is rethrown immediately.
|
|
140
|
+
* If the retry also returns 401, throws `GoodVibesSdkError{kind:'auth'}`.
|
|
141
|
+
*
|
|
142
|
+
* @param fn - The request function to execute. Must be side-effect-safe to
|
|
143
|
+
* call twice (called at most twice).
|
|
144
|
+
*/
|
|
145
|
+
async withRetryOn401(fn) {
|
|
146
|
+
try {
|
|
147
|
+
return await fn();
|
|
148
|
+
}
|
|
149
|
+
catch (firstError) {
|
|
150
|
+
if (!this.#autoRefresh || !is401Error(firstError)) {
|
|
151
|
+
throw firstError;
|
|
152
|
+
}
|
|
153
|
+
// Trigger refresh (queued if already in progress).
|
|
154
|
+
const wasExpired = await this.#isExpired();
|
|
155
|
+
await this.#doRefresh();
|
|
156
|
+
// Retry once.
|
|
157
|
+
try {
|
|
158
|
+
return await fn();
|
|
159
|
+
}
|
|
160
|
+
catch (retryError) {
|
|
161
|
+
if (is401Error(retryError)) {
|
|
162
|
+
// Terminal auth failure.
|
|
163
|
+
const authErr = new GoodVibesSdkError('Authentication failed · Token is invalid or has expired · ' +
|
|
164
|
+
(wasExpired
|
|
165
|
+
? 'Re-login to obtain a fresh token.'
|
|
166
|
+
: 'Verify your credentials and token store configuration.'), {
|
|
167
|
+
category: 'authentication',
|
|
168
|
+
source: 'transport',
|
|
169
|
+
status: 401,
|
|
170
|
+
recoverable: false,
|
|
171
|
+
});
|
|
172
|
+
invokeObserver(() => this.#observer?.onError?.(authErr));
|
|
173
|
+
throw authErr;
|
|
174
|
+
}
|
|
175
|
+
throw retryError;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Middleware-facing retry helper
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
/**
|
|
183
|
+
* Refresh the token immediately and execute `fn` exactly once as the retry.
|
|
184
|
+
*
|
|
185
|
+
* Unlike `withRetryOn401`, this method does NOT call `fn` before refreshing —
|
|
186
|
+
* it assumes the caller already received a 401 on the initial attempt. It
|
|
187
|
+
* refreshes the token (serialised via the shared promise, as with
|
|
188
|
+
* `withRetryOn401`) and then calls `fn` a single time.
|
|
189
|
+
*
|
|
190
|
+
* If `fn` throws a 401 on retry, a terminal `GoodVibesSdkError{kind:'auth'}`
|
|
191
|
+
* is thrown with the Wave 6 three-part message format.
|
|
192
|
+
*
|
|
193
|
+
* Used by `createAutoRefreshMiddleware` to avoid making an extra HTTP call
|
|
194
|
+
* when the middleware already observed the initial 401 from `next()`.
|
|
195
|
+
*
|
|
196
|
+
* @param fn - The retry request to execute after the refresh completes.
|
|
197
|
+
*/
|
|
198
|
+
async refreshAndRetryOnce(fn) {
|
|
199
|
+
const wasExpired = await this.#isExpired();
|
|
200
|
+
// Refresh (serialised — if one is already in progress, join it).
|
|
201
|
+
await this.#doRefresh();
|
|
202
|
+
// Single retry attempt.
|
|
203
|
+
try {
|
|
204
|
+
return await fn();
|
|
205
|
+
}
|
|
206
|
+
catch (retryError) {
|
|
207
|
+
if (is401Error(retryError)) {
|
|
208
|
+
const authErr = new GoodVibesSdkError('Authentication failed · Token is invalid or has expired · ' +
|
|
209
|
+
(wasExpired
|
|
210
|
+
? 'Re-login to obtain a fresh token.'
|
|
211
|
+
: 'Verify your credentials and token store configuration.'), {
|
|
212
|
+
category: 'authentication',
|
|
213
|
+
source: 'transport',
|
|
214
|
+
status: 401,
|
|
215
|
+
recoverable: false,
|
|
216
|
+
});
|
|
217
|
+
invokeObserver(() => this.#observer?.onError?.(authErr));
|
|
218
|
+
throw authErr;
|
|
219
|
+
}
|
|
220
|
+
throw retryError;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// Helpers
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
function is401Error(error) {
|
|
228
|
+
if (typeof error !== 'object' || error === null)
|
|
229
|
+
return false;
|
|
230
|
+
const e = error;
|
|
231
|
+
const status = e.status ??
|
|
232
|
+
e.transport?.status ??
|
|
233
|
+
e.response?.status ??
|
|
234
|
+
e.cause?.response?.status;
|
|
235
|
+
return status === 401;
|
|
236
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* expo-secure-token-store.ts
|
|
3
|
+
*
|
|
4
|
+
* Token store backed by `expo-secure-store` — Expo's hardware-backed secure
|
|
5
|
+
* storage layer (iOS Keychain / Android Keystore behind the scenes).
|
|
6
|
+
*
|
|
7
|
+
* `expo-secure-store` is an **optional peer dependency** — this module does
|
|
8
|
+
* NOT eagerly import it. The module is loaded lazily so the SDK remains
|
|
9
|
+
* loadable in environments where the native module is absent.
|
|
10
|
+
*
|
|
11
|
+
* ## Installation
|
|
12
|
+
*
|
|
13
|
+
* ```sh
|
|
14
|
+
* expo install expo-secure-store
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Wave 6 three-part error messages: [what happened] · [why] · [what to do]
|
|
18
|
+
*/
|
|
19
|
+
import type { GoodVibesTokenStore } from '../../../auth.js';
|
|
20
|
+
/**
|
|
21
|
+
* String union of the `SecureStore.ACCESSIBLE_*` constant names.
|
|
22
|
+
* Resolved by property lookup on the dynamically-imported module so that
|
|
23
|
+
* numeric values never need to be hard-coded here.
|
|
24
|
+
*/
|
|
25
|
+
export type ExpoSecureStoreAccessible = 'AFTER_FIRST_UNLOCK' | 'AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY' | 'ALWAYS' | 'ALWAYS_THIS_DEVICE_ONLY' | 'WHEN_PASSCODE_SET_THIS_DEVICE_ONLY' | 'WHEN_UNLOCKED' | 'WHEN_UNLOCKED_THIS_DEVICE_ONLY';
|
|
26
|
+
export interface ExpoSecureTokenStoreOptions {
|
|
27
|
+
/**
|
|
28
|
+
* The key used for the secure-store entry.
|
|
29
|
+
* @default 'goodvibes-sdk-token'
|
|
30
|
+
*/
|
|
31
|
+
readonly key?: string;
|
|
32
|
+
/**
|
|
33
|
+
* iOS Keychain service name. Passed through to `SecureStore.setItemAsync`
|
|
34
|
+
* as `keychainService`. Has no effect on Android.
|
|
35
|
+
*/
|
|
36
|
+
readonly keychainService?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Controls when the stored data is accessible. Maps to the
|
|
39
|
+
* `SecureStore.ACCESSIBLE_*` constants.
|
|
40
|
+
*
|
|
41
|
+
* @default 'WHEN_UNLOCKED_THIS_DEVICE_ONLY'
|
|
42
|
+
*/
|
|
43
|
+
readonly accessible?: ExpoSecureStoreAccessible;
|
|
44
|
+
}
|
|
45
|
+
export interface ExpoSecureTokenStore extends GoodVibesTokenStore {
|
|
46
|
+
/**
|
|
47
|
+
* Persist a token together with an optional Unix-epoch expiry (ms).
|
|
48
|
+
* Both values are serialised into a single secure-store entry.
|
|
49
|
+
* Picked up automatically by the `TokenStore` wrapper via duck-typing.
|
|
50
|
+
*/
|
|
51
|
+
setTokenEntry(token: string | null, expiresAt?: number): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Return the stored token and optional expiry timestamp.
|
|
54
|
+
* Picked up automatically by the `TokenStore` wrapper via duck-typing.
|
|
55
|
+
*/
|
|
56
|
+
getTokenEntry(): Promise<{
|
|
57
|
+
token: string | null;
|
|
58
|
+
expiresAt?: number;
|
|
59
|
+
}>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create a `GoodVibesTokenStore` backed by `expo-secure-store`.
|
|
63
|
+
*
|
|
64
|
+
* Both the `token` and `expiresAt` values are serialised as a single JSON
|
|
65
|
+
* blob into one secure-store entry, keeping the keychain tidy.
|
|
66
|
+
*
|
|
67
|
+
* `expo-secure-store` is an **optional peer dependency** — install it with:
|
|
68
|
+
*
|
|
69
|
+
* ```sh
|
|
70
|
+
* expo install expo-secure-store
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* import { createExpoSecureTokenStore, createExpoGoodVibesSdk } from '@pellux/goodvibes-sdk/expo';
|
|
76
|
+
*
|
|
77
|
+
* const tokenStore = createExpoSecureTokenStore({ key: 'gv-token' });
|
|
78
|
+
* const sdk = createExpoGoodVibesSdk({ baseUrl: 'https://daemon.example.com', tokenStore });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function createExpoSecureTokenStore(options?: ExpoSecureTokenStoreOptions, __loadModule?: () => Promise<unknown>): ExpoSecureTokenStore;
|
|
82
|
+
//# sourceMappingURL=expo-secure-token-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expo-secure-token-store.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/auth/expo-secure-token-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAM5D;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GACjC,oBAAoB,GACpB,qCAAqC,GACrC,QAAQ,GACR,yBAAyB,GACzB,oCAAoC,GACpC,eAAe,GACf,gCAAgC,CAAC;AAMrC,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAElC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,yBAAyB,CAAC;CACjD;AA4CD,MAAM,WAAW,oBAAqB,SAAQ,mBAAmB;IAC/D;;;;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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,GAAE,2BAAgC,EACzC,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GACpC,oBAAoB,CAiFtB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* expo-secure-token-store.ts
|
|
3
|
+
*
|
|
4
|
+
* Token store backed by `expo-secure-store` — Expo's hardware-backed secure
|
|
5
|
+
* storage layer (iOS Keychain / Android Keystore behind the scenes).
|
|
6
|
+
*
|
|
7
|
+
* `expo-secure-store` is an **optional peer dependency** — this module does
|
|
8
|
+
* NOT eagerly import it. The module is loaded lazily so the SDK remains
|
|
9
|
+
* loadable in environments where the native module is absent.
|
|
10
|
+
*
|
|
11
|
+
* ## Installation
|
|
12
|
+
*
|
|
13
|
+
* ```sh
|
|
14
|
+
* expo install expo-secure-store
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Wave 6 three-part error messages: [what happened] · [why] · [what to do]
|
|
18
|
+
*/
|
|
19
|
+
import { GoodVibesSdkError } from '../../errors/index.js';
|
|
20
|
+
let _mod = null;
|
|
21
|
+
async function loadExpoSecureStore() {
|
|
22
|
+
if (_mod !== null)
|
|
23
|
+
return _mod;
|
|
24
|
+
try {
|
|
25
|
+
_mod = await import('expo-secure-store');
|
|
26
|
+
return _mod;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new GoodVibesSdkError('expo-secure-store is not installed — the Expo secure token store cannot be initialised. ' +
|
|
30
|
+
'This optional peer dependency is required to persist tokens in native hardware-backed storage. ' +
|
|
31
|
+
'Run `expo install expo-secure-store` and rebuild your app.', {
|
|
32
|
+
code: 'EXPO_SECURE_STORE_NOT_INSTALLED',
|
|
33
|
+
category: 'config',
|
|
34
|
+
source: 'config',
|
|
35
|
+
recoverable: false,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Factory
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
/**
|
|
43
|
+
* Create a `GoodVibesTokenStore` backed by `expo-secure-store`.
|
|
44
|
+
*
|
|
45
|
+
* Both the `token` and `expiresAt` values are serialised as a single JSON
|
|
46
|
+
* blob into one secure-store entry, keeping the keychain tidy.
|
|
47
|
+
*
|
|
48
|
+
* `expo-secure-store` is an **optional peer dependency** — install it with:
|
|
49
|
+
*
|
|
50
|
+
* ```sh
|
|
51
|
+
* expo install expo-secure-store
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { createExpoSecureTokenStore, createExpoGoodVibesSdk } from '@pellux/goodvibes-sdk/expo';
|
|
57
|
+
*
|
|
58
|
+
* const tokenStore = createExpoSecureTokenStore({ key: 'gv-token' });
|
|
59
|
+
* const sdk = createExpoGoodVibesSdk({ baseUrl: 'https://daemon.example.com', tokenStore });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function createExpoSecureTokenStore(options = {}, __loadModule) {
|
|
63
|
+
const key = options.key?.trim() || 'goodvibes-sdk-token';
|
|
64
|
+
const accessible = options.accessible ?? 'WHEN_UNLOCKED_THIS_DEVICE_ONLY';
|
|
65
|
+
const keychainService = options.keychainService;
|
|
66
|
+
async function resolveModule() {
|
|
67
|
+
if (__loadModule !== undefined) {
|
|
68
|
+
return __loadModule();
|
|
69
|
+
}
|
|
70
|
+
return loadExpoSecureStore();
|
|
71
|
+
}
|
|
72
|
+
function buildStoreOptions(mod) {
|
|
73
|
+
const opts = {};
|
|
74
|
+
const accessibleValue = mod[accessible];
|
|
75
|
+
if (accessibleValue !== undefined) {
|
|
76
|
+
opts['accessible'] = accessibleValue;
|
|
77
|
+
}
|
|
78
|
+
else if (options.accessible !== undefined) {
|
|
79
|
+
console.warn(`[pellux/goodvibes-sdk] expo-secure-store does not expose ${accessible}; falling back to default`);
|
|
80
|
+
}
|
|
81
|
+
if (keychainService !== undefined) {
|
|
82
|
+
opts['keychainService'] = keychainService;
|
|
83
|
+
}
|
|
84
|
+
return opts;
|
|
85
|
+
}
|
|
86
|
+
async function readPayload() {
|
|
87
|
+
const mod = await resolveModule();
|
|
88
|
+
const raw = await mod.getItemAsync(key, buildStoreOptions(mod));
|
|
89
|
+
if (raw === null || raw === '')
|
|
90
|
+
return null;
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(raw);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function writePayload(payload) {
|
|
99
|
+
const mod = await resolveModule();
|
|
100
|
+
if (payload === null) {
|
|
101
|
+
await mod.deleteItemAsync(key, buildStoreOptions(mod));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await mod.setItemAsync(key, JSON.stringify(payload), buildStoreOptions(mod));
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
async getToken() {
|
|
108
|
+
const payload = await readPayload();
|
|
109
|
+
return payload?.token ?? null;
|
|
110
|
+
},
|
|
111
|
+
async setToken(token) {
|
|
112
|
+
if (token === null) {
|
|
113
|
+
await writePayload(null);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
await writePayload({ token, expiresAt: null });
|
|
117
|
+
},
|
|
118
|
+
async clearToken() {
|
|
119
|
+
await writePayload(null);
|
|
120
|
+
},
|
|
121
|
+
async setTokenEntry(token, expiresAt) {
|
|
122
|
+
if (token === null) {
|
|
123
|
+
await writePayload(null);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
await writePayload({ token, expiresAt: expiresAt ?? null });
|
|
127
|
+
},
|
|
128
|
+
async getTokenEntry() {
|
|
129
|
+
const payload = await readPayload();
|
|
130
|
+
if (payload === null)
|
|
131
|
+
return { token: null };
|
|
132
|
+
return { token: payload.token, expiresAt: payload.expiresAt ?? undefined };
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { PermissionResolver } from './permission-resolver.js';
|
|
2
2
|
export { SessionManager } from './session-manager.js';
|
|
3
3
|
export { TokenStore } from './token-store.js';
|
|
4
|
+
export { AutoRefreshCoordinator } from './auto-refresh.js';
|
|
5
|
+
export type { AutoRefreshOptions, AutoRefreshCoordinatorOptions } from './auto-refresh.js';
|
|
6
|
+
export { createAutoRefreshMiddleware } from './auto-refresh-middleware.js';
|
|
4
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/auth/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/auth/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,YAAY,EAAE,kBAAkB,EAAE,6BAA6B,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC"}
|
|
@@ -5,3 +5,5 @@
|
|
|
5
5
|
export { PermissionResolver } from './permission-resolver.js';
|
|
6
6
|
export { SessionManager } from './session-manager.js';
|
|
7
7
|
export { TokenStore } from './token-store.js';
|
|
8
|
+
export { AutoRefreshCoordinator } from './auto-refresh.js';
|
|
9
|
+
export { createAutoRefreshMiddleware } from './auto-refresh-middleware.js';
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ios-keychain-token-store.ts
|
|
3
|
+
*
|
|
4
|
+
* Token store backed by `react-native-keychain` for bare React Native on iOS.
|
|
5
|
+
*
|
|
6
|
+
* Uses `Keychain.setGenericPassword` / `getGenericPassword` /
|
|
7
|
+
* `resetGenericPassword` to persist tokens in the iOS Keychain.
|
|
8
|
+
*
|
|
9
|
+
* `react-native-keychain` is an **optional peer dependency** — this module
|
|
10
|
+
* does NOT import it at the top level.
|
|
11
|
+
*
|
|
12
|
+
* ## Installation
|
|
13
|
+
*
|
|
14
|
+
* ```sh
|
|
15
|
+
* npm install react-native-keychain
|
|
16
|
+
* npx pod-install # iOS CocoaPods link
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* Wave 6 three-part error messages: [what happened] · [why] · [what to do]
|
|
20
|
+
*/
|
|
21
|
+
import type { GoodVibesTokenStore } from '../../../auth.js';
|
|
22
|
+
/**
|
|
23
|
+
* String union of the `Keychain.ACCESSIBLE` enum values.
|
|
24
|
+
* Resolved by property lookup on `Keychain.ACCESSIBLE` at runtime.
|
|
25
|
+
*/
|
|
26
|
+
export type KeychainAccessible = 'WHEN_UNLOCKED' | 'AFTER_FIRST_UNLOCK' | 'ALWAYS' | 'WHEN_PASSCODE_SET_THIS_DEVICE_ONLY' | 'WHEN_UNLOCKED_THIS_DEVICE_ONLY' | 'AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY' | 'ALWAYS_THIS_DEVICE_ONLY';
|
|
27
|
+
export interface IOSKeychainTokenStoreOptions {
|
|
28
|
+
/**
|
|
29
|
+
* The keychain service identifier.
|
|
30
|
+
* @default 'com.pellux.goodvibes-sdk'
|
|
31
|
+
*/
|
|
32
|
+
readonly service?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Controls when stored data is accessible.
|
|
35
|
+
* Maps to `Keychain.ACCESSIBLE` constants.
|
|
36
|
+
* @default 'WHEN_UNLOCKED_THIS_DEVICE_ONLY'
|
|
37
|
+
*/
|
|
38
|
+
readonly accessible?: KeychainAccessible;
|
|
39
|
+
/**
|
|
40
|
+
* iOS Keychain access group for sharing credentials between apps.
|
|
41
|
+
* Maps to `Keychain.Options.accessGroup`.
|
|
42
|
+
*/
|
|
43
|
+
readonly accessGroup?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface IOSKeychainTokenStore extends GoodVibesTokenStore {
|
|
46
|
+
/**
|
|
47
|
+
* Persist a token together with an optional Unix-epoch expiry (ms).
|
|
48
|
+
* Token and expiresAt are serialised as JSON in the keychain password slot.
|
|
49
|
+
* Picked up automatically by the `TokenStore` wrapper via duck-typing.
|
|
50
|
+
*/
|
|
51
|
+
setTokenEntry(token: string | null, expiresAt?: number): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Return the stored token and optional expiry timestamp.
|
|
54
|
+
* Picked up automatically by the `TokenStore` wrapper via duck-typing.
|
|
55
|
+
*/
|
|
56
|
+
getTokenEntry(): Promise<{
|
|
57
|
+
token: string | null;
|
|
58
|
+
expiresAt?: number;
|
|
59
|
+
}>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create a `GoodVibesTokenStore` backed by the iOS Keychain via
|
|
63
|
+
* `react-native-keychain`.
|
|
64
|
+
*
|
|
65
|
+
* Suitable for **bare React Native** iOS apps. For Expo-managed workflow, use
|
|
66
|
+
* `createExpoSecureTokenStore` instead.
|
|
67
|
+
*
|
|
68
|
+
* Both the `token` and `expiresAt` values are serialised as a single JSON
|
|
69
|
+
* blob in the keychain password slot. The username slot is fixed to
|
|
70
|
+
* `'goodvibes-sdk'`.
|
|
71
|
+
*
|
|
72
|
+
* `react-native-keychain` is an **optional peer dependency** — install it with:
|
|
73
|
+
*
|
|
74
|
+
* ```sh
|
|
75
|
+
* npm install react-native-keychain
|
|
76
|
+
* npx pod-install
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* import { createIOSKeychainTokenStore, createReactNativeGoodVibesSdk } from '@pellux/goodvibes-sdk/react-native';
|
|
82
|
+
*
|
|
83
|
+
* const tokenStore = createIOSKeychainTokenStore({ service: 'com.myapp.gv' });
|
|
84
|
+
* const sdk = createReactNativeGoodVibesSdk({ baseUrl: 'https://daemon.example.com', tokenStore });
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export declare function createIOSKeychainTokenStore(options?: IOSKeychainTokenStoreOptions, __loadModule?: () => Promise<unknown>): IOSKeychainTokenStore;
|
|
88
|
+
//# sourceMappingURL=ios-keychain-token-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ios-keychain-token-store.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/auth/ios-keychain-token-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAM5D;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAC1B,eAAe,GACf,oBAAoB,GACpB,QAAQ,GACR,oCAAoC,GACpC,gCAAgC,GAChC,qCAAqC,GACrC,yBAAyB,CAAC;AAM9B,MAAM,WAAW,4BAA4B;IAC3C;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAEzC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAkDD,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE;;;;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;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,GAAE,4BAAiC,EAC1C,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GACpC,qBAAqB,CAgFvB"}
|