@corbado/observe 1.8.0 → 1.9.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/dist/analysis/client.mjs +82 -56
- package/dist/analysis/config-manager.mjs +30 -5
- package/dist/analysis/device-info-manager.mjs +16 -1
- package/dist/analysis/device.mjs +88 -10
- package/dist/analysis/handoff.mjs +7 -0
- package/dist/analysis/low-events/grouped-input-tracker.mjs +148 -33
- package/dist/analysis/low-events/input-tracker.mjs +138 -59
- package/dist/analysis/low-events/shared-window-observer.mjs +148 -77
- package/dist/analysis/low-events/viewport-batcher.mjs +3 -3
- package/dist/analysis/queue.mjs +75 -8
- package/dist/analysis/{low-events/input-paste-observer.mjs → readers/input-paste.mjs} +2 -2
- package/dist/analysis/readers/input.mjs +61 -0
- package/dist/analysis/readers/loader-global.mjs +15 -0
- package/dist/analysis/readers/network.mjs +14 -0
- package/dist/analysis/readers/page.mjs +30 -0
- package/dist/analysis/readers/storage.mjs +28 -0
- package/dist/analysis/{utils → readers}/web-lock.mjs +6 -2
- package/dist/analysis/readers/window-events.mjs +48 -0
- package/dist/analysis/storage/index.mjs +61 -9
- package/dist/analysis/store/event-store.mjs +34 -16
- package/dist/analysis/transports/fetch.mjs +45 -3
- package/dist/analysis/utils/email-link-operation-full.mjs +22 -2
- package/dist/analysis/utils/email-otp-operation-full.mjs +0 -1
- package/dist/analysis/utils/form-operation-full.mjs +0 -1
- package/dist/analysis/utils/operation-full-provide-identifier-with-cui.mjs +6 -6
- package/dist/analysis/utils/passkey-enrollment-operation-full.mjs +9 -6
- package/dist/analysis/utils/passkey-login-operation-full.mjs +10 -5
- package/dist/analysis/utils/password-login-operation-full.mjs +8 -4
- package/dist/analysis/utils/password-manager-sampler.mjs +44 -4
- package/dist/analysis/utils/sms-otp-operation-full.mjs +0 -1
- package/dist/analysis/utils/totp-operation-full.mjs +0 -1
- package/dist/analysis/version.mjs +1 -1
- package/dist/cdn/script.global.js +4 -4
- package/dist/index.d.mts +131 -19
- package/dist/index.d.ts +131 -19
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +5 -3
- package/tools/reader-meta-text.d.mts +2 -0
- package/tools/reader-meta-text.mjs +26 -0
- package/tools/strip-reader-meta.d.mts +33 -0
- package/tools/strip-reader-meta.mjs +131 -0
package/dist/analysis/queue.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { v7 as uuidv7 } from "uuid";
|
|
|
3
3
|
import { AuthEventName } from "./types.mjs";
|
|
4
4
|
import { CONFIG_REQUEST_HEADER, DEFAULT_RELIABILITY_CONFIG } from "./config.mjs";
|
|
5
5
|
import { EventStore } from "./store/event-store.mjs";
|
|
6
|
+
import { listenWindow, readDocumentHidden, readPageUrl } from "./readers/page.mjs";
|
|
6
7
|
var PROBE_QUEUE_MAX_ITEMS = 500;
|
|
7
8
|
var PROBE_QUEUE_MAX_BYTES = 512 * 1024;
|
|
8
9
|
var PROBE_BATCH_MAX_ITEMS = 200;
|
|
@@ -96,6 +97,8 @@ var RequestQueue = class {
|
|
|
96
97
|
dirtySinceSyncFlush = false;
|
|
97
98
|
onVisibilityChange = null;
|
|
98
99
|
onPageHide = null;
|
|
100
|
+
removeVisibilityChange = null;
|
|
101
|
+
removePageHide = null;
|
|
99
102
|
batchSize;
|
|
100
103
|
flushIntervalOverride;
|
|
101
104
|
debug;
|
|
@@ -743,7 +746,21 @@ var RequestQueue = class {
|
|
|
743
746
|
this.onVisibilityChange = () => {
|
|
744
747
|
try {
|
|
745
748
|
this.writeLifecycleDebugOutput("cbo_visibility_change_debug");
|
|
746
|
-
if (
|
|
749
|
+
if (readDocumentHidden({
|
|
750
|
+
key: "page:lifecycle.visibility",
|
|
751
|
+
title: "Page visibility",
|
|
752
|
+
accessNote: "document.visibilityState",
|
|
753
|
+
group: "Page lifecycle",
|
|
754
|
+
origin: "observe-external",
|
|
755
|
+
fields: [
|
|
756
|
+
{
|
|
757
|
+
path: "visibilityState",
|
|
758
|
+
label: "Whether the page is hidden",
|
|
759
|
+
exposure: "enum",
|
|
760
|
+
transform: { values: ["hidden", "other"] }
|
|
761
|
+
}
|
|
762
|
+
]
|
|
763
|
+
}) && this.config.tvc) {
|
|
747
764
|
this.flushSync("visibility-change");
|
|
748
765
|
}
|
|
749
766
|
} catch (error) {
|
|
@@ -760,8 +777,44 @@ var RequestQueue = class {
|
|
|
760
777
|
this.logger.error("Unexpected error in queue flushSync", error);
|
|
761
778
|
}
|
|
762
779
|
};
|
|
763
|
-
|
|
764
|
-
|
|
780
|
+
this.removeVisibilityChange = listenWindow(
|
|
781
|
+
{
|
|
782
|
+
key: "page:lifecycle.visibilitychange",
|
|
783
|
+
title: "Page visibility changes",
|
|
784
|
+
accessNote: "visibilitychange event on document",
|
|
785
|
+
group: "Page lifecycle",
|
|
786
|
+
origin: "observe-external",
|
|
787
|
+
fields: [
|
|
788
|
+
{
|
|
789
|
+
path: "event.visibilitychange",
|
|
790
|
+
label: "Page hidden or shown again",
|
|
791
|
+
exposure: "presence",
|
|
792
|
+
transform: { note: "flushes pending events when the page is hidden" }
|
|
793
|
+
}
|
|
794
|
+
]
|
|
795
|
+
},
|
|
796
|
+
"visibilitychange",
|
|
797
|
+
this.onVisibilityChange
|
|
798
|
+
);
|
|
799
|
+
this.removePageHide = listenWindow(
|
|
800
|
+
{
|
|
801
|
+
key: "page:lifecycle.pagehide",
|
|
802
|
+
title: "Leaving the page",
|
|
803
|
+
accessNote: "pagehide event on window",
|
|
804
|
+
group: "Page lifecycle",
|
|
805
|
+
origin: "observe-external",
|
|
806
|
+
fields: [
|
|
807
|
+
{
|
|
808
|
+
path: "event.pagehide",
|
|
809
|
+
label: "Leaving the page",
|
|
810
|
+
exposure: "presence",
|
|
811
|
+
transform: { note: "flushes pending events on unload" }
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
},
|
|
815
|
+
"pagehide",
|
|
816
|
+
this.onPageHide
|
|
817
|
+
);
|
|
765
818
|
}
|
|
766
819
|
writeLifecycleDebugOutput(key) {
|
|
767
820
|
if (!this.debug || !this.storage) {
|
|
@@ -769,7 +822,21 @@ var RequestQueue = class {
|
|
|
769
822
|
}
|
|
770
823
|
const output = {
|
|
771
824
|
last_executed: Date.now(),
|
|
772
|
-
url:
|
|
825
|
+
url: readPageUrl({
|
|
826
|
+
key: "page:location.url",
|
|
827
|
+
title: "Page address (debug output only)",
|
|
828
|
+
accessNote: "location.href, only written to the debug log",
|
|
829
|
+
group: "Page",
|
|
830
|
+
origin: "observe-external",
|
|
831
|
+
fields: [
|
|
832
|
+
{
|
|
833
|
+
path: "url",
|
|
834
|
+
label: "Full page address",
|
|
835
|
+
exposure: "raw",
|
|
836
|
+
transform: { note: "debug mode only; written to own localStorage, never sent" }
|
|
837
|
+
}
|
|
838
|
+
]
|
|
839
|
+
})
|
|
773
840
|
};
|
|
774
841
|
this.storage.setItem(key, output);
|
|
775
842
|
}
|
|
@@ -862,11 +929,11 @@ var RequestQueue = class {
|
|
|
862
929
|
this.clearTimer();
|
|
863
930
|
this.dropProbes(this.probeQueue.slice());
|
|
864
931
|
if (typeof window !== "undefined") {
|
|
865
|
-
if (this.
|
|
866
|
-
|
|
932
|
+
if (this.removeVisibilityChange) {
|
|
933
|
+
this.removeVisibilityChange();
|
|
867
934
|
}
|
|
868
|
-
if (this.
|
|
869
|
-
|
|
935
|
+
if (this.removePageHide) {
|
|
936
|
+
this.removePageHide();
|
|
870
937
|
}
|
|
871
938
|
}
|
|
872
939
|
} catch (error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/readers/input-paste.ts
|
|
2
2
|
var InputPasteObserver = class {
|
|
3
|
-
constructor(inputs, logger, changed, reserveCaptureSeq) {
|
|
3
|
+
constructor(_meta, inputs, logger, changed, reserveCaptureSeq) {
|
|
4
4
|
this.inputs = inputs;
|
|
5
5
|
this.logger = logger;
|
|
6
6
|
this.changed = changed;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/readers/input.ts
|
|
2
|
+
var FIELD_EVENTS = ["focus", "blur", "pointerdown", "pointerup", "click", "input", "keyup"];
|
|
3
|
+
function observeInputField(_meta, field, handlers, guard, order = FIELD_EVENTS) {
|
|
4
|
+
const listeners = [];
|
|
5
|
+
for (const type of order) {
|
|
6
|
+
const listener = reduceFieldEvent(type, handlers, guard);
|
|
7
|
+
if (listener) listeners.push([type, listener]);
|
|
8
|
+
}
|
|
9
|
+
for (const [type, listener] of listeners) field.addEventListener(type, listener);
|
|
10
|
+
return () => {
|
|
11
|
+
for (const [type, listener] of listeners) field.removeEventListener(type, listener);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function reduceFieldEvent(type, handlers, guard) {
|
|
15
|
+
switch (type) {
|
|
16
|
+
case "focus":
|
|
17
|
+
case "blur": {
|
|
18
|
+
const handler = handlers[type];
|
|
19
|
+
return handler && ((event) => guard(type, () => handler(event.relatedTarget)));
|
|
20
|
+
}
|
|
21
|
+
case "pointerdown":
|
|
22
|
+
case "pointerup":
|
|
23
|
+
case "click": {
|
|
24
|
+
const handler = handlers[type];
|
|
25
|
+
return handler && (() => guard(type, () => handler()));
|
|
26
|
+
}
|
|
27
|
+
case "input": {
|
|
28
|
+
const handler = handlers.input;
|
|
29
|
+
return handler && ((event) => guard(
|
|
30
|
+
"input",
|
|
31
|
+
() => handler(
|
|
32
|
+
event.isComposing || event.inputType === "insertCompositionText",
|
|
33
|
+
event
|
|
34
|
+
)
|
|
35
|
+
));
|
|
36
|
+
}
|
|
37
|
+
case "keyup": {
|
|
38
|
+
const handler = handlers.keyupEscape;
|
|
39
|
+
return handler && ((event) => guard("keyup", () => {
|
|
40
|
+
if (event.key === "Escape") handler();
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function readInputContentLength(_meta, field) {
|
|
46
|
+
if (field.type === "checkbox" || field.type === "radio") return field.checked ? 1 : 0;
|
|
47
|
+
return field.value.length;
|
|
48
|
+
}
|
|
49
|
+
function readInputGroupValueLength(_meta, fields) {
|
|
50
|
+
return fields.reduce((total, field) => total + field.value.length, 0);
|
|
51
|
+
}
|
|
52
|
+
function readFocusedField(_meta, fields) {
|
|
53
|
+
const active = document.activeElement;
|
|
54
|
+
return fields.find((field) => field === active);
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
observeInputField,
|
|
58
|
+
readFocusedField,
|
|
59
|
+
readInputContentLength,
|
|
60
|
+
readInputGroupValueLength
|
|
61
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/readers/loader-global.ts
|
|
2
|
+
function drainLoaderQueue(_meta, name, replay) {
|
|
3
|
+
const global = window.Corbado;
|
|
4
|
+
if (global?.[name]) {
|
|
5
|
+
replay(global[name]);
|
|
6
|
+
delete window.Corbado[name];
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function installLoaderGlobal(_meta, api) {
|
|
10
|
+
window.Corbado = api;
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
drainLoaderQueue,
|
|
14
|
+
installLoaderGlobal
|
|
15
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/readers/network.ts
|
|
2
|
+
function sendFetch(_meta, url, init) {
|
|
3
|
+
return fetch(url, init);
|
|
4
|
+
}
|
|
5
|
+
function beaconSender(_meta) {
|
|
6
|
+
if (typeof navigator?.sendBeacon !== "function") {
|
|
7
|
+
return void 0;
|
|
8
|
+
}
|
|
9
|
+
return (url, data) => navigator.sendBeacon(url, data);
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
beaconSender,
|
|
13
|
+
sendFetch
|
|
14
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/readers/page.ts
|
|
2
|
+
import { applySourcePathQuery } from "../utils/source-path.mjs";
|
|
3
|
+
function listenWindow(_meta, type, handler, options) {
|
|
4
|
+
if (options === void 0) {
|
|
5
|
+
window.addEventListener(type, handler);
|
|
6
|
+
} else {
|
|
7
|
+
window.addEventListener(type, handler, options);
|
|
8
|
+
}
|
|
9
|
+
return () => window.removeEventListener(type, handler);
|
|
10
|
+
}
|
|
11
|
+
function readDocumentHidden(_meta) {
|
|
12
|
+
return document.visibilityState === "hidden";
|
|
13
|
+
}
|
|
14
|
+
function readDocumentComplete(_meta) {
|
|
15
|
+
return document.readyState === "complete";
|
|
16
|
+
}
|
|
17
|
+
function readPageUrl(_meta) {
|
|
18
|
+
return window.location.href;
|
|
19
|
+
}
|
|
20
|
+
function readSourcePath(_meta, mode, allow) {
|
|
21
|
+
const search = applySourcePathQuery(window.location.search, mode, allow);
|
|
22
|
+
return `${window.location.pathname}${search}`;
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
listenWindow,
|
|
26
|
+
readDocumentComplete,
|
|
27
|
+
readDocumentHidden,
|
|
28
|
+
readPageUrl,
|
|
29
|
+
readSourcePath
|
|
30
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/readers/storage.ts
|
|
2
|
+
function localStorageArea(_meta) {
|
|
3
|
+
return {
|
|
4
|
+
getItem: (key) => localStorage.getItem(key),
|
|
5
|
+
setItem: (key, value) => localStorage.setItem(key, value),
|
|
6
|
+
removeItem: (key) => localStorage.removeItem(key)
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function sessionStorageArea(_meta) {
|
|
10
|
+
return {
|
|
11
|
+
getItem: (key) => sessionStorage.getItem(key),
|
|
12
|
+
setItem: (key, value) => sessionStorage.setItem(key, value),
|
|
13
|
+
removeItem: (key) => sessionStorage.removeItem(key)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function cookieJar(_meta) {
|
|
17
|
+
return {
|
|
18
|
+
read: () => document.cookie,
|
|
19
|
+
write: (cookie) => {
|
|
20
|
+
document.cookie = cookie;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
cookieJar,
|
|
26
|
+
localStorageArea,
|
|
27
|
+
sessionStorageArea
|
|
28
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
function withWebLock(name, fn, options = {}) {
|
|
1
|
+
// src/readers/web-lock.ts
|
|
2
|
+
function withWebLock(_meta, name, fn, options = {}) {
|
|
3
3
|
const locks = typeof navigator !== "undefined" ? navigator.locks : void 0;
|
|
4
4
|
if (locks?.request) {
|
|
5
5
|
const timeoutMs = options.timeoutMs;
|
|
@@ -47,6 +47,10 @@ function withWebLock(name, fn, options = {}) {
|
|
|
47
47
|
return Promise.reject(error);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
function hasWebLocks(_meta) {
|
|
51
|
+
return typeof navigator !== "undefined" && !!navigator.locks?.request;
|
|
52
|
+
}
|
|
50
53
|
export {
|
|
54
|
+
hasWebLocks,
|
|
51
55
|
withWebLock
|
|
52
56
|
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/readers/window-events.ts
|
|
2
|
+
function observeWindowLowEvents(_meta, handlers, guard) {
|
|
3
|
+
const removers = [];
|
|
4
|
+
const listen = (target, name, handler) => {
|
|
5
|
+
const safe = (event) => guard(name, () => handler(event));
|
|
6
|
+
target.addEventListener(name, safe);
|
|
7
|
+
removers.push(() => target.removeEventListener(name, safe));
|
|
8
|
+
};
|
|
9
|
+
listen(window, "blur", () => handlers.blur());
|
|
10
|
+
listen(window, "focus", () => handlers.focus());
|
|
11
|
+
listen(document, "visibilitychange", () => handlers.visibilityChange(document.visibilityState === "hidden"));
|
|
12
|
+
listen(document, "focusout", (event) => handlers.focusOut(event.target, !!event.relatedTarget));
|
|
13
|
+
listen(document, "focusin", (event) => handlers.focusIn(() => event.composedPath()));
|
|
14
|
+
listen(document, "pointerdown", (event) => handlers.pointerDown(event.target));
|
|
15
|
+
if (window.visualViewport) {
|
|
16
|
+
listen(window.visualViewport, "resize", () => handlers.viewportResize());
|
|
17
|
+
listen(window.visualViewport, "scroll", () => handlers.viewportScroll());
|
|
18
|
+
}
|
|
19
|
+
listen(window, "pagehide", () => handlers.pageHide());
|
|
20
|
+
const navigation = window.navigation;
|
|
21
|
+
if (navigation) {
|
|
22
|
+
listen(navigation, "currententrychange", (event) => {
|
|
23
|
+
const change = event;
|
|
24
|
+
const from = change.from?.index;
|
|
25
|
+
const to = navigation.currentEntry?.index;
|
|
26
|
+
if (change.navigationType === "traverse" && typeof from === "number" && typeof to === "number" && from >= 0 && to >= 0 && to < from) {
|
|
27
|
+
handlers.navigateBack();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return removers;
|
|
32
|
+
}
|
|
33
|
+
function readFocusedChain(_meta) {
|
|
34
|
+
const chain = [];
|
|
35
|
+
for (let active = document.activeElement; active; active = active.shadowRoot?.activeElement ?? null) {
|
|
36
|
+
chain.push(active);
|
|
37
|
+
}
|
|
38
|
+
return chain;
|
|
39
|
+
}
|
|
40
|
+
function readActiveFocus(_meta) {
|
|
41
|
+
const active = document.activeElement;
|
|
42
|
+
return { active, idle: active === document.body || !active };
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
observeWindowLowEvents,
|
|
46
|
+
readActiveFocus,
|
|
47
|
+
readFocusedChain
|
|
48
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/storage/index.ts
|
|
2
|
+
import { cookieJar, localStorageArea, sessionStorageArea } from "../readers/storage.mjs";
|
|
2
3
|
function namespacedKey(key, namespace) {
|
|
3
4
|
return `${key}_${namespace}`;
|
|
4
5
|
}
|
|
@@ -21,6 +22,23 @@ var LocalStorage = class {
|
|
|
21
22
|
constructor(logger) {
|
|
22
23
|
this.logger = logger;
|
|
23
24
|
}
|
|
25
|
+
area = localStorageArea({
|
|
26
|
+
key: "storage:sdk.local-storage",
|
|
27
|
+
title: "Observe data saved in the browser",
|
|
28
|
+
accessNote: "localStorage, Observe's own cbo_ keys",
|
|
29
|
+
group: "Observe SDK state",
|
|
30
|
+
origin: "observe-internal",
|
|
31
|
+
fields: [
|
|
32
|
+
{
|
|
33
|
+
path: "value",
|
|
34
|
+
label: "Observe's own saved data",
|
|
35
|
+
exposure: "none",
|
|
36
|
+
transform: {
|
|
37
|
+
note: "own cbo_* keys only (session, sequence, outbox, config cache, experiments, client env, debug); written and read back by the SDK"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
});
|
|
24
42
|
/**
|
|
25
43
|
* Error situations:
|
|
26
44
|
* - localStorage is not available: return null
|
|
@@ -30,7 +48,7 @@ var LocalStorage = class {
|
|
|
30
48
|
getItem(key) {
|
|
31
49
|
try {
|
|
32
50
|
if (typeof localStorage === "undefined") return null;
|
|
33
|
-
const item =
|
|
51
|
+
const item = this.area.getItem(key);
|
|
34
52
|
if (item) {
|
|
35
53
|
return JSON.parse(item);
|
|
36
54
|
}
|
|
@@ -49,7 +67,7 @@ var LocalStorage = class {
|
|
|
49
67
|
setItem(key, value) {
|
|
50
68
|
try {
|
|
51
69
|
if (typeof localStorage === "undefined") return;
|
|
52
|
-
|
|
70
|
+
this.area.setItem(key, JSON.stringify(value));
|
|
53
71
|
} catch (error) {
|
|
54
72
|
this.logger.error(`LocalStorage.setItem failed for key "${key}"`, error);
|
|
55
73
|
this.logger.reportTelemetry?.("error", `localStorage.setItem failed: ${key}`);
|
|
@@ -63,7 +81,7 @@ var LocalStorage = class {
|
|
|
63
81
|
removeItem(key) {
|
|
64
82
|
try {
|
|
65
83
|
if (typeof localStorage === "undefined") return;
|
|
66
|
-
|
|
84
|
+
this.area.removeItem(key);
|
|
67
85
|
} catch (error) {
|
|
68
86
|
this.logger.error(`LocalStorage.removeItem failed for key "${key}"`, error);
|
|
69
87
|
}
|
|
@@ -74,6 +92,23 @@ var CookieStorage = class {
|
|
|
74
92
|
this.logger = logger;
|
|
75
93
|
this.domain = domain;
|
|
76
94
|
}
|
|
95
|
+
jar = cookieJar({
|
|
96
|
+
key: "storage:sdk.cookie",
|
|
97
|
+
title: "Observe data saved in cookies",
|
|
98
|
+
accessNote: "document.cookie, Observe's own cbo_ cookies",
|
|
99
|
+
group: "Observe SDK state",
|
|
100
|
+
origin: "observe-internal",
|
|
101
|
+
fields: [
|
|
102
|
+
{
|
|
103
|
+
path: "value",
|
|
104
|
+
label: "Observe's own cookies",
|
|
105
|
+
exposure: "none",
|
|
106
|
+
transform: {
|
|
107
|
+
note: "own cbo_* cookies only (cookie storage mode); the cookie string is scanned for the requested key"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
});
|
|
77
112
|
/**
|
|
78
113
|
* Error situations:
|
|
79
114
|
* - document is not available: return null
|
|
@@ -85,7 +120,7 @@ var CookieStorage = class {
|
|
|
85
120
|
try {
|
|
86
121
|
if (typeof document === "undefined") return null;
|
|
87
122
|
const name = key + "=";
|
|
88
|
-
const parts =
|
|
123
|
+
const parts = this.jar.read().split(";");
|
|
89
124
|
for (let i = 0; i < parts.length; i++) {
|
|
90
125
|
const part = parts[i].trim();
|
|
91
126
|
if (part.indexOf(name) === 0) {
|
|
@@ -112,7 +147,7 @@ var CookieStorage = class {
|
|
|
112
147
|
if (this.domain) {
|
|
113
148
|
cookieString += `; domain=${this.domain}`;
|
|
114
149
|
}
|
|
115
|
-
|
|
150
|
+
this.jar.write(cookieString);
|
|
116
151
|
} catch (error) {
|
|
117
152
|
this.logger.error(`CookieStorage.setItem failed for key "${key}"`, error);
|
|
118
153
|
this.logger.reportTelemetry?.("error", `cookie.setItem failed: ${key}`);
|
|
@@ -130,7 +165,7 @@ var CookieStorage = class {
|
|
|
130
165
|
if (this.domain) {
|
|
131
166
|
cookieString += `; domain=${this.domain}`;
|
|
132
167
|
}
|
|
133
|
-
|
|
168
|
+
this.jar.write(cookieString);
|
|
134
169
|
} catch (error) {
|
|
135
170
|
this.logger.error(`CookieStorage.removeItem failed for key "${key}"`, error);
|
|
136
171
|
}
|
|
@@ -140,6 +175,23 @@ var SessionStorage = class {
|
|
|
140
175
|
constructor(logger) {
|
|
141
176
|
this.logger = logger;
|
|
142
177
|
}
|
|
178
|
+
area = sessionStorageArea({
|
|
179
|
+
key: "storage:sdk.session-storage",
|
|
180
|
+
title: "Observe data saved for the current tab",
|
|
181
|
+
accessNote: "sessionStorage, Observe's own cbo_ keys",
|
|
182
|
+
group: "Observe SDK state",
|
|
183
|
+
origin: "observe-internal",
|
|
184
|
+
fields: [
|
|
185
|
+
{
|
|
186
|
+
path: "value",
|
|
187
|
+
label: "Observe's own data for this tab",
|
|
188
|
+
exposure: "none",
|
|
189
|
+
transform: {
|
|
190
|
+
note: "own cbo_* keys only (per-tab session, sequence and tab id); written and read back by the SDK"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
});
|
|
143
195
|
/**
|
|
144
196
|
* Error situations:
|
|
145
197
|
* - sessionStorage is not available: return null
|
|
@@ -149,7 +201,7 @@ var SessionStorage = class {
|
|
|
149
201
|
getItem(key) {
|
|
150
202
|
try {
|
|
151
203
|
if (typeof sessionStorage === "undefined") return null;
|
|
152
|
-
const item =
|
|
204
|
+
const item = this.area.getItem(key);
|
|
153
205
|
if (item) {
|
|
154
206
|
return JSON.parse(item);
|
|
155
207
|
}
|
|
@@ -168,7 +220,7 @@ var SessionStorage = class {
|
|
|
168
220
|
setItem(key, value) {
|
|
169
221
|
try {
|
|
170
222
|
if (typeof sessionStorage === "undefined") return;
|
|
171
|
-
|
|
223
|
+
this.area.setItem(key, JSON.stringify(value));
|
|
172
224
|
} catch (error) {
|
|
173
225
|
this.logger.error(`SessionStorage.setItem failed for key "${key}"`, error);
|
|
174
226
|
this.logger.reportTelemetry?.("error", `sessionStorage.setItem failed: ${key}`);
|
|
@@ -182,7 +234,7 @@ var SessionStorage = class {
|
|
|
182
234
|
removeItem(key) {
|
|
183
235
|
try {
|
|
184
236
|
if (typeof sessionStorage === "undefined") return;
|
|
185
|
-
|
|
237
|
+
this.area.removeItem(key);
|
|
186
238
|
} catch (error) {
|
|
187
239
|
this.logger.error(`SessionStorage.removeItem failed for key "${key}"`, error);
|
|
188
240
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/store/event-store.ts
|
|
2
|
-
import { withWebLock } from "../
|
|
2
|
+
import { withWebLock } from "../readers/web-lock.mjs";
|
|
3
3
|
var OUTBOX_STORAGE_KEY = "cbo_outbox";
|
|
4
4
|
var OUTBOX_LOCK_NAME = "cbo_outbox";
|
|
5
5
|
var DEFAULT_MAX_ENTRIES = 500;
|
|
@@ -38,23 +38,41 @@ var EventStore = class {
|
|
|
38
38
|
return valid;
|
|
39
39
|
}
|
|
40
40
|
mutate(fn) {
|
|
41
|
-
return withWebLock(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
return withWebLock(
|
|
42
|
+
{
|
|
43
|
+
key: "browser-api:sdk.web-locks",
|
|
44
|
+
title: "Coordination of Observe data across open tabs",
|
|
45
|
+
accessNote: "Web Locks API (navigator.locks) on Observe's own lock names",
|
|
46
|
+
group: "Observe SDK state",
|
|
47
|
+
origin: "observe-internal",
|
|
48
|
+
fields: [
|
|
49
|
+
{
|
|
50
|
+
path: "result",
|
|
51
|
+
label: "Coordination between open tabs",
|
|
52
|
+
exposure: "none",
|
|
53
|
+
transform: { note: "own cbo_* lock names; serializes the SDK's storage updates across tabs" }
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
this.lockName,
|
|
58
|
+
() => {
|
|
59
|
+
try {
|
|
60
|
+
let next = fn(this.read());
|
|
61
|
+
if (next.length > this.maxEntries) {
|
|
62
|
+
const evicted = next.length - this.maxEntries;
|
|
63
|
+
next = next.slice(next.length - this.maxEntries);
|
|
64
|
+
this.logger.error(`EventStore evicted ${evicted} oldest entries (cap ${this.maxEntries})`);
|
|
65
|
+
this.logger.reportTelemetry?.("error", "event store evicted entries (cap)");
|
|
66
|
+
}
|
|
67
|
+
next = this.enforceSizeBudget(next);
|
|
68
|
+
this.entries = next;
|
|
69
|
+
this.storage.setItem(this.storageKey, next);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
this.logger.error("Failed to mutate event store", error);
|
|
72
|
+
this.logger.reportTelemetry?.("error", "failed to mutate event store");
|
|
49
73
|
}
|
|
50
|
-
next = this.enforceSizeBudget(next);
|
|
51
|
-
this.entries = next;
|
|
52
|
-
this.storage.setItem(this.storageKey, next);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
this.logger.error("Failed to mutate event store", error);
|
|
55
|
-
this.logger.reportTelemetry?.("error", "failed to mutate event store");
|
|
56
74
|
}
|
|
57
|
-
|
|
75
|
+
);
|
|
58
76
|
}
|
|
59
77
|
/** Evict oldest entries until the serialized outbox fits {@link maxSerializedLength}. */
|
|
60
78
|
enforceSizeBudget(next) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/transports/fetch.ts
|
|
2
2
|
import { parseReliabilityConfig } from "../config.mjs";
|
|
3
|
+
import { beaconSender, sendFetch } from "../readers/network.mjs";
|
|
3
4
|
var MAX_KEEPALIVE_BODY_BYTES = 6e4;
|
|
4
5
|
var FETCH_TIMEOUT_MS = 6e4;
|
|
5
6
|
var FetchTransport = class {
|
|
@@ -37,7 +38,33 @@ var FetchTransport = class {
|
|
|
37
38
|
keepalive: new Blob([body]).size <= MAX_KEEPALIVE_BODY_BYTES,
|
|
38
39
|
...controller ? { signal: controller.signal } : {}
|
|
39
40
|
};
|
|
40
|
-
const response = await
|
|
41
|
+
const response = await sendFetch(
|
|
42
|
+
{
|
|
43
|
+
key: "network:observe.events",
|
|
44
|
+
title: "Sending events to Corbado Observe",
|
|
45
|
+
accessNote: "fetch (keepalive) to the Observe ingestion API",
|
|
46
|
+
group: "Event delivery",
|
|
47
|
+
origin: "observe-internal",
|
|
48
|
+
fields: [
|
|
49
|
+
{ path: "response.status", label: "Response code", exposure: "raw" },
|
|
50
|
+
{ path: "response.header.retry-after", label: "Retry delay requested by the server", exposure: "raw" },
|
|
51
|
+
{
|
|
52
|
+
path: "response.header.x-corbado-observe-accepted",
|
|
53
|
+
label: "Whether Observe accepted the events",
|
|
54
|
+
exposure: "enum",
|
|
55
|
+
transform: { values: ["1", "other"] }
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
path: "response.body",
|
|
59
|
+
label: "Observe settings in the response",
|
|
60
|
+
exposure: "raw",
|
|
61
|
+
transform: { note: "SDK reliability config (HTTP 200 only)" }
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
this.options.url,
|
|
66
|
+
requestOptions
|
|
67
|
+
);
|
|
41
68
|
const result = {
|
|
42
69
|
statusCode: response.status,
|
|
43
70
|
headers: {
|
|
@@ -77,7 +104,22 @@ var FetchTransport = class {
|
|
|
77
104
|
sendBeacon(batch) {
|
|
78
105
|
try {
|
|
79
106
|
this.options.logger.debug(`Sending ${batch.events.length} events via beacon`);
|
|
80
|
-
|
|
107
|
+
const send = beaconSender({
|
|
108
|
+
key: "network:observe.events-beacon",
|
|
109
|
+
title: "Sending events to Corbado Observe when the page closes",
|
|
110
|
+
accessNote: "navigator.sendBeacon to the Observe ingestion API",
|
|
111
|
+
group: "Event delivery",
|
|
112
|
+
origin: "observe-internal",
|
|
113
|
+
fields: [
|
|
114
|
+
{
|
|
115
|
+
path: "result",
|
|
116
|
+
label: "Whether the browser accepted the data for sending",
|
|
117
|
+
exposure: "enum",
|
|
118
|
+
transform: { values: [true, false], note: "whether the browser queued the beacon" }
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
if (!send) {
|
|
81
123
|
return false;
|
|
82
124
|
}
|
|
83
125
|
batch.meta = {
|
|
@@ -90,7 +132,7 @@ var FetchTransport = class {
|
|
|
90
132
|
// can handle both text/plain and application/json.
|
|
91
133
|
type: "text/plain"
|
|
92
134
|
});
|
|
93
|
-
return
|
|
135
|
+
return send(this.options.url, blob);
|
|
94
136
|
} catch (error) {
|
|
95
137
|
this.options.logger.error("Failed to send event via beacon", error);
|
|
96
138
|
return false;
|