@fullstory/browser 2.0.8 → 2.1.0-beta.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/index.d.ts +0 -17
- package/dist/index.esm.js +35 -28
- package/dist/index.js +35 -28
- package/package.json +13 -13
- package/src/index.test.ts +99 -38
- package/src/index.ts +46 -45
package/dist/index.d.ts
CHANGED
|
@@ -38,23 +38,6 @@ type ReadyCallback = (data: {
|
|
|
38
38
|
sessionUrl: string;
|
|
39
39
|
settings: Readonly<object>;
|
|
40
40
|
}) => void;
|
|
41
|
-
declare global {
|
|
42
|
-
interface Window {
|
|
43
|
-
_fs_app_host?: string;
|
|
44
|
-
_fs_asset_map_id?: string;
|
|
45
|
-
_fs_capture_on_startup?: boolean;
|
|
46
|
-
_fs_cookie_domain?: string;
|
|
47
|
-
_fs_debug?: boolean;
|
|
48
|
-
_fs_dev_mode?: boolean;
|
|
49
|
-
_fs_host?: string;
|
|
50
|
-
_fs_initialized?: boolean;
|
|
51
|
-
_fs_is_outer_script?: boolean;
|
|
52
|
-
_fs_namespace?: string;
|
|
53
|
-
_fs_org?: string;
|
|
54
|
-
_fs_run_in_iframe?: boolean;
|
|
55
|
-
_fs_script?: string;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
41
|
declare const init: (inputOptions: SnippetOptions, readyCallback?: ReadyCallback) => void;
|
|
59
42
|
declare const isInitialized: () => boolean;
|
|
60
43
|
declare const FullStory: FSApi;
|
package/dist/index.esm.js
CHANGED
|
@@ -14,7 +14,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
14
14
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
15
|
PERFORMANCE OF THIS SOFTWARE.
|
|
16
16
|
***************************************************************************** */
|
|
17
|
-
/* global Reflect, Promise */
|
|
17
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
var __assign = function() {
|
|
@@ -26,17 +26,21 @@ var __assign = function() {
|
|
|
26
26
|
return t;
|
|
27
27
|
};
|
|
28
28
|
return __assign.apply(this, arguments);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
32
|
+
var e = new Error(message);
|
|
33
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
29
34
|
};
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
};
|
|
36
|
+
// Module-level state set during `_init` (replaces former window `_fs_*` globals).
|
|
37
|
+
var _namespace;
|
|
38
|
+
var _initialized = false;
|
|
39
|
+
var _isDevMode = false;
|
|
40
|
+
var getFullStory = function (ns) { return (window[ns]); };
|
|
37
41
|
var ensureSnippetLoaded = function () {
|
|
38
|
-
var fs = getFullStory();
|
|
39
|
-
if (!fs) {
|
|
42
|
+
var fs = _namespace ? getFullStory(_namespace) : undefined;
|
|
43
|
+
if (!_namespace || !fs) {
|
|
40
44
|
throw Error('FullStory is not loaded, please ensure the init function is invoked before calling FullStory API functions');
|
|
41
45
|
}
|
|
42
46
|
return fs;
|
|
@@ -44,30 +48,36 @@ var ensureSnippetLoaded = function () {
|
|
|
44
48
|
var _init = function (inputOptions, readyCallback) {
|
|
45
49
|
// Make a copy so we can modify `options` if desired.
|
|
46
50
|
var options = __assign({}, inputOptions);
|
|
47
|
-
|
|
51
|
+
var ns = options.namespace || 'FS';
|
|
52
|
+
_namespace = ns;
|
|
53
|
+
if (getFullStory(ns)) {
|
|
48
54
|
console.warn('The FullStory snippet has already been defined elsewhere (likely in the <head> element)');
|
|
49
55
|
return;
|
|
50
56
|
}
|
|
57
|
+
var fsInitEnv = {};
|
|
51
58
|
// see README for details on the recordCrossDomainIFrames option
|
|
52
59
|
if (options.recordCrossDomainIFrames) {
|
|
53
|
-
|
|
60
|
+
fsInitEnv.runInIframe = true;
|
|
54
61
|
}
|
|
55
62
|
if (options.appHost) {
|
|
56
|
-
|
|
63
|
+
fsInitEnv.appHost = options.appHost;
|
|
57
64
|
}
|
|
58
65
|
if (options.assetMapId) {
|
|
59
|
-
|
|
66
|
+
fsInitEnv.assetMapId = options.assetMapId;
|
|
60
67
|
}
|
|
61
68
|
if (options.startCaptureManually) {
|
|
62
|
-
|
|
69
|
+
fsInitEnv.captureOnStartup = false;
|
|
63
70
|
}
|
|
64
71
|
// record the contents of this iFrame when embedded in a parent site
|
|
65
72
|
if (options.recordOnlyThisIFrame) {
|
|
66
|
-
|
|
73
|
+
fsInitEnv.isOuterScript = true;
|
|
67
74
|
}
|
|
68
75
|
// Set cookie domain if it was specified.
|
|
69
76
|
if (options.cookieDomain) {
|
|
70
|
-
|
|
77
|
+
fsInitEnv.cookieDomain = options.cookieDomain;
|
|
78
|
+
}
|
|
79
|
+
if (options.sessionUid) {
|
|
80
|
+
fsInitEnv.sessionUid = options.sessionUid;
|
|
71
81
|
}
|
|
72
82
|
if (options.debug === true) {
|
|
73
83
|
if (!options.script) {
|
|
@@ -78,14 +88,12 @@ var _init = function (inputOptions, readyCallback) {
|
|
|
78
88
|
}
|
|
79
89
|
}
|
|
80
90
|
initFS(options);
|
|
81
|
-
var fs = getFullStory();
|
|
91
|
+
var fs = getFullStory(_namespace);
|
|
82
92
|
if (!fs) {
|
|
83
93
|
console.warn('Failed to initialize FS snippet');
|
|
84
94
|
return;
|
|
85
95
|
}
|
|
86
|
-
|
|
87
|
-
fs('init', { env: { sessionUid: options.sessionUid } });
|
|
88
|
-
}
|
|
96
|
+
fs('init', { env: fsInitEnv });
|
|
89
97
|
if (readyCallback) {
|
|
90
98
|
fs('observe', { type: 'start', callback: readyCallback });
|
|
91
99
|
}
|
|
@@ -98,22 +106,21 @@ var _init = function (inputOptions, readyCallback) {
|
|
|
98
106
|
}
|
|
99
107
|
});
|
|
100
108
|
fs('shutdown');
|
|
101
|
-
|
|
109
|
+
_isDevMode = true;
|
|
102
110
|
console.warn(message);
|
|
103
111
|
}
|
|
104
112
|
};
|
|
105
113
|
var initOnce = function (message) { return function (inputOptions, readyCallback) {
|
|
106
|
-
if (
|
|
114
|
+
if (_initialized) {
|
|
107
115
|
if (message)
|
|
108
116
|
console.warn(message);
|
|
109
117
|
return;
|
|
110
118
|
}
|
|
111
119
|
_init(inputOptions, readyCallback);
|
|
112
|
-
|
|
120
|
+
_initialized = true;
|
|
113
121
|
}; };
|
|
114
122
|
var init = initOnce('FullStory init has already been called once, additional invocations are ignored');
|
|
115
|
-
|
|
116
|
-
var isInitialized = function () { return !!window._fs_initialized; };
|
|
123
|
+
var isInitialized = function () { return _initialized; };
|
|
117
124
|
var hasFullStoryWithFunction = function () {
|
|
118
125
|
var testNames = [];
|
|
119
126
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
@@ -127,12 +134,12 @@ var guard = function (name) { return function () {
|
|
|
127
134
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
128
135
|
args[_i] = arguments[_i];
|
|
129
136
|
}
|
|
130
|
-
if (
|
|
137
|
+
if (_isDevMode) {
|
|
131
138
|
var message = "FullStory is in dev mode and is not capturing: ".concat(name, " method not executed");
|
|
132
139
|
console.warn(message);
|
|
133
140
|
return message;
|
|
134
141
|
}
|
|
135
|
-
var fs = getFullStory();
|
|
142
|
+
var fs = _namespace ? getFullStory(_namespace) : undefined;
|
|
136
143
|
if (hasFullStoryWithFunction(name) && fs) {
|
|
137
144
|
return fs[name].apply(fs, args);
|
|
138
145
|
}
|
|
@@ -142,7 +149,7 @@ var guard = function (name) { return function () {
|
|
|
142
149
|
var buildFullStoryShim = function () {
|
|
143
150
|
var FS = function (operation, options, source) {
|
|
144
151
|
var fs = ensureSnippetLoaded();
|
|
145
|
-
if (
|
|
152
|
+
if (_isDevMode) {
|
|
146
153
|
var message = "FullStory is in dev mode and is not capturing: ".concat(operation, " not executed");
|
|
147
154
|
console.warn(message);
|
|
148
155
|
return undefined;
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
16
16
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
17
17
|
PERFORMANCE OF THIS SOFTWARE.
|
|
18
18
|
***************************************************************************** */
|
|
19
|
-
/* global Reflect, Promise */
|
|
19
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
var __assign = function() {
|
|
@@ -28,17 +28,21 @@ var __assign = function() {
|
|
|
28
28
|
return t;
|
|
29
29
|
};
|
|
30
30
|
return __assign.apply(this, arguments);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
34
|
+
var e = new Error(message);
|
|
35
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
31
36
|
};
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
38
|
+
// Module-level state set during `_init` (replaces former window `_fs_*` globals).
|
|
39
|
+
var _namespace;
|
|
40
|
+
var _initialized = false;
|
|
41
|
+
var _isDevMode = false;
|
|
42
|
+
var getFullStory = function (ns) { return (window[ns]); };
|
|
39
43
|
var ensureSnippetLoaded = function () {
|
|
40
|
-
var fs = getFullStory();
|
|
41
|
-
if (!fs) {
|
|
44
|
+
var fs = _namespace ? getFullStory(_namespace) : undefined;
|
|
45
|
+
if (!_namespace || !fs) {
|
|
42
46
|
throw Error('FullStory is not loaded, please ensure the init function is invoked before calling FullStory API functions');
|
|
43
47
|
}
|
|
44
48
|
return fs;
|
|
@@ -46,30 +50,36 @@ var ensureSnippetLoaded = function () {
|
|
|
46
50
|
var _init = function (inputOptions, readyCallback) {
|
|
47
51
|
// Make a copy so we can modify `options` if desired.
|
|
48
52
|
var options = __assign({}, inputOptions);
|
|
49
|
-
|
|
53
|
+
var ns = options.namespace || 'FS';
|
|
54
|
+
_namespace = ns;
|
|
55
|
+
if (getFullStory(ns)) {
|
|
50
56
|
console.warn('The FullStory snippet has already been defined elsewhere (likely in the <head> element)');
|
|
51
57
|
return;
|
|
52
58
|
}
|
|
59
|
+
var fsInitEnv = {};
|
|
53
60
|
// see README for details on the recordCrossDomainIFrames option
|
|
54
61
|
if (options.recordCrossDomainIFrames) {
|
|
55
|
-
|
|
62
|
+
fsInitEnv.runInIframe = true;
|
|
56
63
|
}
|
|
57
64
|
if (options.appHost) {
|
|
58
|
-
|
|
65
|
+
fsInitEnv.appHost = options.appHost;
|
|
59
66
|
}
|
|
60
67
|
if (options.assetMapId) {
|
|
61
|
-
|
|
68
|
+
fsInitEnv.assetMapId = options.assetMapId;
|
|
62
69
|
}
|
|
63
70
|
if (options.startCaptureManually) {
|
|
64
|
-
|
|
71
|
+
fsInitEnv.captureOnStartup = false;
|
|
65
72
|
}
|
|
66
73
|
// record the contents of this iFrame when embedded in a parent site
|
|
67
74
|
if (options.recordOnlyThisIFrame) {
|
|
68
|
-
|
|
75
|
+
fsInitEnv.isOuterScript = true;
|
|
69
76
|
}
|
|
70
77
|
// Set cookie domain if it was specified.
|
|
71
78
|
if (options.cookieDomain) {
|
|
72
|
-
|
|
79
|
+
fsInitEnv.cookieDomain = options.cookieDomain;
|
|
80
|
+
}
|
|
81
|
+
if (options.sessionUid) {
|
|
82
|
+
fsInitEnv.sessionUid = options.sessionUid;
|
|
73
83
|
}
|
|
74
84
|
if (options.debug === true) {
|
|
75
85
|
if (!options.script) {
|
|
@@ -80,14 +90,12 @@ var _init = function (inputOptions, readyCallback) {
|
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
92
|
snippet.initFS(options);
|
|
83
|
-
var fs = getFullStory();
|
|
93
|
+
var fs = getFullStory(_namespace);
|
|
84
94
|
if (!fs) {
|
|
85
95
|
console.warn('Failed to initialize FS snippet');
|
|
86
96
|
return;
|
|
87
97
|
}
|
|
88
|
-
|
|
89
|
-
fs('init', { env: { sessionUid: options.sessionUid } });
|
|
90
|
-
}
|
|
98
|
+
fs('init', { env: fsInitEnv });
|
|
91
99
|
if (readyCallback) {
|
|
92
100
|
fs('observe', { type: 'start', callback: readyCallback });
|
|
93
101
|
}
|
|
@@ -100,22 +108,21 @@ var _init = function (inputOptions, readyCallback) {
|
|
|
100
108
|
}
|
|
101
109
|
});
|
|
102
110
|
fs('shutdown');
|
|
103
|
-
|
|
111
|
+
_isDevMode = true;
|
|
104
112
|
console.warn(message);
|
|
105
113
|
}
|
|
106
114
|
};
|
|
107
115
|
var initOnce = function (message) { return function (inputOptions, readyCallback) {
|
|
108
|
-
if (
|
|
116
|
+
if (_initialized) {
|
|
109
117
|
if (message)
|
|
110
118
|
console.warn(message);
|
|
111
119
|
return;
|
|
112
120
|
}
|
|
113
121
|
_init(inputOptions, readyCallback);
|
|
114
|
-
|
|
122
|
+
_initialized = true;
|
|
115
123
|
}; };
|
|
116
124
|
var init = initOnce('FullStory init has already been called once, additional invocations are ignored');
|
|
117
|
-
|
|
118
|
-
var isInitialized = function () { return !!window._fs_initialized; };
|
|
125
|
+
var isInitialized = function () { return _initialized; };
|
|
119
126
|
var hasFullStoryWithFunction = function () {
|
|
120
127
|
var testNames = [];
|
|
121
128
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
@@ -129,12 +136,12 @@ var guard = function (name) { return function () {
|
|
|
129
136
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
130
137
|
args[_i] = arguments[_i];
|
|
131
138
|
}
|
|
132
|
-
if (
|
|
139
|
+
if (_isDevMode) {
|
|
133
140
|
var message = "FullStory is in dev mode and is not capturing: ".concat(name, " method not executed");
|
|
134
141
|
console.warn(message);
|
|
135
142
|
return message;
|
|
136
143
|
}
|
|
137
|
-
var fs = getFullStory();
|
|
144
|
+
var fs = _namespace ? getFullStory(_namespace) : undefined;
|
|
138
145
|
if (hasFullStoryWithFunction(name) && fs) {
|
|
139
146
|
return fs[name].apply(fs, args);
|
|
140
147
|
}
|
|
@@ -144,7 +151,7 @@ var guard = function (name) { return function () {
|
|
|
144
151
|
var buildFullStoryShim = function () {
|
|
145
152
|
var FS = function (operation, options, source) {
|
|
146
153
|
var fs = ensureSnippetLoaded();
|
|
147
|
-
if (
|
|
154
|
+
if (_isDevMode) {
|
|
148
155
|
var message = "FullStory is in dev mode and is not capturing: ".concat(operation, " not executed");
|
|
149
156
|
console.warn(message);
|
|
150
157
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fullstory/browser",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.1.0-beta.0",
|
|
4
4
|
"description": "The official Fullstory browser SDK",
|
|
5
5
|
"repository": "git://github.com/fullstorydev/fullstory-browser-sdk.git",
|
|
6
6
|
"homepage": "https://github.com/fullstorydev/fullstory-browser-sdk",
|
|
@@ -25,30 +25,30 @@
|
|
|
25
25
|
"sdk"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@fullstory/snippet": "2.0.
|
|
28
|
+
"@fullstory/snippet": "2.1.0-beta.0"
|
|
29
|
+
},
|
|
30
|
+
"overrides": {
|
|
31
|
+
"brace-expansion": "^5.0.8"
|
|
29
32
|
},
|
|
30
33
|
"devDependencies": {
|
|
31
34
|
"@babel/core": "^7.8.7",
|
|
32
|
-
"@babel/eslint-parser": "^7.16.5",
|
|
33
35
|
"@babel/preset-env": "^7.10.4",
|
|
34
36
|
"@babel/register": "^7.8.6",
|
|
37
|
+
"@eslint/js": "^10.0.1",
|
|
35
38
|
"@rollup/plugin-typescript": "^11.1.0",
|
|
36
39
|
"@types/jest": "^29.5.0",
|
|
37
40
|
"@types/node": "^18.15.11",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"eslint-plugin-import": "^2.20.1",
|
|
43
|
-
"eslint-plugin-jsx-a11y": "^6.5.1",
|
|
44
|
-
"eslint-plugin-react": "^7.28.0",
|
|
45
|
-
"jest": "^29.7.0",
|
|
41
|
+
"eslint": "^10.8.0",
|
|
42
|
+
"eslint-plugin-import-x": "^4.17.1",
|
|
43
|
+
"globals": "^17.8.0",
|
|
44
|
+
"jest": "^30.4.2",
|
|
46
45
|
"jest-environment-jsdom": "^30.3.0",
|
|
47
|
-
"rimraf": "^
|
|
46
|
+
"rimraf": "^6.1.3",
|
|
48
47
|
"rollup": "^3.21.0",
|
|
49
|
-
"ts-jest": "^29.
|
|
48
|
+
"ts-jest": "^29.4.12",
|
|
50
49
|
"tslib": "^2.5.0",
|
|
51
50
|
"typescript": "^4.2.4",
|
|
51
|
+
"typescript-eslint": "^8.65.0",
|
|
52
52
|
"webpack": "^5.76.1"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/src/index.test.ts
CHANGED
|
@@ -1,27 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
FullStory as FS,
|
|
3
|
-
init,
|
|
4
|
-
isInitialized,
|
|
5
|
-
SnippetOptions,
|
|
6
|
-
} from '.';
|
|
1
|
+
import type { SnippetOptions } from '.';
|
|
7
2
|
|
|
8
3
|
const testOrg = '123';
|
|
9
4
|
|
|
5
|
+
type FsQueue = unknown[][];
|
|
6
|
+
type FsWithQueue = { q?: FsQueue };
|
|
7
|
+
|
|
8
|
+
const getFsQueue = (ns = 'FS'): FsQueue | undefined => (
|
|
9
|
+
(window[ns] as unknown as FsWithQueue | undefined)?.q
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
/** Browser-sdk init call (after snippet's own init); last matching entry wins. */
|
|
13
|
+
const findBrowserInitEnv = (ns = 'FS'): Record<string, unknown> | undefined => {
|
|
14
|
+
const initCalls = getFsQueue(ns)?.filter((call) => call[0] === 'init') ?? [];
|
|
15
|
+
// Snippet 2.1 pushes an init with orgId/host/script; browser SDK pushes a follow-up.
|
|
16
|
+
const browserInit = initCalls.length > 1 ? initCalls[initCalls.length - 1] : initCalls[0];
|
|
17
|
+
const initOptions = browserInit?.[1] as { env?: Record<string, unknown> } | undefined;
|
|
18
|
+
return initOptions?.env;
|
|
19
|
+
};
|
|
20
|
+
|
|
10
21
|
beforeEach(() => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
// Module-level init state lives in the SDK; re-import for a clean slate per test.
|
|
23
|
+
jest.resetModules();
|
|
24
|
+
Object.keys(window).forEach((key) => {
|
|
25
|
+
const value = window[key as keyof Window];
|
|
26
|
+
if (value && typeof value === 'function' && (value as FsWithQueue).q) {
|
|
27
|
+
delete window[key as keyof Window];
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
delete (window as { FS?: unknown }).FS;
|
|
17
31
|
});
|
|
18
32
|
|
|
33
|
+
const loadSdk = async () => {
|
|
34
|
+
const mod = await import('.');
|
|
35
|
+
return {
|
|
36
|
+
FS: mod.FullStory,
|
|
37
|
+
init: mod.init,
|
|
38
|
+
isInitialized: mod.isInitialized,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
|
|
19
42
|
describe('init', () => {
|
|
20
|
-
it('should throw error if not initialized with an orgId', () => {
|
|
43
|
+
it('should throw error if not initialized with an orgId', async () => {
|
|
44
|
+
const { init } = await loadSdk();
|
|
21
45
|
expect(() => { init({} as SnippetOptions); }).toThrow();
|
|
22
46
|
});
|
|
23
47
|
|
|
24
|
-
it('should throw error if API called before init', () => {
|
|
48
|
+
it('should throw error if API called before init', async () => {
|
|
49
|
+
const { FS, init } = await loadSdk();
|
|
25
50
|
try {
|
|
26
51
|
FS('log', { msg: 'my log' });
|
|
27
52
|
throw new Error('this should have thrown');
|
|
@@ -32,58 +57,65 @@ describe('init', () => {
|
|
|
32
57
|
expect(() => { FS('log', { msg: 'my log' }); }).not.toThrow();
|
|
33
58
|
});
|
|
34
59
|
|
|
35
|
-
it('should
|
|
60
|
+
it('should load FS onto the window under the default namespace', async () => {
|
|
61
|
+
const { init } = await loadSdk();
|
|
36
62
|
init({ orgId: testOrg });
|
|
37
|
-
expect(window.
|
|
63
|
+
expect((window as unknown as { FS?: unknown }).FS).toBeDefined();
|
|
38
64
|
});
|
|
39
65
|
|
|
40
|
-
it('should
|
|
66
|
+
it('should pass runInIframe via FS init env', async () => {
|
|
67
|
+
const { init } = await loadSdk();
|
|
41
68
|
init({
|
|
42
69
|
orgId: testOrg,
|
|
43
70
|
recordCrossDomainIFrames: true,
|
|
44
71
|
});
|
|
45
|
-
expect(
|
|
72
|
+
expect(findBrowserInitEnv()?.runInIframe).toBe(true);
|
|
46
73
|
});
|
|
47
74
|
|
|
48
|
-
it('should
|
|
75
|
+
it('should pass isOuterScript via FS init env', async () => {
|
|
76
|
+
const { init } = await loadSdk();
|
|
49
77
|
init({
|
|
50
78
|
orgId: testOrg,
|
|
51
79
|
recordOnlyThisIFrame: true,
|
|
52
80
|
});
|
|
53
|
-
expect(
|
|
81
|
+
expect(findBrowserInitEnv()?.isOuterScript).toBe(true);
|
|
54
82
|
});
|
|
55
83
|
|
|
56
|
-
it('should
|
|
84
|
+
it('should stop recording when initialized with devMode', async () => {
|
|
85
|
+
const { init } = await loadSdk();
|
|
57
86
|
init({
|
|
58
87
|
orgId: testOrg,
|
|
59
88
|
devMode: true,
|
|
60
89
|
});
|
|
61
90
|
|
|
62
|
-
|
|
91
|
+
const queue = getFsQueue();
|
|
92
|
+
expect(queue?.some((call) => call[0] === 'shutdown')).toBe(true);
|
|
63
93
|
});
|
|
64
94
|
|
|
65
|
-
it('should return whether initialized', () => {
|
|
66
|
-
|
|
67
|
-
expect(
|
|
95
|
+
it('should return whether initialized', async () => {
|
|
96
|
+
const { init, isInitialized } = await loadSdk();
|
|
97
|
+
expect(isInitialized()).toBe(false);
|
|
68
98
|
|
|
69
99
|
init({
|
|
70
100
|
orgId: testOrg,
|
|
71
101
|
});
|
|
72
102
|
|
|
73
|
-
|
|
74
|
-
expect(isInit).toBe(true);
|
|
103
|
+
expect(isInitialized()).toBe(true);
|
|
75
104
|
});
|
|
76
105
|
|
|
77
|
-
it('should load fs-debug.js when debug is set', () => {
|
|
106
|
+
it('should load fs-debug.js when debug is set', async () => {
|
|
107
|
+
const { init } = await loadSdk();
|
|
78
108
|
init({
|
|
79
109
|
orgId: testOrg,
|
|
80
110
|
debug: true,
|
|
81
111
|
});
|
|
82
112
|
|
|
83
|
-
|
|
113
|
+
const script = document.querySelector('script[data-fs-namespace]') as HTMLScriptElement | null;
|
|
114
|
+
expect(script?.src).toMatch(/fs-debug\.js/);
|
|
84
115
|
});
|
|
85
116
|
|
|
86
|
-
it('should pass sessionUid to FS init', () => {
|
|
117
|
+
it('should pass sessionUid to FS init', async () => {
|
|
118
|
+
const { init } = await loadSdk();
|
|
87
119
|
const sessionUid = 'session-123';
|
|
88
120
|
|
|
89
121
|
init({
|
|
@@ -91,11 +123,35 @@ describe('init', () => {
|
|
|
91
123
|
sessionUid,
|
|
92
124
|
});
|
|
93
125
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
126
|
+
expect(findBrowserInitEnv()?.sessionUid).toBe(sessionUid);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should pass cookieDomain, appHost, and assetMapId via FS init env', async () => {
|
|
130
|
+
const { init } = await loadSdk();
|
|
131
|
+
init({
|
|
132
|
+
orgId: testOrg,
|
|
133
|
+
cookieDomain: 'example.com',
|
|
134
|
+
appHost: 'app.fullstory.com',
|
|
135
|
+
assetMapId: 'map-1',
|
|
136
|
+
startCaptureManually: true,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const env = findBrowserInitEnv();
|
|
140
|
+
expect(env?.cookieDomain).toBe('example.com');
|
|
141
|
+
expect(env?.appHost).toBe('app.fullstory.com');
|
|
142
|
+
expect(env?.assetMapId).toBe('map-1');
|
|
143
|
+
expect(env?.captureOnStartup).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should use a custom namespace on window', async () => {
|
|
147
|
+
const { init, FS } = await loadSdk();
|
|
148
|
+
init({
|
|
149
|
+
orgId: testOrg,
|
|
150
|
+
namespace: 'MyFS',
|
|
151
|
+
});
|
|
97
152
|
|
|
98
|
-
expect(
|
|
153
|
+
expect((window as unknown as { MyFS?: unknown }).MyFS).toBeDefined();
|
|
154
|
+
expect(() => { FS('log', { msg: 'ok' }); }).not.toThrow();
|
|
99
155
|
});
|
|
100
156
|
});
|
|
101
157
|
|
|
@@ -111,7 +167,8 @@ describe('devMode', () => {
|
|
|
111
167
|
consoleWarnedMessage = undefined;
|
|
112
168
|
});
|
|
113
169
|
|
|
114
|
-
it('should return a message for functions invoked when in devMode', () => {
|
|
170
|
+
it('should return a message for functions invoked when in devMode', async () => {
|
|
171
|
+
const { init, FS } = await loadSdk();
|
|
115
172
|
expect(consoleWarnedMessage).toBeUndefined();
|
|
116
173
|
init({
|
|
117
174
|
orgId: testOrg,
|
|
@@ -127,7 +184,8 @@ describe('devMode', () => {
|
|
|
127
184
|
});
|
|
128
185
|
|
|
129
186
|
describe('getCurrentSessionURL', () => {
|
|
130
|
-
it('should return null before fs.js is fully bootstrapped', () => {
|
|
187
|
+
it('should return null before fs.js is fully bootstrapped', async () => {
|
|
188
|
+
const { init, FS } = await loadSdk();
|
|
131
189
|
init({ orgId: testOrg });
|
|
132
190
|
// in theory, this is a race condition - assuming that fs.js
|
|
133
191
|
// can't load by the time the following statement is executed
|
|
@@ -137,7 +195,8 @@ describe('getCurrentSessionURL', () => {
|
|
|
137
195
|
});
|
|
138
196
|
|
|
139
197
|
describe('typescript safety', () => {
|
|
140
|
-
it('provides type assistance matching the api', () => {
|
|
198
|
+
it('provides type assistance matching the api', async () => {
|
|
199
|
+
const { init, FS } = await loadSdk();
|
|
141
200
|
// Just a quick non-exhaustive check that types are working as expected.
|
|
142
201
|
// The "@ts-expect-error" declaration will fail if the types do NOT throw
|
|
143
202
|
// an error.
|
|
@@ -190,7 +249,8 @@ describe('typescript safety', () => {
|
|
|
190
249
|
expect(true).toBe(true);
|
|
191
250
|
});
|
|
192
251
|
|
|
193
|
-
it('allows the optional "source" param', () => {
|
|
252
|
+
it('allows the optional "source" param', async () => {
|
|
253
|
+
const { init, FS } = await loadSdk();
|
|
194
254
|
init({ orgId: testOrg });
|
|
195
255
|
|
|
196
256
|
FS('setProperties', {
|
|
@@ -223,6 +283,7 @@ describe('typescript safety', () => {
|
|
|
223
283
|
|
|
224
284
|
// NOTE: don't run this test, it will hang since fs.js isn't really running. It's only for typescript safety checks.
|
|
225
285
|
it.skip('provides type assistance for the async api', async () => {
|
|
286
|
+
const { init, FS } = await loadSdk();
|
|
226
287
|
init({ orgId: testOrg });
|
|
227
288
|
|
|
228
289
|
const disconnector = await FS('observeAsync', { type: 'start', callback: () => console.log('STARTED') });
|
package/src/index.ts
CHANGED
|
@@ -38,34 +38,29 @@ export interface SnippetOptions {
|
|
|
38
38
|
*/
|
|
39
39
|
type ReadyCallback = (data: { sessionUrl: string, settings: Readonly<object> }) => void;
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
_fs_initialized?: boolean;
|
|
51
|
-
_fs_is_outer_script?: boolean;
|
|
52
|
-
_fs_namespace?: string;
|
|
53
|
-
_fs_org?: string;
|
|
54
|
-
_fs_run_in_iframe?: boolean;
|
|
55
|
-
_fs_script?: string;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const getFullStory = (): FSApi | undefined => {
|
|
60
|
-
if (window._fs_namespace) {
|
|
61
|
-
return window[window._fs_namespace];
|
|
62
|
-
}
|
|
63
|
-
return undefined;
|
|
41
|
+
/** Env overrides passed to `FS('init', { env })` after the snippet loads. */
|
|
42
|
+
type FsInitEnv = {
|
|
43
|
+
appHost?: string;
|
|
44
|
+
assetMapId?: string;
|
|
45
|
+
captureOnStartup?: boolean;
|
|
46
|
+
cookieDomain?: string;
|
|
47
|
+
isOuterScript?: boolean;
|
|
48
|
+
runInIframe?: boolean;
|
|
49
|
+
sessionUid?: string;
|
|
64
50
|
};
|
|
65
51
|
|
|
52
|
+
// Module-level state set during `_init` (replaces former window `_fs_*` globals).
|
|
53
|
+
let _namespace: string | undefined;
|
|
54
|
+
let _initialized = false;
|
|
55
|
+
let _isDevMode = false;
|
|
56
|
+
|
|
57
|
+
const getFullStory = (ns: string): FSApi | undefined => (
|
|
58
|
+
(window as unknown as Record<string, FSApi | undefined>)[ns]
|
|
59
|
+
);
|
|
60
|
+
|
|
66
61
|
const ensureSnippetLoaded = (): FSApi => {
|
|
67
|
-
const fs = getFullStory();
|
|
68
|
-
if (!fs) {
|
|
62
|
+
const fs = _namespace ? getFullStory(_namespace) : undefined;
|
|
63
|
+
if (!_namespace || !fs) {
|
|
69
64
|
throw Error(
|
|
70
65
|
'FullStory is not loaded, please ensure the init function is invoked before calling FullStory API functions'
|
|
71
66
|
);
|
|
@@ -77,36 +72,45 @@ const ensureSnippetLoaded = (): FSApi => {
|
|
|
77
72
|
const _init = (inputOptions: SnippetOptions, readyCallback?: ReadyCallback) => {
|
|
78
73
|
// Make a copy so we can modify `options` if desired.
|
|
79
74
|
const options = { ...inputOptions };
|
|
80
|
-
|
|
75
|
+
const ns = options.namespace || 'FS';
|
|
76
|
+
_namespace = ns;
|
|
77
|
+
|
|
78
|
+
if (getFullStory(ns)) {
|
|
81
79
|
console.warn('The FullStory snippet has already been defined elsewhere (likely in the <head> element)');
|
|
82
80
|
return;
|
|
83
81
|
}
|
|
84
82
|
|
|
83
|
+
const fsInitEnv: FsInitEnv = {};
|
|
84
|
+
|
|
85
85
|
// see README for details on the recordCrossDomainIFrames option
|
|
86
86
|
if (options.recordCrossDomainIFrames) {
|
|
87
|
-
|
|
87
|
+
fsInitEnv.runInIframe = true;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
if (options.appHost) {
|
|
91
|
-
|
|
91
|
+
fsInitEnv.appHost = options.appHost;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
if (options.assetMapId) {
|
|
95
|
-
|
|
95
|
+
fsInitEnv.assetMapId = options.assetMapId;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
if (options.startCaptureManually) {
|
|
99
|
-
|
|
99
|
+
fsInitEnv.captureOnStartup = false;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
// record the contents of this iFrame when embedded in a parent site
|
|
103
103
|
if (options.recordOnlyThisIFrame) {
|
|
104
|
-
|
|
104
|
+
fsInitEnv.isOuterScript = true;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
// Set cookie domain if it was specified.
|
|
108
108
|
if (options.cookieDomain) {
|
|
109
|
-
|
|
109
|
+
fsInitEnv.cookieDomain = options.cookieDomain;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (options.sessionUid) {
|
|
113
|
+
fsInitEnv.sessionUid = options.sessionUid;
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
if (options.debug === true) {
|
|
@@ -119,16 +123,14 @@ const _init = (inputOptions: SnippetOptions, readyCallback?: ReadyCallback) => {
|
|
|
119
123
|
|
|
120
124
|
initFS(options);
|
|
121
125
|
|
|
122
|
-
const fs = getFullStory();
|
|
126
|
+
const fs = getFullStory(_namespace);
|
|
123
127
|
|
|
124
128
|
if (!fs) {
|
|
125
129
|
console.warn('Failed to initialize FS snippet');
|
|
126
130
|
return;
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
fs('init', { env: { sessionUid: options.sessionUid } });
|
|
131
|
-
}
|
|
133
|
+
fs('init', { env: fsInitEnv });
|
|
132
134
|
|
|
133
135
|
if (readyCallback) {
|
|
134
136
|
fs('observe', { type: 'start', callback: readyCallback });
|
|
@@ -143,38 +145,37 @@ const _init = (inputOptions: SnippetOptions, readyCallback?: ReadyCallback) => {
|
|
|
143
145
|
}
|
|
144
146
|
});
|
|
145
147
|
fs('shutdown');
|
|
146
|
-
|
|
148
|
+
_isDevMode = true;
|
|
147
149
|
console.warn(message);
|
|
148
150
|
}
|
|
149
151
|
};
|
|
150
152
|
|
|
151
153
|
const initOnce = (message) => (inputOptions: SnippetOptions, readyCallback?: ReadyCallback) => {
|
|
152
|
-
if (
|
|
154
|
+
if (_initialized) {
|
|
153
155
|
if (message) console.warn(message);
|
|
154
156
|
return;
|
|
155
157
|
}
|
|
156
158
|
_init(inputOptions, readyCallback);
|
|
157
|
-
|
|
159
|
+
_initialized = true;
|
|
158
160
|
};
|
|
159
161
|
|
|
160
162
|
const init = initOnce('FullStory init has already been called once, additional invocations are ignored');
|
|
161
163
|
|
|
162
|
-
|
|
163
|
-
const isInitialized = () => !!window._fs_initialized;
|
|
164
|
+
const isInitialized = () => _initialized;
|
|
164
165
|
|
|
165
|
-
const hasFullStoryWithFunction = (...testNames) => {
|
|
166
|
+
const hasFullStoryWithFunction = (...testNames:string[]) => {
|
|
166
167
|
const fs = ensureSnippetLoaded();
|
|
167
168
|
return testNames.every((current) => fs[current]);
|
|
168
169
|
};
|
|
169
170
|
|
|
170
171
|
const guard = (name) => (...args) => {
|
|
171
|
-
if (
|
|
172
|
+
if (_isDevMode) {
|
|
172
173
|
const message = `FullStory is in dev mode and is not capturing: ${name} method not executed`;
|
|
173
174
|
console.warn(message);
|
|
174
175
|
return message;
|
|
175
176
|
}
|
|
176
177
|
|
|
177
|
-
const fs = getFullStory();
|
|
178
|
+
const fs = _namespace ? getFullStory(_namespace) : undefined;
|
|
178
179
|
if (hasFullStoryWithFunction(name) && fs) {
|
|
179
180
|
return fs[name](...args);
|
|
180
181
|
}
|
|
@@ -186,7 +187,7 @@ const buildFullStoryShim = (): FSApi => {
|
|
|
186
187
|
const FS = (operation, options, source) => {
|
|
187
188
|
const fs = ensureSnippetLoaded();
|
|
188
189
|
|
|
189
|
-
if (
|
|
190
|
+
if (_isDevMode) {
|
|
190
191
|
const message = `FullStory is in dev mode and is not capturing: ${operation} not executed`;
|
|
191
192
|
console.warn(message);
|
|
192
193
|
return undefined;
|