@dereekb/oauth-resource 14.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/express/index.d.ts +1 -0
- package/express/index.esm.js +321 -0
- package/express/package.json +29 -0
- package/express/src/index.d.ts +1 -0
- package/express/src/lib/bearer.middleware.d.ts +78 -0
- package/express/src/lib/index.d.ts +2 -0
- package/express/src/lib/well-known.router.d.ts +35 -0
- package/firebase/index.d.ts +1 -0
- package/firebase/index.esm.js +1924 -0
- package/firebase/package.json +27 -0
- package/firebase/src/index.d.ts +1 -0
- package/firebase/src/lib/firestore/firestore.sdk-identity.d.ts +165 -0
- package/firebase/src/lib/firestore/index.d.ts +1 -0
- package/firebase/src/lib/index.d.ts +2 -0
- package/firebase/src/lib/session/firebase-client.config.d.ts +86 -0
- package/firebase/src/lib/session/firebase-user-session.d.ts +223 -0
- package/firebase/src/lib/session/firebase-user-session.pool.d.ts +168 -0
- package/firebase/src/lib/session/firestore-session.cache.d.ts +79 -0
- package/firebase/src/lib/session/firestore-session.client.d.ts +149 -0
- package/firebase/src/lib/session/index.d.ts +5 -0
- package/index.d.ts +1 -0
- package/index.esm.js +1066 -0
- package/package.json +53 -0
- package/src/index.d.ts +1 -0
- package/src/lib/auth/index.d.ts +1 -0
- package/src/lib/auth/oauth.resource.auth.d.ts +55 -0
- package/src/lib/challenge/bearer.challenge.d.ts +73 -0
- package/src/lib/challenge/index.d.ts +1 -0
- package/src/lib/error/index.d.ts +1 -0
- package/src/lib/error/oauth.resource.error.d.ts +76 -0
- package/src/lib/index.d.ts +6 -0
- package/src/lib/issuer/index.d.ts +1 -0
- package/src/lib/issuer/issuer.profile.d.ts +98 -0
- package/src/lib/metadata/index.d.ts +1 -0
- package/src/lib/metadata/protected-resource.metadata.d.ts +64 -0
- package/src/lib/verify/index.d.ts +1 -0
- package/src/lib/verify/verify.bearer.d.ts +95 -0
|
@@ -0,0 +1,1924 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { Firestore, getFirestore, connectFirestoreEmulator } from 'firebase/firestore';
|
|
4
|
+
import { initializeApp, deleteApp, getApps } from 'firebase/app';
|
|
5
|
+
import { initializeAppCheck, CustomProvider } from 'firebase/app-check';
|
|
6
|
+
import { signInWithCustomToken, getAuth, connectAuthEmulator } from 'firebase/auth';
|
|
7
|
+
import { clientFirebaseFirestoreContextFactory } from '@dereekb/firebase';
|
|
8
|
+
import { BaseError } from 'make-error';
|
|
9
|
+
import { expirationDetails } from '@dereekb/util';
|
|
10
|
+
|
|
11
|
+
function _define_property$3(obj, key, value) {
|
|
12
|
+
if (key in obj) {
|
|
13
|
+
Object.defineProperty(obj, key, {
|
|
14
|
+
value: value,
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true
|
|
18
|
+
});
|
|
19
|
+
} else obj[key] = value;
|
|
20
|
+
return obj;
|
|
21
|
+
}
|
|
22
|
+
function _instanceof$2(left, right) {
|
|
23
|
+
"@swc/helpers - instanceof";
|
|
24
|
+
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
|
|
25
|
+
return !!right[Symbol.hasInstance](left);
|
|
26
|
+
} else return left instanceof right;
|
|
27
|
+
}
|
|
28
|
+
function _object_spread$2(target) {
|
|
29
|
+
for(var i = 1; i < arguments.length; i++){
|
|
30
|
+
var source = arguments[i] != null ? arguments[i] : {};
|
|
31
|
+
var ownKeys = Object.keys(source);
|
|
32
|
+
if (typeof Object.getOwnPropertySymbols === "function") {
|
|
33
|
+
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
34
|
+
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
ownKeys.forEach(function(key) {
|
|
38
|
+
_define_property$3(target, key, source[key]);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return target;
|
|
42
|
+
}
|
|
43
|
+
function ownKeys$2(object, enumerableOnly) {
|
|
44
|
+
var keys = Object.keys(object);
|
|
45
|
+
if (Object.getOwnPropertySymbols) {
|
|
46
|
+
var symbols = Object.getOwnPropertySymbols(object);
|
|
47
|
+
keys.push.apply(keys, symbols);
|
|
48
|
+
}
|
|
49
|
+
return keys;
|
|
50
|
+
}
|
|
51
|
+
function _object_spread_props$2(target, source) {
|
|
52
|
+
source = source != null ? source : {};
|
|
53
|
+
if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
54
|
+
else {
|
|
55
|
+
ownKeys$2(Object(source)).forEach(function(key) {
|
|
56
|
+
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return target;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The `drivers.firestoreDriverIdentifier` a client-SDK `FirestoreContext` reports.
|
|
63
|
+
*
|
|
64
|
+
* `clientFirebaseFirestoreContextFactory` stamps this; the server's
|
|
65
|
+
* `googleCloudFirestoreContextFactory` stamps `@google-cloud/firestore` instead. Comparing against
|
|
66
|
+
* it is how {@link inspectFirebaseClientFirestoreIdentity} tells an admin-SDK context that reached
|
|
67
|
+
* the client-SDK read path apart from a genuinely broken client one.
|
|
68
|
+
*/ var CLIENT_FIRESTORE_DRIVER_IDENTIFIER = '@firebase/firestore';
|
|
69
|
+
/**
|
|
70
|
+
* Default {@link InspectFirebaseClientFirestoreIdentityInput.consumerName}.
|
|
71
|
+
*/ var DEFAULT_FIRESTORE_IDENTITY_CONSUMER_NAME = '@dereekb/oauth-resource/firebase';
|
|
72
|
+
/**
|
|
73
|
+
* Checks that a session's Firestore handle is one THIS copy of the client SDK will accept, and
|
|
74
|
+
* reports where every consumer resolved the SDK from.
|
|
75
|
+
*
|
|
76
|
+
* Exists because `collection()` refuses any non-`Firestore` first argument with one message —
|
|
77
|
+
* `Expected first argument to collection() to be a CollectionReference, a DocumentReference or
|
|
78
|
+
* FirebaseFirestore` — that names neither the model, nor the collection, nor which of the four
|
|
79
|
+
* distinct causes ({@link FirebaseClientFirestoreIdentityProblem}) produced it.
|
|
80
|
+
*
|
|
81
|
+
* Lives in this package rather than in a consumer because the `instanceof Firestore` brand check
|
|
82
|
+
* must be performed by the module that CONSTRUCTS the handle, and that module is
|
|
83
|
+
* `openFirebaseUserSession`.
|
|
84
|
+
*
|
|
85
|
+
* The `instanceof` test is the load-bearing one and it is deliberately NOT structural: an identity
|
|
86
|
+
* check across the package boundary is the only thing that can detect a duplicated
|
|
87
|
+
* `@firebase/firestore`, which is exactly what a structural check would hide.
|
|
88
|
+
*
|
|
89
|
+
* @param input - The session's `firestoreContext`, when one was built, plus the consumer's
|
|
90
|
+
* resolution root.
|
|
91
|
+
* @returns The provenance + brand-check report.
|
|
92
|
+
*/ function inspectFirebaseClientFirestoreIdentity(input) {
|
|
93
|
+
var _input_firestoreContext, _input_firestoreContext_drivers, _input_firestoreContext1;
|
|
94
|
+
var firestore = (_input_firestoreContext = input.firestoreContext) === null || _input_firestoreContext === void 0 ? void 0 : _input_firestoreContext.firestore;
|
|
95
|
+
var driverIdentifier = (_input_firestoreContext1 = input.firestoreContext) === null || _input_firestoreContext1 === void 0 ? void 0 : (_input_firestoreContext_drivers = _input_firestoreContext1.drivers) === null || _input_firestoreContext_drivers === void 0 ? void 0 : _input_firestoreContext_drivers.firestoreDriverIdentifier;
|
|
96
|
+
var firestoreDriverIdentifier = typeof driverIdentifier === 'string' ? driverIdentifier : undefined;
|
|
97
|
+
var firestorePresent = firestore != null;
|
|
98
|
+
var firestoreIsSdkInstance = _instanceof$2(firestore, Firestore);
|
|
99
|
+
var firestoreConstructor = firestoreConstructorName(firestore);
|
|
100
|
+
var modules = resolveFirebaseClientFirestoreSdkModules(input.consumerRequire);
|
|
101
|
+
var sdkDuplicated = modules.sdkFromConsumer.packageDir != null && modules.sdkFromDbxFirebase.packageDir != null && modules.sdkFromConsumer.packageDir !== modules.sdkFromDbxFirebase.packageDir;
|
|
102
|
+
var problem;
|
|
103
|
+
if (!firestorePresent) {
|
|
104
|
+
problem = 'no-firestore-handle';
|
|
105
|
+
} else if (firestoreDriverIdentifier != null && firestoreDriverIdentifier !== CLIENT_FIRESTORE_DRIVER_IDENTIFIER) {
|
|
106
|
+
problem = 'unexpected-driver';
|
|
107
|
+
} else if (!firestoreIsSdkInstance) {
|
|
108
|
+
problem = sdkDuplicated ? 'duplicated-firestore-sdk' : 'foreign-firestore-instance';
|
|
109
|
+
}
|
|
110
|
+
return _object_spread_props$2(_object_spread$2(_object_spread_props$2(_object_spread$2({
|
|
111
|
+
ok: problem == null
|
|
112
|
+
}, problem == null ? {} : {
|
|
113
|
+
problem: problem
|
|
114
|
+
}), {
|
|
115
|
+
firestorePresent: firestorePresent,
|
|
116
|
+
firestoreIsSdkInstance: firestoreIsSdkInstance
|
|
117
|
+
}), firestoreConstructor == null ? {} : {
|
|
118
|
+
firestoreConstructor: firestoreConstructor
|
|
119
|
+
}, firestoreDriverIdentifier == null ? {} : {
|
|
120
|
+
firestoreDriverIdentifier: firestoreDriverIdentifier
|
|
121
|
+
}, modules), {
|
|
122
|
+
sdkDuplicated: sdkDuplicated
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The actionable next step for a failed {@link inspectFirebaseClientFirestoreIdentity}, keyed on
|
|
127
|
+
* which problem was found.
|
|
128
|
+
*
|
|
129
|
+
* Each branch names the fix rather than the symptom — a raw SDK sentence sends an operator looking at
|
|
130
|
+
* security rules and App Check, neither of which is ever the cause here.
|
|
131
|
+
*
|
|
132
|
+
* @param report - The report to describe.
|
|
133
|
+
* @param overrides - Per-problem replacement text, for a consumer whose operator-facing wording
|
|
134
|
+
* names its own remediation (`re-run with --verbose`, `rebuild the CLI`, …).
|
|
135
|
+
* @param consumerName - Names the consumer in the duplicated-SDK text. Defaults to
|
|
136
|
+
* {@link DEFAULT_FIRESTORE_IDENTITY_CONSUMER_NAME}.
|
|
137
|
+
* @returns The suggestion, or `undefined` when the report passed.
|
|
138
|
+
*
|
|
139
|
+
* @__NO_SIDE_EFFECTS__
|
|
140
|
+
*/ function firebaseClientFirestoreIdentitySuggestion(report, overrides, consumerName) {
|
|
141
|
+
var consumer = consumerName || DEFAULT_FIRESTORE_IDENTITY_CONSUMER_NAME;
|
|
142
|
+
var override = report.problem == null ? undefined : overrides === null || overrides === void 0 ? void 0 : overrides[report.problem];
|
|
143
|
+
var result;
|
|
144
|
+
if (override == null) {
|
|
145
|
+
switch(report.problem){
|
|
146
|
+
case 'no-firestore-handle':
|
|
147
|
+
result = "The session produced no Firestore handle, so every collection reference built from it fails. This is a wiring fault, not a rules or App Check fault — verify the handshake completed and that ".concat(consumer, " resolves ONE `@dereekb/firebase`.");
|
|
148
|
+
break;
|
|
149
|
+
case 'unexpected-driver':
|
|
150
|
+
result = 'The `FirestoreContext` reports driver "'.concat(report.firestoreDriverIdentifier, '", not "').concat(CLIENT_FIRESTORE_DRIVER_IDENTIFIER, '". An admin-SDK context reached the client-SDK read path — build the context with `clientFirebaseFirestoreContextFactory`, not `googleCloudFirestoreContextFactory`.');
|
|
151
|
+
break;
|
|
152
|
+
case 'duplicated-firestore-sdk':
|
|
153
|
+
result = "Two copies of `@firebase/firestore` are loaded — ".concat(consumer, " resolves ").concat(report.sdkFromConsumer.packageDir, ", `@dereekb/firebase` resolves ").concat(report.sdkFromDbxFirebase.packageDir, " — so the handle one minted fails the other's brand check. Dedupe the install (`npm ls @firebase/firestore`) and rebuild.");
|
|
154
|
+
break;
|
|
155
|
+
case 'foreign-firestore-instance':
|
|
156
|
+
var _report_firestoreConstructor;
|
|
157
|
+
result = "The session's handle (`".concat((_report_firestoreConstructor = report.firestoreConstructor) !== null && _report_firestoreConstructor !== void 0 ? _report_firestoreConstructor : 'unknown', "`) is not an instance of the loaded SDK's `Firestore`, and only one copy of `@firebase/firestore` is installed. The likeliest cause is a STALE build artifact running against a newer `@dereekb/*` in `node_modules` — rebuild ").concat(consumer, "'s consumer and re-run.");
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
result = undefined;
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
result = override;
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
// MARK: Internal
|
|
169
|
+
/**
|
|
170
|
+
* The handle's constructor name, when it has one.
|
|
171
|
+
*
|
|
172
|
+
* @param firestore - The candidate handle.
|
|
173
|
+
* @returns The constructor name, or `undefined`.
|
|
174
|
+
*/ function firestoreConstructorName(firestore) {
|
|
175
|
+
var _firestore_constructor;
|
|
176
|
+
var name = firestore == null ? undefined : (_firestore_constructor = firestore.constructor) === null || _firestore_constructor === void 0 ? void 0 : _firestore_constructor.name;
|
|
177
|
+
return typeof name === 'string' ? name : undefined;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Resolves `@firebase/firestore` from both the consumer and `@dereekb/firebase`, plus the `firebase`
|
|
181
|
+
* umbrella version.
|
|
182
|
+
*
|
|
183
|
+
* `createRequire` rather than `import.meta.resolve` because the resolution has to be performed
|
|
184
|
+
* relative to ANOTHER package's location (`@dereekb/firebase`'s entry file) to answer the question
|
|
185
|
+
* that matters — whether the two consumers see the same copy.
|
|
186
|
+
*
|
|
187
|
+
* @param consumerRequire - The consumer's own `require`. Defaults to this package's.
|
|
188
|
+
* @returns The two module identities and the umbrella version, each degrading to an `error` field
|
|
189
|
+
* rather than throwing: this runs inside a diagnostic, and a failed resolution is itself a finding.
|
|
190
|
+
*/ function resolveFirebaseClientFirestoreSdkModules(consumerRequire) {
|
|
191
|
+
var requireFromConsumer = consumerRequire !== null && consumerRequire !== void 0 ? consumerRequire : createRequire(import.meta.url);
|
|
192
|
+
var sdkFromConsumer = resolveFirestoreSdkIdentity(requireFromConsumer);
|
|
193
|
+
var sdkFromDbxFirebase;
|
|
194
|
+
try {
|
|
195
|
+
sdkFromDbxFirebase = resolveFirestoreSdkIdentity(createRequire(requireFromConsumer.resolve('@dereekb/firebase')));
|
|
196
|
+
} catch (e) {
|
|
197
|
+
sdkFromDbxFirebase = {
|
|
198
|
+
error: _instanceof$2(e, Error) ? e.message : String(e)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
var firebaseVersion;
|
|
202
|
+
try {
|
|
203
|
+
firebaseVersion = readPackageVersion(requireFromConsumer, 'firebase/package.json');
|
|
204
|
+
} catch (unused) {
|
|
205
|
+
// the umbrella version is context for the report, not a finding on its own
|
|
206
|
+
firebaseVersion = undefined;
|
|
207
|
+
}
|
|
208
|
+
return _object_spread$2({
|
|
209
|
+
sdkFromConsumer: sdkFromConsumer,
|
|
210
|
+
sdkFromDbxFirebase: sdkFromDbxFirebase
|
|
211
|
+
}, firebaseVersion == null ? {} : {
|
|
212
|
+
firebaseVersion: firebaseVersion
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Resolves `@firebase/firestore`'s package directory + version through one `require`.
|
|
217
|
+
*
|
|
218
|
+
* Resolves `package.json` rather than the package entry because the entry differs per export
|
|
219
|
+
* condition (`index.node.cjs.js` vs `index.node.mjs`), which would report two copies where there is
|
|
220
|
+
* one. The `package.json` path is condition-independent, so its directory is a stable identity.
|
|
221
|
+
*
|
|
222
|
+
* @param resolver - The `require` to resolve through.
|
|
223
|
+
* @returns The module identity, with `error` set when resolution failed.
|
|
224
|
+
*/ function resolveFirestoreSdkIdentity(resolver) {
|
|
225
|
+
var result;
|
|
226
|
+
try {
|
|
227
|
+
var packageJsonPath = resolver.resolve('@firebase/firestore/package.json');
|
|
228
|
+
var version = readPackageVersion(resolver, '@firebase/firestore/package.json');
|
|
229
|
+
result = _object_spread$2({
|
|
230
|
+
packageDir: dirname(packageJsonPath)
|
|
231
|
+
}, version == null ? {} : {
|
|
232
|
+
version: version
|
|
233
|
+
});
|
|
234
|
+
} catch (e) {
|
|
235
|
+
result = {
|
|
236
|
+
error: _instanceof$2(e, Error) ? e.message : String(e)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Reads a resolvable `package.json`'s `version`.
|
|
243
|
+
*
|
|
244
|
+
* @param resolver - The `require` to load through.
|
|
245
|
+
* @param specifier - The `package.json` specifier.
|
|
246
|
+
* @returns The version string, or `undefined` when absent.
|
|
247
|
+
*/ function readPackageVersion(resolver, specifier) {
|
|
248
|
+
var contents = resolver(specifier);
|
|
249
|
+
return typeof contents.version === 'string' ? contents.version : undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Default host used for Firebase emulator connections when a {@link FirebaseClientEmulatorsConfig}
|
|
254
|
+
* omits one.
|
|
255
|
+
*/ var DEFAULT_FIREBASE_CLIENT_EMULATOR_HOST = 'localhost';
|
|
256
|
+
/**
|
|
257
|
+
* Returns true when the config carries the minimum Firebase client values needed to open a
|
|
258
|
+
* user-scoped Firestore session.
|
|
259
|
+
*
|
|
260
|
+
* Deliberately a standalone predicate rather than a field of a wider "config is complete" check: a
|
|
261
|
+
* host's Firebase config is optional, and folding it into a general completeness check would break
|
|
262
|
+
* every consumer that only talks to the HTTP API.
|
|
263
|
+
*
|
|
264
|
+
* @param firebase - The Firebase client config, if any.
|
|
265
|
+
* @returns `true` when `apiKey`, `projectId`, and `appId` are all present and non-empty.
|
|
266
|
+
*/ function isFirebaseClientConfigComplete(firebase) {
|
|
267
|
+
return Boolean((firebase === null || firebase === void 0 ? void 0 : firebase.apiKey) && (firebase === null || firebase === void 0 ? void 0 : firebase.projectId) && (firebase === null || firebase === void 0 ? void 0 : firebase.appId));
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Returns true when the config's emulator targets are present and active.
|
|
271
|
+
*
|
|
272
|
+
* @param firebase - The Firebase client config, if any.
|
|
273
|
+
* @returns `true` when emulators are configured and not explicitly disabled.
|
|
274
|
+
*/ function firebaseClientEmulatorsInUse(firebase) {
|
|
275
|
+
var emulators = firebase === null || firebase === void 0 ? void 0 : firebase.emulators;
|
|
276
|
+
return Boolean(emulators && emulators.useEmulators !== false && (emulators.authPort != null || emulators.firestorePort != null));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function _assert_this_initialized(self) {
|
|
280
|
+
if (self === void 0) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
|
281
|
+
return self;
|
|
282
|
+
}
|
|
283
|
+
function asyncGeneratorStep$2(gen, resolve, reject, _next, _throw, key, arg) {
|
|
284
|
+
try {
|
|
285
|
+
var info = gen[key](arg);
|
|
286
|
+
var value = info.value;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
reject(error);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (info.done) resolve(value);
|
|
292
|
+
else Promise.resolve(value).then(_next, _throw);
|
|
293
|
+
}
|
|
294
|
+
function _async_to_generator$2(fn) {
|
|
295
|
+
return function() {
|
|
296
|
+
var self = this, args = arguments;
|
|
297
|
+
return new Promise(function(resolve, reject) {
|
|
298
|
+
var gen = fn.apply(self, args);
|
|
299
|
+
function _next(value) {
|
|
300
|
+
asyncGeneratorStep$2(gen, resolve, reject, _next, _throw, "next", value);
|
|
301
|
+
}
|
|
302
|
+
function _throw(err) {
|
|
303
|
+
asyncGeneratorStep$2(gen, resolve, reject, _next, _throw, "throw", err);
|
|
304
|
+
}
|
|
305
|
+
_next(undefined);
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function _call_super(_this, derived, args) {
|
|
310
|
+
derived = _get_prototype_of(derived);
|
|
311
|
+
return _possible_constructor_return(_this, _is_native_reflect_construct() ? Reflect.construct(derived, args || [], _get_prototype_of(_this).constructor) : derived.apply(_this, args));
|
|
312
|
+
}
|
|
313
|
+
function _class_call_check(instance, Constructor) {
|
|
314
|
+
if (!(instance instanceof Constructor)) throw new TypeError("Cannot call a class as a function");
|
|
315
|
+
}
|
|
316
|
+
function _define_property$2(obj, key, value) {
|
|
317
|
+
if (key in obj) {
|
|
318
|
+
Object.defineProperty(obj, key, {
|
|
319
|
+
value: value,
|
|
320
|
+
enumerable: true,
|
|
321
|
+
configurable: true,
|
|
322
|
+
writable: true
|
|
323
|
+
});
|
|
324
|
+
} else obj[key] = value;
|
|
325
|
+
return obj;
|
|
326
|
+
}
|
|
327
|
+
function _get_prototype_of(o) {
|
|
328
|
+
_get_prototype_of = Object.setPrototypeOf ? Object.getPrototypeOf : function getPrototypeOf(o) {
|
|
329
|
+
return o.__proto__ || Object.getPrototypeOf(o);
|
|
330
|
+
};
|
|
331
|
+
return _get_prototype_of(o);
|
|
332
|
+
}
|
|
333
|
+
function _inherits(subClass, superClass) {
|
|
334
|
+
if (typeof superClass !== "function" && superClass !== null) {
|
|
335
|
+
throw new TypeError("Super expression must either be null or a function");
|
|
336
|
+
}
|
|
337
|
+
subClass.prototype = Object.create(superClass && superClass.prototype, {
|
|
338
|
+
constructor: {
|
|
339
|
+
value: subClass,
|
|
340
|
+
writable: true,
|
|
341
|
+
configurable: true
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
if (superClass) _set_prototype_of(subClass, superClass);
|
|
345
|
+
}
|
|
346
|
+
function _instanceof$1(left, right) {
|
|
347
|
+
"@swc/helpers - instanceof";
|
|
348
|
+
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
|
|
349
|
+
return !!right[Symbol.hasInstance](left);
|
|
350
|
+
} else return left instanceof right;
|
|
351
|
+
}
|
|
352
|
+
function _is_native_reflect_construct() {
|
|
353
|
+
try {
|
|
354
|
+
var result = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}));
|
|
355
|
+
} catch (_) {}
|
|
356
|
+
return (_is_native_reflect_construct = function() {
|
|
357
|
+
return !!result;
|
|
358
|
+
})();
|
|
359
|
+
}
|
|
360
|
+
function _possible_constructor_return(self, call) {
|
|
361
|
+
if (call && (_type_of(call) === "object" || typeof call === "function")) return call;
|
|
362
|
+
return _assert_this_initialized(self);
|
|
363
|
+
}
|
|
364
|
+
function _set_prototype_of(o, p) {
|
|
365
|
+
_set_prototype_of = Object.setPrototypeOf || function setPrototypeOf(o, p) {
|
|
366
|
+
o.__proto__ = p;
|
|
367
|
+
return o;
|
|
368
|
+
};
|
|
369
|
+
return _set_prototype_of(o, p);
|
|
370
|
+
}
|
|
371
|
+
function _ts_generator$2(thisArg, body) {
|
|
372
|
+
var f, y, t, _ = {
|
|
373
|
+
label: 0,
|
|
374
|
+
sent: function() {
|
|
375
|
+
if (t[0] & 1) throw t[1];
|
|
376
|
+
return t[1];
|
|
377
|
+
},
|
|
378
|
+
trys: [],
|
|
379
|
+
ops: []
|
|
380
|
+
}, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
|
|
381
|
+
return d(g, "next", {
|
|
382
|
+
value: verb(0)
|
|
383
|
+
}), d(g, "throw", {
|
|
384
|
+
value: verb(1)
|
|
385
|
+
}), d(g, "return", {
|
|
386
|
+
value: verb(2)
|
|
387
|
+
}), typeof Symbol === "function" && d(g, Symbol.iterator, {
|
|
388
|
+
value: function() {
|
|
389
|
+
return this;
|
|
390
|
+
}
|
|
391
|
+
}), g;
|
|
392
|
+
function verb(n) {
|
|
393
|
+
return function(v) {
|
|
394
|
+
return step([
|
|
395
|
+
n,
|
|
396
|
+
v
|
|
397
|
+
]);
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function step(op) {
|
|
401
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
402
|
+
while(g && (g = 0, op[0] && (_ = 0)), _)try {
|
|
403
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
404
|
+
if (y = 0, t) op = [
|
|
405
|
+
op[0] & 2,
|
|
406
|
+
t.value
|
|
407
|
+
];
|
|
408
|
+
switch(op[0]){
|
|
409
|
+
case 0:
|
|
410
|
+
case 1:
|
|
411
|
+
t = op;
|
|
412
|
+
break;
|
|
413
|
+
case 4:
|
|
414
|
+
_.label++;
|
|
415
|
+
return {
|
|
416
|
+
value: op[1],
|
|
417
|
+
done: false
|
|
418
|
+
};
|
|
419
|
+
case 5:
|
|
420
|
+
_.label++;
|
|
421
|
+
y = op[1];
|
|
422
|
+
op = [
|
|
423
|
+
0
|
|
424
|
+
];
|
|
425
|
+
continue;
|
|
426
|
+
case 7:
|
|
427
|
+
op = _.ops.pop();
|
|
428
|
+
_.trys.pop();
|
|
429
|
+
continue;
|
|
430
|
+
default:
|
|
431
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
|
|
432
|
+
_ = 0;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
|
|
436
|
+
_.label = op[1];
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
if (op[0] === 6 && _.label < t[1]) {
|
|
440
|
+
_.label = t[1];
|
|
441
|
+
t = op;
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
if (t && _.label < t[2]) {
|
|
445
|
+
_.label = t[2];
|
|
446
|
+
_.ops.push(op);
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
if (t[2]) _.ops.pop();
|
|
450
|
+
_.trys.pop();
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
op = body.call(thisArg, _);
|
|
454
|
+
} catch (e) {
|
|
455
|
+
op = [
|
|
456
|
+
6,
|
|
457
|
+
e
|
|
458
|
+
];
|
|
459
|
+
y = 0;
|
|
460
|
+
} finally{
|
|
461
|
+
f = t = 0;
|
|
462
|
+
}
|
|
463
|
+
if (op[0] & 5) throw op[1];
|
|
464
|
+
return {
|
|
465
|
+
value: op[0] ? op[1] : void 0,
|
|
466
|
+
done: true
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function _type_of(obj) {
|
|
471
|
+
"@swc/helpers - typeof";
|
|
472
|
+
return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Path (relative to the API base URL) of the user-scoped Firestore session endpoint served by
|
|
476
|
+
* `@dereekb/firebase-server`'s `SessionApiController`.
|
|
477
|
+
*
|
|
478
|
+
* Duplicated here rather than imported because importing it from `@dereekb/firebase-server` would be
|
|
479
|
+
* a dependency CYCLE: `firebase-server` already depends on `@dereekb/oauth-resource`
|
|
480
|
+
* (`packages/firebase-server/oidc/src/lib/service/oidc.service.ts`). The source of truth is
|
|
481
|
+
* `packages/firebase-server/src/lib/nest/controller/session/session.api.config.ts`.
|
|
482
|
+
*/ var FIRESTORE_SESSION_API_PATH = "/session/firestore";
|
|
483
|
+
/**
|
|
484
|
+
* Error raised when a user-scoped Firestore session cannot be obtained.
|
|
485
|
+
*
|
|
486
|
+
* Consumers that already have their own error type do not have to catch and re-wrap this one: pass a
|
|
487
|
+
* {@link FirestoreSessionErrorFactory} and the session functions throw that type instead — the same
|
|
488
|
+
* seam `OAuthResourceErrorFactory` provides for verification.
|
|
489
|
+
*/ var FirestoreSessionError = /*#__PURE__*/ function(BaseError) {
|
|
490
|
+
_inherits(FirestoreSessionError, BaseError);
|
|
491
|
+
function FirestoreSessionError(input) {
|
|
492
|
+
_class_call_check(this, FirestoreSessionError);
|
|
493
|
+
var _this;
|
|
494
|
+
_this = _call_super(this, FirestoreSessionError, [
|
|
495
|
+
input.message
|
|
496
|
+
]), _define_property$2(_this, "code", void 0), _define_property$2(_this, "status", void 0), _define_property$2(_this, "suggestion", void 0), _define_property$2(_this, "details", void 0);
|
|
497
|
+
_this.code = input.code;
|
|
498
|
+
if (input.status != null) {
|
|
499
|
+
_this.status = input.status;
|
|
500
|
+
}
|
|
501
|
+
if (input.suggestion != null) {
|
|
502
|
+
_this.suggestion = input.suggestion;
|
|
503
|
+
}
|
|
504
|
+
if (input.details != null) {
|
|
505
|
+
_this.details = input.details;
|
|
506
|
+
}
|
|
507
|
+
return _this;
|
|
508
|
+
}
|
|
509
|
+
return FirestoreSessionError;
|
|
510
|
+
}(BaseError);
|
|
511
|
+
/**
|
|
512
|
+
* Default {@link FirestoreSessionErrorFactory}, producing a {@link FirestoreSessionError}.
|
|
513
|
+
*
|
|
514
|
+
* @param input - The code, message, status, suggestion, and details of the failure.
|
|
515
|
+
* @returns The error to throw.
|
|
516
|
+
*/ var defaultFirestoreSessionErrorFactory = function defaultFirestoreSessionErrorFactory(input) {
|
|
517
|
+
return new FirestoreSessionError(input);
|
|
518
|
+
};
|
|
519
|
+
/**
|
|
520
|
+
* Returns true when the input is a {@link FirestoreSessionError}.
|
|
521
|
+
*
|
|
522
|
+
* @param error - The value to test.
|
|
523
|
+
* @returns Whether the value is a {@link FirestoreSessionError}.
|
|
524
|
+
*/ function isFirestoreSessionError(error) {
|
|
525
|
+
return _instanceof$1(error, FirestoreSessionError);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Maps an HTTP status from the mint endpoint onto a {@link FirestoreSessionErrorCode}.
|
|
529
|
+
*
|
|
530
|
+
* @param status - The response status.
|
|
531
|
+
* @returns The matching error code.
|
|
532
|
+
* @__NO_SIDE_EFFECTS__
|
|
533
|
+
*/ function firestoreSessionErrorCodeForStatus(status) {
|
|
534
|
+
var result;
|
|
535
|
+
if (status === 401) {
|
|
536
|
+
result = 'unauthorized';
|
|
537
|
+
} else if (status === 403) {
|
|
538
|
+
result = 'forbidden';
|
|
539
|
+
} else if (status === 404) {
|
|
540
|
+
result = 'not_found';
|
|
541
|
+
} else if (status >= 500) {
|
|
542
|
+
result = 'unavailable';
|
|
543
|
+
} else {
|
|
544
|
+
result = 'invalid_response';
|
|
545
|
+
}
|
|
546
|
+
return result;
|
|
547
|
+
}
|
|
548
|
+
// MARK: Fetch
|
|
549
|
+
/**
|
|
550
|
+
* Composes the session endpoint's absolute URL from an API base URL.
|
|
551
|
+
*
|
|
552
|
+
* @param apiBaseUrl - The API base URL, with or without a trailing slash.
|
|
553
|
+
* @returns The absolute `/session/firestore` URL.
|
|
554
|
+
* @__NO_SIDE_EFFECTS__
|
|
555
|
+
*/ function firestoreSessionUrl(apiBaseUrl) {
|
|
556
|
+
return "".concat(trimSlash(apiBaseUrl)).concat(FIRESTORE_SESSION_API_PATH);
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Fetches a user-scoped Firestore session from the API with a Bearer access token.
|
|
560
|
+
*
|
|
561
|
+
* The endpoint is admin-only and additionally gated on the `session.firestore` OIDC scope, so a 403
|
|
562
|
+
* here usually means the token's user is not an admin or the token was issued without that scope.
|
|
563
|
+
*
|
|
564
|
+
* @param input - The API target, access token, and optional fetch/error overrides.
|
|
565
|
+
* @returns The parsed {@link FirestoreSessionCredentials}.
|
|
566
|
+
* @throws {FirestoreSessionError} (or the `errorFactory`'s type) When the endpoint answers non-2xx or returns an unusable body.
|
|
567
|
+
*/ function fetchFirestoreSession(input) {
|
|
568
|
+
return _async_to_generator$2(function() {
|
|
569
|
+
var _input_errorFactory, _input_fetcher, errorFactory, url, fetchImpl, res, text, body, credentials;
|
|
570
|
+
return _ts_generator$2(this, function(_state) {
|
|
571
|
+
switch(_state.label){
|
|
572
|
+
case 0:
|
|
573
|
+
errorFactory = (_input_errorFactory = input.errorFactory) !== null && _input_errorFactory !== void 0 ? _input_errorFactory : defaultFirestoreSessionErrorFactory;
|
|
574
|
+
url = firestoreSessionUrl(input.apiBaseUrl);
|
|
575
|
+
fetchImpl = (_input_fetcher = input.fetcher) !== null && _input_fetcher !== void 0 ? _input_fetcher : fetch;
|
|
576
|
+
return [
|
|
577
|
+
4,
|
|
578
|
+
fetchImpl(url, {
|
|
579
|
+
method: 'GET',
|
|
580
|
+
headers: {
|
|
581
|
+
Accept: 'application/json',
|
|
582
|
+
Authorization: "Bearer ".concat(input.accessToken)
|
|
583
|
+
}
|
|
584
|
+
})
|
|
585
|
+
];
|
|
586
|
+
case 1:
|
|
587
|
+
res = _state.sent();
|
|
588
|
+
return [
|
|
589
|
+
4,
|
|
590
|
+
res.text()
|
|
591
|
+
];
|
|
592
|
+
case 2:
|
|
593
|
+
text = _state.sent();
|
|
594
|
+
if (text.length > 0) {
|
|
595
|
+
try {
|
|
596
|
+
body = JSON.parse(text);
|
|
597
|
+
} catch (unused) {
|
|
598
|
+
body = text;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (!res.ok) {
|
|
602
|
+
throw errorFactory({
|
|
603
|
+
message: "firestore session request failed: ".concat(extractMessage(body, text, res)),
|
|
604
|
+
code: firestoreSessionErrorCodeForStatus(res.status),
|
|
605
|
+
status: res.status,
|
|
606
|
+
suggestion: res.status === 401 || res.status === 403 ? '`/session/firestore` is admin-only and additionally gated on the `session.firestore` OIDC scope. The presented access token must belong to an admin user and carry that scope.' : 'Verify the API exposes `/session/firestore` (the `@dereekb/firebase-server` session module must be registered and `/api/session` added to the OIDC `protectedPaths`).'
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
credentials = body;
|
|
610
|
+
if (!(credentials === null || credentials === void 0 ? void 0 : credentials.customToken)) {
|
|
611
|
+
throw errorFactory({
|
|
612
|
+
message: 'firestore session response did not include a customToken.',
|
|
613
|
+
code: 'invalid_response',
|
|
614
|
+
status: res.status,
|
|
615
|
+
suggestion: 'Verify the API is running a `@dereekb/firebase-server` version that serves `/session/firestore`.'
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
return [
|
|
619
|
+
2,
|
|
620
|
+
credentials
|
|
621
|
+
];
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
})();
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* The most descriptive message available for a failed response.
|
|
628
|
+
*
|
|
629
|
+
* @param body - The parsed body, when one parsed.
|
|
630
|
+
* @param fallback - The raw response text.
|
|
631
|
+
* @param res - The response, for its status line.
|
|
632
|
+
* @returns The message to report.
|
|
633
|
+
*/ function extractMessage(body, fallback, res) {
|
|
634
|
+
var bodyMessage = (typeof body === "undefined" ? "undefined" : _type_of(body)) === 'object' && body && 'message' in body ? body.message : undefined;
|
|
635
|
+
var messageString = typeof bodyMessage === 'string' ? bodyMessage : undefined;
|
|
636
|
+
return messageString !== null && messageString !== void 0 ? messageString : fallback || "".concat(res.status, " ").concat(res.statusText);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Removes a single trailing slash from a URL.
|
|
640
|
+
*
|
|
641
|
+
* @param url - The URL to trim.
|
|
642
|
+
* @returns The URL with no trailing slash.
|
|
643
|
+
*/ function trimSlash(url) {
|
|
644
|
+
return url.endsWith('/') ? url.slice(0, -1) : url;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Hard ceiling on how long a minted user-scoped Firestore session may be reused, regardless of what
|
|
649
|
+
* the API reported in `expiresAt`.
|
|
650
|
+
*
|
|
651
|
+
* One hour, because that is the Firebase ceiling the credentials themselves sit under: a custom
|
|
652
|
+
* token is exchangeable for one hour, and the ID token it mints lives one hour. Holding a session
|
|
653
|
+
* past that buys nothing — the sign-in would fail — and re-minting is one HTTP round-trip.
|
|
654
|
+
*/ var FIRESTORE_SESSION_MAX_CACHE_MS = 60 * 60 * 1000;
|
|
655
|
+
/**
|
|
656
|
+
* Default skew/latency buffer applied when deciding whether a cached session is still usable.
|
|
657
|
+
*/ var FIRESTORE_SESSION_EXPIRY_BUFFER_MS = 60000;
|
|
658
|
+
/**
|
|
659
|
+
* Resolves the epoch-millis instant at which a cached session stops being usable.
|
|
660
|
+
*
|
|
661
|
+
* The effective expiry is the EARLIER of the API-reported `expiresAt` and
|
|
662
|
+
* {@link FIRESTORE_SESSION_MAX_CACHE_MS} past the write. Taking the earlier of the two means a
|
|
663
|
+
* server that reports an over-long (or unparsable) window still cannot push a session past the
|
|
664
|
+
* Firebase credential ceiling.
|
|
665
|
+
*
|
|
666
|
+
* @param entry - The cached entry.
|
|
667
|
+
* @returns The effective expiry in unix epoch milliseconds.
|
|
668
|
+
*
|
|
669
|
+
* @__NO_SIDE_EFFECTS__
|
|
670
|
+
*/ function firestoreSessionEntryExpiresAt(entry) {
|
|
671
|
+
var ceiling = entry.cachedAt + FIRESTORE_SESSION_MAX_CACHE_MS;
|
|
672
|
+
var reported = Date.parse(entry.session.expiresAt);
|
|
673
|
+
return Number.isFinite(reported) ? Math.min(reported, ceiling) : ceiling;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Returns true when the cached session is at or near its effective expiry.
|
|
677
|
+
*
|
|
678
|
+
* @param entry - The cached entry (`null`/`undefined` is treated as expired).
|
|
679
|
+
* @param nowMs - The current time in unix epoch milliseconds. Defaults to `Date.now()`.
|
|
680
|
+
* @param bufferMs - Skew/latency buffer; the entry is treated as expired this far ahead of its effective expiry.
|
|
681
|
+
* @returns `true` when the entry is unusable, otherwise `false`.
|
|
682
|
+
*/ function isFirestoreSessionExpired(entry) {
|
|
683
|
+
var nowMs = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : Date.now(), bufferMs = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : FIRESTORE_SESSION_EXPIRY_BUFFER_MS;
|
|
684
|
+
return entry == null || expirationDetails({
|
|
685
|
+
expiresFromDate: firestoreSessionEntryExpiresAt(entry),
|
|
686
|
+
expiresIn: -bufferMs,
|
|
687
|
+
now: new Date(nowMs)
|
|
688
|
+
}).hasExpired();
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function asyncGeneratorStep$1(gen, resolve, reject, _next, _throw, key, arg) {
|
|
692
|
+
try {
|
|
693
|
+
var info = gen[key](arg);
|
|
694
|
+
var value = info.value;
|
|
695
|
+
} catch (error) {
|
|
696
|
+
reject(error);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
if (info.done) resolve(value);
|
|
700
|
+
else Promise.resolve(value).then(_next, _throw);
|
|
701
|
+
}
|
|
702
|
+
function _async_to_generator$1(fn) {
|
|
703
|
+
return function() {
|
|
704
|
+
var self = this, args = arguments;
|
|
705
|
+
return new Promise(function(resolve, reject) {
|
|
706
|
+
var gen = fn.apply(self, args);
|
|
707
|
+
function _next(value) {
|
|
708
|
+
asyncGeneratorStep$1(gen, resolve, reject, _next, _throw, "next", value);
|
|
709
|
+
}
|
|
710
|
+
function _throw(err) {
|
|
711
|
+
asyncGeneratorStep$1(gen, resolve, reject, _next, _throw, "throw", err);
|
|
712
|
+
}
|
|
713
|
+
_next(undefined);
|
|
714
|
+
});
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function _define_property$1(obj, key, value) {
|
|
718
|
+
if (key in obj) {
|
|
719
|
+
Object.defineProperty(obj, key, {
|
|
720
|
+
value: value,
|
|
721
|
+
enumerable: true,
|
|
722
|
+
configurable: true,
|
|
723
|
+
writable: true
|
|
724
|
+
});
|
|
725
|
+
} else obj[key] = value;
|
|
726
|
+
return obj;
|
|
727
|
+
}
|
|
728
|
+
function _instanceof(left, right) {
|
|
729
|
+
"@swc/helpers - instanceof";
|
|
730
|
+
if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
|
|
731
|
+
return !!right[Symbol.hasInstance](left);
|
|
732
|
+
} else return left instanceof right;
|
|
733
|
+
}
|
|
734
|
+
function _object_spread$1(target) {
|
|
735
|
+
for(var i = 1; i < arguments.length; i++){
|
|
736
|
+
var source = arguments[i] != null ? arguments[i] : {};
|
|
737
|
+
var ownKeys = Object.keys(source);
|
|
738
|
+
if (typeof Object.getOwnPropertySymbols === "function") {
|
|
739
|
+
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
740
|
+
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
741
|
+
}));
|
|
742
|
+
}
|
|
743
|
+
ownKeys.forEach(function(key) {
|
|
744
|
+
_define_property$1(target, key, source[key]);
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
return target;
|
|
748
|
+
}
|
|
749
|
+
function ownKeys$1(object, enumerableOnly) {
|
|
750
|
+
var keys = Object.keys(object);
|
|
751
|
+
if (Object.getOwnPropertySymbols) {
|
|
752
|
+
var symbols = Object.getOwnPropertySymbols(object);
|
|
753
|
+
keys.push.apply(keys, symbols);
|
|
754
|
+
}
|
|
755
|
+
return keys;
|
|
756
|
+
}
|
|
757
|
+
function _object_spread_props$1(target, source) {
|
|
758
|
+
source = source != null ? source : {};
|
|
759
|
+
if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
760
|
+
else {
|
|
761
|
+
ownKeys$1(Object(source)).forEach(function(key) {
|
|
762
|
+
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
return target;
|
|
766
|
+
}
|
|
767
|
+
function _ts_generator$1(thisArg, body) {
|
|
768
|
+
var f, y, t, _ = {
|
|
769
|
+
label: 0,
|
|
770
|
+
sent: function() {
|
|
771
|
+
if (t[0] & 1) throw t[1];
|
|
772
|
+
return t[1];
|
|
773
|
+
},
|
|
774
|
+
trys: [],
|
|
775
|
+
ops: []
|
|
776
|
+
}, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
|
|
777
|
+
return d(g, "next", {
|
|
778
|
+
value: verb(0)
|
|
779
|
+
}), d(g, "throw", {
|
|
780
|
+
value: verb(1)
|
|
781
|
+
}), d(g, "return", {
|
|
782
|
+
value: verb(2)
|
|
783
|
+
}), typeof Symbol === "function" && d(g, Symbol.iterator, {
|
|
784
|
+
value: function() {
|
|
785
|
+
return this;
|
|
786
|
+
}
|
|
787
|
+
}), g;
|
|
788
|
+
function verb(n) {
|
|
789
|
+
return function(v) {
|
|
790
|
+
return step([
|
|
791
|
+
n,
|
|
792
|
+
v
|
|
793
|
+
]);
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
function step(op) {
|
|
797
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
798
|
+
while(g && (g = 0, op[0] && (_ = 0)), _)try {
|
|
799
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
800
|
+
if (y = 0, t) op = [
|
|
801
|
+
op[0] & 2,
|
|
802
|
+
t.value
|
|
803
|
+
];
|
|
804
|
+
switch(op[0]){
|
|
805
|
+
case 0:
|
|
806
|
+
case 1:
|
|
807
|
+
t = op;
|
|
808
|
+
break;
|
|
809
|
+
case 4:
|
|
810
|
+
_.label++;
|
|
811
|
+
return {
|
|
812
|
+
value: op[1],
|
|
813
|
+
done: false
|
|
814
|
+
};
|
|
815
|
+
case 5:
|
|
816
|
+
_.label++;
|
|
817
|
+
y = op[1];
|
|
818
|
+
op = [
|
|
819
|
+
0
|
|
820
|
+
];
|
|
821
|
+
continue;
|
|
822
|
+
case 7:
|
|
823
|
+
op = _.ops.pop();
|
|
824
|
+
_.trys.pop();
|
|
825
|
+
continue;
|
|
826
|
+
default:
|
|
827
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
|
|
828
|
+
_ = 0;
|
|
829
|
+
continue;
|
|
830
|
+
}
|
|
831
|
+
if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
|
|
832
|
+
_.label = op[1];
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
if (op[0] === 6 && _.label < t[1]) {
|
|
836
|
+
_.label = t[1];
|
|
837
|
+
t = op;
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
if (t && _.label < t[2]) {
|
|
841
|
+
_.label = t[2];
|
|
842
|
+
_.ops.push(op);
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
if (t[2]) _.ops.pop();
|
|
846
|
+
_.trys.pop();
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
op = body.call(thisArg, _);
|
|
850
|
+
} catch (e) {
|
|
851
|
+
op = [
|
|
852
|
+
6,
|
|
853
|
+
e
|
|
854
|
+
];
|
|
855
|
+
y = 0;
|
|
856
|
+
} finally{
|
|
857
|
+
f = t = 0;
|
|
858
|
+
}
|
|
859
|
+
if (op[0] & 5) throw op[1];
|
|
860
|
+
return {
|
|
861
|
+
value: op[0] ? op[1] : void 0,
|
|
862
|
+
done: true
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
// MARK: App Name
|
|
867
|
+
/**
|
|
868
|
+
* Separator between the segments of a {@link firebaseUserSessionAppName}.
|
|
869
|
+
*
|
|
870
|
+
* `::` rather than `-` because a Firebase project id is `[a-z0-9-]+`, so a single `-` makes
|
|
871
|
+
* `(ns='srv', scope='a-b', uid='c')` and `(ns='srv', scope='a', uid='b-c')` collide on `srv-a-b-c`.
|
|
872
|
+
* `:` cannot appear in a project id, so `::` is collision-free for any namespace that does not itself
|
|
873
|
+
* contain `::`.
|
|
874
|
+
*/ var FIREBASE_USER_SESSION_KEY_SEPARATOR = '::';
|
|
875
|
+
/**
|
|
876
|
+
* The base Firebase app name a user-scoped session registers.
|
|
877
|
+
*
|
|
878
|
+
* A DERIVED name rather than a side registry is what lets {@link closeFirebaseUserSessionApps} find
|
|
879
|
+
* every app this owner opened from `getApps()` alone — no registry to keep in sync, and idempotent by
|
|
880
|
+
* construction.
|
|
881
|
+
*
|
|
882
|
+
* Note this is the BASE name: {@link openFirebaseUserSession} appends `#2`, `#3`, … when the base is
|
|
883
|
+
* already taken, because a session that mints its own credentials must never reuse an existing app
|
|
884
|
+
* registration (see the note on {@link openFirebaseUserSession}).
|
|
885
|
+
*
|
|
886
|
+
* @param input - The namespace, scope, and uid the session targets.
|
|
887
|
+
* @returns The base Firebase app name.
|
|
888
|
+
* @__NO_SIDE_EFFECTS__
|
|
889
|
+
*/ function firebaseUserSessionAppName(input) {
|
|
890
|
+
return [
|
|
891
|
+
input.namespace,
|
|
892
|
+
input.scope,
|
|
893
|
+
input.uid
|
|
894
|
+
].join(FIREBASE_USER_SESSION_KEY_SEPARATOR);
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* The `getApps()` name prefix every session opened under a namespace shares.
|
|
898
|
+
*
|
|
899
|
+
* @param namespace - The namespace to sweep.
|
|
900
|
+
* @returns The app-name prefix.
|
|
901
|
+
* @__NO_SIDE_EFFECTS__
|
|
902
|
+
*/ function firebaseUserSessionNamespacePrefix(namespace) {
|
|
903
|
+
return "".concat(namespace).concat(FIREBASE_USER_SESSION_KEY_SEPARATOR);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Opens a direct Firestore connection as the user the presented access token belongs to.
|
|
907
|
+
*
|
|
908
|
+
* Steps, in a strict order:
|
|
909
|
+
*
|
|
910
|
+
* 0. When a `credentialsCache` is supplied, reuse the cached credential envelope if it is still live.
|
|
911
|
+
* Sessions are cached for up to an hour (see {@link FIRESTORE_SESSION_MAX_CACHE_MS}), which is the
|
|
912
|
+
* ceiling the Firebase credentials themselves sit under. A hit skips step 1 only — the Firebase
|
|
913
|
+
* app is per-session, so the sign-in in step 5 always runs.
|
|
914
|
+
* 1. `GET <apiBaseUrl>/session/firestore` for a custom token + App Check attestation.
|
|
915
|
+
* 2. `initializeApp` under the first FREE name derived from {@link firebaseUserSessionAppName}.
|
|
916
|
+
*
|
|
917
|
+
* **Never reuse an existing registration.** `initializeAppCheck` on an app whose App Check
|
|
918
|
+
* provider is already initialized silently returns the EXISTING instance when
|
|
919
|
+
* `CustomProvider.isEqual` matches — and `isEqual` compares `getToken.toString()`, the source text
|
|
920
|
+
* of the arrow function, which is byte-identical across two closures built at this call site over
|
|
921
|
+
* different tokens. Reusing an app therefore keeps the FIRST attestation and drops the freshly
|
|
922
|
+
* minted one on the floor, so requests go out under a stale (eventually expired) App Check token.
|
|
923
|
+
* App reuse is the POOL's responsibility, at the session-object level, never at the app-registry
|
|
924
|
+
* level.
|
|
925
|
+
* 3. `initializeAppCheck` with a `CustomProvider` handing back the server-minted token. **This must
|
|
926
|
+
* happen before any other Firebase call** — `dbx-firebase`'s provider documents the same
|
|
927
|
+
* constraint: "App Check must be initialized before any Firebase request goes out, otherwise
|
|
928
|
+
* requests are sent without an App Check token and are rejected in production." Skipped when the
|
|
929
|
+
* config targets emulators (which do not verify attestations) or when the API minted no token.
|
|
930
|
+
* 4. `getAuth` / `getFirestore`, connecting each to its emulator when configured.
|
|
931
|
+
* 5. `signInWithCustomToken`. The user's stored custom claims land at the top level of the exchanged
|
|
932
|
+
* ID token, so `request.auth.token.<claim>` reads in security rules behave exactly as in the app.
|
|
933
|
+
*
|
|
934
|
+
* There is deliberately NO fallback to an Admin-SDK context — the whole point is that rules stay in
|
|
935
|
+
* force, so a failure here throws.
|
|
936
|
+
*
|
|
937
|
+
* @param input - The namespace, Firebase client config, API target, and access token.
|
|
938
|
+
* @returns The live {@link FirebaseUserSession}.
|
|
939
|
+
* @throws {FirestoreSessionError} (or the `errorFactory`'s type) When the config is incomplete, or any step of the handshake fails.
|
|
940
|
+
*/ function openFirebaseUserSession(input) {
|
|
941
|
+
return _async_to_generator$1(function() {
|
|
942
|
+
var _input_maxSessionAgeMs, _input_errorFactory, _input_now, _ref, _ref1, _firebase_emulators, _firebase_emulators1, _firebase_emulators2, namespace, firebase, apiBaseUrl, accessToken, fetcher, credentialsCache, cacheKey, _input_refreshCredentials, refreshCredentials, maxSessionAgeMs, errorFactory, now, scope, readKey, cached, _tmp, liveCached, fromCache, credentials, _tmp1, writeKey, appName, app, createdAt, useEmulators, appCheckToken, expireTimeMillis, emulatorHost, auth, firestore, e;
|
|
943
|
+
return _ts_generator$1(this, function(_state) {
|
|
944
|
+
switch(_state.label){
|
|
945
|
+
case 0:
|
|
946
|
+
namespace = input.namespace, firebase = input.firebase, apiBaseUrl = input.apiBaseUrl, accessToken = input.accessToken, fetcher = input.fetcher, credentialsCache = input.credentialsCache, cacheKey = input.cacheKey, _input_refreshCredentials = input.refreshCredentials, refreshCredentials = _input_refreshCredentials === void 0 ? false : _input_refreshCredentials;
|
|
947
|
+
maxSessionAgeMs = (_input_maxSessionAgeMs = input.maxSessionAgeMs) !== null && _input_maxSessionAgeMs !== void 0 ? _input_maxSessionAgeMs : FIRESTORE_SESSION_MAX_CACHE_MS;
|
|
948
|
+
errorFactory = (_input_errorFactory = input.errorFactory) !== null && _input_errorFactory !== void 0 ? _input_errorFactory : defaultFirestoreSessionErrorFactory;
|
|
949
|
+
now = (_input_now = input.now) !== null && _input_now !== void 0 ? _input_now : function() {
|
|
950
|
+
return Date.now();
|
|
951
|
+
};
|
|
952
|
+
if (!isFirebaseClientConfigComplete(firebase)) {
|
|
953
|
+
throw errorFactory({
|
|
954
|
+
code: 'invalid_config',
|
|
955
|
+
message: 'No complete Firebase client config was supplied, so a user-scoped Firestore session cannot be opened.',
|
|
956
|
+
suggestion: "Set `apiKey`, `projectId`, and `appId` on the Firebase client config. Copy them from the app's Firebase web app config."
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
scope = input.scope || firebase.projectId;
|
|
960
|
+
readKey = (_ref = cacheKey !== null && cacheKey !== void 0 ? cacheKey : input.uid) !== null && _ref !== void 0 ? _ref : undefined;
|
|
961
|
+
if (!(credentialsCache && !refreshCredentials && readKey != null)) return [
|
|
962
|
+
3,
|
|
963
|
+
2
|
|
964
|
+
];
|
|
965
|
+
return [
|
|
966
|
+
4,
|
|
967
|
+
credentialsCache.get(readKey)
|
|
968
|
+
];
|
|
969
|
+
case 1:
|
|
970
|
+
_tmp = _state.sent();
|
|
971
|
+
return [
|
|
972
|
+
3,
|
|
973
|
+
3
|
|
974
|
+
];
|
|
975
|
+
case 2:
|
|
976
|
+
_tmp = undefined;
|
|
977
|
+
_state.label = 3;
|
|
978
|
+
case 3:
|
|
979
|
+
cached = _tmp;
|
|
980
|
+
liveCached = isFirestoreSessionExpired(cached, now()) ? undefined : cached;
|
|
981
|
+
fromCache = liveCached != null;
|
|
982
|
+
if (!((_ref1 = liveCached === null || liveCached === void 0 ? void 0 : liveCached.session) !== null && _ref1 !== void 0)) return [
|
|
983
|
+
3,
|
|
984
|
+
4
|
|
985
|
+
];
|
|
986
|
+
_tmp1 = _ref1;
|
|
987
|
+
return [
|
|
988
|
+
3,
|
|
989
|
+
6
|
|
990
|
+
];
|
|
991
|
+
case 4:
|
|
992
|
+
return [
|
|
993
|
+
4,
|
|
994
|
+
fetchFirestoreSession({
|
|
995
|
+
apiBaseUrl: apiBaseUrl,
|
|
996
|
+
accessToken: accessToken,
|
|
997
|
+
fetcher: fetcher,
|
|
998
|
+
errorFactory: errorFactory
|
|
999
|
+
})
|
|
1000
|
+
];
|
|
1001
|
+
case 5:
|
|
1002
|
+
_tmp1 = _state.sent();
|
|
1003
|
+
_state.label = 6;
|
|
1004
|
+
case 6:
|
|
1005
|
+
credentials = _tmp1;
|
|
1006
|
+
if (input.uid != null && credentials.uid !== input.uid) {
|
|
1007
|
+
throw errorFactory({
|
|
1008
|
+
code: 'invalid_response',
|
|
1009
|
+
message: 'The session endpoint minted credentials for uid "'.concat(credentials.uid, '" but "').concat(input.uid, '" was requested.'),
|
|
1010
|
+
suggestion: "The mint endpoint always mints for the presented token's own `auth.uid`. A mismatch means the access token does not belong to the requested user."
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
writeKey = cacheKey !== null && cacheKey !== void 0 ? cacheKey : credentials.uid;
|
|
1014
|
+
if (!(!fromCache && credentialsCache)) return [
|
|
1015
|
+
3,
|
|
1016
|
+
8
|
|
1017
|
+
];
|
|
1018
|
+
return [
|
|
1019
|
+
4,
|
|
1020
|
+
credentialsCache.set(writeKey, {
|
|
1021
|
+
session: credentials,
|
|
1022
|
+
cachedAt: now(),
|
|
1023
|
+
uid: credentials.uid
|
|
1024
|
+
})
|
|
1025
|
+
];
|
|
1026
|
+
case 7:
|
|
1027
|
+
_state.sent();
|
|
1028
|
+
_state.label = 8;
|
|
1029
|
+
case 8:
|
|
1030
|
+
appName = nextFreeFirebaseAppName(firebaseUserSessionAppName({
|
|
1031
|
+
namespace: namespace,
|
|
1032
|
+
scope: scope,
|
|
1033
|
+
uid: credentials.uid
|
|
1034
|
+
}));
|
|
1035
|
+
app = initializeApp({
|
|
1036
|
+
apiKey: firebase.apiKey,
|
|
1037
|
+
authDomain: firebase.authDomain,
|
|
1038
|
+
projectId: firebase.projectId,
|
|
1039
|
+
appId: firebase.appId
|
|
1040
|
+
}, appName);
|
|
1041
|
+
createdAt = now();
|
|
1042
|
+
useEmulators = firebaseClientEmulatorsInUse(firebase);
|
|
1043
|
+
// App Check must be registered on the app before any Firebase request goes out. Mirrors
|
|
1044
|
+
// `createDbxFirebaseAppCheck`, which likewise disables App Check whenever emulators are in use.
|
|
1045
|
+
if (credentials.appCheckToken && !useEmulators) {
|
|
1046
|
+
appCheckToken = credentials.appCheckToken;
|
|
1047
|
+
expireTimeMillis = expireTimeMillisFromCredentials(credentials, now());
|
|
1048
|
+
initializeAppCheck(app, {
|
|
1049
|
+
provider: new CustomProvider({
|
|
1050
|
+
getToken: function getToken() {
|
|
1051
|
+
return _async_to_generator$1(function() {
|
|
1052
|
+
return _ts_generator$1(this, function(_state) {
|
|
1053
|
+
return [
|
|
1054
|
+
2,
|
|
1055
|
+
{
|
|
1056
|
+
token: appCheckToken,
|
|
1057
|
+
expireTimeMillis: expireTimeMillis
|
|
1058
|
+
}
|
|
1059
|
+
];
|
|
1060
|
+
});
|
|
1061
|
+
})();
|
|
1062
|
+
}
|
|
1063
|
+
}),
|
|
1064
|
+
// the token is minted per-session by the API; there is no local attestation to refresh against
|
|
1065
|
+
isTokenAutoRefreshEnabled: false
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
emulatorHost = ((_firebase_emulators = firebase.emulators) === null || _firebase_emulators === void 0 ? void 0 : _firebase_emulators.host) || DEFAULT_FIREBASE_CLIENT_EMULATOR_HOST;
|
|
1069
|
+
auth = getAuth(app);
|
|
1070
|
+
if (useEmulators && ((_firebase_emulators1 = firebase.emulators) === null || _firebase_emulators1 === void 0 ? void 0 : _firebase_emulators1.authPort) != null) {
|
|
1071
|
+
connectAuthEmulator(auth, "http://".concat(emulatorHost, ":").concat(firebase.emulators.authPort), {
|
|
1072
|
+
disableWarnings: true
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
firestore = getFirestore(app);
|
|
1076
|
+
if (useEmulators && ((_firebase_emulators2 = firebase.emulators) === null || _firebase_emulators2 === void 0 ? void 0 : _firebase_emulators2.firestorePort) != null) {
|
|
1077
|
+
connectFirestoreEmulator(firestore, emulatorHost, firebase.emulators.firestorePort);
|
|
1078
|
+
}
|
|
1079
|
+
_state.label = 9;
|
|
1080
|
+
case 9:
|
|
1081
|
+
_state.trys.push([
|
|
1082
|
+
9,
|
|
1083
|
+
11,
|
|
1084
|
+
,
|
|
1085
|
+
15
|
|
1086
|
+
]);
|
|
1087
|
+
return [
|
|
1088
|
+
4,
|
|
1089
|
+
signInWithCustomToken(auth, credentials.customToken)
|
|
1090
|
+
];
|
|
1091
|
+
case 10:
|
|
1092
|
+
_state.sent();
|
|
1093
|
+
return [
|
|
1094
|
+
3,
|
|
1095
|
+
15
|
|
1096
|
+
];
|
|
1097
|
+
case 11:
|
|
1098
|
+
e = _state.sent();
|
|
1099
|
+
// The app created on THIS attempt is torn down explicitly rather than left to the namespace
|
|
1100
|
+
// sweep: every open registers its OWN app (see step 2), so a caller that retries — or a pool
|
|
1101
|
+
// whose config is broken and fails once per request — would otherwise accumulate one dead app,
|
|
1102
|
+
// with its refresh timer and network stack, per failed attempt.
|
|
1103
|
+
return [
|
|
1104
|
+
4,
|
|
1105
|
+
closeFirebaseUserSessionApp(app)
|
|
1106
|
+
];
|
|
1107
|
+
case 12:
|
|
1108
|
+
_state.sent();
|
|
1109
|
+
if (!(fromCache && credentialsCache)) return [
|
|
1110
|
+
3,
|
|
1111
|
+
14
|
|
1112
|
+
];
|
|
1113
|
+
return [
|
|
1114
|
+
4,
|
|
1115
|
+
credentialsCache.remove(writeKey)
|
|
1116
|
+
];
|
|
1117
|
+
case 13:
|
|
1118
|
+
_state.sent();
|
|
1119
|
+
return [
|
|
1120
|
+
2,
|
|
1121
|
+
openFirebaseUserSession(_object_spread_props$1(_object_spread$1({}, input), {
|
|
1122
|
+
refreshCredentials: true
|
|
1123
|
+
}))
|
|
1124
|
+
];
|
|
1125
|
+
case 14:
|
|
1126
|
+
throw errorFactory({
|
|
1127
|
+
code: 'unauthorized',
|
|
1128
|
+
message: "Failed to sign in with the minted custom token: ".concat(_instanceof(e, Error) ? e.message : String(e)),
|
|
1129
|
+
suggestion: 'Verify `firebase.projectId`/`firebase.apiKey` match the project the API mints tokens for.'
|
|
1130
|
+
});
|
|
1131
|
+
case 15:
|
|
1132
|
+
return [
|
|
1133
|
+
2,
|
|
1134
|
+
{
|
|
1135
|
+
uid: credentials.uid,
|
|
1136
|
+
credentials: credentials,
|
|
1137
|
+
fromCache: fromCache,
|
|
1138
|
+
appName: appName,
|
|
1139
|
+
app: app,
|
|
1140
|
+
auth: auth,
|
|
1141
|
+
firestore: firestore,
|
|
1142
|
+
firestoreContext: clientFirebaseFirestoreContextFactory(firestore),
|
|
1143
|
+
createdAt: createdAt,
|
|
1144
|
+
expiresAt: firebaseUserSessionExpiresAt(credentials, createdAt, maxSessionAgeMs)
|
|
1145
|
+
}
|
|
1146
|
+
];
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
})();
|
|
1150
|
+
}
|
|
1151
|
+
// MARK: Teardown
|
|
1152
|
+
/**
|
|
1153
|
+
* Tears down a session opened by {@link openFirebaseUserSession}.
|
|
1154
|
+
*
|
|
1155
|
+
* Required, not optional hygiene. A signed-in `Auth` runs a token-refresh timer and a live
|
|
1156
|
+
* `Firestore` holds open handles, so both leak per user without teardown — and in a CLI-shaped
|
|
1157
|
+
* process they keep the Node event loop alive indefinitely, so the process never exits.
|
|
1158
|
+
*
|
|
1159
|
+
* `deleteApp` is the single call that covers it — it disposes every registered component, which for
|
|
1160
|
+
* Firestore runs the same shutdown `terminate()` does, and for Auth stops the token-refresh timer.
|
|
1161
|
+
*
|
|
1162
|
+
* Deliberately tolerant: teardown normally runs after the caller's result is already produced, so a
|
|
1163
|
+
* failure here must not change the outcome.
|
|
1164
|
+
*
|
|
1165
|
+
* @param session - The session to close.
|
|
1166
|
+
*/ function closeFirebaseUserSession(session) {
|
|
1167
|
+
return _async_to_generator$1(function() {
|
|
1168
|
+
return _ts_generator$1(this, function(_state) {
|
|
1169
|
+
switch(_state.label){
|
|
1170
|
+
case 0:
|
|
1171
|
+
return [
|
|
1172
|
+
4,
|
|
1173
|
+
closeFirebaseUserSessionApp(session.app)
|
|
1174
|
+
];
|
|
1175
|
+
case 1:
|
|
1176
|
+
_state.sent();
|
|
1177
|
+
return [
|
|
1178
|
+
2
|
|
1179
|
+
];
|
|
1180
|
+
}
|
|
1181
|
+
});
|
|
1182
|
+
})();
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Deletes one Firebase app, swallowing any failure.
|
|
1186
|
+
*
|
|
1187
|
+
* @param app - The app to delete.
|
|
1188
|
+
*/ function closeFirebaseUserSessionApp(app) {
|
|
1189
|
+
return _async_to_generator$1(function() {
|
|
1190
|
+
return _ts_generator$1(this, function(_state) {
|
|
1191
|
+
switch(_state.label){
|
|
1192
|
+
case 0:
|
|
1193
|
+
_state.trys.push([
|
|
1194
|
+
0,
|
|
1195
|
+
2,
|
|
1196
|
+
,
|
|
1197
|
+
3
|
|
1198
|
+
]);
|
|
1199
|
+
return [
|
|
1200
|
+
4,
|
|
1201
|
+
deleteApp(app)
|
|
1202
|
+
];
|
|
1203
|
+
case 1:
|
|
1204
|
+
_state.sent();
|
|
1205
|
+
return [
|
|
1206
|
+
3,
|
|
1207
|
+
3
|
|
1208
|
+
];
|
|
1209
|
+
case 2:
|
|
1210
|
+
_state.sent();
|
|
1211
|
+
return [
|
|
1212
|
+
3,
|
|
1213
|
+
3
|
|
1214
|
+
];
|
|
1215
|
+
case 3:
|
|
1216
|
+
return [
|
|
1217
|
+
2
|
|
1218
|
+
];
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
})();
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Deletes every still-live Firebase app opened under a namespace, whether or not a session was
|
|
1225
|
+
* handed back.
|
|
1226
|
+
*
|
|
1227
|
+
* The last line of defence against a leak. {@link closeFirebaseUserSession} covers the normal path,
|
|
1228
|
+
* but it needs a session to be handed to it, and three cases never produce one:
|
|
1229
|
+
*
|
|
1230
|
+
* - a handshake that fails AFTER `initializeApp` — a rejected custom token, a failed App Check
|
|
1231
|
+
* registration — throws, so the caller that catches it has an initialized app and no session;
|
|
1232
|
+
* - a caller that opens its own session outside a pool owns its own teardown, and forgetting it
|
|
1233
|
+
* leaks;
|
|
1234
|
+
* - an owner orphaned mid-invocation carries the only reference to its session.
|
|
1235
|
+
*
|
|
1236
|
+
* `getApps()` already tracks every live app and `deleteApp` removes it from that list, so the app
|
|
1237
|
+
* names {@link firebaseUserSessionAppName} derives are enough to find them again — no side registry
|
|
1238
|
+
* to keep in sync, and idempotent by construction.
|
|
1239
|
+
*
|
|
1240
|
+
* @param input - The function inputs.
|
|
1241
|
+
* @param input.namespace - The namespace whose apps should be closed. Apps belonging to other
|
|
1242
|
+
* Firebase consumers in the same process are left alone.
|
|
1243
|
+
* @returns Resolves once every matching app has been deleted.
|
|
1244
|
+
*/ function closeFirebaseUserSessionApps(input) {
|
|
1245
|
+
return _async_to_generator$1(function() {
|
|
1246
|
+
var prefix, apps;
|
|
1247
|
+
return _ts_generator$1(this, function(_state) {
|
|
1248
|
+
switch(_state.label){
|
|
1249
|
+
case 0:
|
|
1250
|
+
prefix = firebaseUserSessionNamespacePrefix(input.namespace);
|
|
1251
|
+
apps = getApps().filter(function(x) {
|
|
1252
|
+
return x.name.startsWith(prefix);
|
|
1253
|
+
});
|
|
1254
|
+
return [
|
|
1255
|
+
4,
|
|
1256
|
+
Promise.all(apps.map(function(app) {
|
|
1257
|
+
return closeFirebaseUserSessionApp(app);
|
|
1258
|
+
}))
|
|
1259
|
+
];
|
|
1260
|
+
case 1:
|
|
1261
|
+
_state.sent();
|
|
1262
|
+
return [
|
|
1263
|
+
2
|
|
1264
|
+
];
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
})();
|
|
1268
|
+
}
|
|
1269
|
+
// MARK: Internal
|
|
1270
|
+
/**
|
|
1271
|
+
* The first unregistered app name at or after `base`, trying `base`, `base#2`, `base#3`, ….
|
|
1272
|
+
*
|
|
1273
|
+
* @param base - The derived base app name.
|
|
1274
|
+
* @returns A name no live app currently holds.
|
|
1275
|
+
*/ function nextFreeFirebaseAppName(base) {
|
|
1276
|
+
var taken = new Set(getApps().map(function(x) {
|
|
1277
|
+
return x.name;
|
|
1278
|
+
}));
|
|
1279
|
+
var result = base;
|
|
1280
|
+
var attempt = 1;
|
|
1281
|
+
while(taken.has(result)){
|
|
1282
|
+
attempt += 1;
|
|
1283
|
+
result = "".concat(base, "#").concat(attempt);
|
|
1284
|
+
}
|
|
1285
|
+
return result;
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* The session's effective expiry: the EARLIER of the API-reported `expiresAt` and the age ceiling.
|
|
1289
|
+
*
|
|
1290
|
+
* Mirrors `firestoreSessionEntryExpiresAt` — a server reporting an over-long (or unparsable) window
|
|
1291
|
+
* still cannot push a session past the Firebase credential ceiling.
|
|
1292
|
+
*
|
|
1293
|
+
* @param credentials - The minted envelope.
|
|
1294
|
+
* @param createdAt - When the session was opened, in unix epoch milliseconds.
|
|
1295
|
+
* @param maxSessionAgeMs - The age ceiling.
|
|
1296
|
+
* @returns The effective expiry in unix epoch milliseconds.
|
|
1297
|
+
*/ function firebaseUserSessionExpiresAt(credentials, createdAt, maxSessionAgeMs) {
|
|
1298
|
+
var ceiling = createdAt + maxSessionAgeMs;
|
|
1299
|
+
var reported = Date.parse(credentials.expiresAt);
|
|
1300
|
+
return Number.isFinite(reported) ? Math.min(reported, ceiling) : ceiling;
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Resolves the App Check token's expiry from the credential envelope, falling back to a short window
|
|
1304
|
+
* when the API returned an unparsable `expiresAt`.
|
|
1305
|
+
*
|
|
1306
|
+
* @param credentials - The credential envelope returned by the API.
|
|
1307
|
+
* @param nowMs - The current time in unix epoch milliseconds.
|
|
1308
|
+
* @returns The epoch-millis expiry to advertise to the App Check `CustomProvider`.
|
|
1309
|
+
*/ function expireTimeMillisFromCredentials(credentials, nowMs) {
|
|
1310
|
+
var parsed = Date.parse(credentials.expiresAt);
|
|
1311
|
+
return Number.isFinite(parsed) ? parsed : nowMs + 30 * 60 * 1000;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
1315
|
+
try {
|
|
1316
|
+
var info = gen[key](arg);
|
|
1317
|
+
var value = info.value;
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
reject(error);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
if (info.done) resolve(value);
|
|
1323
|
+
else Promise.resolve(value).then(_next, _throw);
|
|
1324
|
+
}
|
|
1325
|
+
function _async_to_generator(fn) {
|
|
1326
|
+
return function() {
|
|
1327
|
+
var self = this, args = arguments;
|
|
1328
|
+
return new Promise(function(resolve, reject) {
|
|
1329
|
+
var gen = fn.apply(self, args);
|
|
1330
|
+
function _next(value) {
|
|
1331
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
|
|
1332
|
+
}
|
|
1333
|
+
function _throw(err) {
|
|
1334
|
+
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
|
|
1335
|
+
}
|
|
1336
|
+
_next(undefined);
|
|
1337
|
+
});
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
function _define_property(obj, key, value) {
|
|
1341
|
+
if (key in obj) {
|
|
1342
|
+
Object.defineProperty(obj, key, {
|
|
1343
|
+
value: value,
|
|
1344
|
+
enumerable: true,
|
|
1345
|
+
configurable: true,
|
|
1346
|
+
writable: true
|
|
1347
|
+
});
|
|
1348
|
+
} else obj[key] = value;
|
|
1349
|
+
return obj;
|
|
1350
|
+
}
|
|
1351
|
+
function _object_spread(target) {
|
|
1352
|
+
for(var i = 1; i < arguments.length; i++){
|
|
1353
|
+
var source = arguments[i] != null ? arguments[i] : {};
|
|
1354
|
+
var ownKeys = Object.keys(source);
|
|
1355
|
+
if (typeof Object.getOwnPropertySymbols === "function") {
|
|
1356
|
+
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
|
|
1357
|
+
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
1358
|
+
}));
|
|
1359
|
+
}
|
|
1360
|
+
ownKeys.forEach(function(key) {
|
|
1361
|
+
_define_property(target, key, source[key]);
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
return target;
|
|
1365
|
+
}
|
|
1366
|
+
function ownKeys(object, enumerableOnly) {
|
|
1367
|
+
var keys = Object.keys(object);
|
|
1368
|
+
if (Object.getOwnPropertySymbols) {
|
|
1369
|
+
var symbols = Object.getOwnPropertySymbols(object);
|
|
1370
|
+
keys.push.apply(keys, symbols);
|
|
1371
|
+
}
|
|
1372
|
+
return keys;
|
|
1373
|
+
}
|
|
1374
|
+
function _object_spread_props(target, source) {
|
|
1375
|
+
source = source != null ? source : {};
|
|
1376
|
+
if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
|
|
1377
|
+
else {
|
|
1378
|
+
ownKeys(Object(source)).forEach(function(key) {
|
|
1379
|
+
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
return target;
|
|
1383
|
+
}
|
|
1384
|
+
function _ts_generator(thisArg, body) {
|
|
1385
|
+
var f, y, t, _ = {
|
|
1386
|
+
label: 0,
|
|
1387
|
+
sent: function() {
|
|
1388
|
+
if (t[0] & 1) throw t[1];
|
|
1389
|
+
return t[1];
|
|
1390
|
+
},
|
|
1391
|
+
trys: [],
|
|
1392
|
+
ops: []
|
|
1393
|
+
}, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype), d = Object.defineProperty;
|
|
1394
|
+
return d(g, "next", {
|
|
1395
|
+
value: verb(0)
|
|
1396
|
+
}), d(g, "throw", {
|
|
1397
|
+
value: verb(1)
|
|
1398
|
+
}), d(g, "return", {
|
|
1399
|
+
value: verb(2)
|
|
1400
|
+
}), typeof Symbol === "function" && d(g, Symbol.iterator, {
|
|
1401
|
+
value: function() {
|
|
1402
|
+
return this;
|
|
1403
|
+
}
|
|
1404
|
+
}), g;
|
|
1405
|
+
function verb(n) {
|
|
1406
|
+
return function(v) {
|
|
1407
|
+
return step([
|
|
1408
|
+
n,
|
|
1409
|
+
v
|
|
1410
|
+
]);
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
function step(op) {
|
|
1414
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
1415
|
+
while(g && (g = 0, op[0] && (_ = 0)), _)try {
|
|
1416
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
1417
|
+
if (y = 0, t) op = [
|
|
1418
|
+
op[0] & 2,
|
|
1419
|
+
t.value
|
|
1420
|
+
];
|
|
1421
|
+
switch(op[0]){
|
|
1422
|
+
case 0:
|
|
1423
|
+
case 1:
|
|
1424
|
+
t = op;
|
|
1425
|
+
break;
|
|
1426
|
+
case 4:
|
|
1427
|
+
_.label++;
|
|
1428
|
+
return {
|
|
1429
|
+
value: op[1],
|
|
1430
|
+
done: false
|
|
1431
|
+
};
|
|
1432
|
+
case 5:
|
|
1433
|
+
_.label++;
|
|
1434
|
+
y = op[1];
|
|
1435
|
+
op = [
|
|
1436
|
+
0
|
|
1437
|
+
];
|
|
1438
|
+
continue;
|
|
1439
|
+
case 7:
|
|
1440
|
+
op = _.ops.pop();
|
|
1441
|
+
_.trys.pop();
|
|
1442
|
+
continue;
|
|
1443
|
+
default:
|
|
1444
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
|
|
1445
|
+
_ = 0;
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
|
|
1449
|
+
_.label = op[1];
|
|
1450
|
+
break;
|
|
1451
|
+
}
|
|
1452
|
+
if (op[0] === 6 && _.label < t[1]) {
|
|
1453
|
+
_.label = t[1];
|
|
1454
|
+
t = op;
|
|
1455
|
+
break;
|
|
1456
|
+
}
|
|
1457
|
+
if (t && _.label < t[2]) {
|
|
1458
|
+
_.label = t[2];
|
|
1459
|
+
_.ops.push(op);
|
|
1460
|
+
break;
|
|
1461
|
+
}
|
|
1462
|
+
if (t[2]) _.ops.pop();
|
|
1463
|
+
_.trys.pop();
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
op = body.call(thisArg, _);
|
|
1467
|
+
} catch (e) {
|
|
1468
|
+
op = [
|
|
1469
|
+
6,
|
|
1470
|
+
e
|
|
1471
|
+
];
|
|
1472
|
+
y = 0;
|
|
1473
|
+
} finally{
|
|
1474
|
+
f = t = 0;
|
|
1475
|
+
}
|
|
1476
|
+
if (op[0] & 5) throw op[1];
|
|
1477
|
+
return {
|
|
1478
|
+
value: op[0] ? op[1] : void 0,
|
|
1479
|
+
done: true
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Default {@link FirebaseUserSessionPoolConfig.maxSessions}.
|
|
1485
|
+
*
|
|
1486
|
+
* Bounds *concurrently distinct users*, not total users. Each entry is a full Firebase app: one
|
|
1487
|
+
* `Firestore` with its network stack and one `Auth` with a refresh timer. 32 is deliberate — high
|
|
1488
|
+
* enough that a normal request mix never evicts, low enough that an unbounded-uid caller cannot
|
|
1489
|
+
* exhaust the process.
|
|
1490
|
+
*/ var DEFAULT_FIREBASE_USER_SESSION_POOL_SIZE = 32;
|
|
1491
|
+
/**
|
|
1492
|
+
* Creates a per-`(scope, uid)` Firebase session pool with an explicit cap and TTL/LRU eviction.
|
|
1493
|
+
*
|
|
1494
|
+
* Exists because {@link openFirebaseUserSession} alone does not generalize past one user per process:
|
|
1495
|
+
* a signed-in `Auth` runs a token-refresh timer and a live `Firestore` holds handles, so a server
|
|
1496
|
+
* holding N concurrent user sessions leaks both per user without an owner. The pool is that owner.
|
|
1497
|
+
*
|
|
1498
|
+
* ### Why the TTL is driven by App Check, not Auth
|
|
1499
|
+
*
|
|
1500
|
+
* A signed-in `Auth` refreshes its ID token indefinitely — a Firebase refresh token does not expire
|
|
1501
|
+
* on an hourly clock. The App Check token does not refresh: it is minted once by the API and
|
|
1502
|
+
* `isTokenAutoRefreshEnabled` is `false`, because there is no local attestation to refresh against.
|
|
1503
|
+
* So the session's real ceiling is the envelope's `expiresAt` (documented by the endpoint as "the
|
|
1504
|
+
* earliest expiry among its credentials"), floored by {@link FirebaseUserSessionPoolConfig.maxSessionAgeMs}.
|
|
1505
|
+
* Tearing the whole app down on expiry and re-minting costs one round trip; keeping it alive costs a
|
|
1506
|
+
* silently unattested connection.
|
|
1507
|
+
*
|
|
1508
|
+
* ### Why a lease rather than a bare getter
|
|
1509
|
+
*
|
|
1510
|
+
* Reference counting is what makes eviction safe. Evicting an entry someone is mid-query on turns a
|
|
1511
|
+
* capacity event into a user-visible failure, so the pool never tears down a leased session.
|
|
1512
|
+
*
|
|
1513
|
+
* @param config - The pool configuration.
|
|
1514
|
+
* @returns The pool.
|
|
1515
|
+
*/ function firebaseUserSessionPool(config) {
|
|
1516
|
+
var _config_errorFactory, _config_openSession, _config_now, _config_maxSessions, _config_maxSessionAgeMs, _config_refreshSkewMs;
|
|
1517
|
+
var namespace = config.namespace, firebase = config.firebase, apiBaseUrl = config.apiBaseUrl, fetcher = config.fetcher, credentialsCache = config.credentialsCache;
|
|
1518
|
+
var errorFactory = (_config_errorFactory = config.errorFactory) !== null && _config_errorFactory !== void 0 ? _config_errorFactory : defaultFirestoreSessionErrorFactory;
|
|
1519
|
+
var openSessionImpl = (_config_openSession = config.openSession) !== null && _config_openSession !== void 0 ? _config_openSession : openFirebaseUserSession;
|
|
1520
|
+
var now = (_config_now = config.now) !== null && _config_now !== void 0 ? _config_now : function() {
|
|
1521
|
+
return Date.now();
|
|
1522
|
+
};
|
|
1523
|
+
var scope = config.scope || firebase.projectId || namespace;
|
|
1524
|
+
var maxSessions = (_config_maxSessions = config.maxSessions) !== null && _config_maxSessions !== void 0 ? _config_maxSessions : DEFAULT_FIREBASE_USER_SESSION_POOL_SIZE;
|
|
1525
|
+
var maxSessionAgeMs = (_config_maxSessionAgeMs = config.maxSessionAgeMs) !== null && _config_maxSessionAgeMs !== void 0 ? _config_maxSessionAgeMs : FIRESTORE_SESSION_MAX_CACHE_MS;
|
|
1526
|
+
var refreshSkewMs = (_config_refreshSkewMs = config.refreshSkewMs) !== null && _config_refreshSkewMs !== void 0 ? _config_refreshSkewMs : FIRESTORE_SESSION_EXPIRY_BUFFER_MS;
|
|
1527
|
+
var onEvent = config.onEvent;
|
|
1528
|
+
var entries = new Map();
|
|
1529
|
+
var closed = false;
|
|
1530
|
+
function emit(type, entry, error) {
|
|
1531
|
+
onEvent === null || onEvent === void 0 ? void 0 : onEvent(_object_spread({
|
|
1532
|
+
type: type,
|
|
1533
|
+
uid: entry.uid,
|
|
1534
|
+
appName: entry.appName,
|
|
1535
|
+
size: entries.size
|
|
1536
|
+
}, error === undefined ? {} : {
|
|
1537
|
+
error: error
|
|
1538
|
+
}));
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Removes the entry from the map when it is still the live one for its key.
|
|
1542
|
+
*
|
|
1543
|
+
* Guarded on identity because a replacement entry may already have been inserted for the same key
|
|
1544
|
+
* (the expiry path detaches and immediately re-opens), and deleting blindly would drop the new one.
|
|
1545
|
+
*
|
|
1546
|
+
* @param entry - The entry to remove.
|
|
1547
|
+
*/ function detach(entry) {
|
|
1548
|
+
if (entries.get(entry.key) === entry) {
|
|
1549
|
+
entries.delete(entry.key);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
function teardown(entry) {
|
|
1553
|
+
return _async_to_generator(function() {
|
|
1554
|
+
var session, error;
|
|
1555
|
+
return _ts_generator(this, function(_state) {
|
|
1556
|
+
switch(_state.label){
|
|
1557
|
+
case 0:
|
|
1558
|
+
_state.trys.push([
|
|
1559
|
+
0,
|
|
1560
|
+
2,
|
|
1561
|
+
,
|
|
1562
|
+
3
|
|
1563
|
+
]);
|
|
1564
|
+
return [
|
|
1565
|
+
4,
|
|
1566
|
+
entry.loading
|
|
1567
|
+
];
|
|
1568
|
+
case 1:
|
|
1569
|
+
session = _state.sent();
|
|
1570
|
+
return [
|
|
1571
|
+
3,
|
|
1572
|
+
3
|
|
1573
|
+
];
|
|
1574
|
+
case 2:
|
|
1575
|
+
_state.sent();
|
|
1576
|
+
// an open that never produced a session has nothing to tear down; the rejection was already
|
|
1577
|
+
// surfaced to the caller that awaited it
|
|
1578
|
+
session = undefined;
|
|
1579
|
+
return [
|
|
1580
|
+
3,
|
|
1581
|
+
3
|
|
1582
|
+
];
|
|
1583
|
+
case 3:
|
|
1584
|
+
if (!(session != null)) return [
|
|
1585
|
+
3,
|
|
1586
|
+
7
|
|
1587
|
+
];
|
|
1588
|
+
entry.appName = session.appName;
|
|
1589
|
+
_state.label = 4;
|
|
1590
|
+
case 4:
|
|
1591
|
+
_state.trys.push([
|
|
1592
|
+
4,
|
|
1593
|
+
6,
|
|
1594
|
+
,
|
|
1595
|
+
7
|
|
1596
|
+
]);
|
|
1597
|
+
// `deleteApp` directly rather than `closeFirebaseUserSession`, which deliberately swallows:
|
|
1598
|
+
// a long-lived pool wants a failed teardown OBSERVABLE — it means an app whose refresh timer
|
|
1599
|
+
// and network stack are still running — even though it must never be fatal.
|
|
1600
|
+
return [
|
|
1601
|
+
4,
|
|
1602
|
+
deleteApp(session.app)
|
|
1603
|
+
];
|
|
1604
|
+
case 5:
|
|
1605
|
+
_state.sent();
|
|
1606
|
+
emit('closed', entry);
|
|
1607
|
+
return [
|
|
1608
|
+
3,
|
|
1609
|
+
7
|
|
1610
|
+
];
|
|
1611
|
+
case 6:
|
|
1612
|
+
error = _state.sent();
|
|
1613
|
+
emit('teardown-failed', entry, error);
|
|
1614
|
+
return [
|
|
1615
|
+
3,
|
|
1616
|
+
7
|
|
1617
|
+
];
|
|
1618
|
+
case 7:
|
|
1619
|
+
return [
|
|
1620
|
+
2
|
|
1621
|
+
];
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1624
|
+
})();
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Tears the entry down now when it is idle, otherwise marks it for teardown on its last release.
|
|
1628
|
+
*
|
|
1629
|
+
* @param entry - The detached entry.
|
|
1630
|
+
* @returns The teardown promise when one started, otherwise `undefined`.
|
|
1631
|
+
*/ function teardownWhenIdle(entry) {
|
|
1632
|
+
var result;
|
|
1633
|
+
if (entry.leases === 0) {
|
|
1634
|
+
result = teardown(entry);
|
|
1635
|
+
} else {
|
|
1636
|
+
entry.pendingTeardown = true;
|
|
1637
|
+
}
|
|
1638
|
+
return result;
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Brings the pool back to its cap after an insert.
|
|
1642
|
+
*
|
|
1643
|
+
* The cap is a TARGET, not a hard ceiling. When every entry is leased there is nothing safe to
|
|
1644
|
+
* evict, so the pool admits anyway and marks the LRU entry for teardown-on-release: refusing a user
|
|
1645
|
+
* because other users are busy is worse than a brief overshoot, and killing a live handle is worse
|
|
1646
|
+
* than both. The overshoot is bounded by the host's own request concurrency.
|
|
1647
|
+
*
|
|
1648
|
+
* @param admitted - The entry just inserted, which is never the one given up.
|
|
1649
|
+
*/ function enforceCapacity(admitted) {
|
|
1650
|
+
while(entries.size > maxSessions){
|
|
1651
|
+
var idle = leastRecentlyUsed(function(x) {
|
|
1652
|
+
return x.leases === 0 && !x.pendingTeardown && x !== admitted;
|
|
1653
|
+
});
|
|
1654
|
+
if (idle == null) {
|
|
1655
|
+
var lru = leastRecentlyUsed(function(x) {
|
|
1656
|
+
return !x.pendingTeardown && x !== admitted;
|
|
1657
|
+
});
|
|
1658
|
+
if (lru == null) {
|
|
1659
|
+
// every entry is leased AND already marked; there is nothing further to give up
|
|
1660
|
+
emit('over-capacity', admitted);
|
|
1661
|
+
break;
|
|
1662
|
+
} else {
|
|
1663
|
+
lru.pendingTeardown = true;
|
|
1664
|
+
detach(lru);
|
|
1665
|
+
emit('over-capacity', lru);
|
|
1666
|
+
}
|
|
1667
|
+
} else {
|
|
1668
|
+
detach(idle);
|
|
1669
|
+
emit('evicted', idle);
|
|
1670
|
+
void teardown(idle);
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
function leastRecentlyUsed(predicate) {
|
|
1675
|
+
var result;
|
|
1676
|
+
entries.forEach(function(entry) {
|
|
1677
|
+
if (predicate(entry) && (result == null || entry.lastUsedAt < result.lastUsedAt)) {
|
|
1678
|
+
result = entry;
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
return result;
|
|
1682
|
+
}
|
|
1683
|
+
function openEntry(input, key, openedAt) {
|
|
1684
|
+
var entry = {
|
|
1685
|
+
key: key,
|
|
1686
|
+
uid: input.uid,
|
|
1687
|
+
appName: key,
|
|
1688
|
+
leases: 0,
|
|
1689
|
+
lastUsedAt: openedAt,
|
|
1690
|
+
pendingTeardown: false,
|
|
1691
|
+
loading: openSessionImpl(_object_spread_props(_object_spread({
|
|
1692
|
+
namespace: namespace,
|
|
1693
|
+
scope: scope,
|
|
1694
|
+
firebase: firebase,
|
|
1695
|
+
apiBaseUrl: apiBaseUrl,
|
|
1696
|
+
accessToken: input.accessToken,
|
|
1697
|
+
uid: input.uid,
|
|
1698
|
+
fetcher: fetcher,
|
|
1699
|
+
errorFactory: errorFactory,
|
|
1700
|
+
credentialsCache: credentialsCache,
|
|
1701
|
+
cacheKey: key
|
|
1702
|
+
}, input.refreshCredentials == null ? {} : {
|
|
1703
|
+
refreshCredentials: input.refreshCredentials
|
|
1704
|
+
}), {
|
|
1705
|
+
maxSessionAgeMs: maxSessionAgeMs,
|
|
1706
|
+
now: now
|
|
1707
|
+
}))
|
|
1708
|
+
};
|
|
1709
|
+
entry.loading.then(function(session) {
|
|
1710
|
+
entry.appName = session.appName;
|
|
1711
|
+
// the ceiling is re-derived from the POOL's clock so it holds whatever the opener reported —
|
|
1712
|
+
// an envelope claiming a six-hour window is still capped at maxSessionAgeMs
|
|
1713
|
+
entry.expiresAt = Math.min(session.expiresAt, openedAt + maxSessionAgeMs);
|
|
1714
|
+
}, function() {
|
|
1715
|
+
// Cache only on success: a rejected open is removed outright so the next acquisition retries
|
|
1716
|
+
// rather than latching onto the failure.
|
|
1717
|
+
detach(entry);
|
|
1718
|
+
});
|
|
1719
|
+
return entry;
|
|
1720
|
+
}
|
|
1721
|
+
function isExpired(entry, nowMs) {
|
|
1722
|
+
return entry.expiresAt != null && entry.expiresAt - nowMs <= refreshSkewMs;
|
|
1723
|
+
}
|
|
1724
|
+
function leaseFor(entry, session) {
|
|
1725
|
+
var released = false;
|
|
1726
|
+
return {
|
|
1727
|
+
session: session,
|
|
1728
|
+
release: function release() {
|
|
1729
|
+
if (!released) {
|
|
1730
|
+
released = true;
|
|
1731
|
+
entry.leases -= 1;
|
|
1732
|
+
entry.lastUsedAt = now();
|
|
1733
|
+
if (entry.pendingTeardown && entry.leases === 0) {
|
|
1734
|
+
void teardown(entry);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
function acquire(input) {
|
|
1741
|
+
return _async_to_generator(function() {
|
|
1742
|
+
var nowMs, key, existing, entry, acquired, session, e;
|
|
1743
|
+
return _ts_generator(this, function(_state) {
|
|
1744
|
+
switch(_state.label){
|
|
1745
|
+
case 0:
|
|
1746
|
+
if (closed) {
|
|
1747
|
+
throw errorFactory({
|
|
1748
|
+
code: 'unavailable',
|
|
1749
|
+
message: 'The Firebase user session pool is closed.',
|
|
1750
|
+
suggestion: 'Do not reuse a pool after `close()`; build a new one.'
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
nowMs = now();
|
|
1754
|
+
key = firebaseUserSessionAppName({
|
|
1755
|
+
namespace: namespace,
|
|
1756
|
+
scope: scope,
|
|
1757
|
+
uid: input.uid
|
|
1758
|
+
});
|
|
1759
|
+
existing = entries.get(key);
|
|
1760
|
+
if (existing != null && !existing.pendingTeardown && !isExpired(existing, nowMs)) {
|
|
1761
|
+
entry = existing;
|
|
1762
|
+
// leases + lastUsedAt are bumped SYNCHRONOUSLY, before any await, so a concurrent eviction
|
|
1763
|
+
// cannot tear down a session that is about to be handed out
|
|
1764
|
+
entry.leases += 1;
|
|
1765
|
+
entry.lastUsedAt = nowMs;
|
|
1766
|
+
emit('reused', entry);
|
|
1767
|
+
} else {
|
|
1768
|
+
if (existing != null) {
|
|
1769
|
+
detach(existing);
|
|
1770
|
+
emit('expired', existing);
|
|
1771
|
+
void teardownWhenIdle(existing);
|
|
1772
|
+
}
|
|
1773
|
+
entry = openEntry(input, key, nowMs);
|
|
1774
|
+
entry.leases += 1;
|
|
1775
|
+
entries.set(key, entry);
|
|
1776
|
+
emit('opened', entry);
|
|
1777
|
+
enforceCapacity(entry);
|
|
1778
|
+
}
|
|
1779
|
+
acquired = entry;
|
|
1780
|
+
_state.label = 1;
|
|
1781
|
+
case 1:
|
|
1782
|
+
_state.trys.push([
|
|
1783
|
+
1,
|
|
1784
|
+
3,
|
|
1785
|
+
,
|
|
1786
|
+
4
|
|
1787
|
+
]);
|
|
1788
|
+
return [
|
|
1789
|
+
4,
|
|
1790
|
+
acquired.loading
|
|
1791
|
+
];
|
|
1792
|
+
case 2:
|
|
1793
|
+
session = _state.sent();
|
|
1794
|
+
return [
|
|
1795
|
+
3,
|
|
1796
|
+
4
|
|
1797
|
+
];
|
|
1798
|
+
case 3:
|
|
1799
|
+
e = _state.sent();
|
|
1800
|
+
acquired.leases -= 1;
|
|
1801
|
+
throw e;
|
|
1802
|
+
case 4:
|
|
1803
|
+
return [
|
|
1804
|
+
2,
|
|
1805
|
+
leaseFor(acquired, session)
|
|
1806
|
+
];
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
})();
|
|
1810
|
+
}
|
|
1811
|
+
return {
|
|
1812
|
+
openSession: acquire,
|
|
1813
|
+
useSession: function useSession(input, fn) {
|
|
1814
|
+
return _async_to_generator(function() {
|
|
1815
|
+
var lease;
|
|
1816
|
+
return _ts_generator(this, function(_state) {
|
|
1817
|
+
switch(_state.label){
|
|
1818
|
+
case 0:
|
|
1819
|
+
return [
|
|
1820
|
+
4,
|
|
1821
|
+
acquire(input)
|
|
1822
|
+
];
|
|
1823
|
+
case 1:
|
|
1824
|
+
lease = _state.sent();
|
|
1825
|
+
_state.label = 2;
|
|
1826
|
+
case 2:
|
|
1827
|
+
_state.trys.push([
|
|
1828
|
+
2,
|
|
1829
|
+
,
|
|
1830
|
+
4,
|
|
1831
|
+
5
|
|
1832
|
+
]);
|
|
1833
|
+
return [
|
|
1834
|
+
4,
|
|
1835
|
+
fn(lease.session)
|
|
1836
|
+
];
|
|
1837
|
+
case 3:
|
|
1838
|
+
return [
|
|
1839
|
+
2,
|
|
1840
|
+
_state.sent()
|
|
1841
|
+
];
|
|
1842
|
+
case 4:
|
|
1843
|
+
lease.release();
|
|
1844
|
+
return [
|
|
1845
|
+
7
|
|
1846
|
+
];
|
|
1847
|
+
case 5:
|
|
1848
|
+
return [
|
|
1849
|
+
2
|
|
1850
|
+
];
|
|
1851
|
+
}
|
|
1852
|
+
});
|
|
1853
|
+
})();
|
|
1854
|
+
},
|
|
1855
|
+
closeSession: function closeSession(uid) {
|
|
1856
|
+
return _async_to_generator(function() {
|
|
1857
|
+
var entry;
|
|
1858
|
+
return _ts_generator(this, function(_state) {
|
|
1859
|
+
switch(_state.label){
|
|
1860
|
+
case 0:
|
|
1861
|
+
entry = entries.get(firebaseUserSessionAppName({
|
|
1862
|
+
namespace: namespace,
|
|
1863
|
+
scope: scope,
|
|
1864
|
+
uid: uid
|
|
1865
|
+
}));
|
|
1866
|
+
if (!(entry != null)) return [
|
|
1867
|
+
3,
|
|
1868
|
+
2
|
|
1869
|
+
];
|
|
1870
|
+
detach(entry);
|
|
1871
|
+
return [
|
|
1872
|
+
4,
|
|
1873
|
+
teardownWhenIdle(entry)
|
|
1874
|
+
];
|
|
1875
|
+
case 1:
|
|
1876
|
+
_state.sent();
|
|
1877
|
+
_state.label = 2;
|
|
1878
|
+
case 2:
|
|
1879
|
+
return [
|
|
1880
|
+
2
|
|
1881
|
+
];
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
})();
|
|
1885
|
+
},
|
|
1886
|
+
close: function close() {
|
|
1887
|
+
return _async_to_generator(function() {
|
|
1888
|
+
var all;
|
|
1889
|
+
return _ts_generator(this, function(_state) {
|
|
1890
|
+
switch(_state.label){
|
|
1891
|
+
case 0:
|
|
1892
|
+
closed = true;
|
|
1893
|
+
all = Array.from(entries.values());
|
|
1894
|
+
entries.clear();
|
|
1895
|
+
return [
|
|
1896
|
+
4,
|
|
1897
|
+
Promise.all(all.map(function(entry) {
|
|
1898
|
+
return teardown(entry);
|
|
1899
|
+
}))
|
|
1900
|
+
];
|
|
1901
|
+
case 1:
|
|
1902
|
+
_state.sent();
|
|
1903
|
+
return [
|
|
1904
|
+
2
|
|
1905
|
+
];
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
})();
|
|
1909
|
+
},
|
|
1910
|
+
stats: function stats() {
|
|
1911
|
+
var leased = 0;
|
|
1912
|
+
entries.forEach(function(entry) {
|
|
1913
|
+
leased += entry.leases;
|
|
1914
|
+
});
|
|
1915
|
+
return {
|
|
1916
|
+
size: entries.size,
|
|
1917
|
+
leased: leased,
|
|
1918
|
+
maxSessions: maxSessions
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
export { CLIENT_FIRESTORE_DRIVER_IDENTIFIER, DEFAULT_FIREBASE_CLIENT_EMULATOR_HOST, DEFAULT_FIREBASE_USER_SESSION_POOL_SIZE, DEFAULT_FIRESTORE_IDENTITY_CONSUMER_NAME, FIREBASE_USER_SESSION_KEY_SEPARATOR, FIRESTORE_SESSION_API_PATH, FIRESTORE_SESSION_EXPIRY_BUFFER_MS, FIRESTORE_SESSION_MAX_CACHE_MS, FirestoreSessionError, closeFirebaseUserSession, closeFirebaseUserSessionApp, closeFirebaseUserSessionApps, defaultFirestoreSessionErrorFactory, fetchFirestoreSession, firebaseClientEmulatorsInUse, firebaseClientFirestoreIdentitySuggestion, firebaseUserSessionAppName, firebaseUserSessionNamespacePrefix, firebaseUserSessionPool, firestoreSessionEntryExpiresAt, firestoreSessionErrorCodeForStatus, firestoreSessionUrl, inspectFirebaseClientFirestoreIdentity, isFirebaseClientConfigComplete, isFirestoreSessionError, isFirestoreSessionExpired, openFirebaseUserSession };
|