@corbado/observe 1.7.1 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/analysis/client.mjs +1851 -0
  2. package/dist/analysis/config-manager.mjs +83 -0
  3. package/dist/analysis/config.mjs +216 -0
  4. package/dist/analysis/device-info-manager.mjs +153 -0
  5. package/dist/analysis/device.mjs +562 -0
  6. package/dist/analysis/index.mjs +118 -0
  7. package/dist/analysis/logger.mjs +50 -0
  8. package/dist/analysis/low-events/capture-sequence.mjs +11 -0
  9. package/dist/analysis/low-events/ceremony-initiator.mjs +26 -0
  10. package/dist/analysis/low-events/form-input-binding.mjs +46 -0
  11. package/dist/analysis/low-events/grouped-input-tracker.mjs +133 -0
  12. package/dist/analysis/low-events/input-change-recorder.mjs +200 -0
  13. package/dist/analysis/low-events/input-paste-observer.mjs +91 -0
  14. package/dist/analysis/low-events/input-tracker.mjs +223 -0
  15. package/dist/analysis/low-events/otp-input-binding.mjs +67 -0
  16. package/dist/analysis/low-events/shared-window-observer.mjs +225 -0
  17. package/dist/analysis/low-events/sink.mjs +0 -0
  18. package/dist/analysis/low-events/viewport-batcher.mjs +42 -0
  19. package/dist/analysis/low-events/window-tracker.mjs +40 -0
  20. package/dist/analysis/queue.mjs +887 -0
  21. package/dist/analysis/readers/device.mjs +73 -0
  22. package/dist/analysis/readers/meta.mjs +18 -0
  23. package/dist/analysis/readers/password-manager-dom.mjs +112 -0
  24. package/dist/analysis/readers/password-manager-extensions.mjs +247 -0
  25. package/dist/analysis/readers/private-mode.mjs +9 -0
  26. package/dist/analysis/storage/index.mjs +197 -0
  27. package/dist/analysis/storage/migration.mjs +56 -0
  28. package/dist/analysis/store/event-store.mjs +114 -0
  29. package/dist/analysis/transport.mjs +0 -0
  30. package/dist/analysis/transports/fetch.mjs +108 -0
  31. package/dist/analysis/types.mjs +25 -0
  32. package/dist/analysis/utils/appconfirmation-operation-full.mjs +19 -0
  33. package/dist/analysis/utils/blocked-user-agents.mjs +37 -0
  34. package/dist/analysis/utils/captcha-operation-full.mjs +17 -0
  35. package/dist/analysis/utils/email-link-continuity.mjs +50 -0
  36. package/dist/analysis/utils/email-link-operation-full.mjs +142 -0
  37. package/dist/analysis/utils/email-otp-operation-full.mjs +39 -0
  38. package/dist/analysis/utils/form-operation-full.mjs +81 -0
  39. package/dist/analysis/utils/key-operation-full.mjs +38 -0
  40. package/dist/analysis/utils/normalize-error.mjs +29 -0
  41. package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +46 -0
  42. package/dist/analysis/utils/operation-full.mjs +235 -0
  43. package/dist/analysis/utils/passkey-deletion-operation-full.mjs +17 -0
  44. package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +32 -0
  45. package/dist/analysis/utils/passkey-login-operation-full.mjs +34 -0
  46. package/dist/analysis/utils/password-enrollment-operation-full.mjs +27 -0
  47. package/dist/analysis/utils/password-login-operation-full.mjs +61 -0
  48. package/dist/analysis/utils/password-manager-sampler.mjs +130 -0
  49. package/dist/analysis/utils/provide-data-operation-full.mjs +28 -0
  50. package/dist/analysis/utils/serialize-raw-error.mjs +205 -0
  51. package/dist/analysis/utils/sms-otp-operation-full.mjs +39 -0
  52. package/dist/analysis/utils/social-operation-login.mjs +17 -0
  53. package/dist/analysis/utils/source-path.mjs +39 -0
  54. package/dist/analysis/utils/sso-operation-login.mjs +37 -0
  55. package/dist/analysis/utils/totp-operation-full.mjs +35 -0
  56. package/dist/analysis/utils/trusted-device-operation-full.mjs +100 -0
  57. package/dist/analysis/utils/web-lock.mjs +52 -0
  58. package/dist/analysis/utils/webauthn-payloads.mjs +101 -0
  59. package/dist/analysis/version.mjs +7 -0
  60. package/dist/cdn/script.global.js +4 -4
  61. package/dist/index.d.mts +164 -11
  62. package/dist/index.d.ts +164 -11
  63. package/dist/index.js +4 -4
  64. package/dist/index.mjs +4 -4
  65. package/package.json +7 -2
