@firebase/remote-config 0.4.10 → 0.4.11
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/esm/index.esm2017.js +1193 -1193
- package/dist/esm/index.esm2017.js.map +1 -1
- package/dist/esm/src/api.d.ts +115 -115
- package/dist/esm/src/api2.d.ts +40 -40
- package/dist/esm/src/client/caching_client.d.ts +45 -45
- package/dist/esm/src/client/remote_config_fetch_client.d.ts +123 -123
- package/dist/esm/src/client/rest_client.d.ts +40 -40
- package/dist/esm/src/client/retrying_client.d.ts +49 -49
- package/dist/esm/src/constants.d.ts +17 -17
- package/dist/esm/src/errors.d.ts +62 -62
- package/dist/esm/src/index.d.ts +14 -14
- package/dist/esm/src/language.d.ts +26 -26
- package/dist/esm/src/public_types.d.ts +128 -128
- package/dist/esm/src/register.d.ts +2 -2
- package/dist/esm/src/remote_config.d.ts +79 -79
- package/dist/esm/src/storage/storage.d.ts +76 -76
- package/dist/esm/src/storage/storage_cache.d.ts +48 -48
- package/dist/esm/src/value.d.ts +26 -26
- package/dist/esm/test/client/caching_client.test.d.ts +17 -17
- package/dist/esm/test/client/rest_client.test.d.ts +17 -17
- package/dist/esm/test/client/retrying_client.test.d.ts +17 -17
- package/dist/esm/test/errors.test.d.ts +17 -17
- package/dist/esm/test/language.test.d.ts +17 -17
- package/dist/esm/test/remote_config.test.d.ts +17 -17
- package/dist/esm/test/setup.d.ts +17 -17
- package/dist/esm/test/storage/storage.test.d.ts +17 -17
- package/dist/esm/test/storage/storage_cache.test.d.ts +17 -17
- package/dist/esm/test/value.test.d.ts +17 -17
- package/dist/index.cjs.js +1193 -1193
- package/dist/index.cjs.js.map +1 -1
- package/dist/src/api.d.ts +115 -115
- package/dist/src/api2.d.ts +40 -40
- package/dist/src/client/caching_client.d.ts +45 -45
- package/dist/src/client/remote_config_fetch_client.d.ts +123 -123
- package/dist/src/client/rest_client.d.ts +40 -40
- package/dist/src/client/retrying_client.d.ts +49 -49
- package/dist/src/constants.d.ts +17 -17
- package/dist/src/errors.d.ts +62 -62
- package/dist/src/index.d.ts +14 -14
- package/dist/src/language.d.ts +26 -26
- package/dist/src/public_types.d.ts +128 -128
- package/dist/src/register.d.ts +2 -2
- package/dist/src/remote_config.d.ts +79 -79
- package/dist/src/storage/storage.d.ts +76 -76
- package/dist/src/storage/storage_cache.d.ts +48 -48
- package/dist/src/value.d.ts +26 -26
- package/dist/test/client/caching_client.test.d.ts +17 -17
- package/dist/test/client/rest_client.test.d.ts +17 -17
- package/dist/test/client/retrying_client.test.d.ts +17 -17
- package/dist/test/errors.test.d.ts +17 -17
- package/dist/test/language.test.d.ts +17 -17
- package/dist/test/remote_config.test.d.ts +17 -17
- package/dist/test/setup.d.ts +17 -17
- package/dist/test/storage/storage.test.d.ts +17 -17
- package/dist/test/storage/storage_cache.test.d.ts +17 -17
- package/dist/test/value.test.d.ts +17 -17
- package/package.json +7 -7
|
@@ -5,1228 +5,1228 @@ import { LogLevel, Logger } from '@firebase/logger';
|
|
|
5
5
|
import '@firebase/installations';
|
|
6
6
|
|
|
7
7
|
const name = "@firebase/remote-config";
|
|
8
|
-
const version = "0.4.
|
|
8
|
+
const version = "0.4.11";
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* @license
|
|
12
|
-
* Copyright 2019 Google LLC
|
|
13
|
-
*
|
|
14
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
15
|
-
* you may not use this file except in compliance with the License.
|
|
16
|
-
* You may obtain a copy of the License at
|
|
17
|
-
*
|
|
18
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
19
|
-
*
|
|
20
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
21
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
22
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
23
|
-
* See the License for the specific language governing permissions and
|
|
24
|
-
* limitations under the License.
|
|
25
|
-
*/
|
|
26
|
-
/**
|
|
27
|
-
* Shims a minimal AbortSignal.
|
|
28
|
-
*
|
|
29
|
-
* <p>AbortController's AbortSignal conveniently decouples fetch timeout logic from other aspects
|
|
30
|
-
* of networking, such as retries. Firebase doesn't use AbortController enough to justify a
|
|
31
|
-
* polyfill recommendation, like we do with the Fetch API, but this minimal shim can easily be
|
|
32
|
-
* swapped out if/when we do.
|
|
33
|
-
*/
|
|
34
|
-
class RemoteConfigAbortSignal {
|
|
35
|
-
constructor() {
|
|
36
|
-
this.listeners = [];
|
|
37
|
-
}
|
|
38
|
-
addEventListener(listener) {
|
|
39
|
-
this.listeners.push(listener);
|
|
40
|
-
}
|
|
41
|
-
abort() {
|
|
42
|
-
this.listeners.forEach(listener => listener());
|
|
43
|
-
}
|
|
10
|
+
/**
|
|
11
|
+
* @license
|
|
12
|
+
* Copyright 2019 Google LLC
|
|
13
|
+
*
|
|
14
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
15
|
+
* you may not use this file except in compliance with the License.
|
|
16
|
+
* You may obtain a copy of the License at
|
|
17
|
+
*
|
|
18
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
19
|
+
*
|
|
20
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
21
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
22
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
23
|
+
* See the License for the specific language governing permissions and
|
|
24
|
+
* limitations under the License.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Shims a minimal AbortSignal.
|
|
28
|
+
*
|
|
29
|
+
* <p>AbortController's AbortSignal conveniently decouples fetch timeout logic from other aspects
|
|
30
|
+
* of networking, such as retries. Firebase doesn't use AbortController enough to justify a
|
|
31
|
+
* polyfill recommendation, like we do with the Fetch API, but this minimal shim can easily be
|
|
32
|
+
* swapped out if/when we do.
|
|
33
|
+
*/
|
|
34
|
+
class RemoteConfigAbortSignal {
|
|
35
|
+
constructor() {
|
|
36
|
+
this.listeners = [];
|
|
37
|
+
}
|
|
38
|
+
addEventListener(listener) {
|
|
39
|
+
this.listeners.push(listener);
|
|
40
|
+
}
|
|
41
|
+
abort() {
|
|
42
|
+
this.listeners.forEach(listener => listener());
|
|
43
|
+
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* @license
|
|
48
|
-
* Copyright 2020 Google LLC
|
|
49
|
-
*
|
|
50
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
51
|
-
* you may not use this file except in compliance with the License.
|
|
52
|
-
* You may obtain a copy of the License at
|
|
53
|
-
*
|
|
54
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
55
|
-
*
|
|
56
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
57
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
58
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
59
|
-
* See the License for the specific language governing permissions and
|
|
60
|
-
* limitations under the License.
|
|
61
|
-
*/
|
|
46
|
+
/**
|
|
47
|
+
* @license
|
|
48
|
+
* Copyright 2020 Google LLC
|
|
49
|
+
*
|
|
50
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
51
|
+
* you may not use this file except in compliance with the License.
|
|
52
|
+
* You may obtain a copy of the License at
|
|
53
|
+
*
|
|
54
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
55
|
+
*
|
|
56
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
57
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
58
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
59
|
+
* See the License for the specific language governing permissions and
|
|
60
|
+
* limitations under the License.
|
|
61
|
+
*/
|
|
62
62
|
const RC_COMPONENT_NAME = 'remote-config';
|
|
63
63
|
|
|
64
|
-
/**
|
|
65
|
-
* @license
|
|
66
|
-
* Copyright 2019 Google LLC
|
|
67
|
-
*
|
|
68
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
69
|
-
* you may not use this file except in compliance with the License.
|
|
70
|
-
* You may obtain a copy of the License at
|
|
71
|
-
*
|
|
72
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
73
|
-
*
|
|
74
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
75
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
76
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
77
|
-
* See the License for the specific language governing permissions and
|
|
78
|
-
* limitations under the License.
|
|
79
|
-
*/
|
|
80
|
-
const ERROR_DESCRIPTION_MAP = {
|
|
81
|
-
["registration-window" /* ErrorCode.REGISTRATION_WINDOW */]: 'Undefined window object. This SDK only supports usage in a browser environment.',
|
|
82
|
-
["registration-project-id" /* ErrorCode.REGISTRATION_PROJECT_ID */]: 'Undefined project identifier. Check Firebase app initialization.',
|
|
83
|
-
["registration-api-key" /* ErrorCode.REGISTRATION_API_KEY */]: 'Undefined API key. Check Firebase app initialization.',
|
|
84
|
-
["registration-app-id" /* ErrorCode.REGISTRATION_APP_ID */]: 'Undefined app identifier. Check Firebase app initialization.',
|
|
85
|
-
["storage-open" /* ErrorCode.STORAGE_OPEN */]: 'Error thrown when opening storage. Original error: {$originalErrorMessage}.',
|
|
86
|
-
["storage-get" /* ErrorCode.STORAGE_GET */]: 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.',
|
|
87
|
-
["storage-set" /* ErrorCode.STORAGE_SET */]: 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.',
|
|
88
|
-
["storage-delete" /* ErrorCode.STORAGE_DELETE */]: 'Error thrown when deleting from storage. Original error: {$originalErrorMessage}.',
|
|
89
|
-
["fetch-client-network" /* ErrorCode.FETCH_NETWORK */]: 'Fetch client failed to connect to a network. Check Internet connection.' +
|
|
90
|
-
' Original error: {$originalErrorMessage}.',
|
|
91
|
-
["fetch-timeout" /* ErrorCode.FETCH_TIMEOUT */]: 'The config fetch request timed out. ' +
|
|
92
|
-
' Configure timeout using "fetchTimeoutMillis" SDK setting.',
|
|
93
|
-
["fetch-throttle" /* ErrorCode.FETCH_THROTTLE */]: 'The config fetch request timed out while in an exponential backoff state.' +
|
|
94
|
-
' Configure timeout using "fetchTimeoutMillis" SDK setting.' +
|
|
95
|
-
' Unix timestamp in milliseconds when fetch request throttling ends: {$throttleEndTimeMillis}.',
|
|
96
|
-
["fetch-client-parse" /* ErrorCode.FETCH_PARSE */]: 'Fetch client could not parse response.' +
|
|
97
|
-
' Original error: {$originalErrorMessage}.',
|
|
98
|
-
["fetch-status" /* ErrorCode.FETCH_STATUS */]: 'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.',
|
|
99
|
-
["indexed-db-unavailable" /* ErrorCode.INDEXED_DB_UNAVAILABLE */]: 'Indexed DB is not supported by current browser'
|
|
100
|
-
};
|
|
101
|
-
const ERROR_FACTORY = new ErrorFactory('remoteconfig' /* service */, 'Remote Config' /* service name */, ERROR_DESCRIPTION_MAP);
|
|
102
|
-
// Note how this is like typeof/instanceof, but for ErrorCode.
|
|
103
|
-
function hasErrorCode(e, errorCode) {
|
|
104
|
-
return e instanceof FirebaseError && e.code.indexOf(errorCode) !== -1;
|
|
64
|
+
/**
|
|
65
|
+
* @license
|
|
66
|
+
* Copyright 2019 Google LLC
|
|
67
|
+
*
|
|
68
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
69
|
+
* you may not use this file except in compliance with the License.
|
|
70
|
+
* You may obtain a copy of the License at
|
|
71
|
+
*
|
|
72
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
73
|
+
*
|
|
74
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
75
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
76
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
77
|
+
* See the License for the specific language governing permissions and
|
|
78
|
+
* limitations under the License.
|
|
79
|
+
*/
|
|
80
|
+
const ERROR_DESCRIPTION_MAP = {
|
|
81
|
+
["registration-window" /* ErrorCode.REGISTRATION_WINDOW */]: 'Undefined window object. This SDK only supports usage in a browser environment.',
|
|
82
|
+
["registration-project-id" /* ErrorCode.REGISTRATION_PROJECT_ID */]: 'Undefined project identifier. Check Firebase app initialization.',
|
|
83
|
+
["registration-api-key" /* ErrorCode.REGISTRATION_API_KEY */]: 'Undefined API key. Check Firebase app initialization.',
|
|
84
|
+
["registration-app-id" /* ErrorCode.REGISTRATION_APP_ID */]: 'Undefined app identifier. Check Firebase app initialization.',
|
|
85
|
+
["storage-open" /* ErrorCode.STORAGE_OPEN */]: 'Error thrown when opening storage. Original error: {$originalErrorMessage}.',
|
|
86
|
+
["storage-get" /* ErrorCode.STORAGE_GET */]: 'Error thrown when reading from storage. Original error: {$originalErrorMessage}.',
|
|
87
|
+
["storage-set" /* ErrorCode.STORAGE_SET */]: 'Error thrown when writing to storage. Original error: {$originalErrorMessage}.',
|
|
88
|
+
["storage-delete" /* ErrorCode.STORAGE_DELETE */]: 'Error thrown when deleting from storage. Original error: {$originalErrorMessage}.',
|
|
89
|
+
["fetch-client-network" /* ErrorCode.FETCH_NETWORK */]: 'Fetch client failed to connect to a network. Check Internet connection.' +
|
|
90
|
+
' Original error: {$originalErrorMessage}.',
|
|
91
|
+
["fetch-timeout" /* ErrorCode.FETCH_TIMEOUT */]: 'The config fetch request timed out. ' +
|
|
92
|
+
' Configure timeout using "fetchTimeoutMillis" SDK setting.',
|
|
93
|
+
["fetch-throttle" /* ErrorCode.FETCH_THROTTLE */]: 'The config fetch request timed out while in an exponential backoff state.' +
|
|
94
|
+
' Configure timeout using "fetchTimeoutMillis" SDK setting.' +
|
|
95
|
+
' Unix timestamp in milliseconds when fetch request throttling ends: {$throttleEndTimeMillis}.',
|
|
96
|
+
["fetch-client-parse" /* ErrorCode.FETCH_PARSE */]: 'Fetch client could not parse response.' +
|
|
97
|
+
' Original error: {$originalErrorMessage}.',
|
|
98
|
+
["fetch-status" /* ErrorCode.FETCH_STATUS */]: 'Fetch server returned an HTTP error status. HTTP status: {$httpStatus}.',
|
|
99
|
+
["indexed-db-unavailable" /* ErrorCode.INDEXED_DB_UNAVAILABLE */]: 'Indexed DB is not supported by current browser'
|
|
100
|
+
};
|
|
101
|
+
const ERROR_FACTORY = new ErrorFactory('remoteconfig' /* service */, 'Remote Config' /* service name */, ERROR_DESCRIPTION_MAP);
|
|
102
|
+
// Note how this is like typeof/instanceof, but for ErrorCode.
|
|
103
|
+
function hasErrorCode(e, errorCode) {
|
|
104
|
+
return e instanceof FirebaseError && e.code.indexOf(errorCode) !== -1;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
/**
|
|
108
|
-
* @license
|
|
109
|
-
* Copyright 2019 Google LLC
|
|
110
|
-
*
|
|
111
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
112
|
-
* you may not use this file except in compliance with the License.
|
|
113
|
-
* You may obtain a copy of the License at
|
|
114
|
-
*
|
|
115
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
116
|
-
*
|
|
117
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
118
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
119
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
120
|
-
* See the License for the specific language governing permissions and
|
|
121
|
-
* limitations under the License.
|
|
122
|
-
*/
|
|
123
|
-
const DEFAULT_VALUE_FOR_BOOLEAN = false;
|
|
124
|
-
const DEFAULT_VALUE_FOR_STRING = '';
|
|
125
|
-
const DEFAULT_VALUE_FOR_NUMBER = 0;
|
|
126
|
-
const BOOLEAN_TRUTHY_VALUES = ['1', 'true', 't', 'yes', 'y', 'on'];
|
|
127
|
-
class Value {
|
|
128
|
-
constructor(_source, _value = DEFAULT_VALUE_FOR_STRING) {
|
|
129
|
-
this._source = _source;
|
|
130
|
-
this._value = _value;
|
|
131
|
-
}
|
|
132
|
-
asString() {
|
|
133
|
-
return this._value;
|
|
134
|
-
}
|
|
135
|
-
asBoolean() {
|
|
136
|
-
if (this._source === 'static') {
|
|
137
|
-
return DEFAULT_VALUE_FOR_BOOLEAN;
|
|
138
|
-
}
|
|
139
|
-
return BOOLEAN_TRUTHY_VALUES.indexOf(this._value.toLowerCase()) >= 0;
|
|
140
|
-
}
|
|
141
|
-
asNumber() {
|
|
142
|
-
if (this._source === 'static') {
|
|
143
|
-
return DEFAULT_VALUE_FOR_NUMBER;
|
|
144
|
-
}
|
|
145
|
-
let num = Number(this._value);
|
|
146
|
-
if (isNaN(num)) {
|
|
147
|
-
num = DEFAULT_VALUE_FOR_NUMBER;
|
|
148
|
-
}
|
|
149
|
-
return num;
|
|
150
|
-
}
|
|
151
|
-
getSource() {
|
|
152
|
-
return this._source;
|
|
153
|
-
}
|
|
107
|
+
/**
|
|
108
|
+
* @license
|
|
109
|
+
* Copyright 2019 Google LLC
|
|
110
|
+
*
|
|
111
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
112
|
+
* you may not use this file except in compliance with the License.
|
|
113
|
+
* You may obtain a copy of the License at
|
|
114
|
+
*
|
|
115
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
116
|
+
*
|
|
117
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
118
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
119
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
120
|
+
* See the License for the specific language governing permissions and
|
|
121
|
+
* limitations under the License.
|
|
122
|
+
*/
|
|
123
|
+
const DEFAULT_VALUE_FOR_BOOLEAN = false;
|
|
124
|
+
const DEFAULT_VALUE_FOR_STRING = '';
|
|
125
|
+
const DEFAULT_VALUE_FOR_NUMBER = 0;
|
|
126
|
+
const BOOLEAN_TRUTHY_VALUES = ['1', 'true', 't', 'yes', 'y', 'on'];
|
|
127
|
+
class Value {
|
|
128
|
+
constructor(_source, _value = DEFAULT_VALUE_FOR_STRING) {
|
|
129
|
+
this._source = _source;
|
|
130
|
+
this._value = _value;
|
|
131
|
+
}
|
|
132
|
+
asString() {
|
|
133
|
+
return this._value;
|
|
134
|
+
}
|
|
135
|
+
asBoolean() {
|
|
136
|
+
if (this._source === 'static') {
|
|
137
|
+
return DEFAULT_VALUE_FOR_BOOLEAN;
|
|
138
|
+
}
|
|
139
|
+
return BOOLEAN_TRUTHY_VALUES.indexOf(this._value.toLowerCase()) >= 0;
|
|
140
|
+
}
|
|
141
|
+
asNumber() {
|
|
142
|
+
if (this._source === 'static') {
|
|
143
|
+
return DEFAULT_VALUE_FOR_NUMBER;
|
|
144
|
+
}
|
|
145
|
+
let num = Number(this._value);
|
|
146
|
+
if (isNaN(num)) {
|
|
147
|
+
num = DEFAULT_VALUE_FOR_NUMBER;
|
|
148
|
+
}
|
|
149
|
+
return num;
|
|
150
|
+
}
|
|
151
|
+
getSource() {
|
|
152
|
+
return this._source;
|
|
153
|
+
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
/**
|
|
157
|
-
* @license
|
|
158
|
-
* Copyright 2020 Google LLC
|
|
159
|
-
*
|
|
160
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
161
|
-
* you may not use this file except in compliance with the License.
|
|
162
|
-
* You may obtain a copy of the License at
|
|
163
|
-
*
|
|
164
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
165
|
-
*
|
|
166
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
167
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
168
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
169
|
-
* See the License for the specific language governing permissions and
|
|
170
|
-
* limitations under the License.
|
|
171
|
-
*/
|
|
172
|
-
/**
|
|
173
|
-
*
|
|
174
|
-
* @param app - The {@link @firebase/app#FirebaseApp} instance.
|
|
175
|
-
* @returns A {@link RemoteConfig} instance.
|
|
176
|
-
*
|
|
177
|
-
* @public
|
|
178
|
-
*/
|
|
179
|
-
function getRemoteConfig(app = getApp()) {
|
|
180
|
-
app = getModularInstance(app);
|
|
181
|
-
const rcProvider = _getProvider(app, RC_COMPONENT_NAME);
|
|
182
|
-
return rcProvider.getImmediate();
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Makes the last fetched config available to the getters.
|
|
186
|
-
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
187
|
-
* @returns A `Promise` which resolves to true if the current call activated the fetched configs.
|
|
188
|
-
* If the fetched configs were already activated, the `Promise` will resolve to false.
|
|
189
|
-
*
|
|
190
|
-
* @public
|
|
191
|
-
*/
|
|
192
|
-
async function activate(remoteConfig) {
|
|
193
|
-
const rc = getModularInstance(remoteConfig);
|
|
194
|
-
const [lastSuccessfulFetchResponse, activeConfigEtag] = await Promise.all([
|
|
195
|
-
rc._storage.getLastSuccessfulFetchResponse(),
|
|
196
|
-
rc._storage.getActiveConfigEtag()
|
|
197
|
-
]);
|
|
198
|
-
if (!lastSuccessfulFetchResponse ||
|
|
199
|
-
!lastSuccessfulFetchResponse.config ||
|
|
200
|
-
!lastSuccessfulFetchResponse.eTag ||
|
|
201
|
-
lastSuccessfulFetchResponse.eTag === activeConfigEtag) {
|
|
202
|
-
// Either there is no successful fetched config, or is the same as current active
|
|
203
|
-
// config.
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
await Promise.all([
|
|
207
|
-
rc._storageCache.setActiveConfig(lastSuccessfulFetchResponse.config),
|
|
208
|
-
rc._storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag)
|
|
209
|
-
]);
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* Ensures the last activated config are available to the getters.
|
|
214
|
-
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
215
|
-
*
|
|
216
|
-
* @returns A `Promise` that resolves when the last activated config is available to the getters.
|
|
217
|
-
* @public
|
|
218
|
-
*/
|
|
219
|
-
function ensureInitialized(remoteConfig) {
|
|
220
|
-
const rc = getModularInstance(remoteConfig);
|
|
221
|
-
if (!rc._initializePromise) {
|
|
222
|
-
rc._initializePromise = rc._storageCache.loadFromStorage().then(() => {
|
|
223
|
-
rc._isInitializationComplete = true;
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
return rc._initializePromise;
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Fetches and caches configuration from the Remote Config service.
|
|
230
|
-
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
231
|
-
* @public
|
|
232
|
-
*/
|
|
233
|
-
async function fetchConfig(remoteConfig) {
|
|
234
|
-
const rc = getModularInstance(remoteConfig);
|
|
235
|
-
// Aborts the request after the given timeout, causing the fetch call to
|
|
236
|
-
// reject with an `AbortError`.
|
|
237
|
-
//
|
|
238
|
-
// <p>Aborting after the request completes is a no-op, so we don't need a
|
|
239
|
-
// corresponding `clearTimeout`.
|
|
240
|
-
//
|
|
241
|
-
// Locating abort logic here because:
|
|
242
|
-
// * it uses a developer setting (timeout)
|
|
243
|
-
// * it applies to all retries (like curl's max-time arg)
|
|
244
|
-
// * it is consistent with the Fetch API's signal input
|
|
245
|
-
const abortSignal = new RemoteConfigAbortSignal();
|
|
246
|
-
setTimeout(async () => {
|
|
247
|
-
// Note a very low delay, eg < 10ms, can elapse before listeners are initialized.
|
|
248
|
-
abortSignal.abort();
|
|
249
|
-
}, rc.settings.fetchTimeoutMillis);
|
|
250
|
-
// Catches *all* errors thrown by client so status can be set consistently.
|
|
251
|
-
try {
|
|
252
|
-
await rc._client.fetch({
|
|
253
|
-
cacheMaxAgeMillis: rc.settings.minimumFetchIntervalMillis,
|
|
254
|
-
signal: abortSignal
|
|
255
|
-
});
|
|
256
|
-
await rc._storageCache.setLastFetchStatus('success');
|
|
257
|
-
}
|
|
258
|
-
catch (e) {
|
|
259
|
-
const lastFetchStatus = hasErrorCode(e, "fetch-throttle" /* ErrorCode.FETCH_THROTTLE */)
|
|
260
|
-
? 'throttle'
|
|
261
|
-
: 'failure';
|
|
262
|
-
await rc._storageCache.setLastFetchStatus(lastFetchStatus);
|
|
263
|
-
throw e;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* Gets all config.
|
|
268
|
-
*
|
|
269
|
-
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
270
|
-
* @returns All config.
|
|
271
|
-
*
|
|
272
|
-
* @public
|
|
273
|
-
*/
|
|
274
|
-
function getAll(remoteConfig) {
|
|
275
|
-
const rc = getModularInstance(remoteConfig);
|
|
276
|
-
return getAllKeys(rc._storageCache.getActiveConfig(), rc.defaultConfig).reduce((allConfigs, key) => {
|
|
277
|
-
allConfigs[key] = getValue(remoteConfig, key);
|
|
278
|
-
return allConfigs;
|
|
279
|
-
}, {});
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Gets the value for the given key as a boolean.
|
|
283
|
-
*
|
|
284
|
-
* Convenience method for calling <code>remoteConfig.getValue(key).asBoolean()</code>.
|
|
285
|
-
*
|
|
286
|
-
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
287
|
-
* @param key - The name of the parameter.
|
|
288
|
-
*
|
|
289
|
-
* @returns The value for the given key as a boolean.
|
|
290
|
-
* @public
|
|
291
|
-
*/
|
|
292
|
-
function getBoolean(remoteConfig, key) {
|
|
293
|
-
return getValue(getModularInstance(remoteConfig), key).asBoolean();
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Gets the value for the given key as a number.
|
|
297
|
-
*
|
|
298
|
-
* Convenience method for calling <code>remoteConfig.getValue(key).asNumber()</code>.
|
|
299
|
-
*
|
|
300
|
-
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
301
|
-
* @param key - The name of the parameter.
|
|
302
|
-
*
|
|
303
|
-
* @returns The value for the given key as a number.
|
|
304
|
-
*
|
|
305
|
-
* @public
|
|
306
|
-
*/
|
|
307
|
-
function getNumber(remoteConfig, key) {
|
|
308
|
-
return getValue(getModularInstance(remoteConfig), key).asNumber();
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Gets the value for the given key as a string.
|
|
312
|
-
* Convenience method for calling <code>remoteConfig.getValue(key).asString()</code>.
|
|
313
|
-
*
|
|
314
|
-
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
315
|
-
* @param key - The name of the parameter.
|
|
316
|
-
*
|
|
317
|
-
* @returns The value for the given key as a string.
|
|
318
|
-
*
|
|
319
|
-
* @public
|
|
320
|
-
*/
|
|
321
|
-
function getString(remoteConfig, key) {
|
|
322
|
-
return getValue(getModularInstance(remoteConfig), key).asString();
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Gets the {@link Value} for the given key.
|
|
326
|
-
*
|
|
327
|
-
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
328
|
-
* @param key - The name of the parameter.
|
|
329
|
-
*
|
|
330
|
-
* @returns The value for the given key.
|
|
331
|
-
*
|
|
332
|
-
* @public
|
|
333
|
-
*/
|
|
334
|
-
function getValue(remoteConfig, key) {
|
|
335
|
-
const rc = getModularInstance(remoteConfig);
|
|
336
|
-
if (!rc._isInitializationComplete) {
|
|
337
|
-
rc._logger.debug(`A value was requested for key "${key}" before SDK initialization completed.` +
|
|
338
|
-
' Await on ensureInitialized if the intent was to get a previously activated value.');
|
|
339
|
-
}
|
|
340
|
-
const activeConfig = rc._storageCache.getActiveConfig();
|
|
341
|
-
if (activeConfig && activeConfig[key] !== undefined) {
|
|
342
|
-
return new Value('remote', activeConfig[key]);
|
|
343
|
-
}
|
|
344
|
-
else if (rc.defaultConfig && rc.defaultConfig[key] !== undefined) {
|
|
345
|
-
return new Value('default', String(rc.defaultConfig[key]));
|
|
346
|
-
}
|
|
347
|
-
rc._logger.debug(`Returning static value for key "${key}".` +
|
|
348
|
-
' Define a default or remote value if this is unintentional.');
|
|
349
|
-
return new Value('static');
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* Defines the log level to use.
|
|
353
|
-
*
|
|
354
|
-
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
355
|
-
* @param logLevel - The log level to set.
|
|
356
|
-
*
|
|
357
|
-
* @public
|
|
358
|
-
*/
|
|
359
|
-
function setLogLevel(remoteConfig, logLevel) {
|
|
360
|
-
const rc = getModularInstance(remoteConfig);
|
|
361
|
-
switch (logLevel) {
|
|
362
|
-
case 'debug':
|
|
363
|
-
rc._logger.logLevel = LogLevel.DEBUG;
|
|
364
|
-
break;
|
|
365
|
-
case 'silent':
|
|
366
|
-
rc._logger.logLevel = LogLevel.SILENT;
|
|
367
|
-
break;
|
|
368
|
-
default:
|
|
369
|
-
rc._logger.logLevel = LogLevel.ERROR;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Dedupes and returns an array of all the keys of the received objects.
|
|
374
|
-
*/
|
|
375
|
-
function getAllKeys(obj1 = {}, obj2 = {}) {
|
|
376
|
-
return Object.keys(Object.assign(Object.assign({}, obj1), obj2));
|
|
156
|
+
/**
|
|
157
|
+
* @license
|
|
158
|
+
* Copyright 2020 Google LLC
|
|
159
|
+
*
|
|
160
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
161
|
+
* you may not use this file except in compliance with the License.
|
|
162
|
+
* You may obtain a copy of the License at
|
|
163
|
+
*
|
|
164
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
165
|
+
*
|
|
166
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
167
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
168
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
169
|
+
* See the License for the specific language governing permissions and
|
|
170
|
+
* limitations under the License.
|
|
171
|
+
*/
|
|
172
|
+
/**
|
|
173
|
+
*
|
|
174
|
+
* @param app - The {@link @firebase/app#FirebaseApp} instance.
|
|
175
|
+
* @returns A {@link RemoteConfig} instance.
|
|
176
|
+
*
|
|
177
|
+
* @public
|
|
178
|
+
*/
|
|
179
|
+
function getRemoteConfig(app = getApp()) {
|
|
180
|
+
app = getModularInstance(app);
|
|
181
|
+
const rcProvider = _getProvider(app, RC_COMPONENT_NAME);
|
|
182
|
+
return rcProvider.getImmediate();
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Makes the last fetched config available to the getters.
|
|
186
|
+
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
187
|
+
* @returns A `Promise` which resolves to true if the current call activated the fetched configs.
|
|
188
|
+
* If the fetched configs were already activated, the `Promise` will resolve to false.
|
|
189
|
+
*
|
|
190
|
+
* @public
|
|
191
|
+
*/
|
|
192
|
+
async function activate(remoteConfig) {
|
|
193
|
+
const rc = getModularInstance(remoteConfig);
|
|
194
|
+
const [lastSuccessfulFetchResponse, activeConfigEtag] = await Promise.all([
|
|
195
|
+
rc._storage.getLastSuccessfulFetchResponse(),
|
|
196
|
+
rc._storage.getActiveConfigEtag()
|
|
197
|
+
]);
|
|
198
|
+
if (!lastSuccessfulFetchResponse ||
|
|
199
|
+
!lastSuccessfulFetchResponse.config ||
|
|
200
|
+
!lastSuccessfulFetchResponse.eTag ||
|
|
201
|
+
lastSuccessfulFetchResponse.eTag === activeConfigEtag) {
|
|
202
|
+
// Either there is no successful fetched config, or is the same as current active
|
|
203
|
+
// config.
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
await Promise.all([
|
|
207
|
+
rc._storageCache.setActiveConfig(lastSuccessfulFetchResponse.config),
|
|
208
|
+
rc._storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag)
|
|
209
|
+
]);
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Ensures the last activated config are available to the getters.
|
|
214
|
+
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
215
|
+
*
|
|
216
|
+
* @returns A `Promise` that resolves when the last activated config is available to the getters.
|
|
217
|
+
* @public
|
|
218
|
+
*/
|
|
219
|
+
function ensureInitialized(remoteConfig) {
|
|
220
|
+
const rc = getModularInstance(remoteConfig);
|
|
221
|
+
if (!rc._initializePromise) {
|
|
222
|
+
rc._initializePromise = rc._storageCache.loadFromStorage().then(() => {
|
|
223
|
+
rc._isInitializationComplete = true;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return rc._initializePromise;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Fetches and caches configuration from the Remote Config service.
|
|
230
|
+
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
231
|
+
* @public
|
|
232
|
+
*/
|
|
233
|
+
async function fetchConfig(remoteConfig) {
|
|
234
|
+
const rc = getModularInstance(remoteConfig);
|
|
235
|
+
// Aborts the request after the given timeout, causing the fetch call to
|
|
236
|
+
// reject with an `AbortError`.
|
|
237
|
+
//
|
|
238
|
+
// <p>Aborting after the request completes is a no-op, so we don't need a
|
|
239
|
+
// corresponding `clearTimeout`.
|
|
240
|
+
//
|
|
241
|
+
// Locating abort logic here because:
|
|
242
|
+
// * it uses a developer setting (timeout)
|
|
243
|
+
// * it applies to all retries (like curl's max-time arg)
|
|
244
|
+
// * it is consistent with the Fetch API's signal input
|
|
245
|
+
const abortSignal = new RemoteConfigAbortSignal();
|
|
246
|
+
setTimeout(async () => {
|
|
247
|
+
// Note a very low delay, eg < 10ms, can elapse before listeners are initialized.
|
|
248
|
+
abortSignal.abort();
|
|
249
|
+
}, rc.settings.fetchTimeoutMillis);
|
|
250
|
+
// Catches *all* errors thrown by client so status can be set consistently.
|
|
251
|
+
try {
|
|
252
|
+
await rc._client.fetch({
|
|
253
|
+
cacheMaxAgeMillis: rc.settings.minimumFetchIntervalMillis,
|
|
254
|
+
signal: abortSignal
|
|
255
|
+
});
|
|
256
|
+
await rc._storageCache.setLastFetchStatus('success');
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
const lastFetchStatus = hasErrorCode(e, "fetch-throttle" /* ErrorCode.FETCH_THROTTLE */)
|
|
260
|
+
? 'throttle'
|
|
261
|
+
: 'failure';
|
|
262
|
+
await rc._storageCache.setLastFetchStatus(lastFetchStatus);
|
|
263
|
+
throw e;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Gets all config.
|
|
268
|
+
*
|
|
269
|
+
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
270
|
+
* @returns All config.
|
|
271
|
+
*
|
|
272
|
+
* @public
|
|
273
|
+
*/
|
|
274
|
+
function getAll(remoteConfig) {
|
|
275
|
+
const rc = getModularInstance(remoteConfig);
|
|
276
|
+
return getAllKeys(rc._storageCache.getActiveConfig(), rc.defaultConfig).reduce((allConfigs, key) => {
|
|
277
|
+
allConfigs[key] = getValue(remoteConfig, key);
|
|
278
|
+
return allConfigs;
|
|
279
|
+
}, {});
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Gets the value for the given key as a boolean.
|
|
283
|
+
*
|
|
284
|
+
* Convenience method for calling <code>remoteConfig.getValue(key).asBoolean()</code>.
|
|
285
|
+
*
|
|
286
|
+
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
287
|
+
* @param key - The name of the parameter.
|
|
288
|
+
*
|
|
289
|
+
* @returns The value for the given key as a boolean.
|
|
290
|
+
* @public
|
|
291
|
+
*/
|
|
292
|
+
function getBoolean(remoteConfig, key) {
|
|
293
|
+
return getValue(getModularInstance(remoteConfig), key).asBoolean();
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Gets the value for the given key as a number.
|
|
297
|
+
*
|
|
298
|
+
* Convenience method for calling <code>remoteConfig.getValue(key).asNumber()</code>.
|
|
299
|
+
*
|
|
300
|
+
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
301
|
+
* @param key - The name of the parameter.
|
|
302
|
+
*
|
|
303
|
+
* @returns The value for the given key as a number.
|
|
304
|
+
*
|
|
305
|
+
* @public
|
|
306
|
+
*/
|
|
307
|
+
function getNumber(remoteConfig, key) {
|
|
308
|
+
return getValue(getModularInstance(remoteConfig), key).asNumber();
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Gets the value for the given key as a string.
|
|
312
|
+
* Convenience method for calling <code>remoteConfig.getValue(key).asString()</code>.
|
|
313
|
+
*
|
|
314
|
+
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
315
|
+
* @param key - The name of the parameter.
|
|
316
|
+
*
|
|
317
|
+
* @returns The value for the given key as a string.
|
|
318
|
+
*
|
|
319
|
+
* @public
|
|
320
|
+
*/
|
|
321
|
+
function getString(remoteConfig, key) {
|
|
322
|
+
return getValue(getModularInstance(remoteConfig), key).asString();
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Gets the {@link Value} for the given key.
|
|
326
|
+
*
|
|
327
|
+
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
328
|
+
* @param key - The name of the parameter.
|
|
329
|
+
*
|
|
330
|
+
* @returns The value for the given key.
|
|
331
|
+
*
|
|
332
|
+
* @public
|
|
333
|
+
*/
|
|
334
|
+
function getValue(remoteConfig, key) {
|
|
335
|
+
const rc = getModularInstance(remoteConfig);
|
|
336
|
+
if (!rc._isInitializationComplete) {
|
|
337
|
+
rc._logger.debug(`A value was requested for key "${key}" before SDK initialization completed.` +
|
|
338
|
+
' Await on ensureInitialized if the intent was to get a previously activated value.');
|
|
339
|
+
}
|
|
340
|
+
const activeConfig = rc._storageCache.getActiveConfig();
|
|
341
|
+
if (activeConfig && activeConfig[key] !== undefined) {
|
|
342
|
+
return new Value('remote', activeConfig[key]);
|
|
343
|
+
}
|
|
344
|
+
else if (rc.defaultConfig && rc.defaultConfig[key] !== undefined) {
|
|
345
|
+
return new Value('default', String(rc.defaultConfig[key]));
|
|
346
|
+
}
|
|
347
|
+
rc._logger.debug(`Returning static value for key "${key}".` +
|
|
348
|
+
' Define a default or remote value if this is unintentional.');
|
|
349
|
+
return new Value('static');
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Defines the log level to use.
|
|
353
|
+
*
|
|
354
|
+
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
355
|
+
* @param logLevel - The log level to set.
|
|
356
|
+
*
|
|
357
|
+
* @public
|
|
358
|
+
*/
|
|
359
|
+
function setLogLevel(remoteConfig, logLevel) {
|
|
360
|
+
const rc = getModularInstance(remoteConfig);
|
|
361
|
+
switch (logLevel) {
|
|
362
|
+
case 'debug':
|
|
363
|
+
rc._logger.logLevel = LogLevel.DEBUG;
|
|
364
|
+
break;
|
|
365
|
+
case 'silent':
|
|
366
|
+
rc._logger.logLevel = LogLevel.SILENT;
|
|
367
|
+
break;
|
|
368
|
+
default:
|
|
369
|
+
rc._logger.logLevel = LogLevel.ERROR;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Dedupes and returns an array of all the keys of the received objects.
|
|
374
|
+
*/
|
|
375
|
+
function getAllKeys(obj1 = {}, obj2 = {}) {
|
|
376
|
+
return Object.keys(Object.assign(Object.assign({}, obj1), obj2));
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
-
/**
|
|
380
|
-
* @license
|
|
381
|
-
* Copyright 2019 Google LLC
|
|
382
|
-
*
|
|
383
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
384
|
-
* you may not use this file except in compliance with the License.
|
|
385
|
-
* You may obtain a copy of the License at
|
|
386
|
-
*
|
|
387
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
388
|
-
*
|
|
389
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
390
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
391
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
392
|
-
* See the License for the specific language governing permissions and
|
|
393
|
-
* limitations under the License.
|
|
394
|
-
*/
|
|
395
|
-
/**
|
|
396
|
-
* Implements the {@link RemoteConfigClient} abstraction with success response caching.
|
|
397
|
-
*
|
|
398
|
-
* <p>Comparable to the browser's Cache API for responses, but the Cache API requires a Service
|
|
399
|
-
* Worker, which requires HTTPS, which would significantly complicate SDK installation. Also, the
|
|
400
|
-
* Cache API doesn't support matching entries by time.
|
|
401
|
-
*/
|
|
402
|
-
class CachingClient {
|
|
403
|
-
constructor(client, storage, storageCache, logger) {
|
|
404
|
-
this.client = client;
|
|
405
|
-
this.storage = storage;
|
|
406
|
-
this.storageCache = storageCache;
|
|
407
|
-
this.logger = logger;
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Returns true if the age of the cached fetched configs is less than or equal to
|
|
411
|
-
* {@link Settings#minimumFetchIntervalInSeconds}.
|
|
412
|
-
*
|
|
413
|
-
* <p>This is comparable to passing `headers = { 'Cache-Control': max-age <maxAge> }` to the
|
|
414
|
-
* native Fetch API.
|
|
415
|
-
*
|
|
416
|
-
* <p>Visible for testing.
|
|
417
|
-
*/
|
|
418
|
-
isCachedDataFresh(cacheMaxAgeMillis, lastSuccessfulFetchTimestampMillis) {
|
|
419
|
-
// Cache can only be fresh if it's populated.
|
|
420
|
-
if (!lastSuccessfulFetchTimestampMillis) {
|
|
421
|
-
this.logger.debug('Config fetch cache check. Cache unpopulated.');
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
// Calculates age of cache entry.
|
|
425
|
-
const cacheAgeMillis = Date.now() - lastSuccessfulFetchTimestampMillis;
|
|
426
|
-
const isCachedDataFresh = cacheAgeMillis <= cacheMaxAgeMillis;
|
|
427
|
-
this.logger.debug('Config fetch cache check.' +
|
|
428
|
-
` Cache age millis: ${cacheAgeMillis}.` +
|
|
429
|
-
` Cache max age millis (minimumFetchIntervalMillis setting): ${cacheMaxAgeMillis}.` +
|
|
430
|
-
` Is cache hit: ${isCachedDataFresh}.`);
|
|
431
|
-
return isCachedDataFresh;
|
|
432
|
-
}
|
|
433
|
-
async fetch(request) {
|
|
434
|
-
// Reads from persisted storage to avoid cache miss if callers don't wait on initialization.
|
|
435
|
-
const [lastSuccessfulFetchTimestampMillis, lastSuccessfulFetchResponse] = await Promise.all([
|
|
436
|
-
this.storage.getLastSuccessfulFetchTimestampMillis(),
|
|
437
|
-
this.storage.getLastSuccessfulFetchResponse()
|
|
438
|
-
]);
|
|
439
|
-
// Exits early on cache hit.
|
|
440
|
-
if (lastSuccessfulFetchResponse &&
|
|
441
|
-
this.isCachedDataFresh(request.cacheMaxAgeMillis, lastSuccessfulFetchTimestampMillis)) {
|
|
442
|
-
return lastSuccessfulFetchResponse;
|
|
443
|
-
}
|
|
444
|
-
// Deviates from pure decorator by not honoring a passed ETag since we don't have a public API
|
|
445
|
-
// that allows the caller to pass an ETag.
|
|
446
|
-
request.eTag =
|
|
447
|
-
lastSuccessfulFetchResponse && lastSuccessfulFetchResponse.eTag;
|
|
448
|
-
// Falls back to service on cache miss.
|
|
449
|
-
const response = await this.client.fetch(request);
|
|
450
|
-
// Fetch throws for non-success responses, so success is guaranteed here.
|
|
451
|
-
const storageOperations = [
|
|
452
|
-
// Uses write-through cache for consistency with synchronous public API.
|
|
453
|
-
this.storageCache.setLastSuccessfulFetchTimestampMillis(Date.now())
|
|
454
|
-
];
|
|
455
|
-
if (response.status === 200) {
|
|
456
|
-
// Caches response only if it has changed, ie non-304 responses.
|
|
457
|
-
storageOperations.push(this.storage.setLastSuccessfulFetchResponse(response));
|
|
458
|
-
}
|
|
459
|
-
await Promise.all(storageOperations);
|
|
460
|
-
return response;
|
|
461
|
-
}
|
|
379
|
+
/**
|
|
380
|
+
* @license
|
|
381
|
+
* Copyright 2019 Google LLC
|
|
382
|
+
*
|
|
383
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
384
|
+
* you may not use this file except in compliance with the License.
|
|
385
|
+
* You may obtain a copy of the License at
|
|
386
|
+
*
|
|
387
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
388
|
+
*
|
|
389
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
390
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
391
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
392
|
+
* See the License for the specific language governing permissions and
|
|
393
|
+
* limitations under the License.
|
|
394
|
+
*/
|
|
395
|
+
/**
|
|
396
|
+
* Implements the {@link RemoteConfigClient} abstraction with success response caching.
|
|
397
|
+
*
|
|
398
|
+
* <p>Comparable to the browser's Cache API for responses, but the Cache API requires a Service
|
|
399
|
+
* Worker, which requires HTTPS, which would significantly complicate SDK installation. Also, the
|
|
400
|
+
* Cache API doesn't support matching entries by time.
|
|
401
|
+
*/
|
|
402
|
+
class CachingClient {
|
|
403
|
+
constructor(client, storage, storageCache, logger) {
|
|
404
|
+
this.client = client;
|
|
405
|
+
this.storage = storage;
|
|
406
|
+
this.storageCache = storageCache;
|
|
407
|
+
this.logger = logger;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Returns true if the age of the cached fetched configs is less than or equal to
|
|
411
|
+
* {@link Settings#minimumFetchIntervalInSeconds}.
|
|
412
|
+
*
|
|
413
|
+
* <p>This is comparable to passing `headers = { 'Cache-Control': max-age <maxAge> }` to the
|
|
414
|
+
* native Fetch API.
|
|
415
|
+
*
|
|
416
|
+
* <p>Visible for testing.
|
|
417
|
+
*/
|
|
418
|
+
isCachedDataFresh(cacheMaxAgeMillis, lastSuccessfulFetchTimestampMillis) {
|
|
419
|
+
// Cache can only be fresh if it's populated.
|
|
420
|
+
if (!lastSuccessfulFetchTimestampMillis) {
|
|
421
|
+
this.logger.debug('Config fetch cache check. Cache unpopulated.');
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
// Calculates age of cache entry.
|
|
425
|
+
const cacheAgeMillis = Date.now() - lastSuccessfulFetchTimestampMillis;
|
|
426
|
+
const isCachedDataFresh = cacheAgeMillis <= cacheMaxAgeMillis;
|
|
427
|
+
this.logger.debug('Config fetch cache check.' +
|
|
428
|
+
` Cache age millis: ${cacheAgeMillis}.` +
|
|
429
|
+
` Cache max age millis (minimumFetchIntervalMillis setting): ${cacheMaxAgeMillis}.` +
|
|
430
|
+
` Is cache hit: ${isCachedDataFresh}.`);
|
|
431
|
+
return isCachedDataFresh;
|
|
432
|
+
}
|
|
433
|
+
async fetch(request) {
|
|
434
|
+
// Reads from persisted storage to avoid cache miss if callers don't wait on initialization.
|
|
435
|
+
const [lastSuccessfulFetchTimestampMillis, lastSuccessfulFetchResponse] = await Promise.all([
|
|
436
|
+
this.storage.getLastSuccessfulFetchTimestampMillis(),
|
|
437
|
+
this.storage.getLastSuccessfulFetchResponse()
|
|
438
|
+
]);
|
|
439
|
+
// Exits early on cache hit.
|
|
440
|
+
if (lastSuccessfulFetchResponse &&
|
|
441
|
+
this.isCachedDataFresh(request.cacheMaxAgeMillis, lastSuccessfulFetchTimestampMillis)) {
|
|
442
|
+
return lastSuccessfulFetchResponse;
|
|
443
|
+
}
|
|
444
|
+
// Deviates from pure decorator by not honoring a passed ETag since we don't have a public API
|
|
445
|
+
// that allows the caller to pass an ETag.
|
|
446
|
+
request.eTag =
|
|
447
|
+
lastSuccessfulFetchResponse && lastSuccessfulFetchResponse.eTag;
|
|
448
|
+
// Falls back to service on cache miss.
|
|
449
|
+
const response = await this.client.fetch(request);
|
|
450
|
+
// Fetch throws for non-success responses, so success is guaranteed here.
|
|
451
|
+
const storageOperations = [
|
|
452
|
+
// Uses write-through cache for consistency with synchronous public API.
|
|
453
|
+
this.storageCache.setLastSuccessfulFetchTimestampMillis(Date.now())
|
|
454
|
+
];
|
|
455
|
+
if (response.status === 200) {
|
|
456
|
+
// Caches response only if it has changed, ie non-304 responses.
|
|
457
|
+
storageOperations.push(this.storage.setLastSuccessfulFetchResponse(response));
|
|
458
|
+
}
|
|
459
|
+
await Promise.all(storageOperations);
|
|
460
|
+
return response;
|
|
461
|
+
}
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
-
/**
|
|
465
|
-
* @license
|
|
466
|
-
* Copyright 2019 Google LLC
|
|
467
|
-
*
|
|
468
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
469
|
-
* you may not use this file except in compliance with the License.
|
|
470
|
-
* You may obtain a copy of the License at
|
|
471
|
-
*
|
|
472
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
473
|
-
*
|
|
474
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
475
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
476
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
477
|
-
* See the License for the specific language governing permissions and
|
|
478
|
-
* limitations under the License.
|
|
479
|
-
*/
|
|
480
|
-
/**
|
|
481
|
-
* Attempts to get the most accurate browser language setting.
|
|
482
|
-
*
|
|
483
|
-
* <p>Adapted from getUserLanguage in packages/auth/src/utils.js for TypeScript.
|
|
484
|
-
*
|
|
485
|
-
* <p>Defers default language specification to server logic for consistency.
|
|
486
|
-
*
|
|
487
|
-
* @param navigatorLanguage Enables tests to override read-only {@link NavigatorLanguage}.
|
|
488
|
-
*/
|
|
489
|
-
function getUserLanguage(navigatorLanguage = navigator) {
|
|
490
|
-
return (
|
|
491
|
-
// Most reliable, but only supported in Chrome/Firefox.
|
|
492
|
-
(navigatorLanguage.languages && navigatorLanguage.languages[0]) ||
|
|
493
|
-
// Supported in most browsers, but returns the language of the browser
|
|
494
|
-
// UI, not the language set in browser settings.
|
|
495
|
-
navigatorLanguage.language
|
|
496
|
-
// Polyfill otherwise.
|
|
497
|
-
);
|
|
464
|
+
/**
|
|
465
|
+
* @license
|
|
466
|
+
* Copyright 2019 Google LLC
|
|
467
|
+
*
|
|
468
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
469
|
+
* you may not use this file except in compliance with the License.
|
|
470
|
+
* You may obtain a copy of the License at
|
|
471
|
+
*
|
|
472
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
473
|
+
*
|
|
474
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
475
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
476
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
477
|
+
* See the License for the specific language governing permissions and
|
|
478
|
+
* limitations under the License.
|
|
479
|
+
*/
|
|
480
|
+
/**
|
|
481
|
+
* Attempts to get the most accurate browser language setting.
|
|
482
|
+
*
|
|
483
|
+
* <p>Adapted from getUserLanguage in packages/auth/src/utils.js for TypeScript.
|
|
484
|
+
*
|
|
485
|
+
* <p>Defers default language specification to server logic for consistency.
|
|
486
|
+
*
|
|
487
|
+
* @param navigatorLanguage Enables tests to override read-only {@link NavigatorLanguage}.
|
|
488
|
+
*/
|
|
489
|
+
function getUserLanguage(navigatorLanguage = navigator) {
|
|
490
|
+
return (
|
|
491
|
+
// Most reliable, but only supported in Chrome/Firefox.
|
|
492
|
+
(navigatorLanguage.languages && navigatorLanguage.languages[0]) ||
|
|
493
|
+
// Supported in most browsers, but returns the language of the browser
|
|
494
|
+
// UI, not the language set in browser settings.
|
|
495
|
+
navigatorLanguage.language
|
|
496
|
+
// Polyfill otherwise.
|
|
497
|
+
);
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
/**
|
|
501
|
-
* @license
|
|
502
|
-
* Copyright 2019 Google LLC
|
|
503
|
-
*
|
|
504
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
505
|
-
* you may not use this file except in compliance with the License.
|
|
506
|
-
* You may obtain a copy of the License at
|
|
507
|
-
*
|
|
508
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
509
|
-
*
|
|
510
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
511
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
512
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
513
|
-
* See the License for the specific language governing permissions and
|
|
514
|
-
* limitations under the License.
|
|
515
|
-
*/
|
|
516
|
-
/**
|
|
517
|
-
* Implements the Client abstraction for the Remote Config REST API.
|
|
518
|
-
*/
|
|
519
|
-
class RestClient {
|
|
520
|
-
constructor(firebaseInstallations, sdkVersion, namespace, projectId, apiKey, appId) {
|
|
521
|
-
this.firebaseInstallations = firebaseInstallations;
|
|
522
|
-
this.sdkVersion = sdkVersion;
|
|
523
|
-
this.namespace = namespace;
|
|
524
|
-
this.projectId = projectId;
|
|
525
|
-
this.apiKey = apiKey;
|
|
526
|
-
this.appId = appId;
|
|
527
|
-
}
|
|
528
|
-
/**
|
|
529
|
-
* Fetches from the Remote Config REST API.
|
|
530
|
-
*
|
|
531
|
-
* @throws a {@link ErrorCode.FETCH_NETWORK} error if {@link GlobalFetch#fetch} can't
|
|
532
|
-
* connect to the network.
|
|
533
|
-
* @throws a {@link ErrorCode.FETCH_PARSE} error if {@link Response#json} can't parse the
|
|
534
|
-
* fetch response.
|
|
535
|
-
* @throws a {@link ErrorCode.FETCH_STATUS} error if the service returns an HTTP error status.
|
|
536
|
-
*/
|
|
537
|
-
async fetch(request) {
|
|
538
|
-
const [installationId, installationToken] = await Promise.all([
|
|
539
|
-
this.firebaseInstallations.getId(),
|
|
540
|
-
this.firebaseInstallations.getToken()
|
|
541
|
-
]);
|
|
542
|
-
const urlBase = window.FIREBASE_REMOTE_CONFIG_URL_BASE ||
|
|
543
|
-
'https://firebaseremoteconfig.googleapis.com';
|
|
544
|
-
const url = `${urlBase}/v1/projects/${this.projectId}/namespaces/${this.namespace}:fetch?key=${this.apiKey}`;
|
|
545
|
-
const headers = {
|
|
546
|
-
'Content-Type': 'application/json',
|
|
547
|
-
'Content-Encoding': 'gzip',
|
|
548
|
-
// Deviates from pure decorator by not passing max-age header since we don't currently have
|
|
549
|
-
// service behavior using that header.
|
|
550
|
-
'If-None-Match': request.eTag || '*'
|
|
551
|
-
};
|
|
552
|
-
const requestBody = {
|
|
553
|
-
/* eslint-disable camelcase */
|
|
554
|
-
sdk_version: this.sdkVersion,
|
|
555
|
-
app_instance_id: installationId,
|
|
556
|
-
app_instance_id_token: installationToken,
|
|
557
|
-
app_id: this.appId,
|
|
558
|
-
language_code: getUserLanguage()
|
|
559
|
-
/* eslint-enable camelcase */
|
|
560
|
-
};
|
|
561
|
-
const options = {
|
|
562
|
-
method: 'POST',
|
|
563
|
-
headers,
|
|
564
|
-
body: JSON.stringify(requestBody)
|
|
565
|
-
};
|
|
566
|
-
// This logic isn't REST-specific, but shimming abort logic isn't worth another decorator.
|
|
567
|
-
const fetchPromise = fetch(url, options);
|
|
568
|
-
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
569
|
-
// Maps async event listener to Promise API.
|
|
570
|
-
request.signal.addEventListener(() => {
|
|
571
|
-
// Emulates https://heycam.github.io/webidl/#aborterror
|
|
572
|
-
const error = new Error('The operation was aborted.');
|
|
573
|
-
error.name = 'AbortError';
|
|
574
|
-
reject(error);
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
let response;
|
|
578
|
-
try {
|
|
579
|
-
await Promise.race([fetchPromise, timeoutPromise]);
|
|
580
|
-
response = await fetchPromise;
|
|
581
|
-
}
|
|
582
|
-
catch (originalError) {
|
|
583
|
-
let errorCode = "fetch-client-network" /* ErrorCode.FETCH_NETWORK */;
|
|
584
|
-
if ((originalError === null || originalError === void 0 ? void 0 : originalError.name) === 'AbortError') {
|
|
585
|
-
errorCode = "fetch-timeout" /* ErrorCode.FETCH_TIMEOUT */;
|
|
586
|
-
}
|
|
587
|
-
throw ERROR_FACTORY.create(errorCode, {
|
|
588
|
-
originalErrorMessage: originalError === null || originalError === void 0 ? void 0 : originalError.message
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
let status = response.status;
|
|
592
|
-
// Normalizes nullable header to optional.
|
|
593
|
-
const responseEtag = response.headers.get('ETag') || undefined;
|
|
594
|
-
let config;
|
|
595
|
-
let state;
|
|
596
|
-
// JSON parsing throws SyntaxError if the response body isn't a JSON string.
|
|
597
|
-
// Requesting application/json and checking for a 200 ensures there's JSON data.
|
|
598
|
-
if (response.status === 200) {
|
|
599
|
-
let responseBody;
|
|
600
|
-
try {
|
|
601
|
-
responseBody = await response.json();
|
|
602
|
-
}
|
|
603
|
-
catch (originalError) {
|
|
604
|
-
throw ERROR_FACTORY.create("fetch-client-parse" /* ErrorCode.FETCH_PARSE */, {
|
|
605
|
-
originalErrorMessage: originalError === null || originalError === void 0 ? void 0 : originalError.message
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
config = responseBody['entries'];
|
|
609
|
-
state = responseBody['state'];
|
|
610
|
-
}
|
|
611
|
-
// Normalizes based on legacy state.
|
|
612
|
-
if (state === 'INSTANCE_STATE_UNSPECIFIED') {
|
|
613
|
-
status = 500;
|
|
614
|
-
}
|
|
615
|
-
else if (state === 'NO_CHANGE') {
|
|
616
|
-
status = 304;
|
|
617
|
-
}
|
|
618
|
-
else if (state === 'NO_TEMPLATE' || state === 'EMPTY_CONFIG') {
|
|
619
|
-
// These cases can be fixed remotely, so normalize to safe value.
|
|
620
|
-
config = {};
|
|
621
|
-
}
|
|
622
|
-
// Normalize to exception-based control flow for non-success cases.
|
|
623
|
-
// Encapsulates HTTP specifics in this class as much as possible. Status is still the best for
|
|
624
|
-
// differentiating success states (200 from 304; the state body param is undefined in a
|
|
625
|
-
// standard 304).
|
|
626
|
-
if (status !== 304 && status !== 200) {
|
|
627
|
-
throw ERROR_FACTORY.create("fetch-status" /* ErrorCode.FETCH_STATUS */, {
|
|
628
|
-
httpStatus: status
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
return { status, eTag: responseEtag, config };
|
|
632
|
-
}
|
|
500
|
+
/**
|
|
501
|
+
* @license
|
|
502
|
+
* Copyright 2019 Google LLC
|
|
503
|
+
*
|
|
504
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
505
|
+
* you may not use this file except in compliance with the License.
|
|
506
|
+
* You may obtain a copy of the License at
|
|
507
|
+
*
|
|
508
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
509
|
+
*
|
|
510
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
511
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
512
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
513
|
+
* See the License for the specific language governing permissions and
|
|
514
|
+
* limitations under the License.
|
|
515
|
+
*/
|
|
516
|
+
/**
|
|
517
|
+
* Implements the Client abstraction for the Remote Config REST API.
|
|
518
|
+
*/
|
|
519
|
+
class RestClient {
|
|
520
|
+
constructor(firebaseInstallations, sdkVersion, namespace, projectId, apiKey, appId) {
|
|
521
|
+
this.firebaseInstallations = firebaseInstallations;
|
|
522
|
+
this.sdkVersion = sdkVersion;
|
|
523
|
+
this.namespace = namespace;
|
|
524
|
+
this.projectId = projectId;
|
|
525
|
+
this.apiKey = apiKey;
|
|
526
|
+
this.appId = appId;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Fetches from the Remote Config REST API.
|
|
530
|
+
*
|
|
531
|
+
* @throws a {@link ErrorCode.FETCH_NETWORK} error if {@link GlobalFetch#fetch} can't
|
|
532
|
+
* connect to the network.
|
|
533
|
+
* @throws a {@link ErrorCode.FETCH_PARSE} error if {@link Response#json} can't parse the
|
|
534
|
+
* fetch response.
|
|
535
|
+
* @throws a {@link ErrorCode.FETCH_STATUS} error if the service returns an HTTP error status.
|
|
536
|
+
*/
|
|
537
|
+
async fetch(request) {
|
|
538
|
+
const [installationId, installationToken] = await Promise.all([
|
|
539
|
+
this.firebaseInstallations.getId(),
|
|
540
|
+
this.firebaseInstallations.getToken()
|
|
541
|
+
]);
|
|
542
|
+
const urlBase = window.FIREBASE_REMOTE_CONFIG_URL_BASE ||
|
|
543
|
+
'https://firebaseremoteconfig.googleapis.com';
|
|
544
|
+
const url = `${urlBase}/v1/projects/${this.projectId}/namespaces/${this.namespace}:fetch?key=${this.apiKey}`;
|
|
545
|
+
const headers = {
|
|
546
|
+
'Content-Type': 'application/json',
|
|
547
|
+
'Content-Encoding': 'gzip',
|
|
548
|
+
// Deviates from pure decorator by not passing max-age header since we don't currently have
|
|
549
|
+
// service behavior using that header.
|
|
550
|
+
'If-None-Match': request.eTag || '*'
|
|
551
|
+
};
|
|
552
|
+
const requestBody = {
|
|
553
|
+
/* eslint-disable camelcase */
|
|
554
|
+
sdk_version: this.sdkVersion,
|
|
555
|
+
app_instance_id: installationId,
|
|
556
|
+
app_instance_id_token: installationToken,
|
|
557
|
+
app_id: this.appId,
|
|
558
|
+
language_code: getUserLanguage()
|
|
559
|
+
/* eslint-enable camelcase */
|
|
560
|
+
};
|
|
561
|
+
const options = {
|
|
562
|
+
method: 'POST',
|
|
563
|
+
headers,
|
|
564
|
+
body: JSON.stringify(requestBody)
|
|
565
|
+
};
|
|
566
|
+
// This logic isn't REST-specific, but shimming abort logic isn't worth another decorator.
|
|
567
|
+
const fetchPromise = fetch(url, options);
|
|
568
|
+
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
569
|
+
// Maps async event listener to Promise API.
|
|
570
|
+
request.signal.addEventListener(() => {
|
|
571
|
+
// Emulates https://heycam.github.io/webidl/#aborterror
|
|
572
|
+
const error = new Error('The operation was aborted.');
|
|
573
|
+
error.name = 'AbortError';
|
|
574
|
+
reject(error);
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
let response;
|
|
578
|
+
try {
|
|
579
|
+
await Promise.race([fetchPromise, timeoutPromise]);
|
|
580
|
+
response = await fetchPromise;
|
|
581
|
+
}
|
|
582
|
+
catch (originalError) {
|
|
583
|
+
let errorCode = "fetch-client-network" /* ErrorCode.FETCH_NETWORK */;
|
|
584
|
+
if ((originalError === null || originalError === void 0 ? void 0 : originalError.name) === 'AbortError') {
|
|
585
|
+
errorCode = "fetch-timeout" /* ErrorCode.FETCH_TIMEOUT */;
|
|
586
|
+
}
|
|
587
|
+
throw ERROR_FACTORY.create(errorCode, {
|
|
588
|
+
originalErrorMessage: originalError === null || originalError === void 0 ? void 0 : originalError.message
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
let status = response.status;
|
|
592
|
+
// Normalizes nullable header to optional.
|
|
593
|
+
const responseEtag = response.headers.get('ETag') || undefined;
|
|
594
|
+
let config;
|
|
595
|
+
let state;
|
|
596
|
+
// JSON parsing throws SyntaxError if the response body isn't a JSON string.
|
|
597
|
+
// Requesting application/json and checking for a 200 ensures there's JSON data.
|
|
598
|
+
if (response.status === 200) {
|
|
599
|
+
let responseBody;
|
|
600
|
+
try {
|
|
601
|
+
responseBody = await response.json();
|
|
602
|
+
}
|
|
603
|
+
catch (originalError) {
|
|
604
|
+
throw ERROR_FACTORY.create("fetch-client-parse" /* ErrorCode.FETCH_PARSE */, {
|
|
605
|
+
originalErrorMessage: originalError === null || originalError === void 0 ? void 0 : originalError.message
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
config = responseBody['entries'];
|
|
609
|
+
state = responseBody['state'];
|
|
610
|
+
}
|
|
611
|
+
// Normalizes based on legacy state.
|
|
612
|
+
if (state === 'INSTANCE_STATE_UNSPECIFIED') {
|
|
613
|
+
status = 500;
|
|
614
|
+
}
|
|
615
|
+
else if (state === 'NO_CHANGE') {
|
|
616
|
+
status = 304;
|
|
617
|
+
}
|
|
618
|
+
else if (state === 'NO_TEMPLATE' || state === 'EMPTY_CONFIG') {
|
|
619
|
+
// These cases can be fixed remotely, so normalize to safe value.
|
|
620
|
+
config = {};
|
|
621
|
+
}
|
|
622
|
+
// Normalize to exception-based control flow for non-success cases.
|
|
623
|
+
// Encapsulates HTTP specifics in this class as much as possible. Status is still the best for
|
|
624
|
+
// differentiating success states (200 from 304; the state body param is undefined in a
|
|
625
|
+
// standard 304).
|
|
626
|
+
if (status !== 304 && status !== 200) {
|
|
627
|
+
throw ERROR_FACTORY.create("fetch-status" /* ErrorCode.FETCH_STATUS */, {
|
|
628
|
+
httpStatus: status
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
return { status, eTag: responseEtag, config };
|
|
632
|
+
}
|
|
633
633
|
}
|
|
634
634
|
|
|
635
|
-
/**
|
|
636
|
-
* @license
|
|
637
|
-
* Copyright 2019 Google LLC
|
|
638
|
-
*
|
|
639
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
640
|
-
* you may not use this file except in compliance with the License.
|
|
641
|
-
* You may obtain a copy of the License at
|
|
642
|
-
*
|
|
643
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
644
|
-
*
|
|
645
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
646
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
647
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
648
|
-
* See the License for the specific language governing permissions and
|
|
649
|
-
* limitations under the License.
|
|
650
|
-
*/
|
|
651
|
-
/**
|
|
652
|
-
* Supports waiting on a backoff by:
|
|
653
|
-
*
|
|
654
|
-
* <ul>
|
|
655
|
-
* <li>Promisifying setTimeout, so we can set a timeout in our Promise chain</li>
|
|
656
|
-
* <li>Listening on a signal bus for abort events, just like the Fetch API</li>
|
|
657
|
-
* <li>Failing in the same way the Fetch API fails, so timing out a live request and a throttled
|
|
658
|
-
* request appear the same.</li>
|
|
659
|
-
* </ul>
|
|
660
|
-
*
|
|
661
|
-
* <p>Visible for testing.
|
|
662
|
-
*/
|
|
663
|
-
function setAbortableTimeout(signal, throttleEndTimeMillis) {
|
|
664
|
-
return new Promise((resolve, reject) => {
|
|
665
|
-
// Derives backoff from given end time, normalizing negative numbers to zero.
|
|
666
|
-
const backoffMillis = Math.max(throttleEndTimeMillis - Date.now(), 0);
|
|
667
|
-
const timeout = setTimeout(resolve, backoffMillis);
|
|
668
|
-
// Adds listener, rather than sets onabort, because signal is a shared object.
|
|
669
|
-
signal.addEventListener(() => {
|
|
670
|
-
clearTimeout(timeout);
|
|
671
|
-
// If the request completes before this timeout, the rejection has no effect.
|
|
672
|
-
reject(ERROR_FACTORY.create("fetch-throttle" /* ErrorCode.FETCH_THROTTLE */, {
|
|
673
|
-
throttleEndTimeMillis
|
|
674
|
-
}));
|
|
675
|
-
});
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
/**
|
|
679
|
-
* Returns true if the {@link Error} indicates a fetch request may succeed later.
|
|
680
|
-
*/
|
|
681
|
-
function isRetriableError(e) {
|
|
682
|
-
if (!(e instanceof FirebaseError) || !e.customData) {
|
|
683
|
-
return false;
|
|
684
|
-
}
|
|
685
|
-
// Uses string index defined by ErrorData, which FirebaseError implements.
|
|
686
|
-
const httpStatus = Number(e.customData['httpStatus']);
|
|
687
|
-
return (httpStatus === 429 ||
|
|
688
|
-
httpStatus === 500 ||
|
|
689
|
-
httpStatus === 503 ||
|
|
690
|
-
httpStatus === 504);
|
|
691
|
-
}
|
|
692
|
-
/**
|
|
693
|
-
* Decorates a Client with retry logic.
|
|
694
|
-
*
|
|
695
|
-
* <p>Comparable to CachingClient, but uses backoff logic instead of cache max age and doesn't cache
|
|
696
|
-
* responses (because the SDK has no use for error responses).
|
|
697
|
-
*/
|
|
698
|
-
class RetryingClient {
|
|
699
|
-
constructor(client, storage) {
|
|
700
|
-
this.client = client;
|
|
701
|
-
this.storage = storage;
|
|
702
|
-
}
|
|
703
|
-
async fetch(request) {
|
|
704
|
-
const throttleMetadata = (await this.storage.getThrottleMetadata()) || {
|
|
705
|
-
backoffCount: 0,
|
|
706
|
-
throttleEndTimeMillis: Date.now()
|
|
707
|
-
};
|
|
708
|
-
return this.attemptFetch(request, throttleMetadata);
|
|
709
|
-
}
|
|
710
|
-
/**
|
|
711
|
-
* A recursive helper for attempting a fetch request repeatedly.
|
|
712
|
-
*
|
|
713
|
-
* @throws any non-retriable errors.
|
|
714
|
-
*/
|
|
715
|
-
async attemptFetch(request, { throttleEndTimeMillis, backoffCount }) {
|
|
716
|
-
// Starts with a (potentially zero) timeout to support resumption from stored state.
|
|
717
|
-
// Ensures the throttle end time is honored if the last attempt timed out.
|
|
718
|
-
// Note the SDK will never make a request if the fetch timeout expires at this point.
|
|
719
|
-
await setAbortableTimeout(request.signal, throttleEndTimeMillis);
|
|
720
|
-
try {
|
|
721
|
-
const response = await this.client.fetch(request);
|
|
722
|
-
// Note the SDK only clears throttle state if response is success or non-retriable.
|
|
723
|
-
await this.storage.deleteThrottleMetadata();
|
|
724
|
-
return response;
|
|
725
|
-
}
|
|
726
|
-
catch (e) {
|
|
727
|
-
if (!isRetriableError(e)) {
|
|
728
|
-
throw e;
|
|
729
|
-
}
|
|
730
|
-
// Increments backoff state.
|
|
731
|
-
const throttleMetadata = {
|
|
732
|
-
throttleEndTimeMillis: Date.now() + calculateBackoffMillis(backoffCount),
|
|
733
|
-
backoffCount: backoffCount + 1
|
|
734
|
-
};
|
|
735
|
-
// Persists state.
|
|
736
|
-
await this.storage.setThrottleMetadata(throttleMetadata);
|
|
737
|
-
return this.attemptFetch(request, throttleMetadata);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
635
|
+
/**
|
|
636
|
+
* @license
|
|
637
|
+
* Copyright 2019 Google LLC
|
|
638
|
+
*
|
|
639
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
640
|
+
* you may not use this file except in compliance with the License.
|
|
641
|
+
* You may obtain a copy of the License at
|
|
642
|
+
*
|
|
643
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
644
|
+
*
|
|
645
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
646
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
647
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
648
|
+
* See the License for the specific language governing permissions and
|
|
649
|
+
* limitations under the License.
|
|
650
|
+
*/
|
|
651
|
+
/**
|
|
652
|
+
* Supports waiting on a backoff by:
|
|
653
|
+
*
|
|
654
|
+
* <ul>
|
|
655
|
+
* <li>Promisifying setTimeout, so we can set a timeout in our Promise chain</li>
|
|
656
|
+
* <li>Listening on a signal bus for abort events, just like the Fetch API</li>
|
|
657
|
+
* <li>Failing in the same way the Fetch API fails, so timing out a live request and a throttled
|
|
658
|
+
* request appear the same.</li>
|
|
659
|
+
* </ul>
|
|
660
|
+
*
|
|
661
|
+
* <p>Visible for testing.
|
|
662
|
+
*/
|
|
663
|
+
function setAbortableTimeout(signal, throttleEndTimeMillis) {
|
|
664
|
+
return new Promise((resolve, reject) => {
|
|
665
|
+
// Derives backoff from given end time, normalizing negative numbers to zero.
|
|
666
|
+
const backoffMillis = Math.max(throttleEndTimeMillis - Date.now(), 0);
|
|
667
|
+
const timeout = setTimeout(resolve, backoffMillis);
|
|
668
|
+
// Adds listener, rather than sets onabort, because signal is a shared object.
|
|
669
|
+
signal.addEventListener(() => {
|
|
670
|
+
clearTimeout(timeout);
|
|
671
|
+
// If the request completes before this timeout, the rejection has no effect.
|
|
672
|
+
reject(ERROR_FACTORY.create("fetch-throttle" /* ErrorCode.FETCH_THROTTLE */, {
|
|
673
|
+
throttleEndTimeMillis
|
|
674
|
+
}));
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Returns true if the {@link Error} indicates a fetch request may succeed later.
|
|
680
|
+
*/
|
|
681
|
+
function isRetriableError(e) {
|
|
682
|
+
if (!(e instanceof FirebaseError) || !e.customData) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
// Uses string index defined by ErrorData, which FirebaseError implements.
|
|
686
|
+
const httpStatus = Number(e.customData['httpStatus']);
|
|
687
|
+
return (httpStatus === 429 ||
|
|
688
|
+
httpStatus === 500 ||
|
|
689
|
+
httpStatus === 503 ||
|
|
690
|
+
httpStatus === 504);
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Decorates a Client with retry logic.
|
|
694
|
+
*
|
|
695
|
+
* <p>Comparable to CachingClient, but uses backoff logic instead of cache max age and doesn't cache
|
|
696
|
+
* responses (because the SDK has no use for error responses).
|
|
697
|
+
*/
|
|
698
|
+
class RetryingClient {
|
|
699
|
+
constructor(client, storage) {
|
|
700
|
+
this.client = client;
|
|
701
|
+
this.storage = storage;
|
|
702
|
+
}
|
|
703
|
+
async fetch(request) {
|
|
704
|
+
const throttleMetadata = (await this.storage.getThrottleMetadata()) || {
|
|
705
|
+
backoffCount: 0,
|
|
706
|
+
throttleEndTimeMillis: Date.now()
|
|
707
|
+
};
|
|
708
|
+
return this.attemptFetch(request, throttleMetadata);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* A recursive helper for attempting a fetch request repeatedly.
|
|
712
|
+
*
|
|
713
|
+
* @throws any non-retriable errors.
|
|
714
|
+
*/
|
|
715
|
+
async attemptFetch(request, { throttleEndTimeMillis, backoffCount }) {
|
|
716
|
+
// Starts with a (potentially zero) timeout to support resumption from stored state.
|
|
717
|
+
// Ensures the throttle end time is honored if the last attempt timed out.
|
|
718
|
+
// Note the SDK will never make a request if the fetch timeout expires at this point.
|
|
719
|
+
await setAbortableTimeout(request.signal, throttleEndTimeMillis);
|
|
720
|
+
try {
|
|
721
|
+
const response = await this.client.fetch(request);
|
|
722
|
+
// Note the SDK only clears throttle state if response is success or non-retriable.
|
|
723
|
+
await this.storage.deleteThrottleMetadata();
|
|
724
|
+
return response;
|
|
725
|
+
}
|
|
726
|
+
catch (e) {
|
|
727
|
+
if (!isRetriableError(e)) {
|
|
728
|
+
throw e;
|
|
729
|
+
}
|
|
730
|
+
// Increments backoff state.
|
|
731
|
+
const throttleMetadata = {
|
|
732
|
+
throttleEndTimeMillis: Date.now() + calculateBackoffMillis(backoffCount),
|
|
733
|
+
backoffCount: backoffCount + 1
|
|
734
|
+
};
|
|
735
|
+
// Persists state.
|
|
736
|
+
await this.storage.setThrottleMetadata(throttleMetadata);
|
|
737
|
+
return this.attemptFetch(request, throttleMetadata);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
740
|
}
|
|
741
741
|
|
|
742
|
-
/**
|
|
743
|
-
* @license
|
|
744
|
-
* Copyright 2019 Google LLC
|
|
745
|
-
*
|
|
746
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
747
|
-
* you may not use this file except in compliance with the License.
|
|
748
|
-
* You may obtain a copy of the License at
|
|
749
|
-
*
|
|
750
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
751
|
-
*
|
|
752
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
753
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
754
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
755
|
-
* See the License for the specific language governing permissions and
|
|
756
|
-
* limitations under the License.
|
|
757
|
-
*/
|
|
758
|
-
const DEFAULT_FETCH_TIMEOUT_MILLIS = 60 * 1000; // One minute
|
|
759
|
-
const DEFAULT_CACHE_MAX_AGE_MILLIS = 12 * 60 * 60 * 1000; // Twelve hours.
|
|
760
|
-
/**
|
|
761
|
-
* Encapsulates business logic mapping network and storage dependencies to the public SDK API.
|
|
762
|
-
*
|
|
763
|
-
* See {@link https://github.com/firebase/firebase-js-sdk/blob/main/packages/firebase/index.d.ts|interface documentation} for method descriptions.
|
|
764
|
-
*/
|
|
765
|
-
class RemoteConfig {
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
this.
|
|
798
|
-
this.
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
}
|
|
742
|
+
/**
|
|
743
|
+
* @license
|
|
744
|
+
* Copyright 2019 Google LLC
|
|
745
|
+
*
|
|
746
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
747
|
+
* you may not use this file except in compliance with the License.
|
|
748
|
+
* You may obtain a copy of the License at
|
|
749
|
+
*
|
|
750
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
751
|
+
*
|
|
752
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
753
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
754
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
755
|
+
* See the License for the specific language governing permissions and
|
|
756
|
+
* limitations under the License.
|
|
757
|
+
*/
|
|
758
|
+
const DEFAULT_FETCH_TIMEOUT_MILLIS = 60 * 1000; // One minute
|
|
759
|
+
const DEFAULT_CACHE_MAX_AGE_MILLIS = 12 * 60 * 60 * 1000; // Twelve hours.
|
|
760
|
+
/**
|
|
761
|
+
* Encapsulates business logic mapping network and storage dependencies to the public SDK API.
|
|
762
|
+
*
|
|
763
|
+
* See {@link https://github.com/firebase/firebase-js-sdk/blob/main/packages/firebase/index.d.ts|interface documentation} for method descriptions.
|
|
764
|
+
*/
|
|
765
|
+
class RemoteConfig {
|
|
766
|
+
get fetchTimeMillis() {
|
|
767
|
+
return this._storageCache.getLastSuccessfulFetchTimestampMillis() || -1;
|
|
768
|
+
}
|
|
769
|
+
get lastFetchStatus() {
|
|
770
|
+
return this._storageCache.getLastFetchStatus() || 'no-fetch-yet';
|
|
771
|
+
}
|
|
772
|
+
constructor(
|
|
773
|
+
// Required by FirebaseServiceFactory interface.
|
|
774
|
+
app,
|
|
775
|
+
// JS doesn't support private yet
|
|
776
|
+
// (https://github.com/tc39/proposal-class-fields#private-fields), so we hint using an
|
|
777
|
+
// underscore prefix.
|
|
778
|
+
/**
|
|
779
|
+
* @internal
|
|
780
|
+
*/
|
|
781
|
+
_client,
|
|
782
|
+
/**
|
|
783
|
+
* @internal
|
|
784
|
+
*/
|
|
785
|
+
_storageCache,
|
|
786
|
+
/**
|
|
787
|
+
* @internal
|
|
788
|
+
*/
|
|
789
|
+
_storage,
|
|
790
|
+
/**
|
|
791
|
+
* @internal
|
|
792
|
+
*/
|
|
793
|
+
_logger) {
|
|
794
|
+
this.app = app;
|
|
795
|
+
this._client = _client;
|
|
796
|
+
this._storageCache = _storageCache;
|
|
797
|
+
this._storage = _storage;
|
|
798
|
+
this._logger = _logger;
|
|
799
|
+
/**
|
|
800
|
+
* Tracks completion of initialization promise.
|
|
801
|
+
* @internal
|
|
802
|
+
*/
|
|
803
|
+
this._isInitializationComplete = false;
|
|
804
|
+
this.settings = {
|
|
805
|
+
fetchTimeoutMillis: DEFAULT_FETCH_TIMEOUT_MILLIS,
|
|
806
|
+
minimumFetchIntervalMillis: DEFAULT_CACHE_MAX_AGE_MILLIS
|
|
807
|
+
};
|
|
808
|
+
this.defaultConfig = {};
|
|
809
|
+
}
|
|
810
810
|
}
|
|
811
811
|
|
|
812
|
-
/**
|
|
813
|
-
* @license
|
|
814
|
-
* Copyright 2019 Google LLC
|
|
815
|
-
*
|
|
816
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
817
|
-
* you may not use this file except in compliance with the License.
|
|
818
|
-
* You may obtain a copy of the License at
|
|
819
|
-
*
|
|
820
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
821
|
-
*
|
|
822
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
823
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
824
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
825
|
-
* See the License for the specific language governing permissions and
|
|
826
|
-
* limitations under the License.
|
|
827
|
-
*/
|
|
828
|
-
/**
|
|
829
|
-
* Converts an error event associated with a {@link IDBRequest} to a {@link FirebaseError}.
|
|
830
|
-
*/
|
|
831
|
-
function toFirebaseError(event, errorCode) {
|
|
832
|
-
const originalError = event.target.error || undefined;
|
|
833
|
-
return ERROR_FACTORY.create(errorCode, {
|
|
834
|
-
originalErrorMessage: originalError && (originalError === null || originalError === void 0 ? void 0 : originalError.message)
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* A general-purpose store keyed by app + namespace + {@link
|
|
839
|
-
* ProjectNamespaceKeyFieldValue}.
|
|
840
|
-
*
|
|
841
|
-
* <p>The Remote Config SDK can be used with multiple app installations, and each app can interact
|
|
842
|
-
* with multiple namespaces, so this store uses app (ID + name) and namespace as common parent keys
|
|
843
|
-
* for a set of key-value pairs. See {@link Storage#createCompositeKey}.
|
|
844
|
-
*
|
|
845
|
-
* <p>Visible for testing.
|
|
846
|
-
*/
|
|
847
|
-
const APP_NAMESPACE_STORE = 'app_namespace_store';
|
|
848
|
-
const DB_NAME = 'firebase_remote_config';
|
|
849
|
-
const DB_VERSION = 1;
|
|
850
|
-
// Visible for testing.
|
|
851
|
-
function openDatabase() {
|
|
852
|
-
return new Promise((resolve, reject) => {
|
|
853
|
-
try {
|
|
854
|
-
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
855
|
-
request.onerror = event => {
|
|
856
|
-
reject(toFirebaseError(event, "storage-open" /* ErrorCode.STORAGE_OPEN */));
|
|
857
|
-
};
|
|
858
|
-
request.onsuccess = event => {
|
|
859
|
-
resolve(event.target.result);
|
|
860
|
-
};
|
|
861
|
-
request.onupgradeneeded = event => {
|
|
862
|
-
const db = event.target.result;
|
|
863
|
-
// We don't use 'break' in this switch statement, the fall-through
|
|
864
|
-
// behavior is what we want, because if there are multiple versions between
|
|
865
|
-
// the old version and the current version, we want ALL the migrations
|
|
866
|
-
// that correspond to those versions to run, not only the last one.
|
|
867
|
-
// eslint-disable-next-line default-case
|
|
868
|
-
switch (event.oldVersion) {
|
|
869
|
-
case 0:
|
|
870
|
-
db.createObjectStore(APP_NAMESPACE_STORE, {
|
|
871
|
-
keyPath: 'compositeKey'
|
|
872
|
-
});
|
|
873
|
-
}
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
catch (error) {
|
|
877
|
-
reject(ERROR_FACTORY.create("storage-open" /* ErrorCode.STORAGE_OPEN */, {
|
|
878
|
-
originalErrorMessage: error === null || error === void 0 ? void 0 : error.message
|
|
879
|
-
}));
|
|
880
|
-
}
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* Abstracts data persistence.
|
|
885
|
-
*/
|
|
886
|
-
class Storage {
|
|
887
|
-
/**
|
|
888
|
-
* @param appId enables storage segmentation by app (ID + name).
|
|
889
|
-
* @param appName enables storage segmentation by app (ID + name).
|
|
890
|
-
* @param namespace enables storage segmentation by namespace.
|
|
891
|
-
*/
|
|
892
|
-
constructor(appId, appName, namespace, openDbPromise = openDatabase()) {
|
|
893
|
-
this.appId = appId;
|
|
894
|
-
this.appName = appName;
|
|
895
|
-
this.namespace = namespace;
|
|
896
|
-
this.openDbPromise = openDbPromise;
|
|
897
|
-
}
|
|
898
|
-
getLastFetchStatus() {
|
|
899
|
-
return this.get('last_fetch_status');
|
|
900
|
-
}
|
|
901
|
-
setLastFetchStatus(status) {
|
|
902
|
-
return this.set('last_fetch_status', status);
|
|
903
|
-
}
|
|
904
|
-
// This is comparable to a cache entry timestamp. If we need to expire other data, we could
|
|
905
|
-
// consider adding timestamp to all storage records and an optional max age arg to getters.
|
|
906
|
-
getLastSuccessfulFetchTimestampMillis() {
|
|
907
|
-
return this.get('last_successful_fetch_timestamp_millis');
|
|
908
|
-
}
|
|
909
|
-
setLastSuccessfulFetchTimestampMillis(timestamp) {
|
|
910
|
-
return this.set('last_successful_fetch_timestamp_millis', timestamp);
|
|
911
|
-
}
|
|
912
|
-
getLastSuccessfulFetchResponse() {
|
|
913
|
-
return this.get('last_successful_fetch_response');
|
|
914
|
-
}
|
|
915
|
-
setLastSuccessfulFetchResponse(response) {
|
|
916
|
-
return this.set('last_successful_fetch_response', response);
|
|
917
|
-
}
|
|
918
|
-
getActiveConfig() {
|
|
919
|
-
return this.get('active_config');
|
|
920
|
-
}
|
|
921
|
-
setActiveConfig(config) {
|
|
922
|
-
return this.set('active_config', config);
|
|
923
|
-
}
|
|
924
|
-
getActiveConfigEtag() {
|
|
925
|
-
return this.get('active_config_etag');
|
|
926
|
-
}
|
|
927
|
-
setActiveConfigEtag(etag) {
|
|
928
|
-
return this.set('active_config_etag', etag);
|
|
929
|
-
}
|
|
930
|
-
getThrottleMetadata() {
|
|
931
|
-
return this.get('throttle_metadata');
|
|
932
|
-
}
|
|
933
|
-
setThrottleMetadata(metadata) {
|
|
934
|
-
return this.set('throttle_metadata', metadata);
|
|
935
|
-
}
|
|
936
|
-
deleteThrottleMetadata() {
|
|
937
|
-
return this.delete('throttle_metadata');
|
|
938
|
-
}
|
|
939
|
-
async get(key) {
|
|
940
|
-
const db = await this.openDbPromise;
|
|
941
|
-
return new Promise((resolve, reject) => {
|
|
942
|
-
const transaction = db.transaction([APP_NAMESPACE_STORE], 'readonly');
|
|
943
|
-
const objectStore = transaction.objectStore(APP_NAMESPACE_STORE);
|
|
944
|
-
const compositeKey = this.createCompositeKey(key);
|
|
945
|
-
try {
|
|
946
|
-
const request = objectStore.get(compositeKey);
|
|
947
|
-
request.onerror = event => {
|
|
948
|
-
reject(toFirebaseError(event, "storage-get" /* ErrorCode.STORAGE_GET */));
|
|
949
|
-
};
|
|
950
|
-
request.onsuccess = event => {
|
|
951
|
-
const result = event.target.result;
|
|
952
|
-
if (result) {
|
|
953
|
-
resolve(result.value);
|
|
954
|
-
}
|
|
955
|
-
else {
|
|
956
|
-
resolve(undefined);
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
catch (e) {
|
|
961
|
-
reject(ERROR_FACTORY.create("storage-get" /* ErrorCode.STORAGE_GET */, {
|
|
962
|
-
originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
|
|
963
|
-
}));
|
|
964
|
-
}
|
|
965
|
-
});
|
|
966
|
-
}
|
|
967
|
-
async set(key, value) {
|
|
968
|
-
const db = await this.openDbPromise;
|
|
969
|
-
return new Promise((resolve, reject) => {
|
|
970
|
-
const transaction = db.transaction([APP_NAMESPACE_STORE], 'readwrite');
|
|
971
|
-
const objectStore = transaction.objectStore(APP_NAMESPACE_STORE);
|
|
972
|
-
const compositeKey = this.createCompositeKey(key);
|
|
973
|
-
try {
|
|
974
|
-
const request = objectStore.put({
|
|
975
|
-
compositeKey,
|
|
976
|
-
value
|
|
977
|
-
});
|
|
978
|
-
request.onerror = (event) => {
|
|
979
|
-
reject(toFirebaseError(event, "storage-set" /* ErrorCode.STORAGE_SET */));
|
|
980
|
-
};
|
|
981
|
-
request.onsuccess = () => {
|
|
982
|
-
resolve();
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
catch (e) {
|
|
986
|
-
reject(ERROR_FACTORY.create("storage-set" /* ErrorCode.STORAGE_SET */, {
|
|
987
|
-
originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
|
|
988
|
-
}));
|
|
989
|
-
}
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
async delete(key) {
|
|
993
|
-
const db = await this.openDbPromise;
|
|
994
|
-
return new Promise((resolve, reject) => {
|
|
995
|
-
const transaction = db.transaction([APP_NAMESPACE_STORE], 'readwrite');
|
|
996
|
-
const objectStore = transaction.objectStore(APP_NAMESPACE_STORE);
|
|
997
|
-
const compositeKey = this.createCompositeKey(key);
|
|
998
|
-
try {
|
|
999
|
-
const request = objectStore.delete(compositeKey);
|
|
1000
|
-
request.onerror = (event) => {
|
|
1001
|
-
reject(toFirebaseError(event, "storage-delete" /* ErrorCode.STORAGE_DELETE */));
|
|
1002
|
-
};
|
|
1003
|
-
request.onsuccess = () => {
|
|
1004
|
-
resolve();
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
catch (e) {
|
|
1008
|
-
reject(ERROR_FACTORY.create("storage-delete" /* ErrorCode.STORAGE_DELETE */, {
|
|
1009
|
-
originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
|
|
1010
|
-
}));
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
// Facilitates composite key functionality (which is unsupported in IE).
|
|
1015
|
-
createCompositeKey(key) {
|
|
1016
|
-
return [this.appId, this.appName, this.namespace, key].join();
|
|
1017
|
-
}
|
|
812
|
+
/**
|
|
813
|
+
* @license
|
|
814
|
+
* Copyright 2019 Google LLC
|
|
815
|
+
*
|
|
816
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
817
|
+
* you may not use this file except in compliance with the License.
|
|
818
|
+
* You may obtain a copy of the License at
|
|
819
|
+
*
|
|
820
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
821
|
+
*
|
|
822
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
823
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
824
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
825
|
+
* See the License for the specific language governing permissions and
|
|
826
|
+
* limitations under the License.
|
|
827
|
+
*/
|
|
828
|
+
/**
|
|
829
|
+
* Converts an error event associated with a {@link IDBRequest} to a {@link FirebaseError}.
|
|
830
|
+
*/
|
|
831
|
+
function toFirebaseError(event, errorCode) {
|
|
832
|
+
const originalError = event.target.error || undefined;
|
|
833
|
+
return ERROR_FACTORY.create(errorCode, {
|
|
834
|
+
originalErrorMessage: originalError && (originalError === null || originalError === void 0 ? void 0 : originalError.message)
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* A general-purpose store keyed by app + namespace + {@link
|
|
839
|
+
* ProjectNamespaceKeyFieldValue}.
|
|
840
|
+
*
|
|
841
|
+
* <p>The Remote Config SDK can be used with multiple app installations, and each app can interact
|
|
842
|
+
* with multiple namespaces, so this store uses app (ID + name) and namespace as common parent keys
|
|
843
|
+
* for a set of key-value pairs. See {@link Storage#createCompositeKey}.
|
|
844
|
+
*
|
|
845
|
+
* <p>Visible for testing.
|
|
846
|
+
*/
|
|
847
|
+
const APP_NAMESPACE_STORE = 'app_namespace_store';
|
|
848
|
+
const DB_NAME = 'firebase_remote_config';
|
|
849
|
+
const DB_VERSION = 1;
|
|
850
|
+
// Visible for testing.
|
|
851
|
+
function openDatabase() {
|
|
852
|
+
return new Promise((resolve, reject) => {
|
|
853
|
+
try {
|
|
854
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
855
|
+
request.onerror = event => {
|
|
856
|
+
reject(toFirebaseError(event, "storage-open" /* ErrorCode.STORAGE_OPEN */));
|
|
857
|
+
};
|
|
858
|
+
request.onsuccess = event => {
|
|
859
|
+
resolve(event.target.result);
|
|
860
|
+
};
|
|
861
|
+
request.onupgradeneeded = event => {
|
|
862
|
+
const db = event.target.result;
|
|
863
|
+
// We don't use 'break' in this switch statement, the fall-through
|
|
864
|
+
// behavior is what we want, because if there are multiple versions between
|
|
865
|
+
// the old version and the current version, we want ALL the migrations
|
|
866
|
+
// that correspond to those versions to run, not only the last one.
|
|
867
|
+
// eslint-disable-next-line default-case
|
|
868
|
+
switch (event.oldVersion) {
|
|
869
|
+
case 0:
|
|
870
|
+
db.createObjectStore(APP_NAMESPACE_STORE, {
|
|
871
|
+
keyPath: 'compositeKey'
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
catch (error) {
|
|
877
|
+
reject(ERROR_FACTORY.create("storage-open" /* ErrorCode.STORAGE_OPEN */, {
|
|
878
|
+
originalErrorMessage: error === null || error === void 0 ? void 0 : error.message
|
|
879
|
+
}));
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Abstracts data persistence.
|
|
885
|
+
*/
|
|
886
|
+
class Storage {
|
|
887
|
+
/**
|
|
888
|
+
* @param appId enables storage segmentation by app (ID + name).
|
|
889
|
+
* @param appName enables storage segmentation by app (ID + name).
|
|
890
|
+
* @param namespace enables storage segmentation by namespace.
|
|
891
|
+
*/
|
|
892
|
+
constructor(appId, appName, namespace, openDbPromise = openDatabase()) {
|
|
893
|
+
this.appId = appId;
|
|
894
|
+
this.appName = appName;
|
|
895
|
+
this.namespace = namespace;
|
|
896
|
+
this.openDbPromise = openDbPromise;
|
|
897
|
+
}
|
|
898
|
+
getLastFetchStatus() {
|
|
899
|
+
return this.get('last_fetch_status');
|
|
900
|
+
}
|
|
901
|
+
setLastFetchStatus(status) {
|
|
902
|
+
return this.set('last_fetch_status', status);
|
|
903
|
+
}
|
|
904
|
+
// This is comparable to a cache entry timestamp. If we need to expire other data, we could
|
|
905
|
+
// consider adding timestamp to all storage records and an optional max age arg to getters.
|
|
906
|
+
getLastSuccessfulFetchTimestampMillis() {
|
|
907
|
+
return this.get('last_successful_fetch_timestamp_millis');
|
|
908
|
+
}
|
|
909
|
+
setLastSuccessfulFetchTimestampMillis(timestamp) {
|
|
910
|
+
return this.set('last_successful_fetch_timestamp_millis', timestamp);
|
|
911
|
+
}
|
|
912
|
+
getLastSuccessfulFetchResponse() {
|
|
913
|
+
return this.get('last_successful_fetch_response');
|
|
914
|
+
}
|
|
915
|
+
setLastSuccessfulFetchResponse(response) {
|
|
916
|
+
return this.set('last_successful_fetch_response', response);
|
|
917
|
+
}
|
|
918
|
+
getActiveConfig() {
|
|
919
|
+
return this.get('active_config');
|
|
920
|
+
}
|
|
921
|
+
setActiveConfig(config) {
|
|
922
|
+
return this.set('active_config', config);
|
|
923
|
+
}
|
|
924
|
+
getActiveConfigEtag() {
|
|
925
|
+
return this.get('active_config_etag');
|
|
926
|
+
}
|
|
927
|
+
setActiveConfigEtag(etag) {
|
|
928
|
+
return this.set('active_config_etag', etag);
|
|
929
|
+
}
|
|
930
|
+
getThrottleMetadata() {
|
|
931
|
+
return this.get('throttle_metadata');
|
|
932
|
+
}
|
|
933
|
+
setThrottleMetadata(metadata) {
|
|
934
|
+
return this.set('throttle_metadata', metadata);
|
|
935
|
+
}
|
|
936
|
+
deleteThrottleMetadata() {
|
|
937
|
+
return this.delete('throttle_metadata');
|
|
938
|
+
}
|
|
939
|
+
async get(key) {
|
|
940
|
+
const db = await this.openDbPromise;
|
|
941
|
+
return new Promise((resolve, reject) => {
|
|
942
|
+
const transaction = db.transaction([APP_NAMESPACE_STORE], 'readonly');
|
|
943
|
+
const objectStore = transaction.objectStore(APP_NAMESPACE_STORE);
|
|
944
|
+
const compositeKey = this.createCompositeKey(key);
|
|
945
|
+
try {
|
|
946
|
+
const request = objectStore.get(compositeKey);
|
|
947
|
+
request.onerror = event => {
|
|
948
|
+
reject(toFirebaseError(event, "storage-get" /* ErrorCode.STORAGE_GET */));
|
|
949
|
+
};
|
|
950
|
+
request.onsuccess = event => {
|
|
951
|
+
const result = event.target.result;
|
|
952
|
+
if (result) {
|
|
953
|
+
resolve(result.value);
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
resolve(undefined);
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
catch (e) {
|
|
961
|
+
reject(ERROR_FACTORY.create("storage-get" /* ErrorCode.STORAGE_GET */, {
|
|
962
|
+
originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
|
|
963
|
+
}));
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
async set(key, value) {
|
|
968
|
+
const db = await this.openDbPromise;
|
|
969
|
+
return new Promise((resolve, reject) => {
|
|
970
|
+
const transaction = db.transaction([APP_NAMESPACE_STORE], 'readwrite');
|
|
971
|
+
const objectStore = transaction.objectStore(APP_NAMESPACE_STORE);
|
|
972
|
+
const compositeKey = this.createCompositeKey(key);
|
|
973
|
+
try {
|
|
974
|
+
const request = objectStore.put({
|
|
975
|
+
compositeKey,
|
|
976
|
+
value
|
|
977
|
+
});
|
|
978
|
+
request.onerror = (event) => {
|
|
979
|
+
reject(toFirebaseError(event, "storage-set" /* ErrorCode.STORAGE_SET */));
|
|
980
|
+
};
|
|
981
|
+
request.onsuccess = () => {
|
|
982
|
+
resolve();
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
catch (e) {
|
|
986
|
+
reject(ERROR_FACTORY.create("storage-set" /* ErrorCode.STORAGE_SET */, {
|
|
987
|
+
originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
|
|
988
|
+
}));
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
async delete(key) {
|
|
993
|
+
const db = await this.openDbPromise;
|
|
994
|
+
return new Promise((resolve, reject) => {
|
|
995
|
+
const transaction = db.transaction([APP_NAMESPACE_STORE], 'readwrite');
|
|
996
|
+
const objectStore = transaction.objectStore(APP_NAMESPACE_STORE);
|
|
997
|
+
const compositeKey = this.createCompositeKey(key);
|
|
998
|
+
try {
|
|
999
|
+
const request = objectStore.delete(compositeKey);
|
|
1000
|
+
request.onerror = (event) => {
|
|
1001
|
+
reject(toFirebaseError(event, "storage-delete" /* ErrorCode.STORAGE_DELETE */));
|
|
1002
|
+
};
|
|
1003
|
+
request.onsuccess = () => {
|
|
1004
|
+
resolve();
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
catch (e) {
|
|
1008
|
+
reject(ERROR_FACTORY.create("storage-delete" /* ErrorCode.STORAGE_DELETE */, {
|
|
1009
|
+
originalErrorMessage: e === null || e === void 0 ? void 0 : e.message
|
|
1010
|
+
}));
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
// Facilitates composite key functionality (which is unsupported in IE).
|
|
1015
|
+
createCompositeKey(key) {
|
|
1016
|
+
return [this.appId, this.appName, this.namespace, key].join();
|
|
1017
|
+
}
|
|
1018
1018
|
}
|
|
1019
1019
|
|
|
1020
|
-
/**
|
|
1021
|
-
* @license
|
|
1022
|
-
* Copyright 2019 Google LLC
|
|
1023
|
-
*
|
|
1024
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1025
|
-
* you may not use this file except in compliance with the License.
|
|
1026
|
-
* You may obtain a copy of the License at
|
|
1027
|
-
*
|
|
1028
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1029
|
-
*
|
|
1030
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
1031
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1032
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1033
|
-
* See the License for the specific language governing permissions and
|
|
1034
|
-
* limitations under the License.
|
|
1035
|
-
*/
|
|
1036
|
-
/**
|
|
1037
|
-
* A memory cache layer over storage to support the SDK's synchronous read requirements.
|
|
1038
|
-
*/
|
|
1039
|
-
class StorageCache {
|
|
1040
|
-
constructor(storage) {
|
|
1041
|
-
this.storage = storage;
|
|
1042
|
-
}
|
|
1043
|
-
/**
|
|
1044
|
-
* Memory-only getters
|
|
1045
|
-
*/
|
|
1046
|
-
getLastFetchStatus() {
|
|
1047
|
-
return this.lastFetchStatus;
|
|
1048
|
-
}
|
|
1049
|
-
getLastSuccessfulFetchTimestampMillis() {
|
|
1050
|
-
return this.lastSuccessfulFetchTimestampMillis;
|
|
1051
|
-
}
|
|
1052
|
-
getActiveConfig() {
|
|
1053
|
-
return this.activeConfig;
|
|
1054
|
-
}
|
|
1055
|
-
/**
|
|
1056
|
-
* Read-ahead getter
|
|
1057
|
-
*/
|
|
1058
|
-
async loadFromStorage() {
|
|
1059
|
-
const lastFetchStatusPromise = this.storage.getLastFetchStatus();
|
|
1060
|
-
const lastSuccessfulFetchTimestampMillisPromise = this.storage.getLastSuccessfulFetchTimestampMillis();
|
|
1061
|
-
const activeConfigPromise = this.storage.getActiveConfig();
|
|
1062
|
-
// Note:
|
|
1063
|
-
// 1. we consistently check for undefined to avoid clobbering defined values
|
|
1064
|
-
// in memory
|
|
1065
|
-
// 2. we defer awaiting to improve readability, as opposed to destructuring
|
|
1066
|
-
// a Promise.all result, for example
|
|
1067
|
-
const lastFetchStatus = await lastFetchStatusPromise;
|
|
1068
|
-
if (lastFetchStatus) {
|
|
1069
|
-
this.lastFetchStatus = lastFetchStatus;
|
|
1070
|
-
}
|
|
1071
|
-
const lastSuccessfulFetchTimestampMillis = await lastSuccessfulFetchTimestampMillisPromise;
|
|
1072
|
-
if (lastSuccessfulFetchTimestampMillis) {
|
|
1073
|
-
this.lastSuccessfulFetchTimestampMillis =
|
|
1074
|
-
lastSuccessfulFetchTimestampMillis;
|
|
1075
|
-
}
|
|
1076
|
-
const activeConfig = await activeConfigPromise;
|
|
1077
|
-
if (activeConfig) {
|
|
1078
|
-
this.activeConfig = activeConfig;
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
/**
|
|
1082
|
-
* Write-through setters
|
|
1083
|
-
*/
|
|
1084
|
-
setLastFetchStatus(status) {
|
|
1085
|
-
this.lastFetchStatus = status;
|
|
1086
|
-
return this.storage.setLastFetchStatus(status);
|
|
1087
|
-
}
|
|
1088
|
-
setLastSuccessfulFetchTimestampMillis(timestampMillis) {
|
|
1089
|
-
this.lastSuccessfulFetchTimestampMillis = timestampMillis;
|
|
1090
|
-
return this.storage.setLastSuccessfulFetchTimestampMillis(timestampMillis);
|
|
1091
|
-
}
|
|
1092
|
-
setActiveConfig(activeConfig) {
|
|
1093
|
-
this.activeConfig = activeConfig;
|
|
1094
|
-
return this.storage.setActiveConfig(activeConfig);
|
|
1095
|
-
}
|
|
1020
|
+
/**
|
|
1021
|
+
* @license
|
|
1022
|
+
* Copyright 2019 Google LLC
|
|
1023
|
+
*
|
|
1024
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1025
|
+
* you may not use this file except in compliance with the License.
|
|
1026
|
+
* You may obtain a copy of the License at
|
|
1027
|
+
*
|
|
1028
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1029
|
+
*
|
|
1030
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1031
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1032
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1033
|
+
* See the License for the specific language governing permissions and
|
|
1034
|
+
* limitations under the License.
|
|
1035
|
+
*/
|
|
1036
|
+
/**
|
|
1037
|
+
* A memory cache layer over storage to support the SDK's synchronous read requirements.
|
|
1038
|
+
*/
|
|
1039
|
+
class StorageCache {
|
|
1040
|
+
constructor(storage) {
|
|
1041
|
+
this.storage = storage;
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Memory-only getters
|
|
1045
|
+
*/
|
|
1046
|
+
getLastFetchStatus() {
|
|
1047
|
+
return this.lastFetchStatus;
|
|
1048
|
+
}
|
|
1049
|
+
getLastSuccessfulFetchTimestampMillis() {
|
|
1050
|
+
return this.lastSuccessfulFetchTimestampMillis;
|
|
1051
|
+
}
|
|
1052
|
+
getActiveConfig() {
|
|
1053
|
+
return this.activeConfig;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Read-ahead getter
|
|
1057
|
+
*/
|
|
1058
|
+
async loadFromStorage() {
|
|
1059
|
+
const lastFetchStatusPromise = this.storage.getLastFetchStatus();
|
|
1060
|
+
const lastSuccessfulFetchTimestampMillisPromise = this.storage.getLastSuccessfulFetchTimestampMillis();
|
|
1061
|
+
const activeConfigPromise = this.storage.getActiveConfig();
|
|
1062
|
+
// Note:
|
|
1063
|
+
// 1. we consistently check for undefined to avoid clobbering defined values
|
|
1064
|
+
// in memory
|
|
1065
|
+
// 2. we defer awaiting to improve readability, as opposed to destructuring
|
|
1066
|
+
// a Promise.all result, for example
|
|
1067
|
+
const lastFetchStatus = await lastFetchStatusPromise;
|
|
1068
|
+
if (lastFetchStatus) {
|
|
1069
|
+
this.lastFetchStatus = lastFetchStatus;
|
|
1070
|
+
}
|
|
1071
|
+
const lastSuccessfulFetchTimestampMillis = await lastSuccessfulFetchTimestampMillisPromise;
|
|
1072
|
+
if (lastSuccessfulFetchTimestampMillis) {
|
|
1073
|
+
this.lastSuccessfulFetchTimestampMillis =
|
|
1074
|
+
lastSuccessfulFetchTimestampMillis;
|
|
1075
|
+
}
|
|
1076
|
+
const activeConfig = await activeConfigPromise;
|
|
1077
|
+
if (activeConfig) {
|
|
1078
|
+
this.activeConfig = activeConfig;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Write-through setters
|
|
1083
|
+
*/
|
|
1084
|
+
setLastFetchStatus(status) {
|
|
1085
|
+
this.lastFetchStatus = status;
|
|
1086
|
+
return this.storage.setLastFetchStatus(status);
|
|
1087
|
+
}
|
|
1088
|
+
setLastSuccessfulFetchTimestampMillis(timestampMillis) {
|
|
1089
|
+
this.lastSuccessfulFetchTimestampMillis = timestampMillis;
|
|
1090
|
+
return this.storage.setLastSuccessfulFetchTimestampMillis(timestampMillis);
|
|
1091
|
+
}
|
|
1092
|
+
setActiveConfig(activeConfig) {
|
|
1093
|
+
this.activeConfig = activeConfig;
|
|
1094
|
+
return this.storage.setActiveConfig(activeConfig);
|
|
1095
|
+
}
|
|
1096
1096
|
}
|
|
1097
1097
|
|
|
1098
|
-
/**
|
|
1099
|
-
* @license
|
|
1100
|
-
* Copyright 2020 Google LLC
|
|
1101
|
-
*
|
|
1102
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1103
|
-
* you may not use this file except in compliance with the License.
|
|
1104
|
-
* You may obtain a copy of the License at
|
|
1105
|
-
*
|
|
1106
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1107
|
-
*
|
|
1108
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
1109
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1110
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1111
|
-
* See the License for the specific language governing permissions and
|
|
1112
|
-
* limitations under the License.
|
|
1113
|
-
*/
|
|
1114
|
-
function registerRemoteConfig() {
|
|
1115
|
-
_registerComponent(new Component(RC_COMPONENT_NAME, remoteConfigFactory, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
|
|
1116
|
-
registerVersion(name, version);
|
|
1117
|
-
// BUILD_TARGET will be replaced by values like esm2017, cjs2017, etc during the compilation
|
|
1118
|
-
registerVersion(name, version, 'esm2017');
|
|
1119
|
-
function remoteConfigFactory(container, { instanceIdentifier: namespace }) {
|
|
1120
|
-
/* Dependencies */
|
|
1121
|
-
// getImmediate for FirebaseApp will always succeed
|
|
1122
|
-
const app = container.getProvider('app').getImmediate();
|
|
1123
|
-
// The following call will always succeed because rc has `import '@firebase/installations'`
|
|
1124
|
-
const installations = container
|
|
1125
|
-
.getProvider('installations-internal')
|
|
1126
|
-
.getImmediate();
|
|
1127
|
-
// Guards against the SDK being used in non-browser environments.
|
|
1128
|
-
if (typeof window === 'undefined') {
|
|
1129
|
-
throw ERROR_FACTORY.create("registration-window" /* ErrorCode.REGISTRATION_WINDOW */);
|
|
1130
|
-
}
|
|
1131
|
-
// Guards against the SDK being used when indexedDB is not available.
|
|
1132
|
-
if (!isIndexedDBAvailable()) {
|
|
1133
|
-
throw ERROR_FACTORY.create("indexed-db-unavailable" /* ErrorCode.INDEXED_DB_UNAVAILABLE */);
|
|
1134
|
-
}
|
|
1135
|
-
// Normalizes optional inputs.
|
|
1136
|
-
const { projectId, apiKey, appId } = app.options;
|
|
1137
|
-
if (!projectId) {
|
|
1138
|
-
throw ERROR_FACTORY.create("registration-project-id" /* ErrorCode.REGISTRATION_PROJECT_ID */);
|
|
1139
|
-
}
|
|
1140
|
-
if (!apiKey) {
|
|
1141
|
-
throw ERROR_FACTORY.create("registration-api-key" /* ErrorCode.REGISTRATION_API_KEY */);
|
|
1142
|
-
}
|
|
1143
|
-
if (!appId) {
|
|
1144
|
-
throw ERROR_FACTORY.create("registration-app-id" /* ErrorCode.REGISTRATION_APP_ID */);
|
|
1145
|
-
}
|
|
1146
|
-
namespace = namespace || 'firebase';
|
|
1147
|
-
const storage = new Storage(appId, app.name, namespace);
|
|
1148
|
-
const storageCache = new StorageCache(storage);
|
|
1149
|
-
const logger = new Logger(name);
|
|
1150
|
-
// Sets ERROR as the default log level.
|
|
1151
|
-
// See RemoteConfig#setLogLevel for corresponding normalization to ERROR log level.
|
|
1152
|
-
logger.logLevel = LogLevel.ERROR;
|
|
1153
|
-
const restClient = new RestClient(installations,
|
|
1154
|
-
// Uses the JS SDK version, by which the RC package version can be deduced, if necessary.
|
|
1155
|
-
SDK_VERSION, namespace, projectId, apiKey, appId);
|
|
1156
|
-
const retryingClient = new RetryingClient(restClient, storage);
|
|
1157
|
-
const cachingClient = new CachingClient(retryingClient, storage, storageCache, logger);
|
|
1158
|
-
const remoteConfigInstance = new RemoteConfig(app, cachingClient, storageCache, storage, logger);
|
|
1159
|
-
// Starts warming cache.
|
|
1160
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1161
|
-
ensureInitialized(remoteConfigInstance);
|
|
1162
|
-
return remoteConfigInstance;
|
|
1163
|
-
}
|
|
1098
|
+
/**
|
|
1099
|
+
* @license
|
|
1100
|
+
* Copyright 2020 Google LLC
|
|
1101
|
+
*
|
|
1102
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1103
|
+
* you may not use this file except in compliance with the License.
|
|
1104
|
+
* You may obtain a copy of the License at
|
|
1105
|
+
*
|
|
1106
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1107
|
+
*
|
|
1108
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1109
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1110
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1111
|
+
* See the License for the specific language governing permissions and
|
|
1112
|
+
* limitations under the License.
|
|
1113
|
+
*/
|
|
1114
|
+
function registerRemoteConfig() {
|
|
1115
|
+
_registerComponent(new Component(RC_COMPONENT_NAME, remoteConfigFactory, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
|
|
1116
|
+
registerVersion(name, version);
|
|
1117
|
+
// BUILD_TARGET will be replaced by values like esm2017, cjs2017, etc during the compilation
|
|
1118
|
+
registerVersion(name, version, 'esm2017');
|
|
1119
|
+
function remoteConfigFactory(container, { instanceIdentifier: namespace }) {
|
|
1120
|
+
/* Dependencies */
|
|
1121
|
+
// getImmediate for FirebaseApp will always succeed
|
|
1122
|
+
const app = container.getProvider('app').getImmediate();
|
|
1123
|
+
// The following call will always succeed because rc has `import '@firebase/installations'`
|
|
1124
|
+
const installations = container
|
|
1125
|
+
.getProvider('installations-internal')
|
|
1126
|
+
.getImmediate();
|
|
1127
|
+
// Guards against the SDK being used in non-browser environments.
|
|
1128
|
+
if (typeof window === 'undefined') {
|
|
1129
|
+
throw ERROR_FACTORY.create("registration-window" /* ErrorCode.REGISTRATION_WINDOW */);
|
|
1130
|
+
}
|
|
1131
|
+
// Guards against the SDK being used when indexedDB is not available.
|
|
1132
|
+
if (!isIndexedDBAvailable()) {
|
|
1133
|
+
throw ERROR_FACTORY.create("indexed-db-unavailable" /* ErrorCode.INDEXED_DB_UNAVAILABLE */);
|
|
1134
|
+
}
|
|
1135
|
+
// Normalizes optional inputs.
|
|
1136
|
+
const { projectId, apiKey, appId } = app.options;
|
|
1137
|
+
if (!projectId) {
|
|
1138
|
+
throw ERROR_FACTORY.create("registration-project-id" /* ErrorCode.REGISTRATION_PROJECT_ID */);
|
|
1139
|
+
}
|
|
1140
|
+
if (!apiKey) {
|
|
1141
|
+
throw ERROR_FACTORY.create("registration-api-key" /* ErrorCode.REGISTRATION_API_KEY */);
|
|
1142
|
+
}
|
|
1143
|
+
if (!appId) {
|
|
1144
|
+
throw ERROR_FACTORY.create("registration-app-id" /* ErrorCode.REGISTRATION_APP_ID */);
|
|
1145
|
+
}
|
|
1146
|
+
namespace = namespace || 'firebase';
|
|
1147
|
+
const storage = new Storage(appId, app.name, namespace);
|
|
1148
|
+
const storageCache = new StorageCache(storage);
|
|
1149
|
+
const logger = new Logger(name);
|
|
1150
|
+
// Sets ERROR as the default log level.
|
|
1151
|
+
// See RemoteConfig#setLogLevel for corresponding normalization to ERROR log level.
|
|
1152
|
+
logger.logLevel = LogLevel.ERROR;
|
|
1153
|
+
const restClient = new RestClient(installations,
|
|
1154
|
+
// Uses the JS SDK version, by which the RC package version can be deduced, if necessary.
|
|
1155
|
+
SDK_VERSION, namespace, projectId, apiKey, appId);
|
|
1156
|
+
const retryingClient = new RetryingClient(restClient, storage);
|
|
1157
|
+
const cachingClient = new CachingClient(retryingClient, storage, storageCache, logger);
|
|
1158
|
+
const remoteConfigInstance = new RemoteConfig(app, cachingClient, storageCache, storage, logger);
|
|
1159
|
+
// Starts warming cache.
|
|
1160
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1161
|
+
ensureInitialized(remoteConfigInstance);
|
|
1162
|
+
return remoteConfigInstance;
|
|
1163
|
+
}
|
|
1164
1164
|
}
|
|
1165
1165
|
|
|
1166
|
-
/**
|
|
1167
|
-
* @license
|
|
1168
|
-
* Copyright 2020 Google LLC
|
|
1169
|
-
*
|
|
1170
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1171
|
-
* you may not use this file except in compliance with the License.
|
|
1172
|
-
* You may obtain a copy of the License at
|
|
1173
|
-
*
|
|
1174
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1175
|
-
*
|
|
1176
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
1177
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1178
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1179
|
-
* See the License for the specific language governing permissions and
|
|
1180
|
-
* limitations under the License.
|
|
1181
|
-
*/
|
|
1182
|
-
// This API is put in a separate file, so we can stub fetchConfig and activate in tests.
|
|
1183
|
-
// It's not possible to stub standalone functions from the same module.
|
|
1184
|
-
/**
|
|
1185
|
-
*
|
|
1186
|
-
* Performs fetch and activate operations, as a convenience.
|
|
1187
|
-
*
|
|
1188
|
-
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
1189
|
-
*
|
|
1190
|
-
* @returns A `Promise` which resolves to true if the current call activated the fetched configs.
|
|
1191
|
-
* If the fetched configs were already activated, the `Promise` will resolve to false.
|
|
1192
|
-
*
|
|
1193
|
-
* @public
|
|
1194
|
-
*/
|
|
1195
|
-
async function fetchAndActivate(remoteConfig) {
|
|
1196
|
-
remoteConfig = getModularInstance(remoteConfig);
|
|
1197
|
-
await fetchConfig(remoteConfig);
|
|
1198
|
-
return activate(remoteConfig);
|
|
1199
|
-
}
|
|
1200
|
-
/**
|
|
1201
|
-
* This method provides two different checks:
|
|
1202
|
-
*
|
|
1203
|
-
* 1. Check if IndexedDB exists in the browser environment.
|
|
1204
|
-
* 2. Check if the current browser context allows IndexedDB `open()` calls.
|
|
1205
|
-
*
|
|
1206
|
-
* @returns A `Promise` which resolves to true if a {@link RemoteConfig} instance
|
|
1207
|
-
* can be initialized in this environment, or false if it cannot.
|
|
1208
|
-
* @public
|
|
1209
|
-
*/
|
|
1210
|
-
async function isSupported() {
|
|
1211
|
-
if (!isIndexedDBAvailable()) {
|
|
1212
|
-
return false;
|
|
1213
|
-
}
|
|
1214
|
-
try {
|
|
1215
|
-
const isDBOpenable = await validateIndexedDBOpenable();
|
|
1216
|
-
return isDBOpenable;
|
|
1217
|
-
}
|
|
1218
|
-
catch (error) {
|
|
1219
|
-
return false;
|
|
1220
|
-
}
|
|
1166
|
+
/**
|
|
1167
|
+
* @license
|
|
1168
|
+
* Copyright 2020 Google LLC
|
|
1169
|
+
*
|
|
1170
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1171
|
+
* you may not use this file except in compliance with the License.
|
|
1172
|
+
* You may obtain a copy of the License at
|
|
1173
|
+
*
|
|
1174
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1175
|
+
*
|
|
1176
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1177
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1178
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1179
|
+
* See the License for the specific language governing permissions and
|
|
1180
|
+
* limitations under the License.
|
|
1181
|
+
*/
|
|
1182
|
+
// This API is put in a separate file, so we can stub fetchConfig and activate in tests.
|
|
1183
|
+
// It's not possible to stub standalone functions from the same module.
|
|
1184
|
+
/**
|
|
1185
|
+
*
|
|
1186
|
+
* Performs fetch and activate operations, as a convenience.
|
|
1187
|
+
*
|
|
1188
|
+
* @param remoteConfig - The {@link RemoteConfig} instance.
|
|
1189
|
+
*
|
|
1190
|
+
* @returns A `Promise` which resolves to true if the current call activated the fetched configs.
|
|
1191
|
+
* If the fetched configs were already activated, the `Promise` will resolve to false.
|
|
1192
|
+
*
|
|
1193
|
+
* @public
|
|
1194
|
+
*/
|
|
1195
|
+
async function fetchAndActivate(remoteConfig) {
|
|
1196
|
+
remoteConfig = getModularInstance(remoteConfig);
|
|
1197
|
+
await fetchConfig(remoteConfig);
|
|
1198
|
+
return activate(remoteConfig);
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* This method provides two different checks:
|
|
1202
|
+
*
|
|
1203
|
+
* 1. Check if IndexedDB exists in the browser environment.
|
|
1204
|
+
* 2. Check if the current browser context allows IndexedDB `open()` calls.
|
|
1205
|
+
*
|
|
1206
|
+
* @returns A `Promise` which resolves to true if a {@link RemoteConfig} instance
|
|
1207
|
+
* can be initialized in this environment, or false if it cannot.
|
|
1208
|
+
* @public
|
|
1209
|
+
*/
|
|
1210
|
+
async function isSupported() {
|
|
1211
|
+
if (!isIndexedDBAvailable()) {
|
|
1212
|
+
return false;
|
|
1213
|
+
}
|
|
1214
|
+
try {
|
|
1215
|
+
const isDBOpenable = await validateIndexedDBOpenable();
|
|
1216
|
+
return isDBOpenable;
|
|
1217
|
+
}
|
|
1218
|
+
catch (error) {
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
1221
|
}
|
|
1222
1222
|
|
|
1223
|
-
/**
|
|
1224
|
-
* The Firebase Remote Config Web SDK.
|
|
1225
|
-
* This SDK does not work in a Node.js environment.
|
|
1226
|
-
*
|
|
1227
|
-
* @packageDocumentation
|
|
1228
|
-
*/
|
|
1229
|
-
/** register component and version */
|
|
1223
|
+
/**
|
|
1224
|
+
* The Firebase Remote Config Web SDK.
|
|
1225
|
+
* This SDK does not work in a Node.js environment.
|
|
1226
|
+
*
|
|
1227
|
+
* @packageDocumentation
|
|
1228
|
+
*/
|
|
1229
|
+
/** register component and version */
|
|
1230
1230
|
registerRemoteConfig();
|
|
1231
1231
|
|
|
1232
1232
|
export { activate, ensureInitialized, fetchAndActivate, fetchConfig, getAll, getBoolean, getNumber, getRemoteConfig, getString, getValue, isSupported, setLogLevel };
|