noibu-react-native 0.0.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/README.md +155 -0
- package/dist/api/clientConfig.js +416 -0
- package/dist/api/helpCode.js +106 -0
- package/dist/api/inputManager.js +233 -0
- package/dist/api/metroplexSocket.js +882 -0
- package/dist/api/storedMetrics.js +201 -0
- package/dist/api/storedPageVisit.js +235 -0
- package/dist/const_matchers.js +260 -0
- package/dist/constants.d.ts +264 -0
- package/dist/constants.js +528 -0
- package/dist/entry/index.d.ts +8 -0
- package/dist/entry/index.js +15 -0
- package/dist/entry/init.js +91 -0
- package/dist/monitors/clickMonitor.js +284 -0
- package/dist/monitors/elementMonitor.js +174 -0
- package/dist/monitors/errorMonitor.js +295 -0
- package/dist/monitors/gqlErrorValidator.js +306 -0
- package/dist/monitors/httpDataBundler.js +665 -0
- package/dist/monitors/inputMonitor.js +130 -0
- package/dist/monitors/keyboardInputMonitor.js +67 -0
- package/dist/monitors/locationChangeMonitor.js +30 -0
- package/dist/monitors/pageMonitor.js +119 -0
- package/dist/monitors/requestMonitor.js +679 -0
- package/dist/pageVisit/pageVisit.js +172 -0
- package/dist/pageVisit/pageVisitEventError/pageVisitEventError.js +313 -0
- package/dist/pageVisit/pageVisitEventHTTP/pageVisitEventHTTP.js +115 -0
- package/dist/pageVisit/userStep/userStep.js +20 -0
- package/dist/react/ErrorBoundary.d.ts +72 -0
- package/dist/react/ErrorBoundary.js +102 -0
- package/dist/storage/localStorageProvider.js +23 -0
- package/dist/storage/rnStorageProvider.js +62 -0
- package/dist/storage/sessionStorageProvider.js +23 -0
- package/dist/storage/storage.js +119 -0
- package/dist/storage/storageProvider.js +83 -0
- package/dist/utils/date.js +62 -0
- package/dist/utils/eventlistener.js +67 -0
- package/dist/utils/function.js +398 -0
- package/dist/utils/object.js +144 -0
- package/dist/utils/performance.js +21 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Noibu React-Native public SDK
|
|
2
|
+
|
|
3
|
+
Noibu's React Native SDK allows customers to easily track checkout errors in their Android and iOS apps written in React Native.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Usage](#usage)
|
|
9
|
+
- [Configuration](#configuration)
|
|
10
|
+
- [API Reference](#api-reference)
|
|
11
|
+
- [Contributing](#contributing)
|
|
12
|
+
- [License](#license)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
### Requirements
|
|
17
|
+
|
|
18
|
+
For alpha version there are no strong limitations, except for:
|
|
19
|
+
|
|
20
|
+
- react-native version should be >=0.63.0
|
|
21
|
+
|
|
22
|
+
### Installing the SDK
|
|
23
|
+
|
|
24
|
+
Install using favourite node package manager (npm, yarn, etc.):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install noibu-react-native --save
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
Wrap your root App component into SDK ErrorBoundary:
|
|
33
|
+
|
|
34
|
+
```jsx
|
|
35
|
+
import React from 'react';
|
|
36
|
+
import { View, Text } from 'react-native';
|
|
37
|
+
import { ErrorBoundary } from 'noibu-react-native';
|
|
38
|
+
|
|
39
|
+
export default function App() {
|
|
40
|
+
return (
|
|
41
|
+
<ErrorBoundary
|
|
42
|
+
fallback={() => (
|
|
43
|
+
<View>
|
|
44
|
+
<Text>Oh no!</Text>
|
|
45
|
+
</View>
|
|
46
|
+
)}
|
|
47
|
+
>
|
|
48
|
+
<View>
|
|
49
|
+
<Text>Hello world!</Text>
|
|
50
|
+
</View>
|
|
51
|
+
</ErrorBoundary>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
That's it! First time the module is imported, it runs an init and starts listening to errors.
|
|
57
|
+
|
|
58
|
+
## Configuration
|
|
59
|
+
|
|
60
|
+
ErrorBoundary component has a few useful properties described here https://help.noibu.com/hc/en-us/articles/9562254753677-Noibu-React-SDK under the section _ErrorBoundary Class -> Props_.
|
|
61
|
+
|
|
62
|
+
## API Reference
|
|
63
|
+
|
|
64
|
+
Apart from exporting ErrorBoundary component, noibu-react-native module has NoibuJS object export with useful methods.
|
|
65
|
+
|
|
66
|
+
### `NoibuJS`
|
|
67
|
+
|
|
68
|
+
#### `requestHelpCode(alert?: boolean): Promise<string>`
|
|
69
|
+
|
|
70
|
+
Requests a help code from the HelpCode instance. To read more about help codes, refer to the page: https://help.noibu.com/hc/en-us/articles/14051818012813-How-to-Find-a-Session-with-Help-Code
|
|
71
|
+
|
|
72
|
+
- `@returns {Promise<string>}` - A promise that resolves with the requested help code.
|
|
73
|
+
|
|
74
|
+
```jsx
|
|
75
|
+
import { NoibuJS } from 'noibu-react-native';
|
|
76
|
+
import { useCallback, useState } from 'react';
|
|
77
|
+
import { Alert, Text, Pressable, View} from 'react-native';
|
|
78
|
+
|
|
79
|
+
const AlertHelpCode = () => {
|
|
80
|
+
const triggerHelpCodeAlert = useCallback(async () => {
|
|
81
|
+
const response = await NoibuJS.requestHelpCode();
|
|
82
|
+
if (response) {
|
|
83
|
+
Alert.alert('Help Code delivered:', response);
|
|
84
|
+
}
|
|
85
|
+
}, []);
|
|
86
|
+
return (
|
|
87
|
+
<View>
|
|
88
|
+
<Pressable onPress={triggerHelpCodeAlert}>
|
|
89
|
+
<View>
|
|
90
|
+
<Text>Tap to view Help Code</Text>
|
|
91
|
+
</View>
|
|
92
|
+
</Pressable>
|
|
93
|
+
</View>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `addCustomAttribute(name: string, value: string) => string`
|
|
99
|
+
|
|
100
|
+
Adds a custom attribute to the session.
|
|
101
|
+
|
|
102
|
+
- `@param {string} name` - Name of a custom attribute.
|
|
103
|
+
- `@param {any} value` - It's value, should be a JSON.stringify-able type.
|
|
104
|
+
- `@returns {string}` - A success message, or validation failure cause.
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
import { NoibuJS } from 'noibu-react-native';
|
|
108
|
+
|
|
109
|
+
NoibuJS.addCustomAttribute('myNameIs', 'Jeff'); // SUCCESS
|
|
110
|
+
NoibuJS.addCustomAttribute('veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLong', 'Jeff'); // NAME_TOO_LONG
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### `addError(customError: Error) => string`
|
|
114
|
+
|
|
115
|
+
Adds a custom Error to the session.
|
|
116
|
+
|
|
117
|
+
- `@param {{ message: string, stack: string }} customError` - an Error-like object to be reported with the session.
|
|
118
|
+
- `@returns {string}` - A success message, or validation failure cause.
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
import { NoibuJS } from 'noibu-react-native';
|
|
122
|
+
|
|
123
|
+
NoibuJS.addError(new Error('My Error'));
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### `addJsSdkError(customError: Error, errorSource: string) => string`
|
|
127
|
+
|
|
128
|
+
Adds an error from a JS SDK to the session, this method is used by ErrorBoundary internally. Similar to addError(), but additionally allows to set a cause.
|
|
129
|
+
|
|
130
|
+
- `@param {{ message: string, stack: string }} error` - an Error-like object to be reported with the session.
|
|
131
|
+
- `@param {string} errorSource` - source of an error.
|
|
132
|
+
- `@returns {string}` - A success message, or validation failure cause.
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
import { NoibuJS } from 'noibu-react-native';
|
|
136
|
+
|
|
137
|
+
NoibuJS.addJsSdkError(new Error('My Error'), 'myModule.js');
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Publishing
|
|
141
|
+
|
|
142
|
+
Ci has a job to publish a new version of the SDK, but has to be manually triggered.
|
|
143
|
+
Docker image used for publishing is noibujssdkci:0.2
|
|
144
|
+
|
|
145
|
+
## Contributing
|
|
146
|
+
|
|
147
|
+
You can contribute by checking out the project page and open issues. https://linear.app/noibu/project/react-native-sdk-5ccd19a3343a
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
Copyright 2023 Noibu.com
|
|
152
|
+
|
|
153
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
154
|
+
|
|
155
|
+
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import uuid from 'react-native-uuid';
|
|
2
|
+
import { MAX_METROPLEX_SOCKET_INNACTIVE_TIME, DISABLED_STATUS_KEY, CLIENT_UNLOCK_TIME_KEY, SEVERITY_WARN, LAST_ACTIVE_TIME_KEY, CURRENT_PAGE_VISIT_COUNT_KEY, NOIBU_BROWSER_ID_KYWRD, BROWSER_ID_KEY, PV_SEQ_NUM_RESET_TIME_MINUTES, SEVERITY_ERROR, JS_ENV, PAGE_VISIT_ID_KEY, MAX_PAGEVISIT_VISITED, CLIENT_LOCK_TIME_MINUTES, GET_SCRIPT_ID, MAX_COLLECT_ERROR_LOG } from '../constants.js';
|
|
3
|
+
import { stringifyJSON, getUserAgent, asString, makeRequest } from '../utils/function.js';
|
|
4
|
+
import Storage from '../storage/storage.js';
|
|
5
|
+
|
|
6
|
+
/** @module ClientConfig */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Singleton class to manage the client configuration
|
|
10
|
+
* this class will be responsible for controlling the disabled
|
|
11
|
+
* status of the client script as well as managing all storage
|
|
12
|
+
* storing and retrieval.
|
|
13
|
+
*/
|
|
14
|
+
class ClientConfig {
|
|
15
|
+
/**
|
|
16
|
+
* Creates a ClientConfig singleton instance
|
|
17
|
+
* @param {} noibuErrorURL
|
|
18
|
+
*/
|
|
19
|
+
constructor(noibuErrorURL) {
|
|
20
|
+
// sets up this.browserId, this.disabledStatus
|
|
21
|
+
this.pageVisitId = uuid.v4();
|
|
22
|
+
// variables stored in storage
|
|
23
|
+
this.isClientDisabled = null;
|
|
24
|
+
this.browserId = null;
|
|
25
|
+
this.pageVisitSeq = null;
|
|
26
|
+
// This variable tracks the last time the user was active in this session.
|
|
27
|
+
// It is also written to storage. Initialized to now so the session can be
|
|
28
|
+
// timed out even if a PV is never sent.
|
|
29
|
+
this.lastActiveTime = new Date();
|
|
30
|
+
// error URL to send Noibu errors to
|
|
31
|
+
this.noibuErrorURL = noibuErrorURL;
|
|
32
|
+
// sets up this.browserId, this.isClientDisabled, this.pageVisitSeq
|
|
33
|
+
this._setupStorageVars();
|
|
34
|
+
// error sent to backend counter
|
|
35
|
+
this.cltErrorPostCounter = 0;
|
|
36
|
+
// variables for checking if the socket is inactive
|
|
37
|
+
// used a class variables in order to be changed in testing
|
|
38
|
+
this.maxSocketInactiveTime = MAX_METROPLEX_SOCKET_INNACTIVE_TIME;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Configures the singleton instance */
|
|
42
|
+
static configureInstance(noibuErrorURL) {
|
|
43
|
+
if (!this.instance) {
|
|
44
|
+
// Set this.noibuErrorURL preemptively in case ClientConfig isn't able to be
|
|
45
|
+
// configured properly and throws an error.
|
|
46
|
+
// This will ensure we get the expected error POST request at the correct URL.
|
|
47
|
+
this.noibuErrorURL = noibuErrorURL;
|
|
48
|
+
this.instance = new ClientConfig(noibuErrorURL);
|
|
49
|
+
this.instance.noibuErrorURL = noibuErrorURL;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* gets the singleton instance
|
|
55
|
+
* @returns {ClientConfig}
|
|
56
|
+
*/
|
|
57
|
+
static getInstance() {
|
|
58
|
+
if (!this.instance) {
|
|
59
|
+
throw new Error('ClientConfig was not configured');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return this.instance;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** lockClient will disable the client script for a single pagevisit for
|
|
66
|
+
* duration given in minuntes */
|
|
67
|
+
lockClient(duration, msg) {
|
|
68
|
+
const expiryTime = new Date();
|
|
69
|
+
expiryTime.setMinutes(expiryTime.getMinutes() + duration);
|
|
70
|
+
|
|
71
|
+
const noibuLSObject = this._getClientState();
|
|
72
|
+
noibuLSObject[DISABLED_STATUS_KEY] = true;
|
|
73
|
+
noibuLSObject[CLIENT_UNLOCK_TIME_KEY] = expiryTime;
|
|
74
|
+
this._storeBrowserData(noibuLSObject);
|
|
75
|
+
this.postNoibuErrorAndOptionallyDisableClient(msg, true, SEVERITY_WARN);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Locks the client until the next page loads */
|
|
79
|
+
lockClientUntilNextPage(msg) {
|
|
80
|
+
this.postNoibuErrorAndOptionallyDisableClient(msg, true, SEVERITY_WARN);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Updates the config object to store the given last active time */
|
|
84
|
+
updateLastActiveTime(lastActiveTime) {
|
|
85
|
+
this.lastActiveTime = lastActiveTime;
|
|
86
|
+
const newConfigData = this._getLsObject();
|
|
87
|
+
newConfigData[LAST_ACTIVE_TIME_KEY] = lastActiveTime;
|
|
88
|
+
this._storeBrowserData(newConfigData);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Gets the current page visit sequence number that should be used */
|
|
92
|
+
getPageVisitSeq() {
|
|
93
|
+
if (this._pageVisitSeqNeedsReset()) {
|
|
94
|
+
// Reset the page visit sequence number to zero and store the next seq number in storage
|
|
95
|
+
this.pageVisitSeq = 0;
|
|
96
|
+
const newConfigData = this._getLsObject();
|
|
97
|
+
newConfigData[CURRENT_PAGE_VISIT_COUNT_KEY] = this.pageVisitSeq + 1;
|
|
98
|
+
// Update the last active time since we are actively requesting the seq
|
|
99
|
+
newConfigData[LAST_ACTIVE_TIME_KEY] = new Date();
|
|
100
|
+
this._storeBrowserData(newConfigData);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return this.pageVisitSeq;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Returns the client config object from storage or generates a new one
|
|
108
|
+
* What is stored in storage will look like this
|
|
109
|
+
* {
|
|
110
|
+
* BrowserId: UUIDV4
|
|
111
|
+
* ExpiryTime: DATE OBJ
|
|
112
|
+
* DisabledStatus: BOOL
|
|
113
|
+
* CurrentPageVisitCount: INT
|
|
114
|
+
* ClientUnlockTime: DATE OBJ
|
|
115
|
+
* LastActiveTime: DATE OBJ
|
|
116
|
+
* }
|
|
117
|
+
*/
|
|
118
|
+
_getLsObject() {
|
|
119
|
+
const storage = Storage.getInstance();
|
|
120
|
+
const storedConfig = storage.load(NOIBU_BROWSER_ID_KYWRD);
|
|
121
|
+
|
|
122
|
+
// first time browsing since noibu was installed
|
|
123
|
+
if (!storedConfig) {
|
|
124
|
+
return this._generateAndStoreData();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let parsedConfig = {};
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
parsedConfig = JSON.parse(storedConfig);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return this._generateAndStoreData();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// checking if it's a valid object. The CLIENT_UNLOCK_TIME_KEY doesn't have to exist
|
|
136
|
+
// since it's not written to the storage object when set to null.
|
|
137
|
+
if (
|
|
138
|
+
parsedConfig[BROWSER_ID_KEY] == null ||
|
|
139
|
+
parsedConfig[DISABLED_STATUS_KEY] == null ||
|
|
140
|
+
parsedConfig[CURRENT_PAGE_VISIT_COUNT_KEY] == null ||
|
|
141
|
+
parsedConfig[LAST_ACTIVE_TIME_KEY] == null
|
|
142
|
+
) {
|
|
143
|
+
return this._generateAndStoreData();
|
|
144
|
+
}
|
|
145
|
+
return parsedConfig;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if we have surpased the last active time and the page visit seq number needs resetting
|
|
150
|
+
*/
|
|
151
|
+
_pageVisitSeqNeedsReset() {
|
|
152
|
+
const noibuLSObject = this._getClientState();
|
|
153
|
+
const someTimeAgo = new Date();
|
|
154
|
+
someTimeAgo.setMinutes(
|
|
155
|
+
someTimeAgo.getMinutes() - PV_SEQ_NUM_RESET_TIME_MINUTES,
|
|
156
|
+
);
|
|
157
|
+
return new Date(noibuLSObject[LAST_ACTIVE_TIME_KEY]) < someTimeAgo;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* _setupStorageVars will set all class variables that depend
|
|
162
|
+
* on the storage's value.
|
|
163
|
+
*/
|
|
164
|
+
_setupStorageVars() {
|
|
165
|
+
const storage = Storage.getInstance();
|
|
166
|
+
if (!storage.isAvailable()) {
|
|
167
|
+
this.postNoibuErrorAndOptionallyDisableClient(
|
|
168
|
+
`Storage is unavailable, disabling client. ${storage.getDiagnoseInfo()}`,
|
|
169
|
+
true,
|
|
170
|
+
SEVERITY_ERROR,
|
|
171
|
+
);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// getting the current content of the storage
|
|
176
|
+
const noibuLSObject = this._getClientState();
|
|
177
|
+
|
|
178
|
+
// Check if we have surpased the last active time and reset the sequence number if so
|
|
179
|
+
if (this._pageVisitSeqNeedsReset()) {
|
|
180
|
+
noibuLSObject[CURRENT_PAGE_VISIT_COUNT_KEY] = 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this.browserId = noibuLSObject[BROWSER_ID_KEY];
|
|
184
|
+
this.pageVisitSeq = noibuLSObject[CURRENT_PAGE_VISIT_COUNT_KEY];
|
|
185
|
+
this.isClientDisabled = noibuLSObject[DISABLED_STATUS_KEY];
|
|
186
|
+
|
|
187
|
+
// If the client has been disabled just return.
|
|
188
|
+
// Calling _getClientState() above performs the disabled expirey check
|
|
189
|
+
if (this.isClientDisabled) return;
|
|
190
|
+
|
|
191
|
+
// Update the LS object values before storing it for the next page visit
|
|
192
|
+
noibuLSObject[CURRENT_PAGE_VISIT_COUNT_KEY] += 1;
|
|
193
|
+
noibuLSObject[LAST_ACTIVE_TIME_KEY] = new Date();
|
|
194
|
+
|
|
195
|
+
// Expose page visit ID in storage for use by Trailbreaker video tests
|
|
196
|
+
// This will be done for the testvideo and lambdavideo bundles used by Trailbreaker
|
|
197
|
+
if (JS_ENV().includes('video')) {
|
|
198
|
+
noibuLSObject[PAGE_VISIT_ID_KEY] = this.pageVisitId;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// if we have reached the max page visits for a browser id then
|
|
202
|
+
// we disabled collect for 45 minutes
|
|
203
|
+
if (noibuLSObject[CURRENT_PAGE_VISIT_COUNT_KEY] >= MAX_PAGEVISIT_VISITED) {
|
|
204
|
+
// if we bust the max pagevisit visited limit we lock the client for 45 minutes
|
|
205
|
+
// since its probably a bot.
|
|
206
|
+
const expiryTime = new Date();
|
|
207
|
+
expiryTime.setMinutes(expiryTime.getMinutes() + CLIENT_LOCK_TIME_MINUTES);
|
|
208
|
+
// setting the lock time
|
|
209
|
+
noibuLSObject[CLIENT_UNLOCK_TIME_KEY] = expiryTime;
|
|
210
|
+
noibuLSObject[DISABLED_STATUS_KEY] = true;
|
|
211
|
+
this.postNoibuErrorAndOptionallyDisableClient(
|
|
212
|
+
`Hit max page visits, disabling client for ${CLIENT_LOCK_TIME_MINUTES}mins`,
|
|
213
|
+
true,
|
|
214
|
+
SEVERITY_ERROR,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// we now check if we successfully saved the data
|
|
219
|
+
const savedData = this._storeBrowserData(noibuLSObject);
|
|
220
|
+
|
|
221
|
+
// if the browser is null, we cannot access the storage or an
|
|
222
|
+
// error happened, thus we disable collect.
|
|
223
|
+
if (savedData[BROWSER_ID_KEY] === null) {
|
|
224
|
+
// we do not set a lock expiry date here since we cannot store to storage
|
|
225
|
+
this.postNoibuErrorAndOptionallyDisableClient(
|
|
226
|
+
`Null browser in storage, disabling client`,
|
|
227
|
+
true,
|
|
228
|
+
SEVERITY_ERROR,
|
|
229
|
+
);
|
|
230
|
+
this.browserId = null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Function will get the Noibu Storage Object
|
|
236
|
+
* 1. Generate a brand new one
|
|
237
|
+
* Get it from storage if the expiry date is not in the past
|
|
238
|
+
* Generate a brand new one if the expiry date is in the past
|
|
239
|
+
*/
|
|
240
|
+
_getClientState() {
|
|
241
|
+
const newConfigData = this._getLsObject();
|
|
242
|
+
|
|
243
|
+
// if the lock expired, we remove the lock period and enable the client
|
|
244
|
+
if (
|
|
245
|
+
newConfigData[CLIENT_UNLOCK_TIME_KEY] &&
|
|
246
|
+
new Date(newConfigData[CLIENT_UNLOCK_TIME_KEY]) <= new Date()
|
|
247
|
+
) {
|
|
248
|
+
newConfigData[CLIENT_UNLOCK_TIME_KEY] = null;
|
|
249
|
+
newConfigData[DISABLED_STATUS_KEY] = false;
|
|
250
|
+
this._storeBrowserData(newConfigData);
|
|
251
|
+
}
|
|
252
|
+
// return the stored browserId
|
|
253
|
+
return newConfigData;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* _generateAndStoreData generates brand new data and then proceeds to store
|
|
258
|
+
* it.
|
|
259
|
+
*/
|
|
260
|
+
_generateAndStoreData() {
|
|
261
|
+
const data = this._storeBrowserData(this._generateNewBrowserData());
|
|
262
|
+
return data;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* _generateNewBrowserData will create new data to be stored in storage
|
|
267
|
+
* and persisted throughout a session
|
|
268
|
+
*/
|
|
269
|
+
_generateNewBrowserData() {
|
|
270
|
+
const noibuBrowserData = {
|
|
271
|
+
[DISABLED_STATUS_KEY]: false,
|
|
272
|
+
[BROWSER_ID_KEY]: uuid.v4(),
|
|
273
|
+
[CURRENT_PAGE_VISIT_COUNT_KEY]: 0,
|
|
274
|
+
[CLIENT_UNLOCK_TIME_KEY]: null,
|
|
275
|
+
[LAST_ACTIVE_TIME_KEY]: new Date(),
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Expose page visit ID in storage for use by Trailbreaker video tests
|
|
279
|
+
// This will be done for the testvideo and lambdavideo bundles used by Trailbreaker
|
|
280
|
+
if (JS_ENV().includes('video')) {
|
|
281
|
+
noibuBrowserData[PAGE_VISIT_ID_KEY] = this.pageVisitId;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return noibuBrowserData;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* _storeBrowserData will store the passed object in storage.
|
|
289
|
+
* @param {} data the data to be stored
|
|
290
|
+
*/
|
|
291
|
+
_storeBrowserData(data) {
|
|
292
|
+
const storage = Storage.getInstance();
|
|
293
|
+
try {
|
|
294
|
+
storage.save(NOIBU_BROWSER_ID_KYWRD, stringifyJSON(data));
|
|
295
|
+
return data;
|
|
296
|
+
} catch (e) {
|
|
297
|
+
this.postNoibuErrorAndOptionallyDisableClient(
|
|
298
|
+
`Error writing browser data to storage, disabling client: ${e.message}, ` +
|
|
299
|
+
`${storage.getDiagnoseInfo()}`,
|
|
300
|
+
true,
|
|
301
|
+
SEVERITY_ERROR,
|
|
302
|
+
);
|
|
303
|
+
// sending empty fields if we encountered errors while storing in the LS
|
|
304
|
+
const invalidData = {
|
|
305
|
+
[DISABLED_STATUS_KEY]: true,
|
|
306
|
+
[BROWSER_ID_KEY]: null,
|
|
307
|
+
[CURRENT_PAGE_VISIT_COUNT_KEY]: 0,
|
|
308
|
+
};
|
|
309
|
+
return invalidData;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* postNoibuErrorAndOptionallyDisableClient will post errors that were thrown by collect
|
|
315
|
+
* and disable the client if required
|
|
316
|
+
* severity expects one of the SEVERITY_x level constants, or else error will be used
|
|
317
|
+
* @param {} error
|
|
318
|
+
* @param {} disableClient
|
|
319
|
+
* @param {} severity
|
|
320
|
+
* @param {} keepAlive=false
|
|
321
|
+
*/
|
|
322
|
+
async postNoibuErrorAndOptionallyDisableClient(
|
|
323
|
+
error,
|
|
324
|
+
disableClient,
|
|
325
|
+
severity,
|
|
326
|
+
keepAlive = false,
|
|
327
|
+
) {
|
|
328
|
+
if (this.isClientDisabled) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (disableClient) {
|
|
332
|
+
this.isClientDisabled = true;
|
|
333
|
+
}
|
|
334
|
+
if (severity === SEVERITY_WARN) {
|
|
335
|
+
// don't log warning messages by default, as a cost savings
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let errMsg = `Noibu Browser ID(${
|
|
340
|
+
this.browserId ? this.browserId : ''
|
|
341
|
+
}), PV ID ${
|
|
342
|
+
this.pageVisitId
|
|
343
|
+
}, Script ID ${GET_SCRIPT_ID()}, and User Agent ${await getUserAgent()} error: ${asString(
|
|
344
|
+
error,
|
|
345
|
+
)}`;
|
|
346
|
+
|
|
347
|
+
// if the page visits sends more errors than the
|
|
348
|
+
// allowed threshold we disable the client
|
|
349
|
+
if (this.cltErrorPostCounter >= MAX_COLLECT_ERROR_LOG) {
|
|
350
|
+
// we disable the client for 10 minute if we sent too many logs
|
|
351
|
+
// we manually lock the client to not cause an infinite loop
|
|
352
|
+
const expiryTime = new Date();
|
|
353
|
+
expiryTime.setMinutes(expiryTime.getMinutes() + 10); // 10 minutes lock
|
|
354
|
+
|
|
355
|
+
const noibuLSObject = this._getClientState();
|
|
356
|
+
noibuLSObject[DISABLED_STATUS_KEY] = true;
|
|
357
|
+
noibuLSObject[CLIENT_UNLOCK_TIME_KEY] = expiryTime;
|
|
358
|
+
this._storeBrowserData(noibuLSObject);
|
|
359
|
+
this.isClientDisabled = true;
|
|
360
|
+
// end of lock
|
|
361
|
+
// overriding the message to be an alert that we are shutting collect off.
|
|
362
|
+
errMsg =
|
|
363
|
+
'Shutting collect off, we reached the ' +
|
|
364
|
+
'maximum limit of collect errors sent.';
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const errorContent = {
|
|
368
|
+
url: (global.location && global.location.href) || '',
|
|
369
|
+
err_msg: errMsg,
|
|
370
|
+
sev: severity,
|
|
371
|
+
};
|
|
372
|
+
const headers = {
|
|
373
|
+
'content-type': 'application/json',
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
if (!keepAlive) {
|
|
377
|
+
makeRequest(
|
|
378
|
+
'POST',
|
|
379
|
+
this.noibuErrorURL,
|
|
380
|
+
errorContent,
|
|
381
|
+
headers,
|
|
382
|
+
2000,
|
|
383
|
+
false,
|
|
384
|
+
).catch(() => {
|
|
385
|
+
// we do nothing and let this error silently fail
|
|
386
|
+
});
|
|
387
|
+
} else {
|
|
388
|
+
fetch(this.noibuErrorURL, {
|
|
389
|
+
method: 'POST',
|
|
390
|
+
headers,
|
|
391
|
+
body: stringifyJSON(errorContent),
|
|
392
|
+
// keep alive outlives the current page, its the same as beacon
|
|
393
|
+
keepalive: true,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// only increment if this was an actual error, not a warning or otherwise
|
|
398
|
+
if (severity === SEVERITY_ERROR) {
|
|
399
|
+
this.cltErrorPostCounter += 1;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Returns true if the page visit is considered to be inactive
|
|
405
|
+
*/
|
|
406
|
+
isInactive() {
|
|
407
|
+
const someTimeAgo = new Date();
|
|
408
|
+
someTimeAgo.setSeconds(
|
|
409
|
+
someTimeAgo.getSeconds() - this.maxSocketInactiveTime,
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
return this.lastActiveTime < someTimeAgo;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export { ClientConfig as default };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import MetroplexSocket from './metroplexSocket.js';
|
|
2
|
+
import { SEVERITY_ERROR, WORK_REQUEST_ATT_NAME, HELP_CODE_ATT_NAME } from '../constants.js';
|
|
3
|
+
import ClientConfig from './clientConfig.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* HelpCode class is responsible for help code feature related functionality
|
|
7
|
+
*/
|
|
8
|
+
class HelpCode {
|
|
9
|
+
/**
|
|
10
|
+
* Singleton instance
|
|
11
|
+
* @returns {HelpCode}
|
|
12
|
+
*/
|
|
13
|
+
static getInstance() {
|
|
14
|
+
if (!this.instance) {
|
|
15
|
+
this.instance = new HelpCode();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return this.instance;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Constructs instance and sets up event listeners
|
|
23
|
+
*/
|
|
24
|
+
constructor() {
|
|
25
|
+
this.requestContext = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Requests a help code and returns a Promise that resolves when the help code is obtained
|
|
30
|
+
* or rejects if the noibu connection is unavailable.
|
|
31
|
+
|
|
32
|
+
* @returns {Promise<string>} Promise object representing the help code request.
|
|
33
|
+
* @throws {string} Throws an error if the noibu connection is unavailable.
|
|
34
|
+
*/
|
|
35
|
+
requestHelpCode() {
|
|
36
|
+
if (this.requestContext != null) {
|
|
37
|
+
return this.requestContext.promise;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const context = {
|
|
41
|
+
resolve: null,
|
|
42
|
+
reject: null,
|
|
43
|
+
promise: null,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
context.promise = new Promise((resolve, reject) => {
|
|
47
|
+
context.resolve = resolve;
|
|
48
|
+
context.reject = reject;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.requestContext = context;
|
|
52
|
+
|
|
53
|
+
const result = this._sendRequest();
|
|
54
|
+
|
|
55
|
+
if (result === false) {
|
|
56
|
+
this.requestContext = null;
|
|
57
|
+
return Promise.reject(new Error('noibu connection is unavailable'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return this.requestContext.promise;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Handles the received help code event.
|
|
65
|
+
* @param {CustomEvent<string>} event - The event object with string detail property.
|
|
66
|
+
* @returns {void}
|
|
67
|
+
*/
|
|
68
|
+
receiveHelpCode(event) {
|
|
69
|
+
if (this.requestContext === null) {
|
|
70
|
+
const { success, data } = event.detail;
|
|
71
|
+
|
|
72
|
+
if (!success) {
|
|
73
|
+
const message = `Noibu help code is not available due to ${data}`;
|
|
74
|
+
|
|
75
|
+
ClientConfig.getInstance().postNoibuErrorAndOptionallyDisableClient(
|
|
76
|
+
message,
|
|
77
|
+
false,
|
|
78
|
+
SEVERITY_ERROR,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const context = this.requestContext;
|
|
85
|
+
this.requestContext = null;
|
|
86
|
+
|
|
87
|
+
const { success, data } = event.detail;
|
|
88
|
+
|
|
89
|
+
if (success) {
|
|
90
|
+
context.resolve(data);
|
|
91
|
+
} else {
|
|
92
|
+
context.reject(new Error(data));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Sends metroplex request
|
|
98
|
+
*/
|
|
99
|
+
_sendRequest() {
|
|
100
|
+
return MetroplexSocket.getInstance().sendMessage(WORK_REQUEST_ATT_NAME, {
|
|
101
|
+
[WORK_REQUEST_ATT_NAME]: HELP_CODE_ATT_NAME,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { HelpCode as default };
|