@statsig/client-core 3.2.0 → 3.3.0-beta.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/package.json +1 -1
- package/src/DnsTxtQuery.d.ts +2 -0
- package/src/DnsTxtQuery.js +63 -0
- package/src/NetworkCore.d.ts +7 -2
- package/src/NetworkCore.js +78 -39
- package/src/NetworkFallbackResolver.d.ts +18 -0
- package/src/NetworkFallbackResolver.js +197 -0
- package/src/StatsigClientBase.js +1 -1
- package/src/StatsigMetadata.d.ts +1 -1
- package/src/StatsigMetadata.js +1 -1
package/package.json
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports._fetchTxtRecords = void 0;
|
|
13
|
+
// See example: https://github.com/statsig-io/private-js-client-monorepo/pull/340
|
|
14
|
+
const FEATURE_ASSETS_DNS_QUERY = new Uint8Array([
|
|
15
|
+
0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d,
|
|
16
|
+
0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73,
|
|
17
|
+
0x03, 0x6f, 0x72, 0x67, 0x00, 0x00, 0x10, 0x00, 0x01,
|
|
18
|
+
]);
|
|
19
|
+
const DNS_QUERY_ENDPOINT = 'https://cloudflare-dns.com/dns-query';
|
|
20
|
+
const DOMAIN_CHARS = [
|
|
21
|
+
'i', // initialize
|
|
22
|
+
'e', // events
|
|
23
|
+
'd', // dcs
|
|
24
|
+
];
|
|
25
|
+
const MAX_START_LOOKUP = 200;
|
|
26
|
+
function _fetchTxtRecords(networkFunc) {
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
const response = yield networkFunc(DNS_QUERY_ENDPOINT, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/dns-message',
|
|
32
|
+
Accept: 'application/dns-message',
|
|
33
|
+
},
|
|
34
|
+
body: FEATURE_ASSETS_DNS_QUERY,
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const err = new Error('Failed to fetch TXT records from DNS');
|
|
38
|
+
err.name = 'DnsTxtFetchError';
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
const data = yield response.arrayBuffer();
|
|
42
|
+
const bytes = new Uint8Array(data);
|
|
43
|
+
return _parseDnsResponse(bytes);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
exports._fetchTxtRecords = _fetchTxtRecords;
|
|
47
|
+
function _parseDnsResponse(input) {
|
|
48
|
+
// loop until we find the first valid domain char. One of [i=, e=, d=]
|
|
49
|
+
const start = input.findIndex((byte, index) => index < MAX_START_LOOKUP &&
|
|
50
|
+
String.fromCharCode(byte) === '=' &&
|
|
51
|
+
DOMAIN_CHARS.includes(String.fromCharCode(input[index - 1])));
|
|
52
|
+
if (start === -1) {
|
|
53
|
+
const err = new Error('Failed to parse TXT records from DNS');
|
|
54
|
+
err.name = 'DnsTxtParseError';
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
// decode the remaining bytes as a string
|
|
58
|
+
let result = '';
|
|
59
|
+
for (let i = start - 1; i < input.length; i++) {
|
|
60
|
+
result += String.fromCharCode(input[i]);
|
|
61
|
+
}
|
|
62
|
+
return result.split(',');
|
|
63
|
+
}
|
package/src/NetworkCore.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import './$_StatsigGlobal';
|
|
2
|
+
import { ErrorBoundary } from './ErrorBoundary';
|
|
2
3
|
import { NetworkPriority } from './NetworkConfig';
|
|
3
4
|
import { StatsigClientEmitEventFunc } from './StatsigClientBase';
|
|
4
5
|
import { AnyStatsigOptions } from './StatsigOptionsCommon';
|
|
@@ -28,14 +29,18 @@ export declare class NetworkCore {
|
|
|
28
29
|
private readonly _timeout;
|
|
29
30
|
private readonly _netConfig;
|
|
30
31
|
private readonly _options;
|
|
32
|
+
private readonly _fallbackResolver;
|
|
33
|
+
private _errorBoundary;
|
|
31
34
|
constructor(options: AnyStatsigOptions | null, _emitter?: StatsigClientEmitEventFunc | undefined);
|
|
32
|
-
|
|
33
|
-
get(args: RequestArgs): Promise<NetworkResponse | null>;
|
|
35
|
+
setErrorBoundary(errorBoundary: ErrorBoundary): void;
|
|
34
36
|
isBeaconSupported(): boolean;
|
|
35
37
|
beacon(args: BeaconRequestArgs): Promise<boolean>;
|
|
38
|
+
post(args: RequestArgsWithData): Promise<NetworkResponse | null>;
|
|
39
|
+
get(args: RequestArgs): Promise<NetworkResponse | null>;
|
|
36
40
|
private _sendRequest;
|
|
37
41
|
private _getPopulatedURL;
|
|
38
42
|
private _getPopulatedBody;
|
|
39
43
|
private _attemptToEncodeString;
|
|
44
|
+
private _getInternalRequestArgs;
|
|
40
45
|
}
|
|
41
46
|
export {};
|
package/src/NetworkCore.js
CHANGED
|
@@ -15,6 +15,7 @@ const __StatsigGlobal_1 = require("./$_StatsigGlobal");
|
|
|
15
15
|
const Diagnostics_1 = require("./Diagnostics");
|
|
16
16
|
const Log_1 = require("./Log");
|
|
17
17
|
const NetworkConfig_1 = require("./NetworkConfig");
|
|
18
|
+
const NetworkFallbackResolver_1 = require("./NetworkFallbackResolver");
|
|
18
19
|
const SDKType_1 = require("./SDKType");
|
|
19
20
|
const SafeJs_1 = require("./SafeJs");
|
|
20
21
|
const SessionID_1 = require("./SessionID");
|
|
@@ -29,6 +30,7 @@ class NetworkCore {
|
|
|
29
30
|
this._timeout = DEFAULT_TIMEOUT_MS;
|
|
30
31
|
this._netConfig = {};
|
|
31
32
|
this._options = {};
|
|
33
|
+
this._errorBoundary = null;
|
|
32
34
|
if (options) {
|
|
33
35
|
this._options = options;
|
|
34
36
|
}
|
|
@@ -38,18 +40,13 @@ class NetworkCore {
|
|
|
38
40
|
if (this._netConfig.networkTimeoutMs) {
|
|
39
41
|
this._timeout = this._netConfig.networkTimeoutMs;
|
|
40
42
|
}
|
|
43
|
+
this._fallbackResolver = new NetworkFallbackResolver_1.NetworkFallbackResolver(this._options);
|
|
41
44
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
return this._sendRequest(Object.assign({ method: 'POST', body }, args));
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
get(args) {
|
|
52
|
-
return this._sendRequest(Object.assign({ method: 'GET' }, args));
|
|
45
|
+
setErrorBoundary(errorBoundary) {
|
|
46
|
+
this._errorBoundary = errorBoundary;
|
|
47
|
+
this._errorBoundary.wrap(this);
|
|
48
|
+
this._errorBoundary.wrap(this._fallbackResolver);
|
|
49
|
+
this._fallbackResolver.setErrorBoundary(errorBoundary);
|
|
53
50
|
}
|
|
54
51
|
isBeaconSupported() {
|
|
55
52
|
return (typeof navigator !== 'undefined' &&
|
|
@@ -60,12 +57,27 @@ class NetworkCore {
|
|
|
60
57
|
if (!_ensureValidSdkKey(args)) {
|
|
61
58
|
return false;
|
|
62
59
|
}
|
|
63
|
-
const
|
|
64
|
-
const
|
|
60
|
+
const argsInternal = this._getInternalRequestArgs('POST', args);
|
|
61
|
+
const body = yield this._getPopulatedBody(argsInternal, args.data);
|
|
62
|
+
const url = yield this._getPopulatedURL(argsInternal);
|
|
65
63
|
const nav = navigator;
|
|
66
64
|
return nav.sendBeacon.bind(nav)(url, body);
|
|
67
65
|
});
|
|
68
66
|
}
|
|
67
|
+
post(args) {
|
|
68
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
const argsInternal = this._getInternalRequestArgs('POST', args);
|
|
70
|
+
argsInternal.body = yield this._getPopulatedBody(argsInternal, args.data);
|
|
71
|
+
if (args.isStatsigEncodable) {
|
|
72
|
+
argsInternal.body = this._attemptToEncodeString(argsInternal, argsInternal.body);
|
|
73
|
+
}
|
|
74
|
+
return this._sendRequest(argsInternal);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
get(args) {
|
|
78
|
+
const argsInternal = this._getInternalRequestArgs('GET', args);
|
|
79
|
+
return this._sendRequest(argsInternal);
|
|
80
|
+
}
|
|
69
81
|
_sendRequest(args) {
|
|
70
82
|
var _a, _b, _c;
|
|
71
83
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -77,9 +89,11 @@ class NetworkCore {
|
|
|
77
89
|
}
|
|
78
90
|
const { method, body, retries, attempt } = args;
|
|
79
91
|
const currentAttempt = attempt !== null && attempt !== void 0 ? attempt : 1;
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
|
|
92
|
+
const abortController = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
93
|
+
const timeoutHandle = setTimeout(() => {
|
|
94
|
+
abortController === null || abortController === void 0 ? void 0 : abortController.abort(`Timeout of ${this._timeout}ms expired.`);
|
|
95
|
+
}, this._timeout);
|
|
96
|
+
const populatedUrl = yield this._getPopulatedURL(args);
|
|
83
97
|
let response = null;
|
|
84
98
|
const keepalive = (0, VisibilityObserving_1._isUnloading)();
|
|
85
99
|
try {
|
|
@@ -87,76 +101,76 @@ class NetworkCore {
|
|
|
87
101
|
method,
|
|
88
102
|
body,
|
|
89
103
|
headers: Object.assign({}, args.headers),
|
|
90
|
-
signal:
|
|
104
|
+
signal: abortController === null || abortController === void 0 ? void 0 : abortController.signal,
|
|
91
105
|
priority: args.priority,
|
|
92
106
|
keepalive,
|
|
93
107
|
};
|
|
94
|
-
|
|
95
|
-
Diagnostics_1.Diagnostics._markInitNetworkReqStart(args.sdkKey, {
|
|
96
|
-
attempt: currentAttempt,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
108
|
+
_tryMarkInitStart(args, currentAttempt);
|
|
99
109
|
const func = (_a = this._netConfig.networkOverrideFunc) !== null && _a !== void 0 ? _a : fetch;
|
|
100
|
-
response = yield func(
|
|
101
|
-
clearTimeout(
|
|
110
|
+
response = yield func(populatedUrl, config);
|
|
111
|
+
clearTimeout(timeoutHandle);
|
|
102
112
|
if (!response.ok) {
|
|
103
113
|
const text = yield response.text().catch(() => 'No Text');
|
|
104
|
-
const err = new Error(`NetworkError: ${
|
|
114
|
+
const err = new Error(`NetworkError: ${populatedUrl} ${text}`);
|
|
105
115
|
err.name = 'NetworkError';
|
|
106
116
|
throw err;
|
|
107
117
|
}
|
|
108
118
|
const text = yield response.text();
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
119
|
+
_tryMarkInitEnd(args, response, currentAttempt, text);
|
|
120
|
+
this._fallbackResolver.tryBumpExpiryTime(args.sdkKey, populatedUrl);
|
|
112
121
|
return {
|
|
113
122
|
body: text,
|
|
114
123
|
code: response.status,
|
|
115
124
|
};
|
|
116
125
|
}
|
|
117
126
|
catch (error) {
|
|
118
|
-
const errorMessage = _getErrorMessage(
|
|
119
|
-
|
|
120
|
-
|
|
127
|
+
const errorMessage = _getErrorMessage(abortController, error);
|
|
128
|
+
const timedOut = _didTimeout(abortController);
|
|
129
|
+
_tryMarkInitEnd(args, response, currentAttempt, '', error);
|
|
130
|
+
const fallbackUpdated = yield this._fallbackResolver.tryFetchUpdatedFallbackInfo(args.sdkKey, populatedUrl, errorMessage, timedOut);
|
|
131
|
+
if (fallbackUpdated) {
|
|
132
|
+
args.fallbackUrl = this._fallbackResolver.getFallbackUrl(args.sdkKey, args.url);
|
|
121
133
|
}
|
|
122
134
|
if (!retries ||
|
|
123
135
|
currentAttempt > retries ||
|
|
124
136
|
!RETRYABLE_CODES.has((_b = response === null || response === void 0 ? void 0 : response.status) !== null && _b !== void 0 ? _b : 500)) {
|
|
125
137
|
(_c = this._emitter) === null || _c === void 0 ? void 0 : _c.call(this, { name: 'error', error });
|
|
126
|
-
Log_1.Log.error(`A networking error occured during ${method} request to ${
|
|
138
|
+
Log_1.Log.error(`A networking error occured during ${method} request to ${populatedUrl}.`, errorMessage, error);
|
|
127
139
|
return null;
|
|
128
140
|
}
|
|
129
|
-
return this._sendRequest(Object.assign(Object.assign({}, args), { retries
|
|
141
|
+
return this._sendRequest(Object.assign(Object.assign({}, args), { retries, attempt: currentAttempt + 1 }));
|
|
130
142
|
}
|
|
131
143
|
});
|
|
132
144
|
}
|
|
133
145
|
_getPopulatedURL(args) {
|
|
146
|
+
var _a;
|
|
134
147
|
return __awaiter(this, void 0, void 0, function* () {
|
|
148
|
+
const url = (_a = args.fallbackUrl) !== null && _a !== void 0 ? _a : args.url;
|
|
135
149
|
const params = Object.assign({ [NetworkConfig_1.NetworkParam.SdkKey]: args.sdkKey, [NetworkConfig_1.NetworkParam.SdkType]: SDKType_1.SDKType._get(args.sdkKey), [NetworkConfig_1.NetworkParam.SdkVersion]: StatsigMetadata_1.SDK_VERSION, [NetworkConfig_1.NetworkParam.Time]: String(Date.now()), [NetworkConfig_1.NetworkParam.SessionID]: SessionID_1.SessionID.get(args.sdkKey) }, args.params);
|
|
136
150
|
const query = Object.keys(params)
|
|
137
151
|
.map((key) => {
|
|
138
152
|
return `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
|
|
139
153
|
})
|
|
140
154
|
.join('&');
|
|
141
|
-
return `${
|
|
155
|
+
return `${url}${query ? `?${query}` : ''}`;
|
|
142
156
|
});
|
|
143
157
|
}
|
|
144
|
-
_getPopulatedBody(args) {
|
|
158
|
+
_getPopulatedBody(args, data) {
|
|
145
159
|
return __awaiter(this, void 0, void 0, function* () {
|
|
146
|
-
const {
|
|
160
|
+
const { sdkKey, fallbackUrl } = args;
|
|
147
161
|
const stableID = StableID_1.StableID.get(sdkKey);
|
|
148
162
|
const sessionID = SessionID_1.SessionID.get(sdkKey);
|
|
149
163
|
const sdkType = SDKType_1.SDKType._get(sdkKey);
|
|
150
164
|
return JSON.stringify(Object.assign(Object.assign({}, data), { statsigMetadata: Object.assign(Object.assign({}, StatsigMetadata_1.StatsigMetadataProvider.get()), { stableID,
|
|
151
165
|
sessionID,
|
|
152
|
-
sdkType
|
|
166
|
+
sdkType,
|
|
167
|
+
fallbackUrl }) }));
|
|
153
168
|
});
|
|
154
169
|
}
|
|
155
170
|
_attemptToEncodeString(args, input) {
|
|
156
171
|
var _a, _b;
|
|
157
172
|
const win = (0, SafeJs_1._getWindowSafe)();
|
|
158
|
-
if (
|
|
159
|
-
this._options.disableStatsigEncoding ||
|
|
173
|
+
if (this._options.disableStatsigEncoding ||
|
|
160
174
|
(0, __StatsigGlobal_1._getStatsigGlobalFlag)('no-encode') != null ||
|
|
161
175
|
!(win === null || win === void 0 ? void 0 : win.btoa)) {
|
|
162
176
|
return input;
|
|
@@ -171,6 +185,11 @@ class NetworkCore {
|
|
|
171
185
|
return input;
|
|
172
186
|
}
|
|
173
187
|
}
|
|
188
|
+
_getInternalRequestArgs(method, args) {
|
|
189
|
+
const fallbackUrl = this._fallbackResolver.getFallbackUrl(args.sdkKey, args.url);
|
|
190
|
+
return Object.assign(Object.assign({}, args), { method,
|
|
191
|
+
fallbackUrl });
|
|
192
|
+
}
|
|
174
193
|
}
|
|
175
194
|
exports.NetworkCore = NetworkCore;
|
|
176
195
|
const _ensureValidSdkKey = (args) => {
|
|
@@ -193,3 +212,23 @@ function _getErrorMessage(controller, error) {
|
|
|
193
212
|
}
|
|
194
213
|
return 'Unknown Error';
|
|
195
214
|
}
|
|
215
|
+
function _didTimeout(controller) {
|
|
216
|
+
const timeout = (controller === null || controller === void 0 ? void 0 : controller.signal.aborted) &&
|
|
217
|
+
typeof controller.signal.reason === 'string' &&
|
|
218
|
+
controller.signal.reason.includes('Timeout');
|
|
219
|
+
return timeout || false;
|
|
220
|
+
}
|
|
221
|
+
function _tryMarkInitStart(args, attempt) {
|
|
222
|
+
if (!args.isInitialize) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
Diagnostics_1.Diagnostics._markInitNetworkReqStart(args.sdkKey, {
|
|
226
|
+
attempt,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
function _tryMarkInitEnd(args, response, attempt, body, err) {
|
|
230
|
+
if (!args.isInitialize) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
Diagnostics_1.Diagnostics._markInitNetworkReqEnd(args.sdkKey, Diagnostics_1.Diagnostics._getDiagnosticsData(response, attempt, body, err));
|
|
234
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ErrorBoundary } from './ErrorBoundary';
|
|
2
|
+
import { AnyStatsigOptions } from './StatsigOptionsCommon';
|
|
3
|
+
export type FallbackResolverArgs = {
|
|
4
|
+
fallbackUrl: string | null;
|
|
5
|
+
};
|
|
6
|
+
export declare class NetworkFallbackResolver {
|
|
7
|
+
private _fallbackInfo;
|
|
8
|
+
private _errorBoundary;
|
|
9
|
+
private _networkOverrideFunc?;
|
|
10
|
+
constructor(options: AnyStatsigOptions);
|
|
11
|
+
setErrorBoundary(errorBoundary: ErrorBoundary): void;
|
|
12
|
+
tryBumpExpiryTime(sdkKey: string, url: string): void;
|
|
13
|
+
getFallbackUrl(sdkKey: string, url: string): string | null;
|
|
14
|
+
tryFetchUpdatedFallbackInfo(sdkKey: string, url: string, errorMessage: string | null, timedOut: boolean): Promise<boolean>;
|
|
15
|
+
private _fetchFallbackUrl;
|
|
16
|
+
}
|
|
17
|
+
export declare function _isDefaultUrl(url: string): boolean;
|
|
18
|
+
export declare function _isDomainFailure(errorMsg: string | null, timedOut: boolean): boolean;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports._isDomainFailure = exports._isDefaultUrl = exports.NetworkFallbackResolver = void 0;
|
|
13
|
+
const DnsTxtQuery_1 = require("./DnsTxtQuery");
|
|
14
|
+
const Hashing_1 = require("./Hashing");
|
|
15
|
+
const Log_1 = require("./Log");
|
|
16
|
+
const NetworkConfig_1 = require("./NetworkConfig");
|
|
17
|
+
const StorageProvider_1 = require("./StorageProvider");
|
|
18
|
+
const DEFAULT_TTL = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
19
|
+
class NetworkFallbackResolver {
|
|
20
|
+
constructor(options) {
|
|
21
|
+
var _a;
|
|
22
|
+
this._fallbackInfo = null;
|
|
23
|
+
this._errorBoundary = null;
|
|
24
|
+
this._networkOverrideFunc = (_a = options.networkConfig) === null || _a === void 0 ? void 0 : _a.networkOverrideFunc;
|
|
25
|
+
}
|
|
26
|
+
setErrorBoundary(errorBoundary) {
|
|
27
|
+
this._errorBoundary = errorBoundary;
|
|
28
|
+
}
|
|
29
|
+
tryBumpExpiryTime(sdkKey, url) {
|
|
30
|
+
var _a;
|
|
31
|
+
const domainKey = _getDomainKeyFromEndpoint(url);
|
|
32
|
+
if (!domainKey) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const info = (_a = this._fallbackInfo) === null || _a === void 0 ? void 0 : _a[domainKey];
|
|
36
|
+
if (!info) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
info.expiryTime = Date.now() + DEFAULT_TTL;
|
|
40
|
+
_tryWriteFallbackInfoToCache(sdkKey, Object.assign(Object.assign({}, this._fallbackInfo), { [domainKey]: info }));
|
|
41
|
+
}
|
|
42
|
+
getFallbackUrl(sdkKey, url) {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
const domainKey = _getDomainKeyFromEndpoint(url);
|
|
45
|
+
if (!_isDefaultUrl(url) || !domainKey) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
let info = this._fallbackInfo;
|
|
49
|
+
if (info == null) {
|
|
50
|
+
info = (_a = _readFallbackInfoFromCache(sdkKey)) !== null && _a !== void 0 ? _a : {};
|
|
51
|
+
this._fallbackInfo = info;
|
|
52
|
+
}
|
|
53
|
+
const entry = info[domainKey];
|
|
54
|
+
if (!entry || Date.now() > ((_b = entry.expiryTime) !== null && _b !== void 0 ? _b : 0)) {
|
|
55
|
+
delete info[domainKey];
|
|
56
|
+
this._fallbackInfo = info;
|
|
57
|
+
_tryWriteFallbackInfoToCache(sdkKey, this._fallbackInfo);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const endpoint = _extractEndpointForUrl(url);
|
|
61
|
+
if (entry.url) {
|
|
62
|
+
return `https://${entry.url}/${endpoint}`;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
tryFetchUpdatedFallbackInfo(sdkKey, url, errorMessage, timedOut) {
|
|
67
|
+
var _a, _b, _c, _d;
|
|
68
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
try {
|
|
70
|
+
const domainKey = _getDomainKeyFromEndpoint(url);
|
|
71
|
+
if (!_isDomainFailure(errorMessage, timedOut) || !domainKey) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const newUrl = yield this._fetchFallbackUrl(domainKey);
|
|
75
|
+
if (!newUrl) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const newFallbackInfo = {
|
|
79
|
+
url: newUrl,
|
|
80
|
+
expiryTime: Date.now() + DEFAULT_TTL,
|
|
81
|
+
previous: [],
|
|
82
|
+
};
|
|
83
|
+
const previousInfo = (_a = this._fallbackInfo) === null || _a === void 0 ? void 0 : _a[domainKey];
|
|
84
|
+
if (previousInfo) {
|
|
85
|
+
newFallbackInfo.previous.push(...previousInfo.previous);
|
|
86
|
+
}
|
|
87
|
+
if (newFallbackInfo.previous.length > 10) {
|
|
88
|
+
newFallbackInfo.previous = [];
|
|
89
|
+
}
|
|
90
|
+
const previousUrl = (_c = (_b = this._fallbackInfo) === null || _b === void 0 ? void 0 : _b[domainKey]) === null || _c === void 0 ? void 0 : _c.url;
|
|
91
|
+
if (previousUrl != null) {
|
|
92
|
+
newFallbackInfo.previous.push(previousUrl);
|
|
93
|
+
}
|
|
94
|
+
this._fallbackInfo = Object.assign(Object.assign({}, this._fallbackInfo), { [domainKey]: newFallbackInfo });
|
|
95
|
+
_tryWriteFallbackInfoToCache(sdkKey, this._fallbackInfo);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
(_d = this._errorBoundary) === null || _d === void 0 ? void 0 : _d.logError('tryFetchUpdatedFallbackInfo', error);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
_fetchFallbackUrl(domainKey) {
|
|
105
|
+
var _a, _b, _c, _d, _e, _f;
|
|
106
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
const records = yield (0, DnsTxtQuery_1._fetchTxtRecords)((_a = this._networkOverrideFunc) !== null && _a !== void 0 ? _a : fetch);
|
|
108
|
+
if (records.length === 0) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const seen = new Set((_d = (_c = (_b = this._fallbackInfo) === null || _b === void 0 ? void 0 : _b[domainKey]) === null || _c === void 0 ? void 0 : _c.previous) !== null && _d !== void 0 ? _d : []);
|
|
112
|
+
const currentUrl = (_f = (_e = this._fallbackInfo) === null || _e === void 0 ? void 0 : _e[domainKey]) === null || _f === void 0 ? void 0 : _f.url;
|
|
113
|
+
let found = null;
|
|
114
|
+
for (const record of records) {
|
|
115
|
+
const [recordKey, recordUrl] = record.split('=');
|
|
116
|
+
if (!recordUrl || recordKey !== domainKey) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
let url = recordUrl;
|
|
120
|
+
if (recordUrl.endsWith('/')) {
|
|
121
|
+
url = recordUrl.slice(0, -1);
|
|
122
|
+
}
|
|
123
|
+
if (!seen.has(recordUrl) && url !== currentUrl) {
|
|
124
|
+
found = url;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return found;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.NetworkFallbackResolver = NetworkFallbackResolver;
|
|
133
|
+
function _isDefaultUrl(url) {
|
|
134
|
+
for (const key in NetworkConfig_1.NetworkDefault) {
|
|
135
|
+
if (url.startsWith(NetworkConfig_1.NetworkDefault[key])) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
exports._isDefaultUrl = _isDefaultUrl;
|
|
142
|
+
function _isDomainFailure(errorMsg, timedOut) {
|
|
143
|
+
var _a;
|
|
144
|
+
const lowerErrorMsg = (_a = errorMsg === null || errorMsg === void 0 ? void 0 : errorMsg.toLowerCase()) !== null && _a !== void 0 ? _a : '';
|
|
145
|
+
return (timedOut ||
|
|
146
|
+
lowerErrorMsg.includes('uncaught exception') ||
|
|
147
|
+
lowerErrorMsg.includes('failed to fetch') ||
|
|
148
|
+
lowerErrorMsg.includes('networkerror when attempting to fetch resource'));
|
|
149
|
+
}
|
|
150
|
+
exports._isDomainFailure = _isDomainFailure;
|
|
151
|
+
function _getFallbackInfoStorageKey(sdkKey) {
|
|
152
|
+
return `statsig.network_fallback.${(0, Hashing_1._DJB2)(sdkKey)}`;
|
|
153
|
+
}
|
|
154
|
+
function _tryWriteFallbackInfoToCache(sdkKey, info) {
|
|
155
|
+
const hashKey = _getFallbackInfoStorageKey(sdkKey);
|
|
156
|
+
if (!info || Object.keys(info).length === 0) {
|
|
157
|
+
StorageProvider_1.Storage.removeItem(hashKey);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
StorageProvider_1.Storage.setItem(hashKey, JSON.stringify(info));
|
|
161
|
+
}
|
|
162
|
+
function _readFallbackInfoFromCache(sdkKey) {
|
|
163
|
+
const hashKey = _getFallbackInfoStorageKey(sdkKey);
|
|
164
|
+
const data = StorageProvider_1.Storage.getItem(hashKey);
|
|
165
|
+
if (!data) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(data);
|
|
170
|
+
}
|
|
171
|
+
catch (_a) {
|
|
172
|
+
Log_1.Log.error('Failed to parse FallbackInfo');
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function _extractEndpointForUrl(urlString) {
|
|
177
|
+
try {
|
|
178
|
+
const url = new URL(urlString);
|
|
179
|
+
const endpoint = url.pathname.substring(1);
|
|
180
|
+
return endpoint;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
return '';
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function _getDomainKeyFromEndpoint(endpoint) {
|
|
187
|
+
if (endpoint.includes('initialize')) {
|
|
188
|
+
return 'i';
|
|
189
|
+
}
|
|
190
|
+
if (endpoint.includes('rgstr')) {
|
|
191
|
+
return 'e';
|
|
192
|
+
}
|
|
193
|
+
if (endpoint.includes('download_config_specs')) {
|
|
194
|
+
return 'd';
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
package/src/StatsigClientBase.js
CHANGED
|
@@ -36,9 +36,9 @@ class StatsigClientBase {
|
|
|
36
36
|
this._logger = new EventLogger_1.EventLogger(sdkKey, emitter, network, options);
|
|
37
37
|
this._errorBoundary = new ErrorBoundary_1.ErrorBoundary(sdkKey, options, emitter);
|
|
38
38
|
this._errorBoundary.wrap(this);
|
|
39
|
-
this._errorBoundary.wrap(network);
|
|
40
39
|
this._errorBoundary.wrap(adapter);
|
|
41
40
|
this._errorBoundary.wrap(this._logger);
|
|
41
|
+
network.setErrorBoundary(this._errorBoundary);
|
|
42
42
|
this.dataAdapter = adapter;
|
|
43
43
|
this.dataAdapter.attach(sdkKey, options);
|
|
44
44
|
this.storageProvider = StorageProvider_1.Storage;
|
package/src/StatsigMetadata.d.ts
CHANGED
package/src/StatsigMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StatsigMetadataProvider = exports.SDK_VERSION = void 0;
|
|
4
|
-
exports.SDK_VERSION = '3.
|
|
4
|
+
exports.SDK_VERSION = '3.3.0-beta.1';
|
|
5
5
|
let metadata = {
|
|
6
6
|
sdkVersion: exports.SDK_VERSION,
|
|
7
7
|
sdkType: 'js-mono', // js-mono is overwritten by Precomp and OnDevice clients
|