@configdirector/nuxt-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -0
- package/dist/module.d.mts +85 -0
- package/dist/module.json +8 -0
- package/dist/module.mjs +66 -0
- package/dist/runtime/app/composables/useConfigDirectorClient.d.mts +8 -0
- package/dist/runtime/app/composables/useConfigDirectorClient.d.ts +8 -0
- package/dist/runtime/app/composables/useConfigDirectorClient.mjs +8 -0
- package/dist/runtime/app/composables/useConfigDirectorContext.d.mts +12 -0
- package/dist/runtime/app/composables/useConfigDirectorContext.d.ts +12 -0
- package/dist/runtime/app/composables/useConfigDirectorContext.mjs +30 -0
- package/dist/runtime/app/composables/useConfigDirectorStatus.d.mts +10 -0
- package/dist/runtime/app/composables/useConfigDirectorStatus.d.ts +10 -0
- package/dist/runtime/app/composables/useConfigDirectorStatus.mjs +13 -0
- package/dist/runtime/app/composables/useConfigDirectorValue.d.mts +12 -0
- package/dist/runtime/app/composables/useConfigDirectorValue.d.ts +12 -0
- package/dist/runtime/app/composables/useConfigDirectorValue.mjs +19 -0
- package/dist/runtime/nitro/middleware.d.mts +130 -0
- package/dist/runtime/nitro/middleware.d.ts +130 -0
- package/dist/runtime/nitro/middleware.mjs +8 -0
- package/dist/runtime/nitro/plugin.d.mts +12 -0
- package/dist/runtime/nitro/plugin.d.ts +12 -0
- package/dist/runtime/nitro/plugin.mjs +1397 -0
- package/dist/runtime/plugin.client.d.mts +4 -0
- package/dist/runtime/plugin.client.mjs +1162 -0
- package/dist/runtime/plugin.server.d.mts +4 -0
- package/dist/runtime/plugin.server.mjs +106 -0
- package/dist/runtime/server/composables/useConfigDirectorClient.d.mts +6 -0
- package/dist/runtime/server/composables/useConfigDirectorClient.d.ts +6 -0
- package/dist/runtime/server/composables/useConfigDirectorClient.mjs +9 -0
- package/dist/runtime/types-CQWNx-gI.d.mts +34 -0
- package/dist/runtime/types-CkzBXJwA.d.mts +4 -0
- package/dist/runtime/types-D9MFb76Q.d.mts +85 -0
- package/dist/runtime/types-KSnINlzh.d.mts +135 -0
- package/package.json +76 -0
|
@@ -0,0 +1,1162 @@
|
|
|
1
|
+
import { defineNuxtPlugin, useNuxtApp, useRuntimeConfig, useState } from "#app";
|
|
2
|
+
import { readonly, shallowRef, toRaw } from "vue";
|
|
3
|
+
import { consola } from "consola";
|
|
4
|
+
//#region ../js-client-core/src/Emitter.ts
|
|
5
|
+
var Emitter = class {
|
|
6
|
+
handlerMap = /* @__PURE__ */ new Map();
|
|
7
|
+
on(name, handler) {
|
|
8
|
+
const handlers = this.handlerMap.get(name);
|
|
9
|
+
if (handlers) handlers.push(handler);
|
|
10
|
+
else this.handlerMap.set(name, [handler]);
|
|
11
|
+
}
|
|
12
|
+
once(name, handler) {
|
|
13
|
+
const self = this;
|
|
14
|
+
function onceHandler(payload) {
|
|
15
|
+
self.off(name, onceHandler);
|
|
16
|
+
handler.apply(self, payload);
|
|
17
|
+
}
|
|
18
|
+
this.on(name, onceHandler);
|
|
19
|
+
}
|
|
20
|
+
off(name, handler) {
|
|
21
|
+
const handlers = this.handlerMap.get(name);
|
|
22
|
+
if (!handlers) return;
|
|
23
|
+
if (handler) {
|
|
24
|
+
const listenerIndex = handlers.indexOf(handler);
|
|
25
|
+
if (listenerIndex >= 0) handlers.splice(listenerIndex, 1);
|
|
26
|
+
} else this.handlerMap.set(name, []);
|
|
27
|
+
}
|
|
28
|
+
clear() {
|
|
29
|
+
this.handlerMap.clear();
|
|
30
|
+
}
|
|
31
|
+
emit(name, payload) {
|
|
32
|
+
const handlers = this.handlerMap.get(name);
|
|
33
|
+
if (!handlers) return;
|
|
34
|
+
handlers.slice().map((h) => h(payload));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region ../shared/src/errors.ts
|
|
39
|
+
var ConfigDirectorConnectionError = class ConfigDirectorConnectionError extends Error {
|
|
40
|
+
name = "ConfigDirectorConnectionError";
|
|
41
|
+
status;
|
|
42
|
+
constructor(message, status) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.status = status;
|
|
45
|
+
Object.setPrototypeOf(this, ConfigDirectorConnectionError.prototype);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var ConfigDirectorValidationError = class ConfigDirectorValidationError extends Error {
|
|
49
|
+
name = "ConfigDirectorValidationError";
|
|
50
|
+
constructor(message) {
|
|
51
|
+
super(message);
|
|
52
|
+
Object.setPrototypeOf(this, ConfigDirectorValidationError.prototype);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var ConfigDirectorInitializationError = class ConfigDirectorInitializationError extends Error {
|
|
56
|
+
name = "ConfigDirectorInitializationError";
|
|
57
|
+
constructor(message) {
|
|
58
|
+
super(message);
|
|
59
|
+
Object.setPrototypeOf(this, ConfigDirectorInitializationError.prototype);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region ../js-client-core/src/errors.ts
|
|
64
|
+
const isFetchErrorFatal = (fetchError) => {
|
|
65
|
+
if (fetchError?.name === "NotAllowedError") return true;
|
|
66
|
+
else if (fetchError instanceof TypeError) return true;
|
|
67
|
+
return false;
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region ../eventsource/src/errors.ts
|
|
71
|
+
var StreamClosedError = class StreamClosedError extends Error {
|
|
72
|
+
name = "StreamClosedError";
|
|
73
|
+
constructor(message) {
|
|
74
|
+
super(message);
|
|
75
|
+
Object.setPrototypeOf(this, StreamClosedError.prototype);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var MissingResponseBodyError = class MissingResponseBodyError extends Error {
|
|
79
|
+
name = "MissingResponseBodyError";
|
|
80
|
+
constructor(message) {
|
|
81
|
+
super(message);
|
|
82
|
+
Object.setPrototypeOf(this, MissingResponseBodyError.prototype);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var InvalidOptionError = class InvalidOptionError extends Error {
|
|
86
|
+
name = "InvalidOptionError";
|
|
87
|
+
constructor(message) {
|
|
88
|
+
super(message);
|
|
89
|
+
Object.setPrototypeOf(this, InvalidOptionError.prototype);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var ValueOutOfRangeError = class ValueOutOfRangeError extends Error {
|
|
93
|
+
name = "ValueOutOfRangeError";
|
|
94
|
+
constructor(message) {
|
|
95
|
+
super(message);
|
|
96
|
+
Object.setPrototypeOf(this, ValueOutOfRangeError.prototype);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region ../eventsource/src/types.ts
|
|
101
|
+
let ReadyState = /* @__PURE__ */ function(ReadyState) {
|
|
102
|
+
ReadyState["OPEN"] = "open";
|
|
103
|
+
ReadyState["CLOSED"] = "closed";
|
|
104
|
+
ReadyState["CONNECTING"] = "connecting";
|
|
105
|
+
return ReadyState;
|
|
106
|
+
}({});
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region ../eventsource/src/EventSourceParser.ts
|
|
109
|
+
var EventSourceParser = class {
|
|
110
|
+
isFirstChunk = true;
|
|
111
|
+
bufferedInput = "";
|
|
112
|
+
currentEvent = { data: "" };
|
|
113
|
+
lastEventId;
|
|
114
|
+
onEvent;
|
|
115
|
+
onRetry;
|
|
116
|
+
onComment;
|
|
117
|
+
constructor(options) {
|
|
118
|
+
this.currentEvent = { data: "" };
|
|
119
|
+
this.onEvent = options.onEvent ?? (() => {});
|
|
120
|
+
this.onRetry = options.onRetry;
|
|
121
|
+
this.onComment = options.onComment;
|
|
122
|
+
}
|
|
123
|
+
parse(chunk) {
|
|
124
|
+
let input = chunk;
|
|
125
|
+
if (this.isFirstChunk) {
|
|
126
|
+
this.isFirstChunk = false;
|
|
127
|
+
if (input.startsWith("")) input = input.slice(1);
|
|
128
|
+
else if (input.startsWith("")) input = input.slice(3);
|
|
129
|
+
}
|
|
130
|
+
const lines = this.scanLines(this.bufferedInput + input);
|
|
131
|
+
for (const line of lines) this.dispatchLine(line);
|
|
132
|
+
}
|
|
133
|
+
finish() {
|
|
134
|
+
this.bufferedInput = "";
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Scans `text` character by character to extract complete lines, recognizing
|
|
138
|
+
* CR, LF, and CRLF as line terminators per the SSE spec. Any unterminated
|
|
139
|
+
* trailing text is buffered and prepended to the next chunk.
|
|
140
|
+
*/
|
|
141
|
+
scanLines(text) {
|
|
142
|
+
const lines = [];
|
|
143
|
+
let lineStart = 0;
|
|
144
|
+
let i = 0;
|
|
145
|
+
while (i < text.length) {
|
|
146
|
+
const ch = text[i];
|
|
147
|
+
if (ch === "\r" || ch === "\n") {
|
|
148
|
+
lines.push(text.slice(lineStart, i));
|
|
149
|
+
if (ch === "\r" && text[i + 1] === "\n") i++;
|
|
150
|
+
i++;
|
|
151
|
+
lineStart = i;
|
|
152
|
+
} else i++;
|
|
153
|
+
}
|
|
154
|
+
this.bufferedInput = text.slice(lineStart);
|
|
155
|
+
return lines;
|
|
156
|
+
}
|
|
157
|
+
dispatchLine(line) {
|
|
158
|
+
if (line.startsWith(":")) {
|
|
159
|
+
this.onComment?.(this.extractValue(line, 1));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (line === "") {
|
|
163
|
+
this.emitEvent();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
this.applyField(this.parseField(line));
|
|
167
|
+
}
|
|
168
|
+
emitEvent() {
|
|
169
|
+
const data = this.currentEvent.data.endsWith("\n") ? this.currentEvent.data.slice(0, -1) : this.currentEvent.data;
|
|
170
|
+
if (data !== "") this.onEvent({
|
|
171
|
+
...this.currentEvent,
|
|
172
|
+
id: this.lastEventId,
|
|
173
|
+
data
|
|
174
|
+
});
|
|
175
|
+
this.currentEvent = { data: "" };
|
|
176
|
+
}
|
|
177
|
+
applyField(parsed) {
|
|
178
|
+
switch (parsed.field) {
|
|
179
|
+
case "event":
|
|
180
|
+
this.currentEvent.type = parsed.value;
|
|
181
|
+
break;
|
|
182
|
+
case "data":
|
|
183
|
+
this.currentEvent.data += parsed.value + "\n";
|
|
184
|
+
break;
|
|
185
|
+
case "id":
|
|
186
|
+
if (!parsed.value.includes("\0")) this.lastEventId = parsed.value;
|
|
187
|
+
break;
|
|
188
|
+
case "retry":
|
|
189
|
+
if (/^\d+$/.test(parsed.value)) this.onRetry?.(Number.parseInt(parsed.value, 10));
|
|
190
|
+
break;
|
|
191
|
+
default: break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
parseField(line) {
|
|
195
|
+
const colon = line.indexOf(":");
|
|
196
|
+
if (colon === -1) return {
|
|
197
|
+
field: line,
|
|
198
|
+
value: ""
|
|
199
|
+
};
|
|
200
|
+
return {
|
|
201
|
+
field: line.slice(0, colon),
|
|
202
|
+
value: this.extractValue(line, colon + 1)
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/** Returns the value portion of a line, stripping a single leading space per spec. */
|
|
206
|
+
extractValue(line, from) {
|
|
207
|
+
return line[from] === " " ? line.slice(from + 1) : line.slice(from);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region ../eventsource/src/EventSourceClient.ts
|
|
212
|
+
var EventSourceClient = class {
|
|
213
|
+
DEFAULT_SHOULD_RECONNECT = () => true;
|
|
214
|
+
DEFAULT_CALCULATE_RECONNECT_DELAY = () => this.serverReconnectionTime;
|
|
215
|
+
url;
|
|
216
|
+
headers;
|
|
217
|
+
requestOptions;
|
|
218
|
+
lastEventId;
|
|
219
|
+
reconnectAttempt = 0;
|
|
220
|
+
abortController = new AbortController();
|
|
221
|
+
reconnectTimeout;
|
|
222
|
+
_readyState = ReadyState.CLOSED;
|
|
223
|
+
serverReconnectionTime = 2e3;
|
|
224
|
+
_fetch;
|
|
225
|
+
_onError;
|
|
226
|
+
_onMessage;
|
|
227
|
+
_onComment;
|
|
228
|
+
_onConnect;
|
|
229
|
+
_onDisconnect;
|
|
230
|
+
_shouldReconnect;
|
|
231
|
+
_calculateReconnectDelay;
|
|
232
|
+
constructor(options) {
|
|
233
|
+
this.url = options.url.toString();
|
|
234
|
+
this.lastEventId = options.lastEventId;
|
|
235
|
+
this.headers = {
|
|
236
|
+
Accept: "text/event-stream",
|
|
237
|
+
...options.headers
|
|
238
|
+
};
|
|
239
|
+
this.requestOptions = {
|
|
240
|
+
body: options.body,
|
|
241
|
+
method: options.method ?? "GET",
|
|
242
|
+
mode: options.mode,
|
|
243
|
+
credentials: options.credentials,
|
|
244
|
+
redirect: options.redirect,
|
|
245
|
+
referrer: options.referrer,
|
|
246
|
+
referrerPolicy: options.referrerPolicy
|
|
247
|
+
};
|
|
248
|
+
this._fetch = options.fetch ?? ((url, init) => fetch(url, init));
|
|
249
|
+
this._onError = this.resolveCallback(options.onError, () => {}, "onError");
|
|
250
|
+
this._onConnect = this.resolveCallback(options.onConnect, () => {}, "onConnect");
|
|
251
|
+
this._onDisconnect = this.resolveCallback(options.onDisconnect, () => {}, "onDisconnect");
|
|
252
|
+
this._onMessage = this.resolveCallback(options.onMessage, () => {}, "onMessage");
|
|
253
|
+
this._onComment = this.resolveCallback(options.onComment, () => {}, "onComment");
|
|
254
|
+
this._shouldReconnect = this.resolveCallback(options.shouldReconnect, this.DEFAULT_SHOULD_RECONNECT, "shouldReconnect");
|
|
255
|
+
this._calculateReconnectDelay = this.resolveCallback(options.calculateReconnectDelay, this.DEFAULT_CALCULATE_RECONNECT_DELAY, "calculateReconnectDelay");
|
|
256
|
+
}
|
|
257
|
+
set onError(value) {
|
|
258
|
+
this._onError = this.resolveCallback(value, () => {}, "onError");
|
|
259
|
+
}
|
|
260
|
+
set onMessage(value) {
|
|
261
|
+
this._onMessage = this.resolveCallback(value, () => {}, "onMessage");
|
|
262
|
+
}
|
|
263
|
+
set onComment(value) {
|
|
264
|
+
this._onComment = this.resolveCallback(value, () => {}, "onComment");
|
|
265
|
+
}
|
|
266
|
+
set onConnect(value) {
|
|
267
|
+
this._onConnect = this.resolveCallback(value, () => {}, "onConnect");
|
|
268
|
+
}
|
|
269
|
+
set onDisconnect(value) {
|
|
270
|
+
this._onDisconnect = this.resolveCallback(value, () => {}, "onDisconnect");
|
|
271
|
+
}
|
|
272
|
+
set shouldReconnect(value) {
|
|
273
|
+
this._shouldReconnect = this.resolveCallback(value, this.DEFAULT_SHOULD_RECONNECT, "shouldReconnect");
|
|
274
|
+
}
|
|
275
|
+
set calculateReconnectDelay(value) {
|
|
276
|
+
this._calculateReconnectDelay = this.resolveCallback(value, this.DEFAULT_CALCULATE_RECONNECT_DELAY, "shouldReconnect");
|
|
277
|
+
}
|
|
278
|
+
get readyState() {
|
|
279
|
+
return this._readyState;
|
|
280
|
+
}
|
|
281
|
+
connect() {
|
|
282
|
+
if (this._readyState !== ReadyState.CLOSED) return;
|
|
283
|
+
this.reconnectAttempt = 0;
|
|
284
|
+
this.initiateConnection();
|
|
285
|
+
}
|
|
286
|
+
close() {
|
|
287
|
+
clearTimeout(this.reconnectTimeout);
|
|
288
|
+
this._readyState = ReadyState.CLOSED;
|
|
289
|
+
this.abortController.abort();
|
|
290
|
+
}
|
|
291
|
+
initiateConnection() {
|
|
292
|
+
this.abortController = new AbortController();
|
|
293
|
+
this._readyState = ReadyState.CONNECTING;
|
|
294
|
+
this._fetch(this.url, {
|
|
295
|
+
...this.requestOptions,
|
|
296
|
+
headers: this.buildRequestHeaders(),
|
|
297
|
+
signal: this.abortController.signal,
|
|
298
|
+
cache: "no-store"
|
|
299
|
+
}).then((response) => this.handleFetchResponse(response)).catch((error) => {
|
|
300
|
+
if (error.name === "AbortError" || this.abortController.signal.aborted) {
|
|
301
|
+
this.close();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this._onError(error);
|
|
305
|
+
this.scheduleReconnect({ error });
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
async handleFetchResponse(response) {
|
|
309
|
+
const { body, status } = response;
|
|
310
|
+
if (status === 204) {
|
|
311
|
+
this.disconnected();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (status >= 400) {
|
|
315
|
+
this.scheduleReconnect({ status });
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (!body) throw new MissingResponseBodyError("The server response did not have a body");
|
|
319
|
+
this._readyState = ReadyState.OPEN;
|
|
320
|
+
this._onConnect();
|
|
321
|
+
this.reconnectAttempt = 0;
|
|
322
|
+
const parser = new EventSourceParser({
|
|
323
|
+
onEvent: (event) => {
|
|
324
|
+
if (typeof event.id === "string") this.lastEventId = event.id;
|
|
325
|
+
this._onMessage(event);
|
|
326
|
+
},
|
|
327
|
+
onComment: (comment) => this._onComment(comment),
|
|
328
|
+
onRetry: (retryDelay) => this.serverReconnectionTime = retryDelay
|
|
329
|
+
});
|
|
330
|
+
await this.readResponseStream(body, parser);
|
|
331
|
+
this.scheduleReconnect({
|
|
332
|
+
status,
|
|
333
|
+
error: new StreamClosedError("The server response stream was closed")
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
async readResponseStream(body, parser) {
|
|
337
|
+
const reader = body.getReader();
|
|
338
|
+
const decoder = new TextDecoder();
|
|
339
|
+
let done = false;
|
|
340
|
+
while (!done) {
|
|
341
|
+
const result = await reader.read();
|
|
342
|
+
if (result.value) parser.parse(decoder.decode(result.value, { stream: true }));
|
|
343
|
+
done = result.done;
|
|
344
|
+
}
|
|
345
|
+
parser.finish();
|
|
346
|
+
}
|
|
347
|
+
disconnected() {
|
|
348
|
+
clearTimeout(this.reconnectTimeout);
|
|
349
|
+
this._readyState = ReadyState.CLOSED;
|
|
350
|
+
this._onDisconnect();
|
|
351
|
+
}
|
|
352
|
+
buildRequestHeaders() {
|
|
353
|
+
const lastEventIdHeader = this.lastEventId ? { "Last-Event-ID": this.lastEventId } : void 0;
|
|
354
|
+
return {
|
|
355
|
+
...this.headers,
|
|
356
|
+
...lastEventIdHeader
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
scheduleReconnect(state) {
|
|
360
|
+
if (this.abortController.signal.aborted) return;
|
|
361
|
+
clearTimeout(this.reconnectTimeout);
|
|
362
|
+
this.reconnectAttempt += 1;
|
|
363
|
+
const reconnectionState = {
|
|
364
|
+
attempt: this.reconnectAttempt,
|
|
365
|
+
status: state.status,
|
|
366
|
+
error: state.error,
|
|
367
|
+
serverReconnectionTime: this.serverReconnectionTime
|
|
368
|
+
};
|
|
369
|
+
if (!this._shouldReconnect(reconnectionState)) {
|
|
370
|
+
this.disconnected();
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
this._readyState = ReadyState.CONNECTING;
|
|
374
|
+
let delay = this._calculateReconnectDelay(reconnectionState);
|
|
375
|
+
if (!Number.isFinite(delay) || delay < 1 || delay > 1e3 * 60 * 60) {
|
|
376
|
+
this._onError(new ValueOutOfRangeError(`The calculated reconnect delay is out of range: ${delay}. Defaulting to ${this.serverReconnectionTime}`));
|
|
377
|
+
delay = this.serverReconnectionTime;
|
|
378
|
+
}
|
|
379
|
+
this.reconnectTimeout = setTimeout(() => this.initiateConnection(), delay);
|
|
380
|
+
}
|
|
381
|
+
resolveCallback(provided, fallback, optionName) {
|
|
382
|
+
if (provided == null) return fallback;
|
|
383
|
+
if (typeof provided !== "function") throw new InvalidOptionError(`${optionName} must be a function`);
|
|
384
|
+
return provided;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
//#endregion
|
|
388
|
+
//#region ../eventsource/src/index.ts
|
|
389
|
+
const createEventSourceClient = (options) => {
|
|
390
|
+
return new EventSourceClient(options);
|
|
391
|
+
};
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region ../js-client-core/src/StreamingTransport.ts
|
|
394
|
+
var StreamingTransport = class {
|
|
395
|
+
logger;
|
|
396
|
+
eventSource;
|
|
397
|
+
eventEmitter = new Emitter();
|
|
398
|
+
url;
|
|
399
|
+
timeoutTimer;
|
|
400
|
+
constructor(options) {
|
|
401
|
+
this.options = options;
|
|
402
|
+
this.options = options;
|
|
403
|
+
this.logger = options.logger;
|
|
404
|
+
this.url = options.resolveUrl("client/sse/v1", options.baseUrl);
|
|
405
|
+
}
|
|
406
|
+
async connect(context, timeout) {
|
|
407
|
+
if (this.eventSource) this.close();
|
|
408
|
+
const eventSourcePromise = new Promise((resolve, reject) => {
|
|
409
|
+
this.eventSource = createEventSourceClient({
|
|
410
|
+
url: this.url,
|
|
411
|
+
method: "POST",
|
|
412
|
+
headers: { "Content-Type": "application/json" },
|
|
413
|
+
fetch: this.options.fetch,
|
|
414
|
+
body: JSON.stringify({
|
|
415
|
+
givenContext: context,
|
|
416
|
+
metaContext: this.options.metaContext,
|
|
417
|
+
clientSdkKey: this.options.clientSdkKey
|
|
418
|
+
}),
|
|
419
|
+
onMessage: ({ data }) => {
|
|
420
|
+
this.dispatchMessage(data);
|
|
421
|
+
},
|
|
422
|
+
onConnect: () => {
|
|
423
|
+
this.logger.debug("[EventSourceTransport] Connected");
|
|
424
|
+
resolve(this);
|
|
425
|
+
},
|
|
426
|
+
shouldReconnect: (state) => {
|
|
427
|
+
const reconnect = !this.isStatusFatal(state.status);
|
|
428
|
+
if (!reconnect) reject(this.prepareFatalError(state.status, state.error));
|
|
429
|
+
return reconnect;
|
|
430
|
+
},
|
|
431
|
+
calculateReconnectDelay: (state) => {
|
|
432
|
+
const delay = this.options.connectionRetryDelay(state.attempt);
|
|
433
|
+
this.logger.warn(`[EventSourceTransport] Scheduling reconnect in ${delay}ms.`);
|
|
434
|
+
return delay;
|
|
435
|
+
},
|
|
436
|
+
onError: (error) => {
|
|
437
|
+
this.logger.debug("[EventSourceTransport] Error: ", error);
|
|
438
|
+
},
|
|
439
|
+
onDisconnect: () => {
|
|
440
|
+
this.logger.debug("[EventSourceTransport] Disconnected");
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
this.eventSource.connect();
|
|
444
|
+
});
|
|
445
|
+
clearTimeout(this.timeoutTimer);
|
|
446
|
+
this.timeoutTimer = void 0;
|
|
447
|
+
return Promise.race([eventSourcePromise, new Promise((resolve) => {
|
|
448
|
+
this.timeoutTimer = setTimeout(() => resolve(this), timeout);
|
|
449
|
+
})]).finally(() => clearTimeout(this.timeoutTimer));
|
|
450
|
+
}
|
|
451
|
+
dispatchMessage(data) {
|
|
452
|
+
try {
|
|
453
|
+
const json = JSON.parse(data);
|
|
454
|
+
this.eventEmitter.emit("configSetReceived", json);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
this.logger.error("[EventSourceTransport] Error parsing and dispatching config data update: ", error);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
prepareFatalError(responseStatus, error) {
|
|
460
|
+
const status = responseStatus ?? 0;
|
|
461
|
+
return new ConfigDirectorConnectionError(`${`Connection failed with status: ${responseStatus ?? "unknown"}.`}${error ? ` Error: ${error}` : ""}. This is an unrecoverable error, will not attempt to reconnect.`, status);
|
|
462
|
+
}
|
|
463
|
+
isStatusFatal(status) {
|
|
464
|
+
return !!status && status >= 400 && status < 500;
|
|
465
|
+
}
|
|
466
|
+
on(name, handler) {
|
|
467
|
+
this.eventEmitter.on(name, handler);
|
|
468
|
+
}
|
|
469
|
+
off(name, handler) {
|
|
470
|
+
this.eventEmitter.off(name, handler);
|
|
471
|
+
}
|
|
472
|
+
clear() {
|
|
473
|
+
this.eventEmitter.clear();
|
|
474
|
+
}
|
|
475
|
+
close() {
|
|
476
|
+
this.eventSource?.close();
|
|
477
|
+
clearTimeout(this.timeoutTimer);
|
|
478
|
+
}
|
|
479
|
+
dispose() {
|
|
480
|
+
this.close();
|
|
481
|
+
this.clear();
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region ../shared/src/value-parser.ts
|
|
486
|
+
const getRequestedType = (defaultValue) => {
|
|
487
|
+
const baseType = typeof defaultValue;
|
|
488
|
+
if (baseType === "object") try {
|
|
489
|
+
return defaultValue.constructor?.name ?? baseType;
|
|
490
|
+
} catch {
|
|
491
|
+
return baseType;
|
|
492
|
+
}
|
|
493
|
+
else if (baseType === "function") try {
|
|
494
|
+
const functionName = defaultValue.name;
|
|
495
|
+
return functionName ? `function: ${functionName}` : baseType;
|
|
496
|
+
} catch {
|
|
497
|
+
return baseType;
|
|
498
|
+
}
|
|
499
|
+
return baseType;
|
|
500
|
+
};
|
|
501
|
+
const isNumericNativeType = (requestedType) => {
|
|
502
|
+
return requestedType === "number" || requestedType === "bigint";
|
|
503
|
+
};
|
|
504
|
+
const parseConfigValue = (configState, defaultValue) => {
|
|
505
|
+
const value = configState.value;
|
|
506
|
+
const requestedType = getRequestedType(defaultValue);
|
|
507
|
+
if (!value) return {
|
|
508
|
+
parsedValue: defaultValue,
|
|
509
|
+
requestedType,
|
|
510
|
+
usedDefault: true,
|
|
511
|
+
reason: "value-missing"
|
|
512
|
+
};
|
|
513
|
+
if (typeof defaultValue === "string") return {
|
|
514
|
+
parsedValue: value,
|
|
515
|
+
requestedType,
|
|
516
|
+
usedDefault: false,
|
|
517
|
+
reason: "found-match"
|
|
518
|
+
};
|
|
519
|
+
if (typeof defaultValue === "boolean" && configState.type === "boolean") {
|
|
520
|
+
const boolValue = parseConfigBoolean(value);
|
|
521
|
+
const hasBoolean = typeof boolValue === "boolean";
|
|
522
|
+
return {
|
|
523
|
+
parsedValue: hasBoolean ? boolValue : defaultValue,
|
|
524
|
+
requestedType,
|
|
525
|
+
usedDefault: !hasBoolean,
|
|
526
|
+
reason: hasBoolean ? "found-match" : "invalid-boolean"
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
if (isNumericNativeType(typeof defaultValue) && configState.type === "integer") {
|
|
530
|
+
const numValue = parseConfigInteger(value);
|
|
531
|
+
const hasNumber = typeof numValue === "number";
|
|
532
|
+
return {
|
|
533
|
+
parsedValue: hasNumber ? numValue : defaultValue,
|
|
534
|
+
requestedType,
|
|
535
|
+
usedDefault: !hasNumber,
|
|
536
|
+
reason: hasNumber ? "found-match" : "invalid-number"
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
if (isNumericNativeType(typeof defaultValue) && (configState.type === "float" || configState.type === "enum")) {
|
|
540
|
+
const numValue = parseConfigFloat(value);
|
|
541
|
+
const hasNumber = typeof numValue === "number";
|
|
542
|
+
return {
|
|
543
|
+
parsedValue: hasNumber ? numValue : defaultValue,
|
|
544
|
+
requestedType,
|
|
545
|
+
usedDefault: !hasNumber,
|
|
546
|
+
reason: hasNumber ? "found-match" : "invalid-number"
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
parsedValue: value,
|
|
551
|
+
requestedType,
|
|
552
|
+
usedDefault: false,
|
|
553
|
+
reason: "found-match"
|
|
554
|
+
};
|
|
555
|
+
};
|
|
556
|
+
const parseConfigBoolean = (value) => {
|
|
557
|
+
if (!value) return;
|
|
558
|
+
const lowerValue = value.toLowerCase();
|
|
559
|
+
if (lowerValue != "true" && lowerValue != "false") return;
|
|
560
|
+
return lowerValue === "true";
|
|
561
|
+
};
|
|
562
|
+
const parseConfigInteger = (value) => {
|
|
563
|
+
if (!value) return;
|
|
564
|
+
const num = Number.parseInt(value);
|
|
565
|
+
if (isNaN(num) || !isFinite(num)) return;
|
|
566
|
+
return num;
|
|
567
|
+
};
|
|
568
|
+
const parseConfigFloat = (value) => {
|
|
569
|
+
if (!value) return;
|
|
570
|
+
const num = Number.parseFloat(value);
|
|
571
|
+
if (isNaN(num) || !isFinite(num)) return;
|
|
572
|
+
return num;
|
|
573
|
+
};
|
|
574
|
+
//#endregion
|
|
575
|
+
//#region ../shared/src/logger.ts
|
|
576
|
+
const LEVELS = {
|
|
577
|
+
off: -1,
|
|
578
|
+
error: 0,
|
|
579
|
+
warn: 1,
|
|
580
|
+
info: 2,
|
|
581
|
+
debug: 3
|
|
582
|
+
};
|
|
583
|
+
const buildDateFormatter = () => {
|
|
584
|
+
const baseOptions = {
|
|
585
|
+
year: "numeric",
|
|
586
|
+
month: "2-digit",
|
|
587
|
+
day: "2-digit",
|
|
588
|
+
hour: "2-digit",
|
|
589
|
+
minute: "2-digit",
|
|
590
|
+
second: "2-digit",
|
|
591
|
+
timeZoneName: "short"
|
|
592
|
+
};
|
|
593
|
+
try {
|
|
594
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
595
|
+
...baseOptions,
|
|
596
|
+
fractionalSecondDigits: 3
|
|
597
|
+
});
|
|
598
|
+
} catch {
|
|
599
|
+
return new Intl.DateTimeFormat(void 0, baseOptions);
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
var DefaultConsoleLogger = class {
|
|
603
|
+
dateFormatter;
|
|
604
|
+
constructor(level, decorator) {
|
|
605
|
+
this.level = level;
|
|
606
|
+
this.decorator = decorator;
|
|
607
|
+
this.level = level;
|
|
608
|
+
this.decorator = decorator;
|
|
609
|
+
this.dateFormatter = buildDateFormatter();
|
|
610
|
+
}
|
|
611
|
+
debug(message, ...args) {
|
|
612
|
+
this.log(console.debug, "debug", message, ...args);
|
|
613
|
+
}
|
|
614
|
+
info(message, ...args) {
|
|
615
|
+
this.log(console.info, "info", message, ...args);
|
|
616
|
+
}
|
|
617
|
+
warn(message, ...args) {
|
|
618
|
+
this.log(console.warn, "warn", message, ...args);
|
|
619
|
+
}
|
|
620
|
+
error(message, ...args) {
|
|
621
|
+
this.log(console.error, "error", message, ...args);
|
|
622
|
+
}
|
|
623
|
+
log(loggerFunction, level, message, ...args) {
|
|
624
|
+
if (LEVELS[this.level] >= LEVELS[level]) loggerFunction(`[${this.dateFormatter.format(/* @__PURE__ */ new Date())}] ${this.decorator?.decorateMessage(message)}`, ...args);
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
//#endregion
|
|
628
|
+
//#region ../js-client-core/src/logger.ts
|
|
629
|
+
var LogMessageDecorator = class {
|
|
630
|
+
decorateMessage(message) {
|
|
631
|
+
return `[ConfigDirector:js-client-sdk] ${message}`;
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
const createDefaultLogger$1 = (level, messageDecorator) => {
|
|
635
|
+
return new DefaultConsoleLogger(level ?? "warn", messageDecorator ?? new LogMessageDecorator());
|
|
636
|
+
};
|
|
637
|
+
//#endregion
|
|
638
|
+
//#region ../shared/src/fetchWithTimeout.ts
|
|
639
|
+
const fetchWithTimeout = async (timeout, resource, options, logger) => {
|
|
640
|
+
const abortController = new AbortController();
|
|
641
|
+
const abortTimeoutId = setTimeout(() => {
|
|
642
|
+
logger.debug("[fetchWithTimeout] Reached timeout, aborting request");
|
|
643
|
+
abortController.abort();
|
|
644
|
+
}, timeout);
|
|
645
|
+
try {
|
|
646
|
+
const response = await fetch(typeof resource === "string" || resource instanceof Request ? resource : resource.toString(), {
|
|
647
|
+
...options,
|
|
648
|
+
signal: abortController.signal
|
|
649
|
+
});
|
|
650
|
+
clearTimeout(abortTimeoutId);
|
|
651
|
+
return response;
|
|
652
|
+
} catch (error) {
|
|
653
|
+
logger.warn("[fetchWithTimeout] Fetch error: ", error);
|
|
654
|
+
clearTimeout(abortTimeoutId);
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region ../js-client-core/src/PullTransport.ts
|
|
660
|
+
var PullTransport = class {
|
|
661
|
+
logger;
|
|
662
|
+
eventEmitter = new Emitter();
|
|
663
|
+
url;
|
|
664
|
+
fatalError = false;
|
|
665
|
+
constructor(options) {
|
|
666
|
+
this.options = options;
|
|
667
|
+
this.options = options;
|
|
668
|
+
this.logger = options.logger;
|
|
669
|
+
this.url = options.resolveUrl("client/pull/v1", options.baseUrl);
|
|
670
|
+
}
|
|
671
|
+
async connect(context, timeout) {
|
|
672
|
+
if (this.fatalError) {
|
|
673
|
+
this.logger.warn("[PullTransport] There was a prior unrecoverable error. Ignoring attempt to reconnect.");
|
|
674
|
+
return this;
|
|
675
|
+
}
|
|
676
|
+
try {
|
|
677
|
+
const response = await fetchWithTimeout(timeout, this.url, {
|
|
678
|
+
method: "POST",
|
|
679
|
+
headers: { "Content-Type": "application/json" },
|
|
680
|
+
body: JSON.stringify({
|
|
681
|
+
givenContext: context,
|
|
682
|
+
metaContext: this.options.metaContext,
|
|
683
|
+
clientSdkKey: this.options.clientSdkKey
|
|
684
|
+
})
|
|
685
|
+
}, this.logger);
|
|
686
|
+
if (!response.ok) if (this.isStatusFatal(response.status)) {
|
|
687
|
+
this.fatalError = true;
|
|
688
|
+
throw this.prepareFatalResponseStatusError(response.status, await response.text());
|
|
689
|
+
} else throw new ConfigDirectorConnectionError(`Connection failed with status: ${response.status}`, response.status);
|
|
690
|
+
const json = JSON.parse(await response.text());
|
|
691
|
+
this.eventEmitter.emit("configSetReceived", json);
|
|
692
|
+
return this;
|
|
693
|
+
} catch (fetchError) {
|
|
694
|
+
if (isFetchErrorFatal(fetchError)) {
|
|
695
|
+
this.fatalError = true;
|
|
696
|
+
throw new ConfigDirectorConnectionError(`Connection failed with fatal error: ${fetchError}. This is an unrecoverable error, retry attempts will be ignored.`);
|
|
697
|
+
} else if (fetchError instanceof SyntaxError) throw new ConfigDirectorConnectionError(`Failed to parse the response from the server: ${fetchError}`);
|
|
698
|
+
else throw new ConfigDirectorConnectionError(`Connection failed with error: ${fetchError}.`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
prepareFatalResponseStatusError(responseStatus, errorBody) {
|
|
702
|
+
const status = responseStatus ?? 0;
|
|
703
|
+
return new ConfigDirectorConnectionError(`${`Connection failed with status: ${responseStatus ?? "unknown"}`}${(errorBody?.trim()?.length ?? 0) > 0 ? ` (${errorBody})` : ""}. This is an unrecoverable error, retry attempts will be ignored.`, status);
|
|
704
|
+
}
|
|
705
|
+
isStatusFatal(status) {
|
|
706
|
+
return !!status && status >= 400 && status < 500;
|
|
707
|
+
}
|
|
708
|
+
on(name, handler) {
|
|
709
|
+
this.eventEmitter.on(name, handler);
|
|
710
|
+
}
|
|
711
|
+
off(name, handler) {
|
|
712
|
+
this.eventEmitter.off(name, handler);
|
|
713
|
+
}
|
|
714
|
+
clear() {
|
|
715
|
+
this.eventEmitter.clear();
|
|
716
|
+
}
|
|
717
|
+
close() {}
|
|
718
|
+
dispose() {
|
|
719
|
+
this.close();
|
|
720
|
+
this.clear();
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region ../shared/src/url.ts
|
|
725
|
+
const defaultUrlFactory = (input, base) => new URL(input, base?.toString());
|
|
726
|
+
const parseUrl = (url) => {
|
|
727
|
+
if (!url) return;
|
|
728
|
+
try {
|
|
729
|
+
return new URL(url);
|
|
730
|
+
} catch (error) {
|
|
731
|
+
throw new ConfigDirectorValidationError(`Invalid URL '${url}'. Parsing failed: ${error}`);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
//#endregion
|
|
735
|
+
//#region ../js-client-core/src/DefaultConfigDirectorClient.ts
|
|
736
|
+
const MAX_EXPONENTIAL_DELAY = 9;
|
|
737
|
+
var DefaultConfigDirectorClient = class {
|
|
738
|
+
logger;
|
|
739
|
+
telemetryClient;
|
|
740
|
+
configSet;
|
|
741
|
+
handlersMap = /* @__PURE__ */ new Map();
|
|
742
|
+
transport;
|
|
743
|
+
eventEmitter = new Emitter();
|
|
744
|
+
timeout;
|
|
745
|
+
timeoutTimer;
|
|
746
|
+
ready = false;
|
|
747
|
+
initializing = false;
|
|
748
|
+
readyPromise;
|
|
749
|
+
readyResolve;
|
|
750
|
+
currentContext;
|
|
751
|
+
streaming;
|
|
752
|
+
constructor(telemetryClient, clientSdkKey, sdkOptions, clientOptions, internalClientOptions) {
|
|
753
|
+
this.logger = clientOptions?.logger ?? createDefaultLogger$1();
|
|
754
|
+
this.timeout = clientOptions?.connection?.timeout ?? 3e3;
|
|
755
|
+
const urlFactory = internalClientOptions?.urlFactory ?? defaultUrlFactory;
|
|
756
|
+
const baseUrl = this.parseUrl(clientOptions?.connection?.url, urlFactory) ?? "https://client-sdk-api.configdirector.com";
|
|
757
|
+
this.streaming = clientOptions?.connection?.streaming === false ? false : true;
|
|
758
|
+
const transportConstructor = this.streaming ? StreamingTransport : PullTransport;
|
|
759
|
+
this.telemetryClient = telemetryClient;
|
|
760
|
+
this.transport = new transportConstructor({
|
|
761
|
+
clientSdkKey,
|
|
762
|
+
baseUrl,
|
|
763
|
+
resolveUrl: urlFactory,
|
|
764
|
+
metaContext: {
|
|
765
|
+
...clientOptions?.metadata,
|
|
766
|
+
sdkName: sdkOptions.sdkName,
|
|
767
|
+
sdkVersion: sdkOptions.sdkVersion,
|
|
768
|
+
userAgent: navigator?.userAgent
|
|
769
|
+
},
|
|
770
|
+
logger: this.logger,
|
|
771
|
+
fetch: internalClientOptions?.fetch,
|
|
772
|
+
connectionRetryDelay: internalClientOptions?.connectionRetryDelay ?? ((attempt) => {
|
|
773
|
+
return Math.pow(2, Math.min(attempt, MAX_EXPONENTIAL_DELAY)) * 1e3;
|
|
774
|
+
})
|
|
775
|
+
});
|
|
776
|
+
this.transport.on("configSetReceived", (configSet) => {
|
|
777
|
+
this.readyResolve?.();
|
|
778
|
+
const configKeys = Object.keys(configSet.configs);
|
|
779
|
+
if (!this.configSet || configSet.kind == "full") {
|
|
780
|
+
this.configSet = configSet;
|
|
781
|
+
this.eventEmitter.emit("configsUpdated", { keys: configKeys });
|
|
782
|
+
this.updateWatchers(configSet.configs);
|
|
783
|
+
} else {
|
|
784
|
+
this.configSet.configs = {
|
|
785
|
+
...this.configSet.configs,
|
|
786
|
+
...configSet.configs
|
|
787
|
+
};
|
|
788
|
+
this.eventEmitter.emit("configsUpdated", { keys: configKeys });
|
|
789
|
+
this.updateWatchers(configSet.configs);
|
|
790
|
+
}
|
|
791
|
+
this.logger.debug("[ConfigDirectorClient] ConfigSet updated from server:", { keys: configKeys });
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
async initialize(context) {
|
|
795
|
+
this.initializing = true;
|
|
796
|
+
await this.connectToTransport(context, "initialization");
|
|
797
|
+
}
|
|
798
|
+
async updateContext(context) {
|
|
799
|
+
await this.connectToTransport(context, "context update");
|
|
800
|
+
}
|
|
801
|
+
get context() {
|
|
802
|
+
return this.currentContext;
|
|
803
|
+
}
|
|
804
|
+
async connectToTransport(context, caller) {
|
|
805
|
+
try {
|
|
806
|
+
this.ready = false;
|
|
807
|
+
this.readyPromise = new Promise((resolve) => {
|
|
808
|
+
this.readyResolve = resolve;
|
|
809
|
+
}).then(() => {
|
|
810
|
+
this.ready = true;
|
|
811
|
+
this.initializing = false;
|
|
812
|
+
this.eventEmitter.emit("clientReady", { action: caller });
|
|
813
|
+
this.logger.debug("[ConfigDirectorClient] Received initial payload from the server, client is ready");
|
|
814
|
+
});
|
|
815
|
+
const startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
816
|
+
await this.transport.connect(context ?? {}, this.timeout);
|
|
817
|
+
this.currentContext = context;
|
|
818
|
+
this.telemetryClient.updateContext(context);
|
|
819
|
+
this.eventEmitter.emit("contextUpdated", { context });
|
|
820
|
+
const elapsedTime = (/* @__PURE__ */ new Date()).getTime() - startTime;
|
|
821
|
+
const remainingTimeout = this.timeout - elapsedTime;
|
|
822
|
+
clearTimeout(this.timeoutTimer);
|
|
823
|
+
this.timeoutTimer = void 0;
|
|
824
|
+
if (remainingTimeout > 0) await Promise.race([this.readyPromise, new Promise((resolve) => {
|
|
825
|
+
this.timeoutTimer = setTimeout(() => resolve(), remainingTimeout);
|
|
826
|
+
})]).finally(() => clearTimeout(this.timeoutTimer));
|
|
827
|
+
if (!this.ready) {
|
|
828
|
+
const warningDetails = this.streaming ? "The client will continue to retry since there were no fatal errors detected. Configs will return the default value until the connection succeeds." : "Since the client was configured without streaming, configs may not update and always return the default value.";
|
|
829
|
+
this.logger.warn(`[ConfigDirectorClient] Timed out waiting for ${caller} after ${this.timeout}ms. ${warningDetails}`);
|
|
830
|
+
}
|
|
831
|
+
} catch (error) {
|
|
832
|
+
this.logger.error(`[ConfigDirectorClient] An error occurred during ${caller}: `, error);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
updateWatchers(configsMap) {
|
|
836
|
+
Object.values(configsMap).forEach((v) => this.updateWatchersForConfig(v));
|
|
837
|
+
}
|
|
838
|
+
updateWatchersForConfig(configState) {
|
|
839
|
+
this.handlersMap.get(configState.key)?.forEach((h) => {
|
|
840
|
+
const value = this.getValueFromConfigState(configState.key, configState, h.defaultValue);
|
|
841
|
+
h.handler(value);
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
watch(configKey, defaultValue, callback) {
|
|
845
|
+
this.validateDefaultValue(defaultValue);
|
|
846
|
+
const handlers = this.handlersMap.get(configKey);
|
|
847
|
+
const handlerWithOptions = {
|
|
848
|
+
handler: callback,
|
|
849
|
+
defaultValue,
|
|
850
|
+
requestedType: typeof defaultValue
|
|
851
|
+
};
|
|
852
|
+
if (handlers) handlers.push(handlerWithOptions);
|
|
853
|
+
else this.handlersMap.set(configKey, [handlerWithOptions]);
|
|
854
|
+
return () => this.unwatch(configKey, callback);
|
|
855
|
+
}
|
|
856
|
+
unwatch(configKey, callback) {
|
|
857
|
+
const handlers = this.handlersMap.get(configKey);
|
|
858
|
+
if (!handlers) return;
|
|
859
|
+
if (callback) {
|
|
860
|
+
const index = handlers.findIndex((h) => h.handler == callback);
|
|
861
|
+
if (index >= 0) handlers?.splice(index, 1);
|
|
862
|
+
} else handlers.splice(0);
|
|
863
|
+
}
|
|
864
|
+
getValue(configKey, defaultValue) {
|
|
865
|
+
this.validateDefaultValue(defaultValue);
|
|
866
|
+
const configState = this.configSet?.configs[configKey];
|
|
867
|
+
return this.getValueFromConfigState(configKey, configState, defaultValue);
|
|
868
|
+
}
|
|
869
|
+
getValueFromConfigState(configKey, configState, defaultValue) {
|
|
870
|
+
if (!configState) {
|
|
871
|
+
this.logger.debug(`[ConfigDirectorClient] No config state found for '${configKey}', returning default value '${defaultValue}'`);
|
|
872
|
+
this.telemetryClient.evaluatedConfig({
|
|
873
|
+
contextId: this.currentContext?.id,
|
|
874
|
+
key: configKey,
|
|
875
|
+
defaultValue,
|
|
876
|
+
requestedType: getRequestedType(defaultValue),
|
|
877
|
+
evaluatedValue: defaultValue,
|
|
878
|
+
usedDefault: true,
|
|
879
|
+
evaluationReason: "config-state-missing"
|
|
880
|
+
});
|
|
881
|
+
return defaultValue;
|
|
882
|
+
}
|
|
883
|
+
const parseResult = parseConfigValue(configState, defaultValue);
|
|
884
|
+
this.telemetryClient.evaluatedConfig({
|
|
885
|
+
contextId: this.currentContext?.id,
|
|
886
|
+
key: configKey,
|
|
887
|
+
defaultValue,
|
|
888
|
+
requestedType: parseResult.requestedType,
|
|
889
|
+
evaluatedValue: parseResult.parsedValue,
|
|
890
|
+
usedDefault: parseResult.usedDefault,
|
|
891
|
+
evaluationReason: parseResult.reason
|
|
892
|
+
});
|
|
893
|
+
this.logger.debug(`[ConfigDirectorClient] Evaluated '${configKey}' to '${parseResult.parsedValue}'`);
|
|
894
|
+
return parseResult.parsedValue;
|
|
895
|
+
}
|
|
896
|
+
parseUrl(url, urlFactory) {
|
|
897
|
+
if (!url) return;
|
|
898
|
+
try {
|
|
899
|
+
return urlFactory(url);
|
|
900
|
+
} catch (error) {
|
|
901
|
+
throw new ConfigDirectorValidationError(`Invalid base URL '${url}'. Parsing failed: ${error}`);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
validateDefaultValue(defaultValue) {
|
|
905
|
+
if (defaultValue === void 0 || defaultValue === null) throw new ConfigDirectorValidationError("Invalid default value. The default value for a config must be defined and non-null.");
|
|
906
|
+
if (typeof defaultValue === "function") throw new ConfigDirectorValidationError("Invalid default value. The default value for a config cannot be a function.");
|
|
907
|
+
}
|
|
908
|
+
get isReady() {
|
|
909
|
+
return this.ready;
|
|
910
|
+
}
|
|
911
|
+
get isInitializing() {
|
|
912
|
+
return this.initializing;
|
|
913
|
+
}
|
|
914
|
+
on(eventName, handler) {
|
|
915
|
+
this.eventEmitter.on(eventName, handler);
|
|
916
|
+
}
|
|
917
|
+
off(eventName, handler) {
|
|
918
|
+
this.eventEmitter.off(eventName, handler);
|
|
919
|
+
}
|
|
920
|
+
clear() {
|
|
921
|
+
this.logger.debug("[ConfigDirectorClient] clear() has been called, removing all observers");
|
|
922
|
+
this.eventEmitter.clear();
|
|
923
|
+
this.handlersMap.clear();
|
|
924
|
+
}
|
|
925
|
+
unwatchAll() {
|
|
926
|
+
this.handlersMap.clear();
|
|
927
|
+
}
|
|
928
|
+
pauseNetwork() {
|
|
929
|
+
this.logger.debug("[ConfigDirectorClient] pauseNetwork() called, pausing transport connection");
|
|
930
|
+
clearTimeout(this.timeoutTimer);
|
|
931
|
+
this.timeoutTimer = void 0;
|
|
932
|
+
this.transport.close();
|
|
933
|
+
this.ready = false;
|
|
934
|
+
}
|
|
935
|
+
async resumeNetwork() {
|
|
936
|
+
await this.connectToTransport(this.currentContext, "network resume");
|
|
937
|
+
}
|
|
938
|
+
async close() {
|
|
939
|
+
this.logger.debug("[ConfigDirectorClient] close() has been called, closing connection to server");
|
|
940
|
+
clearTimeout(this.timeoutTimer);
|
|
941
|
+
this.readyResolve?.();
|
|
942
|
+
this.telemetryClient.close();
|
|
943
|
+
this.transport.close();
|
|
944
|
+
this.ready = false;
|
|
945
|
+
}
|
|
946
|
+
dispose() {
|
|
947
|
+
this.clear();
|
|
948
|
+
this.close();
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
//#endregion
|
|
952
|
+
//#region ../shared/src/telemetry/utils.ts
|
|
953
|
+
const CONFIG_VALUE_MAX_LENGTH = 500;
|
|
954
|
+
const sanitizeValue = (value, type) => {
|
|
955
|
+
if (type === "json") try {
|
|
956
|
+
return djb2Hash(JSON.stringify(value));
|
|
957
|
+
} catch {
|
|
958
|
+
return value.toString().slice(0, CONFIG_VALUE_MAX_LENGTH);
|
|
959
|
+
}
|
|
960
|
+
return value.toString().slice(0, CONFIG_VALUE_MAX_LENGTH);
|
|
961
|
+
};
|
|
962
|
+
const djb2Hash = (data) => {
|
|
963
|
+
return djb2(new TextEncoder().encode(data)).toString(16).padStart(8, "0");
|
|
964
|
+
};
|
|
965
|
+
const djb2 = (bytes) => {
|
|
966
|
+
let hash = 5381;
|
|
967
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
968
|
+
hash = (hash << 5) + hash + (bytes[i] ?? 0);
|
|
969
|
+
hash = hash >>> 0;
|
|
970
|
+
}
|
|
971
|
+
return hash;
|
|
972
|
+
};
|
|
973
|
+
//#endregion
|
|
974
|
+
//#region ../js-browser-client/src/telemetry/WebWorkerTelemetryClient.ts
|
|
975
|
+
var WebWorkerTelemetryClient = class {
|
|
976
|
+
logger;
|
|
977
|
+
worker;
|
|
978
|
+
closePromise;
|
|
979
|
+
closeResolve;
|
|
980
|
+
visibilityHandler;
|
|
981
|
+
constructor(options) {
|
|
982
|
+
this.logger = options.logger;
|
|
983
|
+
const initializeMessage = {
|
|
984
|
+
type: "Initialize",
|
|
985
|
+
payload: {
|
|
986
|
+
sdkKey: options.sdkKey,
|
|
987
|
+
baseUrl: options.baseUrl.toString(),
|
|
988
|
+
evaluationQueueLimit: options.evaluationQueueLimit ?? 1e3,
|
|
989
|
+
initialFlushIntervalDelay: options.initialFlushIntervalDelay ?? 5e3,
|
|
990
|
+
flushIntervalDelay: options.flushIntervalDelay ?? 3e4
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
this.worker = new Worker(URL.createObjectURL(new Blob(["(function(){let e=(e,n)=>{if(n===`json`)try{return t(JSON.stringify(e))}catch{return e.toString().slice(0,500)}return e.toString().slice(0,500)},t=e=>n(new TextEncoder().encode(e)).toString(16).padStart(8,`0`),n=e=>{let t=5381;for(let n=0;n<e.length;n++)t=(t<<5)+t+(e[n]??0),t>>>=0;return t},r=e=>{let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]?.length??0)>0)return!1;return!0},i=e=>{if(!e)return!0;let t=Object.keys(e);if(t.length==0)return!0;for(let n of t)if((e[n]??0)>0)return!1;return!0},a=async(e,t,n,r)=>{let i=new AbortController,a=setTimeout(()=>{r.debug(`[fetchWithTimeout] Reached timeout, aborting request`),i.abort()},e);try{let e=await fetch(typeof t==`string`||t instanceof Request?t:t.toString(),{...n,signal:i.signal});return clearTimeout(a),e}catch(e){throw r.warn(`[fetchWithTimeout] Fetch error: `,e),clearTimeout(a),e}},o=e=>e?.name===`NotAllowedError`?!0:e instanceof TypeError;var s=class{constructor(e){this.executeRequests=!0,this.sdkKey=e.sdkKey,this.logger=e.logger,this.url=e.urlFactory(`client/telemetry/v1`,e.baseUrl)}async report({context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r}){if(!this.executeRequests)return{success:!1,fatalError:!0};let i={clientSdkKey:this.sdkKey,context:e,discreteEvents:t,aggregatedEvents:n,droppedEvents:r};if(this.isReportEmpty(i))return{success:!0,fatalError:!1};let a=await this.sendReport(i);return a.fatalError&&(this.executeRequests=!1),a}isReportEmpty(e){return r(e.discreteEvents)&&r(e.aggregatedEvents)&&i(e.droppedEvents)}async sendReport(e){try{let t=await a(5e3,this.url,{method:`POST`,body:JSON.stringify(e),keepalive:!0},this.logger),n=this.isStatusFatal(t.status);return n&&this.logger.warn(`[EventReporter] Received a fatal status response (${t.status}) from the telemetry endpoint. No more telemetry data will be sent.`),{success:t.ok,fatalError:n}}catch(e){let t=o(e);return this.logger.warn(`[EventReporter] Error attempting to send telemetry data: ${e}`,{error:e}),{success:!1,fatalError:t}}}isStatusFatal(e){return!!e&&e>=400&&e<500}},c=class{aggregate(e){if(e.events.length==0)return[];let t=new Map;for(let n of e.events){let e=JSON.stringify(n),r=t.get(e)?.count||0;t.set(e,{event:n,count:r+1})}return Array.from(t).map(([,t])=>({startTime:e.startTime,endTime:e.endTime,count:t.count,event:t.event}))}},l=class{constructor(e){this._events=[],this._droppedEventCount=0,this.limit=e??1e3}get events(){return this._events}get reachedLimit(){return this._events.length>=this.limit}get droppedEventCount(){return this._droppedEventCount}push(...e){this.startTime||=new Date;let t=Math.max(0,e.length-this.limit),n=e.slice(t,e.length);if(this._droppedEventCount+=t,this._events.length+n.length>this.limit){let e=this._events.length+n.length-this.limit;this._events.splice(0,e),this._droppedEventCount+=e}return this._events.push(...n)}takeSnapshot(){let e=new Date,t=this.startTime??e,n=this._events.splice(0),r=this._droppedEventCount;return this.startTime=void 0,this._droppedEventCount=0,{startTime:t,endTime:e,events:n,droppedCount:r}}clear(){this._events=[],this.startTime=void 0,this._droppedEventCount=0}},u=class{constructor(e){this.aggregator=new c,this.collectEvents=!0,this.logger=e.logger,this.evaluationEventQueue=new l(e.evaluationQueueLimit??1e3),this.flushIntervalDelay=e.flushIntervalDelay??3e4;let t=e.initialFlushIntervalDelay??5e3;this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),t)}sanitizeValue(t,n){return e(t,n)}async flushAndScheduleNext(){(await this.flush()).fatalError?(this.collectEvents=!1,this.close(),this.logger.warn(`[TelemetryEventCollector] Received a fatal error from telemetry collection. No longer collecting events.`)):this.flushTimeout=setTimeout(()=>this.flushAndScheduleNext(),this.flushIntervalDelay)}async flush(){if(!this.reporter)return{success:!1,fatalError:!1};let e=this.evaluationEventQueue.takeSnapshot();return await this.reporter?.report({context:this._context,discreteEvents:{},aggregatedEvents:{evaluatedConfig:this.aggregator.aggregate(e)},droppedEvents:{evaluatedConfig:e.droppedCount}})}cancelScheduledFlush(){clearTimeout(this.flushTimeout)}async close(){this.collectEvents=!1,clearTimeout(this.flushTimeout),await this.flush(),this.evaluationEventQueue.clear()}dispose(){this.close()}},d=class extends u{constructor(e){super(e),this.reporter=new s(e)}async updateContext(e){this.cancelScheduledFlush(),await this.flush(),this._context=e,await this.flushAndScheduleNext()}evaluatedConfig(e){this.collectEvents&&this.evaluationEventQueue.push(e)}async forceFlush(){this.cancelScheduledFlush(),await this.flushAndScheduleNext()}};let f=(e,t)=>new URL(e,t?.toString()),p,m=(e,t,...n)=>({type:`Log`,payload:{level:e,message:t,args:n}}),h=e=>{try{self.postMessage(e)}catch{self.postMessage(m(e.payload.level,e.payload.message))}},g={debug:function(e,...t){h(m(`debug`,e,t))},info:function(e,...t){h(m(`info`,e,t))},warn:function(e,...t){h(m(`warn`,e,t))},error:function(e,...t){h(m(`error`,e,t))}};addEventListener(`message`,e=>{let t=e.data;if(t.type!=`Initialize`&&!p){g.info(`[TelemetryWorker] Ignoring ${t.type} because no collector is configured`);return}switch(g.debug(`[TelemetryWorker] Received '${t.type}' event:`,t),t.type){case`Initialize`:p=new d({...t.payload,baseUrl:f(t.payload.baseUrl),urlFactory:f,logger:g});break;case`EvaluatedConfigEvent`:p?.evaluatedConfig(t.payload);break;case`UpdateContext`:p?.updateContext(t.payload.context);break;case`Flush`:p?.forceFlush();break;case`Close`:(p?.close()??Promise.resolve()).then(()=>{self.postMessage({type:`Closed`}),self.close()});break}})})();"], { type: "application/javascript" })));
|
|
994
|
+
this.worker.onmessage = (message) => {
|
|
995
|
+
this.handleWorkerEvent(message.data);
|
|
996
|
+
};
|
|
997
|
+
this.worker.postMessage(initializeMessage);
|
|
998
|
+
this.visibilityHandler = () => {
|
|
999
|
+
if (document.visibilityState === "hidden") this.flush();
|
|
1000
|
+
};
|
|
1001
|
+
try {
|
|
1002
|
+
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
this.logger.warn("[TelemetryEventCollector] Could not configure 'visibilitychange' listener: ", error);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
async updateContext(value) {
|
|
1008
|
+
const updateContextMessage = {
|
|
1009
|
+
type: "UpdateContext",
|
|
1010
|
+
payload: { context: value }
|
|
1011
|
+
};
|
|
1012
|
+
this.worker.postMessage(updateContextMessage);
|
|
1013
|
+
}
|
|
1014
|
+
evaluatedConfig(event) {
|
|
1015
|
+
const evaluatedConfigMessage = {
|
|
1016
|
+
type: "EvaluatedConfigEvent",
|
|
1017
|
+
payload: this.sanitizeEvaluatedConfigEvent(event)
|
|
1018
|
+
};
|
|
1019
|
+
this.worker.postMessage(evaluatedConfigMessage);
|
|
1020
|
+
}
|
|
1021
|
+
sanitizeEvaluatedConfigEvent(event) {
|
|
1022
|
+
return {
|
|
1023
|
+
...event,
|
|
1024
|
+
defaultValue: sanitizeValue(event.defaultValue, event.type),
|
|
1025
|
+
evaluatedValue: sanitizeValue(event.evaluatedValue, event.type)
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
handleWorkerEvent(event) {
|
|
1029
|
+
if (event.type === "Closed") {
|
|
1030
|
+
this.closeResolve?.();
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (!event.payload?.level || !event.payload?.message) return;
|
|
1034
|
+
const { level, message, args } = event.payload;
|
|
1035
|
+
switch (level) {
|
|
1036
|
+
case "debug":
|
|
1037
|
+
this.logger.debug(message, ...args);
|
|
1038
|
+
break;
|
|
1039
|
+
case "info":
|
|
1040
|
+
this.logger.info(message, ...args);
|
|
1041
|
+
break;
|
|
1042
|
+
case "warn":
|
|
1043
|
+
this.logger.warn(message, ...args);
|
|
1044
|
+
break;
|
|
1045
|
+
case "error":
|
|
1046
|
+
this.logger.warn(message, ...args);
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
flush() {
|
|
1051
|
+
this.worker.postMessage({ type: "Flush" });
|
|
1052
|
+
}
|
|
1053
|
+
close() {
|
|
1054
|
+
if (this.closePromise) return this.closePromise;
|
|
1055
|
+
return this.performClose();
|
|
1056
|
+
}
|
|
1057
|
+
performClose() {
|
|
1058
|
+
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
1059
|
+
this.closePromise = new Promise((resolve) => {
|
|
1060
|
+
this.closeResolve = resolve;
|
|
1061
|
+
this.worker.postMessage({ type: "Close" });
|
|
1062
|
+
});
|
|
1063
|
+
return this.closePromise;
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
//#endregion
|
|
1067
|
+
//#region ../js-browser-client/src/BrowserConfigDirectorClient.ts
|
|
1068
|
+
const createBrowserClient = (clientSdkKey, sdkOptions, clientOptions) => {
|
|
1069
|
+
const logger = clientOptions?.logger ?? createDefaultLogger$1();
|
|
1070
|
+
return new DefaultConfigDirectorClient(new WebWorkerTelemetryClient({
|
|
1071
|
+
sdkKey: clientSdkKey,
|
|
1072
|
+
logger,
|
|
1073
|
+
baseUrl: parseUrl(clientOptions?.connection?.url) ?? "https://client-sdk-api.configdirector.com",
|
|
1074
|
+
urlFactory: defaultUrlFactory
|
|
1075
|
+
}), clientSdkKey, sdkOptions, {
|
|
1076
|
+
...clientOptions,
|
|
1077
|
+
logger
|
|
1078
|
+
});
|
|
1079
|
+
};
|
|
1080
|
+
//#endregion
|
|
1081
|
+
//#region src/runtime/app/composables/useConfigDirectorClient.ts
|
|
1082
|
+
const useConfigDirectorClient = () => {
|
|
1083
|
+
const { $configDirectorClient } = useNuxtApp();
|
|
1084
|
+
return { client: $configDirectorClient };
|
|
1085
|
+
};
|
|
1086
|
+
//#endregion
|
|
1087
|
+
//#region src/runtime/app/composables/useConfigDirectorContext.ts
|
|
1088
|
+
const DEFAULT_TIMEOUT = 3e3;
|
|
1089
|
+
const getEffectiveTimeout = (option) => {
|
|
1090
|
+
return option && option > 0 ? option : DEFAULT_TIMEOUT;
|
|
1091
|
+
};
|
|
1092
|
+
const useConfigDirectorContext = () => {
|
|
1093
|
+
const state = useState("configdirector-context");
|
|
1094
|
+
const { client } = useConfigDirectorClient();
|
|
1095
|
+
const updateContext = async (context, options) => {
|
|
1096
|
+
const rawContext = toRaw(context);
|
|
1097
|
+
const oldContext = JSON.stringify(state.value);
|
|
1098
|
+
const newContext = JSON.stringify(rawContext);
|
|
1099
|
+
state.value = rawContext;
|
|
1100
|
+
if (oldContext != newContext) {
|
|
1101
|
+
const timeout = getEffectiveTimeout(options?.timeoutMilliseconds);
|
|
1102
|
+
await Promise.race([client.updateContext(rawContext), new Promise((resolve) => {
|
|
1103
|
+
setTimeout(() => resolve(), timeout);
|
|
1104
|
+
})]);
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
return {
|
|
1108
|
+
context: readonly(state),
|
|
1109
|
+
updateContext
|
|
1110
|
+
};
|
|
1111
|
+
};
|
|
1112
|
+
//#endregion
|
|
1113
|
+
//#region src/runtime/logger.ts
|
|
1114
|
+
const createDefaultLogger = (level) => {
|
|
1115
|
+
const logger = consola.withTag("configdirector-nuxt-sdk");
|
|
1116
|
+
if (level !== void 0) logger.level = level;
|
|
1117
|
+
return logger;
|
|
1118
|
+
};
|
|
1119
|
+
//#endregion
|
|
1120
|
+
//#region src/runtime/plugin.client.ts
|
|
1121
|
+
var plugin_client_default = defineNuxtPlugin((nuxtApp) => {
|
|
1122
|
+
const runtimeConfig = useRuntimeConfig();
|
|
1123
|
+
const logger = createDefaultLogger(runtimeConfig.public?.configdirector?.logLevel);
|
|
1124
|
+
if (!runtimeConfig.public?.configdirector?.clientSdkKey) {
|
|
1125
|
+
const message = "The ConfigDirector clientSdkKey must be configured for the plugin to initialize. You can provide it in nuxt.config.ts under 'runtimeConfig.public.configdirector.clientSdkKey' or as a runtime environment variable named NUXT_PUBLIC_CONFIGDIRECTOR_CLIENT_SDK_KEY";
|
|
1126
|
+
logger.error(message);
|
|
1127
|
+
throw new ConfigDirectorInitializationError(message);
|
|
1128
|
+
}
|
|
1129
|
+
logger.debug("Installed ConfigDirector Nuxt plugin");
|
|
1130
|
+
const { clientSdkKey, appName, appVersion, baseUrl } = runtimeConfig.public.configdirector;
|
|
1131
|
+
const client = createBrowserClient(clientSdkKey, {
|
|
1132
|
+
sdkName: "nuxt-sdk",
|
|
1133
|
+
sdkVersion: "0.1.0"
|
|
1134
|
+
}, {
|
|
1135
|
+
metadata: {
|
|
1136
|
+
appName,
|
|
1137
|
+
appVersion
|
|
1138
|
+
},
|
|
1139
|
+
connection: {
|
|
1140
|
+
timeout: 2e3,
|
|
1141
|
+
...baseUrl ? { url: baseUrl } : {}
|
|
1142
|
+
},
|
|
1143
|
+
logger
|
|
1144
|
+
});
|
|
1145
|
+
const readyStatus = shallowRef(client.isReady ? "ready" : "loading");
|
|
1146
|
+
client.on("clientReady", () => {
|
|
1147
|
+
readyStatus.value = "ready";
|
|
1148
|
+
});
|
|
1149
|
+
nuxtApp.hooks.hook("app:created", async () => {
|
|
1150
|
+
logger.debug("Initializing browser client");
|
|
1151
|
+
const { context } = useConfigDirectorContext();
|
|
1152
|
+
await client.initialize(toRaw(context.value));
|
|
1153
|
+
if (!client.isReady) readyStatus.value = "default";
|
|
1154
|
+
logger.debug(`Browser client initialization awaited, ready status: ${client.isReady}`);
|
|
1155
|
+
});
|
|
1156
|
+
return { provide: {
|
|
1157
|
+
configDirectorClient: client,
|
|
1158
|
+
configDirectorClientReadyStatus: readonly(readyStatus)
|
|
1159
|
+
} };
|
|
1160
|
+
});
|
|
1161
|
+
//#endregion
|
|
1162
|
+
export { plugin_client_default as default };
|