@@ -0,0 +1,83 @@
1
+ // src/config-manager.ts
2
+ import { cacheConfig, loadCachedConfig, parseReliabilityConfig } from "./config.mjs";
3
+ var CONFIG_FETCH_TIMEOUT_MS = 10 * 1e3;
4
+ var CONFIG_REFRESH_INTERVAL_MS = 10 * 60 * 1e3;
5
+ var CONFIG_RETRY_DELAYS_MS = [1e3, 3e3];
6
+ var ConfigManager = class {
7
+ constructor(url, storage, logger) {
8
+ this.url = url;
9
+ this.storage = storage;
10
+ this.logger = logger;
11
+ this.initialConfig = loadCachedConfig(storage) ?? void 0;
12
+ }
13
+ initialConfig;
14
+ refreshTimer;
15
+ requestTimer;
16
+ retryTimer;
17
+ controller;
18
+ fetching = false;
19
+ stopped = false;
20
+ onConfig;
21
+ start(onConfig) {
22
+ if (this.stopped || this.onConfig) return;
23
+ this.onConfig = onConfig;
24
+ void this.fetchConfig();
25
+ }
26
+ async fetchConfig(attempt = 0) {
27
+ if (this.stopped || this.fetching) return;
28
+ if (attempt === 0) {
29
+ clearTimeout(this.refreshTimer);
30
+ clearTimeout(this.retryTimer);
31
+ this.refreshTimer = setTimeout(() => void this.fetchConfig(), CONFIG_REFRESH_INTERVAL_MS);
32
+ }
33
+ this.fetching = true;
34
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
35
+ this.controller = controller;
36
+ this.requestTimer = controller ? setTimeout(() => controller.abort(), CONFIG_FETCH_TIMEOUT_MS) : void 0;
37
+ let retryable = true;
38
+ try {
39
+ const response = await fetch(this.url, {
40
+ method: "GET",
41
+ cache: "no-store",
42
+ ...controller ? { signal: controller.signal } : {}
43
+ });
44
+ if (!response.ok) {
45
+ retryable = (response.status === 408 || response.status === 429 || response.status >= 500) && !response.headers.has("Retry-After");
46
+ throw new Error(`SDK config request failed (${response.status})`);
47
+ }
48
+ const raw = await response.json();
49
+ retryable = false;
50
+ const config = parseReliabilityConfig(raw);
51
+ if (!config) throw new Error("Invalid SDK config response");
52
+ if (this.stopped || controller?.signal.aborted) return;
53
+ cacheConfig(this.storage, config);
54
+ this.onConfig?.(config);
55
+ } catch (error) {
56
+ if (!this.stopped) {
57
+ this.logger.error("Failed to fetch SDK config", error);
58
+ const delay = CONFIG_RETRY_DELAYS_MS[attempt];
59
+ if ((retryable && !(error instanceof SyntaxError) || controller?.signal.aborted) && delay !== void 0) {
60
+ this.retryTimer = setTimeout(() => void this.fetchConfig(attempt + 1), delay);
61
+ }
62
+ }
63
+ } finally {
64
+ clearTimeout(this.requestTimer);
65
+ this.requestTimer = void 0;
66
+ this.controller = void 0;
67
+ this.fetching = false;
68
+ }
69
+ }
70
+ destroy() {
71
+ this.stopped = true;
72
+ clearTimeout(this.refreshTimer);
73
+ clearTimeout(this.requestTimer);
74
+ clearTimeout(this.retryTimer);
75
+ this.controller?.abort();
76
+ this.onConfig = void 0;
77
+ }
78
+ };
79
+ export {
80
+ CONFIG_FETCH_TIMEOUT_MS,
81
+ CONFIG_REFRESH_INTERVAL_MS,
82
+ ConfigManager
83
+ };
@@ -0,0 +1,216 @@
1
+ // src/config.ts
2
+ var CONFIG_STORAGE_KEY = "cbo_sdk_config";
3
+ var CONFIG_REQUEST_HEADER = "X-Corbado-Observe-Config";
4
+ var DEFAULT_FLUSH_INTERVAL_MS = 500;
5
+ var DEFAULT_SEQ_LOCK_TIMEOUT_MS = 50;
6
+ var DEFAULT_DEVICE_INFO_COLLECTOR_TIMEOUT_MS = 1e3;
7
+ var CONFIG_BOUNDS = {
8
+ flushIntervalMs: { min: 100, max: 6e4 },
9
+ retryMaxAttempts: { min: 1, max: 10 },
10
+ retryDelayMs: { min: 50, max: 12e4 },
11
+ sessionInactivityMs: { min: 6e4, max: 864e5 },
12
+ // 0 is the sentinel that disables the seq Web Lock, so it must survive clamping.
13
+ seqLockTimeoutMs: { min: 0, max: 250 },
14
+ // Below ~10ms even a working browser API would time out on every load; above 10s the device info
15
+ // snapshot would arrive so late that early events ship without it anyway.
16
+ deviceInfoCollectorTimeoutMs: { min: 10, max: 1e4 },
17
+ // A long list of priority flow types would degrade toward flush-per-enqueue; cap it.
18
+ flushOnFlowTypeFinished: { maxEntries: 20, maxNameLength: 100 },
19
+ // Query keys whose values stay in reported page URLs; the server caps identically.
20
+ sourcePathQueryAllow: { maxEntries: 50, maxKeyLength: 64 }
21
+ };
22
+ var DEFAULT_SESSION_INACTIVITY_MS = 30 * 60 * 1e3;
23
+ var DEFAULT_RELIABILITY_CONFIG = {
24
+ version: "",
25
+ flushIntervalMs: DEFAULT_FLUSH_INTERVAL_MS,
26
+ durableOutbox: false,
27
+ durableLowOutbox: false,
28
+ beaconKeepalive: false,
29
+ sessionContinuity: false,
30
+ sessionInactivityMs: DEFAULT_SESSION_INACTIVITY_MS,
31
+ seqLockTimeoutMs: DEFAULT_SEQ_LOCK_TIMEOUT_MS,
32
+ // Flush-trigger switches are ON by default: they gate existing delivery behavior, so the safe
33
+ // fallback is "keep flushing" (the opposite polarity of the feature flags above).
34
+ tvc: true,
35
+ tph: true,
36
+ td: true,
37
+ tlf: true,
38
+ // Telemetry defaults ON (on out of the box, per product decision); flush-on-telemetry defaults OFF
39
+ // so a telemetry entry alone never triggers a flush — it piggybacks the next event request.
40
+ telemetry: true,
41
+ flushOnTelemetry: false,
42
+ flushOnFlowTypeFinished: [],
43
+ deviceInfoCollectorTimeoutMs: DEFAULT_DEVICE_INFO_COLLECTOR_TIMEOUT_MS,
44
+ // Diagnostic stream, off until switched on server-side for an investigation.
45
+ deviceInfoTiming: false,
46
+ rawErrors: true,
47
+ // Password manager detection defaults ON; a project can switch it off server-side.
48
+ pmd: true,
49
+ retry: {
50
+ maxAttempts: 1,
51
+ baseDelayMs: 0,
52
+ maxDelayMs: 0
53
+ },
54
+ aap: "",
55
+ // Query values in reported page URLs are masked unless their key is allowlisted (none by default)
56
+ // or the project opts out with "all": URLs regularly carry tokens and credentials.
57
+ sourcePathQuery: "allowlist",
58
+ sourcePathQueryAllow: []
59
+ };
60
+ function clamp(value, min, max) {
61
+ return Math.min(Math.max(value, min), max);
62
+ }
63
+ function asNumber(value, fallback) {
64
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
65
+ }
66
+ function asBoolean(value, fallback) {
67
+ return typeof value === "boolean" ? value : fallback;
68
+ }
69
+ function asString(value, fallback) {
70
+ return typeof value === "string" ? value : fallback;
71
+ }
72
+ function asFlowTypeList(value) {
73
+ if (!Array.isArray(value)) {
74
+ return [];
75
+ }
76
+ return value.filter(
77
+ (name) => typeof name === "string" && name.length > 0 && name.length <= CONFIG_BOUNDS.flushOnFlowTypeFinished.maxNameLength
78
+ ).slice(0, CONFIG_BOUNDS.flushOnFlowTypeFinished.maxEntries);
79
+ }
80
+ function asSourcePathQuery(value) {
81
+ return typeof value === "string" && value.trim().toLowerCase() === "all" ? "all" : "allowlist";
82
+ }
83
+ function asQueryKeyList(value) {
84
+ if (!Array.isArray(value)) {
85
+ return [];
86
+ }
87
+ return value.filter((key) => typeof key === "string").map((key) => key.trim()).filter((key) => key.length > 0 && key.length <= CONFIG_BOUNDS.sourcePathQueryAllow.maxKeyLength).slice(0, CONFIG_BOUNDS.sourcePathQueryAllow.maxEntries);
88
+ }
89
+ function parseReliabilityConfig(raw) {
90
+ if (!raw || typeof raw !== "object") {
91
+ return null;
92
+ }
93
+ const value = raw;
94
+ if (typeof value.version !== "string" || value.version === "") {
95
+ return null;
96
+ }
97
+ return normalizeReliabilityConfig(value);
98
+ }
99
+ function normalizeReliabilityConfig(value) {
100
+ const retryRaw = value.retry && typeof value.retry === "object" ? value.retry : {};
101
+ const baseDelayMs = clamp(
102
+ asNumber(retryRaw.baseDelayMs, DEFAULT_RELIABILITY_CONFIG.retry.baseDelayMs),
103
+ CONFIG_BOUNDS.retryDelayMs.min,
104
+ CONFIG_BOUNDS.retryDelayMs.max
105
+ );
106
+ return {
107
+ version: asString(value.version, ""),
108
+ flushIntervalMs: clamp(
109
+ asNumber(value.flushIntervalMs, DEFAULT_RELIABILITY_CONFIG.flushIntervalMs),
110
+ CONFIG_BOUNDS.flushIntervalMs.min,
111
+ CONFIG_BOUNDS.flushIntervalMs.max
112
+ ),
113
+ durableOutbox: value.durableOutbox === true,
114
+ durableLowOutbox: value.durableLowOutbox === true,
115
+ beaconKeepalive: value.beaconKeepalive === true,
116
+ sessionContinuity: value.sessionContinuity === true,
117
+ sessionInactivityMs: clamp(
118
+ asNumber(value.sessionInactivityMs, DEFAULT_RELIABILITY_CONFIG.sessionInactivityMs),
119
+ CONFIG_BOUNDS.sessionInactivityMs.min,
120
+ CONFIG_BOUNDS.sessionInactivityMs.max
121
+ ),
122
+ seqLockTimeoutMs: clamp(
123
+ asNumber(value.seqLockTimeoutMs, DEFAULT_RELIABILITY_CONFIG.seqLockTimeoutMs),
124
+ CONFIG_BOUNDS.seqLockTimeoutMs.min,
125
+ CONFIG_BOUNDS.seqLockTimeoutMs.max
126
+ ),
127
+ tvc: asBoolean(value.tvc, DEFAULT_RELIABILITY_CONFIG.tvc),
128
+ tph: asBoolean(value.tph, DEFAULT_RELIABILITY_CONFIG.tph),
129
+ td: asBoolean(value.td, DEFAULT_RELIABILITY_CONFIG.td),
130
+ tlf: asBoolean(value.tlf, DEFAULT_RELIABILITY_CONFIG.tlf),
131
+ // Master switch defaults ON (like the trigger switches): a partial payload keeps telemetry on.
132
+ telemetry: asBoolean(value.telemetry, DEFAULT_RELIABILITY_CONFIG.telemetry),
133
+ // Feature switch defaults OFF: a partial payload can never silently enable immediate flushing.
134
+ flushOnTelemetry: value.flushOnTelemetry === true,
135
+ flushOnFlowTypeFinished: asFlowTypeList(value.flushOnFlowTypeFinished),
136
+ deviceInfoCollectorTimeoutMs: clamp(
137
+ asNumber(value.deviceInfoCollectorTimeoutMs, DEFAULT_RELIABILITY_CONFIG.deviceInfoCollectorTimeoutMs),
138
+ CONFIG_BOUNDS.deviceInfoCollectorTimeoutMs.min,
139
+ CONFIG_BOUNDS.deviceInfoCollectorTimeoutMs.max
140
+ ),
141
+ // Feature switch defaults OFF: a partial payload can never silently enable the timing stream.
142
+ deviceInfoTiming: value.deviceInfoTiming === true,
143
+ rawErrors: asBoolean(value.rawErrors, DEFAULT_RELIABILITY_CONFIG.rawErrors),
144
+ pmd: asBoolean(value.pmd, DEFAULT_RELIABILITY_CONFIG.pmd),
145
+ aap: asString(value.aap, ""),
146
+ sourcePathQuery: asSourcePathQuery(value.sourcePathQuery),
147
+ sourcePathQueryAllow: asQueryKeyList(value.sourcePathQueryAllow),
148
+ retry: {
149
+ maxAttempts: clamp(
150
+ asNumber(retryRaw.maxAttempts, DEFAULT_RELIABILITY_CONFIG.retry.maxAttempts),
151
+ CONFIG_BOUNDS.retryMaxAttempts.min,
152
+ CONFIG_BOUNDS.retryMaxAttempts.max
153
+ ),
154
+ baseDelayMs,
155
+ maxDelayMs: clamp(
156
+ asNumber(retryRaw.maxDelayMs, DEFAULT_RELIABILITY_CONFIG.retry.maxDelayMs),
157
+ baseDelayMs,
158
+ CONFIG_BOUNDS.retryDelayMs.max
159
+ )
160
+ }
161
+ };
162
+ }
163
+ function resolveReliabilityConfig(serverConfig, overrides = {}) {
164
+ const base = serverConfig ?? DEFAULT_RELIABILITY_CONFIG;
165
+ const fields = Object.fromEntries(Object.entries(overrides).filter(([, value]) => value !== void 0));
166
+ const retryFields = Object.fromEntries(
167
+ Object.entries(overrides.retry ?? {}).filter(([, value]) => value !== void 0)
168
+ );
169
+ const resolved = normalizeReliabilityConfig({ ...base, ...fields, retry: { ...base.retry, ...retryFields } });
170
+ resolved.retry = {
171
+ ...base.retry,
172
+ ...Object.fromEntries(
173
+ Object.keys(retryFields).map((key) => [key, resolved.retry[key]])
174
+ )
175
+ };
176
+ resolved.retry.maxDelayMs = Math.max(resolved.retry.baseDelayMs, resolved.retry.maxDelayMs);
177
+ return resolved;
178
+ }
179
+ function resolveFixedDataConfig(raw) {
180
+ try {
181
+ if (!raw || typeof raw !== "object") {
182
+ return void 0;
183
+ }
184
+ const value = raw;
185
+ const pmd = value.pmd;
186
+ const pmdExplicit = typeof pmd === "boolean";
187
+ return {
188
+ pmd: pmdExplicit ? pmd : false,
189
+ pmdExplicit,
190
+ privateMode: asBoolean(value.privateMode, true)
191
+ };
192
+ } catch {
193
+ return void 0;
194
+ }
195
+ }
196
+ function loadCachedConfig(storage) {
197
+ return parseReliabilityConfig(storage.getItem(CONFIG_STORAGE_KEY));
198
+ }
199
+ function cacheConfig(storage, config) {
200
+ storage.setItem(CONFIG_STORAGE_KEY, config);
201
+ }
202
+ export {
203
+ CONFIG_BOUNDS,
204
+ CONFIG_REQUEST_HEADER,
205
+ CONFIG_STORAGE_KEY,
206
+ DEFAULT_DEVICE_INFO_COLLECTOR_TIMEOUT_MS,
207
+ DEFAULT_FLUSH_INTERVAL_MS,
208
+ DEFAULT_RELIABILITY_CONFIG,
209
+ DEFAULT_SEQ_LOCK_TIMEOUT_MS,
210
+ DEFAULT_SESSION_INACTIVITY_MS,
211
+ cacheConfig,
212
+ loadCachedConfig,
213
+ parseReliabilityConfig,
214
+ resolveFixedDataConfig,
215
+ resolveReliabilityConfig
216
+ };
@@ -0,0 +1,153 @@
1
+ // src/device-info-manager.ts
2
+ function isDocumentHidden() {
3
+ return typeof document !== "undefined" && document.visibilityState === "hidden";
4
+ }
5
+ var DeviceInfoManager = class {
6
+ constructor(logger, source, refreshInterval, attachInterval, diagnostics, onUndelivered) {
7
+ this.logger = logger;
8
+ this.source = source;
9
+ this.refreshInterval = refreshInterval;
10
+ this.attachInterval = attachInterval;
11
+ this.diagnostics = diagnostics;
12
+ this.onUndelivered = onUndelivered;
13
+ }
14
+ snapshot;
15
+ snapshotPhase;
16
+ publishedAt = 0;
17
+ version = 0;
18
+ deliveredVersion = 0;
19
+ refreshedAt = 0;
20
+ lastAttachedAt = null;
21
+ /** True once any event of this page asked for device info: unload reports only matter then. */
22
+ eventsSeen = false;
23
+ refreshPromise;
24
+ timer;
25
+ destroyed = false;
26
+ start() {
27
+ void this.refresh();
28
+ }
29
+ /**
30
+ * Device info to attach to an event being tracked now: the current snapshot when its version was
31
+ * not delivered yet, otherwise at most once per attach interval.
32
+ */
33
+ getForEvent() {
34
+ this.eventsSeen = true;
35
+ if (Date.now() - this.refreshedAt >= this.refreshInterval) {
36
+ void this.refresh();
37
+ }
38
+ if (!this.snapshot) {
39
+ return void 0;
40
+ }
41
+ if (this.version !== this.deliveredVersion) {
42
+ return this.markDelivered("event");
43
+ }
44
+ if (this.lastAttachedAt !== null && Date.now() - this.lastAttachedAt < this.attachInterval) {
45
+ return void 0;
46
+ }
47
+ this.lastAttachedAt = Date.now();
48
+ return this.snapshot;
49
+ }
50
+ /** True when a snapshot version exists that no event has carried yet. */
51
+ hasUndelivered() {
52
+ return this.snapshot !== void 0 && this.version !== this.deliveredVersion;
53
+ }
54
+ /**
55
+ * The undelivered snapshot, marked as delivered, for the queue to attach to an event of this page in
56
+ * the batch it is about to send; undefined when everything was delivered already.
57
+ */
58
+ takeUndelivered() {
59
+ if (!this.hasUndelivered()) {
60
+ return void 0;
61
+ }
62
+ return this.markDelivered("flush");
63
+ }
64
+ /** True when an undelivered snapshot may be sent on its own: this page tracked events. */
65
+ hasStandalone() {
66
+ return this.eventsSeen && this.hasUndelivered();
67
+ }
68
+ /** The undelivered snapshot, marked as delivered, for the request-level device info of a batch. */
69
+ takeStandalone() {
70
+ if (!this.hasStandalone()) {
71
+ return void 0;
72
+ }
73
+ return this.markDelivered("request");
74
+ }
75
+ destroy() {
76
+ this.destroyed = true;
77
+ if (this.timer) {
78
+ clearTimeout(this.timer);
79
+ this.timer = void 0;
80
+ }
81
+ }
82
+ markDelivered(path) {
83
+ this.deliveredVersion = this.version;
84
+ this.lastAttachedAt = Date.now();
85
+ if (this.snapshotPhase === "update") {
86
+ this.report(`device info update delivered via ${path} after ${Math.max(0, Date.now() - this.publishedAt)}ms`);
87
+ }
88
+ return this.snapshot;
89
+ }
90
+ publish(info, phase) {
91
+ if (phase === "fast") {
92
+ this.refreshedAt = Date.now();
93
+ }
94
+ if (this.snapshot && JSON.stringify(this.snapshot) === JSON.stringify(info)) {
95
+ return;
96
+ }
97
+ this.snapshot = info;
98
+ this.snapshotPhase = phase;
99
+ this.publishedAt = Date.now();
100
+ this.version++;
101
+ if (this.eventsSeen && this.onUndelivered) {
102
+ try {
103
+ this.onUndelivered();
104
+ } catch (error) {
105
+ this.logger.error("Failed to announce undelivered device info", error);
106
+ }
107
+ }
108
+ }
109
+ report(message) {
110
+ if (!this.diagnostics) {
111
+ return;
112
+ }
113
+ try {
114
+ if (this.diagnostics.timing()) {
115
+ this.diagnostics.emit("info", message);
116
+ }
117
+ } catch (error) {
118
+ this.logger.error("Failed to report device info delivery", error);
119
+ }
120
+ }
121
+ scheduleRefresh() {
122
+ if (this.timer) {
123
+ clearTimeout(this.timer);
124
+ }
125
+ this.timer = setTimeout(() => void this.refresh(), this.refreshInterval);
126
+ }
127
+ refresh() {
128
+ if (this.refreshPromise) {
129
+ return this.refreshPromise;
130
+ }
131
+ if (this.timer) {
132
+ clearTimeout(this.timer);
133
+ this.timer = void 0;
134
+ }
135
+ if (this.snapshot && isDocumentHidden()) {
136
+ if (!this.destroyed) {
137
+ this.scheduleRefresh();
138
+ }
139
+ return Promise.resolve();
140
+ }
141
+ const refresh = this.snapshot !== void 0;
142
+ this.refreshPromise = Promise.resolve().then(() => this.source.collect((info, phase) => this.publish(info, phase), { refresh })).catch((error) => this.logger.error("Failed to refresh device info", error)).finally(() => {
143
+ this.refreshPromise = void 0;
144
+ if (!this.destroyed) {
145
+ this.scheduleRefresh();
146
+ }
147
+ });
148
+ return this.refreshPromise;
149
+ }
150
+ };
151
+ export {
152
+ DeviceInfoManager
153
+ };