@corbado/observe 1.9.0 → 1.9.1
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/analysis/config-manager.mjs +143 -14
- package/dist/analysis/config.mjs +12 -4
- package/dist/analysis/version.mjs +1 -1
- package/dist/cdn/script.global.js +4 -4
- package/dist/index.d.mts +21 -8
- package/dist/index.d.ts +21 -8
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
|
@@ -1,36 +1,139 @@
|
|
|
1
1
|
// src/config-manager.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
cacheConfigRecord,
|
|
4
|
+
CONFIG_STORAGE_KEY,
|
|
5
|
+
loadCachedConfig,
|
|
6
|
+
loadConfigRecord,
|
|
7
|
+
parseReliabilityConfig
|
|
8
|
+
} from "./config.mjs";
|
|
3
9
|
import { sendFetch } from "./readers/network.mjs";
|
|
10
|
+
import { listenWindow, readDocumentHidden } from "./readers/page.mjs";
|
|
4
11
|
var CONFIG_FETCH_TIMEOUT_MS = 10 * 1e3;
|
|
5
|
-
var
|
|
12
|
+
var CONFIG_MIN_ATTEMPT_GAP_MS = 60 * 1e3;
|
|
13
|
+
var CONFIG_DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
14
|
+
var CONFIG_MIN_TTL_MS = 5 * 60 * 1e3;
|
|
15
|
+
var CONFIG_MAX_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
6
16
|
var CONFIG_RETRY_DELAYS_MS = [1e3, 3e3];
|
|
17
|
+
function configTtlMs(headers) {
|
|
18
|
+
const maxAge = /(?:^|[\s,])max-age\s*=\s*"?(\d+)"?/i.exec(headers?.get("Cache-Control") ?? "");
|
|
19
|
+
if (!maxAge) return CONFIG_DEFAULT_TTL_MS;
|
|
20
|
+
const age = Number(headers?.get("Age") ?? 0);
|
|
21
|
+
const ttlMs = (Number(maxAge[1]) - (Number.isFinite(age) && age > 0 ? age : 0)) * 1e3;
|
|
22
|
+
return Math.min(Math.max(ttlMs, CONFIG_MIN_TTL_MS), CONFIG_MAX_TTL_MS);
|
|
23
|
+
}
|
|
7
24
|
var ConfigManager = class {
|
|
8
25
|
constructor(url, storage, logger) {
|
|
9
26
|
this.url = url;
|
|
10
27
|
this.storage = storage;
|
|
11
28
|
this.logger = logger;
|
|
12
29
|
this.initialConfig = loadCachedConfig(storage) ?? void 0;
|
|
30
|
+
this.appliedVersion = this.initialConfig?.version;
|
|
13
31
|
}
|
|
14
32
|
initialConfig;
|
|
15
|
-
refreshTimer;
|
|
16
33
|
requestTimer;
|
|
17
34
|
retryTimer;
|
|
18
35
|
controller;
|
|
36
|
+
removers = [];
|
|
19
37
|
fetching = false;
|
|
20
38
|
stopped = false;
|
|
39
|
+
lastAttemptAt;
|
|
40
|
+
/** Version last handed to (or initially applied by) the tracker. */
|
|
41
|
+
appliedVersion;
|
|
21
42
|
onConfig;
|
|
22
43
|
start(onConfig) {
|
|
23
44
|
if (this.stopped || this.onConfig) return;
|
|
24
45
|
this.onConfig = onConfig;
|
|
25
|
-
|
|
46
|
+
this.listen();
|
|
47
|
+
this.refresh();
|
|
26
48
|
}
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
listen() {
|
|
50
|
+
if (typeof window === "undefined") return;
|
|
51
|
+
const onVisibilityChange = () => {
|
|
52
|
+
const hidden = typeof document === "undefined" || readDocumentHidden({
|
|
53
|
+
key: "page:lifecycle.visibility",
|
|
54
|
+
title: "Page visibility",
|
|
55
|
+
accessNote: "document.visibilityState",
|
|
56
|
+
group: "Page lifecycle",
|
|
57
|
+
origin: "observe-external",
|
|
58
|
+
fields: [
|
|
59
|
+
{
|
|
60
|
+
path: "visibilityState",
|
|
61
|
+
label: "Whether the page is hidden",
|
|
62
|
+
exposure: "enum",
|
|
63
|
+
transform: { values: ["hidden", "other"] }
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
});
|
|
67
|
+
if (!hidden) this.refresh();
|
|
68
|
+
};
|
|
69
|
+
this.removers.push(
|
|
70
|
+
listenWindow(
|
|
71
|
+
{
|
|
72
|
+
key: "page:lifecycle.visibilitychange",
|
|
73
|
+
title: "Page visibility changes",
|
|
74
|
+
accessNote: "visibilitychange event on document",
|
|
75
|
+
group: "Page lifecycle",
|
|
76
|
+
origin: "observe-external",
|
|
77
|
+
fields: [
|
|
78
|
+
{
|
|
79
|
+
path: "event.visibilitychange",
|
|
80
|
+
label: "Page hidden or shown again",
|
|
81
|
+
exposure: "presence",
|
|
82
|
+
transform: { note: "checks whether Observe settings are due for a refresh when the page is shown" }
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"visibilitychange",
|
|
87
|
+
onVisibilityChange
|
|
88
|
+
),
|
|
89
|
+
listenWindow(
|
|
90
|
+
{
|
|
91
|
+
key: "page:lifecycle.pageshow",
|
|
92
|
+
title: "Returning to the page",
|
|
93
|
+
accessNote: "pageshow event on window, its persisted flag",
|
|
94
|
+
group: "Page lifecycle",
|
|
95
|
+
origin: "observe-external",
|
|
96
|
+
fields: [
|
|
97
|
+
{
|
|
98
|
+
path: "event.persisted",
|
|
99
|
+
label: "Page restored from the browser's back/forward cache",
|
|
100
|
+
exposure: "presence",
|
|
101
|
+
transform: { note: "checks whether Observe settings are due for a refresh" }
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
"pageshow",
|
|
106
|
+
(event) => {
|
|
107
|
+
if (event.persisted) this.refresh();
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
/** Fetches when the cached config is stale; adopts a fresh config another tab stored meanwhile. */
|
|
113
|
+
refresh() {
|
|
114
|
+
try {
|
|
115
|
+
if (this.stopped || this.fetching) return;
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
const record = loadConfigRecord(this.storage);
|
|
118
|
+
const { fetchedAt, expiresAt } = record ?? {};
|
|
119
|
+
if (record?.endpoint === this.url && typeof fetchedAt === "number" && typeof expiresAt === "number" && fetchedAt > 0 && expiresAt - fetchedAt <= CONFIG_MAX_TTL_MS && fetchedAt <= now && now < expiresAt) {
|
|
120
|
+
if (record.config.version !== this.appliedVersion) {
|
|
121
|
+
this.appliedVersion = record.config.version;
|
|
122
|
+
this.onConfig?.(record.config);
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const sinceAttempt = this.lastAttemptAt === void 0 ? Infinity : now - this.lastAttemptAt;
|
|
127
|
+
if (sinceAttempt >= 0 && sinceAttempt < CONFIG_MIN_ATTEMPT_GAP_MS) return;
|
|
128
|
+
this.lastAttemptAt = now;
|
|
31
129
|
clearTimeout(this.retryTimer);
|
|
32
|
-
|
|
130
|
+
void this.fetchConfig();
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.logger.error("Unexpected error in SDK config refresh", error);
|
|
33
133
|
}
|
|
134
|
+
}
|
|
135
|
+
async fetchConfig(attempt = 0) {
|
|
136
|
+
if (this.stopped || this.fetching) return;
|
|
34
137
|
this.fetching = true;
|
|
35
138
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
36
139
|
this.controller = controller;
|
|
@@ -51,6 +154,18 @@ var ConfigManager = class {
|
|
|
51
154
|
label: "Whether the server asked to retry later",
|
|
52
155
|
exposure: "presence"
|
|
53
156
|
},
|
|
157
|
+
{
|
|
158
|
+
path: "response.header.cache-control",
|
|
159
|
+
label: "How long the server allows reusing the settings",
|
|
160
|
+
exposure: "computed",
|
|
161
|
+
transform: { note: "max-age, reduced to the settings' expiry time" }
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
path: "response.header.age",
|
|
165
|
+
label: "How long the settings were already stored on the way",
|
|
166
|
+
exposure: "computed",
|
|
167
|
+
transform: { note: "subtracted from max-age for the settings' expiry time" }
|
|
168
|
+
},
|
|
54
169
|
{
|
|
55
170
|
path: "response.body",
|
|
56
171
|
label: "Observe settings",
|
|
@@ -62,7 +177,9 @@ var ConfigManager = class {
|
|
|
62
177
|
this.url,
|
|
63
178
|
{
|
|
64
179
|
method: "GET",
|
|
65
|
-
cache
|
|
180
|
+
// Revalidates the browser's HTTP cache entry (If-None-Match once the server sends an ETag)
|
|
181
|
+
// without a custom header, which would add a CORS preflight.
|
|
182
|
+
cache: "no-cache",
|
|
66
183
|
...controller ? { signal: controller.signal } : {}
|
|
67
184
|
}
|
|
68
185
|
);
|
|
@@ -75,7 +192,15 @@ var ConfigManager = class {
|
|
|
75
192
|
const config = parseReliabilityConfig(raw);
|
|
76
193
|
if (!config) throw new Error("Invalid SDK config response");
|
|
77
194
|
if (this.stopped || controller?.signal.aborted) return;
|
|
78
|
-
|
|
195
|
+
const now = Date.now();
|
|
196
|
+
cacheConfigRecord(this.storage, {
|
|
197
|
+
endpoint: this.url,
|
|
198
|
+
fetchedAt: now,
|
|
199
|
+
expiresAt: now + configTtlMs(response.headers),
|
|
200
|
+
config
|
|
201
|
+
});
|
|
202
|
+
if (loadConfigRecord(this.storage)) this.storage.removeItem(CONFIG_STORAGE_KEY);
|
|
203
|
+
this.appliedVersion = config.version;
|
|
79
204
|
this.onConfig?.(config);
|
|
80
205
|
} catch (error) {
|
|
81
206
|
if (!this.stopped) {
|
|
@@ -94,7 +219,7 @@ var ConfigManager = class {
|
|
|
94
219
|
}
|
|
95
220
|
destroy() {
|
|
96
221
|
this.stopped = true;
|
|
97
|
-
|
|
222
|
+
this.removers.splice(0).forEach((remove) => remove());
|
|
98
223
|
clearTimeout(this.requestTimer);
|
|
99
224
|
clearTimeout(this.retryTimer);
|
|
100
225
|
this.controller?.abort();
|
|
@@ -102,7 +227,11 @@ var ConfigManager = class {
|
|
|
102
227
|
}
|
|
103
228
|
};
|
|
104
229
|
export {
|
|
230
|
+
CONFIG_DEFAULT_TTL_MS,
|
|
105
231
|
CONFIG_FETCH_TIMEOUT_MS,
|
|
106
|
-
|
|
107
|
-
|
|
232
|
+
CONFIG_MAX_TTL_MS,
|
|
233
|
+
CONFIG_MIN_ATTEMPT_GAP_MS,
|
|
234
|
+
CONFIG_MIN_TTL_MS,
|
|
235
|
+
ConfigManager,
|
|
236
|
+
configTtlMs
|
|
108
237
|
};
|
package/dist/analysis/config.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
2
|
var CONFIG_STORAGE_KEY = "cbo_sdk_config";
|
|
3
|
+
var CONFIG_CACHE_STORAGE_KEY = "cbo_sdk_config_cache";
|
|
3
4
|
var CONFIG_REQUEST_HEADER = "X-Corbado-Observe-Config";
|
|
4
5
|
var DEFAULT_FLUSH_INTERVAL_MS = 500;
|
|
5
6
|
var DEFAULT_SEQ_LOCK_TIMEOUT_MS = 50;
|
|
@@ -193,14 +194,20 @@ function resolveFixedDataConfig(raw) {
|
|
|
193
194
|
return void 0;
|
|
194
195
|
}
|
|
195
196
|
}
|
|
197
|
+
function loadConfigRecord(storage) {
|
|
198
|
+
const record = storage.getItem(CONFIG_CACHE_STORAGE_KEY);
|
|
199
|
+
const config = record && typeof record === "object" ? parseReliabilityConfig(record.config) : null;
|
|
200
|
+
return config ? { ...record, config } : null;
|
|
201
|
+
}
|
|
196
202
|
function loadCachedConfig(storage) {
|
|
197
|
-
return parseReliabilityConfig(storage.getItem(CONFIG_STORAGE_KEY));
|
|
203
|
+
return loadConfigRecord(storage)?.config ?? parseReliabilityConfig(storage.getItem(CONFIG_STORAGE_KEY));
|
|
198
204
|
}
|
|
199
|
-
function
|
|
200
|
-
storage.setItem(
|
|
205
|
+
function cacheConfigRecord(storage, record) {
|
|
206
|
+
storage.setItem(CONFIG_CACHE_STORAGE_KEY, record);
|
|
201
207
|
}
|
|
202
208
|
export {
|
|
203
209
|
CONFIG_BOUNDS,
|
|
210
|
+
CONFIG_CACHE_STORAGE_KEY,
|
|
204
211
|
CONFIG_REQUEST_HEADER,
|
|
205
212
|
CONFIG_STORAGE_KEY,
|
|
206
213
|
DEFAULT_DEVICE_INFO_COLLECTOR_TIMEOUT_MS,
|
|
@@ -208,8 +215,9 @@ export {
|
|
|
208
215
|
DEFAULT_RELIABILITY_CONFIG,
|
|
209
216
|
DEFAULT_SEQ_LOCK_TIMEOUT_MS,
|
|
210
217
|
DEFAULT_SESSION_INACTIVITY_MS,
|
|
211
|
-
|
|
218
|
+
cacheConfigRecord,
|
|
212
219
|
loadCachedConfig,
|
|
220
|
+
loadConfigRecord,
|
|
213
221
|
parseReliabilityConfig,
|
|
214
222
|
resolveFixedDataConfig,
|
|
215
223
|
resolveReliabilityConfig
|