From 72bf15a7a3acf517cbd54326b53f8ac2e7fe4dd3 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 12 Jan 2021 13:50:18 -0800 Subject: [PATCH 01/41] fix: Added lightweight App interface (#1132) * fix: Added lightweight App interface * fix: Updated API report * fix: Consolidated export statements --- docgen/generate-docs.js | 1 + etc/firebase-admin.api.md | 12 ++- gulpfile.js | 1 + src/core.ts | 143 ++++++++++++++++++++++++++++++++++ src/firebase-namespace-api.ts | 130 ++----------------------------- 5 files changed, 158 insertions(+), 129 deletions(-) create mode 100644 src/core.ts diff --git a/docgen/generate-docs.js b/docgen/generate-docs.js index 4f8b8df736..7025ff8da5 100644 --- a/docgen/generate-docs.js +++ b/docgen/generate-docs.js @@ -28,6 +28,7 @@ const repoPath = path.resolve(`${__dirname}/..`); const defaultSources = [ `${repoPath}/lib/firebase-namespace.d.ts`, `${repoPath}/lib/firebase-namespace-api.d.ts`, + `${repoPath}/lib/core.d.ts`, `${repoPath}/lib/**/*.d.ts`, ]; diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index b5b810a5e6..a89b4359c8 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -9,17 +9,23 @@ import { Bucket } from '@google-cloud/storage'; import * as _firestore from '@google-cloud/firestore'; import * as rtdb from '@firebase/database-types'; +// @public (undocumented) +export interface App { + delete(): Promise; + name: string; + options: AppOptions; +} + // @public (undocumented) export function app(name?: string): app.App; // @public (undocumented) export namespace app { - export interface App { + export interface App extends App { // (undocumented) auth(): auth.Auth; // (undocumented) database(url?: string): database.Database; - delete(): Promise; // (undocumented) firestore(): firestore.Firestore; // (undocumented) @@ -28,8 +34,6 @@ export namespace app { machineLearning(): machineLearning.MachineLearning; // (undocumented) messaging(): messaging.Messaging; - name: string; - options: AppOptions; // (undocumented) projectManagement(): projectManagement.ProjectManagement; // (undocumented) diff --git a/gulpfile.js b/gulpfile.js index 9ca1aeb941..3776404b3e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -86,6 +86,7 @@ gulp.task('compile', function() { 'lib/**/*.js', 'lib/**/index.d.ts', 'lib/firebase-namespace-api.d.ts', + 'lib/core.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/core.ts b/src/core.ts new file mode 100644 index 0000000000..b36cc4bb1d --- /dev/null +++ b/src/core.ts @@ -0,0 +1,143 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Agent } from 'http'; + +import { credential } from './credential/index'; + +/** + * Available options to pass to [`initializeApp()`](admin#.initializeApp). + */ +export interface AppOptions { + + /** + * A {@link credential.Credential `Credential`} object used to + * authenticate the Admin SDK. + * + * See [Initialize the SDK](/docs/admin/setup#initialize_the_sdk) for detailed + * documentation and code samples. + */ + credential?: credential.Credential; + + /** + * The object to use as the [`auth`](/docs/reference/security/database/#auth) + * variable in your Realtime Database Rules when the Admin SDK reads from or + * writes to the Realtime Database. This allows you to downscope the Admin SDK + * from its default full read and write privileges. + * + * You can pass `null` to act as an unauthenticated client. + * + * See + * [Authenticate with limited privileges](/docs/database/admin/start#authenticate-with-limited-privileges) + * for detailed documentation and code samples. + */ + databaseAuthVariableOverride?: object | null; + + /** + * The URL of the Realtime Database from which to read and write data. + */ + databaseURL?: string; + + /** + * The ID of the service account to be used for signing custom tokens. This + * can be found in the `client_email` field of a service account JSON file. + */ + serviceAccountId?: string; + + /** + * The name of the Google Cloud Storage bucket used for storing application data. + * Use only the bucket name without any prefixes or additions (do *not* prefix + * the name with "gs://"). + */ + storageBucket?: string; + + /** + * The ID of the Google Cloud project associated with the App. + */ + projectId?: string; + + /** + * An [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * to be used when making outgoing HTTP calls. This Agent instance is used + * by all services that make REST calls (e.g. `auth`, `messaging`, + * `projectManagement`). + * + * Realtime Database and Firestore use other means of communicating with + * the backend servers, so they do not use this HTTP Agent. `Credential` + * instances also do not use this HTTP Agent, but instead support + * specifying an HTTP Agent in the corresponding factory methods. + */ + httpAgent?: Agent; +} + +export interface App { + + /** + * The (read-only) name for this app. + * + * The default app's name is `"[DEFAULT]"`. + * + * @example + * ```javascript + * // The default app's name is "[DEFAULT]" + * admin.initializeApp(defaultAppConfig); + * console.log(admin.app().name); // "[DEFAULT]" + * ``` + * + * @example + * ```javascript + * // A named app's name is what you provide to initializeApp() + * var otherApp = admin.initializeApp(otherAppConfig, "other"); + * console.log(otherApp.name); // "other" + * ``` + */ + name: string; + + /** + * The (read-only) configuration options for this app. These are the original + * parameters given in + * {@link + * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp + * `admin.initializeApp()`}. + * + * @example + * ```javascript + * var app = admin.initializeApp(config); + * console.log(app.options.credential === config.credential); // true + * console.log(app.options.databaseURL === config.databaseURL); // true + * ``` + */ + options: AppOptions; + + /** + * Renders this local `FirebaseApp` unusable and frees the resources of + * all associated services (though it does *not* clean up any backend + * resources). When running the SDK locally, this method + * must be called to ensure graceful termination of the process. + * + * @example + * ```javascript + * app.delete() + * .then(function() { + * console.log("App deleted successfully"); + * }) + * .catch(function(error) { + * console.log("Error deleting app:", error); + * }); + * ``` + */ + delete(): Promise; +} diff --git a/src/firebase-namespace-api.ts b/src/firebase-namespace-api.ts index 36df1b778d..da78a8c393 100644 --- a/src/firebase-namespace-api.ts +++ b/src/firebase-namespace-api.ts @@ -14,9 +14,7 @@ * limitations under the License. */ -import { Agent } from 'http'; import { auth } from './auth/index'; -import { credential } from './credential/index'; import { database } from './database/index'; import { firestore } from './firestore/index'; import { instanceId } from './instance-id/index'; @@ -27,6 +25,10 @@ import { remoteConfig } from './remote-config/index'; import { securityRules } from './security-rules/index'; import { storage } from './storage/index'; +import { App as AppCore, AppOptions } from './core'; + +export * from './core'; + /** * `FirebaseError` is a subclass of the standard JavaScript `Error` object. In * addition to a message string and stack trace, it contains a string code. @@ -106,71 +108,6 @@ export interface FirebaseArrayIndexError { error: FirebaseError; } -/** - * Available options to pass to [`initializeApp()`](admin#.initializeApp). - */ -export interface AppOptions { - - /** - * A {@link credential.Credential `Credential`} object used to - * authenticate the Admin SDK. - * - * See [Initialize the SDK](/docs/admin/setup#initialize_the_sdk) for detailed - * documentation and code samples. - */ - credential?: credential.Credential; - - /** - * The object to use as the [`auth`](/docs/reference/security/database/#auth) - * variable in your Realtime Database Rules when the Admin SDK reads from or - * writes to the Realtime Database. This allows you to downscope the Admin SDK - * from its default full read and write privileges. - * - * You can pass `null` to act as an unauthenticated client. - * - * See - * [Authenticate with limited privileges](/docs/database/admin/start#authenticate-with-limited-privileges) - * for detailed documentation and code samples. - */ - databaseAuthVariableOverride?: object | null; - - /** - * The URL of the Realtime Database from which to read and write data. - */ - databaseURL?: string; - - /** - * The ID of the service account to be used for signing custom tokens. This - * can be found in the `client_email` field of a service account JSON file. - */ - serviceAccountId?: string; - - /** - * The name of the Google Cloud Storage bucket used for storing application data. - * Use only the bucket name without any prefixes or additions (do *not* prefix - * the name with "gs://"). - */ - storageBucket?: string; - - /** - * The ID of the Google Cloud project associated with the App. - */ - projectId?: string; - - /** - * An [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) - * to be used when making outgoing HTTP calls. This Agent instance is used - * by all services that make REST calls (e.g. `auth`, `messaging`, - * `projectManagement`). - * - * Realtime Database and Firestore use other means of communicating with - * the backend servers, so they do not use this HTTP Agent. `Credential` - * instances also do not use this HTTP Agent, but instead support - * specifying an HTTP Agent in the corresponding factory methods. - */ - httpAgent?: Agent; -} - // eslint-disable-next-line @typescript-eslint/no-namespace export namespace app { /** @@ -183,45 +120,7 @@ export namespace app { * `admin.initializeApp()`} * to create an app. */ - export interface App { - - /** - * The (read-only) name for this app. - * - * The default app's name is `"[DEFAULT]"`. - * - * @example - * ```javascript - * // The default app's name is "[DEFAULT]" - * admin.initializeApp(defaultAppConfig); - * console.log(admin.app().name); // "[DEFAULT]" - * ``` - * - * @example - * ```javascript - * // A named app's name is what you provide to initializeApp() - * var otherApp = admin.initializeApp(otherAppConfig, "other"); - * console.log(otherApp.name); // "other" - * ``` - */ - name: string; - - /** - * The (read-only) configuration options for this app. These are the original - * parameters given in - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. - * - * @example - * ```javascript - * var app = admin.initializeApp(config); - * console.log(app.options.credential === config.credential); // true - * console.log(app.options.databaseURL === config.databaseURL); // true - * ``` - */ - options: AppOptions; - + export interface App extends AppCore { auth(): auth.Auth; database(url?: string): database.Database; firestore(): firestore.Firestore; @@ -232,25 +131,6 @@ export namespace app { remoteConfig(): remoteConfig.RemoteConfig; securityRules(): securityRules.SecurityRules; storage(): storage.Storage; - - /** - * Renders this local `FirebaseApp` unusable and frees the resources of - * all associated services (though it does *not* clean up any backend - * resources). When running the SDK locally, this method - * must be called to ensure graceful termination of the process. - * - * @example - * ```javascript - * app.delete() - * .then(function() { - * console.log("App deleted successfully"); - * }) - * .catch(function(error) { - * console.log("Error deleting app:", error); - * }); - * ``` - */ - delete(): Promise; } } From 641dee0f74b9bf3e1c0008c3e4395e6b71611c16 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 14 Jan 2021 15:27:05 -0800 Subject: [PATCH 02/41] feat: Added firebase-admin/app submodule (#1134) * feat: Added firebase-admin/app submodule * fix: Updated API report for deleteApp and getApps * fix: Removed delete() method from lightweight App interface * fix: Updated some license headers --- api-extractor.json | 4 +- etc/firebase-admin.api.md | 11 +- gulpfile.js | 3 +- src/{ => app}/core.ts | 22 +-- src/{ => app}/firebase-app.ts | 56 +++--- src/{ => app}/firebase-namespace.ts | 52 ++--- src/app/index.ts | 23 +++ src/app/lifecycle.ts | 55 ++++++ src/auth/auth-api-request.ts | 2 +- src/auth/auth.ts | 2 +- src/auth/tenant-manager.ts | 2 +- src/auth/token-generator.ts | 2 +- src/auth/token-verifier.ts | 4 +- src/database/database-internal.ts | 2 +- ...-namespace.d.ts => default-namespace.d.ts} | 0 src/default-namespace.ts | 4 +- src/firebase-namespace-api.ts | 23 ++- src/firestore/firestore-internal.ts | 2 +- src/index.d.ts | 2 +- .../instance-id-request-internal.ts | 2 +- src/instance-id/instance-id.ts | 2 +- .../machine-learning-api-client.ts | 2 +- src/machine-learning/machine-learning.ts | 2 +- .../messaging-api-request-internal.ts | 2 +- src/messaging/messaging.ts | 2 +- ...project-management-api-request-internal.ts | 2 +- src/project-management/project-management.ts | 2 +- .../remote-config-api-client-internal.ts | 2 +- src/remote-config/remote-config.ts | 2 +- .../security-rules-api-client-internal.ts | 2 +- src/security-rules/security-rules.ts | 2 +- src/storage/storage.ts | 2 +- src/utils/api-request.ts | 2 +- test/resources/mocks.ts | 4 +- test/unit/{ => app}/firebase-app.spec.ts | 40 ++-- .../unit/{ => app}/firebase-namespace.spec.ts | 48 ++--- test/unit/app/index.spec.ts | 183 ++++++++++++++++++ test/unit/auth/auth-api-request.spec.ts | 2 +- test/unit/auth/auth.spec.ts | 2 +- test/unit/auth/tenant-manager.spec.ts | 2 +- test/unit/auth/token-generator.spec.ts | 2 +- test/unit/auth/token-verifier.spec.ts | 4 +- test/unit/database/database.spec.ts | 2 +- test/unit/firebase.spec.ts | 2 +- test/unit/firestore/firestore.spec.ts | 2 +- test/unit/index.spec.ts | 5 +- .../instance-id/instance-id-request.spec.ts | 2 +- test/unit/instance-id/instance-id.spec.ts | 2 +- .../machine-learning-api-client.spec.ts | 2 +- .../machine-learning/machine-learning.spec.ts | 2 +- test/unit/messaging/messaging.spec.ts | 2 +- .../project-management/android-app.spec.ts | 2 +- test/unit/project-management/ios-app.spec.ts | 2 +- .../project-management-api-request.spec.ts | 2 +- .../project-management.spec.ts | 2 +- .../remote-config-api-client.spec.ts | 2 +- test/unit/remote-config/remote-config.spec.ts | 2 +- .../security-rules-api-client.spec.ts | 2 +- .../security-rules/security-rules.spec.ts | 2 +- test/unit/storage/storage.spec.ts | 2 +- test/unit/utils.ts | 4 +- test/unit/utils/api-request.spec.ts | 2 +- test/unit/utils/index.spec.ts | 2 +- tsconfig.json | 1 + 64 files changed, 454 insertions(+), 180 deletions(-) rename src/{ => app}/core.ts (86%) rename src/{ => app}/firebase-app.ts (89%) rename src/{ => app}/firebase-namespace.ts (90%) create mode 100644 src/app/index.ts create mode 100644 src/app/lifecycle.ts rename src/{firebase-namespace.d.ts => default-namespace.d.ts} (100%) rename test/unit/{ => app}/firebase-app.spec.ts (97%) rename test/unit/{ => app}/firebase-namespace.spec.ts (95%) create mode 100644 test/unit/app/index.spec.ts diff --git a/api-extractor.json b/api-extractor.json index 140b645cfe..6f5a35e026 100644 --- a/api-extractor.json +++ b/api-extractor.json @@ -45,10 +45,10 @@ * * SUPPORTED TOKENS: , , */ - // We point to the firebase-namespace.d.ts file since index.d.ts uses namespace imports that are + // We point to the default-namespace.d.ts file since index.d.ts uses namespace imports that are // not supported by API Extractor. See https://github.com/microsoft/rushstack/issues/1029 and // https://github.com/microsoft/rushstack/issues/2338. - "mainEntryPointFilePath": "/lib/firebase-namespace.d.ts", + "mainEntryPointFilePath": "/lib/default-namespace.d.ts", /** * A list of NPM package names whose exports should be treated as part of this package. diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index a89b4359c8..b2f107e95f 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -11,7 +11,6 @@ import * as rtdb from '@firebase/database-types'; // @public (undocumented) export interface App { - delete(): Promise; name: string; options: AppOptions; } @@ -26,6 +25,7 @@ export namespace app { auth(): auth.Auth; // (undocumented) database(url?: string): database.Database; + delete(): Promise; // (undocumented) firestore(): firestore.Firestore; // (undocumented) @@ -427,6 +427,9 @@ export namespace database { const ServerValue: rtdb.ServerValue; } +// @public (undocumented) +export function deleteApp(app: App): Promise; + // @public export interface FirebaseArrayIndexError { error: FirebaseError; @@ -477,6 +480,12 @@ export namespace firestore { import setLogFunction = _firestore.setLogFunction; } +// @public (undocumented) +export function getApp(name?: string): App; + +// @public (undocumented) +export function getApps(): App[]; + // @public export interface GoogleOAuthAccessToken { // (undocumented) diff --git a/gulpfile.js b/gulpfile.js index 3776404b3e..1e1adcbba6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -87,6 +87,7 @@ gulp.task('compile', function() { 'lib/**/index.d.ts', 'lib/firebase-namespace-api.d.ts', 'lib/core.d.ts', + 'lib/app/*.d.ts', '!lib/utils/index.d.ts', ]; @@ -107,7 +108,7 @@ gulp.task('compile_test', function() { }); gulp.task('copyTypings', function() { - return gulp.src(['src/index.d.ts', 'src/firebase-namespace.d.ts']) + return gulp.src(['src/index.d.ts', 'src/default-namespace.d.ts']) // Add header .pipe(header(banner)) .pipe(gulp.dest(paths.build)) diff --git a/src/core.ts b/src/app/core.ts similarity index 86% rename from src/core.ts rename to src/app/core.ts index b36cc4bb1d..a311907727 100644 --- a/src/core.ts +++ b/src/app/core.ts @@ -1,4 +1,5 @@ /*! + * @license * Copyright 2021 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +17,7 @@ import { Agent } from 'http'; -import { credential } from './credential/index'; +import { credential } from '../credential/index'; /** * Available options to pass to [`initializeApp()`](admin#.initializeApp). @@ -121,23 +122,4 @@ export interface App { * ``` */ options: AppOptions; - - /** - * Renders this local `FirebaseApp` unusable and frees the resources of - * all associated services (though it does *not* clean up any backend - * resources). When running the SDK locally, this method - * must be called to ensure graceful termination of the process. - * - * @example - * ```javascript - * app.delete() - * .then(function() { - * console.log("App deleted successfully"); - * }) - * .catch(function(error) { - * console.log("Error deleting app:", error); - * }); - * ``` - */ - delete(): Promise; } diff --git a/src/firebase-app.ts b/src/app/firebase-app.ts similarity index 89% rename from src/firebase-app.ts rename to src/app/firebase-app.ts index fb8ad8b0f5..80f0fb2088 100644 --- a/src/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -15,26 +15,26 @@ * limitations under the License. */ -import { AppOptions, app } from './firebase-namespace-api'; -import { credential, GoogleOAuthAccessToken } from './credential/index'; -import { getApplicationDefault } from './credential/credential-internal'; -import * as validator from './utils/validator'; -import { deepCopy } from './utils/deep-copy'; +import { AppOptions, app } from '../firebase-namespace-api'; +import { credential, GoogleOAuthAccessToken } from '../credential/index'; +import { getApplicationDefault } from '../credential/credential-internal'; +import * as validator from '../utils/validator'; +import { deepCopy } from '../utils/deep-copy'; import { FirebaseNamespaceInternals } from './firebase-namespace'; -import { AppErrorCodes, FirebaseAppError } from './utils/error'; - -import { Auth } from './auth/auth'; -import { MachineLearning } from './machine-learning/machine-learning'; -import { Messaging } from './messaging/messaging'; -import { Storage } from './storage/storage'; -import { database } from './database/index'; -import { DatabaseService } from './database/database-internal'; +import { AppErrorCodes, FirebaseAppError } from '../utils/error'; + +import { Auth } from '../auth/auth'; +import { MachineLearning } from '../machine-learning/machine-learning'; +import { Messaging } from '../messaging/messaging'; +import { Storage } from '../storage/storage'; +import { database } from '../database/index'; +import { DatabaseService } from '../database/database-internal'; import { Firestore } from '@google-cloud/firestore'; -import { FirestoreService } from './firestore/firestore-internal'; -import { InstanceId } from './instance-id/instance-id'; -import { ProjectManagement } from './project-management/project-management'; -import { SecurityRules } from './security-rules/security-rules'; -import { RemoteConfig } from './remote-config/remote-config'; +import { FirestoreService } from '../firestore/firestore-internal'; +import { InstanceId } from '../instance-id/instance-id'; +import { ProjectManagement } from '../project-management/project-management'; +import { SecurityRules } from '../security-rules/security-rules'; +import { RemoteConfig } from '../remote-config/remote-config'; import Credential = credential.Credential; import Database = database.Database; @@ -277,7 +277,7 @@ export class FirebaseApp implements app.App { */ public auth(): Auth { return this.ensureService_('auth', () => { - const authService: typeof Auth = require('./auth/auth').Auth; + const authService: typeof Auth = require('../auth/auth').Auth; return new authService(this); }); } @@ -289,7 +289,7 @@ export class FirebaseApp implements app.App { */ public database(url?: string): Database { const service: DatabaseService = this.ensureService_('database', () => { - const dbService: typeof DatabaseService = require('./database/database-internal').DatabaseService; + const dbService: typeof DatabaseService = require('../database/database-internal').DatabaseService; return new dbService(this); }); return service.getDatabase(url); @@ -302,7 +302,7 @@ export class FirebaseApp implements app.App { */ public messaging(): Messaging { return this.ensureService_('messaging', () => { - const messagingService: typeof Messaging = require('./messaging/messaging').Messaging; + const messagingService: typeof Messaging = require('../messaging/messaging').Messaging; return new messagingService(this); }); } @@ -314,14 +314,14 @@ export class FirebaseApp implements app.App { */ public storage(): Storage { return this.ensureService_('storage', () => { - const storageService: typeof Storage = require('./storage/storage').Storage; + const storageService: typeof Storage = require('../storage/storage').Storage; return new storageService(this); }); } public firestore(): Firestore { const service: FirestoreService = this.ensureService_('firestore', () => { - const firestoreService: typeof FirestoreService = require('./firestore/firestore-internal').FirestoreService; + const firestoreService: typeof FirestoreService = require('../firestore/firestore-internal').FirestoreService; return new firestoreService(this); }); return service.client; @@ -334,7 +334,7 @@ export class FirebaseApp implements app.App { */ public instanceId(): InstanceId { return this.ensureService_('iid', () => { - const iidService: typeof InstanceId = require('./instance-id/instance-id').InstanceId; + const iidService: typeof InstanceId = require('../instance-id/instance-id').InstanceId; return new iidService(this); }); } @@ -347,7 +347,7 @@ export class FirebaseApp implements app.App { public machineLearning(): MachineLearning { return this.ensureService_('machine-learning', () => { const machineLearningService: typeof MachineLearning = - require('./machine-learning/machine-learning').MachineLearning; + require('../machine-learning/machine-learning').MachineLearning; return new machineLearningService(this); }); } @@ -360,7 +360,7 @@ export class FirebaseApp implements app.App { public projectManagement(): ProjectManagement { return this.ensureService_('project-management', () => { const projectManagementService: typeof ProjectManagement = - require('./project-management/project-management').ProjectManagement; + require('../project-management/project-management').ProjectManagement; return new projectManagementService(this); }); } @@ -373,7 +373,7 @@ export class FirebaseApp implements app.App { public securityRules(): SecurityRules { return this.ensureService_('security-rules', () => { const securityRulesService: typeof SecurityRules = - require('./security-rules/security-rules').SecurityRules; + require('../security-rules/security-rules').SecurityRules; return new securityRulesService(this); }); } @@ -385,7 +385,7 @@ export class FirebaseApp implements app.App { */ public remoteConfig(): RemoteConfig { return this.ensureService_('remoteConfig', () => { - const remoteConfigService: typeof RemoteConfig = require('./remote-config/remote-config').RemoteConfig; + const remoteConfigService: typeof RemoteConfig = require('../remote-config/remote-config').RemoteConfig; return new remoteConfigService(this); }); } diff --git a/src/firebase-namespace.ts b/src/app/firebase-namespace.ts similarity index 90% rename from src/firebase-namespace.ts rename to src/app/firebase-namespace.ts index 43b12a92c9..a0938fec31 100644 --- a/src/firebase-namespace.ts +++ b/src/app/firebase-namespace.ts @@ -17,25 +17,25 @@ import fs = require('fs'); -import { AppErrorCodes, FirebaseAppError } from './utils/error'; -import { AppOptions, app } from './firebase-namespace-api'; +import { AppErrorCodes, FirebaseAppError } from '../utils/error'; +import { AppOptions, app } from '../firebase-namespace-api'; import { FirebaseApp } from './firebase-app'; -import { cert, refreshToken, applicationDefault } from './credential/credential'; -import { getApplicationDefault } from './credential/credential-internal'; - -import { auth } from './auth/index'; -import { database } from './database/index'; -import { firestore } from './firestore/index'; -import { instanceId } from './instance-id/index'; -import { machineLearning } from './machine-learning/index'; -import { messaging } from './messaging/index'; -import { projectManagement } from './project-management/index'; -import { remoteConfig } from './remote-config/index'; -import { securityRules } from './security-rules/index'; -import { storage } from './storage/index'; - -import * as validator from './utils/validator'; -import { getSdkVersion } from './utils/index'; +import { cert, refreshToken, applicationDefault } from '../credential/credential'; +import { getApplicationDefault } from '../credential/credential-internal'; + +import { auth } from '../auth/index'; +import { database } from '../database/index'; +import { firestore } from '../firestore/index'; +import { instanceId } from '../instance-id/index'; +import { machineLearning } from '../machine-learning/index'; +import { messaging } from '../messaging/index'; +import { projectManagement } from '../project-management/index'; +import { remoteConfig } from '../remote-config/index'; +import { securityRules } from '../security-rules/index'; +import { storage } from '../storage/index'; + +import * as validator from '../utils/validator'; +import { getSdkVersion } from '../utils/index'; import App = app.App; import Auth = auth.Auth; @@ -223,7 +223,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).auth(); }; - const auth = require('./auth/auth').Auth; + const auth = require('../auth/auth').Auth; return Object.assign(fn, { Auth: auth }); } @@ -248,7 +248,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).messaging(); }; - const messaging = require('./messaging/messaging').Messaging; + const messaging = require('../messaging/messaging').Messaging; return Object.assign(fn, { Messaging: messaging }); } @@ -260,7 +260,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).storage(); }; - const storage = require('./storage/storage').Storage; + const storage = require('../storage/storage').Storage; return Object.assign(fn, { Storage: storage }); } @@ -305,7 +305,7 @@ export class FirebaseNamespace { return this.ensureApp(app).machineLearning(); }; const machineLearning = - require('./machine-learning/machine-learning').MachineLearning; + require('../machine-learning/machine-learning').MachineLearning; return Object.assign(fn, { MachineLearning: machineLearning }); } @@ -317,7 +317,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).instanceId(); }; - const instanceId = require('./instance-id/instance-id').InstanceId; + const instanceId = require('../instance-id/instance-id').InstanceId; return Object.assign(fn, { InstanceId: instanceId }); } @@ -329,7 +329,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).projectManagement(); }; - const projectManagement = require('./project-management/project-management').ProjectManagement; + const projectManagement = require('../project-management/project-management').ProjectManagement; return Object.assign(fn, { ProjectManagement: projectManagement }); } @@ -341,7 +341,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).securityRules(); }; - const securityRules = require('./security-rules/security-rules').SecurityRules; + const securityRules = require('../security-rules/security-rules').SecurityRules; return Object.assign(fn, { SecurityRules: securityRules }); } @@ -353,7 +353,7 @@ export class FirebaseNamespace { const fn: FirebaseServiceNamespace = (app?: App) => { return this.ensureApp(app).remoteConfig(); }; - const remoteConfig = require('./remote-config/remote-config').RemoteConfig; + const remoteConfig = require('../remote-config/remote-config').RemoteConfig; return Object.assign(fn, { RemoteConfig: remoteConfig }); } diff --git a/src/app/index.ts b/src/app/index.ts new file mode 100644 index 0000000000..5b4125d76a --- /dev/null +++ b/src/app/index.ts @@ -0,0 +1,23 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getSdkVersion } from '../utils'; + +export { App, AppOptions } from './core' +export { initializeApp, getApp, getApps, deleteApp } from './lifecycle'; + +export const SDK_VERSION = getSdkVersion(); diff --git a/src/app/lifecycle.ts b/src/app/lifecycle.ts new file mode 100644 index 0000000000..8fc03b6119 --- /dev/null +++ b/src/app/lifecycle.ts @@ -0,0 +1,55 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AppErrorCodes, FirebaseAppError } from '../utils/error'; +import { App, AppOptions } from './core'; +import { FirebaseNamespace } from './firebase-namespace'; + +/** + * In order to maintain backward compatibility, we instantiate a default namespace instance in + * this module, and delegate all app lifecycle operations to it. In a future implementation where + * the old admin namespace is no longer supported, we should remove this, and implement app + * lifecycle management in this module itself. + * + * @internal + */ +export const defaultNamespace = new FirebaseNamespace(); + +export function initializeApp(options?: AppOptions, name?: string): App { + return defaultNamespace.initializeApp(options, name); +} + +export function getApp(name?: string): App { + return defaultNamespace.app(name); +} + +export function getApps(): App[] { + return defaultNamespace.apps; +} + +export function deleteApp(app: App): Promise { + if (typeof app !== 'object' || app === null || !('options' in app)) { + throw new FirebaseAppError(AppErrorCodes.INVALID_ARGUMENT, 'Invalid app argument.'); + } + + // Make sure the given app already exists. + const existingApp = getApp(app.name); + + // Delegate delete operation to the App instance itself for now. This will tear down any + // local app state, and also remove it from the global map. + return (existingApp as any).delete(); +} diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 0380debeb8..7552f5dfb4 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -21,7 +21,7 @@ import { deepCopy, deepExtend } from '../utils/deep-copy'; import { isUidIdentifier, isEmailIdentifier, isPhoneIdentifier, isProviderIdentifier } from './identifier'; -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 8e35df82c4..c205605224 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -19,7 +19,7 @@ import { UserRecord } from './user-record'; import { isUidIdentifier, isEmailIdentifier, isPhoneIdentifier, isProviderIdentifier, } from './identifier'; -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { FirebaseTokenGenerator, EmulatedSigner, cryptoSignerFromApp } from './token-generator'; import { AbstractAuthRequestHandler, AuthRequestHandler, TenantAwareAuthRequestHandler, useEmulator, diff --git a/src/auth/tenant-manager.ts b/src/auth/tenant-manager.ts index f97f0aaefc..b11f4d605b 100644 --- a/src/auth/tenant-manager.ts +++ b/src/auth/tenant-manager.ts @@ -15,7 +15,7 @@ */ import { AuthRequestHandler } from './auth-api-request'; -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { TenantAwareAuth } from './auth'; import { Tenant, TenantServerResponse } from './tenant'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index a8a76c7b28..f868f08fc8 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { ServiceAccountCredential } from '../credential/credential-internal'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { AuthorizedHttpClient, HttpError, HttpRequestConfig, HttpClient } from '../utils/api-request'; diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index c39aa84fc7..4c244523a4 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -19,7 +19,7 @@ import * as util from '../utils/index'; import * as validator from '../utils/validator'; import * as jwt from 'jsonwebtoken'; import { HttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { auth } from './index'; import DecodedIdToken = auth.DecodedIdToken; @@ -79,7 +79,7 @@ export class FirebaseTokenVerifier { constructor(private clientCertUrl: string, private algorithm: jwt.Algorithm, private issuer: string, private tokenInfo: FirebaseTokenInfo, private readonly app: FirebaseApp) { - + if (!validator.isURL(clientCertUrl)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, diff --git a/src/database/database-internal.ts b/src/database/database-internal.ts index a469a41773..6b7af1c10c 100644 --- a/src/database/database-internal.ts +++ b/src/database/database-internal.ts @@ -17,7 +17,7 @@ import { URL } from 'url'; import * as path from 'path'; -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; import { Database as DatabaseImpl } from '@firebase/database'; import { database } from './index'; diff --git a/src/firebase-namespace.d.ts b/src/default-namespace.d.ts similarity index 100% rename from src/firebase-namespace.d.ts rename to src/default-namespace.d.ts diff --git a/src/default-namespace.ts b/src/default-namespace.ts index d15f3cae02..60dd22fe3a 100644 --- a/src/default-namespace.ts +++ b/src/default-namespace.ts @@ -15,9 +15,7 @@ * limitations under the License. */ -import { FirebaseNamespace } from './firebase-namespace'; - -const firebaseAdmin = new FirebaseNamespace(); +import { defaultNamespace as firebaseAdmin } from './app/lifecycle'; // Inject a circular default export to allow users to use both: // diff --git a/src/firebase-namespace-api.ts b/src/firebase-namespace-api.ts index da78a8c393..9a56547118 100644 --- a/src/firebase-namespace-api.ts +++ b/src/firebase-namespace-api.ts @@ -25,9 +25,9 @@ import { remoteConfig } from './remote-config/index'; import { securityRules } from './security-rules/index'; import { storage } from './storage/index'; -import { App as AppCore, AppOptions } from './core'; +import { App as AppCore, AppOptions } from './app/index'; -export * from './core'; +export * from './app/index'; /** * `FirebaseError` is a subclass of the standard JavaScript `Error` object. In @@ -131,6 +131,25 @@ export namespace app { remoteConfig(): remoteConfig.RemoteConfig; securityRules(): securityRules.SecurityRules; storage(): storage.Storage; + + /** + * Renders this local `FirebaseApp` unusable and frees the resources of + * all associated services (though it does *not* clean up any backend + * resources). When running the SDK locally, this method + * must be called to ensure graceful termination of the process. + * + * @example + * ```javascript + * app.delete() + * .then(function() { + * console.log("App deleted successfully"); + * }) + * .catch(function(error) { + * console.log("Error deleting app:", error); + * }); + * ``` + */ + delete(): Promise; } } diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts index 2188ec223a..dc20f1cfdf 100644 --- a/src/firestore/firestore-internal.ts +++ b/src/firestore/firestore-internal.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { FirebaseFirestoreError } from '../utils/error'; import { ServiceAccountCredential, isApplicationDefault } from '../credential/credential-internal'; import { Firestore, Settings } from '@google-cloud/firestore'; diff --git a/src/index.d.ts b/src/index.d.ts index 1807b1e079..b893c88183 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import * as admin from './firebase-namespace'; +import * as admin from './default-namespace'; declare module 'firebase-admin' { } diff --git a/src/instance-id/instance-id-request-internal.ts b/src/instance-id/instance-id-request-internal.ts index e0d404d602..fbd3ba15ed 100644 --- a/src/instance-id/instance-id-request-internal.ts +++ b/src/instance-id/instance-id-request-internal.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error'; import { ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index 80afaeae48..b92662bfd7 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error'; import { FirebaseInstanceIdRequestHandler } from './instance-id-request-internal'; import { instanceId } from './index'; diff --git a/src/machine-learning/machine-learning-api-client.ts b/src/machine-learning/machine-learning-api-client.ts index 2281b84d53..18a53180cd 100644 --- a/src/machine-learning/machine-learning-api-client.ts +++ b/src/machine-learning/machine-learning-api-client.ts @@ -21,7 +21,7 @@ import { PrefixedFirebaseError } from '../utils/error'; import { FirebaseMachineLearningError, MachineLearningErrorCode } from './machine-learning-utils'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { machineLearning } from './index'; import GcsTfliteModelOptions = machineLearning.GcsTfliteModelOptions; diff --git a/src/machine-learning/machine-learning.ts b/src/machine-learning/machine-learning.ts index dcbb72caec..986c0b852e 100644 --- a/src/machine-learning/machine-learning.ts +++ b/src/machine-learning/machine-learning.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { MachineLearningApiClient, ModelResponse, ModelUpdateOptions, isGcsTfliteModelOptions } from './machine-learning-api-client'; diff --git a/src/messaging/messaging-api-request-internal.ts b/src/messaging/messaging-api-request-internal.ts index d74f621e0d..1e63369018 100644 --- a/src/messaging/messaging-api-request-internal.ts +++ b/src/messaging/messaging-api-request-internal.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { HttpMethod, AuthorizedHttpClient, HttpRequestConfig, HttpError, HttpResponse, } from '../utils/api-request'; diff --git a/src/messaging/messaging.ts b/src/messaging/messaging.ts index 88e66cf565..79da21221a 100644 --- a/src/messaging/messaging.ts +++ b/src/messaging/messaging.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { deepCopy, deepExtend } from '../utils/deep-copy'; import { SubRequest } from './batch-request-internal'; import { validateMessage, BLACKLISTED_DATA_PAYLOAD_KEYS, BLACKLISTED_OPTIONS_KEYS } from './messaging-internal'; diff --git a/src/project-management/project-management-api-request-internal.ts b/src/project-management/project-management-api-request-internal.ts index 445faf83dc..a24111f424 100644 --- a/src/project-management/project-management-api-request-internal.ts +++ b/src/project-management/project-management-api-request-internal.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { AuthorizedHttpClient, HttpError, HttpMethod, HttpRequestConfig, ExponentialBackoffPoller, } from '../utils/api-request'; diff --git a/src/project-management/project-management.ts b/src/project-management/project-management.ts index 9dc8b29902..1a7ac03733 100644 --- a/src/project-management/project-management.ts +++ b/src/project-management/project-management.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { FirebaseProjectManagementError } from '../utils/error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; diff --git a/src/remote-config/remote-config-api-client-internal.ts b/src/remote-config/remote-config-api-client-internal.ts index ac2d071453..bb7b5a7eed 100644 --- a/src/remote-config/remote-config-api-client-internal.ts +++ b/src/remote-config/remote-config-api-client-internal.ts @@ -17,7 +17,7 @@ import { remoteConfig } from './index'; import { HttpRequestConfig, HttpClient, HttpError, AuthorizedHttpClient, HttpResponse } from '../utils/api-request'; import { PrefixedFirebaseError } from '../utils/error'; -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index d0b0046832..c4f9c9fc03 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import * as validator from '../utils/validator'; import { remoteConfig } from './index'; import { FirebaseRemoteConfigError, RemoteConfigApiClient } from './remote-config-api-client-internal'; diff --git a/src/security-rules/security-rules-api-client-internal.ts b/src/security-rules/security-rules-api-client-internal.ts index 50fdbc9cb8..1719e64316 100644 --- a/src/security-rules/security-rules-api-client-internal.ts +++ b/src/security-rules/security-rules-api-client-internal.ts @@ -19,7 +19,7 @@ import { PrefixedFirebaseError } from '../utils/error'; import { FirebaseSecurityRulesError, SecurityRulesErrorCode } from './security-rules-internal'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; const RULES_V1_API = 'https://firebaserules.googleapis.com/v1'; const FIREBASE_VERSION_HEADER = { diff --git a/src/security-rules/security-rules.ts b/src/security-rules/security-rules.ts index 2206410df0..9b624e03d9 100644 --- a/src/security-rules/security-rules.ts +++ b/src/security-rules/security-rules.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import * as validator from '../utils/validator'; import { SecurityRulesApiClient, RulesetResponse, RulesetContent, ListRulesetsResponse, diff --git a/src/storage/storage.ts b/src/storage/storage.ts index 4364658a8c..fb873efe20 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { FirebaseError } from '../utils/error'; import { ServiceAccountCredential, isApplicationDefault } from '../credential/credential-internal'; import { Bucket, Storage as StorageClient } from '@google-cloud/storage'; diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index a41a2f9b48..221a5221f2 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../firebase-app'; +import { FirebaseApp } from '../app/firebase-app'; import { AppErrorCodes, FirebaseAppError } from './error'; import * as validator from './validator'; diff --git a/test/resources/mocks.ts b/test/resources/mocks.ts index 5512756039..1196bba6c8 100644 --- a/test/resources/mocks.ts +++ b/test/resources/mocks.ts @@ -26,8 +26,8 @@ import * as _ from 'lodash'; import * as jwt from 'jsonwebtoken'; import { AppOptions } from '../../src/firebase-namespace-api'; -import { FirebaseNamespace } from '../../src/firebase-namespace'; -import { FirebaseApp } from '../../src/firebase-app'; +import { FirebaseNamespace } from '../../src/app/firebase-namespace'; +import { FirebaseApp } from '../../src/app/firebase-app'; import { credential as _credential, GoogleOAuthAccessToken } from '../../src/credential/index'; import { ServiceAccountCredential } from '../../src/credential/credential-internal'; diff --git a/test/unit/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts similarity index 97% rename from test/unit/firebase-app.spec.ts rename to test/unit/app/firebase-app.spec.ts index 49da6736c5..0d2cbb15fa 100644 --- a/test/unit/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -23,25 +23,27 @@ import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as utils from './utils'; -import * as mocks from '../resources/mocks'; - -import { GoogleOAuthAccessToken } from '../../src/credential/index'; -import { ServiceAccountCredential } from '../../src/credential/credential-internal'; -import { FirebaseApp, FirebaseAccessToken } from '../../src/firebase-app'; -import { FirebaseNamespace, FirebaseNamespaceInternals, FIREBASE_CONFIG_VAR } from '../../src/firebase-namespace'; - -import { auth } from '../../src/auth/index'; -import { messaging } from '../../src/messaging/index'; -import { machineLearning } from '../../src/machine-learning/index'; -import { storage } from '../../src/storage/index'; -import { firestore } from '../../src/firestore/index'; -import { database } from '../../src/database/index'; -import { instanceId } from '../../src/instance-id/index'; -import { projectManagement } from '../../src/project-management/index'; -import { securityRules } from '../../src/security-rules/index'; -import { remoteConfig } from '../../src/remote-config/index'; -import { FirebaseAppError, AppErrorCodes } from '../../src/utils/error'; +import * as utils from '../utils'; +import * as mocks from '../../resources/mocks'; + +import { GoogleOAuthAccessToken } from '../../../src/credential/index'; +import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { FirebaseApp, FirebaseAccessToken } from '../../../src/app/firebase-app'; +import { + FirebaseNamespace, FirebaseNamespaceInternals, FIREBASE_CONFIG_VAR +} from '../../../src/app/firebase-namespace'; + +import { auth } from '../../../src/auth/index'; +import { messaging } from '../../../src/messaging/index'; +import { machineLearning } from '../../../src/machine-learning/index'; +import { storage } from '../../../src/storage/index'; +import { firestore } from '../../../src/firestore/index'; +import { database } from '../../../src/database/index'; +import { instanceId } from '../../../src/instance-id/index'; +import { projectManagement } from '../../../src/project-management/index'; +import { securityRules } from '../../../src/security-rules/index'; +import { remoteConfig } from '../../../src/remote-config/index'; +import { FirebaseAppError, AppErrorCodes } from '../../../src/utils/error'; import Auth = auth.Auth; import Database = database.Database; diff --git a/test/unit/firebase-namespace.spec.ts b/test/unit/app/firebase-namespace.spec.ts similarity index 95% rename from test/unit/firebase-namespace.spec.ts rename to test/unit/app/firebase-namespace.spec.ts index 07e5a8ba78..1bc0c0b636 100644 --- a/test/unit/firebase-namespace.spec.ts +++ b/test/unit/app/firebase-namespace.spec.ts @@ -22,9 +22,9 @@ import * as chai from 'chai'; import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as mocks from '../resources/mocks'; +import * as mocks from '../../resources/mocks'; -import { FirebaseNamespace } from '../../src/firebase-namespace'; +import { FirebaseNamespace } from '../../../src/app/firebase-namespace'; import { enableLogging, Database as DatabaseImpl, @@ -43,28 +43,28 @@ import { v1beta1, setLogFunction, } from '@google-cloud/firestore'; -import { getSdkVersion } from '../../src/utils/index'; - -import { app } from '../../src/firebase-namespace-api'; -import { auth } from '../../src/auth/index'; -import { messaging } from '../../src/messaging/index'; -import { machineLearning } from '../../src/machine-learning/index'; -import { storage } from '../../src/storage/index'; -import { firestore } from '../../src/firestore/index'; -import { database } from '../../src/database/index'; -import { instanceId } from '../../src/instance-id/index'; -import { projectManagement } from '../../src/project-management/index'; -import { securityRules } from '../../src/security-rules/index'; -import { remoteConfig } from '../../src/remote-config/index'; - -import { Auth as AuthImpl } from '../../src/auth/auth'; -import { InstanceId as InstanceIdImpl } from '../../src/instance-id/instance-id'; -import { MachineLearning as MachineLearningImpl } from '../../src/machine-learning/machine-learning'; -import { Messaging as MessagingImpl } from '../../src/messaging/messaging'; -import { ProjectManagement as ProjectManagementImpl } from '../../src/project-management/project-management'; -import { RemoteConfig as RemoteConfigImpl } from '../../src/remote-config/remote-config'; -import { SecurityRules as SecurityRulesImpl } from '../../src/security-rules/security-rules'; -import { Storage as StorageImpl } from '../../src/storage/storage'; +import { getSdkVersion } from '../../../src/utils/index'; + +import { app } from '../../../src/firebase-namespace-api'; +import { auth } from '../../../src/auth/index'; +import { messaging } from '../../../src/messaging/index'; +import { machineLearning } from '../../../src/machine-learning/index'; +import { storage } from '../../../src/storage/index'; +import { firestore } from '../../../src/firestore/index'; +import { database } from '../../../src/database/index'; +import { instanceId } from '../../../src/instance-id/index'; +import { projectManagement } from '../../../src/project-management/index'; +import { securityRules } from '../../../src/security-rules/index'; +import { remoteConfig } from '../../../src/remote-config/index'; + +import { Auth as AuthImpl } from '../../../src/auth/auth'; +import { InstanceId as InstanceIdImpl } from '../../../src/instance-id/instance-id'; +import { MachineLearning as MachineLearningImpl } from '../../../src/machine-learning/machine-learning'; +import { Messaging as MessagingImpl } from '../../../src/messaging/messaging'; +import { ProjectManagement as ProjectManagementImpl } from '../../../src/project-management/project-management'; +import { RemoteConfig as RemoteConfigImpl } from '../../../src/remote-config/remote-config'; +import { SecurityRules as SecurityRulesImpl } from '../../../src/security-rules/security-rules'; +import { Storage as StorageImpl } from '../../../src/storage/storage'; import App = app.App; import Auth = auth.Auth; diff --git a/test/unit/app/index.spec.ts b/test/unit/app/index.spec.ts new file mode 100644 index 0000000000..d4da3ae4b2 --- /dev/null +++ b/test/unit/app/index.spec.ts @@ -0,0 +1,183 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as _ from 'lodash'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as mocks from '../../resources/mocks'; +import * as sinon from 'sinon'; + +import { initializeApp, getApp, getApps, deleteApp, SDK_VERSION } from '../../../src/app/index'; + +chai.should(); +chai.use(chaiAsPromised); + +const expect = chai.expect; + + +describe('firebase-admin/app', () => { + afterEach(() => { + const deletePromises: Array> = []; + getApps().forEach((app) => { + deletePromises.push(deleteApp(app)); + }); + + return Promise.all(deletePromises); + }); + + describe('#initializeApp()', () => { + const invalidOptions: any[] = [null, NaN, 0, 1, true, false, '', 'a', [], _.noop]; + invalidOptions.forEach((invalidOption: any) => { + it('should throw given invalid options object: ' + JSON.stringify(invalidOption), () => { + expect(() => { + initializeApp(invalidOption); + }).to.throw('Invalid Firebase app options'); + }); + }); + + it('should use application default credentials when no credentials are explicitly specified', () => { + const app = initializeApp(mocks.appOptionsNoAuth); + expect(app.options).to.have.property('credential'); + expect(app.options.credential).to.not.be.undefined; + }); + + it('should not modify the provided options object', () => { + const optionsClone = _.clone(mocks.appOptions); + initializeApp(mocks.appOptions); + expect(optionsClone).to.deep.equal(mocks.appOptions); + }); + + const invalidCredentials = [undefined, null, NaN, 0, 1, '', 'a', true, false, '', _.noop]; + invalidCredentials.forEach((invalidCredential) => { + it('should throw given non-object credential: ' + JSON.stringify(invalidCredential), () => { + expect(() => { + initializeApp({ + credential: invalidCredential as any, + }); + }).to.throw('Invalid Firebase app options'); + }); + }); + + it('should throw given a credential which doesn\'t implement the Credential interface', () => { + expect(() => { + initializeApp({ + credential: {}, + } as any); + }).to.throw('Invalid Firebase app options'); + + expect(() => { + initializeApp({ + credential: { + getAccessToken: true, + }, + } as any); + }).to.throw('Invalid Firebase app options'); + }); + }); + + describe('#getApp()', () => { + const invalidOptions: any[] = [null, NaN, 0, 1, true, false, '', [], _.noop]; + invalidOptions.forEach((invalidOption: any) => { + it('should throw given invalid app name: ' + JSON.stringify(invalidOption), () => { + expect(() => { + getApp(invalidOption); + }).to.throw('Invalid Firebase app name'); + }); + }); + + it('should return default app when name not specified', () => { + initializeApp(mocks.appOptionsNoAuth); + const defaulApp = getApp(); + expect(defaulApp.name).to.equal('[DEFAULT]'); + }); + + it('should return named app when available', () => { + initializeApp(mocks.appOptionsNoAuth, 'testApp'); + const testApp = getApp('testApp'); + expect(testApp.name).to.equal('testApp'); + }); + + it('should throw when the default app does not exist', () => { + expect(() => getApp()).to.throw('The default Firebase app does not exist'); + }); + + it('should throw when the specified app does not exist', () => { + expect(() => getApp('testApp')).to.throw('Firebase app named "testApp" does not exist'); + }); + }); + + describe('#getApps()', () => { + it('should return empty array when no apps available', () => { + const apps = getApps(); + expect(apps).to.be.empty; + }); + + it('should return a non-empty array of apps', () => { + initializeApp(mocks.appOptionsNoAuth); + initializeApp(mocks.appOptionsNoAuth, 'testApp'); + const apps = getApps(); + expect(apps.length).to.equal(2); + + const appNames = apps.map((a) => a.name); + expect(appNames).to.contain('[DEFAULT]'); + expect(appNames).to.contain('testApp'); + }); + + it('apps array is immutable', () => { + initializeApp(mocks.appOptionsNoAuth); + const apps = getApps(); + expect(apps.length).to.equal(1); + apps.push({} as any); + + expect(getApps().length).to.equal(1); + }); + }); + + describe('#deleteApp()', () => { + it('should delete the specified app', () => { + const app = initializeApp(mocks.appOptionsNoAuth); + const spy = sinon.spy(app as any, 'delete'); + deleteApp(app); + expect(getApps()).to.be.empty; + expect(spy.calledOnce); + }); + + it('should throw if the app is already deleted', () => { + const app = initializeApp(mocks.appOptionsNoAuth); + deleteApp(app); + expect(() => deleteApp(app)).to.throw('The default Firebase app does not exist'); + }); + + const invalidOptions: any[] = [null, NaN, 0, 1, true, false, '', [], _.noop]; + invalidOptions.forEach((invalidOption: any) => { + it('should throw given invalid app: ' + JSON.stringify(invalidOption), () => { + expect(() => { + deleteApp(invalidOption); + }).to.throw('Invalid app argument'); + }); + }); + }); + + describe('SDK_VERSION', () => { + it('should indicate the current version of the SDK', () => { + const { version } = require('../../../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires + expect(SDK_VERSION).to.equal(version); + }); + }); +}); diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index ff154d3b2c..96b631356d 100644 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -27,7 +27,7 @@ import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { deepCopy, deepExtend } from '../../../src/utils/deep-copy'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; import * as validator from '../../../src/utils/validator'; import { diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index 26521b30cd..ac3a680d8d 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -29,7 +29,7 @@ import * as mocks from '../../resources/mocks'; import { Auth, TenantAwareAuth, BaseAuth } from '../../../src/auth/auth'; import { UserRecord } from '../../../src/auth/user-record'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { AuthRequestHandler, TenantAwareAuthRequestHandler, AbstractAuthRequestHandler, } from '../../../src/auth/auth-api-request'; diff --git a/test/unit/auth/tenant-manager.spec.ts b/test/unit/auth/tenant-manager.spec.ts index 9c7ef80be1..c00096da32 100644 --- a/test/unit/auth/tenant-manager.spec.ts +++ b/test/unit/auth/tenant-manager.spec.ts @@ -23,7 +23,7 @@ import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { AuthRequestHandler } from '../../../src/auth/auth-api-request'; import { Tenant, TenantServerResponse } from '../../../src/auth/tenant'; import { TenantManager } from '../../../src/auth/tenant-manager'; diff --git a/test/unit/auth/token-generator.spec.ts b/test/unit/auth/token-generator.spec.ts index c519c7a3ed..2653b4d36d 100644 --- a/test/unit/auth/token-generator.spec.ts +++ b/test/unit/auth/token-generator.spec.ts @@ -31,7 +31,7 @@ import { import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; import { AuthorizedHttpClient, HttpClient } from '../../../src/utils/api-request'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import * as utils from '../utils'; import { FirebaseAuthError } from '../../../src/utils/error'; diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 6b370a8bb4..76e3444c51 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -33,7 +33,7 @@ import * as verifier from '../../../src/auth/token-verifier'; import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; import { AuthClientErrorCode } from '../../../src/utils/error'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { Algorithm } from 'jsonwebtoken'; chai.should(); @@ -106,7 +106,7 @@ function mockFailedFetchPublicKeys(): nock.Scope { } function createTokenVerifier( - app: FirebaseApp, + app: FirebaseApp, options: { algorithm?: Algorithm } = {} ): verifier.FirebaseTokenVerifier { const algorithm = options.algorithm || 'RS256'; diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index 4a4f969b1e..c946d498de 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -22,7 +22,7 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { DatabaseService } from '../../../src/database/database-internal'; import { database } from '../../../src/database/index'; import * as utils from '../utils'; diff --git a/test/unit/firebase.spec.ts b/test/unit/firebase.spec.ts index 3048121529..fa758584ec 100644 --- a/test/unit/firebase.spec.ts +++ b/test/unit/firebase.spec.ts @@ -28,7 +28,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../resources/mocks'; import * as firebaseAdmin from '../../src/index'; -import { FirebaseApp, FirebaseAppInternals } from '../../src/firebase-app'; +import { FirebaseApp, FirebaseAppInternals } from '../../src/app/firebase-app'; import { RefreshTokenCredential, ServiceAccountCredential, isApplicationDefault } from '../../src/credential/credential-internal'; diff --git a/test/unit/firestore/firestore.spec.ts b/test/unit/firestore/firestore.spec.ts index 197e8dfde6..52c985360c 100644 --- a/test/unit/firestore/firestore.spec.ts +++ b/test/unit/firestore/firestore.spec.ts @@ -21,7 +21,7 @@ import * as _ from 'lodash'; import { expect } from 'chai'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { ComputeEngineCredential, RefreshTokenCredential } from '../../../src/credential/credential-internal'; diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index efbe059e96..887ccb5642 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -17,8 +17,9 @@ // General import './firebase.spec'; -import './firebase-app.spec'; -import './firebase-namespace.spec'; +import './app/index.spec'; +import './app/firebase-app.spec'; +import './app/firebase-namespace.spec'; // Utilities import './utils/index.spec'; diff --git a/test/unit/instance-id/instance-id-request.spec.ts b/test/unit/instance-id/instance-id-request.spec.ts index dd28f0f8ef..37c7347a0e 100644 --- a/test/unit/instance-id/instance-id-request.spec.ts +++ b/test/unit/instance-id/instance-id-request.spec.ts @@ -26,7 +26,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { HttpClient } from '../../../src/utils/api-request'; import { FirebaseInstanceIdRequestHandler } from '../../../src/instance-id/instance-id-request-internal'; diff --git a/test/unit/instance-id/instance-id.spec.ts b/test/unit/instance-id/instance-id.spec.ts index b68f8b3ac1..a2b2650df8 100644 --- a/test/unit/instance-id/instance-id.spec.ts +++ b/test/unit/instance-id/instance-id.spec.ts @@ -28,7 +28,7 @@ import * as mocks from '../../resources/mocks'; import { InstanceId } from '../../../src/instance-id/instance-id'; import { FirebaseInstanceIdRequestHandler } from '../../../src/instance-id/instance-id-request-internal'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../../../src/utils/error'; chai.should(); diff --git a/test/unit/machine-learning/machine-learning-api-client.spec.ts b/test/unit/machine-learning/machine-learning-api-client.spec.ts index 3399dd0f52..1dc8a0df35 100644 --- a/test/unit/machine-learning/machine-learning-api-client.spec.ts +++ b/test/unit/machine-learning/machine-learning-api-client.spec.ts @@ -24,7 +24,7 @@ import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { FirebaseAppError } from '../../../src/utils/error'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { getSdkVersion } from '../../../src/utils/index'; import { MachineLearningApiClient } from '../../../src/machine-learning/machine-learning-api-client'; import { machineLearning } from '../../../src/machine-learning/index'; diff --git a/test/unit/machine-learning/machine-learning.spec.ts b/test/unit/machine-learning/machine-learning.spec.ts index 96493d298b..9c8314fc03 100644 --- a/test/unit/machine-learning/machine-learning.spec.ts +++ b/test/unit/machine-learning/machine-learning.spec.ts @@ -19,7 +19,7 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; import { MachineLearning, Model } from '../../../src/machine-learning/machine-learning'; import { diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index 82f0973f67..c6c1e69fc4 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -27,7 +27,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { messaging } from '../../../src/messaging/index'; import { Messaging } from '../../../src/messaging/messaging'; import { BLACKLISTED_OPTIONS_KEYS, BLACKLISTED_DATA_PAYLOAD_KEYS } from '../../../src/messaging/messaging-internal'; diff --git a/test/unit/project-management/android-app.spec.ts b/test/unit/project-management/android-app.spec.ts index 5feff560a9..1c2d88032f 100644 --- a/test/unit/project-management/android-app.spec.ts +++ b/test/unit/project-management/android-app.spec.ts @@ -19,7 +19,7 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import * as sinon from 'sinon'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { AndroidApp, ShaCertificate } from '../../../src/project-management/android-app'; import { ProjectManagementRequestHandler diff --git a/test/unit/project-management/ios-app.spec.ts b/test/unit/project-management/ios-app.spec.ts index e0163d893f..53966696d1 100644 --- a/test/unit/project-management/ios-app.spec.ts +++ b/test/unit/project-management/ios-app.spec.ts @@ -19,7 +19,7 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import * as sinon from 'sinon'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { IosApp } from '../../../src/project-management/ios-app'; import { ProjectManagementRequestHandler diff --git a/test/unit/project-management/project-management-api-request.spec.ts b/test/unit/project-management/project-management-api-request.spec.ts index f4088e0a1f..0cbd7929cb 100644 --- a/test/unit/project-management/project-management-api-request.spec.ts +++ b/test/unit/project-management/project-management-api-request.spec.ts @@ -21,7 +21,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as _ from 'lodash'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; diff --git a/test/unit/project-management/project-management.spec.ts b/test/unit/project-management/project-management.spec.ts index f7837389c7..a755a5b283 100644 --- a/test/unit/project-management/project-management.spec.ts +++ b/test/unit/project-management/project-management.spec.ts @@ -19,7 +19,7 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import * as sinon from 'sinon'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { AndroidApp } from '../../../src/project-management/android-app'; import { ProjectManagement } from '../../../src/project-management/project-management'; import { diff --git a/test/unit/remote-config/remote-config-api-client.spec.ts b/test/unit/remote-config/remote-config-api-client.spec.ts index c416715dcb..2d982e8808 100644 --- a/test/unit/remote-config/remote-config-api-client.spec.ts +++ b/test/unit/remote-config/remote-config-api-client.spec.ts @@ -28,7 +28,7 @@ import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { FirebaseAppError } from '../../../src/utils/error'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { deepCopy } from '../../../src/utils/deep-copy'; import { getSdkVersion } from '../../../src/utils/index'; diff --git a/test/unit/remote-config/remote-config.spec.ts b/test/unit/remote-config/remote-config.spec.ts index 6c946f41fa..2b25a5b7bc 100644 --- a/test/unit/remote-config/remote-config.spec.ts +++ b/test/unit/remote-config/remote-config.spec.ts @@ -20,7 +20,7 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; import { RemoteConfig } from '../../../src/remote-config/remote-config'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; import { remoteConfig } from '../../../src/remote-config/index'; import { diff --git a/test/unit/security-rules/security-rules-api-client.spec.ts b/test/unit/security-rules/security-rules-api-client.spec.ts index f0cd1c4185..8b83a46fdf 100644 --- a/test/unit/security-rules/security-rules-api-client.spec.ts +++ b/test/unit/security-rules/security-rules-api-client.spec.ts @@ -25,7 +25,7 @@ import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { FirebaseAppError } from '../../../src/utils/error'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { getSdkVersion } from '../../../src/utils/index'; const expect = chai.expect; diff --git a/test/unit/security-rules/security-rules.spec.ts b/test/unit/security-rules/security-rules.spec.ts index fd81fa7fd2..426d46c6ae 100644 --- a/test/unit/security-rules/security-rules.spec.ts +++ b/test/unit/security-rules/security-rules.spec.ts @@ -20,7 +20,7 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; import { SecurityRules } from '../../../src/security-rules/security-rules'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; import { SecurityRulesApiClient, RulesetContent } from '../../../src/security-rules/security-rules-api-client-internal'; import { FirebaseSecurityRulesError } from '../../../src/security-rules/security-rules-internal'; diff --git a/test/unit/storage/storage.spec.ts b/test/unit/storage/storage.spec.ts index 997d44846e..80cb49ab92 100644 --- a/test/unit/storage/storage.spec.ts +++ b/test/unit/storage/storage.spec.ts @@ -21,7 +21,7 @@ import * as _ from 'lodash'; import { expect } from 'chai'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { Storage } from '../../../src/storage/storage'; describe('Storage', () => { diff --git a/test/unit/utils.ts b/test/unit/utils.ts index 6eb845831a..dee46114e6 100644 --- a/test/unit/utils.ts +++ b/test/unit/utils.ts @@ -20,9 +20,9 @@ import * as sinon from 'sinon'; import * as mocks from '../resources/mocks'; -import { FirebaseNamespace } from '../../src/firebase-namespace'; +import { FirebaseNamespace } from '../../src/app/firebase-namespace'; import { AppOptions } from '../../src/firebase-namespace-api'; -import { FirebaseApp, FirebaseAppInternals, FirebaseAccessToken } from '../../src/firebase-app'; +import { FirebaseApp, FirebaseAppInternals, FirebaseAccessToken } from '../../src/app/firebase-app'; import { HttpError, HttpResponse } from '../../src/utils/api-request'; /** diff --git a/test/unit/utils/api-request.spec.ts b/test/unit/utils/api-request.spec.ts index f997cfc9dd..fd92a9c191 100644 --- a/test/unit/utils/api-request.spec.ts +++ b/test/unit/utils/api-request.spec.ts @@ -26,7 +26,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { ApiSettings, HttpClient, HttpError, AuthorizedHttpClient, ApiCallbackFunction, HttpRequestConfig, HttpResponse, parseHttpResponse, RetryConfig, defaultRetryConfig, diff --git a/test/unit/utils/index.spec.ts b/test/unit/utils/index.spec.ts index e63993fb83..27ab60cb71 100644 --- a/test/unit/utils/index.spec.ts +++ b/test/unit/utils/index.spec.ts @@ -25,7 +25,7 @@ import { toWebSafeBase64, formatString, generateUpdateMask, } from '../../../src/utils/index'; import { isNonEmptyString } from '../../../src/utils/validator'; -import { FirebaseApp } from '../../../src/firebase-app'; +import { FirebaseApp } from '../../../src/app/firebase-app'; import { ComputeEngineCredential } from '../../../src/credential/credential-internal'; import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; diff --git a/tsconfig.json b/tsconfig.json index 67f91761f3..f70690c510 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ //"strictPropertyInitialization": true, "lib": ["es2018"], "outDir": "lib", + "stripInternal": true, "rootDir": "." }, "files": [ From d68c9da5227c72b6dfa0769b2278efca912737b8 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Fri, 15 Jan 2021 15:40:31 -0800 Subject: [PATCH 03/41] feat: Added instanceId() modularized API (#1136) * feat: Added instanceId() modularized API * fix: Cleaned up the tests --- etc/firebase-admin.api.md | 15 ++-- gulpfile.js | 1 + src/app/firebase-app.ts | 17 +++-- src/instance-id/index.ts | 50 ++++--------- .../instance-id-request-internal.ts | 7 +- src/instance-id/instance-id.ts | 20 +++-- src/utils/index.ts | 7 +- test/unit/index.spec.ts | 1 + test/unit/instance-id/index.spec.ts | 75 +++++++++++++++++++ test/unit/instance-id/instance-id.spec.ts | 4 +- 10 files changed, 133 insertions(+), 64 deletions(-) create mode 100644 test/unit/instance-id/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index b2f107e95f..9005e91a07 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -498,15 +498,18 @@ export interface GoogleOAuthAccessToken { export function initializeApp(options?: AppOptions, name?: string): app.App; // @public -export function instanceId(app?: app.App): instanceId.InstanceId; +export class InstanceId { + get app(): App; + deleteInstanceId(instanceId: string): Promise; + } + +// @public +export function instanceId(app?: App): InstanceId; // @public (undocumented) export namespace instanceId { - export interface InstanceId { - // (undocumented) - app: app.App; - deleteInstanceId(instanceId: string): Promise; - } + // (undocumented) + export type InstanceId = InstanceId; } // @public diff --git a/gulpfile.js b/gulpfile.js index 1e1adcbba6..0adae29789 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -88,6 +88,7 @@ gulp.task('compile', function() { 'lib/firebase-namespace-api.d.ts', 'lib/core.d.ts', 'lib/app/*.d.ts', + 'lib/instance-id/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 80f0fb2088..bcd8819f23 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -31,7 +31,7 @@ import { database } from '../database/index'; import { DatabaseService } from '../database/database-internal'; import { Firestore } from '@google-cloud/firestore'; import { FirestoreService } from '../firestore/firestore-internal'; -import { InstanceId } from '../instance-id/instance-id'; +import { InstanceId } from '../instance-id/index'; import { ProjectManagement } from '../project-management/project-management'; import { SecurityRules } from '../security-rules/security-rules'; import { RemoteConfig } from '../remote-config/remote-config'; @@ -231,6 +231,8 @@ export class FirebaseAppInternals { /** * Global context object for a collection of services using a shared authentication state. + * + * @internal */ export class FirebaseApp implements app.App { public INTERNAL: FirebaseAppInternals; @@ -333,10 +335,8 @@ export class FirebaseApp implements app.App { * @return The InstanceId service instance of this app. */ public instanceId(): InstanceId { - return this.ensureService_('iid', () => { - const iidService: typeof InstanceId = require('../instance-id/instance-id').InstanceId; - return new iidService(this); - }); + const fn = require('../instance-id/index').instanceId; + return fn(this); } /** @@ -410,6 +410,13 @@ export class FirebaseApp implements app.App { return deepCopy(this.options_); } + /** + * @internal + */ + public getOrInitService(name: string, init: (app: FirebaseApp) => T): T { + return this.ensureService_(name, () => init(this)); + } + /** * Deletes the FirebaseApp instance. * diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index 09cbfe4022..53b12bffcc 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +import { FirebaseApp } from '../app/firebase-app'; +import { App, getApp } from '../app/index'; +import { InstanceId } from './instance-id'; + +export { InstanceId }; /** * Gets the {@link instanceId.InstanceId `InstanceId`} service for the @@ -46,40 +50,18 @@ import { app } from '../firebase-namespace-api'; * no app is provided or the `InstanceId` service associated with the * provided app. */ -export declare function instanceId(app?: app.App): instanceId.InstanceId; +export function instanceId(app?: App): InstanceId { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('instanceId', (app) => new InstanceId(app)); +} + +import { InstanceId as TInstanceId } from './instance-id'; /* eslint-disable @typescript-eslint/no-namespace */ export namespace instanceId { - /** - * Gets the {@link InstanceId `InstanceId`} service for the - * current app. - * - * @example - * ```javascript - * var instanceId = app.instanceId(); - * // The above is shorthand for: - * // var instanceId = admin.instanceId(app); - * ``` - * - * @return The `InstanceId` service for the - * current app. - */ - export interface InstanceId { - app: app.App; - - /** - * Deletes the specified instance ID and the associated data from Firebase. - * - * Note that Google Analytics for Firebase uses its own form of Instance ID to - * keep track of analytics data. Therefore deleting a Firebase Instance ID does - * not delete Analytics data. See - * [Delete an Instance ID](/support/privacy/manage-iids#delete_an_instance_id) - * for more information. - * - * @param instanceId The instance ID to be deleted. - * - * @return A promise fulfilled when the instance ID is deleted. - */ - deleteInstanceId(instanceId: string): Promise; - } + export type InstanceId = TInstanceId; } diff --git a/src/instance-id/instance-id-request-internal.ts b/src/instance-id/instance-id-request-internal.ts index fbd3ba15ed..1ef9125744 100644 --- a/src/instance-id/instance-id-request-internal.ts +++ b/src/instance-id/instance-id-request-internal.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { App } from '../app/index'; import { FirebaseApp } from '../app/firebase-app'; import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error'; import { @@ -54,12 +55,12 @@ export class FirebaseInstanceIdRequestHandler { private path: string; /** - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param app The app used to fetch access tokens to sign API requests. * * @constructor */ - constructor(private readonly app: FirebaseApp) { - this.httpClient = new AuthorizedHttpClient(app); + constructor(private readonly app: App) { + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } public deleteInstanceId(instanceId: string): Promise { diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index b92662bfd7..1af8bdb151 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -14,14 +14,11 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app/index'; import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error'; import { FirebaseInstanceIdRequestHandler } from './instance-id-request-internal'; -import { instanceId } from './index'; import * as validator from '../utils/validator'; -import InstanceIdInterface = instanceId.InstanceId; - /** * Gets the {@link InstanceId `InstanceId`} service for the * current app. @@ -36,20 +33,21 @@ import InstanceIdInterface = instanceId.InstanceId; * @return The `InstanceId` service for the * current app. */ -export class InstanceId implements InstanceIdInterface { +export class InstanceId { - private app_: FirebaseApp; + private app_: App; private requestHandler: FirebaseInstanceIdRequestHandler; /** - * @param {FirebaseApp} app The app for this InstanceId service. + * @param app The app for this InstanceId service. * @constructor + * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseInstanceIdError( InstanceIdClientErrorCode.INVALID_ARGUMENT, - 'First argument passed to admin.instanceId() must be a valid Firebase app instance.', + 'First argument passed to instanceId() must be a valid Firebase app instance.', ); } @@ -80,9 +78,9 @@ export class InstanceId implements InstanceIdInterface { /** * Returns the app associated with this InstanceId instance. * - * @return {FirebaseApp} The app associated with this InstanceId instance. + * @return The app associated with this InstanceId instance. */ - get app(): FirebaseApp { + get app(): App { return this.app_; } } diff --git a/src/utils/index.ts b/src/utils/index.ts index b8cfa2faf0..834b8cb6cd 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { app as _app } from '../firebase-namespace-api'; +import { App } from '../app/index'; import { ServiceAccountCredential, ComputeEngineCredential } from '../credential/credential-internal'; @@ -23,6 +23,7 @@ import * as validator from './validator'; let sdkVersion: string; +// TODO: Move to firebase-admin/app as an internal member. export function getSdkVersion(): string { if (!sdkVersion) { const { version } = require('../../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires @@ -76,7 +77,7 @@ export function addReadonlyGetter(obj: object, prop: string, value: any): void { * * @return A project ID string or null. */ -export function getExplicitProjectId(app: _app.App): string | null { +export function getExplicitProjectId(app: App): string | null { const options = app.options; if (validator.isNonEmptyString(options.projectId)) { return options.projectId; @@ -105,7 +106,7 @@ export function getExplicitProjectId(app: _app.App): string | null { * * @return A project ID string or null. */ -export function findProjectId(app: _app.App): Promise { +export function findProjectId(app: App): Promise { const projectId = getExplicitProjectId(app); if (projectId) { return Promise.resolve(projectId); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 887ccb5642..a7321f8cb0 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -60,6 +60,7 @@ import './storage/storage.spec'; import './firestore/firestore.spec'; // InstanceId +import './instance-id/index.spec'; import './instance-id/instance-id.spec'; import './instance-id/instance-id-request.spec'; diff --git a/test/unit/instance-id/index.spec.ts b/test/unit/instance-id/index.spec.ts new file mode 100644 index 0000000000..125d0ca6fa --- /dev/null +++ b/test/unit/instance-id/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { instanceId, InstanceId } from '../../../src/instance-id/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('InstanceId', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID for InstanceId. Initialize the SDK ' + + 'with service account credentials or set project ID as an app option. Alternatively set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('instanceId()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return instanceId(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const iid = instanceId(mockCredentialApp); + return iid.deleteInstanceId('iid') + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return instanceId(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const iid1: InstanceId = instanceId(mockApp); + const iid2: InstanceId = instanceId(mockApp); + expect(iid1).to.equal(iid2); + }); + }); +}); diff --git a/test/unit/instance-id/instance-id.spec.ts b/test/unit/instance-id/instance-id.spec.ts index a2b2650df8..90bbacdb8d 100644 --- a/test/unit/instance-id/instance-id.spec.ts +++ b/test/unit/instance-id/instance-id.spec.ts @@ -83,7 +83,7 @@ describe('InstanceId', () => { expect(() => { const iidAny: any = InstanceId; return new iidAny(invalidApp); - }).to.throw('First argument passed to admin.instanceId() must be a valid Firebase app instance.'); + }).to.throw('First argument passed to instanceId() must be a valid Firebase app instance.'); }); }); @@ -91,7 +91,7 @@ describe('InstanceId', () => { expect(() => { const iidAny: any = InstanceId; return new iidAny(); - }).to.throw('First argument passed to admin.instanceId() must be a valid Firebase app instance.'); + }).to.throw('First argument passed to instanceId() must be a valid Firebase app instance.'); }); it('should reject given an invalid credential without project ID', () => { From d754b94910a4d55f9b01f4f77ce3e0c427276450 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 21 Jan 2021 13:20:32 -0800 Subject: [PATCH 04/41] feat: Moved the credential APIs under firebase-admin/app (#1137) * feat: Moved the credntial APIs under firebase-admin/app * fix: Removing reference to credentials namespace --- etc/firebase-admin.api.md | 26 +++++-- src/app/core.ts | 6 +- .../credential-factory.ts} | 24 +++++-- .../credential-internal.ts | 5 +- src/app/credential.ts | 45 ++++++++++++ src/app/firebase-app.ts | 5 +- src/app/firebase-namespace.ts | 4 +- src/app/index.ts | 3 + src/auth/token-generator.ts | 2 +- src/credential/index.ts | 44 +++--------- src/firestore/firestore-internal.ts | 2 +- src/storage/storage.ts | 2 +- src/utils/index.ts | 2 +- test/resources/mocks.ts | 7 +- .../credential-internal.spec.ts} | 8 +-- test/unit/app/firebase-app.spec.ts | 4 +- test/unit/app/firebase-namespace.spec.ts | 41 ++++++++++- test/unit/app/index.spec.ts | 68 ++++++++++++++++++- test/unit/auth/auth.spec.ts | 2 +- test/unit/auth/token-generator.spec.ts | 2 +- test/unit/auth/token-verifier.spec.ts | 2 +- test/unit/firebase.spec.ts | 2 +- test/unit/firestore/firestore.spec.ts | 2 +- test/unit/index.spec.ts | 4 +- test/unit/utils/index.spec.ts | 2 +- 25 files changed, 231 insertions(+), 83 deletions(-) rename src/{credential/credential.ts => app/credential-factory.ts} (70%) rename src/{credential => app}/credential-internal.ts (99%) create mode 100644 src/app/credential.ts rename test/unit/{credential/credential.spec.ts => app/credential-internal.spec.ts} (99%) diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 9005e91a07..03a346aa4a 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -45,9 +45,12 @@ export namespace app { } } +// @public (undocumented) +export function applicationDefault(httpAgent?: Agent): Credential; + // @public export interface AppOptions { - credential?: credential.Credential; + credential?: Credential; databaseAuthVariableOverride?: object | null; databaseURL?: string; httpAgent?: Agent; @@ -396,14 +399,20 @@ export namespace auth { } } +// @public (undocumented) +export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; + +// @public (undocumented) +export interface Credential { + getAccessToken(): Promise; +} + // @public (undocumented) export namespace credential { - export function applicationDefault(httpAgent?: Agent): Credential; - export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; - export interface Credential { - getAccessToken(): Promise; - } - export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; + export type Credential = Credential; + const applicationDefault: typeof applicationDefault; + const cert: typeof cert; + const refreshToken: typeof refreshToken; } // @public @@ -930,6 +939,9 @@ export namespace projectManagement { } } +// @public (undocumented) +export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; + // @public export function remoteConfig(app?: app.App): remoteConfig.RemoteConfig; diff --git a/src/app/core.ts b/src/app/core.ts index a311907727..397ce189a5 100644 --- a/src/app/core.ts +++ b/src/app/core.ts @@ -17,7 +17,7 @@ import { Agent } from 'http'; -import { credential } from '../credential/index'; +import { Credential } from './credential'; /** * Available options to pass to [`initializeApp()`](admin#.initializeApp). @@ -25,13 +25,13 @@ import { credential } from '../credential/index'; export interface AppOptions { /** - * A {@link credential.Credential `Credential`} object used to + * A {@link Credential `Credential`} object used to * authenticate the Admin SDK. * * See [Initialize the SDK](/docs/admin/setup#initialize_the_sdk) for detailed * documentation and code samples. */ - credential?: credential.Credential; + credential?: Credential; /** * The object to use as the [`auth`](/docs/reference/security/database/#auth) diff --git a/src/credential/credential.ts b/src/app/credential-factory.ts similarity index 70% rename from src/credential/credential.ts rename to src/app/credential-factory.ts index a2b4413b73..a0fdcc37e1 100644 --- a/src/credential/credential.ts +++ b/src/app/credential-factory.ts @@ -1,6 +1,6 @@ /*! * @license - * Copyright 2017 Google Inc. + * Copyright 2021 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,31 +15,34 @@ * limitations under the License. */ +import { Agent } from 'http'; + +import { Credential, ServiceAccount } from './credential'; import { ServiceAccountCredential, RefreshTokenCredential, getApplicationDefault } from './credential-internal'; -import { credential } from './index'; -let globalAppDefaultCred: credential.Credential; +let globalAppDefaultCred: Credential | undefined; const globalCertCreds: { [key: string]: ServiceAccountCredential } = {}; const globalRefreshTokenCreds: { [key: string]: RefreshTokenCredential } = {}; -export const applicationDefault: typeof credential.applicationDefault = (httpAgent?) => { +export function applicationDefault(httpAgent?: Agent): Credential { if (typeof globalAppDefaultCred === 'undefined') { globalAppDefaultCred = getApplicationDefault(httpAgent); } return globalAppDefaultCred; } -export const cert: typeof credential.cert = (serviceAccountPathOrObject, httpAgent?) => { +export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential { const stringifiedServiceAccount = JSON.stringify(serviceAccountPathOrObject); if (!(stringifiedServiceAccount in globalCertCreds)) { - globalCertCreds[stringifiedServiceAccount] = new ServiceAccountCredential(serviceAccountPathOrObject, httpAgent); + globalCertCreds[stringifiedServiceAccount] = new ServiceAccountCredential( + serviceAccountPathOrObject, httpAgent); } return globalCertCreds[stringifiedServiceAccount]; } -export const refreshToken: typeof credential.refreshToken = (refreshTokenPathOrObject, httpAgent) => { +export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential { const stringifiedRefreshToken = JSON.stringify(refreshTokenPathOrObject); if (!(stringifiedRefreshToken in globalRefreshTokenCreds)) { globalRefreshTokenCreds[stringifiedRefreshToken] = new RefreshTokenCredential( @@ -47,3 +50,10 @@ export const refreshToken: typeof credential.refreshToken = (refreshTokenPathOrO } return globalRefreshTokenCreds[stringifiedRefreshToken]; } + +/** + * Clears the global ADC cache. Exported for testing. + */ +export function clearGlobalAppDefaultCred(): void { + globalAppDefaultCred = undefined; +} diff --git a/src/credential/credential-internal.ts b/src/app/credential-internal.ts similarity index 99% rename from src/credential/credential-internal.ts rename to src/app/credential-internal.ts index a266a42ceb..28dcf7f6b6 100644 --- a/src/credential/credential-internal.ts +++ b/src/app/credential-internal.ts @@ -1,4 +1,5 @@ /*! + * @license * Copyright 2020 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,13 +20,11 @@ import os = require('os'); import path = require('path'); import { Agent } from 'http'; -import { credential, GoogleOAuthAccessToken } from './index'; +import { Credential, GoogleOAuthAccessToken } from './credential'; import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { HttpClient, HttpRequestConfig, HttpError, HttpResponse } from '../utils/api-request'; import * as util from '../utils/validator'; -import Credential = credential.Credential; - const GOOGLE_TOKEN_AUDIENCE = 'https://accounts.google.com/o/oauth2/token'; const GOOGLE_AUTH_TOKEN_HOST = 'accounts.google.com'; const GOOGLE_AUTH_TOKEN_PATH = '/o/oauth2/token'; diff --git a/src/app/credential.ts b/src/app/credential.ts new file mode 100644 index 0000000000..ef4a020494 --- /dev/null +++ b/src/app/credential.ts @@ -0,0 +1,45 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface ServiceAccount { + projectId?: string; + clientEmail?: string; + privateKey?: string; +} + +/** + * Interface for Google OAuth 2.0 access tokens. + */ +export interface GoogleOAuthAccessToken { + access_token: string; + expires_in: number; +} + +export interface Credential { + /** + * Returns a Google OAuth2 access token object used to authenticate with + * Firebase services. + * + * This object contains the following properties: + * * `access_token` (`string`): The actual Google OAuth2 access token. + * * `expires_in` (`number`): The number of seconds from when the token was + * issued that it expires. + * + * @return A Google OAuth2 access token object. + */ + getAccessToken(): Promise; +} \ No newline at end of file diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index bcd8819f23..5c66ca0c60 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -16,8 +16,8 @@ */ import { AppOptions, app } from '../firebase-namespace-api'; -import { credential, GoogleOAuthAccessToken } from '../credential/index'; -import { getApplicationDefault } from '../credential/credential-internal'; +import { Credential, GoogleOAuthAccessToken } from './credential'; +import { getApplicationDefault } from './credential-internal'; import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; import { FirebaseNamespaceInternals } from './firebase-namespace'; @@ -36,7 +36,6 @@ import { ProjectManagement } from '../project-management/project-management'; import { SecurityRules } from '../security-rules/security-rules'; import { RemoteConfig } from '../remote-config/remote-config'; -import Credential = credential.Credential; import Database = database.Database; /** diff --git a/src/app/firebase-namespace.ts b/src/app/firebase-namespace.ts index a0938fec31..502a864929 100644 --- a/src/app/firebase-namespace.ts +++ b/src/app/firebase-namespace.ts @@ -20,8 +20,8 @@ import fs = require('fs'); import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { AppOptions, app } from '../firebase-namespace-api'; import { FirebaseApp } from './firebase-app'; -import { cert, refreshToken, applicationDefault } from '../credential/credential'; -import { getApplicationDefault } from '../credential/credential-internal'; +import { cert, refreshToken, applicationDefault } from './credential-factory'; +import { getApplicationDefault } from './credential-internal'; import { auth } from '../auth/index'; import { database } from '../database/index'; diff --git a/src/app/index.ts b/src/app/index.ts index 5b4125d76a..9aeef5b363 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -20,4 +20,7 @@ import { getSdkVersion } from '../utils'; export { App, AppOptions } from './core' export { initializeApp, getApp, getApps, deleteApp } from './lifecycle'; +export { Credential, ServiceAccount, GoogleOAuthAccessToken } from './credential'; +export { applicationDefault, cert, refreshToken } from './credential-factory'; + export const SDK_VERSION = getSdkVersion(); diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index f868f08fc8..61db2cc9b2 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -16,7 +16,7 @@ */ import { FirebaseApp } from '../app/firebase-app'; -import { ServiceAccountCredential } from '../credential/credential-internal'; +import { ServiceAccountCredential } from '../app/credential-internal'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { AuthorizedHttpClient, HttpError, HttpRequestConfig, HttpClient } from '../utils/api-request'; diff --git a/src/credential/index.ts b/src/credential/index.ts index dfd27819ed..fd20d0ab9f 100644 --- a/src/credential/index.ts +++ b/src/credential/index.ts @@ -14,21 +14,14 @@ * limitations under the License. */ -import { Agent } from 'http'; +import { + Credential as TCredential, + applicationDefault as applicationDefaultFn, + cert as certFn, + refreshToken as refreshTokenFn, +} from '../app/index'; -export interface ServiceAccount { - projectId?: string; - clientEmail?: string; - privateKey?: string; -} - -/** - * Interface for Google OAuth 2.0 access tokens. - */ -export interface GoogleOAuthAccessToken { - access_token: string; - expires_in: number; -} +export { ServiceAccount, GoogleOAuthAccessToken } from '../app/index'; /* eslint-disable @typescript-eslint/no-namespace */ export namespace credential { @@ -40,20 +33,7 @@ export namespace credential { * use the default implementations provided by * {@link credential `admin.credential`}. */ - export interface Credential { - /** - * Returns a Google OAuth2 access token object used to authenticate with - * Firebase services. - * - * This object contains the following properties: - * * `access_token` (`string`): The actual Google OAuth2 access token. - * * `expires_in` (`number`): The number of seconds from when the token was - * issued that it expires. - * - * @return A Google OAuth2 access token object. - */ - getAccessToken(): Promise; - } + export type Credential = TCredential; /** * Returns a credential created from the @@ -89,7 +69,7 @@ export namespace credential { * @return {!admin.credential.Credential} A credential authenticated via Google * Application Default Credentials that can be used to initialize an app. */ - export declare function applicationDefault(httpAgent?: Agent): Credential; + export const applicationDefault = applicationDefaultFn; /** * Returns a credential created from the provided service account that grants @@ -136,8 +116,7 @@ export namespace credential { * @return A credential authenticated via the * provided service account that can be used to initialize an app. */ - export declare function cert( - serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; + export const cert = certFn; /** * Returns a credential created from the provided refresh token that grants @@ -172,6 +151,5 @@ export namespace credential { * @return A credential authenticated via the * provided service account that can be used to initialize an app. */ - export declare function refreshToken( - refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; + export const refreshToken = refreshTokenFn; } diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts index dc20f1cfdf..ef83053ce4 100644 --- a/src/firestore/firestore-internal.ts +++ b/src/firestore/firestore-internal.ts @@ -17,7 +17,7 @@ import { FirebaseApp } from '../app/firebase-app'; import { FirebaseFirestoreError } from '../utils/error'; -import { ServiceAccountCredential, isApplicationDefault } from '../credential/credential-internal'; +import { ServiceAccountCredential, isApplicationDefault } from '../app/credential-internal'; import { Firestore, Settings } from '@google-cloud/firestore'; import * as validator from '../utils/validator'; diff --git a/src/storage/storage.ts b/src/storage/storage.ts index fb873efe20..94989db24e 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -17,7 +17,7 @@ import { FirebaseApp } from '../app/firebase-app'; import { FirebaseError } from '../utils/error'; -import { ServiceAccountCredential, isApplicationDefault } from '../credential/credential-internal'; +import { ServiceAccountCredential, isApplicationDefault } from '../app/credential-internal'; import { Bucket, Storage as StorageClient } from '@google-cloud/storage'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; diff --git a/src/utils/index.ts b/src/utils/index.ts index 834b8cb6cd..559574fb47 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -18,7 +18,7 @@ import { App } from '../app/index'; import { ServiceAccountCredential, ComputeEngineCredential -} from '../credential/credential-internal'; +} from '../app/credential-internal'; import * as validator from './validator'; let sdkVersion: string; diff --git a/test/resources/mocks.ts b/test/resources/mocks.ts index 1196bba6c8..d63677f96c 100644 --- a/test/resources/mocks.ts +++ b/test/resources/mocks.ts @@ -28,8 +28,7 @@ import * as jwt from 'jsonwebtoken'; import { AppOptions } from '../../src/firebase-namespace-api'; import { FirebaseNamespace } from '../../src/app/firebase-namespace'; import { FirebaseApp } from '../../src/app/firebase-app'; -import { credential as _credential, GoogleOAuthAccessToken } from '../../src/credential/index'; -import { ServiceAccountCredential } from '../../src/credential/credential-internal'; +import { Credential, GoogleOAuthAccessToken, cert } from '../../src/app/index'; const ALGORITHM = 'RS256' as const; const ONE_HOUR_IN_SECONDS = 60 * 60; @@ -51,7 +50,7 @@ export const databaseAuthVariableOverride = { 'some#string': 'some#val' }; export const storageBucket = 'bucketName.appspot.com'; -export const credential = new ServiceAccountCredential(path.resolve(__dirname, './mock.key.json')); +export const credential = cert(path.resolve(__dirname, './mock.key.json')); export const appOptions: AppOptions = { credential, @@ -80,7 +79,7 @@ export const appOptionsAuthDB: AppOptions = { databaseURL, }; -export class MockCredential implements _credential.Credential { +export class MockCredential implements Credential { public getAccessToken(): Promise { return Promise.resolve({ access_token: 'mock-token', // eslint-disable-line @typescript-eslint/camelcase diff --git a/test/unit/credential/credential.spec.ts b/test/unit/app/credential-internal.spec.ts similarity index 99% rename from test/unit/credential/credential.spec.ts rename to test/unit/app/credential-internal.spec.ts index d34b569bda..4ac283947c 100644 --- a/test/unit/credential/credential.spec.ts +++ b/test/unit/app/credential-internal.spec.ts @@ -32,12 +32,12 @@ import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { - GoogleOAuthAccessToken, credential -} from '../../../src/credential/index'; + GoogleOAuthAccessToken, Credential +} from '../../../src/app/index'; import { RefreshTokenCredential, ServiceAccountCredential, ComputeEngineCredential, getApplicationDefault, isApplicationDefault -} from '../../../src/credential/credential-internal'; +} from '../../../src/app/credential-internal'; import { HttpClient } from '../../../src/utils/api-request'; import { Agent } from 'https'; import { FirebaseAppError } from '../../../src/utils/error'; @@ -556,7 +556,7 @@ describe('Credential', () => { }); it('should return false for custom credential', () => { - const c: credential.Credential = { + const c: Credential = { getAccessToken: () => { throw new Error(); }, diff --git a/test/unit/app/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts index 0d2cbb15fa..e3aaba5687 100644 --- a/test/unit/app/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -26,8 +26,8 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { GoogleOAuthAccessToken } from '../../../src/credential/index'; -import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { GoogleOAuthAccessToken } from '../../../src/app/index'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { FirebaseApp, FirebaseAccessToken } from '../../../src/app/firebase-app'; import { FirebaseNamespace, FirebaseNamespaceInternals, FIREBASE_CONFIG_VAR diff --git a/test/unit/app/firebase-namespace.spec.ts b/test/unit/app/firebase-namespace.spec.ts index 1bc0c0b636..a3aeb7b99c 100644 --- a/test/unit/app/firebase-namespace.spec.ts +++ b/test/unit/app/firebase-namespace.spec.ts @@ -17,6 +17,8 @@ 'use strict'; +import path = require('path'); + import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinonChai from 'sinon-chai'; @@ -66,6 +68,8 @@ import { RemoteConfig as RemoteConfigImpl } from '../../../src/remote-config/rem import { SecurityRules as SecurityRulesImpl } from '../../../src/security-rules/security-rules'; import { Storage as StorageImpl } from '../../../src/storage/storage'; +import { clearGlobalAppDefaultCred } from '../../../src/app/credential-factory'; + import App = app.App; import Auth = auth.Auth; import Database = database.Database; @@ -461,7 +465,7 @@ describe('FirebaseNamespace', () => { }); }); - describe('#machine-learning()', () => { + describe('#machineLearning()', () => { it('should throw when called before initializating an app', () => { expect(() => { firebaseNamespace.machineLearning(); @@ -759,4 +763,39 @@ describe('FirebaseNamespace', () => { expect(service1).to.equal(service2); }); }); + + describe('credentials', () => { + it('should create a service account credential from object', () => { + const mockCertificateObject = mocks.certificateObject; + const credential = firebaseNamespace.credential.cert(mockCertificateObject); + expect(credential).to.deep.include({ + projectId: mockCertificateObject.project_id, + clientEmail: mockCertificateObject.client_email, + privateKey: mockCertificateObject.private_key, + implicit: false, + }); + }); + + it('should create a refresh token credential from object', () => { + const mockRefreshToken = mocks.refreshToken; + const credential = firebaseNamespace.credential.refreshToken(mockRefreshToken); + expect(credential).to.deep.include({ + implicit: false, + }); + }); + + it('should create application default credentials from environment', () => { + process.env.GOOGLE_APPLICATION_CREDENTIALS = path.resolve(__dirname, '../../resources/mock.key.json'); + const mockCertificateObject = mocks.certificateObject; + const credential = firebaseNamespace.credential.applicationDefault(); + expect(credential).to.deep.include({ + projectId: mockCertificateObject.project_id, + clientEmail: mockCertificateObject.client_email, + privateKey: mockCertificateObject.private_key, + implicit: true, + }); + }); + + after(clearGlobalAppDefaultCred); + }); }); diff --git a/test/unit/app/index.spec.ts b/test/unit/app/index.spec.ts index d4da3ae4b2..4cf35282f5 100644 --- a/test/unit/app/index.spec.ts +++ b/test/unit/app/index.spec.ts @@ -17,13 +17,19 @@ 'use strict'; +import path = require('path'); + import * as _ from 'lodash'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../../resources/mocks'; import * as sinon from 'sinon'; -import { initializeApp, getApp, getApps, deleteApp, SDK_VERSION } from '../../../src/app/index'; +import { + initializeApp, getApp, getApps, deleteApp, SDK_VERSION, + Credential, applicationDefault, cert, refreshToken, +} from '../../../src/app/index'; +import { clearGlobalAppDefaultCred } from '../../../src/app/credential-factory'; chai.should(); chai.use(chaiAsPromised); @@ -180,4 +186,64 @@ describe('firebase-admin/app', () => { expect(SDK_VERSION).to.equal(version); }); }); + + describe('#cert()', () => { + it('should create a service account credential from object', () => { + const mockCertificateObject = mocks.certificateObject; + const credential: Credential = cert(mockCertificateObject); + expect(credential).to.deep.include({ + projectId: mockCertificateObject.project_id, + clientEmail: mockCertificateObject.client_email, + privateKey: mockCertificateObject.private_key, + implicit: false, + }); + }); + + it('should create a service account credential from file path', () => { + const filePath = path.resolve(__dirname, '../../resources/mock.key.json'); + const mockCertificateObject = mocks.certificateObject; + const credential: Credential = cert(filePath); + expect(credential).to.deep.include({ + projectId: mockCertificateObject.project_id, + clientEmail: mockCertificateObject.client_email, + privateKey: mockCertificateObject.private_key, + implicit: false, + }); + }); + }); + + describe('#refreshToken()', () => { + it('should create a refresh token credential from object', () => { + const mockRefreshToken = mocks.refreshToken; + const credential: Credential = refreshToken(mockRefreshToken); + expect(credential).to.deep.include({ + implicit: false, + }); + }); + }); + + describe('#applicationDefault()', () => { + before(() => { + process.env.GOOGLE_APPLICATION_CREDENTIALS = path.resolve(__dirname, '../../resources/mock.key.json'); + }); + + it('should create application default credentials from environment', () => { + const mockCertificateObject = mocks.certificateObject; + const credential: Credential = applicationDefault(); + expect(credential).to.deep.include({ + projectId: mockCertificateObject.project_id, + clientEmail: mockCertificateObject.client_email, + privateKey: mockCertificateObject.private_key, + implicit: true, + }); + }); + + it('should cache application default credentials globally', () => { + const credential1: Credential = applicationDefault(); + const credential2: Credential = applicationDefault(); + expect(credential1).to.equal(credential2); + }); + + after(clearGlobalAppDefaultCred); + }); }); diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index ac3a680d8d..981624cf69 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -42,7 +42,7 @@ import { } from '../../../src/auth/auth-config'; import { deepCopy } from '../../../src/utils/deep-copy'; import { TenantManager } from '../../../src/auth/tenant-manager'; -import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { HttpClient } from '../../../src/utils/api-request'; import { auth } from '../../../src/auth/index'; diff --git a/test/unit/auth/token-generator.spec.ts b/test/unit/auth/token-generator.spec.ts index 2653b4d36d..963c75f6f5 100644 --- a/test/unit/auth/token-generator.spec.ts +++ b/test/unit/auth/token-generator.spec.ts @@ -29,7 +29,7 @@ import { BLACKLISTED_CLAIMS, FirebaseTokenGenerator, ServiceAccountSigner, IAMSigner, EmulatedSigner } from '../../../src/auth/token-generator'; -import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { AuthorizedHttpClient, HttpClient } from '../../../src/utils/api-request'; import { FirebaseApp } from '../../../src/app/firebase-app'; import * as utils from '../utils'; diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 76e3444c51..9553d4375c 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -31,7 +31,7 @@ import * as mocks from '../../resources/mocks'; import { FirebaseTokenGenerator, ServiceAccountSigner } from '../../../src/auth/token-generator'; import * as verifier from '../../../src/auth/token-verifier'; -import { ServiceAccountCredential } from '../../../src/credential/credential-internal'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { AuthClientErrorCode } from '../../../src/utils/error'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { Algorithm } from 'jsonwebtoken'; diff --git a/test/unit/firebase.spec.ts b/test/unit/firebase.spec.ts index fa758584ec..793da4a2fc 100644 --- a/test/unit/firebase.spec.ts +++ b/test/unit/firebase.spec.ts @@ -31,7 +31,7 @@ import * as firebaseAdmin from '../../src/index'; import { FirebaseApp, FirebaseAppInternals } from '../../src/app/firebase-app'; import { RefreshTokenCredential, ServiceAccountCredential, isApplicationDefault -} from '../../src/credential/credential-internal'; +} from '../../src/app/credential-internal'; chai.should(); chai.use(chaiAsPromised); diff --git a/test/unit/firestore/firestore.spec.ts b/test/unit/firestore/firestore.spec.ts index 52c985360c..f8d6f188a0 100644 --- a/test/unit/firestore/firestore.spec.ts +++ b/test/unit/firestore/firestore.spec.ts @@ -24,7 +24,7 @@ import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { ComputeEngineCredential, RefreshTokenCredential -} from '../../../src/credential/credential-internal'; +} from '../../../src/app/credential-internal'; import { FirestoreService, getFirestoreOptions } from '../../../src/firestore/firestore-internal'; describe('Firestore', () => { diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index a7321f8cb0..3730bdb936 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -17,6 +17,7 @@ // General import './firebase.spec'; +import './app/credential-internal.spec'; import './app/index.spec'; import './app/firebase-app.spec'; import './app/firebase-namespace.spec'; @@ -39,9 +40,6 @@ import './auth/auth-config.spec'; import './auth/tenant.spec'; import './auth/tenant-manager.spec'; -// Credential -import './credential/credential.spec'; - // Database import './database/database.spec'; diff --git a/test/unit/utils/index.spec.ts b/test/unit/utils/index.spec.ts index 27ab60cb71..f85510f33c 100644 --- a/test/unit/utils/index.spec.ts +++ b/test/unit/utils/index.spec.ts @@ -26,7 +26,7 @@ import { } from '../../../src/utils/index'; import { isNonEmptyString } from '../../../src/utils/validator'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { ComputeEngineCredential } from '../../../src/credential/credential-internal'; +import { ComputeEngineCredential } from '../../../src/app/credential-internal'; import { HttpClient } from '../../../src/utils/api-request'; import * as utils from '../utils'; import { FirebaseAppError } from '../../../src/utils/error'; From 8d16ac097d673500bc72886173362bb60f6b9fe2 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Mon, 25 Jan 2021 14:38:03 -0800 Subject: [PATCH 05/41] feat(auth): Modularized firebase-admin/auth API entrypoint (#1140) * feat(auth): Modularized firebase-admin/auth API entrypoint * fix(auth): Rearranged imports/exports alphabetically * fix: Fixed indentation; Using trailing underscore for private members --- etc/firebase-admin.api.md | 891 +++++--- gulpfile.js | 1 + package.json | 2 +- src/app/firebase-app.ts | 6 +- src/auth/action-code-settings-builder.ts | 98 +- src/auth/auth-api-request.ts | 158 +- src/auth/auth-config.ts | 453 ++++- src/auth/auth-namespace.ts | 181 ++ src/auth/auth.ts | 798 +------- src/auth/base-auth.ts | 757 +++++++ src/auth/identifier.ts | 47 +- src/auth/index.ts | 2125 +------------------- src/auth/tenant-manager.ts | 144 +- src/auth/tenant.ts | 69 +- src/auth/token-generator.ts | 5 +- src/auth/token-verifier.ts | 182 +- src/auth/user-import-builder.ts | 240 ++- src/auth/user-record.ts | 60 +- test/unit/auth/auth-api-request.spec.ts | 17 +- test/unit/auth/auth-config.spec.ts | 10 +- test/unit/auth/auth.spec.ts | 20 +- test/unit/auth/tenant-manager.spec.ts | 8 +- test/unit/auth/tenant.spec.ts | 8 +- test/unit/auth/user-import-builder.spec.ts | 8 +- test/unit/auth/user-record.spec.ts | 26 +- 25 files changed, 2949 insertions(+), 3365 deletions(-) create mode 100644 src/auth/auth-namespace.ts create mode 100644 src/auth/base-auth.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 03a346aa4a..0464f9c959 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -9,6 +9,21 @@ import { Bucket } from '@google-cloud/storage'; import * as _firestore from '@google-cloud/firestore'; import * as rtdb from '@firebase/database-types'; +// @public +export interface ActionCodeSettings { + android?: { + packageName: string; + installApp?: boolean; + minimumVersion?: string; + }; + dynamicLinkDomain?: string; + handleCodeInApp?: boolean; + iOS?: { + bundleId: string; + }; + url: string; +} + // @public (undocumented) export interface App { name: string; @@ -63,345 +78,189 @@ export interface AppOptions { export const apps: (app.App | null)[]; // @public -export function auth(app?: app.App): auth.Auth; +export class Auth extends BaseAuth { + get app(): App; + tenantManager(): TenantManager; + } + +// @public +export function auth(app?: App): Auth; // @public (undocumented) export namespace auth { - export interface ActionCodeSettings { - android?: { - packageName: string; - installApp?: boolean; - minimumVersion?: string; - }; - dynamicLinkDomain?: string; - handleCodeInApp?: boolean; - iOS?: { - bundleId: string; - }; - url: string; - } // (undocumented) - export interface Auth extends BaseAuth { - // (undocumented) - app: app.App; - tenantManager(): TenantManager; - } - export type AuthFactorType = 'phone'; - export interface AuthProviderConfig { - displayName?: string; - enabled: boolean; - providerId: string; - } - export interface AuthProviderConfigFilter { - maxResults?: number; - pageToken?: string; - type: 'saml' | 'oidc'; - } - // (undocumented) - export interface BaseAuth { - createCustomToken(uid: string, developerClaims?: object): Promise; - createProviderConfig(config: AuthProviderConfig): Promise; - createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; - createUser(properties: CreateRequest): Promise; - deleteProviderConfig(providerId: string): Promise; - deleteUser(uid: string): Promise; - deleteUsers(uids: string[]): Promise; - generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; - generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; - generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise; - getProviderConfig(providerId: string): Promise; - getUser(uid: string): Promise; - getUserByEmail(email: string): Promise; - getUserByPhoneNumber(phoneNumber: string): Promise; - getUsers(identifiers: UserIdentifier[]): Promise; - importUsers(users: UserImportRecord[], options?: UserImportOptions): Promise; - listProviderConfigs(options: AuthProviderConfigFilter): Promise; - listUsers(maxResults?: number, pageToken?: string): Promise; - revokeRefreshTokens(uid: string): Promise; - setCustomUserClaims(uid: string, customUserClaims: object | null): Promise; - updateProviderConfig(providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise; - updateUser(uid: string, properties: UpdateRequest): Promise; - verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; - verifySessionCookie(sessionCookie: string, checkForRevocation?: boolean): Promise; - } - export interface CreateMultiFactorInfoRequest { - displayName?: string; - factorId: string; - } - export interface CreatePhoneMultiFactorInfoRequest extends CreateMultiFactorInfoRequest { - phoneNumber: string; - } - export interface CreateRequest extends UpdateRequest { - multiFactor?: MultiFactorCreateSettings; - uid?: string; - } - export type CreateTenantRequest = UpdateTenantRequest; - export interface DecodedIdToken { - // (undocumented) - [key: string]: any; - aud: string; - auth_time: number; - email?: string; - email_verified?: boolean; - exp: number; - firebase: { - identities: { - [key: string]: any; - }; - sign_in_provider: string; - sign_in_second_factor?: string; - second_factor_identifier?: string; - tenant?: string; - [key: string]: any; - }; - iat: number; - iss: string; - phone_number?: string; - picture?: string; - sub: string; - uid: string; - } - export interface DeleteUsersResult { - errors: FirebaseArrayIndexError[]; - failureCount: number; - successCount: number; - } - export interface EmailIdentifier { - // (undocumented) - email: string; - } - export interface EmailSignInProviderConfig { - enabled: boolean; - passwordRequired?: boolean; - } - export interface GetUsersResult { - notFound: UserIdentifier[]; - users: UserRecord[]; - } + export type ActionCodeSettings = ActionCodeSettings; // (undocumented) - export type HashAlgorithmType = 'SCRYPT' | 'STANDARD_SCRYPT' | 'HMAC_SHA512' | 'HMAC_SHA256' | 'HMAC_SHA1' | 'HMAC_MD5' | 'MD5' | 'PBKDF_SHA1' | 'BCRYPT' | 'PBKDF2_SHA256' | 'SHA512' | 'SHA256' | 'SHA1'; - export interface ListProviderConfigResults { - pageToken?: string; - providerConfigs: AuthProviderConfig[]; - } - export interface ListTenantsResult { - pageToken?: string; - tenants: Tenant[]; - } - export interface ListUsersResult { - pageToken?: string; - users: UserRecord[]; - } - export interface MultiFactorConfig { - factorIds?: AuthFactorType[]; - state: MultiFactorConfigState; - } - export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; - export interface MultiFactorCreateSettings { - enrolledFactors: CreateMultiFactorInfoRequest[]; - } - export interface MultiFactorInfo { - displayName?: string; - enrollmentTime?: string; - factorId: string; - toJSON(): object; - uid: string; - } - export interface MultiFactorSettings { - enrolledFactors: MultiFactorInfo[]; - toJSON(): object; - } - export interface MultiFactorUpdateSettings { - enrolledFactors: UpdateMultiFactorInfoRequest[] | null; - } - export interface OIDCAuthProviderConfig extends AuthProviderConfig { - clientId: string; - issuer: string; - } - export interface OIDCUpdateAuthProviderRequest { - clientId?: string; - displayName?: string; - enabled?: boolean; - issuer?: string; - } - export interface PhoneIdentifier { - // (undocumented) - phoneNumber: string; - } - export interface PhoneMultiFactorInfo extends MultiFactorInfo { - phoneNumber: string; - } - export interface ProviderIdentifier { - // (undocumented) - providerId: string; - // (undocumented) - providerUid: string; - } - export interface SAMLAuthProviderConfig extends AuthProviderConfig { - callbackURL?: string; - idpEntityId: string; - rpEntityId: string; - ssoURL: string; - x509Certificates: string[]; - } - export interface SAMLUpdateAuthProviderRequest { - callbackURL?: string; - displayName?: string; - enabled?: boolean; - idpEntityId?: string; - rpEntityId?: string; - ssoURL?: string; - x509Certificates?: string[]; - } - export interface SessionCookieOptions { - expiresIn: number; - } - export interface Tenant { - displayName?: string; - emailSignInConfig?: { - enabled: boolean; - passwordRequired?: boolean; - }; - multiFactorConfig?: MultiFactorConfig; - tenantId: string; - testPhoneNumbers?: { - [phoneNumber: string]: string; - }; - toJSON(): object; - } - export interface TenantAwareAuth extends BaseAuth { - tenantId: string; - } - export interface TenantManager { - // (undocumented) - authForTenant(tenantId: string): TenantAwareAuth; - createTenant(tenantOptions: CreateTenantRequest): Promise; - deleteTenant(tenantId: string): Promise; - getTenant(tenantId: string): Promise; - listTenants(maxResults?: number, pageToken?: string): Promise; - updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise; - } - export interface UidIdentifier { - // (undocumented) - uid: string; - } + export type Auth = Auth; // (undocumented) - export type UpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; - export interface UpdateMultiFactorInfoRequest { - displayName?: string; - enrollmentTime?: string; - factorId: string; - uid?: string; - } - export interface UpdatePhoneMultiFactorInfoRequest extends UpdateMultiFactorInfoRequest { - phoneNumber: string; - } - export interface UpdateRequest { - disabled?: boolean; - displayName?: string | null; - email?: string; - emailVerified?: boolean; - multiFactor?: MultiFactorUpdateSettings; - password?: string; - phoneNumber?: string | null; - photoURL?: string | null; - } - export interface UpdateTenantRequest { - displayName?: string; - emailSignInConfig?: EmailSignInProviderConfig; - multiFactorConfig?: MultiFactorConfig; - testPhoneNumbers?: { - [phoneNumber: string]: string; - } | null; - } - export type UserIdentifier = UidIdentifier | EmailIdentifier | PhoneIdentifier | ProviderIdentifier; - export interface UserImportOptions { - hash: { - algorithm: HashAlgorithmType; - key?: Buffer; - saltSeparator?: Buffer; - rounds?: number; - memoryCost?: number; - parallelization?: number; - blockSize?: number; - derivedKeyLength?: number; - }; - } - export interface UserImportRecord { - customClaims?: { - [key: string]: any; - }; - disabled?: boolean; - displayName?: string; - email?: string; - emailVerified?: boolean; - metadata?: UserMetadataRequest; - multiFactor?: MultiFactorUpdateSettings; - passwordHash?: Buffer; - passwordSalt?: Buffer; - phoneNumber?: string; - photoURL?: string; - providerData?: UserProviderRequest[]; - tenantId?: string; - uid: string; - } - export interface UserImportResult { - errors: FirebaseArrayIndexError[]; - failureCount: number; - successCount: number; - } - export interface UserInfo { - displayName: string; - email: string; - phoneNumber: string; - photoURL: string; - providerId: string; - toJSON(): object; - uid: string; - } - export interface UserMetadata { - creationTime: string; - lastRefreshTime?: string | null; - lastSignInTime: string; - toJSON(): object; - } - export interface UserMetadataRequest { - creationTime?: string; - lastSignInTime?: string; - } - export interface UserProviderRequest { - displayName?: string; - email?: string; - phoneNumber?: string; - photoURL?: string; - providerId: string; - uid: string; - } - export interface UserRecord { - customClaims?: { - [key: string]: any; - }; - disabled: boolean; - displayName?: string; - email?: string; - emailVerified: boolean; - metadata: UserMetadata; - multiFactor?: MultiFactorSettings; - passwordHash?: string; - passwordSalt?: string; - phoneNumber?: string; - photoURL?: string; - providerData: UserInfo[]; - tenantId?: string | null; - toJSON(): object; - tokensValidAfterTime?: string; - uid: string; - } + export type AuthFactorType = AuthFactorType; + // (undocumented) + export type AuthProviderConfig = AuthProviderConfig; + // (undocumented) + export type AuthProviderConfigFilter = AuthProviderConfigFilter; + // (undocumented) + export type BaseAuth = BaseAuth; + // (undocumented) + export type CreateMultiFactorInfoRequest = CreateMultiFactorInfoRequest; + // (undocumented) + export type CreatePhoneMultiFactorInfoRequest = CreatePhoneMultiFactorInfoRequest; + // (undocumented) + export type CreateRequest = CreateRequest; + // (undocumented) + export type CreateTenantRequest = CreateTenantRequest; + // (undocumented) + export type DecodedIdToken = DecodedIdToken; + // (undocumented) + export type DeleteUsersResult = DeleteUsersResult; + // (undocumented) + export type EmailIdentifier = EmailIdentifier; + // (undocumented) + export type EmailSignInProviderConfig = EmailSignInProviderConfig; + // (undocumented) + export type GetUsersResult = GetUsersResult; + // (undocumented) + export type HashAlgorithmType = HashAlgorithmType; + // (undocumented) + export type ListProviderConfigResults = ListProviderConfigResults; + // (undocumented) + export type ListTenantsResult = ListTenantsResult; + // (undocumented) + export type ListUsersResult = ListUsersResult; + // (undocumented) + export type MultiFactorConfig = MultiFactorConfig; + // (undocumented) + export type MultiFactorConfigState = MultiFactorConfigState; + // (undocumented) + export type MultiFactorCreateSettings = MultiFactorCreateSettings; + // (undocumented) + export type MultiFactorInfo = MultiFactorInfo; + // (undocumented) + export type MultiFactorSettings = MultiFactorSettings; + // (undocumented) + export type MultiFactorUpdateSettings = MultiFactorUpdateSettings; + // (undocumented) + export type OIDCAuthProviderConfig = OIDCAuthProviderConfig; + // (undocumented) + export type OIDCUpdateAuthProviderRequest = OIDCUpdateAuthProviderRequest; + // (undocumented) + export type PhoneIdentifier = PhoneIdentifier; + // (undocumented) + export type PhoneMultiFactorInfo = PhoneMultiFactorInfo; + // (undocumented) + export type ProviderIdentifier = ProviderIdentifier; + // (undocumented) + export type SAMLAuthProviderConfig = SAMLAuthProviderConfig; + // (undocumented) + export type SAMLUpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest; + // (undocumented) + export type SessionCookieOptions = SessionCookieOptions; + // (undocumented) + export type Tenant = Tenant; + // (undocumented) + export type TenantAwareAuth = TenantAwareAuth; + // (undocumented) + export type TenantManager = TenantManager; + // (undocumented) + export type UidIdentifier = UidIdentifier; + // (undocumented) + export type UpdateAuthProviderRequest = UpdateAuthProviderRequest; + // (undocumented) + export type UpdateMultiFactorInfoRequest = UpdateMultiFactorInfoRequest; + // (undocumented) + export type UpdatePhoneMultiFactorInfoRequest = UpdatePhoneMultiFactorInfoRequest; + // (undocumented) + export type UpdateRequest = UpdateRequest; + // (undocumented) + export type UpdateTenantRequest = UpdateTenantRequest; + // (undocumented) + export type UserIdentifier = UserIdentifier; + // (undocumented) + export type UserImportOptions = UserImportOptions; + // (undocumented) + export type UserImportRecord = UserImportRecord; + // (undocumented) + export type UserImportResult = UserImportResult; + // (undocumented) + export type UserInfo = UserInfo; + // (undocumented) + export type UserMetadata = UserMetadata; + // (undocumented) + export type UserMetadataRequest = UserMetadataRequest; + // (undocumented) + export type UserProviderRequest = UserProviderRequest; + // (undocumented) + export type UserRecord = UserRecord; +} + +// @public +export type AuthFactorType = 'phone'; + +// @public +export interface AuthProviderConfig { + displayName?: string; + enabled: boolean; + providerId: string; +} + +// @public +export interface AuthProviderConfigFilter { + maxResults?: number; + pageToken?: string; + type: 'saml' | 'oidc'; +} + +// @public +export class BaseAuth { + createCustomToken(uid: string, developerClaims?: object): Promise; + createProviderConfig(config: AuthProviderConfig): Promise; + createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; + createUser(properties: CreateRequest): Promise; + deleteProviderConfig(providerId: string): Promise; + deleteUser(uid: string): Promise; + // (undocumented) + deleteUsers(uids: string[]): Promise; + generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; + generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; + generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise; + getProviderConfig(providerId: string): Promise; + getUser(uid: string): Promise; + getUserByEmail(email: string): Promise; + getUserByPhoneNumber(phoneNumber: string): Promise; + getUsers(identifiers: UserIdentifier[]): Promise; + importUsers(users: UserImportRecord[], options?: UserImportOptions): Promise; + listProviderConfigs(options: AuthProviderConfigFilter): Promise; + listUsers(maxResults?: number, pageToken?: string): Promise; + revokeRefreshTokens(uid: string): Promise; + setCustomUserClaims(uid: string, customUserClaims: object | null): Promise; + updateProviderConfig(providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise; + updateUser(uid: string, properties: UpdateRequest): Promise; + verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; + verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; } // @public (undocumented) export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; +// @public +export interface CreateMultiFactorInfoRequest { + displayName?: string; + factorId: string; +} + +// @public +export interface CreatePhoneMultiFactorInfoRequest extends CreateMultiFactorInfoRequest { + phoneNumber: string; +} + +// @public +export interface CreateRequest extends UpdateRequest { + multiFactor?: MultiFactorCreateSettings; + uid?: string; +} + +// @public +export type CreateTenantRequest = UpdateTenantRequest; + // @public (undocumented) export interface Credential { getAccessToken(): Promise; @@ -436,9 +295,55 @@ export namespace database { const ServerValue: rtdb.ServerValue; } +// @public +export interface DecodedIdToken { + // (undocumented) + [key: string]: any; + aud: string; + auth_time: number; + email?: string; + email_verified?: boolean; + exp: number; + firebase: { + identities: { + [key: string]: any; + }; + sign_in_provider: string; + sign_in_second_factor?: string; + second_factor_identifier?: string; + tenant?: string; + [key: string]: any; + }; + iat: number; + iss: string; + phone_number?: string; + picture?: string; + sub: string; + uid: string; +} + // @public (undocumented) export function deleteApp(app: App): Promise; +// @public +export interface DeleteUsersResult { + errors: FirebaseArrayIndexError[]; + failureCount: number; + successCount: number; +} + +// @public +export interface EmailIdentifier { + // (undocumented) + email: string; +} + +// @public +export interface EmailSignInProviderConfig { + enabled: boolean; + passwordRequired?: boolean; +} + // @public export interface FirebaseArrayIndexError { error: FirebaseError; @@ -495,6 +400,12 @@ export function getApp(name?: string): App; // @public (undocumented) export function getApps(): App[]; +// @public +export interface GetUsersResult { + notFound: UserIdentifier[]; + users: UserRecord[]; +} + // @public export interface GoogleOAuthAccessToken { // (undocumented) @@ -503,6 +414,9 @@ export interface GoogleOAuthAccessToken { expires_in: number; } +// @public (undocumented) +export type HashAlgorithmType = 'SCRYPT' | 'STANDARD_SCRYPT' | 'HMAC_SHA512' | 'HMAC_SHA256' | 'HMAC_SHA1' | 'HMAC_MD5' | 'MD5' | 'PBKDF_SHA1' | 'BCRYPT' | 'PBKDF2_SHA256' | 'SHA512' | 'SHA256' | 'SHA1'; + // @public (undocumented) export function initializeApp(options?: AppOptions, name?: string): app.App; @@ -521,6 +435,24 @@ export namespace instanceId { export type InstanceId = InstanceId; } +// @public +export interface ListProviderConfigResults { + pageToken?: string; + providerConfigs: AuthProviderConfig[]; +} + +// @public +export interface ListTenantsResult { + pageToken?: string; + tenants: Tenant[]; +} + +// @public +export interface ListUsersResult { + pageToken?: string; + users: UserRecord[]; +} + // @public export function machineLearning(app?: app.App): machineLearning.MachineLearning; @@ -875,6 +807,72 @@ export namespace messaging { {}; } +// @public (undocumented) +export interface MultiFactorConfig { + factorIds?: AuthFactorType[]; + state: MultiFactorConfigState; +} + +// @public +export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; + +// @public +export interface MultiFactorCreateSettings { + enrolledFactors: CreateMultiFactorInfoRequest[]; +} + +// @public +export abstract class MultiFactorInfo { + // (undocumented) + readonly displayName?: string; + // (undocumented) + readonly enrollmentTime?: string; + // (undocumented) + readonly factorId: string; + toJSON(): any; + // (undocumented) + readonly uid: string; +} + +// @public +export class MultiFactorSettings { + // (undocumented) + enrolledFactors: MultiFactorInfo[]; + toJSON(): any; +} + +// @public +export interface MultiFactorUpdateSettings { + enrolledFactors: UpdateMultiFactorInfoRequest[] | null; +} + +// @public +export interface OIDCAuthProviderConfig extends AuthProviderConfig { + clientId: string; + issuer: string; +} + +// @public +export interface OIDCUpdateAuthProviderRequest { + clientId?: string; + displayName?: string; + enabled?: boolean; + issuer?: string; +} + +// @public +export interface PhoneIdentifier { + // (undocumented) + phoneNumber: string; +} + +// @public +export class PhoneMultiFactorInfo extends MultiFactorInfo { + // (undocumented) + readonly phoneNumber: string; + toJSON(): any; +} + // @public export function projectManagement(app?: app.App): projectManagement.ProjectManagement; @@ -939,6 +937,14 @@ export namespace projectManagement { } } +// @public +export interface ProviderIdentifier { + // (undocumented) + providerId: string; + // (undocumented) + providerUid: string; +} + // @public (undocumented) export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; @@ -1025,6 +1031,26 @@ export namespace remoteConfig { } } +// @public +export interface SAMLAuthProviderConfig extends AuthProviderConfig { + callbackURL?: string; + idpEntityId: string; + rpEntityId: string; + ssoURL: string; + x509Certificates: string[]; +} + +// @public +export interface SAMLUpdateAuthProviderRequest { + callbackURL?: string; + displayName?: string; + enabled?: boolean; + idpEntityId?: string; + rpEntityId?: string; + ssoURL?: string; + x509Certificates?: string[]; +} + // @public (undocumented) export const SDK_VERSION: string; @@ -1078,6 +1104,11 @@ export interface ServiceAccount { projectId?: string; } +// @public +export interface SessionCookieOptions { + expiresIn: number; +} + // @public export function storage(app?: app.App): storage.Storage; @@ -1090,6 +1121,210 @@ export namespace storage { } } +// @public +export class Tenant { + // (undocumented) + readonly displayName?: string; + // (undocumented) + get emailSignInConfig(): EmailSignInProviderConfig | undefined; + // (undocumented) + get multiFactorConfig(): MultiFactorConfig | undefined; + // (undocumented) + readonly tenantId: string; + // (undocumented) + readonly testPhoneNumbers?: { + [phoneNumber: string]: string; + }; + toJSON(): object; + } + +// @public +export class TenantAwareAuth extends BaseAuth { + createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; + // (undocumented) + readonly tenantId: string; + verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; + verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; +} + +// @public +export class TenantManager { + authForTenant(tenantId: string): TenantAwareAuth; + createTenant(tenantOptions: CreateTenantRequest): Promise; + deleteTenant(tenantId: string): Promise; + getTenant(tenantId: string): Promise; + listTenants(maxResults?: number, pageToken?: string): Promise; + updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise; +} + +// @public +export interface UidIdentifier { + // (undocumented) + uid: string; +} + +// @public (undocumented) +export type UpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; + +// @public +export interface UpdateMultiFactorInfoRequest { + displayName?: string; + enrollmentTime?: string; + factorId: string; + uid?: string; +} + +// @public +export interface UpdatePhoneMultiFactorInfoRequest extends UpdateMultiFactorInfoRequest { + phoneNumber: string; +} + +// @public +export interface UpdateRequest { + disabled?: boolean; + displayName?: string | null; + email?: string; + emailVerified?: boolean; + multiFactor?: MultiFactorUpdateSettings; + password?: string; + phoneNumber?: string | null; + photoURL?: string | null; +} + +// @public +export interface UpdateTenantRequest { + displayName?: string; + emailSignInConfig?: EmailSignInProviderConfig; + multiFactorConfig?: MultiFactorConfig; + testPhoneNumbers?: { + [phoneNumber: string]: string; + } | null; +} + +// @public +export type UserIdentifier = UidIdentifier | EmailIdentifier | PhoneIdentifier | ProviderIdentifier; + +// @public +export interface UserImportOptions { + hash: { + algorithm: HashAlgorithmType; + key?: Buffer; + saltSeparator?: Buffer; + rounds?: number; + memoryCost?: number; + parallelization?: number; + blockSize?: number; + derivedKeyLength?: number; + }; +} + +// @public +export interface UserImportRecord { + customClaims?: { + [key: string]: any; + }; + disabled?: boolean; + displayName?: string; + email?: string; + emailVerified?: boolean; + metadata?: UserMetadataRequest; + multiFactor?: MultiFactorUpdateSettings; + passwordHash?: Buffer; + passwordSalt?: Buffer; + phoneNumber?: string; + photoURL?: string; + providerData?: UserProviderRequest[]; + tenantId?: string; + uid: string; +} + +// @public +export interface UserImportResult { + errors: FirebaseArrayIndexError[]; + failureCount: number; + successCount: number; +} + +// @public +export class UserInfo { + // (undocumented) + readonly displayName: string; + // (undocumented) + readonly email: string; + // (undocumented) + readonly phoneNumber: string; + // (undocumented) + readonly photoURL: string; + // (undocumented) + readonly providerId: string; + toJSON(): object; + // (undocumented) + readonly uid: string; +} + +// @public +export class UserMetadata { + // (undocumented) + readonly creationTime: string; + readonly lastRefreshTime: string | null; + // (undocumented) + readonly lastSignInTime: string; + toJSON(): object; +} + +// @public +export interface UserMetadataRequest { + creationTime?: string; + lastSignInTime?: string; +} + +// @public +export interface UserProviderRequest { + displayName?: string; + email?: string; + phoneNumber?: string; + photoURL?: string; + providerId: string; + uid: string; +} + +// @public +export class UserRecord { + // (undocumented) + readonly customClaims: { + [key: string]: any; + }; + // (undocumented) + readonly disabled: boolean; + // (undocumented) + readonly displayName: string; + // (undocumented) + readonly email: string; + // (undocumented) + readonly emailVerified: boolean; + // (undocumented) + readonly metadata: UserMetadata; + // (undocumented) + readonly multiFactor?: MultiFactorSettings; + // (undocumented) + readonly passwordHash?: string; + // (undocumented) + readonly passwordSalt?: string; + // (undocumented) + readonly phoneNumber: string; + // (undocumented) + readonly photoURL: string; + // (undocumented) + readonly providerData: UserInfo[]; + // (undocumented) + readonly tenantId?: string | null; + toJSON(): object; + // (undocumented) + readonly tokensValidAfterTime?: string; + // (undocumented) + readonly uid: string; +} + // (No @packageDocumentation comment for this package) diff --git a/gulpfile.js b/gulpfile.js index 0adae29789..9698d11431 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -88,6 +88,7 @@ gulp.task('compile', function() { 'lib/firebase-namespace-api.d.ts', 'lib/core.d.ts', 'lib/app/*.d.ts', + 'lib/auth/*.d.ts', 'lib/instance-id/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/package.json b/package.json index 37620aa245..21b22b48a4 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "lint:test": "eslint test/ --ext .ts", "apidocs": "node docgen/generate-docs.js --api node", "api-extractor": "api-extractor run", - "api-extractor:local": "api-extractor run --local" + "api-extractor:local": "npm run build && api-extractor run --local" }, "nyc": { "extension": [ diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 5c66ca0c60..40177fe793 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -277,10 +277,8 @@ export class FirebaseApp implements app.App { * @return The Auth service instance of this app. */ public auth(): Auth { - return this.ensureService_('auth', () => { - const authService: typeof Auth = require('../auth/auth').Auth; - return new authService(this); - }); + const fn = require('../auth/index').auth; + return fn(this); } /** diff --git a/src/auth/action-code-settings-builder.ts b/src/auth/action-code-settings-builder.ts index 14c212b6fe..ec63e4ea63 100644 --- a/src/auth/action-code-settings-builder.ts +++ b/src/auth/action-code-settings-builder.ts @@ -16,9 +16,87 @@ import * as validator from '../utils/validator'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; -import { auth } from './index'; -import ActionCodeSettings = auth.ActionCodeSettings; +/** + * This is the interface that defines the required continue/state URL with + * optional Android and iOS bundle identifiers. + */ +export interface ActionCodeSettings { + + /** + * Defines the link continue/state URL, which has different meanings in + * different contexts: + *
    + *
  • When the link is handled in the web action widgets, this is the deep + * link in the `continueUrl` query parameter.
  • + *
  • When the link is handled in the app directly, this is the `continueUrl` + * query parameter in the deep link of the Dynamic Link.
  • + *
+ */ + url: string; + + /** + * Whether to open the link via a mobile app or a browser. + * The default is false. When set to true, the action code link is sent + * as a Universal Link or Android App Link and is opened by the app if + * installed. In the false case, the code is sent to the web widget first + * and then redirects to the app if installed. + */ + handleCodeInApp?: boolean; + + /** + * Defines the iOS bundle ID. This will try to open the link in an iOS app if it + * is installed. + */ + iOS?: { + + /** + * Defines the required iOS bundle ID of the app where the link should be + * handled if the application is already installed on the device. + */ + bundleId: string; + }; + + /** + * Defines the Android package name. This will try to open the link in an + * android app if it is installed. If `installApp` is passed, it specifies + * whether to install the Android app if the device supports it and the app is + * not already installed. If this field is provided without a `packageName`, an + * error is thrown explaining that the `packageName` must be provided in + * conjunction with this field. If `minimumVersion` is specified, and an older + * version of the app is installed, the user is taken to the Play Store to + * upgrade the app. + */ + android?: { + + /** + * Defines the required Android package name of the app where the link should be + * handled if the Android app is installed. + */ + packageName: string; + + /** + * Whether to install the Android app if the device supports it and the app is + * not already installed. + */ + installApp?: boolean; + + /** + * The Android minimum version if available. If the installed app is an older + * version, the user is taken to the GOogle Play Store to upgrade the app. + */ + minimumVersion?: string; + }; + + /** + * Defines the dynamic link domain to use for the current link if it is to be + * opened using Firebase Dynamic Links, as multiple dynamic link domains can be + * configured per project. This field provides the ability to explicitly choose + * configured per project. This fields provides the ability explicitly choose + * one. If none is provided, the oldest domain is used by default. + */ + dynamicLinkDomain?: string; +} /** Defines the email action code server request. */ interface EmailActionCodeRequest { @@ -34,6 +112,8 @@ interface EmailActionCodeRequest { /** * Defines the ActionCodeSettings builder class used to convert the * ActionCodeSettings object to its corresponding server request. + * + * @internal */ export class ActionCodeSettingsBuilder { private continueUrl?: string; @@ -70,7 +150,7 @@ export class ActionCodeSettingsBuilder { this.continueUrl = actionCodeSettings.url; if (typeof actionCodeSettings.handleCodeInApp !== 'undefined' && - !validator.isBoolean(actionCodeSettings.handleCodeInApp)) { + !validator.isBoolean(actionCodeSettings.handleCodeInApp)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.handleCodeInApp" must be a boolean.', @@ -79,14 +159,14 @@ export class ActionCodeSettingsBuilder { this.canHandleCodeInApp = actionCodeSettings.handleCodeInApp || false; if (typeof actionCodeSettings.dynamicLinkDomain !== 'undefined' && - !validator.isNonEmptyString(actionCodeSettings.dynamicLinkDomain)) { + !validator.isNonEmptyString(actionCodeSettings.dynamicLinkDomain)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_DYNAMIC_LINK_DOMAIN, ); } this.dynamicLinkDomain = actionCodeSettings.dynamicLinkDomain; - if (typeof actionCodeSettings.iOS !== 'undefined') { + if (typeof actionCodeSettings.iOS !== 'undefined') { if (!validator.isNonNullObject(actionCodeSettings.iOS)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -105,7 +185,7 @@ export class ActionCodeSettingsBuilder { this.ibi = actionCodeSettings.iOS.bundleId; } - if (typeof actionCodeSettings.android !== 'undefined') { + if (typeof actionCodeSettings.android !== 'undefined') { if (!validator.isNonNullObject(actionCodeSettings.android)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -121,13 +201,13 @@ export class ActionCodeSettingsBuilder { '"ActionCodeSettings.android.packageName" must be a valid non-empty string.', ); } else if (typeof actionCodeSettings.android.minimumVersion !== 'undefined' && - !validator.isNonEmptyString(actionCodeSettings.android.minimumVersion)) { + !validator.isNonEmptyString(actionCodeSettings.android.minimumVersion)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.android.minimumVersion" must be a valid non-empty string.', ); } else if (typeof actionCodeSettings.android.installApp !== 'undefined' && - !validator.isBoolean(actionCodeSettings.android.installApp)) { + !validator.isBoolean(actionCodeSettings.android.installApp)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, '"ActionCodeSettings.android.installApp" must be a valid boolean.', @@ -146,7 +226,7 @@ export class ActionCodeSettingsBuilder { * @return {EmailActionCodeRequest} The constructed EmailActionCodeRequest request. */ public buildRequest(): EmailActionCodeRequest { - const request: {[key: string]: any} = { + const request: { [key: string]: any } = { continueUrl: this.continueUrl, canHandleCodeInApp: this.canHandleCodeInApp, dynamicLinkDomain: this.dynamicLinkDomain, diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 7552f5dfb4..8de0f1f29e 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -17,44 +17,31 @@ import * as validator from '../utils/validator'; -import { deepCopy, deepExtend } from '../utils/deep-copy'; -import { - isUidIdentifier, isEmailIdentifier, isPhoneIdentifier, isProviderIdentifier -} from './identifier'; +import { App } from '../app/index'; import { FirebaseApp } from '../app/firebase-app'; +import { deepCopy, deepExtend } from '../utils/deep-copy'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, } from '../utils/api-request'; +import * as utils from '../utils/index'; + import { + UserImportOptions, UserImportRecord, UserImportResult, UserImportBuilder, AuthFactorInfo, convertMultiFactorInfoToServerFormat, } from './user-import-builder'; -import * as utils from '../utils/index'; -import { ActionCodeSettingsBuilder } from './action-code-settings-builder'; +import { ActionCodeSettings, ActionCodeSettingsBuilder } from './action-code-settings-builder'; +import { Tenant, TenantServerResponse, CreateTenantRequest, UpdateTenantRequest } from './tenant'; +import { + isUidIdentifier, isEmailIdentifier, isPhoneIdentifier, isProviderIdentifier, + UserIdentifier, UidIdentifier, EmailIdentifier,PhoneIdentifier, ProviderIdentifier, +} from './identifier'; import { SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, - OIDCConfigServerRequest, SAMLConfigServerRequest, + OIDCConfigServerRequest, SAMLConfigServerRequest, CreateRequest, UpdateRequest, + OIDCAuthProviderConfig, SAMLAuthProviderConfig, OIDCUpdateAuthProviderRequest, + SAMLUpdateAuthProviderRequest } from './auth-config'; -import { Tenant, TenantServerResponse } from './tenant'; -import { auth } from './index'; - -import CreateRequest = auth.CreateRequest; -import UpdateRequest = auth.UpdateRequest; -import UserIdentifier = auth.UserIdentifier; -import UidIdentifier = auth.UidIdentifier; -import EmailIdentifier = auth.EmailIdentifier; -import PhoneIdentifier = auth.PhoneIdentifier; -import ProviderIdentifier = auth.ProviderIdentifier; -import UserImportOptions = auth.UserImportOptions; -import UserImportRecord = auth.UserImportRecord; -import UserImportResult = auth.UserImportResult; -import ActionCodeSettings = auth.ActionCodeSettings; -import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; -import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; -import OIDCUpdateAuthProviderRequest = auth.OIDCUpdateAuthProviderRequest; -import SAMLUpdateAuthProviderRequest = auth.SAMLUpdateAuthProviderRequest; -import CreateTenantRequest = auth.CreateTenantRequest; -import UpdateTenantRequest = auth.UpdateTenantRequest; /** Firebase Auth request header. */ const FIREBASE_AUTH_HEADER = { @@ -143,7 +130,7 @@ class AuthResourceUrlBuilder { * @param {string} version The endpoint API version. * @constructor */ - constructor(protected app: FirebaseApp, protected version: string = 'v1') { + constructor(protected app: App, protected version: string = 'v1') { if (useEmulator()) { this.urlFormat = utils.formatString(FIREBASE_AUTH_EMULATOR_BASE_URL_FORMAT, { host: emulatorHost() @@ -208,7 +195,7 @@ class TenantAwareAuthResourceUrlBuilder extends AuthResourceUrlBuilder { * @param {string} tenantId The tenant ID. * @constructor */ - constructor(protected app: FirebaseApp, protected version: string, protected tenantId: string) { + constructor(protected app: App, protected version: string, protected tenantId: string) { super(app, version); if (useEmulator()) { this.urlFormat = utils.formatString(FIREBASE_AUTH_EMULATOR_TENANT_URL_FORMAT, { @@ -571,7 +558,11 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat } -/** Instantiates the createSessionCookie endpoint settings. */ +/** + * Instantiates the createSessionCookie endpoint settings. + * + * @internal + */ export const FIREBASE_AUTH_CREATE_SESSION_COOKIE = new ApiSettings(':createSessionCookie', 'POST') // Set request validator. @@ -596,11 +587,19 @@ export const FIREBASE_AUTH_CREATE_SESSION_COOKIE = }); -/** Instantiates the uploadAccount endpoint settings. */ +/** + * Instantiates the uploadAccount endpoint settings. + * + * @internal + */ export const FIREBASE_AUTH_UPLOAD_ACCOUNT = new ApiSettings('/accounts:batchCreate', 'POST'); -/** Instantiates the downloadAccount endpoint settings. */ +/** + * Instantiates the downloadAccount endpoint settings. + * + * @internal + */ export const FIREBASE_AUTH_DOWNLOAD_ACCOUNT = new ApiSettings('/accounts:batchGet', 'GET') // Set request validator. .setRequestValidator((request: any) => { @@ -631,7 +630,11 @@ interface GetAccountInfoRequest { }>; } -/** Instantiates the getAccountInfo endpoint settings. */ +/** + * Instantiates the getAccountInfo endpoint settings. + * + * @internal + */ export const FIREBASE_AUTH_GET_ACCOUNT_INFO = new ApiSettings('/accounts:lookup', 'POST') // Set request validator. .setRequestValidator((request: GetAccountInfoRequest) => { @@ -651,6 +654,8 @@ export const FIREBASE_AUTH_GET_ACCOUNT_INFO = new ApiSettings('/accounts:lookup' /** * Instantiates the getAccountInfo endpoint settings for use when fetching info * for multiple accounts. + * + * @internal */ export const FIREBASE_AUTH_GET_ACCOUNTS_INFO = new ApiSettings('/accounts:lookup', 'POST') // Set request validator. @@ -663,7 +668,11 @@ export const FIREBASE_AUTH_GET_ACCOUNTS_INFO = new ApiSettings('/accounts:lookup }); -/** Instantiates the deleteAccount endpoint settings. */ +/** + * Instantiates the deleteAccount endpoint settings. + * + * @internal + */ export const FIREBASE_AUTH_DELETE_ACCOUNT = new ApiSettings('/accounts:delete', 'POST') // Set request validator. .setRequestValidator((request: any) => { @@ -689,6 +698,9 @@ export interface BatchDeleteAccountsResponse { errors?: BatchDeleteErrorInfo[]; } +/** + * @internal + */ export const FIREBASE_AUTH_BATCH_DELETE_ACCOUNTS = new ApiSettings('/accounts:batchDelete', 'POST') .setRequestValidator((request: BatchDeleteAccountsRequest) => { if (!request.localIds) { @@ -719,7 +731,11 @@ export const FIREBASE_AUTH_BATCH_DELETE_ACCOUNTS = new ApiSettings('/accounts:ba }); }); -/** Instantiates the setAccountInfo endpoint settings for updating existing accounts. */ +/** + * Instantiates the setAccountInfo endpoint settings for updating existing accounts. + * + * @internal + */ export const FIREBASE_AUTH_SET_ACCOUNT_INFO = new ApiSettings('/accounts:update', 'POST') // Set request validator. .setRequestValidator((request: any) => { @@ -748,6 +764,8 @@ export const FIREBASE_AUTH_SET_ACCOUNT_INFO = new ApiSettings('/accounts:update' /** * Instantiates the signupNewUser endpoint settings for creating a new user with or without * uid being specified. The backend will create a new one if not provided and return it. + * + * @internal */ export const FIREBASE_AUTH_SIGN_UP_NEW_USER = new ApiSettings('/accounts', 'POST') // Set request validator. @@ -809,7 +827,11 @@ const FIREBASE_AUTH_GET_OOB_CODE = new ApiSettings('/accounts:sendOobCode', 'POS } }); -/** Instantiates the retrieve OIDC configuration endpoint settings. */ +/** + * Instantiates the retrieve OIDC configuration endpoint settings. + * + * @internal + */ const GET_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}', 'GET') // Set response validator. .setResponseValidator((response: any) => { @@ -822,10 +844,18 @@ const GET_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}', 'G } }); -/** Instantiates the delete OIDC configuration endpoint settings. */ +/** + * Instantiates the delete OIDC configuration endpoint settings. + * + * @internal + */ const DELETE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}', 'DELETE'); -/** Instantiates the create OIDC configuration endpoint settings. */ +/** + * Instantiates the create OIDC configuration endpoint settings. + * + * @internal + */ const CREATE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs?oauthIdpConfigId={providerId}', 'POST') // Set response validator. .setResponseValidator((response: any) => { @@ -838,7 +868,11 @@ const CREATE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs?oauthIdpConfig } }); -/** Instantiates the update OIDC configuration endpoint settings. */ +/** + * Instantiates the update OIDC configuration endpoint settings. + * + * @internal + */ const UPDATE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}?updateMask={updateMask}', 'PATCH') // Set response validator. .setResponseValidator((response: any) => { @@ -851,7 +885,11 @@ const UPDATE_OAUTH_IDP_CONFIG = new ApiSettings('/oauthIdpConfigs/{providerId}?u } }); -/** Instantiates the list OIDC configuration endpoint settings. */ +/** + * Instantiates the list OIDC configuration endpoint settings. + * + * @internal + */ const LIST_OAUTH_IDP_CONFIGS = new ApiSettings('/oauthIdpConfigs', 'GET') // Set request validator. .setRequestValidator((request: any) => { @@ -872,7 +910,11 @@ const LIST_OAUTH_IDP_CONFIGS = new ApiSettings('/oauthIdpConfigs', 'GET') } }); -/** Instantiates the retrieve SAML configuration endpoint settings. */ +/** + * Instantiates the retrieve SAML configuration endpoint settings. + * + * @internal + */ const GET_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{providerId}', 'GET') // Set response validator. .setResponseValidator((response: any) => { @@ -885,10 +927,18 @@ const GET_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{providerId } }); -/** Instantiates the delete SAML configuration endpoint settings. */ +/** + * Instantiates the delete SAML configuration endpoint settings. + * + * @internal + */ const DELETE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{providerId}', 'DELETE'); -/** Instantiates the create SAML configuration endpoint settings. */ +/** + * Instantiates the create SAML configuration endpoint settings. + * + * @internal + */ const CREATE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs?inboundSamlConfigId={providerId}', 'POST') // Set response validator. .setResponseValidator((response: any) => { @@ -901,7 +951,11 @@ const CREATE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs?inboundS } }); -/** Instantiates the update SAML configuration endpoint settings. */ +/** + * Instantiates the update SAML configuration endpoint settings. + * + * @internal + */ const UPDATE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{providerId}?updateMask={updateMask}', 'PATCH') // Set response validator. .setResponseValidator((response: any) => { @@ -914,7 +968,11 @@ const UPDATE_INBOUND_SAML_CONFIG = new ApiSettings('/inboundSamlConfigs/{provide } }); -/** Instantiates the list SAML configuration endpoint settings. */ +/** + * Instantiates the list SAML configuration endpoint settings. + * + * @internal + */ const LIST_INBOUND_SAML_CONFIGS = new ApiSettings('/inboundSamlConfigs', 'GET') // Set request validator. .setRequestValidator((request: any) => { @@ -937,6 +995,8 @@ const LIST_INBOUND_SAML_CONFIGS = new ApiSettings('/inboundSamlConfigs', 'GET') /** * Class that provides the mechanism to send requests to the Firebase Auth backend endpoints. + * + * @internal */ export abstract class AbstractAuthRequestHandler { @@ -997,7 +1057,7 @@ export abstract class AbstractAuthRequestHandler { * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. * @constructor */ - constructor(protected readonly app: FirebaseApp) { + constructor(protected readonly app: App) { if (typeof app !== 'object' || app === null || !('options' in app)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -1005,7 +1065,7 @@ export abstract class AbstractAuthRequestHandler { ); } - this.httpClient = new AuthHttpClient(app); + this.httpClient = new AuthHttpClient(app as FirebaseApp); } /** @@ -1915,7 +1975,7 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. * @constructor. */ - constructor(app: FirebaseApp) { + constructor(app: App) { super(app); this.tenantMgmtResourceBuilder = new AuthResourceUrlBuilder(app, 'v2'); } @@ -2061,7 +2121,7 @@ export class TenantAwareAuthRequestHandler extends AbstractAuthRequestHandler { * @param {string} tenantId The request handler's tenant ID. * @constructor */ - constructor(app: FirebaseApp, private readonly tenantId: string) { + constructor(app: App, private readonly tenantId: string) { super(app); } diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 26db94126e..1449d4191f 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -17,14 +17,293 @@ import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; -import { auth } from './index'; -import MultiFactorConfigInterface = auth.MultiFactorConfig; -import MultiFactorConfigState = auth.MultiFactorConfigState; -import AuthFactorType = auth.AuthFactorType; -import EmailSignInProviderConfig = auth.EmailSignInProviderConfig; -import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; -import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; +/** + * Interface representing base properties of a user enrolled second factor for a + * `CreateRequest`. + */ +export interface CreateMultiFactorInfoRequest { + + /** + * The optional display name for an enrolled second factor. + */ + displayName?: string; + + /** + * The type identifier of the second factor. For SMS second factors, this is `phone`. + */ + factorId: string; +} + +/** + * Interface representing a phone specific user enrolled second factor for a + * `CreateRequest`. + */ +export interface CreatePhoneMultiFactorInfoRequest extends CreateMultiFactorInfoRequest { + + /** + * The phone number associated with a phone second factor. + */ + phoneNumber: string; +} + +/** + * Interface representing common properties of a user enrolled second factor + * for an `UpdateRequest`. + */ +export interface UpdateMultiFactorInfoRequest { + + /** + * The ID of the enrolled second factor. This ID is unique to the user. When not provided, + * a new one is provisioned by the Auth server. + */ + uid?: string; + + /** + * The optional display name for an enrolled second factor. + */ + displayName?: string; + + /** + * The optional date the second factor was enrolled, formatted as a UTC string. + */ + enrollmentTime?: string; + + /** + * The type identifier of the second factor. For SMS second factors, this is `phone`. + */ + factorId: string; +} + +/** + * Interface representing a phone specific user enrolled second factor + * for an `UpdateRequest`. + */ +export interface UpdatePhoneMultiFactorInfoRequest extends UpdateMultiFactorInfoRequest { + + /** + * The phone number associated with a phone second factor. + */ + phoneNumber: string; +} + +/** + * The multi-factor related user settings for create operations. + */ +export interface MultiFactorCreateSettings { + + /** + * The created user's list of enrolled second factors. + */ + enrolledFactors: CreateMultiFactorInfoRequest[]; +} + +/** + * The multi-factor related user settings for update operations. + */ +export interface MultiFactorUpdateSettings { + + /** + * The updated list of enrolled second factors. The provided list overwrites the user's + * existing list of second factors. + * When null is passed, all of the user's existing second factors are removed. + */ + enrolledFactors: UpdateMultiFactorInfoRequest[] | null; +} + +/** + * Interface representing the properties to update on the provided user. + */ +export interface UpdateRequest { + + /** + * Whether or not the user is disabled: `true` for disabled; + * `false` for enabled. + */ + disabled?: boolean; + + /** + * The user's display name. + */ + displayName?: string | null; + + /** + * The user's primary email. + */ + email?: string; + + /** + * Whether or not the user's primary email is verified. + */ + emailVerified?: boolean; + + /** + * The user's unhashed password. + */ + password?: string; + + /** + * The user's primary phone number. + */ + phoneNumber?: string | null; + + /** + * The user's photo URL. + */ + photoURL?: string | null; + + /** + * The user's updated multi-factor related properties. + */ + multiFactor?: MultiFactorUpdateSettings; +} + +/** + * Interface representing the properties to set on a new user record to be + * created. + */ +export interface CreateRequest extends UpdateRequest { + + /** + * The user's `uid`. + */ + uid?: string; + + /** + * The user's multi-factor related properties. + */ + multiFactor?: MultiFactorCreateSettings; +} + +/** + * The response interface for listing provider configs. This is only available + * when listing all identity providers' configurations via + * {@link auth.Auth.listProviderConfigs `listProviderConfigs()`}. + */ +export interface ListProviderConfigResults { + + /** + * The list of providers for the specified type in the current page. + */ + providerConfigs: AuthProviderConfig[]; + + /** + * The next page token, if available. + */ + pageToken?: string; +} + +/** + * The filter interface used for listing provider configurations. This is used + * when specifying how to list configured identity providers via + * {@link auth.Auth.listProviderConfigs `listProviderConfigs()`}. + */ +export interface AuthProviderConfigFilter { + + /** + * The Auth provider configuration filter. This can be either `saml` or `oidc`. + * The former is used to look up SAML providers only, while the latter is used + * for OIDC providers. + */ + type: 'saml' | 'oidc'; + + /** + * The maximum number of results to return per page. The default and maximum is + * 100. + */ + maxResults?: number; + + /** + * The next page token. When not specified, the lookup starts from the beginning + * of the list. + */ + pageToken?: string; +} + +/** + * The request interface for updating a SAML Auth provider. This is used + * when updating a SAML provider's configuration via + * {@link auth.Auth.updateProviderConfig `updateProviderConfig()`}. + */ +export interface SAMLUpdateAuthProviderRequest { + + /** + * The SAML provider's updated display name. If not provided, the existing + * configuration's value is not modified. + */ + displayName?: string; + + /** + * Whether the SAML provider is enabled or not. If not provided, the existing + * configuration's setting is not modified. + */ + enabled?: boolean; + + /** + * The SAML provider's updated IdP entity ID. If not provided, the existing + * configuration's value is not modified. + */ + idpEntityId?: string; + + /** + * The SAML provider's updated SSO URL. If not provided, the existing + * configuration's value is not modified. + */ + ssoURL?: string; + + /** + * The SAML provider's updated list of X.509 certificated. If not provided, the + * existing configuration list is not modified. + */ + x509Certificates?: string[]; + + /** + * The SAML provider's updated RP entity ID. If not provided, the existing + * configuration's value is not modified. + */ + rpEntityId?: string; + + /** + * The SAML provider's callback URL. If not provided, the existing + * configuration's value is not modified. + */ + callbackURL?: string; +} + +/** + * The request interface for updating an OIDC Auth provider. This is used + * when updating an OIDC provider's configuration via + * {@link auth.Auth.updateProviderConfig `updateProviderConfig()`}. + */ +export interface OIDCUpdateAuthProviderRequest { + + /** + * The OIDC provider's updated display name. If not provided, the existing + * configuration's value is not modified. + */ + displayName?: string; + + /** + * Whether the OIDC provider is enabled or not. If not provided, the existing + * configuration's setting is not modified. + */ + enabled?: boolean; + + /** + * The OIDC provider's updated client ID. If not provided, the existing + * configuration's value is not modified. + */ + clientId?: string; + + /** + * The OIDC provider's updated issuer. If not provided, the existing + * configuration's value is not modified. + */ + issuer?: string; +} + +export type UpdateAuthProviderRequest = + SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; /** A maximum of 10 test phone number / code pairs can be configured. */ export const MAXIMUM_TEST_PHONE_NUMBERS = 10; @@ -117,11 +396,35 @@ export interface MultiFactorAuthServerConfig { enabledProviders?: AuthFactorServerType[]; } +/** + * Identifies a second factor type. + */ +export type AuthFactorType = 'phone'; + +/** + * Identifies a multi-factor configuration state. + */ +export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; + +export interface MultiFactorConfig { + /** + * The multi-factor config state. + */ + state: MultiFactorConfigState; + + /** + * The list of identifiers for enabled second factors. + * Currently only ‘phone’ is supported. + */ + factorIds?: AuthFactorType[]; +} + /** * Defines the multi-factor config class used to convert client side MultiFactorConfig * to a format that is understood by the Auth server. */ -export class MultiFactorAuthConfig implements MultiFactorConfigInterface { +export class MultiFactorAuthConfig implements MultiFactorConfig { + public readonly state: MultiFactorConfigState; public readonly factorIds: AuthFactorType[]; @@ -131,8 +434,9 @@ export class MultiFactorAuthConfig implements MultiFactorConfigInterface { * * @param options The options object to convert to a server request. * @return The resulting server request. + * @internal */ - public static buildServerRequest(options: MultiFactorConfigInterface): MultiFactorAuthServerConfig { + public static buildServerRequest(options: MultiFactorConfig): MultiFactorAuthServerConfig { const request: MultiFactorAuthServerConfig = {}; MultiFactorAuthConfig.validate(options); if (Object.prototype.hasOwnProperty.call(options, 'state')) { @@ -158,7 +462,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfigInterface { * * @param options The options object to validate. */ - private static validate(options: MultiFactorConfigInterface): void { + private static validate(options: MultiFactorConfig): void { const validKeys = { state: true, factorIds: true, @@ -214,6 +518,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfigInterface { * @param response The server side response used to initialize the * MultiFactorAuthConfig object. * @constructor + * @internal */ constructor(response: MultiFactorAuthServerConfig) { if (typeof response.state === 'undefined') { @@ -278,10 +583,28 @@ export function validateTestPhoneNumbers( } } +/** + * The email sign in configuration. + */ +export interface EmailSignInProviderConfig { + /** + * Whether email provider is enabled. + */ + enabled: boolean; + + /** + * Whether password is required for email sign-in. When not required, + * email sign-in can be performed with password or via email link sign-in. + */ + passwordRequired?: boolean; // In the backend API, default is true if not provided +} + /** * Defines the email sign-in config class used to convert client side EmailSignInConfig * to a format that is understood by the Auth server. + * + * @internal */ export class EmailSignInConfig implements EmailSignInProviderConfig { public readonly enabled: boolean; @@ -293,6 +616,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { * * @param {any} options The options object to convert to a server request. * @return {EmailSignInConfigServerRequest} The resulting server request. + * @internal */ public static buildServerRequest(options: EmailSignInProviderConfig): EmailSignInConfigServerRequest { const request: EmailSignInConfigServerRequest = {}; @@ -375,10 +699,117 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { } } +/** + * The base Auth provider configuration interface. + */ +export interface AuthProviderConfig { + + /** + * The provider ID defined by the developer. + * For a SAML provider, this is always prefixed by `saml.`. + * For an OIDC provider, this is always prefixed by `oidc.`. + */ + providerId: string; + + /** + * The user-friendly display name to the current configuration. This name is + * also used as the provider label in the Cloud Console. + */ + displayName?: string; + + /** + * Whether the provider configuration is enabled or disabled. A user + * cannot sign in using a disabled provider. + */ + enabled: boolean; +} + +/** + * The + * [SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) + * Auth provider configuration interface. A SAML provider can be created via + * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. + */ +export interface SAMLAuthProviderConfig extends AuthProviderConfig { + + /** + * The SAML IdP entity identifier. + */ + idpEntityId: string; + + /** + * The SAML IdP SSO URL. This must be a valid URL. + */ + ssoURL: string; + + /** + * The list of SAML IdP X.509 certificates issued by CA for this provider. + * Multiple certificates are accepted to prevent outages during + * IdP key rotation (for example ADFS rotates every 10 days). When the Auth + * server receives a SAML response, it will match the SAML response with the + * certificate on record. Otherwise the response is rejected. + * Developers are expected to manage the certificate updates as keys are + * rotated. + */ + x509Certificates: string[]; + + /** + * The SAML relying party (service provider) entity ID. + * This is defined by the developer but needs to be provided to the SAML IdP. + */ + rpEntityId: string; + + /** + * This is fixed and must always be the same as the OAuth redirect URL + * provisioned by Firebase Auth, + * `https://project-id.firebaseapp.com/__/auth/handler` unless a custom + * `authDomain` is used. + * The callback URL should also be provided to the SAML IdP during + * configuration. + */ + callbackURL?: string; +} + +/** + * The [OIDC](https://openid.net/specs/openid-connect-core-1_0-final.html) Auth + * provider configuration interface. An OIDC provider can be created via + * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. + */ +export interface OIDCAuthProviderConfig extends AuthProviderConfig { + + /** + * This is the required client ID used to confirm the audience of an OIDC + * provider's + * [ID token](https://openid.net/specs/openid-connect-core-1_0-final.html#IDToken). + */ + clientId: string; + + /** + * This is the required provider issuer used to match the provider issuer of + * the ID token and to determine the corresponding OIDC discovery document, eg. + * [`/.well-known/openid-configuration`](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig). + * This is needed for the following: + *
    + *
  • To verify the provided issuer.
  • + *
  • Determine the authentication/authorization endpoint during the OAuth + * `id_token` authentication flow.
  • + *
  • To retrieve the public signing keys via `jwks_uri` to verify the OIDC + * provider's ID token's signature.
  • + *
  • To determine the claims_supported to construct the user attributes to be + * returned in the additional user info response.
  • + *
+ * ID token validation will be performed as defined in the + * [spec](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation). + */ + issuer: string; +} + /** * Defines the SAMLConfig class used to convert a client side configuration to its * server side representation. + * + * @internal */ export class SAMLConfig implements SAMLAuthProviderConfig { public readonly enabled: boolean; @@ -643,6 +1074,8 @@ export class SAMLConfig implements SAMLAuthProviderConfig { /** * Defines the OIDCConfig class used to convert a client side configuration to its * server side representation. + * + * @internal */ export class OIDCConfig implements OIDCAuthProviderConfig { public readonly enabled: boolean; diff --git a/src/auth/auth-namespace.ts b/src/auth/auth-namespace.ts new file mode 100644 index 0000000000..b4f8361f0f --- /dev/null +++ b/src/auth/auth-namespace.ts @@ -0,0 +1,181 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App, getApp } from '../app/index'; +import { Auth } from './auth'; +import { FirebaseApp } from '../app/firebase-app'; + +// Import all public types with aliases, and re-export from the auth namespace. + +import { ActionCodeSettings as TActionCodeSettings } from './action-code-settings-builder'; + +import { Auth as TAuth } from './auth'; + +import { + AuthFactorType as TAuthFactorType, + AuthProviderConfig as TAuthProviderConfig, + AuthProviderConfigFilter as TAuthProviderConfigFilter, + CreateRequest as TCreateRequest, + CreateMultiFactorInfoRequest as TCreateMultiFactorInfoRequest, + CreatePhoneMultiFactorInfoRequest as TCreatePhoneMultiFactorInfoRequest, + EmailSignInProviderConfig as TEmailSignInProviderConfig, + ListProviderConfigResults as TListProviderConfigResults, + MultiFactorCreateSettings as TMultiFactorCreateSettings, + MultiFactorConfig as TMultiFactorConfig, + MultiFactorConfigState as TMultiFactorConfigState, + MultiFactorUpdateSettings as TMultiFactorUpdateSettings, + OIDCAuthProviderConfig as TOIDCAuthProviderConfig, + OIDCUpdateAuthProviderRequest as TOIDCUpdateAuthProviderRequest, + SAMLAuthProviderConfig as TSAMLAuthProviderConfig, + SAMLUpdateAuthProviderRequest as TSAMLUpdateAuthProviderRequest, + UpdateAuthProviderRequest as TUpdateAuthProviderRequest, + UpdateMultiFactorInfoRequest as TUpdateMultiFactorInfoRequest, + UpdatePhoneMultiFactorInfoRequest as TUpdatePhoneMultiFactorInfoRequest, + UpdateRequest as TUpdateRequest, +} from './auth-config'; + +import { + BaseAuth as TBaseAuth, + DeleteUsersResult as TDeleteUsersResult, + GetUsersResult as TGetUsersResult, + ListUsersResult as TListUsersResult, + SessionCookieOptions as TSessionCookieOptions, +} from './base-auth'; + +import { + EmailIdentifier as TEmailIdentifier, + PhoneIdentifier as TPhoneIdentifier, + ProviderIdentifier as TProviderIdentifier, + UserIdentifier as TUserIdentifier, + UidIdentifier as TUidIdentifier, +} from './identifier'; + +import { + CreateTenantRequest as TCreateTenantRequest, + Tenant as TTenant, + UpdateTenantRequest as TUpdateTenantRequest, +} from './tenant'; + +import { + ListTenantsResult as TListTenantsResult, + TenantAwareAuth as TTenantAwareAuth, + TenantManager as TTenantManager, +} from './tenant-manager'; + +import { DecodedIdToken as TDecodedIdToken } from './token-verifier'; + +import { + HashAlgorithmType as THashAlgorithmType, + UserImportOptions as TUserImportOptions, + UserImportRecord as TUserImportRecord, + UserImportResult as TUserImportResult, + UserMetadataRequest as TUserMetadataRequest, + UserProviderRequest as TUserProviderRequest, +} from './user-import-builder'; + +import { + MultiFactorInfo as TMultiFactorInfo, + MultiFactorSettings as TMultiFactorSettings, + PhoneMultiFactorInfo as TPhoneMultiFactorInfo, + UserInfo as TUserInfo, + UserMetadata as TUserMetadata, + UserRecord as TUserRecord, +} from './user-record'; + +/** + * Gets the {@link auth.Auth `Auth`} service for the default app or a + * given app. + * + * `admin.auth()` can be called with no arguments to access the default app's + * {@link auth.Auth `Auth`} service or as `admin.auth(app)` to access the + * {@link auth.Auth `Auth`} service associated with a specific app. + * + * @example + * ```javascript + * // Get the Auth service for the default app + * var defaultAuth = admin.auth(); + * ``` + * + * @example + * ```javascript + * // Get the Auth service for a given app + * var otherAuth = admin.auth(otherApp); + * ``` + * + */ +export function auth(app?: App): Auth { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('auth', (app) => new Auth(app)); +} + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace auth { + export type ActionCodeSettings = TActionCodeSettings; + export type Auth = TAuth; + export type AuthFactorType = TAuthFactorType; + export type AuthProviderConfig = TAuthProviderConfig; + export type AuthProviderConfigFilter = TAuthProviderConfigFilter; + export type BaseAuth = TBaseAuth; + export type CreateMultiFactorInfoRequest = TCreateMultiFactorInfoRequest; + export type CreatePhoneMultiFactorInfoRequest = TCreatePhoneMultiFactorInfoRequest; + export type CreateRequest = TCreateRequest; + export type CreateTenantRequest = TCreateTenantRequest; + export type DecodedIdToken = TDecodedIdToken; + export type DeleteUsersResult = TDeleteUsersResult; + export type EmailIdentifier = TEmailIdentifier; + export type EmailSignInProviderConfig = TEmailSignInProviderConfig; + export type GetUsersResult = TGetUsersResult; + export type HashAlgorithmType = THashAlgorithmType; + export type ListProviderConfigResults = TListProviderConfigResults; + export type ListTenantsResult = TListTenantsResult; + export type ListUsersResult = TListUsersResult; + export type MultiFactorCreateSettings = TMultiFactorCreateSettings; + export type MultiFactorConfig = TMultiFactorConfig; + export type MultiFactorConfigState = TMultiFactorConfigState; + export type MultiFactorInfo = TMultiFactorInfo; + export type MultiFactorUpdateSettings = TMultiFactorUpdateSettings; + export type MultiFactorSettings = TMultiFactorSettings; + export type OIDCAuthProviderConfig = TOIDCAuthProviderConfig; + export type OIDCUpdateAuthProviderRequest = TOIDCUpdateAuthProviderRequest; + export type PhoneIdentifier = TPhoneIdentifier; + export type PhoneMultiFactorInfo = TPhoneMultiFactorInfo; + export type ProviderIdentifier = TProviderIdentifier; + export type SAMLAuthProviderConfig = TSAMLAuthProviderConfig; + export type SAMLUpdateAuthProviderRequest = TSAMLUpdateAuthProviderRequest; + export type SessionCookieOptions = TSessionCookieOptions; + export type Tenant = TTenant; + export type TenantAwareAuth = TTenantAwareAuth; + export type TenantManager = TTenantManager; + export type UidIdentifier = TUidIdentifier; + export type UpdateAuthProviderRequest = TUpdateAuthProviderRequest; + export type UpdateMultiFactorInfoRequest = TUpdateMultiFactorInfoRequest; + export type UpdatePhoneMultiFactorInfoRequest = TUpdatePhoneMultiFactorInfoRequest; + export type UpdateRequest = TUpdateRequest; + export type UpdateTenantRequest = TUpdateTenantRequest; + export type UserIdentifier = TUserIdentifier; + export type UserImportOptions = TUserImportOptions; + export type UserImportRecord = TUserImportRecord; + export type UserImportResult = TUserImportResult; + export type UserInfo = TUserInfo; + export type UserMetadata = TUserMetadata; + export type UserMetadataRequest = TUserMetadataRequest; + export type UserProviderRequest = TUserProviderRequest; + export type UserRecord = TUserRecord; +} diff --git a/src/auth/auth.ts b/src/auth/auth.ts index c205605224..0378466b76 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -15,804 +15,26 @@ * limitations under the License. */ -import { UserRecord } from './user-record'; -import { - isUidIdentifier, isEmailIdentifier, isPhoneIdentifier, isProviderIdentifier, -} from './identifier'; -import { FirebaseApp } from '../app/firebase-app'; -import { FirebaseTokenGenerator, EmulatedSigner, cryptoSignerFromApp } from './token-generator'; -import { - AbstractAuthRequestHandler, AuthRequestHandler, TenantAwareAuthRequestHandler, useEmulator, -} from './auth-api-request'; -import { AuthClientErrorCode, FirebaseAuthError, ErrorInfo } from '../utils/error'; -import * as utils from '../utils/index'; -import * as validator from '../utils/validator'; -import { auth } from './index'; -import { - FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier, ALGORITHM_RS256 -} from './token-verifier'; -import { - SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, -} from './auth-config'; +import { App } from '../app/index'; +import { AuthRequestHandler } from './auth-api-request'; import { TenantManager } from './tenant-manager'; - -import UserIdentifier = auth.UserIdentifier; -import CreateRequest = auth.CreateRequest; -import UpdateRequest = auth.UpdateRequest; -import ActionCodeSettings = auth.ActionCodeSettings; -import UserImportOptions = auth.UserImportOptions; -import UserImportRecord = auth.UserImportRecord; -import UserImportResult = auth.UserImportResult; -import AuthProviderConfig = auth.AuthProviderConfig; -import AuthProviderConfigFilter = auth.AuthProviderConfigFilter; -import ListProviderConfigResults = auth.ListProviderConfigResults; -import UpdateAuthProviderRequest = auth.UpdateAuthProviderRequest; -import GetUsersResult = auth.GetUsersResult; -import ListUsersResult = auth.ListUsersResult; -import DeleteUsersResult = auth.DeleteUsersResult; -import DecodedIdToken = auth.DecodedIdToken; -import SessionCookieOptions = auth.SessionCookieOptions; -import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; -import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; -import BaseAuthInterface = auth.BaseAuth; -import AuthInterface = auth.Auth; -import TenantAwareAuthInterface = auth.TenantAwareAuth; - -/** - * Base Auth class. Mainly used for user management APIs. - */ -export class BaseAuth implements BaseAuthInterface { - - protected readonly tokenGenerator: FirebaseTokenGenerator; - protected readonly idTokenVerifier: FirebaseTokenVerifier; - protected readonly sessionCookieVerifier: FirebaseTokenVerifier; - - /** - * The BaseAuth class constructor. - * - * @param app The FirebaseApp to associate with this Auth instance. - * @param authRequestHandler The RPC request handler for this instance. - * @param tokenGenerator Optional token generator. If not specified, a - * (non-tenant-aware) instance will be created. Use this paramter to - * specify a tenant-aware tokenGenerator. - * @constructor - */ - constructor(app: FirebaseApp, protected readonly authRequestHandler: T, tokenGenerator?: FirebaseTokenGenerator) { - if (tokenGenerator) { - this.tokenGenerator = tokenGenerator; - } else { - const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); - this.tokenGenerator = new FirebaseTokenGenerator(cryptoSigner); - } - - this.sessionCookieVerifier = createSessionCookieVerifier(app); - this.idTokenVerifier = createIdTokenVerifier(app); - } - - /** - * Creates a new custom token that can be sent back to a client to use with - * signInWithCustomToken(). - * - * @param {string} uid The uid to use as the JWT subject. - * @param {object=} developerClaims Optional additional claims to include in the JWT payload. - * - * @return {Promise} A JWT for the provided payload. - */ - public createCustomToken(uid: string, developerClaims?: object): Promise { - return this.tokenGenerator.createCustomToken(uid, developerClaims); - } - - /** - * Verifies a JWT auth token. Returns a Promise with the tokens claims. Rejects - * the promise if the token could not be verified. If checkRevoked is set to true, - * verifies if the session corresponding to the ID token was revoked. If the corresponding - * user's session was invalidated, an auth/id-token-revoked error is thrown. If not specified - * the check is not applied. - * - * @param {string} idToken The JWT to verify. - * @param {boolean=} checkRevoked Whether to check if the ID token is revoked. - * @return {Promise} A Promise that will be fulfilled after a successful - * verification. - */ - public verifyIdToken(idToken: string, checkRevoked = false): Promise { - return this.idTokenVerifier.verifyJWT(idToken) - .then((decodedIdToken: DecodedIdToken) => { - // Whether to check if the token was revoked. - if (!checkRevoked) { - return decodedIdToken; - } - return this.verifyDecodedJWTNotRevoked( - decodedIdToken, - AuthClientErrorCode.ID_TOKEN_REVOKED); - }); - } - - /** - * Looks up the user identified by the provided user id and returns a promise that is - * fulfilled with a user record for the given user if that user is found. - * - * @param {string} uid The uid of the user to look up. - * @return {Promise} A promise that resolves with the corresponding user record. - */ - public getUser(uid: string): Promise { - return this.authRequestHandler.getAccountInfoByUid(uid) - .then((response: any) => { - // Returns the user record populated with server response. - return new UserRecord(response.users[0]); - }); - } - - /** - * Looks up the user identified by the provided email and returns a promise that is - * fulfilled with a user record for the given user if that user is found. - * - * @param {string} email The email of the user to look up. - * @return {Promise} A promise that resolves with the corresponding user record. - */ - public getUserByEmail(email: string): Promise { - return this.authRequestHandler.getAccountInfoByEmail(email) - .then((response: any) => { - // Returns the user record populated with server response. - return new UserRecord(response.users[0]); - }); - } - - /** - * Looks up the user identified by the provided phone number and returns a promise that is - * fulfilled with a user record for the given user if that user is found. - * - * @param {string} phoneNumber The phone number of the user to look up. - * @return {Promise} A promise that resolves with the corresponding user record. - */ - public getUserByPhoneNumber(phoneNumber: string): Promise { - return this.authRequestHandler.getAccountInfoByPhoneNumber(phoneNumber) - .then((response: any) => { - // Returns the user record populated with server response. - return new UserRecord(response.users[0]); - }); - } - - /** - * Gets the user data corresponding to the specified identifiers. - * - * There are no ordering guarantees; in particular, the nth entry in the result list is not - * guaranteed to correspond to the nth entry in the input parameters list. - * - * Only a maximum of 100 identifiers may be supplied. If more than 100 identifiers are supplied, - * this method will immediately throw a FirebaseAuthError. - * - * @param identifiers The identifiers used to indicate which user records should be returned. Must - * have <= 100 entries. - * @return {Promise} A promise that resolves to the corresponding user records. - * @throws FirebaseAuthError If any of the identifiers are invalid or if more than 100 - * identifiers are specified. - */ - public getUsers(identifiers: UserIdentifier[]): Promise { - if (!validator.isArray(identifiers)) { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, '`identifiers` parameter must be an array'); - } - return this.authRequestHandler - .getAccountInfoByIdentifiers(identifiers) - .then((response: any) => { - /** - * Checks if the specified identifier is within the list of - * UserRecords. - */ - const isUserFound = ((id: UserIdentifier, userRecords: UserRecord[]): boolean => { - return !!userRecords.find((userRecord) => { - if (isUidIdentifier(id)) { - return id.uid === userRecord.uid; - } else if (isEmailIdentifier(id)) { - return id.email === userRecord.email; - } else if (isPhoneIdentifier(id)) { - return id.phoneNumber === userRecord.phoneNumber; - } else if (isProviderIdentifier(id)) { - const matchingUserInfo = userRecord.providerData.find((userInfo) => { - return id.providerId === userInfo.providerId; - }); - return !!matchingUserInfo && id.providerUid === matchingUserInfo.uid; - } else { - throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, - 'Unhandled identifier type'); - } - }); - }); - - const users = response.users ? response.users.map((user: any) => new UserRecord(user)) : []; - const notFound = identifiers.filter((id) => !isUserFound(id, users)); - - return { users, notFound }; - }); - } - - /** - * Exports a batch of user accounts. Batch size is determined by the maxResults argument. - * Starting point of the batch is determined by the pageToken argument. - * - * @param {number=} maxResults The page size, 1000 if undefined. This is also the maximum - * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns users starting - * without any offset. - * @return {Promise<{users: UserRecord[], pageToken?: string}>} A promise that resolves with - * the current batch of downloaded users and the next page token. For the last page, an - * empty list of users and no page token are returned. - */ - public listUsers(maxResults?: number, pageToken?: string): Promise { - return this.authRequestHandler.downloadAccount(maxResults, pageToken) - .then((response: any) => { - // List of users to return. - const users: UserRecord[] = []; - // Convert each user response to a UserRecord. - response.users.forEach((userResponse: any) => { - users.push(new UserRecord(userResponse)); - }); - // Return list of user records and the next page token if available. - const result = { - users, - pageToken: response.nextPageToken, - }; - // Delete result.pageToken if undefined. - if (typeof result.pageToken === 'undefined') { - delete result.pageToken; - } - return result; - }); - } - - /** - * Creates a new user with the properties provided. - * - * @param {CreateRequest} properties The properties to set on the new user record to be created. - * @return {Promise} A promise that resolves with the newly created user record. - */ - public createUser(properties: CreateRequest): Promise { - return this.authRequestHandler.createNewAccount(properties) - .then((uid) => { - // Return the corresponding user record. - return this.getUser(uid); - }) - .catch((error) => { - if (error.code === 'auth/user-not-found') { - // Something must have happened after creating the user and then retrieving it. - throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, - 'Unable to create the user record provided.'); - } - throw error; - }); - } - - /** - * Deletes the user identified by the provided user id and returns a promise that is - * fulfilled when the user is found and successfully deleted. - * - * @param {string} uid The uid of the user to delete. - * @return {Promise} A promise that resolves when the user is successfully deleted. - */ - public deleteUser(uid: string): Promise { - return this.authRequestHandler.deleteAccount(uid) - .then(() => { - // Return nothing on success. - }); - } - - public deleteUsers(uids: string[]): Promise { - if (!validator.isArray(uids)) { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, '`uids` parameter must be an array'); - } - return this.authRequestHandler.deleteAccounts(uids, /*force=*/true) - .then((batchDeleteAccountsResponse) => { - const result: DeleteUsersResult = { - failureCount: 0, - successCount: uids.length, - errors: [], - }; - - if (!validator.isNonEmptyArray(batchDeleteAccountsResponse.errors)) { - return result; - } - - result.failureCount = batchDeleteAccountsResponse.errors.length; - result.successCount = uids.length - batchDeleteAccountsResponse.errors.length; - result.errors = batchDeleteAccountsResponse.errors.map((batchDeleteErrorInfo) => { - if (batchDeleteErrorInfo.index === undefined) { - throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, - 'Corrupt BatchDeleteAccountsResponse detected'); - } - - const errMsgToError = (msg?: string): FirebaseAuthError => { - // We unconditionally set force=true, so the 'NOT_DISABLED' error - // should not be possible. - const code = msg && msg.startsWith('NOT_DISABLED') ? - AuthClientErrorCode.USER_NOT_DISABLED : AuthClientErrorCode.INTERNAL_ERROR; - return new FirebaseAuthError(code, batchDeleteErrorInfo.message); - }; - - return { - index: batchDeleteErrorInfo.index, - error: errMsgToError(batchDeleteErrorInfo.message), - }; - }); - - return result; - }); - } - - /** - * Updates an existing user with the properties provided. - * - * @param {string} uid The uid identifier of the user to update. - * @param {UpdateRequest} properties The properties to update on the existing user. - * @return {Promise} A promise that resolves with the modified user record. - */ - public updateUser(uid: string, properties: UpdateRequest): Promise { - return this.authRequestHandler.updateExistingAccount(uid, properties) - .then((existingUid) => { - // Return the corresponding user record. - return this.getUser(existingUid); - }); - } - - /** - * Sets additional developer claims on an existing user identified by the provided UID. - * - * @param {string} uid The user to edit. - * @param {object} customUserClaims The developer claims to set. - * @return {Promise} A promise that resolves when the operation completes - * successfully. - */ - public setCustomUserClaims(uid: string, customUserClaims: object | null): Promise { - return this.authRequestHandler.setCustomUserClaims(uid, customUserClaims) - .then(() => { - // Return nothing on success. - }); - } - - /** - * Revokes all refresh tokens for the specified user identified by the provided UID. - * In addition to revoking all refresh tokens for a user, all ID tokens issued before - * revocation will also be revoked on the Auth backend. Any request with an ID token - * generated before revocation will be rejected with a token expired error. - * - * @param {string} uid The user whose tokens are to be revoked. - * @return {Promise} A promise that resolves when the operation completes - * successfully. - */ - public revokeRefreshTokens(uid: string): Promise { - return this.authRequestHandler.revokeRefreshTokens(uid) - .then(() => { - // Return nothing on success. - }); - } - - /** - * Imports the list of users provided to Firebase Auth. This is useful when - * migrating from an external authentication system without having to use the Firebase CLI SDK. - * At most, 1000 users are allowed to be imported one at a time. - * When importing a list of password users, UserImportOptions are required to be specified. - * - * @param {UserImportRecord[]} users The list of user records to import to Firebase Auth. - * @param {UserImportOptions=} options The user import options, required when the users provided - * include password credentials. - * @return {Promise} A promise that resolves when the operation completes - * with the result of the import. This includes the number of successful imports, the number - * of failed uploads and their corresponding errors. - */ - public importUsers( - users: UserImportRecord[], options?: UserImportOptions): Promise { - return this.authRequestHandler.uploadAccount(users, options); - } - - /** - * Creates a new Firebase session cookie with the specified options that can be used for - * session management (set as a server side session cookie with custom cookie policy). - * The session cookie JWT will have the same payload claims as the provided ID token. - * - * @param {string} idToken The Firebase ID token to exchange for a session cookie. - * @param {SessionCookieOptions} sessionCookieOptions The session cookie options which includes - * custom session duration. - * - * @return {Promise} A promise that resolves on success with the created session cookie. - */ - public createSessionCookie( - idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { - // Return rejected promise if expiresIn is not available. - if (!validator.isNonNullObject(sessionCookieOptions) || - !validator.isNumber(sessionCookieOptions.expiresIn)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); - } - return this.authRequestHandler.createSessionCookie( - idToken, sessionCookieOptions.expiresIn); - } - - /** - * Verifies a Firebase session cookie. Returns a Promise with the tokens claims. Rejects - * the promise if the token could not be verified. If checkRevoked is set to true, - * verifies if the session corresponding to the session cookie was revoked. If the corresponding - * user's session was invalidated, an auth/session-cookie-revoked error is thrown. If not - * specified the check is not performed. - * - * @param {string} sessionCookie The session cookie to verify. - * @param {boolean=} checkRevoked Whether to check if the session cookie is revoked. - * @return {Promise} A Promise that will be fulfilled after a successful - * verification. - */ - public verifySessionCookie( - sessionCookie: string, checkRevoked = false): Promise { - return this.sessionCookieVerifier.verifyJWT(sessionCookie) - .then((decodedIdToken: DecodedIdToken) => { - // Whether to check if the token was revoked. - if (!checkRevoked) { - return decodedIdToken; - } - return this.verifyDecodedJWTNotRevoked( - decodedIdToken, - AuthClientErrorCode.SESSION_COOKIE_REVOKED); - }); - } - - /** - * Generates the out of band email action link for password reset flows for the - * email specified using the action code settings provided. - * Returns a promise that resolves with the generated link. - * - * @param {string} email The email of the user whose password is to be reset. - * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether - * the link is to be handled by a mobile app and the additional state information to be passed in the - * deep link, etc. - * @return {Promise} A promise that resolves with the password reset link. - */ - public generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { - return this.authRequestHandler.getEmailActionLink('PASSWORD_RESET', email, actionCodeSettings); - } - - /** - * Generates the out of band email action link for email verification flows for the - * email specified using the action code settings provided. - * Returns a promise that resolves with the generated link. - * - * @param {string} email The email of the user to be verified. - * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether - * the link is to be handled by a mobile app and the additional state information to be passed in the - * deep link, etc. - * @return {Promise} A promise that resolves with the email verification link. - */ - public generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { - return this.authRequestHandler.getEmailActionLink('VERIFY_EMAIL', email, actionCodeSettings); - } - - /** - * Generates the out of band email action link for email link sign-in flows for the - * email specified using the action code settings provided. - * Returns a promise that resolves with the generated link. - * - * @param {string} email The email of the user signing in. - * @param {ActionCodeSettings} actionCodeSettings The required action code setings which defines whether - * the link is to be handled by a mobile app and the additional state information to be passed in the - * deep link, etc. - * @return {Promise} A promise that resolves with the email sign-in link. - */ - public generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise { - return this.authRequestHandler.getEmailActionLink('EMAIL_SIGNIN', email, actionCodeSettings); - } - - /** - * Returns the list of existing provider configuation matching the filter provided. - * At most, 100 provider configs are allowed to be imported at a time. - * - * @param {AuthProviderConfigFilter} options The provider config filter to apply. - * @return {Promise} A promise that resolves with the list of provider configs - * meeting the filter requirements. - */ - public listProviderConfigs(options: AuthProviderConfigFilter): Promise { - const processResponse = (response: any, providerConfigs: AuthProviderConfig[]): ListProviderConfigResults => { - // Return list of provider configuration and the next page token if available. - const result: ListProviderConfigResults = { - providerConfigs, - }; - // Delete result.pageToken if undefined. - if (Object.prototype.hasOwnProperty.call(response, 'nextPageToken')) { - result.pageToken = response.nextPageToken; - } - return result; - }; - if (options && options.type === 'oidc') { - return this.authRequestHandler.listOAuthIdpConfigs(options.maxResults, options.pageToken) - .then((response: any) => { - // List of provider configurations to return. - const providerConfigs: OIDCConfig[] = []; - // Convert each provider config response to a OIDCConfig. - response.oauthIdpConfigs.forEach((configResponse: any) => { - providerConfigs.push(new OIDCConfig(configResponse)); - }); - // Return list of provider configuration and the next page token if available. - return processResponse(response, providerConfigs); - }); - } else if (options && options.type === 'saml') { - return this.authRequestHandler.listInboundSamlConfigs(options.maxResults, options.pageToken) - .then((response: any) => { - // List of provider configurations to return. - const providerConfigs: SAMLConfig[] = []; - // Convert each provider config response to a SAMLConfig. - response.inboundSamlConfigs.forEach((configResponse: any) => { - providerConfigs.push(new SAMLConfig(configResponse)); - }); - // Return list of provider configuration and the next page token if available. - return processResponse(response, providerConfigs); - }); - } - return Promise.reject( - new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - '"AuthProviderConfigFilter.type" must be either "saml" or "oidc"')); - } - - /** - * Looks up an Auth provider configuration by ID. - * Returns a promise that resolves with the provider configuration corresponding to the provider ID specified. - * - * @param {string} providerId The provider ID corresponding to the provider config to return. - * @return {Promise} - */ - public getProviderConfig(providerId: string): Promise { - if (OIDCConfig.isProviderId(providerId)) { - return this.authRequestHandler.getOAuthIdpConfig(providerId) - .then((response: OIDCConfigServerResponse) => { - return new OIDCConfig(response); - }); - } else if (SAMLConfig.isProviderId(providerId)) { - return this.authRequestHandler.getInboundSamlConfig(providerId) - .then((response: SAMLConfigServerResponse) => { - return new SAMLConfig(response); - }); - } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); - } - - /** - * Deletes the provider configuration corresponding to the provider ID passed. - * - * @param {string} providerId The provider ID corresponding to the provider config to delete. - * @return {Promise} A promise that resolves on completion. - */ - public deleteProviderConfig(providerId: string): Promise { - if (OIDCConfig.isProviderId(providerId)) { - return this.authRequestHandler.deleteOAuthIdpConfig(providerId); - } else if (SAMLConfig.isProviderId(providerId)) { - return this.authRequestHandler.deleteInboundSamlConfig(providerId); - } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); - } - - /** - * Returns a promise that resolves with the updated AuthProviderConfig when the provider configuration corresponding - * to the provider ID specified is updated with the specified configuration. - * - * @param {string} providerId The provider ID corresponding to the provider config to update. - * @param {UpdateAuthProviderRequest} updatedConfig The updated configuration. - * @return {Promise} A promise that resolves with the updated provider configuration. - */ - public updateProviderConfig( - providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise { - if (!validator.isNonNullObject(updatedConfig)) { - return Promise.reject(new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, - 'Request is missing "UpdateAuthProviderRequest" configuration.', - )); - } - if (OIDCConfig.isProviderId(providerId)) { - return this.authRequestHandler.updateOAuthIdpConfig(providerId, updatedConfig) - .then((response) => { - return new OIDCConfig(response); - }); - } else if (SAMLConfig.isProviderId(providerId)) { - return this.authRequestHandler.updateInboundSamlConfig(providerId, updatedConfig) - .then((response) => { - return new SAMLConfig(response); - }); - } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); - } - - /** - * Returns a promise that resolves with the newly created AuthProviderConfig when the new provider configuration is - * created. - * @param {AuthProviderConfig} config The provider configuration to create. - * @return {Promise} A promise that resolves with the created provider configuration. - */ - public createProviderConfig(config: AuthProviderConfig): Promise { - if (!validator.isNonNullObject(config)) { - return Promise.reject(new FirebaseAuthError( - AuthClientErrorCode.INVALID_CONFIG, - 'Request is missing "AuthProviderConfig" configuration.', - )); - } - if (OIDCConfig.isProviderId(config.providerId)) { - return this.authRequestHandler.createOAuthIdpConfig(config as OIDCAuthProviderConfig) - .then((response) => { - return new OIDCConfig(response); - }); - } else if (SAMLConfig.isProviderId(config.providerId)) { - return this.authRequestHandler.createInboundSamlConfig(config as SAMLAuthProviderConfig) - .then((response) => { - return new SAMLConfig(response); - }); - } - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); - } - - /** - * Verifies the decoded Firebase issued JWT is not revoked. Returns a promise that resolves - * with the decoded claims on success. Rejects the promise with revocation error if revoked. - * - * @param {DecodedIdToken} decodedIdToken The JWT's decoded claims. - * @param {ErrorInfo} revocationErrorInfo The revocation error info to throw on revocation - * detection. - * @return {Promise} A Promise that will be fulfilled after a successful - * verification. - */ - private verifyDecodedJWTNotRevoked( - decodedIdToken: DecodedIdToken, revocationErrorInfo: ErrorInfo): Promise { - // Get tokens valid after time for the corresponding user. - return this.getUser(decodedIdToken.sub) - .then((user: UserRecord) => { - // If no tokens valid after time available, token is not revoked. - if (user.tokensValidAfterTime) { - // Get the ID token authentication time and convert to milliseconds UTC. - const authTimeUtc = decodedIdToken.auth_time * 1000; - // Get user tokens valid after time in milliseconds UTC. - const validSinceUtc = new Date(user.tokensValidAfterTime).getTime(); - // Check if authentication time is older than valid since time. - if (authTimeUtc < validSinceUtc) { - throw new FirebaseAuthError(revocationErrorInfo); - } - } - // All checks above passed. Return the decoded token. - return decodedIdToken; - }); - } - - /** - * Enable or disable ID token verification. This is used to safely short-circuit token verification with the - * Auth emulator. When disabled ONLY unsigned tokens will pass verification, production tokens will not pass. - * - * WARNING: This is a dangerous method that will compromise your app's security and break your app in - * production. Developers should never call this method, it is for internal testing use only. - * - * @internal - */ - // @ts-expect-error: this method appears unused but is used privately. - private setJwtVerificationEnabled(enabled: boolean): void { - if (!enabled && !useEmulator()) { - // We only allow verification to be disabled in conjunction with - // the emulator environment variable. - throw new Error('This method is only available when connected to the Authentication emulator.'); - } - - const algorithm = enabled ? ALGORITHM_RS256 : 'none'; - this.idTokenVerifier.setAlgorithm(algorithm); - this.sessionCookieVerifier.setAlgorithm(algorithm); - } -} - - -/** - * The tenant aware Auth class. - */ -export class TenantAwareAuth - extends BaseAuth - implements TenantAwareAuthInterface { - - public readonly tenantId: string; - - /** - * The TenantAwareAuth class constructor. - * - * @param {object} app The app that created this tenant. - * @param tenantId The corresponding tenant ID. - * @constructor - */ - constructor(app: FirebaseApp, tenantId: string) { - const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); - const tokenGenerator = new FirebaseTokenGenerator(cryptoSigner, tenantId); - super(app, new TenantAwareAuthRequestHandler(app, tenantId), tokenGenerator); - utils.addReadonlyGetter(this, 'tenantId', tenantId); - } - - /** - * Verifies a JWT auth token. Returns a Promise with the tokens claims. Rejects - * the promise if the token could not be verified. If checkRevoked is set to true, - * verifies if the session corresponding to the ID token was revoked. If the corresponding - * user's session was invalidated, an auth/id-token-revoked error is thrown. If not specified - * the check is not applied. - * - * @param {string} idToken The JWT to verify. - * @param {boolean=} checkRevoked Whether to check if the ID token is revoked. - * @return {Promise} A Promise that will be fulfilled after a successful - * verification. - */ - public verifyIdToken(idToken: string, checkRevoked = false): Promise { - return super.verifyIdToken(idToken, checkRevoked) - .then((decodedClaims) => { - // Validate tenant ID. - if (decodedClaims.firebase.tenant !== this.tenantId) { - throw new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); - } - return decodedClaims; - }); - } - - /** - * Creates a new Firebase session cookie with the specified options that can be used for - * session management (set as a server side session cookie with custom cookie policy). - * The session cookie JWT will have the same payload claims as the provided ID token. - * - * @param {string} idToken The Firebase ID token to exchange for a session cookie. - * @param {SessionCookieOptions} sessionCookieOptions The session cookie options which includes - * custom session duration. - * - * @return {Promise} A promise that resolves on success with the created session cookie. - */ - public createSessionCookie( - idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { - // Validate arguments before processing. - if (!validator.isNonEmptyString(idToken)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ID_TOKEN)); - } - if (!validator.isNonNullObject(sessionCookieOptions) || - !validator.isNumber(sessionCookieOptions.expiresIn)) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); - } - // This will verify the ID token and then match the tenant ID before creating the session cookie. - return this.verifyIdToken(idToken) - .then(() => { - return super.createSessionCookie(idToken, sessionCookieOptions); - }); - } - - /** - * Verifies a Firebase session cookie. Returns a Promise with the tokens claims. Rejects - * the promise if the token could not be verified. If checkRevoked is set to true, - * verifies if the session corresponding to the session cookie was revoked. If the corresponding - * user's session was invalidated, an auth/session-cookie-revoked error is thrown. If not - * specified the check is not performed. - * - * @param {string} sessionCookie The session cookie to verify. - * @param {boolean=} checkRevoked Whether to check if the session cookie is revoked. - * @return {Promise} A Promise that will be fulfilled after a successful - * verification. - */ - public verifySessionCookie( - sessionCookie: string, checkRevoked = false): Promise { - return super.verifySessionCookie(sessionCookie, checkRevoked) - .then((decodedClaims) => { - if (decodedClaims.firebase.tenant !== this.tenantId) { - throw new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); - } - return decodedClaims; - }); - } -} - +import { BaseAuth } from './base-auth'; /** * Auth service bound to the provided app. * An Auth instance can have multiple tenants. */ -export class Auth extends BaseAuth implements AuthInterface { +export class Auth extends BaseAuth { private readonly tenantManager_: TenantManager; - private readonly app_: FirebaseApp; + private readonly app_: App; /** - * @param {object} app The app for this Auth service. + * @param app The app for this Auth service. * @constructor + * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { super(app, new AuthRequestHandler(app)); this.app_ = app; this.tenantManager_ = new TenantManager(app); @@ -821,9 +43,9 @@ export class Auth extends BaseAuth implements AuthInterface /** * Returns the app associated with this Auth instance. * - * @return {FirebaseApp} The app associated with this Auth instance. + * @return The app associated with this Auth instance. */ - get app(): FirebaseApp { + get app(): App { return this.app_; } diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts new file mode 100644 index 0000000000..502e4b2f1b --- /dev/null +++ b/src/auth/base-auth.ts @@ -0,0 +1,757 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App } from '../app'; +import { FirebaseArrayIndexError } from '../firebase-namespace-api'; +import { AuthClientErrorCode, ErrorInfo, FirebaseAuthError } from '../utils/error'; +import * as validator from '../utils/validator'; + +import { AbstractAuthRequestHandler, useEmulator } from './auth-api-request'; +import { FirebaseTokenGenerator, EmulatedSigner, cryptoSignerFromApp } from './token-generator'; +import { + FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier, ALGORITHM_RS256, + DecodedIdToken, +} from './token-verifier'; +import { + AuthProviderConfig, SAMLAuthProviderConfig, AuthProviderConfigFilter, ListProviderConfigResults, + SAMLConfig, OIDCConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, + UpdateAuthProviderRequest, OIDCAuthProviderConfig, CreateRequest, UpdateRequest, +} from './auth-config'; +import { UserRecord } from './user-record'; +import { + UserIdentifier, isUidIdentifier, isEmailIdentifier, isPhoneIdentifier, isProviderIdentifier, +} from './identifier'; +import { UserImportOptions, UserImportRecord, UserImportResult } from './user-import-builder'; +import { ActionCodeSettings } from './action-code-settings-builder'; + +/** Represents the result of the {@link auth.Auth.getUsers} API. */ +export interface GetUsersResult { + /** + * Set of user records, corresponding to the set of users that were + * requested. Only users that were found are listed here. The result set is + * unordered. + */ + users: UserRecord[]; + + /** Set of identifiers that were requested, but not found. */ + notFound: UserIdentifier[]; +} + +/** + * Interface representing the object returned from a + * {@link auth.Auth.listUsers `listUsers()`} operation. Contains the list + * of users for the current batch and the next page token if available. + */ +export interface ListUsersResult { + + /** + * The list of {@link auth.UserRecord `UserRecord`} objects for the + * current downloaded batch. + */ + users: UserRecord[]; + + /** + * The next page token if available. This is needed for the next batch download. + */ + pageToken?: string; +} + +/** + * Represents the result of the + * {@link auth.Auth.deleteUsers `deleteUsers()`} + * API. + */ +export interface DeleteUsersResult { + /** + * The number of user records that failed to be deleted (possibly zero). + */ + failureCount: number; + + /** + * The number of users that were deleted successfully (possibly zero). + * Users that did not exist prior to calling `deleteUsers()` are + * considered to be successfully deleted. + */ + successCount: number; + + /** + * A list of `FirebaseArrayIndexError` instances describing the errors that + * were encountered during the deletion. Length of this list is equal to + * the return value of [`failureCount`](#failureCount). + */ + errors: FirebaseArrayIndexError[]; +} + +/** + * Interface representing the session cookie options needed for the + * {@link auth.Auth.createSessionCookie `createSessionCookie()`} method. + */ +export interface SessionCookieOptions { + + /** + * The session cookie custom expiration in milliseconds. The minimum allowed is + * 5 minutes and the maxium allowed is 2 weeks. + */ + expiresIn: number; +} + +/** + * Base Auth class. Mainly used for user management APIs. + */ +export class BaseAuth { + + /** @internal */ + protected readonly tokenGenerator: FirebaseTokenGenerator; + /** @internal */ + protected readonly idTokenVerifier: FirebaseTokenVerifier; + /** @internal */ + protected readonly sessionCookieVerifier: FirebaseTokenVerifier; + + /** + * The BaseAuth class constructor. + * + * @param app The FirebaseApp to associate with this Auth instance. + * @param authRequestHandler The RPC request handler for this instance. + * @param tokenGenerator Optional token generator. If not specified, a + * (non-tenant-aware) instance will be created. Use this paramter to + * specify a tenant-aware tokenGenerator. + * @constructor + * @internal + */ + constructor( + app: App, + /** @internal */protected readonly authRequestHandler: AbstractAuthRequestHandler, + tokenGenerator?: FirebaseTokenGenerator) { + if (tokenGenerator) { + this.tokenGenerator = tokenGenerator; + } else { + const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); + this.tokenGenerator = new FirebaseTokenGenerator(cryptoSigner); + } + + this.sessionCookieVerifier = createSessionCookieVerifier(app); + this.idTokenVerifier = createIdTokenVerifier(app); + } + + /** + * Creates a new custom token that can be sent back to a client to use with + * signInWithCustomToken(). + * + * @param {string} uid The uid to use as the JWT subject. + * @param {object=} developerClaims Optional additional claims to include in the JWT payload. + * + * @return {Promise} A JWT for the provided payload. + */ + public createCustomToken(uid: string, developerClaims?: object): Promise { + return this.tokenGenerator.createCustomToken(uid, developerClaims); + } + + /** + * Verifies a JWT auth token. Returns a Promise with the tokens claims. Rejects + * the promise if the token could not be verified. If checkRevoked is set to true, + * verifies if the session corresponding to the ID token was revoked. If the corresponding + * user's session was invalidated, an auth/id-token-revoked error is thrown. If not specified + * the check is not applied. + * + * @param {string} idToken The JWT to verify. + * @param {boolean=} checkRevoked Whether to check if the ID token is revoked. + * @return {Promise} A Promise that will be fulfilled after a successful + * verification. + */ + public verifyIdToken(idToken: string, checkRevoked = false): Promise { + return this.idTokenVerifier.verifyJWT(idToken) + .then((decodedIdToken: DecodedIdToken) => { + // Whether to check if the token was revoked. + if (!checkRevoked) { + return decodedIdToken; + } + return this.verifyDecodedJWTNotRevoked( + decodedIdToken, + AuthClientErrorCode.ID_TOKEN_REVOKED); + }); + } + + /** + * Looks up the user identified by the provided user id and returns a promise that is + * fulfilled with a user record for the given user if that user is found. + * + * @param {string} uid The uid of the user to look up. + * @return {Promise} A promise that resolves with the corresponding user record. + */ + public getUser(uid: string): Promise { + return this.authRequestHandler.getAccountInfoByUid(uid) + .then((response: any) => { + // Returns the user record populated with server response. + return new UserRecord(response.users[0]); + }); + } + + /** + * Looks up the user identified by the provided email and returns a promise that is + * fulfilled with a user record for the given user if that user is found. + * + * @param {string} email The email of the user to look up. + * @return {Promise} A promise that resolves with the corresponding user record. + */ + public getUserByEmail(email: string): Promise { + return this.authRequestHandler.getAccountInfoByEmail(email) + .then((response: any) => { + // Returns the user record populated with server response. + return new UserRecord(response.users[0]); + }); + } + + /** + * Looks up the user identified by the provided phone number and returns a promise that is + * fulfilled with a user record for the given user if that user is found. + * + * @param {string} phoneNumber The phone number of the user to look up. + * @return {Promise} A promise that resolves with the corresponding user record. + */ + public getUserByPhoneNumber(phoneNumber: string): Promise { + return this.authRequestHandler.getAccountInfoByPhoneNumber(phoneNumber) + .then((response: any) => { + // Returns the user record populated with server response. + return new UserRecord(response.users[0]); + }); + } + + /** + * Gets the user data corresponding to the specified identifiers. + * + * There are no ordering guarantees; in particular, the nth entry in the result list is not + * guaranteed to correspond to the nth entry in the input parameters list. + * + * Only a maximum of 100 identifiers may be supplied. If more than 100 identifiers are supplied, + * this method will immediately throw a FirebaseAuthError. + * + * @param identifiers The identifiers used to indicate which user records should be returned. Must + * have <= 100 entries. + * @return {Promise} A promise that resolves to the corresponding user records. + * @throws FirebaseAuthError If any of the identifiers are invalid or if more than 100 + * identifiers are specified. + */ + public getUsers(identifiers: UserIdentifier[]): Promise { + if (!validator.isArray(identifiers)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, '`identifiers` parameter must be an array'); + } + return this.authRequestHandler + .getAccountInfoByIdentifiers(identifiers) + .then((response: any) => { + /** + * Checks if the specified identifier is within the list of + * UserRecords. + */ + const isUserFound = ((id: UserIdentifier, userRecords: UserRecord[]): boolean => { + return !!userRecords.find((userRecord) => { + if (isUidIdentifier(id)) { + return id.uid === userRecord.uid; + } else if (isEmailIdentifier(id)) { + return id.email === userRecord.email; + } else if (isPhoneIdentifier(id)) { + return id.phoneNumber === userRecord.phoneNumber; + } else if (isProviderIdentifier(id)) { + const matchingUserInfo = userRecord.providerData.find((userInfo) => { + return id.providerId === userInfo.providerId; + }); + return !!matchingUserInfo && id.providerUid === matchingUserInfo.uid; + } else { + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'Unhandled identifier type'); + } + }); + }); + + const users = response.users ? response.users.map((user: any) => new UserRecord(user)) : []; + const notFound = identifiers.filter((id) => !isUserFound(id, users)); + + return { users, notFound }; + }); + } + + /** + * Exports a batch of user accounts. Batch size is determined by the maxResults argument. + * Starting point of the batch is determined by the pageToken argument. + * + * @param {number=} maxResults The page size, 1000 if undefined. This is also the maximum + * allowed limit. + * @param {string=} pageToken The next page token. If not specified, returns users starting + * without any offset. + * @return {Promise<{users: UserRecord[], pageToken?: string}>} A promise that resolves with + * the current batch of downloaded users and the next page token. For the last page, an + * empty list of users and no page token are returned. + */ + public listUsers(maxResults?: number, pageToken?: string): Promise { + return this.authRequestHandler.downloadAccount(maxResults, pageToken) + .then((response: any) => { + // List of users to return. + const users: UserRecord[] = []; + // Convert each user response to a UserRecord. + response.users.forEach((userResponse: any) => { + users.push(new UserRecord(userResponse)); + }); + // Return list of user records and the next page token if available. + const result = { + users, + pageToken: response.nextPageToken, + }; + // Delete result.pageToken if undefined. + if (typeof result.pageToken === 'undefined') { + delete result.pageToken; + } + return result; + }); + } + + /** + * Creates a new user with the properties provided. + * + * @param {CreateRequest} properties The properties to set on the new user record to be created. + * @return {Promise} A promise that resolves with the newly created user record. + */ + public createUser(properties: CreateRequest): Promise { + return this.authRequestHandler.createNewAccount(properties) + .then((uid) => { + // Return the corresponding user record. + return this.getUser(uid); + }) + .catch((error) => { + if (error.code === 'auth/user-not-found') { + // Something must have happened after creating the user and then retrieving it. + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'Unable to create the user record provided.'); + } + throw error; + }); + } + + /** + * Deletes the user identified by the provided user id and returns a promise that is + * fulfilled when the user is found and successfully deleted. + * + * @param {string} uid The uid of the user to delete. + * @return {Promise} A promise that resolves when the user is successfully deleted. + */ + public deleteUser(uid: string): Promise { + return this.authRequestHandler.deleteAccount(uid) + .then(() => { + // Return nothing on success. + }); + } + + public deleteUsers(uids: string[]): Promise { + if (!validator.isArray(uids)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, '`uids` parameter must be an array'); + } + return this.authRequestHandler.deleteAccounts(uids, /*force=*/true) + .then((batchDeleteAccountsResponse) => { + const result: DeleteUsersResult = { + failureCount: 0, + successCount: uids.length, + errors: [], + }; + + if (!validator.isNonEmptyArray(batchDeleteAccountsResponse.errors)) { + return result; + } + + result.failureCount = batchDeleteAccountsResponse.errors.length; + result.successCount = uids.length - batchDeleteAccountsResponse.errors.length; + result.errors = batchDeleteAccountsResponse.errors.map((batchDeleteErrorInfo) => { + if (batchDeleteErrorInfo.index === undefined) { + throw new FirebaseAuthError( + AuthClientErrorCode.INTERNAL_ERROR, + 'Corrupt BatchDeleteAccountsResponse detected'); + } + + const errMsgToError = (msg?: string): FirebaseAuthError => { + // We unconditionally set force=true, so the 'NOT_DISABLED' error + // should not be possible. + const code = msg && msg.startsWith('NOT_DISABLED') ? + AuthClientErrorCode.USER_NOT_DISABLED : AuthClientErrorCode.INTERNAL_ERROR; + return new FirebaseAuthError(code, batchDeleteErrorInfo.message); + }; + + return { + index: batchDeleteErrorInfo.index, + error: errMsgToError(batchDeleteErrorInfo.message), + }; + }); + + return result; + }); + } + + /** + * Updates an existing user with the properties provided. + * + * @param {string} uid The uid identifier of the user to update. + * @param {UpdateRequest} properties The properties to update on the existing user. + * @return {Promise} A promise that resolves with the modified user record. + */ + public updateUser(uid: string, properties: UpdateRequest): Promise { + return this.authRequestHandler.updateExistingAccount(uid, properties) + .then((existingUid) => { + // Return the corresponding user record. + return this.getUser(existingUid); + }); + } + + /** + * Sets additional developer claims on an existing user identified by the provided UID. + * + * @param {string} uid The user to edit. + * @param {object} customUserClaims The developer claims to set. + * @return {Promise} A promise that resolves when the operation completes + * successfully. + */ + public setCustomUserClaims(uid: string, customUserClaims: object | null): Promise { + return this.authRequestHandler.setCustomUserClaims(uid, customUserClaims) + .then(() => { + // Return nothing on success. + }); + } + + /** + * Revokes all refresh tokens for the specified user identified by the provided UID. + * In addition to revoking all refresh tokens for a user, all ID tokens issued before + * revocation will also be revoked on the Auth backend. Any request with an ID token + * generated before revocation will be rejected with a token expired error. + * + * @param {string} uid The user whose tokens are to be revoked. + * @return {Promise} A promise that resolves when the operation completes + * successfully. + */ + public revokeRefreshTokens(uid: string): Promise { + return this.authRequestHandler.revokeRefreshTokens(uid) + .then(() => { + // Return nothing on success. + }); + } + + /** + * Imports the list of users provided to Firebase Auth. This is useful when + * migrating from an external authentication system without having to use the Firebase CLI SDK. + * At most, 1000 users are allowed to be imported one at a time. + * When importing a list of password users, UserImportOptions are required to be specified. + * + * @param {UserImportRecord[]} users The list of user records to import to Firebase Auth. + * @param {UserImportOptions=} options The user import options, required when the users provided + * include password credentials. + * @return {Promise} A promise that resolves when the operation completes + * with the result of the import. This includes the number of successful imports, the number + * of failed uploads and their corresponding errors. + */ + public importUsers( + users: UserImportRecord[], options?: UserImportOptions): Promise { + return this.authRequestHandler.uploadAccount(users, options); + } + + /** + * Creates a new Firebase session cookie with the specified options that can be used for + * session management (set as a server side session cookie with custom cookie policy). + * The session cookie JWT will have the same payload claims as the provided ID token. + * + * @param {string} idToken The Firebase ID token to exchange for a session cookie. + * @param {SessionCookieOptions} sessionCookieOptions The session cookie options which includes + * custom session duration. + * + * @return {Promise} A promise that resolves on success with the created session cookie. + */ + public createSessionCookie( + idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { + // Return rejected promise if expiresIn is not available. + if (!validator.isNonNullObject(sessionCookieOptions) || + !validator.isNumber(sessionCookieOptions.expiresIn)) { + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); + } + return this.authRequestHandler.createSessionCookie( + idToken, sessionCookieOptions.expiresIn); + } + + /** + * Verifies a Firebase session cookie. Returns a Promise with the tokens claims. Rejects + * the promise if the token could not be verified. If checkRevoked is set to true, + * verifies if the session corresponding to the session cookie was revoked. If the corresponding + * user's session was invalidated, an auth/session-cookie-revoked error is thrown. If not + * specified the check is not performed. + * + * @param {string} sessionCookie The session cookie to verify. + * @param {boolean=} checkRevoked Whether to check if the session cookie is revoked. + * @return {Promise} A Promise that will be fulfilled after a successful + * verification. + */ + public verifySessionCookie( + sessionCookie: string, checkRevoked = false): Promise { + return this.sessionCookieVerifier.verifyJWT(sessionCookie) + .then((decodedIdToken: DecodedIdToken) => { + // Whether to check if the token was revoked. + if (!checkRevoked) { + return decodedIdToken; + } + return this.verifyDecodedJWTNotRevoked( + decodedIdToken, + AuthClientErrorCode.SESSION_COOKIE_REVOKED); + }); + } + + /** + * Generates the out of band email action link for password reset flows for the + * email specified using the action code settings provided. + * Returns a promise that resolves with the generated link. + * + * @param {string} email The email of the user whose password is to be reset. + * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether + * the link is to be handled by a mobile app and the additional state information to be passed in the + * deep link, etc. + * @return {Promise} A promise that resolves with the password reset link. + */ + public generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { + return this.authRequestHandler.getEmailActionLink('PASSWORD_RESET', email, actionCodeSettings); + } + + /** + * Generates the out of band email action link for email verification flows for the + * email specified using the action code settings provided. + * Returns a promise that resolves with the generated link. + * + * @param {string} email The email of the user to be verified. + * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether + * the link is to be handled by a mobile app and the additional state information to be passed in the + * deep link, etc. + * @return {Promise} A promise that resolves with the email verification link. + */ + public generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { + return this.authRequestHandler.getEmailActionLink('VERIFY_EMAIL', email, actionCodeSettings); + } + + /** + * Generates the out of band email action link for email link sign-in flows for the + * email specified using the action code settings provided. + * Returns a promise that resolves with the generated link. + * + * @param {string} email The email of the user signing in. + * @param {ActionCodeSettings} actionCodeSettings The required action code setings which defines whether + * the link is to be handled by a mobile app and the additional state information to be passed in the + * deep link, etc. + * @return {Promise} A promise that resolves with the email sign-in link. + */ + public generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise { + return this.authRequestHandler.getEmailActionLink('EMAIL_SIGNIN', email, actionCodeSettings); + } + + /** + * Returns the list of existing provider configuation matching the filter provided. + * At most, 100 provider configs are allowed to be imported at a time. + * + * @param {AuthProviderConfigFilter} options The provider config filter to apply. + * @return {Promise} A promise that resolves with the list of provider configs + * meeting the filter requirements. + */ + public listProviderConfigs(options: AuthProviderConfigFilter): Promise { + const processResponse = (response: any, providerConfigs: AuthProviderConfig[]): ListProviderConfigResults => { + // Return list of provider configuration and the next page token if available. + const result: ListProviderConfigResults = { + providerConfigs, + }; + // Delete result.pageToken if undefined. + if (Object.prototype.hasOwnProperty.call(response, 'nextPageToken')) { + result.pageToken = response.nextPageToken; + } + return result; + }; + if (options && options.type === 'oidc') { + return this.authRequestHandler.listOAuthIdpConfigs(options.maxResults, options.pageToken) + .then((response: any) => { + // List of provider configurations to return. + const providerConfigs: OIDCConfig[] = []; + // Convert each provider config response to a OIDCConfig. + response.oauthIdpConfigs.forEach((configResponse: any) => { + providerConfigs.push(new OIDCConfig(configResponse)); + }); + // Return list of provider configuration and the next page token if available. + return processResponse(response, providerConfigs); + }); + } else if (options && options.type === 'saml') { + return this.authRequestHandler.listInboundSamlConfigs(options.maxResults, options.pageToken) + .then((response: any) => { + // List of provider configurations to return. + const providerConfigs: SAMLConfig[] = []; + // Convert each provider config response to a SAMLConfig. + response.inboundSamlConfigs.forEach((configResponse: any) => { + providerConfigs.push(new SAMLConfig(configResponse)); + }); + // Return list of provider configuration and the next page token if available. + return processResponse(response, providerConfigs); + }); + } + return Promise.reject( + new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + '"AuthProviderConfigFilter.type" must be either "saml" or "oidc"')); + } + + /** + * Looks up an Auth provider configuration by ID. + * Returns a promise that resolves with the provider configuration corresponding to the provider ID specified. + * + * @param {string} providerId The provider ID corresponding to the provider config to return. + * @return {Promise} + */ + public getProviderConfig(providerId: string): Promise { + if (OIDCConfig.isProviderId(providerId)) { + return this.authRequestHandler.getOAuthIdpConfig(providerId) + .then((response: OIDCConfigServerResponse) => { + return new OIDCConfig(response); + }); + } else if (SAMLConfig.isProviderId(providerId)) { + return this.authRequestHandler.getInboundSamlConfig(providerId) + .then((response: SAMLConfigServerResponse) => { + return new SAMLConfig(response); + }); + } + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + } + + /** + * Deletes the provider configuration corresponding to the provider ID passed. + * + * @param {string} providerId The provider ID corresponding to the provider config to delete. + * @return {Promise} A promise that resolves on completion. + */ + public deleteProviderConfig(providerId: string): Promise { + if (OIDCConfig.isProviderId(providerId)) { + return this.authRequestHandler.deleteOAuthIdpConfig(providerId); + } else if (SAMLConfig.isProviderId(providerId)) { + return this.authRequestHandler.deleteInboundSamlConfig(providerId); + } + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + } + + /** + * Returns a promise that resolves with the updated AuthProviderConfig when the provider configuration corresponding + * to the provider ID specified is updated with the specified configuration. + * + * @param {string} providerId The provider ID corresponding to the provider config to update. + * @param {UpdateAuthProviderRequest} updatedConfig The updated configuration. + * @return {Promise} A promise that resolves with the updated provider configuration. + */ + public updateProviderConfig( + providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise { + if (!validator.isNonNullObject(updatedConfig)) { + return Promise.reject(new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + 'Request is missing "UpdateAuthProviderRequest" configuration.', + )); + } + if (OIDCConfig.isProviderId(providerId)) { + return this.authRequestHandler.updateOAuthIdpConfig(providerId, updatedConfig) + .then((response) => { + return new OIDCConfig(response); + }); + } else if (SAMLConfig.isProviderId(providerId)) { + return this.authRequestHandler.updateInboundSamlConfig(providerId, updatedConfig) + .then((response) => { + return new SAMLConfig(response); + }); + } + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + } + + /** + * Returns a promise that resolves with the newly created AuthProviderConfig when the new provider configuration is + * created. + * @param {AuthProviderConfig} config The provider configuration to create. + * @return {Promise} A promise that resolves with the created provider configuration. + */ + public createProviderConfig(config: AuthProviderConfig): Promise { + if (!validator.isNonNullObject(config)) { + return Promise.reject(new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + 'Request is missing "AuthProviderConfig" configuration.', + )); + } + if (OIDCConfig.isProviderId(config.providerId)) { + return this.authRequestHandler.createOAuthIdpConfig(config as OIDCAuthProviderConfig) + .then((response) => { + return new OIDCConfig(response); + }); + } else if (SAMLConfig.isProviderId(config.providerId)) { + return this.authRequestHandler.createInboundSamlConfig(config as SAMLAuthProviderConfig) + .then((response) => { + return new SAMLConfig(response); + }); + } + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID)); + } + + /** + * Verifies the decoded Firebase issued JWT is not revoked. Returns a promise that resolves + * with the decoded claims on success. Rejects the promise with revocation error if revoked. + * + * @param {DecodedIdToken} decodedIdToken The JWT's decoded claims. + * @param {ErrorInfo} revocationErrorInfo The revocation error info to throw on revocation + * detection. + * @return {Promise} A Promise that will be fulfilled after a successful + * verification. + */ + private verifyDecodedJWTNotRevoked( + decodedIdToken: DecodedIdToken, revocationErrorInfo: ErrorInfo): Promise { + // Get tokens valid after time for the corresponding user. + return this.getUser(decodedIdToken.sub) + .then((user: UserRecord) => { + // If no tokens valid after time available, token is not revoked. + if (user.tokensValidAfterTime) { + // Get the ID token authentication time and convert to milliseconds UTC. + const authTimeUtc = decodedIdToken.auth_time * 1000; + // Get user tokens valid after time in milliseconds UTC. + const validSinceUtc = new Date(user.tokensValidAfterTime).getTime(); + // Check if authentication time is older than valid since time. + if (authTimeUtc < validSinceUtc) { + throw new FirebaseAuthError(revocationErrorInfo); + } + } + // All checks above passed. Return the decoded token. + return decodedIdToken; + }); + } + + /** + * Enable or disable ID token verification. This is used to safely short-circuit token verification with the + * Auth emulator. When disabled ONLY unsigned tokens will pass verification, production tokens will not pass. + * + * WARNING: This is a dangerous method that will compromise your app's security and break your app in + * production. Developers should never call this method, it is for internal testing use only. + * + * @internal + */ + // @ts-expect-error: this method appears unused but is used privately. + private setJwtVerificationEnabled(enabled: boolean): void { + if (!enabled && !useEmulator()) { + // We only allow verification to be disabled in conjunction with + // the emulator environment variable. + throw new Error('This method is only available when connected to the Authentication emulator.'); + } + + const algorithm = enabled ? ALGORITHM_RS256 : 'none'; + this.idTokenVerifier.setAlgorithm(algorithm); + this.sessionCookieVerifier.setAlgorithm(algorithm); + } +} \ No newline at end of file diff --git a/src/auth/identifier.ts b/src/auth/identifier.ts index b9e93b1fc0..709ff81a06 100644 --- a/src/auth/identifier.ts +++ b/src/auth/identifier.ts @@ -14,13 +14,48 @@ * limitations under the License. */ -import { auth } from './index'; +/** + * Used for looking up an account by uid. + * + * See auth.getUsers() + */ +export interface UidIdentifier { + uid: string; +} + +/** + * Used for looking up an account by email. + * + * See auth.getUsers() + */ +export interface EmailIdentifier { + email: string; +} + +/** + * Used for looking up an account by phone number. + * + * See auth.getUsers() + */ +export interface PhoneIdentifier { + phoneNumber: string; +} + +/** + * Used for looking up an account by federated provider. + * + * See auth.getUsers() + */ +export interface ProviderIdentifier { + providerId: string; + providerUid: string; +} -import UserIdentifier = auth.UserIdentifier; -import UidIdentifier = auth.UidIdentifier; -import EmailIdentifier = auth.EmailIdentifier; -import PhoneIdentifier = auth.PhoneIdentifier; -import ProviderIdentifier = auth.ProviderIdentifier; +/** + * Identifies a user to be looked up. + */ +export type UserIdentifier = + UidIdentifier | EmailIdentifier | PhoneIdentifier | ProviderIdentifier; /* * User defined type guards. See diff --git a/src/auth/index.ts b/src/auth/index.ts index 1ba9b56af5..91609e66f7 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -14,2050 +14,81 @@ * limitations under the License. */ -import { app, FirebaseArrayIndexError } from '../firebase-namespace-api'; - -/** - * Gets the {@link auth.Auth `Auth`} service for the default app or a - * given app. - * - * `admin.auth()` can be called with no arguments to access the default app's - * {@link auth.Auth `Auth`} service or as `admin.auth(app)` to access the - * {@link auth.Auth `Auth`} service associated with a specific app. - * - * @example - * ```javascript - * // Get the Auth service for the default app - * var defaultAuth = admin.auth(); - * ``` - * - * @example - * ```javascript - * // Get the Auth service for a given app - * var otherAuth = admin.auth(otherApp); - * ``` - * - */ -export declare function auth(app?: app.App): auth.Auth; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace auth { - /** - * Interface representing a user's metadata. - */ - export interface UserMetadata { - - /** - * The date the user last signed in, formatted as a UTC string. - */ - lastSignInTime: string; - - /** - * The date the user was created, formatted as a UTC string. - */ - creationTime: string; - - /** - * The time at which the user was last active (ID token refreshed), - * formatted as a UTC Date string (eg 'Sat, 03 Feb 2001 04:05:06 GMT'). - * Returns null if the user was never active. - */ - lastRefreshTime?: string | null; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; - } - - /** - * Interface representing a user's info from a third-party identity provider - * such as Google or Facebook. - */ - export interface UserInfo { - - /** - * The user identifier for the linked provider. - */ - uid: string; - - /** - * The display name for the linked provider. - */ - displayName: string; - - /** - * The email for the linked provider. - */ - email: string; - - /** - * The phone number for the linked provider. - */ - phoneNumber: string; - - /** - * The photo URL for the linked provider. - */ - photoURL: string; - - /** - * The linked provider ID (for example, "google.com" for the Google provider). - */ - providerId: string; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; - } - - /** - * Interface representing the common properties of a user enrolled second factor. - */ - export interface MultiFactorInfo { - - /** - * The ID of the enrolled second factor. This ID is unique to the user. - */ - uid: string; - - /** - * The optional display name of the enrolled second factor. - */ - displayName?: string; - - /** - * The optional date the second factor was enrolled, formatted as a UTC string. - */ - enrollmentTime?: string; - - /** - * The type identifier of the second factor. For SMS second factors, this is `phone`. - */ - factorId: string; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; - } - - /** - * Interface representing a phone specific user enrolled second factor. - */ - export interface PhoneMultiFactorInfo extends MultiFactorInfo { - - /** - * The phone number associated with a phone second factor. - */ - phoneNumber: string; - } - - /** - * Interface representing a user. - */ - export interface UserRecord { - - /** - * The user's `uid`. - */ - uid: string; - - /** - * The user's primary email, if set. - */ - email?: string; - - /** - * Whether or not the user's primary email is verified. - */ - emailVerified: boolean; - - /** - * The user's display name. - */ - displayName?: string; - - /** - * The user's primary phone number, if set. - */ - phoneNumber?: string; - - /** - * The user's photo URL. - */ - photoURL?: string; - - /** - * Whether or not the user is disabled: `true` for disabled; `false` for - * enabled. - */ - disabled: boolean; - - /** - * Additional metadata about the user. - */ - metadata: UserMetadata; - - /** - * An array of providers (for example, Google, Facebook) linked to the user. - */ - providerData: UserInfo[]; - - /** - * The user's hashed password (base64-encoded), only if Firebase Auth hashing - * algorithm (SCRYPT) is used. If a different hashing algorithm had been used - * when uploading this user, as is typical when migrating from another Auth - * system, this will be an empty string. If no password is set, this is - * null. This is only available when the user is obtained from - * {@link auth.Auth.listUsers `listUsers()`}. - * - */ - passwordHash?: string; - - /** - * The user's password salt (base64-encoded), only if Firebase Auth hashing - * algorithm (SCRYPT) is used. If a different hashing algorithm had been used to - * upload this user, typical when migrating from another Auth system, this will - * be an empty string. If no password is set, this is null. This is only - * available when the user is obtained from - * {@link auth.Auth.listUsers `listUsers()`}. - * - */ - passwordSalt?: string; - - /** - * The user's custom claims object if available, typically used to define - * user roles and propagated to an authenticated user's ID token. - * This is set via - * {@link auth.Auth.setCustomUserClaims `setCustomUserClaims()`} - */ - customClaims?: { [key: string]: any }; - - /** - * The date the user's tokens are valid after, formatted as a UTC string. - * This is updated every time the user's refresh token are revoked either - * from the {@link auth.Auth.revokeRefreshTokens `revokeRefreshTokens()`} - * API or from the Firebase Auth backend on big account changes (password - * resets, password or email updates, etc). - */ - tokensValidAfterTime?: string; - - /** - * The ID of the tenant the user belongs to, if available. - */ - tenantId?: string | null; - - /** - * The multi-factor related properties for the current user, if available. - */ - multiFactor?: MultiFactorSettings; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; - } - - /** - * The multi-factor related user settings. - */ - export interface MultiFactorSettings { - /** - * List of second factors enrolled with the current user. - * Currently only phone second factors are supported. - */ - enrolledFactors: MultiFactorInfo[]; - - /** - * @return A JSON-serializable representation of this multi-factor object. - */ - toJSON(): object; - } - - /** - * The multi-factor related user settings for create operations. - */ - export interface MultiFactorCreateSettings { - - /** - * The created user's list of enrolled second factors. - */ - enrolledFactors: CreateMultiFactorInfoRequest[]; - } - - /** - * The multi-factor related user settings for update operations. - */ - export interface MultiFactorUpdateSettings { - - /** - * The updated list of enrolled second factors. The provided list overwrites the user's - * existing list of second factors. - * When null is passed, all of the user's existing second factors are removed. - */ - enrolledFactors: UpdateMultiFactorInfoRequest[] | null; - } - - /** - * Interface representing common properties of a user enrolled second factor - * for an `UpdateRequest`. - */ - export interface UpdateMultiFactorInfoRequest { - - /** - * The ID of the enrolled second factor. This ID is unique to the user. When not provided, - * a new one is provisioned by the Auth server. - */ - uid?: string; - - /** - * The optional display name for an enrolled second factor. - */ - displayName?: string; - - /** - * The optional date the second factor was enrolled, formatted as a UTC string. - */ - enrollmentTime?: string; - - /** - * The type identifier of the second factor. For SMS second factors, this is `phone`. - */ - factorId: string; - } - - /** - * Interface representing a phone specific user enrolled second factor - * for an `UpdateRequest`. - */ - export interface UpdatePhoneMultiFactorInfoRequest extends UpdateMultiFactorInfoRequest { - - /** - * The phone number associated with a phone second factor. - */ - phoneNumber: string; - } - - /** - * Interface representing the properties to update on the provided user. - */ - export interface UpdateRequest { - - /** - * Whether or not the user is disabled: `true` for disabled; - * `false` for enabled. - */ - disabled?: boolean; - - /** - * The user's display name. - */ - displayName?: string | null; - - /** - * The user's primary email. - */ - email?: string; - - /** - * Whether or not the user's primary email is verified. - */ - emailVerified?: boolean; - - /** - * The user's unhashed password. - */ - password?: string; - - /** - * The user's primary phone number. - */ - phoneNumber?: string | null; - - /** - * The user's photo URL. - */ - photoURL?: string | null; - - /** - * The user's updated multi-factor related properties. - */ - multiFactor?: MultiFactorUpdateSettings; - } - - /** - * Interface representing base properties of a user enrolled second factor for a - * `CreateRequest`. - */ - export interface CreateMultiFactorInfoRequest { - - /** - * The optional display name for an enrolled second factor. - */ - displayName?: string; - - /** - * The type identifier of the second factor. For SMS second factors, this is `phone`. - */ - factorId: string; - } - - /** - * Interface representing a phone specific user enrolled second factor for a - * `CreateRequest`. - */ - export interface CreatePhoneMultiFactorInfoRequest extends CreateMultiFactorInfoRequest { - - /** - * The phone number associated with a phone second factor. - */ - phoneNumber: string; - } - - /** - * Interface representing the properties to set on a new user record to be - * created. - */ - export interface CreateRequest extends UpdateRequest { - - /** - * The user's `uid`. - */ - uid?: string; - - /** - * The user's multi-factor related properties. - */ - multiFactor?: MultiFactorCreateSettings; - } - - /** - * Interface representing a decoded Firebase ID token, returned from the - * {@link auth.Auth.verifyIdToken `verifyIdToken()`} method. - * - * Firebase ID tokens are OpenID Connect spec-compliant JSON Web Tokens (JWTs). - * See the - * [ID Token section of the OpenID Connect spec](http://openid.net/specs/openid-connect-core-1_0.html#IDToken) - * for more information about the specific properties below. - */ - export interface DecodedIdToken { - - /** - * The audience for which this token is intended. - * - * This value is a string equal to your Firebase project ID, the unique - * identifier for your Firebase project, which can be found in [your project's - * settings](https://console.firebase.google.com/project/_/settings/general/android:com.random.android). - */ - aud: string; - - /** - * Time, in seconds since the Unix epoch, when the end-user authentication - * occurred. - * - * This value is not set when this particular ID token was created, but when the - * user initially logged in to this session. In a single session, the Firebase - * SDKs will refresh a user's ID tokens every hour. Each ID token will have a - * different [`iat`](#iat) value, but the same `auth_time` value. - */ - auth_time: number; - - /** - * The email of the user to whom the ID token belongs, if available. - */ - email?: string; - - /** - * Whether or not the email of the user to whom the ID token belongs is - * verified, provided the user has an email. - */ - email_verified?: boolean; - - /** - * The ID token's expiration time, in seconds since the Unix epoch. That is, the - * time at which this ID token expires and should no longer be considered valid. - * - * The Firebase SDKs transparently refresh ID tokens every hour, issuing a new - * ID token with up to a one hour expiration. - */ - exp: number; - - /** - * Information about the sign in event, including which sign in provider was - * used and provider-specific identity details. - * - * This data is provided by the Firebase Authentication service and is a - * reserved claim in the ID token. - */ - firebase: { - - /** - * Provider-specific identity details corresponding - * to the provider used to sign in the user. - */ - identities: { - [key: string]: any; - }; - - /** - * The ID of the provider used to sign in the user. - * One of `"anonymous"`, `"password"`, `"facebook.com"`, `"github.com"`, - * `"google.com"`, `"twitter.com"`, `"apple.com"`, `"microsoft.com"`, - * "yahoo.com"`, `"phone"`, `"playgames.google.com"`, `"gc.apple.com"`, - * or `"custom"`. - * - * Additional Identity Platform provider IDs include `"linkedin.com"`, - * OIDC and SAML identity providers prefixed with `"saml."` and `"oidc."` - * respectively. - */ - sign_in_provider: string; - - /** - * The type identifier or `factorId` of the second factor, provided the - * ID token was obtained from a multi-factor authenticated user. - * For phone, this is `"phone"`. - */ - sign_in_second_factor?: string; - - /** - * The `uid` of the second factor used to sign in, provided the - * ID token was obtained from a multi-factor authenticated user. - */ - second_factor_identifier?: string; - - /** - * The ID of the tenant the user belongs to, if available. - */ - tenant?: string; - [key: string]: any; - }; - - /** - * The ID token's issued-at time, in seconds since the Unix epoch. That is, the - * time at which this ID token was issued and should start to be considered - * valid. - * - * The Firebase SDKs transparently refresh ID tokens every hour, issuing a new - * ID token with a new issued-at time. If you want to get the time at which the - * user session corresponding to the ID token initially occurred, see the - * [`auth_time`](#auth_time) property. - */ - iat: number; - - /** - * The issuer identifier for the issuer of the response. - * - * This value is a URL with the format - * `https://securetoken.google.com/`, where `` is the - * same project ID specified in the [`aud`](#aud) property. - */ - iss: string; - - /** - * The phone number of the user to whom the ID token belongs, if available. - */ - phone_number?: string; - - /** - * The photo URL for the user to whom the ID token belongs, if available. - */ - picture?: string; - - /** - * The `uid` corresponding to the user who the ID token belonged to. - * - * As a convenience, this value is copied over to the [`uid`](#uid) property. - */ - sub: string; - - /** - * The `uid` corresponding to the user who the ID token belonged to. - * - * This value is not actually in the JWT token claims itself. It is added as a - * convenience, and is set as the value of the [`sub`](#sub) property. - */ - uid: string; - [key: string]: any; - } - - /** Represents the result of the {@link auth.Auth.getUsers} API. */ - export interface GetUsersResult { - /** - * Set of user records, corresponding to the set of users that were - * requested. Only users that were found are listed here. The result set is - * unordered. - */ - users: UserRecord[]; - - /** Set of identifiers that were requested, but not found. */ - notFound: UserIdentifier[]; - } - - /** - * Interface representing the object returned from a - * {@link auth.Auth.listUsers `listUsers()`} operation. Contains the list - * of users for the current batch and the next page token if available. - */ - export interface ListUsersResult { - - /** - * The list of {@link auth.UserRecord `UserRecord`} objects for the - * current downloaded batch. - */ - users: UserRecord[]; - - /** - * The next page token if available. This is needed for the next batch download. - */ - pageToken?: string; - } - - export type HashAlgorithmType = 'SCRYPT' | 'STANDARD_SCRYPT' | 'HMAC_SHA512' | - 'HMAC_SHA256' | 'HMAC_SHA1' | 'HMAC_MD5' | 'MD5' | 'PBKDF_SHA1' | 'BCRYPT' | - 'PBKDF2_SHA256' | 'SHA512' | 'SHA256' | 'SHA1'; - - /** - * Interface representing the user import options needed for - * {@link auth.Auth.importUsers `importUsers()`} method. This is used to - * provide the password hashing algorithm information. - */ - export interface UserImportOptions { - - /** - * The password hashing information. - */ - hash: { - - /** - * The password hashing algorithm identifier. The following algorithm - * identifiers are supported: - * `SCRYPT`, `STANDARD_SCRYPT`, `HMAC_SHA512`, `HMAC_SHA256`, `HMAC_SHA1`, - * `HMAC_MD5`, `MD5`, `PBKDF_SHA1`, `BCRYPT`, `PBKDF2_SHA256`, `SHA512`, - * `SHA256` and `SHA1`. - */ - algorithm: HashAlgorithmType; - - /** - * The signing key used in the hash algorithm in buffer bytes. - * Required by hashing algorithms `SCRYPT`, `HMAC_SHA512`, `HMAC_SHA256`, - * `HAMC_SHA1` and `HMAC_MD5`. - */ - key?: Buffer; - - /** - * The salt separator in buffer bytes which is appended to salt when - * verifying a password. This is only used by the `SCRYPT` algorithm. - */ - saltSeparator?: Buffer; - - /** - * The number of rounds for hashing calculation. - * Required for `SCRYPT`, `MD5`, `SHA512`, `SHA256`, `SHA1`, `PBKDF_SHA1` and - * `PBKDF2_SHA256`. - */ - rounds?: number; - - /** - * The memory cost required for `SCRYPT` algorithm, or the CPU/memory cost. - * Required for `STANDARD_SCRYPT` algorithm. - */ - memoryCost?: number; - - /** - * The parallelization of the hashing algorithm. Required for the - * `STANDARD_SCRYPT` algorithm. - */ - parallelization?: number; - - /** - * The block size (normally 8) of the hashing algorithm. Required for the - * `STANDARD_SCRYPT` algorithm. - */ - blockSize?: number; - /** - * The derived key length of the hashing algorithm. Required for the - * `STANDARD_SCRYPT` algorithm. - */ - derivedKeyLength?: number; - }; - } - - /** - * Interface representing the response from the - * {@link auth.Auth.importUsers `importUsers()`} method for batch - * importing users to Firebase Auth. - */ - export interface UserImportResult { - - /** - * The number of user records that failed to import to Firebase Auth. - */ - failureCount: number; - - /** - * The number of user records that successfully imported to Firebase Auth. - */ - successCount: number; - - /** - * An array of errors corresponding to the provided users to import. The - * length of this array is equal to [`failureCount`](#failureCount). - */ - errors: FirebaseArrayIndexError[]; - } - - /** - * Represents the result of the - * {@link auth.Auth.deleteUsers `deleteUsers()`} - * API. - */ - export interface DeleteUsersResult { - /** - * The number of user records that failed to be deleted (possibly zero). - */ - failureCount: number; - - /** - * The number of users that were deleted successfully (possibly zero). - * Users that did not exist prior to calling `deleteUsers()` are - * considered to be successfully deleted. - */ - successCount: number; - - /** - * A list of `FirebaseArrayIndexError` instances describing the errors that - * were encountered during the deletion. Length of this list is equal to - * the return value of [`failureCount`](#failureCount). - */ - errors: FirebaseArrayIndexError[]; - } - - /** - * User metadata to include when importing a user. - */ - export interface UserMetadataRequest { - - /** - * The date the user last signed in, formatted as a UTC string. - */ - lastSignInTime?: string; - - /** - * The date the user was created, formatted as a UTC string. - */ - creationTime?: string; - } - - /** - * User provider data to include when importing a user. - */ - export interface UserProviderRequest { - - /** - * The user identifier for the linked provider. - */ - uid: string; - - /** - * The display name for the linked provider. - */ - displayName?: string; - - /** - * The email for the linked provider. - */ - email?: string; - - /** - * The phone number for the linked provider. - */ - phoneNumber?: string; - - /** - * The photo URL for the linked provider. - */ - photoURL?: string; - - /** - * The linked provider ID (for example, "google.com" for the Google provider). - */ - providerId: string; - } - - /** - * Interface representing a user to import to Firebase Auth via the - * {@link auth.Auth.importUsers `importUsers()`} method. - */ - export interface UserImportRecord { - - /** - * The user's `uid`. - */ - uid: string; - - /** - * The user's primary email, if set. - */ - email?: string; - - /** - * Whether or not the user's primary email is verified. - */ - emailVerified?: boolean; - - /** - * The user's display name. - */ - displayName?: string; - - /** - * The user's primary phone number, if set. - */ - phoneNumber?: string; - - /** - * The user's photo URL. - */ - photoURL?: string; - - /** - * Whether or not the user is disabled: `true` for disabled; `false` for - * enabled. - */ - disabled?: boolean; - - /** - * Additional metadata about the user. - */ - metadata?: UserMetadataRequest; - - /** - * An array of providers (for example, Google, Facebook) linked to the user. - */ - providerData?: UserProviderRequest[]; - - /** - * The user's custom claims object if available, typically used to define - * user roles and propagated to an authenticated user's ID token. - */ - customClaims?: { [key: string]: any }; - - /** - * The buffer of bytes representing the user's hashed password. - * When a user is to be imported with a password hash, - * {@link auth.UserImportOptions `UserImportOptions`} are required to be - * specified to identify the hashing algorithm used to generate this hash. - */ - passwordHash?: Buffer; - - /** - * The buffer of bytes representing the user's password salt. - */ - passwordSalt?: Buffer; - - /** - * The identifier of the tenant where user is to be imported to. - * When not provided in an `admin.auth.Auth` context, the user is uploaded to - * the default parent project. - * When not provided in an `admin.auth.TenantAwareAuth` context, the user is uploaded - * to the tenant corresponding to that `TenantAwareAuth` instance's tenant ID. - */ - tenantId?: string; - - /** - * The user's multi-factor related properties. - */ - multiFactor?: MultiFactorUpdateSettings; - } - - /** - * Interface representing the session cookie options needed for the - * {@link auth.Auth.createSessionCookie `createSessionCookie()`} method. - */ - export interface SessionCookieOptions { - - /** - * The session cookie custom expiration in milliseconds. The minimum allowed is - * 5 minutes and the maxium allowed is 2 weeks. - */ - expiresIn: number; - } - - /** - * This is the interface that defines the required continue/state URL with - * optional Android and iOS bundle identifiers. - */ - export interface ActionCodeSettings { - - /** - * Defines the link continue/state URL, which has different meanings in - * different contexts: - *
    - *
  • When the link is handled in the web action widgets, this is the deep - * link in the `continueUrl` query parameter.
  • - *
  • When the link is handled in the app directly, this is the `continueUrl` - * query parameter in the deep link of the Dynamic Link.
  • - *
- */ - url: string; - - /** - * Whether to open the link via a mobile app or a browser. - * The default is false. When set to true, the action code link is sent - * as a Universal Link or Android App Link and is opened by the app if - * installed. In the false case, the code is sent to the web widget first - * and then redirects to the app if installed. - */ - handleCodeInApp?: boolean; - - /** - * Defines the iOS bundle ID. This will try to open the link in an iOS app if it - * is installed. - */ - iOS?: { - - /** - * Defines the required iOS bundle ID of the app where the link should be - * handled if the application is already installed on the device. - */ - bundleId: string; - }; - - /** - * Defines the Android package name. This will try to open the link in an - * android app if it is installed. If `installApp` is passed, it specifies - * whether to install the Android app if the device supports it and the app is - * not already installed. If this field is provided without a `packageName`, an - * error is thrown explaining that the `packageName` must be provided in - * conjunction with this field. If `minimumVersion` is specified, and an older - * version of the app is installed, the user is taken to the Play Store to - * upgrade the app. - */ - android?: { - - /** - * Defines the required Android package name of the app where the link should be - * handled if the Android app is installed. - */ - packageName: string; - - /** - * Whether to install the Android app if the device supports it and the app is - * not already installed. - */ - installApp?: boolean; - - /** - * The Android minimum version if available. If the installed app is an older - * version, the user is taken to the GOogle Play Store to upgrade the app. - */ - minimumVersion?: string; - }; - - /** - * Defines the dynamic link domain to use for the current link if it is to be - * opened using Firebase Dynamic Links, as multiple dynamic link domains can be - * configured per project. This field provides the ability to explicitly choose - * configured per project. This fields provides the ability explicitly choose - * one. If none is provided, the oldest domain is used by default. - */ - dynamicLinkDomain?: string; - } - - /** - * Interface representing a tenant configuration. - * - * Multi-tenancy support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform) - * - * Before multi-tenancy can be used on a Google Cloud Identity Platform project, - * tenants must be allowed on that project via the Cloud Console UI. - * - * A tenant configuration provides information such as the display name, tenant - * identifier and email authentication configuration. - * For OIDC/SAML provider configuration management, `TenantAwareAuth` instances should - * be used instead of a `Tenant` to retrieve the list of configured IdPs on a tenant. - * When configuring these providers, note that tenants will inherit - * whitelisted domains and authenticated redirect URIs of their parent project. - * - * All other settings of a tenant will also be inherited. These will need to be managed - * from the Cloud Console UI. - */ - export interface Tenant { - - /** - * The tenant identifier. - */ - tenantId: string; - - /** - * The tenant display name. - */ - displayName?: string; - - /** - * The email sign in provider configuration. - */ - emailSignInConfig?: { - - /** - * Whether email provider is enabled. - */ - enabled: boolean; - - /** - * Whether password is required for email sign-in. When not required, - * email sign-in can be performed with password or via email link sign-in. - */ - passwordRequired?: boolean; - }; - - /** - * The multi-factor auth configuration on the current tenant. - */ - multiFactorConfig?: MultiFactorConfig; - - /** - * The map containing the test phone number / code pairs for the tenant. - */ - testPhoneNumbers?: { [phoneNumber: string]: string }; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; - } - - /** - * Identifies a second factor type. - */ - export type AuthFactorType = 'phone'; - - /** - * Identifies a multi-factor configuration state. - */ - export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; - - /** - * Interface representing a multi-factor configuration. - * This can be used to define whether multi-factor authentication is enabled - * or disabled and the list of second factor challenges that are supported. - */ - export interface MultiFactorConfig { - /** - * The multi-factor config state. - */ - state: MultiFactorConfigState; - - /** - * The list of identifiers for enabled second factors. - * Currently only ‘phone’ is supported. - */ - factorIds?: AuthFactorType[]; - } - - /** - * The email sign in configuration. - */ - export interface EmailSignInProviderConfig { - /** - * Whether email provider is enabled. - */ - enabled: boolean; - - /** - * Whether password is required for email sign-in. When not required, - * email sign-in can be performed with password or via email link sign-in. - */ - passwordRequired?: boolean; // In the backend API, default is true if not provided - } - - /** - * Interface representing the properties to update on the provided tenant. - */ - export interface UpdateTenantRequest { - - /** - * The tenant display name. - */ - displayName?: string; - - /** - * The email sign in configuration. - */ - emailSignInConfig?: EmailSignInProviderConfig; - - /** - * The multi-factor auth configuration to update on the tenant. - */ - multiFactorConfig?: MultiFactorConfig; - - /** - * The updated map containing the test phone number / code pairs for the tenant. - * Passing null clears the previously save phone number / code pairs. - */ - testPhoneNumbers?: { [phoneNumber: string]: string } | null; - } - - /** - * Interface representing the properties to set on a new tenant. - */ - export type CreateTenantRequest = UpdateTenantRequest; - - /** - * Interface representing the object returned from a - * {@link auth.TenantManager.listTenants `listTenants()`} - * operation. - * Contains the list of tenants for the current batch and the next page token if available. - */ - export interface ListTenantsResult { - - /** - * The list of {@link auth.Tenant `Tenant`} objects for the downloaded batch. - */ - tenants: Tenant[]; - - /** - * The next page token if available. This is needed for the next batch download. - */ - pageToken?: string; - } - - /** - * The filter interface used for listing provider configurations. This is used - * when specifying how to list configured identity providers via - * {@link auth.Auth.listProviderConfigs `listProviderConfigs()`}. - */ - export interface AuthProviderConfigFilter { - - /** - * The Auth provider configuration filter. This can be either `saml` or `oidc`. - * The former is used to look up SAML providers only, while the latter is used - * for OIDC providers. - */ - type: 'saml' | 'oidc'; - - /** - * The maximum number of results to return per page. The default and maximum is - * 100. - */ - maxResults?: number; - - /** - * The next page token. When not specified, the lookup starts from the beginning - * of the list. - */ - pageToken?: string; - } - - /** - * The base Auth provider configuration interface. - */ - export interface AuthProviderConfig { - - /** - * The provider ID defined by the developer. - * For a SAML provider, this is always prefixed by `saml.`. - * For an OIDC provider, this is always prefixed by `oidc.`. - */ - providerId: string; - - /** - * The user-friendly display name to the current configuration. This name is - * also used as the provider label in the Cloud Console. - */ - displayName?: string; - - /** - * Whether the provider configuration is enabled or disabled. A user - * cannot sign in using a disabled provider. - */ - enabled: boolean; - } - - /** - * The - * [SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) - * Auth provider configuration interface. A SAML provider can be created via - * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. - */ - export interface SAMLAuthProviderConfig extends AuthProviderConfig { - - /** - * The SAML IdP entity identifier. - */ - idpEntityId: string; - - /** - * The SAML IdP SSO URL. This must be a valid URL. - */ - ssoURL: string; - - /** - * The list of SAML IdP X.509 certificates issued by CA for this provider. - * Multiple certificates are accepted to prevent outages during - * IdP key rotation (for example ADFS rotates every 10 days). When the Auth - * server receives a SAML response, it will match the SAML response with the - * certificate on record. Otherwise the response is rejected. - * Developers are expected to manage the certificate updates as keys are - * rotated. - */ - x509Certificates: string[]; - - /** - * The SAML relying party (service provider) entity ID. - * This is defined by the developer but needs to be provided to the SAML IdP. - */ - rpEntityId: string; - - /** - * This is fixed and must always be the same as the OAuth redirect URL - * provisioned by Firebase Auth, - * `https://project-id.firebaseapp.com/__/auth/handler` unless a custom - * `authDomain` is used. - * The callback URL should also be provided to the SAML IdP during - * configuration. - */ - callbackURL?: string; - } - - /** - * The [OIDC](https://openid.net/specs/openid-connect-core-1_0-final.html) Auth - * provider configuration interface. An OIDC provider can be created via - * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. - */ - export interface OIDCAuthProviderConfig extends AuthProviderConfig { - - /** - * This is the required client ID used to confirm the audience of an OIDC - * provider's - * [ID token](https://openid.net/specs/openid-connect-core-1_0-final.html#IDToken). - */ - clientId: string; - - /** - * This is the required provider issuer used to match the provider issuer of - * the ID token and to determine the corresponding OIDC discovery document, eg. - * [`/.well-known/openid-configuration`](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig). - * This is needed for the following: - *
    - *
  • To verify the provided issuer.
  • - *
  • Determine the authentication/authorization endpoint during the OAuth - * `id_token` authentication flow.
  • - *
  • To retrieve the public signing keys via `jwks_uri` to verify the OIDC - * provider's ID token's signature.
  • - *
  • To determine the claims_supported to construct the user attributes to be - * returned in the additional user info response.
  • - *
- * ID token validation will be performed as defined in the - * [spec](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation). - */ - issuer: string; - } - - /** - * The request interface for updating a SAML Auth provider. This is used - * when updating a SAML provider's configuration via - * {@link auth.Auth.updateProviderConfig `updateProviderConfig()`}. - */ - export interface SAMLUpdateAuthProviderRequest { - - /** - * The SAML provider's updated display name. If not provided, the existing - * configuration's value is not modified. - */ - displayName?: string; - - /** - * Whether the SAML provider is enabled or not. If not provided, the existing - * configuration's setting is not modified. - */ - enabled?: boolean; - - /** - * The SAML provider's updated IdP entity ID. If not provided, the existing - * configuration's value is not modified. - */ - idpEntityId?: string; - - /** - * The SAML provider's updated SSO URL. If not provided, the existing - * configuration's value is not modified. - */ - ssoURL?: string; - - /** - * The SAML provider's updated list of X.509 certificated. If not provided, the - * existing configuration list is not modified. - */ - x509Certificates?: string[]; - - /** - * The SAML provider's updated RP entity ID. If not provided, the existing - * configuration's value is not modified. - */ - rpEntityId?: string; - - /** - * The SAML provider's callback URL. If not provided, the existing - * configuration's value is not modified. - */ - callbackURL?: string; - } - - /** - * The request interface for updating an OIDC Auth provider. This is used - * when updating an OIDC provider's configuration via - * {@link auth.Auth.updateProviderConfig `updateProviderConfig()`}. - */ - export interface OIDCUpdateAuthProviderRequest { - - /** - * The OIDC provider's updated display name. If not provided, the existing - * configuration's value is not modified. - */ - displayName?: string; - - /** - * Whether the OIDC provider is enabled or not. If not provided, the existing - * configuration's setting is not modified. - */ - enabled?: boolean; - - /** - * The OIDC provider's updated client ID. If not provided, the existing - * configuration's value is not modified. - */ - clientId?: string; - - /** - * The OIDC provider's updated issuer. If not provided, the existing - * configuration's value is not modified. - */ - issuer?: string; - } - - /** - * The response interface for listing provider configs. This is only available - * when listing all identity providers' configurations via - * {@link auth.Auth.listProviderConfigs `listProviderConfigs()`}. - */ - export interface ListProviderConfigResults { - - /** - * The list of providers for the specified type in the current page. - */ - providerConfigs: AuthProviderConfig[]; - - /** - * The next page token, if available. - */ - pageToken?: string; - } - - export type UpdateAuthProviderRequest = - SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; - - /** - * Used for looking up an account by uid. - * - * See auth.getUsers() - */ - export interface UidIdentifier { - uid: string; - } - - /** - * Used for looking up an account by email. - * - * See auth.getUsers() - */ - export interface EmailIdentifier { - email: string; - } - - /** - * Used for looking up an account by phone number. - * - * See auth.getUsers() - */ - export interface PhoneIdentifier { - phoneNumber: string; - } - - /** - * Used for looking up an account by federated provider. - * - * See auth.getUsers() - */ - export interface ProviderIdentifier { - providerId: string; - providerUid: string; - } - - /** - * Identifies a user to be looked up. - */ - export type UserIdentifier = - UidIdentifier | EmailIdentifier | PhoneIdentifier | ProviderIdentifier; - - export interface BaseAuth { - - /** - * Creates a new Firebase custom token (JWT) that can be sent back to a client - * device to use to sign in with the client SDKs' `signInWithCustomToken()` - * methods. (Tenant-aware instances will also embed the tenant ID in the - * token.) - * - * See [Create Custom Tokens](/docs/auth/admin/create-custom-tokens) for code - * samples and detailed documentation. - * - * @param uid The `uid` to use as the custom token's subject. - * @param developerClaims Optional additional claims to include - * in the custom token's payload. - * - * @return A promise fulfilled with a custom token for the - * provided `uid` and payload. - */ - createCustomToken(uid: string, developerClaims?: object): Promise; - - /** - * Creates a new user. - * - * See [Create a user](/docs/auth/admin/manage-users#create_a_user) for code - * samples and detailed documentation. - * - * @param properties The properties to set on the - * new user record to be created. - * - * @return A promise fulfilled with the user - * data corresponding to the newly created user. - */ - createUser(properties: CreateRequest): Promise; - - /** - * Deletes an existing user. - * - * See [Delete a user](/docs/auth/admin/manage-users#delete_a_user) for code - * samples and detailed documentation. - * - * @param uid The `uid` corresponding to the user to delete. - * - * @return An empty promise fulfilled once the user has been - * deleted. - */ - deleteUser(uid: string): Promise; - - /** - * Deletes the users specified by the given uids. - * - * Deleting a non-existing user won't generate an error (i.e. this method - * is idempotent.) Non-existing users are considered to be successfully - * deleted, and are therefore counted in the - * `DeleteUsersResult.successCount` value. - * - * Only a maximum of 1000 identifiers may be supplied. If more than 1000 - * identifiers are supplied, this method throws a FirebaseAuthError. - * - * This API is currently rate limited at the server to 1 QPS. If you exceed - * this, you may get a quota exceeded error. Therefore, if you want to - * delete more than 1000 users, you may need to add a delay to ensure you - * don't go over this limit. - * - * @param uids The `uids` corresponding to the users to delete. - * - * @return A Promise that resolves to the total number of successful/failed - * deletions, as well as the array of errors that corresponds to the - * failed deletions. - */ - deleteUsers(uids: string[]): Promise; - - /** - * Gets the user data for the user corresponding to a given `uid`. - * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) - * for code samples and detailed documentation. - * - * @param uid The `uid` corresponding to the user whose data to fetch. - * - * @return A promise fulfilled with the user - * data corresponding to the provided `uid`. - */ - getUser(uid: string): Promise; - - /** - * Gets the user data for the user corresponding to a given email. - * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) - * for code samples and detailed documentation. - * - * @param email The email corresponding to the user whose data to - * fetch. - * - * @return A promise fulfilled with the user - * data corresponding to the provided email. - */ - getUserByEmail(email: string): Promise; - - /** - * Gets the user data for the user corresponding to a given phone number. The - * phone number has to conform to the E.164 specification. - * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) - * for code samples and detailed documentation. - * - * @param phoneNumber The phone number corresponding to the user whose - * data to fetch. - * - * @return A promise fulfilled with the user - * data corresponding to the provided phone number. - */ - getUserByPhoneNumber(phoneNumber: string): Promise; - - /** - * Gets the user data corresponding to the specified identifiers. - * - * There are no ordering guarantees; in particular, the nth entry in the result list is not - * guaranteed to correspond to the nth entry in the input parameters list. - * - * Only a maximum of 100 identifiers may be supplied. If more than 100 identifiers are supplied, - * this method throws a FirebaseAuthError. - * - * @param identifiers The identifiers used to indicate which user records should be returned. - * Must have <= 100 entries. - * @return {Promise} A promise that resolves to the corresponding user records. - * @throws FirebaseAuthError If any of the identifiers are invalid or if more than 100 - * identifiers are specified. - */ - getUsers(identifiers: UserIdentifier[]): Promise; - - /** - * Retrieves a list of users (single batch only) with a size of `maxResults` - * starting from the offset as specified by `pageToken`. This is used to - * retrieve all the users of a specified project in batches. - * - * See [List all users](/docs/auth/admin/manage-users#list_all_users) - * for code samples and detailed documentation. - * - * @param maxResults The page size, 1000 if undefined. This is also - * the maximum allowed limit. - * @param pageToken The next page token. If not specified, returns - * users starting without any offset. - * @return A promise that resolves with - * the current batch of downloaded users and the next page token. - */ - listUsers(maxResults?: number, pageToken?: string): Promise; - - /** - * Updates an existing user. - * - * See [Update a user](/docs/auth/admin/manage-users#update_a_user) for code - * samples and detailed documentation. - * - * @param uid The `uid` corresponding to the user to update. - * @param properties The properties to update on - * the provided user. - * - * @return A promise fulfilled with the - * updated user data. - */ - updateUser(uid: string, properties: UpdateRequest): Promise; - - /** - * Verifies a Firebase ID token (JWT). If the token is valid, the promise is - * fulfilled with the token's decoded claims; otherwise, the promise is - * rejected. - * An optional flag can be passed to additionally check whether the ID token - * was revoked. - * - * See [Verify ID Tokens](/docs/auth/admin/verify-id-tokens) for code samples - * and detailed documentation. - * - * @param idToken The ID token to verify. - * @param checkRevoked Whether to check if the ID token was revoked. - * This requires an extra request to the Firebase Auth backend to check - * the `tokensValidAfterTime` time for the corresponding user. - * When not specified, this additional check is not applied. - * - * @return A promise fulfilled with the - * token's decoded claims if the ID token is valid; otherwise, a rejected - * promise. - */ - verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; - - /** - * Sets additional developer claims on an existing user identified by the - * provided `uid`, typically used to define user roles and levels of - * access. These claims should propagate to all devices where the user is - * already signed in (after token expiration or when token refresh is forced) - * and the next time the user signs in. If a reserved OIDC claim name - * is used (sub, iat, iss, etc), an error is thrown. They are set on the - * authenticated user's ID token JWT. - * - * See - * [Defining user roles and access levels](/docs/auth/admin/custom-claims) - * for code samples and detailed documentation. - * - * @param uid The `uid` of the user to edit. - * @param customUserClaims The developer claims to set. If null is - * passed, existing custom claims are deleted. Passing a custom claims payload - * larger than 1000 bytes will throw an error. Custom claims are added to the - * user's ID token which is transmitted on every authenticated request. - * For profile non-access related user attributes, use database or other - * separate storage systems. - * @return A promise that resolves when the operation completes - * successfully. - */ - setCustomUserClaims(uid: string, customUserClaims: object | null): Promise; - - /** - * Revokes all refresh tokens for an existing user. - * - * This API will update the user's - * {@link auth.UserRecord.tokensValidAfterTime `tokensValidAfterTime`} to - * the current UTC. It is important that the server on which this is called has - * its clock set correctly and synchronized. - * - * While this will revoke all sessions for a specified user and disable any - * new ID tokens for existing sessions from getting minted, existing ID tokens - * may remain active until their natural expiration (one hour). To verify that - * ID tokens are revoked, use - * {@link auth.Auth.verifyIdToken `verifyIdToken(idToken, true)`} - * where `checkRevoked` is set to true. - * - * @param uid The `uid` corresponding to the user whose refresh tokens - * are to be revoked. - * - * @return An empty promise fulfilled once the user's refresh - * tokens have been revoked. - */ - revokeRefreshTokens(uid: string): Promise; - - /** - * Imports the provided list of users into Firebase Auth. - * A maximum of 1000 users are allowed to be imported one at a time. - * When importing users with passwords, - * {@link auth.UserImportOptions `UserImportOptions`} are required to be - * specified. - * This operation is optimized for bulk imports and will ignore checks on `uid`, - * `email` and other identifier uniqueness which could result in duplications. - * - * @param users The list of user records to import to Firebase Auth. - * @param options The user import options, required when the users provided include - * password credentials. - * @return A promise that resolves when - * the operation completes with the result of the import. This includes the - * number of successful imports, the number of failed imports and their - * corresponding errors. - */ - importUsers( - users: UserImportRecord[], - options?: UserImportOptions, - ): Promise; - - /** - * Creates a new Firebase session cookie with the specified options. The created - * JWT string can be set as a server-side session cookie with a custom cookie - * policy, and be used for session management. The session cookie JWT will have - * the same payload claims as the provided ID token. - * - * See [Manage Session Cookies](/docs/auth/admin/manage-cookies) for code - * samples and detailed documentation. - * - * @param idToken The Firebase ID token to exchange for a session - * cookie. - * @param sessionCookieOptions The session - * cookie options which includes custom session duration. - * - * @return A promise that resolves on success with the - * created session cookie. - */ - createSessionCookie( - idToken: string, - sessionCookieOptions: SessionCookieOptions, - ): Promise; - - /** - * Verifies a Firebase session cookie. Returns a Promise with the cookie claims. - * Rejects the promise if the cookie could not be verified. If `checkRevoked` is - * set to true, verifies if the session corresponding to the session cookie was - * revoked. If the corresponding user's session was revoked, an - * `auth/session-cookie-revoked` error is thrown. If not specified the check is - * not performed. - * - * See [Verify Session Cookies](/docs/auth/admin/manage-cookies#verify_session_cookie_and_check_permissions) - * for code samples and detailed documentation - * - * @param sessionCookie The session cookie to verify. - * @param checkForRevocation Whether to check if the session cookie was - * revoked. This requires an extra request to the Firebase Auth backend to - * check the `tokensValidAfterTime` time for the corresponding user. - * When not specified, this additional check is not performed. - * - * @return A promise fulfilled with the - * session cookie's decoded claims if the session cookie is valid; otherwise, - * a rejected promise. - */ - verifySessionCookie( - sessionCookie: string, - checkForRevocation?: boolean, - ): Promise; - - /** - * Generates the out of band email action link to reset a user's password. - * The link is generated for the user with the specified email address. The - * optional {@link auth.ActionCodeSettings `ActionCodeSettings`} object - * defines whether the link is to be handled by a mobile app or browser and the - * additional state information to be passed in the deep link, etc. - * - * @example - * ```javascript - * var actionCodeSettings = { - * url: 'https://www.example.com/?email=user@example.com', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * handleCodeInApp: true, - * dynamicLinkDomain: 'custom.page.link' - * }; - * admin.auth() - * .generatePasswordResetLink('user@example.com', actionCodeSettings) - * .then(function(link) { - * // The link was successfully generated. - * }) - * .catch(function(error) { - * // Some error occurred, you can inspect the code: error.code - * }); - * ``` - * - * @param email The email address of the user whose password is to be - * reset. - * @param actionCodeSettings The action - * code settings. If specified, the state/continue URL is set as the - * "continueUrl" parameter in the password reset link. The default password - * reset landing page will use this to display a link to go back to the app - * if it is installed. - * If the actionCodeSettings is not specified, no URL is appended to the - * action URL. - * The state URL provided must belong to a domain that is whitelisted by the - * developer in the console. Otherwise an error is thrown. - * Mobile app redirects are only applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of service. - * The Android package name and iOS bundle ID are respected only if they - * are configured in the same Firebase Auth project. - * @return A promise that resolves with the generated link. - */ - generatePasswordResetLink( - email: string, - actionCodeSettings?: ActionCodeSettings, - ): Promise; - - /** - * Generates the out of band email action link to verify the user's ownership - * of the specified email. The - * {@link auth.ActionCodeSettings `ActionCodeSettings`} object provided - * as an argument to this method defines whether the link is to be handled by a - * mobile app or browser along with additional state information to be passed in - * the deep link, etc. - * - * @example - * ```javascript - * var actionCodeSettings = { - * url: 'https://www.example.com/cart?email=user@example.com&cartId=123', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * handleCodeInApp: true, - * dynamicLinkDomain: 'custom.page.link' - * }; - * admin.auth() - * .generateEmailVerificationLink('user@example.com', actionCodeSettings) - * .then(function(link) { - * // The link was successfully generated. - * }) - * .catch(function(error) { - * // Some error occurred, you can inspect the code: error.code - * }); - * ``` - * - * @param email The email account to verify. - * @param actionCodeSettings The action - * code settings. If specified, the state/continue URL is set as the - * "continueUrl" parameter in the email verification link. The default email - * verification landing page will use this to display a link to go back to - * the app if it is installed. - * If the actionCodeSettings is not specified, no URL is appended to the - * action URL. - * The state URL provided must belong to a domain that is whitelisted by the - * developer in the console. Otherwise an error is thrown. - * Mobile app redirects are only applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of service. - * The Android package name and iOS bundle ID are respected only if they - * are configured in the same Firebase Auth project. - * @return A promise that resolves with the generated link. - */ - generateEmailVerificationLink( - email: string, - actionCodeSettings?: ActionCodeSettings, - ): Promise; - - /** - * Generates the out of band email action link to sign in or sign up the owner - * of the specified email. The - * {@link auth.ActionCodeSettings `ActionCodeSettings`} object provided - * as an argument to this method defines whether the link is to be handled by a - * mobile app or browser along with additional state information to be passed in - * the deep link, etc. - * - * @example - * ```javascript - * var actionCodeSettings = { - * // The URL to redirect to for sign-in completion. This is also the deep - * // link for mobile redirects. The domain (www.example.com) for this URL - * // must be whitelisted in the Firebase Console. - * url: 'https://www.example.com/finishSignUp?cartId=1234', - * iOS: { - * bundleId: 'com.example.ios' - * }, - * android: { - * packageName: 'com.example.android', - * installApp: true, - * minimumVersion: '12' - * }, - * // This must be true. - * handleCodeInApp: true, - * dynamicLinkDomain: 'custom.page.link' - * }; - * admin.auth() - * .generateSignInWithEmailLink('user@example.com', actionCodeSettings) - * .then(function(link) { - * // The link was successfully generated. - * }) - * .catch(function(error) { - * // Some error occurred, you can inspect the code: error.code - * }); - * ``` - * - * @param email The email account to sign in with. - * @param actionCodeSettings The action - * code settings. These settings provide Firebase with instructions on how - * to construct the email link. This includes the sign in completion URL or - * the deep link for redirects and the mobile apps to use when the - * sign-in link is opened on an Android or iOS device. - * Mobile app redirects are only applicable if the developer configures - * and accepts the Firebase Dynamic Links terms of service. - * The Android package name and iOS bundle ID are respected only if they - * are configured in the same Firebase Auth project. - * @return A promise that resolves with the generated link. - */ - generateSignInWithEmailLink( - email: string, - actionCodeSettings: ActionCodeSettings, - ): Promise; - - /** - * Returns the list of existing provider configurations matching the filter - * provided. At most, 100 provider configs can be listed at a time. - * - * SAML and OIDC provider support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). - * - * @param options The provider config filter to apply. - * @return A promise that resolves with the list of provider configs meeting the - * filter requirements. - */ - listProviderConfigs( - options: AuthProviderConfigFilter - ): Promise; - - /** - * Looks up an Auth provider configuration by the provided ID. - * Returns a promise that resolves with the provider configuration - * corresponding to the provider ID specified. If the specified ID does not - * exist, an `auth/configuration-not-found` error is thrown. - * - * SAML and OIDC provider support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). - * - * @param providerId The provider ID corresponding to the provider - * config to return. - * @return A promise that resolves - * with the configuration corresponding to the provided ID. - */ - getProviderConfig(providerId: string): Promise; - - /** - * Deletes the provider configuration corresponding to the provider ID passed. - * If the specified ID does not exist, an `auth/configuration-not-found` error - * is thrown. - * - * SAML and OIDC provider support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). - * - * @param providerId The provider ID corresponding to the provider - * config to delete. - * @return A promise that resolves on completion. - */ - deleteProviderConfig(providerId: string): Promise; - - /** - * Returns a promise that resolves with the updated `AuthProviderConfig` - * corresponding to the provider ID specified. - * If the specified ID does not exist, an `auth/configuration-not-found` error - * is thrown. - * - * SAML and OIDC provider support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). - * - * @param providerId The provider ID corresponding to the provider - * config to update. - * @param updatedConfig The updated configuration. - * @return A promise that resolves with the updated provider configuration. - */ - updateProviderConfig( - providerId: string, updatedConfig: UpdateAuthProviderRequest - ): Promise; - - /** - * Returns a promise that resolves with the newly created `AuthProviderConfig` - * when the new provider configuration is created. - * - * SAML and OIDC provider support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). - * - * @param config The provider configuration to create. - * @return A promise that resolves with the created provider configuration. - */ - createProviderConfig( - config: AuthProviderConfig - ): Promise; - } - - /** - * Tenant-aware `Auth` interface used for managing users, configuring SAML/OIDC providers, - * generating email links for password reset, email verification, etc for specific tenants. - * - * Multi-tenancy support requires Google Cloud's Identity Platform - * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform) - * - * Each tenant contains its own identity providers, settings and sets of users. - * Using `TenantAwareAuth`, users for a specific tenant and corresponding OIDC/SAML - * configurations can also be managed, ID tokens for users signed in to a specific tenant - * can be verified, and email action links can also be generated for users belonging to the - * tenant. - * - * `TenantAwareAuth` instances for a specific `tenantId` can be instantiated by calling - * `auth.tenantManager().authForTenant(tenantId)`. - */ - export interface TenantAwareAuth extends BaseAuth { - - /** - * The tenant identifier corresponding to this `TenantAwareAuth` instance. - * All calls to the user management APIs, OIDC/SAML provider management APIs, email link - * generation APIs, etc will only be applied within the scope of this tenant. - */ - tenantId: string; - } - - export interface Auth extends BaseAuth { - app: app.App; - - /** - * @return The tenant manager instance associated with the current project. - */ - tenantManager(): TenantManager; - } - - /** - * Defines the tenant manager used to help manage tenant related operations. - * This includes: - *
    - *
  • The ability to create, update, list, get and delete tenants for the underlying - * project.
  • - *
  • Getting a `TenantAwareAuth` instance for running Auth related operations - * (user management, provider configuration management, token verification, - * email link generation, etc) in the context of a specified tenant.
  • - *
- */ - export interface TenantManager { - /** - * @param tenantId The tenant ID whose `TenantAwareAuth` instance is to be returned. - * - * @return The `TenantAwareAuth` instance corresponding to this tenant identifier. - */ - authForTenant(tenantId: string): TenantAwareAuth; - - /** - * Gets the tenant configuration for the tenant corresponding to a given `tenantId`. - * - * @param tenantId The tenant identifier corresponding to the tenant whose data to fetch. - * - * @return A promise fulfilled with the tenant configuration to the provided `tenantId`. - */ - getTenant(tenantId: string): Promise; - - /** - * Retrieves a list of tenants (single batch only) with a size of `maxResults` - * starting from the offset as specified by `pageToken`. This is used to - * retrieve all the tenants of a specified project in batches. - * - * @param maxResults The page size, 1000 if undefined. This is also - * the maximum allowed limit. - * @param pageToken The next page token. If not specified, returns - * tenants starting without any offset. - * - * @return A promise that resolves with - * a batch of downloaded tenants and the next page token. - */ - listTenants(maxResults?: number, pageToken?: string): Promise; - - /** - * Deletes an existing tenant. - * - * @param tenantId The `tenantId` corresponding to the tenant to delete. - * - * @return An empty promise fulfilled once the tenant has been deleted. - */ - deleteTenant(tenantId: string): Promise; - - /** - * Creates a new tenant. - * When creating new tenants, tenants that use separate billing and quota will require their - * own project and must be defined as `full_service`. - * - * @param tenantOptions The properties to set on the new tenant configuration to be created. - * - * @return A promise fulfilled with the tenant configuration corresponding to the newly - * created tenant. - */ - createTenant(tenantOptions: CreateTenantRequest): Promise; - - /** - * Updates an existing tenant configuration. - * - * @param tenantId The `tenantId` corresponding to the tenant to delete. - * @param tenantOptions The properties to update on the provided tenant. - * - * @return A promise fulfilled with the update tenant data. - */ - updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise; - } -} +export { ActionCodeSettings } from './action-code-settings-builder'; + +export { + Auth, +} from './auth'; + +export { + AuthFactorType, + AuthProviderConfig, + AuthProviderConfigFilter, + CreateMultiFactorInfoRequest, + CreatePhoneMultiFactorInfoRequest, + CreateRequest, + EmailSignInProviderConfig, + ListProviderConfigResults, + MultiFactorConfig, + MultiFactorConfigState, + MultiFactorCreateSettings, + MultiFactorUpdateSettings, + OIDCAuthProviderConfig, + OIDCUpdateAuthProviderRequest, + SAMLAuthProviderConfig, + SAMLUpdateAuthProviderRequest, + UpdateAuthProviderRequest, + UpdateMultiFactorInfoRequest, + UpdatePhoneMultiFactorInfoRequest, + UpdateRequest, +} from './auth-config'; + +export { + BaseAuth, + DeleteUsersResult, + GetUsersResult, + ListUsersResult, + SessionCookieOptions, +} from './base-auth'; + +export { + EmailIdentifier, + PhoneIdentifier, + ProviderIdentifier, + UidIdentifier, + UserIdentifier, +} from './identifier'; + +export { + CreateTenantRequest, + Tenant, + UpdateTenantRequest, +} from './tenant'; + +export { + ListTenantsResult, + TenantAwareAuth, + TenantManager, +} from './tenant-manager'; + +export { DecodedIdToken } from './token-verifier'; + +export { + HashAlgorithmType, + UserImportOptions, + UserImportRecord, + UserImportResult, + UserMetadataRequest, + UserProviderRequest, +} from './user-import-builder'; + +export { + MultiFactorInfo, + MultiFactorSettings, + PhoneMultiFactorInfo, + UserInfo, + UserMetadata, + UserRecord, +} from './user-record'; + +export { auth } from './auth-namespace'; diff --git a/src/auth/tenant-manager.ts b/src/auth/tenant-manager.ts index b11f4d605b..e58f803615 100644 --- a/src/auth/tenant-manager.ts +++ b/src/auth/tenant-manager.ts @@ -14,18 +14,134 @@ * limitations under the License. */ -import { AuthRequestHandler } from './auth-api-request'; -import { FirebaseApp } from '../app/firebase-app'; -import { TenantAwareAuth } from './auth'; -import { Tenant, TenantServerResponse } from './tenant'; -import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import * as validator from '../utils/validator'; -import { auth } from './index'; +import { App } from '../app'; +import * as utils from '../utils/index'; +import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; + +import { BaseAuth, SessionCookieOptions } from './base-auth'; +import { Tenant, TenantServerResponse, CreateTenantRequest, UpdateTenantRequest } from './tenant'; +import { FirebaseTokenGenerator, EmulatedSigner, cryptoSignerFromApp } from './token-generator'; +import { + AuthRequestHandler, TenantAwareAuthRequestHandler, useEmulator, +} from './auth-api-request'; +import { DecodedIdToken } from './token-verifier'; + +/** + * Interface representing the object returned from a + * {@link auth.TenantManager.listTenants `listTenants()`} + * operation. + * Contains the list of tenants for the current batch and the next page token if available. + */ +export interface ListTenantsResult { -import ListTenantsResult = auth.ListTenantsResult; -import TenantManagerInterface = auth.TenantManager; -import CreateTenantRequest = auth.CreateTenantRequest; -import UpdateTenantRequest = auth.UpdateTenantRequest; + /** + * The list of {@link auth.Tenant `Tenant`} objects for the downloaded batch. + */ + tenants: Tenant[]; + + /** + * The next page token if available. This is needed for the next batch download. + */ + pageToken?: string; +} + +/** + * The tenant aware Auth class. + */ +export class TenantAwareAuth extends BaseAuth { + + public readonly tenantId: string; + + /** + * The TenantAwareAuth class constructor. + * + * @param {object} app The app that created this tenant. + * @param tenantId The corresponding tenant ID. + * @constructor + * @internal + */ + constructor(app: App, tenantId: string) { + const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); + const tokenGenerator = new FirebaseTokenGenerator(cryptoSigner, tenantId); + super(app, new TenantAwareAuthRequestHandler(app, tenantId), tokenGenerator); + utils.addReadonlyGetter(this, 'tenantId', tenantId); + } + + /** + * Verifies a JWT auth token. Returns a Promise with the tokens claims. Rejects + * the promise if the token could not be verified. If checkRevoked is set to true, + * verifies if the session corresponding to the ID token was revoked. If the corresponding + * user's session was invalidated, an auth/id-token-revoked error is thrown. If not specified + * the check is not applied. + * + * @param {string} idToken The JWT to verify. + * @param {boolean=} checkRevoked Whether to check if the ID token is revoked. + * @return {Promise} A Promise that will be fulfilled after a successful + * verification. + */ + public verifyIdToken(idToken: string, checkRevoked = false): Promise { + return super.verifyIdToken(idToken, checkRevoked) + .then((decodedClaims) => { + // Validate tenant ID. + if (decodedClaims.firebase.tenant !== this.tenantId) { + throw new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); + } + return decodedClaims; + }); + } + + /** + * Creates a new Firebase session cookie with the specified options that can be used for + * session management (set as a server side session cookie with custom cookie policy). + * The session cookie JWT will have the same payload claims as the provided ID token. + * + * @param {string} idToken The Firebase ID token to exchange for a session cookie. + * @param {SessionCookieOptions} sessionCookieOptions The session cookie options which includes + * custom session duration. + * + * @return {Promise} A promise that resolves on success with the created session cookie. + */ + public createSessionCookie( + idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { + // Validate arguments before processing. + if (!validator.isNonEmptyString(idToken)) { + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ID_TOKEN)); + } + if (!validator.isNonNullObject(sessionCookieOptions) || + !validator.isNumber(sessionCookieOptions.expiresIn)) { + return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_SESSION_COOKIE_DURATION)); + } + // This will verify the ID token and then match the tenant ID before creating the session cookie. + return this.verifyIdToken(idToken) + .then(() => { + return super.createSessionCookie(idToken, sessionCookieOptions); + }); + } + + /** + * Verifies a Firebase session cookie. Returns a Promise with the tokens claims. Rejects + * the promise if the token could not be verified. If checkRevoked is set to true, + * verifies if the session corresponding to the session cookie was revoked. If the corresponding + * user's session was invalidated, an auth/session-cookie-revoked error is thrown. If not + * specified the check is not performed. + * + * @param {string} sessionCookie The session cookie to verify. + * @param {boolean=} checkRevoked Whether to check if the session cookie is revoked. + * @return {Promise} A Promise that will be fulfilled after a successful + * verification. + */ + public verifySessionCookie( + sessionCookie: string, checkRevoked = false): Promise { + return super.verifySessionCookie(sessionCookie, checkRevoked) + .then((decodedClaims) => { + if (decodedClaims.firebase.tenant !== this.tenantId) { + throw new FirebaseAuthError(AuthClientErrorCode.MISMATCHING_TENANT_ID); + } + return decodedClaims; + }); + } +} /** * Data structure used to help manage tenant related operations. @@ -34,15 +150,19 @@ import UpdateTenantRequest = auth.UpdateTenantRequest; * - Getting a TenantAwareAuth instance for running Auth related operations (user mgmt, provider config mgmt, etc) * in the context of a specified tenant. */ -export class TenantManager implements TenantManagerInterface { +export class TenantManager { private readonly authRequestHandler: AuthRequestHandler; private readonly tenantsMap: {[key: string]: TenantAwareAuth}; /** * Initializes a TenantManager instance for a specified FirebaseApp. + * * @param app The app for this TenantManager instance. + * + * @constructor + * @internal */ - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { this.authRequestHandler = new AuthRequestHandler(app); this.tenantsMap = {}; } diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index a7b1d188f4..8bbc0438c8 100644 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -17,14 +17,45 @@ import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; + import { EmailSignInConfig, EmailSignInConfigServerRequest, MultiFactorAuthServerConfig, - MultiFactorAuthConfig, validateTestPhoneNumbers, + MultiFactorConfig, validateTestPhoneNumbers, EmailSignInProviderConfig, + MultiFactorAuthConfig, } from './auth-config'; -import { auth } from './index'; -import TenantInterface = auth.Tenant; -import UpdateTenantRequest = auth.UpdateTenantRequest; +/** + * Interface representing the properties to update on the provided tenant. + */ +export interface UpdateTenantRequest { + + /** + * The tenant display name. + */ + displayName?: string; + + /** + * The email sign in configuration. + */ + emailSignInConfig?: EmailSignInProviderConfig; + + /** + * The multi-factor auth configuration to update on the tenant. + */ + multiFactorConfig?: MultiFactorConfig; + + /** + * The updated map containing the test phone number / code pairs for the tenant. + * Passing null clears the previously save phone number / code pairs. + */ + testPhoneNumbers?: { [phoneNumber: string]: string } | null; +} + +/** + * Interface representing the properties to set on a new tenant. + */ +export type CreateTenantRequest = UpdateTenantRequest; + /** The corresponding server side representation of a TenantOptions object. */ export interface TenantOptionsServerRequest extends EmailSignInConfigServerRequest { @@ -46,11 +77,12 @@ export interface TenantServerResponse { /** * Tenant class that defines a Firebase Auth tenant. */ -export class Tenant implements TenantInterface { +export class Tenant { + public readonly tenantId: string; public readonly displayName?: string; - public readonly emailSignInConfig?: EmailSignInConfig; - public readonly multiFactorConfig?: MultiFactorAuthConfig; + private readonly emailSignInConfig_?: EmailSignInConfig; + private readonly multiFactorConfig_?: MultiFactorAuthConfig; public readonly testPhoneNumbers?: {[phoneNumber: string]: string}; /** @@ -59,6 +91,8 @@ export class Tenant implements TenantInterface { * @param {TenantOptions} tenantOptions The properties to convert to a server request. * @param {boolean} createRequest Whether this is a create request. * @return {object} The equivalent server request. + * + * @internal */ public static buildServerRequest( tenantOptions: UpdateTenantRequest, createRequest: boolean): TenantOptionsServerRequest { @@ -85,6 +119,8 @@ export class Tenant implements TenantInterface { * * @param {string} resourceName The server side resource name * @return {?string} The tenant ID corresponding to the resource, null otherwise. + * + * @internal */ public static getTenantIdFromResourceName(resourceName: string): string | null { // name is of form projects/project1/tenants/tenant1 @@ -160,6 +196,7 @@ export class Tenant implements TenantInterface { * * @param response The server side response used to initialize the Tenant object. * @constructor + * @internal */ constructor(response: TenantServerResponse) { const tenantId = Tenant.getTenantIdFromResourceName(response.name); @@ -172,28 +209,36 @@ export class Tenant implements TenantInterface { this.tenantId = tenantId; this.displayName = response.displayName; try { - this.emailSignInConfig = new EmailSignInConfig(response); + this.emailSignInConfig_ = new EmailSignInConfig(response); } catch (e) { // If allowPasswordSignup is undefined, it is disabled by default. - this.emailSignInConfig = new EmailSignInConfig({ + this.emailSignInConfig_ = new EmailSignInConfig({ allowPasswordSignup: false, }); } if (typeof response.mfaConfig !== 'undefined') { - this.multiFactorConfig = new MultiFactorAuthConfig(response.mfaConfig); + this.multiFactorConfig_ = new MultiFactorAuthConfig(response.mfaConfig); } if (typeof response.testPhoneNumbers !== 'undefined') { this.testPhoneNumbers = deepCopy(response.testPhoneNumbers || {}); } } + get emailSignInConfig(): EmailSignInProviderConfig | undefined { + return this.emailSignInConfig_; + } + + get multiFactorConfig(): MultiFactorConfig | undefined { + return this.multiFactorConfig_; + } + /** @return {object} The plain object representation of the tenant. */ public toJSON(): object { const json = { tenantId: this.tenantId, displayName: this.displayName, - emailSignInConfig: this.emailSignInConfig?.toJSON(), - multiFactorConfig: this.multiFactorConfig?.toJSON(), + emailSignInConfig: this.emailSignInConfig_?.toJSON(), + multiFactorConfig: this.multiFactorConfig_?.toJSON(), testPhoneNumbers: this.testPhoneNumbers, }; if (typeof json.multiFactorConfig === 'undefined') { diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index 61db2cc9b2..a9c03130c6 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -23,6 +23,7 @@ import { AuthorizedHttpClient, HttpError, HttpRequestConfig, HttpClient } from ' import * as validator from '../utils/validator'; import { toWebSafeBase64 } from '../utils'; import { Algorithm } from 'jsonwebtoken'; +import { App } from '../app'; const ALGORITHM_RS256: Algorithm = 'RS256' as const; @@ -260,13 +261,13 @@ export class EmulatedSigner implements CryptoSigner { * @param {FirebaseApp} app A FirebaseApp instance. * @return {CryptoSigner} A CryptoSigner instance. */ -export function cryptoSignerFromApp(app: FirebaseApp): CryptoSigner { +export function cryptoSignerFromApp(app: App): CryptoSigner { const credential = app.options.credential; if (credential instanceof ServiceAccountCredential) { return new ServiceAccountSigner(credential); } - return new IAMSigner(new AuthorizedHttpClient(app), app.options.serviceAccountId); + return new IAMSigner(new AuthorizedHttpClient(app as FirebaseApp), app.options.serviceAccountId); } /** diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 4c244523a4..99c6372687 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -19,10 +19,156 @@ import * as util from '../utils/index'; import * as validator from '../utils/validator'; import * as jwt from 'jsonwebtoken'; import { HttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; -import { FirebaseApp } from '../app/firebase-app'; -import { auth } from './index'; +import { App } from '../app'; -import DecodedIdToken = auth.DecodedIdToken; +/** + * Interface representing a decoded Firebase ID token, returned from the + * {@link auth.Auth.verifyIdToken `verifyIdToken()`} method. + * + * Firebase ID tokens are OpenID Connect spec-compliant JSON Web Tokens (JWTs). + * See the + * [ID Token section of the OpenID Connect spec](http://openid.net/specs/openid-connect-core-1_0.html#IDToken) + * for more information about the specific properties below. + */ +export interface DecodedIdToken { + + /** + * The audience for which this token is intended. + * + * This value is a string equal to your Firebase project ID, the unique + * identifier for your Firebase project, which can be found in [your project's + * settings](https://console.firebase.google.com/project/_/settings/general/android:com.random.android). + */ + aud: string; + + /** + * Time, in seconds since the Unix epoch, when the end-user authentication + * occurred. + * + * This value is not set when this particular ID token was created, but when the + * user initially logged in to this session. In a single session, the Firebase + * SDKs will refresh a user's ID tokens every hour. Each ID token will have a + * different [`iat`](#iat) value, but the same `auth_time` value. + */ + auth_time: number; + + /** + * The email of the user to whom the ID token belongs, if available. + */ + email?: string; + + /** + * Whether or not the email of the user to whom the ID token belongs is + * verified, provided the user has an email. + */ + email_verified?: boolean; + + /** + * The ID token's expiration time, in seconds since the Unix epoch. That is, the + * time at which this ID token expires and should no longer be considered valid. + * + * The Firebase SDKs transparently refresh ID tokens every hour, issuing a new + * ID token with up to a one hour expiration. + */ + exp: number; + + /** + * Information about the sign in event, including which sign in provider was + * used and provider-specific identity details. + * + * This data is provided by the Firebase Authentication service and is a + * reserved claim in the ID token. + */ + firebase: { + + /** + * Provider-specific identity details corresponding + * to the provider used to sign in the user. + */ + identities: { + [key: string]: any; + }; + + /** + * The ID of the provider used to sign in the user. + * One of `"anonymous"`, `"password"`, `"facebook.com"`, `"github.com"`, + * `"google.com"`, `"twitter.com"`, `"apple.com"`, `"microsoft.com"`, + * "yahoo.com"`, `"phone"`, `"playgames.google.com"`, `"gc.apple.com"`, + * or `"custom"`. + * + * Additional Identity Platform provider IDs include `"linkedin.com"`, + * OIDC and SAML identity providers prefixed with `"saml."` and `"oidc."` + * respectively. + */ + sign_in_provider: string; + + /** + * The type identifier or `factorId` of the second factor, provided the + * ID token was obtained from a multi-factor authenticated user. + * For phone, this is `"phone"`. + */ + sign_in_second_factor?: string; + + /** + * The `uid` of the second factor used to sign in, provided the + * ID token was obtained from a multi-factor authenticated user. + */ + second_factor_identifier?: string; + + /** + * The ID of the tenant the user belongs to, if available. + */ + tenant?: string; + [key: string]: any; + }; + + /** + * The ID token's issued-at time, in seconds since the Unix epoch. That is, the + * time at which this ID token was issued and should start to be considered + * valid. + * + * The Firebase SDKs transparently refresh ID tokens every hour, issuing a new + * ID token with a new issued-at time. If you want to get the time at which the + * user session corresponding to the ID token initially occurred, see the + * [`auth_time`](#auth_time) property. + */ + iat: number; + + /** + * The issuer identifier for the issuer of the response. + * + * This value is a URL with the format + * `https://securetoken.google.com/`, where `` is the + * same project ID specified in the [`aud`](#aud) property. + */ + iss: string; + + /** + * The phone number of the user to whom the ID token belongs, if available. + */ + phone_number?: string; + + /** + * The photo URL for the user to whom the ID token belongs, if available. + */ + picture?: string; + + /** + * The `uid` corresponding to the user who the ID token belonged to. + * + * As a convenience, this value is copied over to the [`uid`](#uid) property. + */ + sub: string; + + /** + * The `uid` corresponding to the user who the ID token belonged to. + * + * This value is not actually in the JWT token claims itself. It is added as a + * convenience, and is set as the value of the [`sub`](#sub) property. + */ + uid: string; + [key: string]: any; +} // Audience to use for Firebase Auth Custom tokens const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit'; @@ -36,7 +182,11 @@ const CLIENT_CERT_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/secur // URL containing the public keys for Firebase session cookies. This will be updated to a different URL soon. const SESSION_COOKIE_CERT_URL = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys'; -/** User facing token information related to the Firebase ID token. */ +/** + * User facing token information related to the Firebase ID token. + * + * @internal + */ export const ID_TOKEN_INFO: FirebaseTokenInfo = { url: 'https://firebase.google.com/docs/auth/admin/verify-id-tokens', verifyApiName: 'verifyIdToken()', @@ -45,7 +195,11 @@ export const ID_TOKEN_INFO: FirebaseTokenInfo = { expiredErrorCode: AuthClientErrorCode.ID_TOKEN_EXPIRED, }; -/** User facing token information related to the Firebase session cookie. */ +/** + * User facing token information related to the Firebase session cookie. + * + * @internal + */ export const SESSION_COOKIE_INFO: FirebaseTokenInfo = { url: 'https://firebase.google.com/docs/auth/admin/manage-cookies', verifyApiName: 'verifySessionCookie()', @@ -54,7 +208,11 @@ export const SESSION_COOKIE_INFO: FirebaseTokenInfo = { expiredErrorCode: AuthClientErrorCode.SESSION_COOKIE_EXPIRED, }; -/** Interface that defines token related user facing information. */ +/** + * Interface that defines token related user facing information. + * + * @internal + */ export interface FirebaseTokenInfo { /** Documentation URL. */ url: string; @@ -70,6 +228,8 @@ export interface FirebaseTokenInfo { /** * Class for verifying general purpose Firebase JWTs. This verifies ID tokens and session cookies. + * + * @internal */ export class FirebaseTokenVerifier { private publicKeys: {[key: string]: string}; @@ -78,7 +238,7 @@ export class FirebaseTokenVerifier { constructor(private clientCertUrl: string, private algorithm: jwt.Algorithm, private issuer: string, private tokenInfo: FirebaseTokenInfo, - private readonly app: FirebaseApp) { + private readonly app: App) { if (!validator.isURL(clientCertUrl)) { throw new FirebaseAuthError( @@ -342,8 +502,10 @@ export class FirebaseTokenVerifier { * * @param {FirebaseApp} app Firebase app instance. * @return {FirebaseTokenVerifier} + * + * @internal */ -export function createIdTokenVerifier(app: FirebaseApp): FirebaseTokenVerifier { +export function createIdTokenVerifier(app: App): FirebaseTokenVerifier { return new FirebaseTokenVerifier( CLIENT_CERT_URL, ALGORITHM_RS256, @@ -358,8 +520,10 @@ export function createIdTokenVerifier(app: FirebaseApp): FirebaseTokenVerifier { * * @param {FirebaseApp} app Firebase app instance. * @return {FirebaseTokenVerifier} + * + * @internal */ -export function createSessionCookieVerifier(app: FirebaseApp): FirebaseTokenVerifier { +export function createSessionCookieVerifier(app: App): FirebaseTokenVerifier { return new FirebaseTokenVerifier( SESSION_COOKIE_CERT_URL, ALGORITHM_RS256, diff --git a/src/auth/user-import-builder.ts b/src/auth/user-import-builder.ts index 92e84cc08c..72faa5304e 100644 --- a/src/auth/user-import-builder.ts +++ b/src/auth/user-import-builder.ts @@ -19,13 +19,241 @@ import * as utils from '../utils'; import * as validator from '../utils/validator'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { FirebaseArrayIndexError } from '../firebase-namespace-api'; -import { auth } from './index'; +import { + UpdateMultiFactorInfoRequest, UpdatePhoneMultiFactorInfoRequest, MultiFactorUpdateSettings +} from './auth-config'; -import UpdateMultiFactorInfoRequest = auth.UpdateMultiFactorInfoRequest; -import UpdatePhoneMultiFactorInfoRequest = auth.UpdatePhoneMultiFactorInfoRequest; -import UserImportRecord = auth.UserImportRecord; -import UserImportOptions = auth.UserImportOptions; -import UserImportResult = auth.UserImportResult; +export type HashAlgorithmType = 'SCRYPT' | 'STANDARD_SCRYPT' | 'HMAC_SHA512' | + 'HMAC_SHA256' | 'HMAC_SHA1' | 'HMAC_MD5' | 'MD5' | 'PBKDF_SHA1' | 'BCRYPT' | + 'PBKDF2_SHA256' | 'SHA512' | 'SHA256' | 'SHA1'; + +/** + * Interface representing the user import options needed for + * {@link auth.Auth.importUsers `importUsers()`} method. This is used to + * provide the password hashing algorithm information. + */ +export interface UserImportOptions { + + /** + * The password hashing information. + */ + hash: { + + /** + * The password hashing algorithm identifier. The following algorithm + * identifiers are supported: + * `SCRYPT`, `STANDARD_SCRYPT`, `HMAC_SHA512`, `HMAC_SHA256`, `HMAC_SHA1`, + * `HMAC_MD5`, `MD5`, `PBKDF_SHA1`, `BCRYPT`, `PBKDF2_SHA256`, `SHA512`, + * `SHA256` and `SHA1`. + */ + algorithm: HashAlgorithmType; + + /** + * The signing key used in the hash algorithm in buffer bytes. + * Required by hashing algorithms `SCRYPT`, `HMAC_SHA512`, `HMAC_SHA256`, + * `HAMC_SHA1` and `HMAC_MD5`. + */ + key?: Buffer; + + /** + * The salt separator in buffer bytes which is appended to salt when + * verifying a password. This is only used by the `SCRYPT` algorithm. + */ + saltSeparator?: Buffer; + + /** + * The number of rounds for hashing calculation. + * Required for `SCRYPT`, `MD5`, `SHA512`, `SHA256`, `SHA1`, `PBKDF_SHA1` and + * `PBKDF2_SHA256`. + */ + rounds?: number; + + /** + * The memory cost required for `SCRYPT` algorithm, or the CPU/memory cost. + * Required for `STANDARD_SCRYPT` algorithm. + */ + memoryCost?: number; + + /** + * The parallelization of the hashing algorithm. Required for the + * `STANDARD_SCRYPT` algorithm. + */ + parallelization?: number; + + /** + * The block size (normally 8) of the hashing algorithm. Required for the + * `STANDARD_SCRYPT` algorithm. + */ + blockSize?: number; + /** + * The derived key length of the hashing algorithm. Required for the + * `STANDARD_SCRYPT` algorithm. + */ + derivedKeyLength?: number; + }; +} + +/** + * Interface representing a user to import to Firebase Auth via the + * {@link auth.Auth.importUsers `importUsers()`} method. + */ +export interface UserImportRecord { + + /** + * The user's `uid`. + */ + uid: string; + + /** + * The user's primary email, if set. + */ + email?: string; + + /** + * Whether or not the user's primary email is verified. + */ + emailVerified?: boolean; + + /** + * The user's display name. + */ + displayName?: string; + + /** + * The user's primary phone number, if set. + */ + phoneNumber?: string; + + /** + * The user's photo URL. + */ + photoURL?: string; + + /** + * Whether or not the user is disabled: `true` for disabled; `false` for + * enabled. + */ + disabled?: boolean; + + /** + * Additional metadata about the user. + */ + metadata?: UserMetadataRequest; + + /** + * An array of providers (for example, Google, Facebook) linked to the user. + */ + providerData?: UserProviderRequest[]; + + /** + * The user's custom claims object if available, typically used to define + * user roles and propagated to an authenticated user's ID token. + */ + customClaims?: { [key: string]: any }; + + /** + * The buffer of bytes representing the user's hashed password. + * When a user is to be imported with a password hash, + * {@link auth.UserImportOptions `UserImportOptions`} are required to be + * specified to identify the hashing algorithm used to generate this hash. + */ + passwordHash?: Buffer; + + /** + * The buffer of bytes representing the user's password salt. + */ + passwordSalt?: Buffer; + + /** + * The identifier of the tenant where user is to be imported to. + * When not provided in an `admin.auth.Auth` context, the user is uploaded to + * the default parent project. + * When not provided in an `admin.auth.TenantAwareAuth` context, the user is uploaded + * to the tenant corresponding to that `TenantAwareAuth` instance's tenant ID. + */ + tenantId?: string; + + /** + * The user's multi-factor related properties. + */ + multiFactor?: MultiFactorUpdateSettings; +} + +/** + * User metadata to include when importing a user. + */ +export interface UserMetadataRequest { + + /** + * The date the user last signed in, formatted as a UTC string. + */ + lastSignInTime?: string; + + /** + * The date the user was created, formatted as a UTC string. + */ + creationTime?: string; +} + +/** + * User provider data to include when importing a user. + */ +export interface UserProviderRequest { + + /** + * The user identifier for the linked provider. + */ + uid: string; + + /** + * The display name for the linked provider. + */ + displayName?: string; + + /** + * The email for the linked provider. + */ + email?: string; + + /** + * The phone number for the linked provider. + */ + phoneNumber?: string; + + /** + * The photo URL for the linked provider. + */ + photoURL?: string; + + /** + * The linked provider ID (for example, "google.com" for the Google provider). + */ + providerId: string; +} + +/** + * Interface representing the response from the + * {@link auth.Auth.importUsers `importUsers()`} method for batch + * importing users to Firebase Auth. + */ +export interface UserImportResult { + + /** + * The number of user records that failed to import to Firebase Auth. + */ + failureCount: number; + + /** + * The number of user records that successfully imported to Firebase Auth. + */ + successCount: number; + + /** + * An array of errors corresponding to the provided users to import. The + * length of this array is equal to [`failureCount`](#failureCount). + */ + errors: FirebaseArrayIndexError[]; +} /** Interface representing an Auth second factor in Auth server format. */ export interface AuthFactorInfo { diff --git a/src/auth/user-record.ts b/src/auth/user-record.ts index 01092a25da..149b2f89e5 100644 --- a/src/auth/user-record.ts +++ b/src/auth/user-record.ts @@ -19,14 +19,6 @@ import { deepCopy } from '../utils/deep-copy'; import { isNonNullObject } from '../utils/validator'; import * as utils from '../utils'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; -import { auth } from './index'; - -import MultiFactorInfoInterface = auth.MultiFactorInfo; -import PhoneMultiFactorInfoInterface = auth.PhoneMultiFactorInfo; -import MultiFactorSettings = auth.MultiFactorSettings; -import UserMetadataInterface = auth.UserMetadata; -import UserInfoInterface = auth.UserInfo; -import UserRecordInterface = auth.UserRecord; /** * 'REDACTED', encoded as a base64 string. @@ -96,7 +88,7 @@ enum MultiFactorId { /** * Abstract class representing a multi-factor info interface. */ -export abstract class MultiFactorInfo implements MultiFactorInfoInterface { +export abstract class MultiFactorInfo { public readonly uid: string; public readonly displayName?: string; public readonly factorId: string; @@ -107,7 +99,7 @@ export abstract class MultiFactorInfo implements MultiFactorInfoInterface { * If no MultiFactorInfo is associated with the response, null is returned. * * @param response The server side response. - * @constructor + * @internal */ public static initMultiFactorInfo(response: MultiFactorInfoResponse): MultiFactorInfo | null { let multiFactorInfo: MultiFactorInfo | null = null; @@ -125,6 +117,7 @@ export abstract class MultiFactorInfo implements MultiFactorInfoInterface { * * @param response The server side response. * @constructor + * @internal */ constructor(response: MultiFactorInfoResponse) { this.initFromServerResponse(response); @@ -146,6 +139,8 @@ export abstract class MultiFactorInfo implements MultiFactorInfoInterface { * @param response The server side response. * @return The multi-factor ID associated with the provided response. If the response is * not associated with any known multi-factor ID, null is returned. + * + * @internal */ protected abstract getFactorId(response: MultiFactorInfoResponse): string | null; @@ -178,7 +173,7 @@ export abstract class MultiFactorInfo implements MultiFactorInfoInterface { } /** Class representing a phone MultiFactorInfo object. */ -export class PhoneMultiFactorInfo extends MultiFactorInfo implements PhoneMultiFactorInfoInterface { +export class PhoneMultiFactorInfo extends MultiFactorInfo { public readonly phoneNumber: string; /** @@ -186,6 +181,7 @@ export class PhoneMultiFactorInfo extends MultiFactorInfo implements PhoneMultiF * * @param response The server side response. * @constructor + * @internal */ constructor(response: MultiFactorInfoResponse) { super(response); @@ -207,6 +203,8 @@ export class PhoneMultiFactorInfo extends MultiFactorInfo implements PhoneMultiF * @param response The server side response. * @return The multi-factor ID associated with the provided response. If the response is * not associated with any known multi-factor ID, null is returned. + * + * @internal */ protected getFactorId(response: MultiFactorInfoResponse): string | null { return (response && response.phoneInfo) ? MultiFactorId.Phone : null; @@ -214,7 +212,7 @@ export class PhoneMultiFactorInfo extends MultiFactorInfo implements PhoneMultiF } /** Class representing multi-factor related properties of a user. */ -export class MultiFactor implements MultiFactorSettings { +export class MultiFactorSettings { public enrolledFactors: MultiFactorInfo[]; /** @@ -222,6 +220,7 @@ export class MultiFactor implements MultiFactorSettings { * * @param response The server side response. * @constructor + * @internal */ constructor(response: GetAccountInfoUserResponse) { const parsedEnrolledFactors: MultiFactorInfo[] = []; @@ -253,12 +252,8 @@ export class MultiFactor implements MultiFactorSettings { /** * User metadata class that provides metadata information like user account creation * and last sign in time. - * - * @param response The server side response returned from the getAccountInfo - * endpoint. - * @constructor */ -export class UserMetadata implements UserMetadataInterface { +export class UserMetadata { public readonly creationTime: string; public readonly lastSignInTime: string; @@ -269,6 +264,12 @@ export class UserMetadata implements UserMetadataInterface { */ public readonly lastRefreshTime: string | null; + /** + * @param response The server side response returned from the getAccountInfo + * endpoint. + * @constructor + * @internal + */ constructor(response: GetAccountInfoUserResponse) { // Creation date should always be available but due to some backend bugs there // were cases in the past where users did not have creation date properly set. @@ -292,12 +293,8 @@ export class UserMetadata implements UserMetadataInterface { /** * User info class that provides provider user information for different * Firebase providers like google.com, facebook.com, password, etc. - * - * @param response The server side response returned from the getAccountInfo - * endpoint. - * @constructor */ -export class UserInfo implements UserInfoInterface { +export class UserInfo { public readonly uid: string; public readonly displayName: string; public readonly email: string; @@ -305,6 +302,13 @@ export class UserInfo implements UserInfoInterface { public readonly providerId: string; public readonly phoneNumber: string; + + /** + * @param response The server side response returned from the getAccountInfo + * endpoint. + * @constructor + * @internal + */ constructor(response: ProviderUserInfoResponse) { // Provider user id and provider id are required. if (!response.rawId || !response.providerId) { @@ -342,7 +346,7 @@ export class UserInfo implements UserInfoInterface { * endpoint. * @constructor */ -export class UserRecord implements UserRecordInterface { +export class UserRecord { public readonly uid: string; public readonly email: string; public readonly emailVerified: boolean; @@ -357,8 +361,14 @@ export class UserRecord implements UserRecordInterface { public readonly customClaims: {[key: string]: any}; public readonly tenantId?: string | null; public readonly tokensValidAfterTime?: string; - public readonly multiFactor?: MultiFactor; + public readonly multiFactor?: MultiFactorSettings; + /** + * @param response The server side response returned from the getAccountInfo + * endpoint. + * @constructor + * @internal + */ constructor(response: GetAccountInfoUserResponse) { // The Firebase user id is required. if (!response.localId) { @@ -404,7 +414,7 @@ export class UserRecord implements UserRecordInterface { } utils.addReadonlyGetter(this, 'tokensValidAfterTime', validAfterTime || undefined); utils.addReadonlyGetter(this, 'tenantId', response.tenantId); - const multiFactor = new MultiFactor(response); + const multiFactor = new MultiFactorSettings(response); if (multiFactor.enrolledFactors.length > 0) { utils.addReadonlyGetter(this, 'multiFactor', multiFactor); } diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index 96b631356d..2eb4f75fe5 100644 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -43,18 +43,11 @@ import { ActionCodeSettingsBuilder } from '../../../src/auth/action-code-setting import { SAMLConfigServerResponse } from '../../../src/auth/auth-config'; import { expectUserImportResult } from './user-import-builder.spec'; import { getSdkVersion } from '../../../src/utils/index'; -import { auth } from '../../../src/auth/index'; - -import UserImportRecord = auth.UserImportRecord; -import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; -import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; -import OIDCUpdateAuthProviderRequest = auth.OIDCUpdateAuthProviderRequest; -import SAMLUpdateAuthProviderRequest = auth.SAMLUpdateAuthProviderRequest; -import UserIdentifier = auth.UserIdentifier; -import UpdateRequest = auth.UpdateRequest; -import UpdateMultiFactorInfoRequest = auth.UpdateMultiFactorInfoRequest; -import CreateTenantRequest = auth.CreateTenantRequest; -import UpdateTenantRequest = auth.UpdateTenantRequest; +import { + UserImportRecord, OIDCAuthProviderConfig, SAMLAuthProviderConfig, OIDCUpdateAuthProviderRequest, + SAMLUpdateAuthProviderRequest, UserIdentifier, UpdateRequest, UpdateMultiFactorInfoRequest, + CreateTenantRequest, UpdateTenantRequest, +} from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/auth-config.spec.ts b/test/unit/auth/auth-config.spec.ts index ad81c5e62c..5e55bb6992 100644 --- a/test/unit/auth/auth-config.spec.ts +++ b/test/unit/auth/auth-config.spec.ts @@ -27,12 +27,10 @@ import { EmailSignInConfig, MultiFactorAuthConfig, validateTestPhoneNumbers, MAXIMUM_TEST_PHONE_NUMBERS, } from '../../../src/auth/auth-config'; -import { auth } from '../../../src/auth/index'; - -import SAMLUpdateAuthProviderRequest = auth.SAMLUpdateAuthProviderRequest; -import OIDCUpdateAuthProviderRequest = auth.OIDCUpdateAuthProviderRequest; -import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; -import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; +import { + SAMLUpdateAuthProviderRequest, OIDCUpdateAuthProviderRequest, + SAMLAuthProviderConfig, OIDCAuthProviderConfig, +} from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index 981624cf69..406d1c7ee7 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -27,7 +27,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { Auth, TenantAwareAuth, BaseAuth } from '../../../src/auth/auth'; +import { Auth, TenantAwareAuth, BaseAuth } from '../../../src/auth/index'; import { UserRecord } from '../../../src/auth/user-record'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { @@ -44,11 +44,7 @@ import { deepCopy } from '../../../src/utils/deep-copy'; import { TenantManager } from '../../../src/auth/tenant-manager'; import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { HttpClient } from '../../../src/utils/api-request'; -import { auth } from '../../../src/auth/index'; - -import DecodedIdToken = auth.DecodedIdToken; -import UpdateRequest = auth.UpdateRequest; -import AuthProviderConfigFilter = auth.AuthProviderConfigFilter; +import { DecodedIdToken, UpdateRequest, AuthProviderConfigFilter } from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); @@ -60,9 +56,9 @@ const expect = chai.expect; interface AuthTest { name: string; supportsTenantManagement: boolean; - Auth: new (...args: any[]) => BaseAuth; + Auth: new (...args: any[]) => BaseAuth; RequestHandler: new (...args: any[]) => AbstractAuthRequestHandler; - init(app: FirebaseApp): BaseAuth; + init(app: FirebaseApp): BaseAuth; } @@ -264,13 +260,13 @@ const AUTH_CONFIGS: AuthTest[] = [ ]; AUTH_CONFIGS.forEach((testConfig) => { describe(testConfig.name, () => { - let auth: BaseAuth; + let auth: BaseAuth; let mockApp: FirebaseApp; let getTokenStub: sinon.SinonStub; let oldProcessEnv: NodeJS.ProcessEnv; - let nullAccessTokenAuth: BaseAuth; - let malformedAccessTokenAuth: BaseAuth; - let rejectedPromiseAccessTokenAuth: BaseAuth; + let nullAccessTokenAuth: BaseAuth; + let malformedAccessTokenAuth: BaseAuth; + let rejectedPromiseAccessTokenAuth: BaseAuth; beforeEach(() => { mockApp = mocks.app(); diff --git a/test/unit/auth/tenant-manager.spec.ts b/test/unit/auth/tenant-manager.spec.ts index c00096da32..b769d236b0 100644 --- a/test/unit/auth/tenant-manager.spec.ts +++ b/test/unit/auth/tenant-manager.spec.ts @@ -28,11 +28,9 @@ import { AuthRequestHandler } from '../../../src/auth/auth-api-request'; import { Tenant, TenantServerResponse } from '../../../src/auth/tenant'; import { TenantManager } from '../../../src/auth/tenant-manager'; import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error'; -import { auth } from '../../../src/auth/index'; - -import CreateTenantRequest = auth.CreateTenantRequest; -import UpdateTenantRequest = auth.UpdateTenantRequest; -import ListTenantsResult = auth.ListTenantsResult; +import { + CreateTenantRequest, UpdateTenantRequest, ListTenantsResult, +} from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/tenant.spec.ts b/test/unit/auth/tenant.spec.ts index db090b3279..31370e17bc 100644 --- a/test/unit/auth/tenant.spec.ts +++ b/test/unit/auth/tenant.spec.ts @@ -22,11 +22,9 @@ import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../../src/utils/deep-copy'; import { EmailSignInConfig, MultiFactorAuthConfig } from '../../../src/auth/auth-config'; import { Tenant, TenantServerResponse } from '../../../src/auth/tenant'; -import { auth } from '../../../src/auth/index'; - -import EmailSignInProviderConfig = auth.EmailSignInProviderConfig; -import CreateTenantRequest = auth.CreateTenantRequest; -import UpdateTenantRequest = auth.UpdateTenantRequest; +import { + CreateTenantRequest, UpdateTenantRequest, EmailSignInProviderConfig, +} from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/user-import-builder.spec.ts b/test/unit/auth/user-import-builder.spec.ts index 14deeaeb64..859265a03a 100644 --- a/test/unit/auth/user-import-builder.spec.ts +++ b/test/unit/auth/user-import-builder.spec.ts @@ -24,11 +24,9 @@ import { } from '../../../src/auth/user-import-builder'; import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error'; import { toWebSafeBase64 } from '../../../src/utils'; -import { auth } from '../../../src/auth/index'; - -import UpdatePhoneMultiFactorInfoRequest = auth.UpdatePhoneMultiFactorInfoRequest; -import UserImportResult = auth.UserImportResult; -import UserImportRecord = auth.UserImportRecord; +import { + UpdatePhoneMultiFactorInfoRequest, UserImportResult, UserImportRecord, +} from '../../../src/auth'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/user-record.spec.ts b/test/unit/auth/user-record.spec.ts index 0a42de92cb..2e1e3286c9 100644 --- a/test/unit/auth/user-record.spec.ts +++ b/test/unit/auth/user-record.spec.ts @@ -21,9 +21,11 @@ import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../../src/utils/deep-copy'; import { - UserInfo, UserMetadata, UserRecord, GetAccountInfoUserResponse, ProviderUserInfoResponse, - MultiFactor, PhoneMultiFactorInfo, MultiFactorInfo, MultiFactorInfoResponse, + GetAccountInfoUserResponse, ProviderUserInfoResponse, MultiFactorInfoResponse, } from '../../../src/auth/user-record'; +import { + UserInfo, UserMetadata, UserRecord, MultiFactorSettings, MultiFactorInfo, PhoneMultiFactorInfo, +} from '../../../src/auth/index'; chai.should(); @@ -395,7 +397,7 @@ describe('MultiFactorInfo', () => { }); }); -describe('MultiFactor', () => { +describe('MultiFactorSettings', () => { const serverResponse = { localId: 'uid123', mfaInfo: [ @@ -440,18 +442,18 @@ describe('MultiFactor', () => { describe('constructor', () => { it('should throw when a non object is provided', () => { expect(() => { - return new MultiFactor(undefined as any); + return new MultiFactorSettings(undefined as any); }).to.throw('INTERNAL ASSERT FAILED: Invalid multi-factor response'); }); it('should populate an empty enrolledFactors array when given an empty object', () => { - const multiFactor = new MultiFactor({} as any); + const multiFactor = new MultiFactorSettings({} as any); expect(multiFactor.enrolledFactors.length).to.equal(0); }); it('should populate expected enrolledFactors', () => { - const multiFactor = new MultiFactor(serverResponse); + const multiFactor = new MultiFactorSettings(serverResponse); expect(multiFactor.enrolledFactors.length).to.equal(2); expect(multiFactor.enrolledFactors[0]).to.deep.equal(expectedMultiFactorInfo[0]); @@ -461,7 +463,7 @@ describe('MultiFactor', () => { describe('getter', () => { it('should throw when modifying readonly enrolledFactors property', () => { - const multiFactor = new MultiFactor(serverResponse); + const multiFactor = new MultiFactorSettings(serverResponse); expect(() => { (multiFactor as any).enrolledFactors = [ @@ -471,7 +473,7 @@ describe('MultiFactor', () => { }); it('should throw when modifying readonly enrolledFactors internals', () => { - const multiFactor = new MultiFactor(serverResponse); + const multiFactor = new MultiFactorSettings(serverResponse); expect(() => { (multiFactor.enrolledFactors as any)[0] = new PhoneMultiFactorInfo({ @@ -486,7 +488,7 @@ describe('MultiFactor', () => { describe('toJSON', () => { it('should return expected JSON object when given an empty response', () => { - const multiFactor = new MultiFactor({} as any); + const multiFactor = new MultiFactorSettings({} as any); expect(multiFactor.toJSON()).to.deep.equal({ enrolledFactors: [], @@ -494,7 +496,7 @@ describe('MultiFactor', () => { }); it('should return expected JSON object when given a populated response', () => { - const multiFactor = new MultiFactor(serverResponse); + const multiFactor = new MultiFactorSettings(serverResponse); expect(multiFactor.toJSON()).to.deep.equal({ enrolledFactors: [ @@ -971,7 +973,7 @@ describe('UserRecord', () => { }); it('should return expected multiFactor', () => { - const multiFactor = new MultiFactor({ + const multiFactor = new MultiFactorSettings({ localId: 'uid123', mfaInfo: [ { @@ -1001,7 +1003,7 @@ describe('UserRecord', () => { it('should throw when modifying readonly multiFactor property', () => { expect(() => { - (userRecord as any).multiFactor = new MultiFactor({ + (userRecord as any).multiFactor = new MultiFactorSettings({ localId: 'uid123', mfaInfo: [{ mfaEnrollmentId: 'enrollmentId3', From c1bf3cda59b5bf1b9666627967e1cc7d76c47337 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 26 Jan 2021 14:50:09 -0800 Subject: [PATCH 06/41] fix: Renaming service functions to getAuth and getInstanceId (#1142) --- etc/firebase-admin.api.md | 10 +++- src/app/firebase-app.ts | 4 +- src/auth/auth-namespace.ts | 18 ++++--- src/auth/index.ts | 2 +- src/instance-id/index.ts | 18 ++++--- test/unit/auth/index.spec.ts | 75 +++++++++++++++++++++++++++++ test/unit/index.spec.ts | 1 + test/unit/instance-id/index.spec.ts | 14 +++--- 8 files changed, 114 insertions(+), 28 deletions(-) create mode 100644 test/unit/auth/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 0464f9c959..535bd8e883 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -84,7 +84,7 @@ export class Auth extends BaseAuth { } // @public -export function auth(app?: App): Auth; +export function auth(app?: App): auth.Auth; // @public (undocumented) export namespace auth { @@ -400,6 +400,12 @@ export function getApp(name?: string): App; // @public (undocumented) export function getApps(): App[]; +// @public (undocumented) +export function getAuth(app?: App): Auth; + +// @public (undocumented) +export function getInstanceId(app?: App): InstanceId; + // @public export interface GetUsersResult { notFound: UserIdentifier[]; @@ -427,7 +433,7 @@ export class InstanceId { } // @public -export function instanceId(app?: App): InstanceId; +export function instanceId(app?: App): instanceId.InstanceId; // @public (undocumented) export namespace instanceId { diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 40177fe793..4545a65c67 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -277,7 +277,7 @@ export class FirebaseApp implements app.App { * @return The Auth service instance of this app. */ public auth(): Auth { - const fn = require('../auth/index').auth; + const fn = require('../auth/index').getAuth; return fn(this); } @@ -332,7 +332,7 @@ export class FirebaseApp implements app.App { * @return The InstanceId service instance of this app. */ public instanceId(): InstanceId { - const fn = require('../instance-id/index').instanceId; + const fn = require('../instance-id/index').getInstanceId; return fn(this); } diff --git a/src/auth/auth-namespace.ts b/src/auth/auth-namespace.ts index b4f8361f0f..d7830bd046 100644 --- a/src/auth/auth-namespace.ts +++ b/src/auth/auth-namespace.ts @@ -95,6 +95,15 @@ import { UserRecord as TUserRecord, } from './user-record'; +export function getAuth(app?: App): Auth { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('auth', (app) => new Auth(app)); +} + /** * Gets the {@link auth.Auth `Auth`} service for the default app or a * given app. @@ -116,14 +125,7 @@ import { * ``` * */ -export function auth(app?: App): Auth { - if (typeof app === 'undefined') { - app = getApp(); - } - - const firebaseApp: FirebaseApp = app as FirebaseApp; - return firebaseApp.getOrInitService('auth', (app) => new Auth(app)); -} +export declare function auth(app?: App): auth.Auth; /* eslint-disable @typescript-eslint/no-namespace */ export namespace auth { diff --git a/src/auth/index.ts b/src/auth/index.ts index 91609e66f7..619abf366c 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -91,4 +91,4 @@ export { UserRecord, } from './user-record'; -export { auth } from './auth-namespace'; +export { getAuth, auth } from './auth-namespace'; diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index 53b12bffcc..6f40351e7c 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -20,6 +20,15 @@ import { InstanceId } from './instance-id'; export { InstanceId }; +export function getInstanceId(app?: App): InstanceId { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('instanceId', (app) => new InstanceId(app)); +} + /** * Gets the {@link instanceId.InstanceId `InstanceId`} service for the * default app or a given app. @@ -50,14 +59,7 @@ export { InstanceId }; * no app is provided or the `InstanceId` service associated with the * provided app. */ -export function instanceId(app?: App): InstanceId { - if (typeof app === 'undefined') { - app = getApp(); - } - - const firebaseApp: FirebaseApp = app as FirebaseApp; - return firebaseApp.getOrInitService('instanceId', (app) => new InstanceId(app)); -} +export declare function instanceId(app?: App): instanceId.InstanceId; import { InstanceId as TInstanceId } from './instance-id'; diff --git a/test/unit/auth/index.spec.ts b/test/unit/auth/index.spec.ts new file mode 100644 index 0000000000..7d98b351c9 --- /dev/null +++ b/test/unit/auth/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getAuth, Auth } from '../../../src/auth/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('InstanceId', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID for Auth. Initialize the SDK ' + + 'with service account credentials or set project ID as an app option. Alternatively set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getAuth()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getAuth(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const auth = getAuth(mockCredentialApp); + return auth.getUser('uid') + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getAuth(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const auth1: Auth = getAuth(mockApp); + const auth2: Auth = getAuth(mockApp); + expect(auth1).to.equal(auth2); + }); + }); +}); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 3730bdb936..316a41899c 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -30,6 +30,7 @@ import './utils/api-request.spec'; // Auth import './auth/auth.spec'; +import './auth/index.spec'; import './auth/user-record.spec'; import './auth/token-generator.spec'; import './auth/token-verifier.spec'; diff --git a/test/unit/instance-id/index.spec.ts b/test/unit/instance-id/index.spec.ts index 125d0ca6fa..b5a96eb39e 100644 --- a/test/unit/instance-id/index.spec.ts +++ b/test/unit/instance-id/index.spec.ts @@ -23,7 +23,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../../resources/mocks'; import { App } from '../../../src/app/index'; -import { instanceId, InstanceId } from '../../../src/instance-id/index'; +import { getInstanceId, InstanceId } from '../../../src/instance-id/index'; chai.should(); chai.use(sinonChai); @@ -44,10 +44,10 @@ describe('InstanceId', () => { mockCredentialApp = mocks.mockCredentialApp(); }); - describe('instanceId()', () => { + describe('getInstanceId()', () => { it('should throw when default app is not available', () => { expect(() => { - return instanceId(); + return getInstanceId(); }).to.throw('The default Firebase app does not exist.'); }); @@ -55,20 +55,20 @@ describe('InstanceId', () => { // Project ID not set in the environment. delete process.env.GOOGLE_CLOUD_PROJECT; delete process.env.GCLOUD_PROJECT; - const iid = instanceId(mockCredentialApp); + const iid = getInstanceId(mockCredentialApp); return iid.deleteInstanceId('iid') .should.eventually.rejectedWith(noProjectIdError); }); it('should not throw given a valid app', () => { expect(() => { - return instanceId(mockApp); + return getInstanceId(mockApp); }).not.to.throw(); }); it('should return the same instance for a given app instance', () => { - const iid1: InstanceId = instanceId(mockApp); - const iid2: InstanceId = instanceId(mockApp); + const iid1: InstanceId = getInstanceId(mockApp); + const iid2: InstanceId = getInstanceId(mockApp); expect(iid1).to.equal(iid2); }); }); From ed36bb1fe7a8fc83e727f0fd6f2b50310e84a483 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 27 Jan 2021 12:08:20 -0800 Subject: [PATCH 07/41] feat(rtdb): Added firebase-admin/database module entrypoint (#1143) * feat(rtdb): Added firebase-admin/database module entrypoint * fix(rtdb): Added unit tests for ServerValue and enableLogging --- etc/firebase-admin.api.md | 67 +++++++++--- gulpfile.js | 1 + src/app/firebase-app.ts | 12 +-- .../{database-internal.ts => database.ts} | 46 ++++++-- src/database/index.ts | 89 +++++++++------- test/unit/auth/index.spec.ts | 2 +- test/unit/database/database.spec.ts | 6 +- test/unit/database/index.spec.ts | 100 ++++++++++++++++++ test/unit/index.spec.ts | 1 + 9 files changed, 249 insertions(+), 75 deletions(-) rename src/database/{database-internal.ts => database.ts} (83%) create mode 100644 test/unit/database/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 535bd8e883..fff4aa60ae 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -6,8 +6,15 @@ import { Agent } from 'http'; import { Bucket } from '@google-cloud/storage'; +import { DataSnapshot } from '@firebase/database-types'; +import { EventType } from '@firebase/database-types'; +import { FirebaseDatabase } from '@firebase/database-types'; import * as _firestore from '@google-cloud/firestore'; +import { OnDisconnect } from '@firebase/database-types'; +import { Query } from '@firebase/database-types'; +import { Reference } from '@firebase/database-types'; import * as rtdb from '@firebase/database-types'; +import { ThenableReference } from '@firebase/database-types'; // @public export interface ActionCodeSettings { @@ -274,27 +281,39 @@ export namespace credential { const refreshToken: typeof refreshToken; } +// @public (undocumented) +export interface Database extends FirebaseDatabase { + getRules(): Promise; + getRulesJSON(): Promise; + setRules(source: string | Buffer | object): Promise; +} + // @public -export function database(app?: app.App): database.Database; +export function database(app?: App): database.Database; // @public (undocumented) export namespace database { // (undocumented) - export interface Database extends rtdb.FirebaseDatabase { - getRules(): Promise; - getRulesJSON(): Promise; - setRules(source: string | Buffer | object): Promise; - } - import DataSnapshot = rtdb.DataSnapshot; - import EventType = rtdb.EventType; - import OnDisconnect = rtdb.OnDisconnect; - import Query = rtdb.Query; - import Reference = rtdb.Reference; - import ThenableReference = rtdb.ThenableReference; - import enableLogging = rtdb.enableLogging; + export type Database = Database; + // (undocumented) + export type DataSnapshot = rtdb.DataSnapshot; + // (undocumented) + export type EventType = rtdb.EventType; + // (undocumented) + export type OnDisconnect = rtdb.OnDisconnect; + // (undocumented) + export type Query = rtdb.Query; + // (undocumented) + export type Reference = rtdb.Reference; + // (undocumented) + export type ThenableReference = rtdb.ThenableReference; + const // (undocumented) + enableLogging: typeof rtdb.enableLogging; const ServerValue: rtdb.ServerValue; } +export { DataSnapshot } + // @public export interface DecodedIdToken { // (undocumented) @@ -344,6 +363,11 @@ export interface EmailSignInProviderConfig { passwordRequired?: boolean; } +// @public (undocumented) +export const enableLogging: typeof rtdb.enableLogging; + +export { EventType } + // @public export interface FirebaseArrayIndexError { error: FirebaseError; @@ -403,6 +427,12 @@ export function getApps(): App[]; // @public (undocumented) export function getAuth(app?: App): Auth; +// @public (undocumented) +export function getDatabase(app?: App): Database; + +// @public (undocumented) +export function getDatabaseWithUrl(url: string, app?: App): Database; + // @public (undocumented) export function getInstanceId(app?: App): InstanceId; @@ -866,6 +896,8 @@ export interface OIDCUpdateAuthProviderRequest { issuer?: string; } +export { OnDisconnect } + // @public export interface PhoneIdentifier { // (undocumented) @@ -951,6 +983,10 @@ export interface ProviderIdentifier { providerUid: string; } +export { Query } + +export { Reference } + // @public (undocumented) export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; @@ -1100,6 +1136,9 @@ export namespace securityRules { } } +// @public (undocumented) +export const ServerValue: rtdb.ServerValue; + // @public (undocumented) export interface ServiceAccount { // (undocumented) @@ -1163,6 +1202,8 @@ export class TenantManager { updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise; } +export { ThenableReference } + // @public export interface UidIdentifier { // (undocumented) diff --git a/gulpfile.js b/gulpfile.js index 9698d11431..7d3580d7cf 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -89,6 +89,7 @@ gulp.task('compile', function() { 'lib/core.d.ts', 'lib/app/*.d.ts', 'lib/auth/*.d.ts', + 'lib/database/*.d.ts', 'lib/instance-id/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 4545a65c67..c1b748e132 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -27,8 +27,7 @@ import { Auth } from '../auth/auth'; import { MachineLearning } from '../machine-learning/machine-learning'; import { Messaging } from '../messaging/messaging'; import { Storage } from '../storage/storage'; -import { database } from '../database/index'; -import { DatabaseService } from '../database/database-internal'; +import { Database } from '../database/index'; import { Firestore } from '@google-cloud/firestore'; import { FirestoreService } from '../firestore/firestore-internal'; import { InstanceId } from '../instance-id/index'; @@ -36,8 +35,6 @@ import { ProjectManagement } from '../project-management/project-management'; import { SecurityRules } from '../security-rules/security-rules'; import { RemoteConfig } from '../remote-config/remote-config'; -import Database = database.Database; - /** * Type representing a callback which is called every time an app lifecycle event occurs. */ @@ -287,11 +284,8 @@ export class FirebaseApp implements app.App { * @return The Database service instance of this app. */ public database(url?: string): Database { - const service: DatabaseService = this.ensureService_('database', () => { - const dbService: typeof DatabaseService = require('../database/database-internal').DatabaseService; - return new dbService(this); - }); - return service.getDatabase(url); + const fn = require('../database/index').getDatabaseWithUrl; + return fn(url, this); } /** diff --git a/src/database/database-internal.ts b/src/database/database.ts similarity index 83% rename from src/database/database-internal.ts rename to src/database/database.ts index 6b7af1c10c..94a00335bd 100644 --- a/src/database/database-internal.ts +++ b/src/database/database.ts @@ -17,26 +17,52 @@ import { URL } from 'url'; import * as path from 'path'; -import { FirebaseApp } from '../app/firebase-app'; -import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; import { Database as DatabaseImpl } from '@firebase/database'; -import { database } from './index'; +import { FirebaseDatabase } from '@firebase/database-types'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; import * as validator from '../utils/validator'; import { AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; import { getSdkVersion } from '../utils/index'; -import Database = database.Database; +export interface Database extends FirebaseDatabase { + /** + * Gets the currently applied security rules as a string. The return value consists of + * the rules source including comments. + * + * @return A promise fulfilled with the rules as a raw string. + */ + getRules(): Promise; + + /** + * Gets the currently applied security rules as a parsed JSON object. Any comments in + * the original source are stripped away. + * + * @return A promise fulfilled with the parsed rules object. + */ + getRulesJSON(): Promise; + + /** + * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is + * specified as a string or a Buffer, it may include comments. + * + * @param source Source of the rules to apply. Must not be `null` or empty. + * @return Resolves when the rules are set on the Realtime Database. + */ + setRules(source: string | Buffer | object): Promise; +} export class DatabaseService { - private readonly appInternal: FirebaseApp; + private readonly appInternal: App; private databases: { [dbUrl: string]: Database; } = {}; - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseDatabaseError({ code: 'invalid-argument', @@ -63,9 +89,9 @@ export class DatabaseService { /** * Returns the app associated with this DatabaseService instance. * - * @return {FirebaseApp} The app associated with this DatabaseService instance. + * @return The app associated with this DatabaseService instance. */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } @@ -122,11 +148,11 @@ class DatabaseRulesClient { private readonly dbUrl: string; private readonly httpClient: AuthorizedHttpClient; - constructor(app: FirebaseApp, dbUrl: string) { + constructor(app: App, dbUrl: string) { const parsedUrl = new URL(dbUrl); parsedUrl.pathname = path.join(parsedUrl.pathname, RULES_URL_PATH); this.dbUrl = parsedUrl.toString(); - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } /** diff --git a/src/database/index.ts b/src/database/index.ts index dae13a3654..4bc350ee05 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -14,9 +14,48 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; -import { ServerValue as sv } from '@firebase/database'; import * as rtdb from '@firebase/database-types'; +import { + enableLogging as enableLoggingFunc, + ServerValue as serverValueConst, +} from '@firebase/database'; + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { Database, DatabaseService } from './database'; +import { Database as TDatabase } from './database'; + +export { Database }; +export { + DataSnapshot, + EventType, + OnDisconnect, + Query, + Reference, + ThenableReference, +} from '@firebase/database-types'; + +export const enableLogging: typeof rtdb.enableLogging = enableLoggingFunc; +export const ServerValue: rtdb.ServerValue = serverValueConst; + +export function getDatabase(app?: App): Database { + return getDatabaseInstance({ app }); +} + +export function getDatabaseWithUrl(url: string, app?: App): Database { + return getDatabaseInstance({ url, app }); +} + +function getDatabaseInstance(options: { url?: string; app?: App }): Database { + let { app } = options; + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + const dbService = firebaseApp.getOrInitService('database', (app) => new DatabaseService(app)); + return dbService.getDatabase(options.url); +} /** * Gets the {@link database.Database `Database`} service for the default @@ -49,49 +88,23 @@ import * as rtdb from '@firebase/database-types'; * @return The default `Database` service if no app * is provided or the `Database` service associated with the provided app. */ -export declare function database(app?: app.App): database.Database; +export declare function database(app?: App): database.Database; /* eslint-disable @typescript-eslint/no-namespace */ export namespace database { - export interface Database extends rtdb.FirebaseDatabase { - /** - * Gets the currently applied security rules as a string. The return value consists of - * the rules source including comments. - * - * @return A promise fulfilled with the rules as a raw string. - */ - getRules(): Promise; - - /** - * Gets the currently applied security rules as a parsed JSON object. Any comments in - * the original source are stripped away. - * - * @return A promise fulfilled with the parsed rules object. - */ - getRulesJSON(): Promise; - - /** - * Sets the specified rules on the Firebase Realtime Database instance. If the rules source is - * specified as a string or a Buffer, it may include comments. - * - * @param source Source of the rules to apply. Must not be `null` or empty. - * @return Resolves when the rules are set on the Realtime Database. - */ - setRules(source: string | Buffer | object): Promise; - } + export type Database = TDatabase; + export type DataSnapshot = rtdb.DataSnapshot; + export type EventType = rtdb.EventType; + export type OnDisconnect = rtdb.OnDisconnect; + export type Query = rtdb.Query; + export type Reference = rtdb.Reference; + export type ThenableReference = rtdb.ThenableReference; - /* eslint-disable @typescript-eslint/no-unused-vars */ - export import DataSnapshot = rtdb.DataSnapshot; - export import EventType = rtdb.EventType; - export import OnDisconnect = rtdb.OnDisconnect; - export import Query = rtdb.Query; - export import Reference = rtdb.Reference; - export import ThenableReference = rtdb.ThenableReference; - export import enableLogging = rtdb.enableLogging; + export declare const enableLogging: typeof rtdb.enableLogging; /** * [`ServerValue`](https://firebase.google.com/docs/reference/js/firebase.database.ServerValue) * module from the `@firebase/database` package. */ - export const ServerValue: rtdb.ServerValue = sv; + export declare const ServerValue: rtdb.ServerValue; } diff --git a/test/unit/auth/index.spec.ts b/test/unit/auth/index.spec.ts index 7d98b351c9..099bb00cd4 100644 --- a/test/unit/auth/index.spec.ts +++ b/test/unit/auth/index.spec.ts @@ -31,7 +31,7 @@ chai.use(chaiAsPromised); const expect = chai.expect; -describe('InstanceId', () => { +describe('Auth', () => { let mockApp: App; let mockCredentialApp: App; diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index c946d498de..af0fd747fa 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -23,13 +23,11 @@ import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { DatabaseService } from '../../../src/database/database-internal'; -import { database } from '../../../src/database/index'; +import { DatabaseService } from '../../../src/database/database'; +import { Database } from '../../../src/database/index'; import * as utils from '../utils'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; -import Database = database.Database; - describe('Database', () => { let mockApp: FirebaseApp; let database: DatabaseService; diff --git a/test/unit/database/index.spec.ts b/test/unit/database/index.spec.ts new file mode 100644 index 0000000000..382a1d96d3 --- /dev/null +++ b/test/unit/database/index.spec.ts @@ -0,0 +1,100 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { + getDatabase, getDatabaseWithUrl, Database, ServerValue, enableLogging , +} from '../../../src/database/index'; +import { FirebaseApp } from '../../../src/app/firebase-app'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Database', () => { + let mockApp: App; + + beforeEach(() => { + mockApp = mocks.app(); + }); + + afterEach(() => { + return (mockApp as FirebaseApp).delete(); + }); + + describe('getDatabase()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getDatabase(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getDatabase(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const db1: Database = getDatabase(mockApp); + const db2: Database = getDatabase(mockApp); + expect(db1).to.equal(db2); + }); + }); + + describe('getDatabaseWithUrl()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getDatabaseWithUrl('https://test.firebaseio.com'); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getDatabaseWithUrl('https://test.firebaseio.com', mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const db1: Database = getDatabaseWithUrl('https://test.firebaseio.com', mockApp); + const db2: Database = getDatabaseWithUrl('https://test.firebaseio.com', mockApp); + const db3: Database = getDatabaseWithUrl('https://other.firebaseio.com', mockApp); + expect(db1).to.equal(db2); + expect(db1).to.not.equal(db3); + }); + }); + + it('should expose ServerValue sentinel', () => { + expect(() => ServerValue.increment(1)).to.not.throw(); + }); + + it('should expose enableLogging global function', () => { + expect(() => { + enableLogging(console.log); + enableLogging(false); + }).to.not.throw(); + }); +}); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 316a41899c..256c2112aa 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -43,6 +43,7 @@ import './auth/tenant-manager.spec'; // Database import './database/database.spec'; +import './database/index.spec'; // Messaging import './messaging/messaging.spec'; From 32edcac4d665076dc8453b7ecddebecead5d5e6c Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Mon, 1 Feb 2021 12:22:21 -0800 Subject: [PATCH 08/41] chore: Generating separate API reports for module entry points (#1144) * chore: Generating separate API reports for module entry points * fix: Using api-extractor library to generate reports --- api-extractor.json | 330 +------------- etc/firebase-admin.api.md | 618 +++++--------------------- etc/firebase-admin.app.api.md | 76 ++++ etc/firebase-admin.auth.api.md | 589 ++++++++++++++++++++++++ etc/firebase-admin.database.api.md | 77 ++++ etc/firebase-admin.instance-id.api.md | 32 ++ generate-reports.js | 95 ++++ package.json | 4 +- src/default-namespace.d.ts | 20 +- 9 files changed, 991 insertions(+), 850 deletions(-) create mode 100644 etc/firebase-admin.app.api.md create mode 100644 etc/firebase-admin.auth.api.md create mode 100644 etc/firebase-admin.database.api.md create mode 100644 etc/firebase-admin.instance-id.api.md create mode 100644 generate-reports.js diff --git a/api-extractor.json b/api-extractor.json index 6f5a35e026..43ef780464 100644 --- a/api-extractor.json +++ b/api-extractor.json @@ -1,338 +1,32 @@ -/** - * Config file for API Extractor. For more info, please visit: https://api-extractor.com - */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - - /** - * Optionally specifies another JSON config file that this file extends from. This provides a way for - * standard settings to be shared across multiple projects. - * - * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains - * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be - * resolved using NodeJS require(). - * - * SUPPORTED TOKENS: none - * DEFAULT VALUE: "" - */ - // "extends": "./shared/api-extractor-base.json" - // "extends": "my-package/include/api-extractor-base.json" - - /** - * Determines the "" token that can be used with other config file settings. The project folder - * typically contains the tsconfig.json and package.json config files, but the path is user-defined. - * - * The path is resolved relative to the folder of the config file that contains the setting. - * - * The default value for "projectFolder" is the token "", which means the folder is determined by traversing - * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder - * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error - * will be reported. - * - * SUPPORTED TOKENS: - * DEFAULT VALUE: "" - */ - // "projectFolder": "..", - - /** - * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor - * analyzes the symbols exported by this module. - * - * The file extension must be ".d.ts" and not ".ts". - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - */ - // We point to the default-namespace.d.ts file since index.d.ts uses namespace imports that are - // not supported by API Extractor. See https://github.com/microsoft/rushstack/issues/1029 and - // https://github.com/microsoft/rushstack/issues/2338. "mainEntryPointFilePath": "/lib/default-namespace.d.ts", - - /** - * A list of NPM package names whose exports should be treated as part of this package. - * - * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", - * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part - * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly - * imports library2. To avoid this, we can specify: - * - * "bundledPackages": [ "library2" ], - * - * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been - * local files for library1. - */ "bundledPackages": [], - - /** - * Determines how the TypeScript compiler engine will be invoked by API Extractor. - */ "compiler": { - /** - * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * Note: This setting will be ignored if "overrideTsconfig" is used. - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "/tsconfig.json" - */ - // "tsconfigFilePath": "/tsconfig.json", - /** - * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. - * The object must conform to the TypeScript tsconfig schema: - * - * http://json.schemastore.org/tsconfig - * - * If omitted, then the tsconfig.json file will be read from the "projectFolder". - * - * DEFAULT VALUE: no overrideTsconfig section - */ - // "overrideTsconfig": { - // . . . - // } - /** - * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended - * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when - * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses - * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. - * - * DEFAULT VALUE: false - */ - // "skipLibCheck": true, - }, - /** - * Configures how the API report file (*.api.md) will be generated. - */ + }, "apiReport": { - /** - * (REQUIRED) Whether to generate an API report. - */ - "enabled": true - - /** - * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce - * a full file path. - * - * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". - * - * SUPPORTED TOKENS: , - * DEFAULT VALUE: ".api.md" - */ - // "reportFileName": ".api.md", - - /** - * Specifies the folder where the API report file is written. The file name portion is determined by - * the "reportFileName" setting. - * - * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, - * e.g. for an API review. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "/etc/" - */ - // "reportFolder": "/etc/", - - /** - * Specifies the folder where the temporary report file is written. The file name portion is determined by - * the "reportFileName" setting. - * - * After the temporary file is written to disk, it is compared with the file in the "reportFolder". - * If they are different, a production build will fail. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "/temp/" - */ - // "reportTempFolder": "/temp/" + "enabled": true, + "reportFileName": "" }, - - /** - * Configures how the doc model file (*.api.json) will be generated. - */ "docModel": { - /** - * (REQUIRED) Whether to generate a doc model file. - */ "enabled": true - - /** - * The output path for the doc model file. The file extension should be ".api.json". - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "/temp/.api.json" - */ - // "apiJsonFilePath": "/temp/.api.json" }, - - /** - * Configures how the .d.ts rollup file will be generated. - */ "dtsRollup": { - /** - * (REQUIRED) Whether to generate the .d.ts rollup file. - */ "enabled": false - - /** - * Specifies the output path for a .d.ts rollup file to be generated without any trimming. - * This file will include all declarations that are exported by the main entry point. - * - * If the path is an empty string, then this file will not be written. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "/dist/.d.ts" - */ - // "untrimmedFilePath": "/dist/.d.ts", - - /** - * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. - * This file will include only declarations that are marked as "@public" or "@beta". - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "" - */ - // "betaTrimmedFilePath": "/dist/-beta.d.ts", - - /** - * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. - * This file will include only declarations that are marked as "@public". - * - * If the path is an empty string, then this file will not be written. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "" - */ - // "publicTrimmedFilePath": "/dist/-public.d.ts", - - /** - * When a declaration is trimmed, by default it will be replaced by a code comment such as - * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the - * declaration completely. - * - * DEFAULT VALUE: false - */ - // "omitTrimmingComments": true }, - - /** - * Configures how the tsdoc-metadata.json file will be generated. - */ "tsdocMetadata": { - /** - * Whether to generate the tsdoc-metadata.json file. - * - * DEFAULT VALUE: true - */ - // "enabled": true, - /** - * Specifies where the TSDoc metadata file should be written. - * - * The path is resolved relative to the folder of the config file that contains the setting; to change this, - * prepend a folder token such as "". - * - * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", - * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup - * falls back to "tsdoc-metadata.json" in the package folder. - * - * SUPPORTED TOKENS: , , - * DEFAULT VALUE: "" - */ - // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" + "enabled": false }, - - /** - * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files - * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. - * To use the OS's default newline kind, specify "os". - * - * DEFAULT VALUE: "crlf" - */ - // "newlineKind": "crlf", - - /** - * Configures how API Extractor reports error and warning messages produced during analysis. - * - * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. - */ "messages": { - /** - * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing - * the input .d.ts files. - * - * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" - * - * DEFAULT VALUE: A single "default" entry with logLevel=warning. - */ "compilerMessageReporting": { - /** - * Configures the default routing for messages that don't match an explicit rule in this table. - */ "default": { - /** - * Specifies whether the message should be written to the the tool's output log. Note that - * the "addToApiReportFile" property may supersede this option. - * - * Possible values: "error", "warning", "none" - * - * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail - * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes - * the "--local" option), the warning is displayed but the build will not fail. - * - * DEFAULT VALUE: "warning" - */ "logLevel": "warning" - - /** - * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), - * then the message will be written inside that file; otherwise, the message is instead logged according to - * the "logLevel" option. - * - * DEFAULT VALUE: false - */ - // "addToApiReportFile": false } - - // "TS2551": { - // "logLevel": "warning", - // "addToApiReportFile": true - // }, - // - // . . . }, - - /** - * Configures handling of messages reported by API Extractor during its analysis. - * - * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" - * - * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings - */ "extractorMessageReporting": { "default": { "logLevel": "warning" - // "addToApiReportFile": false }, "ae-missing-release-tag": { @@ -343,26 +37,10 @@ "logLevel": "none" } }, - - /** - * Configures handling of messages reported by the TSDoc parser when analyzing code comments. - * - * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" - * - * DEFAULT VALUE: A single "default" entry with logLevel=warning. - */ "tsdocMessageReporting": { "default": { "logLevel": "none" - // "addToApiReportFile": false } - - // "tsdoc-link-tag-unescaped-text": { - // "logLevel": "warning", - // "addToApiReportFile": true - // }, - // - // . . . } } } diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index fff4aa60ae..db1e843ede 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -6,30 +6,9 @@ import { Agent } from 'http'; import { Bucket } from '@google-cloud/storage'; -import { DataSnapshot } from '@firebase/database-types'; -import { EventType } from '@firebase/database-types'; import { FirebaseDatabase } from '@firebase/database-types'; import * as _firestore from '@google-cloud/firestore'; -import { OnDisconnect } from '@firebase/database-types'; -import { Query } from '@firebase/database-types'; -import { Reference } from '@firebase/database-types'; import * as rtdb from '@firebase/database-types'; -import { ThenableReference } from '@firebase/database-types'; - -// @public -export interface ActionCodeSettings { - android?: { - packageName: string; - installApp?: boolean; - minimumVersion?: string; - }; - dynamicLinkDomain?: string; - handleCodeInApp?: boolean; - iOS?: { - bundleId: string; - }; - url: string; -} // @public (undocumented) export interface App { @@ -84,190 +63,220 @@ export interface AppOptions { // @public (undocumented) export const apps: (app.App | null)[]; -// @public -export class Auth extends BaseAuth { - get app(): App; - tenantManager(): TenantManager; - } - // @public export function auth(app?: App): auth.Auth; // @public (undocumented) export namespace auth { + // Warning: (ae-forgotten-export) The symbol "ActionCodeSettings" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type ActionCodeSettings = ActionCodeSettings; + // Warning: (ae-forgotten-export) The symbol "Auth" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type Auth = Auth; + // Warning: (ae-forgotten-export) The symbol "AuthFactorType" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type AuthFactorType = AuthFactorType; + // Warning: (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type AuthProviderConfig = AuthProviderConfig; + // Warning: (ae-forgotten-export) The symbol "AuthProviderConfigFilter" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type AuthProviderConfigFilter = AuthProviderConfigFilter; + // Warning: (ae-forgotten-export) The symbol "BaseAuth" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type BaseAuth = BaseAuth; + // Warning: (ae-forgotten-export) The symbol "CreateMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type CreateMultiFactorInfoRequest = CreateMultiFactorInfoRequest; + // Warning: (ae-forgotten-export) The symbol "CreatePhoneMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type CreatePhoneMultiFactorInfoRequest = CreatePhoneMultiFactorInfoRequest; + // Warning: (ae-forgotten-export) The symbol "CreateRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type CreateRequest = CreateRequest; + // Warning: (ae-forgotten-export) The symbol "CreateTenantRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type CreateTenantRequest = CreateTenantRequest; + // Warning: (ae-forgotten-export) The symbol "DecodedIdToken" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type DecodedIdToken = DecodedIdToken; + // Warning: (ae-forgotten-export) The symbol "DeleteUsersResult" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type DeleteUsersResult = DeleteUsersResult; + // Warning: (ae-forgotten-export) The symbol "EmailIdentifier" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type EmailIdentifier = EmailIdentifier; + // Warning: (ae-forgotten-export) The symbol "EmailSignInProviderConfig" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type EmailSignInProviderConfig = EmailSignInProviderConfig; + // Warning: (ae-forgotten-export) The symbol "GetUsersResult" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type GetUsersResult = GetUsersResult; + // Warning: (ae-forgotten-export) The symbol "HashAlgorithmType" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type HashAlgorithmType = HashAlgorithmType; + // Warning: (ae-forgotten-export) The symbol "ListProviderConfigResults" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type ListProviderConfigResults = ListProviderConfigResults; + // Warning: (ae-forgotten-export) The symbol "ListTenantsResult" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type ListTenantsResult = ListTenantsResult; + // Warning: (ae-forgotten-export) The symbol "ListUsersResult" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type ListUsersResult = ListUsersResult; + // Warning: (ae-forgotten-export) The symbol "MultiFactorConfig" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type MultiFactorConfig = MultiFactorConfig; + // Warning: (ae-forgotten-export) The symbol "MultiFactorConfigState" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type MultiFactorConfigState = MultiFactorConfigState; + // Warning: (ae-forgotten-export) The symbol "MultiFactorCreateSettings" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type MultiFactorCreateSettings = MultiFactorCreateSettings; + // Warning: (ae-forgotten-export) The symbol "MultiFactorInfo" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type MultiFactorInfo = MultiFactorInfo; + // Warning: (ae-forgotten-export) The symbol "MultiFactorSettings" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type MultiFactorSettings = MultiFactorSettings; + // Warning: (ae-forgotten-export) The symbol "MultiFactorUpdateSettings" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type MultiFactorUpdateSettings = MultiFactorUpdateSettings; + // Warning: (ae-forgotten-export) The symbol "OIDCAuthProviderConfig" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type OIDCAuthProviderConfig = OIDCAuthProviderConfig; + // Warning: (ae-forgotten-export) The symbol "OIDCUpdateAuthProviderRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type OIDCUpdateAuthProviderRequest = OIDCUpdateAuthProviderRequest; + // Warning: (ae-forgotten-export) The symbol "PhoneIdentifier" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type PhoneIdentifier = PhoneIdentifier; + // Warning: (ae-forgotten-export) The symbol "PhoneMultiFactorInfo" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type PhoneMultiFactorInfo = PhoneMultiFactorInfo; + // Warning: (ae-forgotten-export) The symbol "ProviderIdentifier" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type ProviderIdentifier = ProviderIdentifier; + // Warning: (ae-forgotten-export) The symbol "SAMLAuthProviderConfig" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type SAMLAuthProviderConfig = SAMLAuthProviderConfig; + // Warning: (ae-forgotten-export) The symbol "SAMLUpdateAuthProviderRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type SAMLUpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest; + // Warning: (ae-forgotten-export) The symbol "SessionCookieOptions" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type SessionCookieOptions = SessionCookieOptions; + // Warning: (ae-forgotten-export) The symbol "Tenant" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type Tenant = Tenant; + // Warning: (ae-forgotten-export) The symbol "TenantAwareAuth" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type TenantAwareAuth = TenantAwareAuth; + // Warning: (ae-forgotten-export) The symbol "TenantManager" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type TenantManager = TenantManager; + // Warning: (ae-forgotten-export) The symbol "UidIdentifier" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UidIdentifier = UidIdentifier; + // Warning: (ae-forgotten-export) The symbol "UpdateAuthProviderRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UpdateAuthProviderRequest = UpdateAuthProviderRequest; + // Warning: (ae-forgotten-export) The symbol "UpdateMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UpdateMultiFactorInfoRequest = UpdateMultiFactorInfoRequest; + // Warning: (ae-forgotten-export) The symbol "UpdatePhoneMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UpdatePhoneMultiFactorInfoRequest = UpdatePhoneMultiFactorInfoRequest; + // Warning: (ae-forgotten-export) The symbol "UpdateRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UpdateRequest = UpdateRequest; + // Warning: (ae-forgotten-export) The symbol "UpdateTenantRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UpdateTenantRequest = UpdateTenantRequest; + // Warning: (ae-forgotten-export) The symbol "UserIdentifier" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UserIdentifier = UserIdentifier; + // Warning: (ae-forgotten-export) The symbol "UserImportOptions" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UserImportOptions = UserImportOptions; + // Warning: (ae-forgotten-export) The symbol "UserImportRecord" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UserImportRecord = UserImportRecord; + // Warning: (ae-forgotten-export) The symbol "UserImportResult" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UserImportResult = UserImportResult; + // Warning: (ae-forgotten-export) The symbol "UserInfo" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UserInfo = UserInfo; + // Warning: (ae-forgotten-export) The symbol "UserMetadata" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UserMetadata = UserMetadata; + // Warning: (ae-forgotten-export) The symbol "UserMetadataRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UserMetadataRequest = UserMetadataRequest; + // Warning: (ae-forgotten-export) The symbol "UserProviderRequest" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UserProviderRequest = UserProviderRequest; + // Warning: (ae-forgotten-export) The symbol "UserRecord" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type UserRecord = UserRecord; } -// @public -export type AuthFactorType = 'phone'; - -// @public -export interface AuthProviderConfig { - displayName?: string; - enabled: boolean; - providerId: string; -} - -// @public -export interface AuthProviderConfigFilter { - maxResults?: number; - pageToken?: string; - type: 'saml' | 'oidc'; -} - -// @public -export class BaseAuth { - createCustomToken(uid: string, developerClaims?: object): Promise; - createProviderConfig(config: AuthProviderConfig): Promise; - createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; - createUser(properties: CreateRequest): Promise; - deleteProviderConfig(providerId: string): Promise; - deleteUser(uid: string): Promise; - // (undocumented) - deleteUsers(uids: string[]): Promise; - generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; - generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; - generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise; - getProviderConfig(providerId: string): Promise; - getUser(uid: string): Promise; - getUserByEmail(email: string): Promise; - getUserByPhoneNumber(phoneNumber: string): Promise; - getUsers(identifiers: UserIdentifier[]): Promise; - importUsers(users: UserImportRecord[], options?: UserImportOptions): Promise; - listProviderConfigs(options: AuthProviderConfigFilter): Promise; - listUsers(maxResults?: number, pageToken?: string): Promise; - revokeRefreshTokens(uid: string): Promise; - setCustomUserClaims(uid: string, customUserClaims: object | null): Promise; - updateProviderConfig(providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise; - updateUser(uid: string, properties: UpdateRequest): Promise; - verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; - verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; -} - // @public (undocumented) export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; -// @public -export interface CreateMultiFactorInfoRequest { - displayName?: string; - factorId: string; -} - -// @public -export interface CreatePhoneMultiFactorInfoRequest extends CreateMultiFactorInfoRequest { - phoneNumber: string; -} - -// @public -export interface CreateRequest extends UpdateRequest { - multiFactor?: MultiFactorCreateSettings; - uid?: string; -} - -// @public -export type CreateTenantRequest = UpdateTenantRequest; - // @public (undocumented) export interface Credential { getAccessToken(): Promise; @@ -281,18 +290,13 @@ export namespace credential { const refreshToken: typeof refreshToken; } -// @public (undocumented) -export interface Database extends FirebaseDatabase { - getRules(): Promise; - getRulesJSON(): Promise; - setRules(source: string | Buffer | object): Promise; -} - // @public export function database(app?: App): database.Database; // @public (undocumented) export namespace database { + // Warning: (ae-forgotten-export) The symbol "Database" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type Database = Database; // (undocumented) @@ -312,62 +316,9 @@ export namespace database { const ServerValue: rtdb.ServerValue; } -export { DataSnapshot } - -// @public -export interface DecodedIdToken { - // (undocumented) - [key: string]: any; - aud: string; - auth_time: number; - email?: string; - email_verified?: boolean; - exp: number; - firebase: { - identities: { - [key: string]: any; - }; - sign_in_provider: string; - sign_in_second_factor?: string; - second_factor_identifier?: string; - tenant?: string; - [key: string]: any; - }; - iat: number; - iss: string; - phone_number?: string; - picture?: string; - sub: string; - uid: string; -} - // @public (undocumented) export function deleteApp(app: App): Promise; -// @public -export interface DeleteUsersResult { - errors: FirebaseArrayIndexError[]; - failureCount: number; - successCount: number; -} - -// @public -export interface EmailIdentifier { - // (undocumented) - email: string; -} - -// @public -export interface EmailSignInProviderConfig { - enabled: boolean; - passwordRequired?: boolean; -} - -// @public (undocumented) -export const enableLogging: typeof rtdb.enableLogging; - -export { EventType } - // @public export interface FirebaseArrayIndexError { error: FirebaseError; @@ -424,24 +375,6 @@ export function getApp(name?: string): App; // @public (undocumented) export function getApps(): App[]; -// @public (undocumented) -export function getAuth(app?: App): Auth; - -// @public (undocumented) -export function getDatabase(app?: App): Database; - -// @public (undocumented) -export function getDatabaseWithUrl(url: string, app?: App): Database; - -// @public (undocumented) -export function getInstanceId(app?: App): InstanceId; - -// @public -export interface GetUsersResult { - notFound: UserIdentifier[]; - users: UserRecord[]; -} - // @public export interface GoogleOAuthAccessToken { // (undocumented) @@ -450,45 +383,20 @@ export interface GoogleOAuthAccessToken { expires_in: number; } -// @public (undocumented) -export type HashAlgorithmType = 'SCRYPT' | 'STANDARD_SCRYPT' | 'HMAC_SHA512' | 'HMAC_SHA256' | 'HMAC_SHA1' | 'HMAC_MD5' | 'MD5' | 'PBKDF_SHA1' | 'BCRYPT' | 'PBKDF2_SHA256' | 'SHA512' | 'SHA256' | 'SHA1'; - // @public (undocumented) export function initializeApp(options?: AppOptions, name?: string): app.App; -// @public -export class InstanceId { - get app(): App; - deleteInstanceId(instanceId: string): Promise; - } - // @public export function instanceId(app?: App): instanceId.InstanceId; // @public (undocumented) export namespace instanceId { + // Warning: (ae-forgotten-export) The symbol "InstanceId" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) export type InstanceId = InstanceId; } -// @public -export interface ListProviderConfigResults { - pageToken?: string; - providerConfigs: AuthProviderConfig[]; -} - -// @public -export interface ListTenantsResult { - pageToken?: string; - tenants: Tenant[]; -} - -// @public -export interface ListUsersResult { - pageToken?: string; - users: UserRecord[]; -} - // @public export function machineLearning(app?: app.App): machineLearning.MachineLearning; @@ -843,74 +751,6 @@ export namespace messaging { {}; } -// @public (undocumented) -export interface MultiFactorConfig { - factorIds?: AuthFactorType[]; - state: MultiFactorConfigState; -} - -// @public -export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; - -// @public -export interface MultiFactorCreateSettings { - enrolledFactors: CreateMultiFactorInfoRequest[]; -} - -// @public -export abstract class MultiFactorInfo { - // (undocumented) - readonly displayName?: string; - // (undocumented) - readonly enrollmentTime?: string; - // (undocumented) - readonly factorId: string; - toJSON(): any; - // (undocumented) - readonly uid: string; -} - -// @public -export class MultiFactorSettings { - // (undocumented) - enrolledFactors: MultiFactorInfo[]; - toJSON(): any; -} - -// @public -export interface MultiFactorUpdateSettings { - enrolledFactors: UpdateMultiFactorInfoRequest[] | null; -} - -// @public -export interface OIDCAuthProviderConfig extends AuthProviderConfig { - clientId: string; - issuer: string; -} - -// @public -export interface OIDCUpdateAuthProviderRequest { - clientId?: string; - displayName?: string; - enabled?: boolean; - issuer?: string; -} - -export { OnDisconnect } - -// @public -export interface PhoneIdentifier { - // (undocumented) - phoneNumber: string; -} - -// @public -export class PhoneMultiFactorInfo extends MultiFactorInfo { - // (undocumented) - readonly phoneNumber: string; - toJSON(): any; -} - // @public export function projectManagement(app?: app.App): projectManagement.ProjectManagement; @@ -975,18 +815,6 @@ export namespace projectManagement { } } -// @public -export interface ProviderIdentifier { - // (undocumented) - providerId: string; - // (undocumented) - providerUid: string; -} - -export { Query } - -export { Reference } - // @public (undocumented) export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; @@ -1073,26 +901,6 @@ export namespace remoteConfig { } } -// @public -export interface SAMLAuthProviderConfig extends AuthProviderConfig { - callbackURL?: string; - idpEntityId: string; - rpEntityId: string; - ssoURL: string; - x509Certificates: string[]; -} - -// @public -export interface SAMLUpdateAuthProviderRequest { - callbackURL?: string; - displayName?: string; - enabled?: boolean; - idpEntityId?: string; - rpEntityId?: string; - ssoURL?: string; - x509Certificates?: string[]; -} - // @public (undocumented) export const SDK_VERSION: string; @@ -1136,9 +944,6 @@ export namespace securityRules { } } -// @public (undocumented) -export const ServerValue: rtdb.ServerValue; - // @public (undocumented) export interface ServiceAccount { // (undocumented) @@ -1149,11 +954,6 @@ export interface ServiceAccount { projectId?: string; } -// @public -export interface SessionCookieOptions { - expiresIn: number; -} - // @public export function storage(app?: app.App): storage.Storage; @@ -1166,212 +966,6 @@ export namespace storage { } } -// @public -export class Tenant { - // (undocumented) - readonly displayName?: string; - // (undocumented) - get emailSignInConfig(): EmailSignInProviderConfig | undefined; - // (undocumented) - get multiFactorConfig(): MultiFactorConfig | undefined; - // (undocumented) - readonly tenantId: string; - // (undocumented) - readonly testPhoneNumbers?: { - [phoneNumber: string]: string; - }; - toJSON(): object; - } - -// @public -export class TenantAwareAuth extends BaseAuth { - createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; - // (undocumented) - readonly tenantId: string; - verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; - verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; -} - -// @public -export class TenantManager { - authForTenant(tenantId: string): TenantAwareAuth; - createTenant(tenantOptions: CreateTenantRequest): Promise; - deleteTenant(tenantId: string): Promise; - getTenant(tenantId: string): Promise; - listTenants(maxResults?: number, pageToken?: string): Promise; - updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise; -} - -export { ThenableReference } - -// @public -export interface UidIdentifier { - // (undocumented) - uid: string; -} - -// @public (undocumented) -export type UpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; - -// @public -export interface UpdateMultiFactorInfoRequest { - displayName?: string; - enrollmentTime?: string; - factorId: string; - uid?: string; -} - -// @public -export interface UpdatePhoneMultiFactorInfoRequest extends UpdateMultiFactorInfoRequest { - phoneNumber: string; -} - -// @public -export interface UpdateRequest { - disabled?: boolean; - displayName?: string | null; - email?: string; - emailVerified?: boolean; - multiFactor?: MultiFactorUpdateSettings; - password?: string; - phoneNumber?: string | null; - photoURL?: string | null; -} - -// @public -export interface UpdateTenantRequest { - displayName?: string; - emailSignInConfig?: EmailSignInProviderConfig; - multiFactorConfig?: MultiFactorConfig; - testPhoneNumbers?: { - [phoneNumber: string]: string; - } | null; -} - -// @public -export type UserIdentifier = UidIdentifier | EmailIdentifier | PhoneIdentifier | ProviderIdentifier; - -// @public -export interface UserImportOptions { - hash: { - algorithm: HashAlgorithmType; - key?: Buffer; - saltSeparator?: Buffer; - rounds?: number; - memoryCost?: number; - parallelization?: number; - blockSize?: number; - derivedKeyLength?: number; - }; -} - -// @public -export interface UserImportRecord { - customClaims?: { - [key: string]: any; - }; - disabled?: boolean; - displayName?: string; - email?: string; - emailVerified?: boolean; - metadata?: UserMetadataRequest; - multiFactor?: MultiFactorUpdateSettings; - passwordHash?: Buffer; - passwordSalt?: Buffer; - phoneNumber?: string; - photoURL?: string; - providerData?: UserProviderRequest[]; - tenantId?: string; - uid: string; -} - -// @public -export interface UserImportResult { - errors: FirebaseArrayIndexError[]; - failureCount: number; - successCount: number; -} - -// @public -export class UserInfo { - // (undocumented) - readonly displayName: string; - // (undocumented) - readonly email: string; - // (undocumented) - readonly phoneNumber: string; - // (undocumented) - readonly photoURL: string; - // (undocumented) - readonly providerId: string; - toJSON(): object; - // (undocumented) - readonly uid: string; -} - -// @public -export class UserMetadata { - // (undocumented) - readonly creationTime: string; - readonly lastRefreshTime: string | null; - // (undocumented) - readonly lastSignInTime: string; - toJSON(): object; -} - -// @public -export interface UserMetadataRequest { - creationTime?: string; - lastSignInTime?: string; -} - -// @public -export interface UserProviderRequest { - displayName?: string; - email?: string; - phoneNumber?: string; - photoURL?: string; - providerId: string; - uid: string; -} - -// @public -export class UserRecord { - // (undocumented) - readonly customClaims: { - [key: string]: any; - }; - // (undocumented) - readonly disabled: boolean; - // (undocumented) - readonly displayName: string; - // (undocumented) - readonly email: string; - // (undocumented) - readonly emailVerified: boolean; - // (undocumented) - readonly metadata: UserMetadata; - // (undocumented) - readonly multiFactor?: MultiFactorSettings; - // (undocumented) - readonly passwordHash?: string; - // (undocumented) - readonly passwordSalt?: string; - // (undocumented) - readonly phoneNumber: string; - // (undocumented) - readonly photoURL: string; - // (undocumented) - readonly providerData: UserInfo[]; - // (undocumented) - readonly tenantId?: string | null; - toJSON(): object; - // (undocumented) - readonly tokensValidAfterTime?: string; - // (undocumented) - readonly uid: string; -} - // (No @packageDocumentation comment for this package) diff --git a/etc/firebase-admin.app.api.md b/etc/firebase-admin.app.api.md new file mode 100644 index 0000000000..96c5a4ea55 --- /dev/null +++ b/etc/firebase-admin.app.api.md @@ -0,0 +1,76 @@ +## API Report File for "firebase-admin.app" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; + +// @public (undocumented) +export interface App { + name: string; + options: AppOptions; +} + +// @public (undocumented) +export function applicationDefault(httpAgent?: Agent): Credential; + +// @public +export interface AppOptions { + credential?: Credential; + databaseAuthVariableOverride?: object | null; + databaseURL?: string; + httpAgent?: Agent; + projectId?: string; + serviceAccountId?: string; + storageBucket?: string; +} + +// @public (undocumented) +export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; + +// @public (undocumented) +export interface Credential { + getAccessToken(): Promise; +} + +// @public (undocumented) +export function deleteApp(app: App): Promise; + +// @public (undocumented) +export function getApp(name?: string): App; + +// @public (undocumented) +export function getApps(): App[]; + +// @public +export interface GoogleOAuthAccessToken { + // (undocumented) + access_token: string; + // (undocumented) + expires_in: number; +} + +// @public (undocumented) +export function initializeApp(options?: AppOptions, name?: string): App; + +// @public (undocumented) +export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; + +// @public (undocumented) +export const SDK_VERSION: string; + +// @public (undocumented) +export interface ServiceAccount { + // (undocumented) + clientEmail?: string; + // (undocumented) + privateKey?: string; + // (undocumented) + projectId?: string; +} + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md new file mode 100644 index 0000000000..7e10a8a3f1 --- /dev/null +++ b/etc/firebase-admin.auth.api.md @@ -0,0 +1,589 @@ +## API Report File for "firebase-admin.auth" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; + +// @public +export interface ActionCodeSettings { + android?: { + packageName: string; + installApp?: boolean; + minimumVersion?: string; + }; + dynamicLinkDomain?: string; + handleCodeInApp?: boolean; + iOS?: { + bundleId: string; + }; + url: string; +} + +// @public +export class Auth extends BaseAuth { + // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts + get app(): App; + tenantManager(): TenantManager; + } + +// @public +export function auth(app?: App): auth.Auth; + +// @public (undocumented) +export namespace auth { + // (undocumented) + export type ActionCodeSettings = ActionCodeSettings; + // (undocumented) + export type Auth = Auth; + // (undocumented) + export type AuthFactorType = AuthFactorType; + // (undocumented) + export type AuthProviderConfig = AuthProviderConfig; + // (undocumented) + export type AuthProviderConfigFilter = AuthProviderConfigFilter; + // (undocumented) + export type BaseAuth = BaseAuth; + // (undocumented) + export type CreateMultiFactorInfoRequest = CreateMultiFactorInfoRequest; + // (undocumented) + export type CreatePhoneMultiFactorInfoRequest = CreatePhoneMultiFactorInfoRequest; + // (undocumented) + export type CreateRequest = CreateRequest; + // (undocumented) + export type CreateTenantRequest = CreateTenantRequest; + // (undocumented) + export type DecodedIdToken = DecodedIdToken; + // (undocumented) + export type DeleteUsersResult = DeleteUsersResult; + // (undocumented) + export type EmailIdentifier = EmailIdentifier; + // (undocumented) + export type EmailSignInProviderConfig = EmailSignInProviderConfig; + // (undocumented) + export type GetUsersResult = GetUsersResult; + // (undocumented) + export type HashAlgorithmType = HashAlgorithmType; + // (undocumented) + export type ListProviderConfigResults = ListProviderConfigResults; + // (undocumented) + export type ListTenantsResult = ListTenantsResult; + // (undocumented) + export type ListUsersResult = ListUsersResult; + // (undocumented) + export type MultiFactorConfig = MultiFactorConfig; + // (undocumented) + export type MultiFactorConfigState = MultiFactorConfigState; + // (undocumented) + export type MultiFactorCreateSettings = MultiFactorCreateSettings; + // (undocumented) + export type MultiFactorInfo = MultiFactorInfo; + // (undocumented) + export type MultiFactorSettings = MultiFactorSettings; + // (undocumented) + export type MultiFactorUpdateSettings = MultiFactorUpdateSettings; + // (undocumented) + export type OIDCAuthProviderConfig = OIDCAuthProviderConfig; + // (undocumented) + export type OIDCUpdateAuthProviderRequest = OIDCUpdateAuthProviderRequest; + // (undocumented) + export type PhoneIdentifier = PhoneIdentifier; + // (undocumented) + export type PhoneMultiFactorInfo = PhoneMultiFactorInfo; + // (undocumented) + export type ProviderIdentifier = ProviderIdentifier; + // (undocumented) + export type SAMLAuthProviderConfig = SAMLAuthProviderConfig; + // (undocumented) + export type SAMLUpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest; + // (undocumented) + export type SessionCookieOptions = SessionCookieOptions; + // (undocumented) + export type Tenant = Tenant; + // (undocumented) + export type TenantAwareAuth = TenantAwareAuth; + // (undocumented) + export type TenantManager = TenantManager; + // (undocumented) + export type UidIdentifier = UidIdentifier; + // (undocumented) + export type UpdateAuthProviderRequest = UpdateAuthProviderRequest; + // (undocumented) + export type UpdateMultiFactorInfoRequest = UpdateMultiFactorInfoRequest; + // (undocumented) + export type UpdatePhoneMultiFactorInfoRequest = UpdatePhoneMultiFactorInfoRequest; + // (undocumented) + export type UpdateRequest = UpdateRequest; + // (undocumented) + export type UpdateTenantRequest = UpdateTenantRequest; + // (undocumented) + export type UserIdentifier = UserIdentifier; + // (undocumented) + export type UserImportOptions = UserImportOptions; + // (undocumented) + export type UserImportRecord = UserImportRecord; + // (undocumented) + export type UserImportResult = UserImportResult; + // (undocumented) + export type UserInfo = UserInfo; + // (undocumented) + export type UserMetadata = UserMetadata; + // (undocumented) + export type UserMetadataRequest = UserMetadataRequest; + // (undocumented) + export type UserProviderRequest = UserProviderRequest; + // (undocumented) + export type UserRecord = UserRecord; +} + +// @public +export type AuthFactorType = 'phone'; + +// @public +export interface AuthProviderConfig { + displayName?: string; + enabled: boolean; + providerId: string; +} + +// @public +export interface AuthProviderConfigFilter { + maxResults?: number; + pageToken?: string; + type: 'saml' | 'oidc'; +} + +// @public +export class BaseAuth { + createCustomToken(uid: string, developerClaims?: object): Promise; + createProviderConfig(config: AuthProviderConfig): Promise; + createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; + createUser(properties: CreateRequest): Promise; + deleteProviderConfig(providerId: string): Promise; + deleteUser(uid: string): Promise; + // (undocumented) + deleteUsers(uids: string[]): Promise; + generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; + generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; + generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise; + getProviderConfig(providerId: string): Promise; + getUser(uid: string): Promise; + getUserByEmail(email: string): Promise; + getUserByPhoneNumber(phoneNumber: string): Promise; + getUsers(identifiers: UserIdentifier[]): Promise; + importUsers(users: UserImportRecord[], options?: UserImportOptions): Promise; + listProviderConfigs(options: AuthProviderConfigFilter): Promise; + listUsers(maxResults?: number, pageToken?: string): Promise; + revokeRefreshTokens(uid: string): Promise; + setCustomUserClaims(uid: string, customUserClaims: object | null): Promise; + updateProviderConfig(providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise; + updateUser(uid: string, properties: UpdateRequest): Promise; + verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; + verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; +} + +// @public +export interface CreateMultiFactorInfoRequest { + displayName?: string; + factorId: string; +} + +// @public +export interface CreatePhoneMultiFactorInfoRequest extends CreateMultiFactorInfoRequest { + phoneNumber: string; +} + +// @public +export interface CreateRequest extends UpdateRequest { + multiFactor?: MultiFactorCreateSettings; + uid?: string; +} + +// @public +export type CreateTenantRequest = UpdateTenantRequest; + +// @public +export interface DecodedIdToken { + // (undocumented) + [key: string]: any; + aud: string; + auth_time: number; + email?: string; + email_verified?: boolean; + exp: number; + firebase: { + identities: { + [key: string]: any; + }; + sign_in_provider: string; + sign_in_second_factor?: string; + second_factor_identifier?: string; + tenant?: string; + [key: string]: any; + }; + iat: number; + iss: string; + phone_number?: string; + picture?: string; + sub: string; + uid: string; +} + +// @public +export interface DeleteUsersResult { + // Warning: (ae-forgotten-export) The symbol "FirebaseArrayIndexError" needs to be exported by the entry point index.d.ts + errors: FirebaseArrayIndexError[]; + failureCount: number; + successCount: number; +} + +// @public +export interface EmailIdentifier { + // (undocumented) + email: string; +} + +// @public +export interface EmailSignInProviderConfig { + enabled: boolean; + passwordRequired?: boolean; +} + +// @public (undocumented) +export function getAuth(app?: App): Auth; + +// @public +export interface GetUsersResult { + notFound: UserIdentifier[]; + users: UserRecord[]; +} + +// @public (undocumented) +export type HashAlgorithmType = 'SCRYPT' | 'STANDARD_SCRYPT' | 'HMAC_SHA512' | 'HMAC_SHA256' | 'HMAC_SHA1' | 'HMAC_MD5' | 'MD5' | 'PBKDF_SHA1' | 'BCRYPT' | 'PBKDF2_SHA256' | 'SHA512' | 'SHA256' | 'SHA1'; + +// @public +export interface ListProviderConfigResults { + pageToken?: string; + providerConfigs: AuthProviderConfig[]; +} + +// @public +export interface ListTenantsResult { + pageToken?: string; + tenants: Tenant[]; +} + +// @public +export interface ListUsersResult { + pageToken?: string; + users: UserRecord[]; +} + +// @public (undocumented) +export interface MultiFactorConfig { + factorIds?: AuthFactorType[]; + state: MultiFactorConfigState; +} + +// @public +export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; + +// @public +export interface MultiFactorCreateSettings { + enrolledFactors: CreateMultiFactorInfoRequest[]; +} + +// @public +export abstract class MultiFactorInfo { + // (undocumented) + readonly displayName?: string; + // (undocumented) + readonly enrollmentTime?: string; + // (undocumented) + readonly factorId: string; + toJSON(): any; + // (undocumented) + readonly uid: string; +} + +// @public +export class MultiFactorSettings { + // (undocumented) + enrolledFactors: MultiFactorInfo[]; + toJSON(): any; +} + +// @public +export interface MultiFactorUpdateSettings { + enrolledFactors: UpdateMultiFactorInfoRequest[] | null; +} + +// @public +export interface OIDCAuthProviderConfig extends AuthProviderConfig { + clientId: string; + issuer: string; +} + +// @public +export interface OIDCUpdateAuthProviderRequest { + clientId?: string; + displayName?: string; + enabled?: boolean; + issuer?: string; +} + +// @public +export interface PhoneIdentifier { + // (undocumented) + phoneNumber: string; +} + +// @public +export class PhoneMultiFactorInfo extends MultiFactorInfo { + // (undocumented) + readonly phoneNumber: string; + toJSON(): any; +} + +// @public +export interface ProviderIdentifier { + // (undocumented) + providerId: string; + // (undocumented) + providerUid: string; +} + +// @public +export interface SAMLAuthProviderConfig extends AuthProviderConfig { + callbackURL?: string; + idpEntityId: string; + rpEntityId: string; + ssoURL: string; + x509Certificates: string[]; +} + +// @public +export interface SAMLUpdateAuthProviderRequest { + callbackURL?: string; + displayName?: string; + enabled?: boolean; + idpEntityId?: string; + rpEntityId?: string; + ssoURL?: string; + x509Certificates?: string[]; +} + +// @public +export interface SessionCookieOptions { + expiresIn: number; +} + +// @public +export class Tenant { + // (undocumented) + readonly displayName?: string; + // (undocumented) + get emailSignInConfig(): EmailSignInProviderConfig | undefined; + // (undocumented) + get multiFactorConfig(): MultiFactorConfig | undefined; + // (undocumented) + readonly tenantId: string; + // (undocumented) + readonly testPhoneNumbers?: { + [phoneNumber: string]: string; + }; + toJSON(): object; + } + +// @public +export class TenantAwareAuth extends BaseAuth { + createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; + // (undocumented) + readonly tenantId: string; + verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; + verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; +} + +// @public +export class TenantManager { + authForTenant(tenantId: string): TenantAwareAuth; + createTenant(tenantOptions: CreateTenantRequest): Promise; + deleteTenant(tenantId: string): Promise; + getTenant(tenantId: string): Promise; + listTenants(maxResults?: number, pageToken?: string): Promise; + updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise; +} + +// @public +export interface UidIdentifier { + // (undocumented) + uid: string; +} + +// @public (undocumented) +export type UpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; + +// @public +export interface UpdateMultiFactorInfoRequest { + displayName?: string; + enrollmentTime?: string; + factorId: string; + uid?: string; +} + +// @public +export interface UpdatePhoneMultiFactorInfoRequest extends UpdateMultiFactorInfoRequest { + phoneNumber: string; +} + +// @public +export interface UpdateRequest { + disabled?: boolean; + displayName?: string | null; + email?: string; + emailVerified?: boolean; + multiFactor?: MultiFactorUpdateSettings; + password?: string; + phoneNumber?: string | null; + photoURL?: string | null; +} + +// @public +export interface UpdateTenantRequest { + displayName?: string; + emailSignInConfig?: EmailSignInProviderConfig; + multiFactorConfig?: MultiFactorConfig; + testPhoneNumbers?: { + [phoneNumber: string]: string; + } | null; +} + +// @public +export type UserIdentifier = UidIdentifier | EmailIdentifier | PhoneIdentifier | ProviderIdentifier; + +// @public +export interface UserImportOptions { + hash: { + algorithm: HashAlgorithmType; + key?: Buffer; + saltSeparator?: Buffer; + rounds?: number; + memoryCost?: number; + parallelization?: number; + blockSize?: number; + derivedKeyLength?: number; + }; +} + +// @public +export interface UserImportRecord { + customClaims?: { + [key: string]: any; + }; + disabled?: boolean; + displayName?: string; + email?: string; + emailVerified?: boolean; + metadata?: UserMetadataRequest; + multiFactor?: MultiFactorUpdateSettings; + passwordHash?: Buffer; + passwordSalt?: Buffer; + phoneNumber?: string; + photoURL?: string; + providerData?: UserProviderRequest[]; + tenantId?: string; + uid: string; +} + +// @public +export interface UserImportResult { + errors: FirebaseArrayIndexError[]; + failureCount: number; + successCount: number; +} + +// @public +export class UserInfo { + // (undocumented) + readonly displayName: string; + // (undocumented) + readonly email: string; + // (undocumented) + readonly phoneNumber: string; + // (undocumented) + readonly photoURL: string; + // (undocumented) + readonly providerId: string; + toJSON(): object; + // (undocumented) + readonly uid: string; +} + +// @public +export class UserMetadata { + // (undocumented) + readonly creationTime: string; + readonly lastRefreshTime: string | null; + // (undocumented) + readonly lastSignInTime: string; + toJSON(): object; +} + +// @public +export interface UserMetadataRequest { + creationTime?: string; + lastSignInTime?: string; +} + +// @public +export interface UserProviderRequest { + displayName?: string; + email?: string; + phoneNumber?: string; + photoURL?: string; + providerId: string; + uid: string; +} + +// @public +export class UserRecord { + // (undocumented) + readonly customClaims: { + [key: string]: any; + }; + // (undocumented) + readonly disabled: boolean; + // (undocumented) + readonly displayName: string; + // (undocumented) + readonly email: string; + // (undocumented) + readonly emailVerified: boolean; + // (undocumented) + readonly metadata: UserMetadata; + // (undocumented) + readonly multiFactor?: MultiFactorSettings; + // (undocumented) + readonly passwordHash?: string; + // (undocumented) + readonly passwordSalt?: string; + // (undocumented) + readonly phoneNumber: string; + // (undocumented) + readonly photoURL: string; + // (undocumented) + readonly providerData: UserInfo[]; + // (undocumented) + readonly tenantId?: string | null; + toJSON(): object; + // (undocumented) + readonly tokensValidAfterTime?: string; + // (undocumented) + readonly uid: string; +} + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/etc/firebase-admin.database.api.md b/etc/firebase-admin.database.api.md new file mode 100644 index 0000000000..0b8c5b0790 --- /dev/null +++ b/etc/firebase-admin.database.api.md @@ -0,0 +1,77 @@ +## API Report File for "firebase-admin.database" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; +import { DataSnapshot } from '@firebase/database-types'; +import { EventType } from '@firebase/database-types'; +import { FirebaseDatabase } from '@firebase/database-types'; +import { OnDisconnect } from '@firebase/database-types'; +import { Query } from '@firebase/database-types'; +import { Reference } from '@firebase/database-types'; +import * as rtdb from '@firebase/database-types'; +import { ThenableReference } from '@firebase/database-types'; + +// @public (undocumented) +export interface Database extends FirebaseDatabase { + getRules(): Promise; + getRulesJSON(): Promise; + setRules(source: string | Buffer | object): Promise; +} + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function database(app?: App): database.Database; + +// @public (undocumented) +export namespace database { + // (undocumented) + export type Database = Database; + // (undocumented) + export type DataSnapshot = rtdb.DataSnapshot; + // (undocumented) + export type EventType = rtdb.EventType; + // (undocumented) + export type OnDisconnect = rtdb.OnDisconnect; + // (undocumented) + export type Query = rtdb.Query; + // (undocumented) + export type Reference = rtdb.Reference; + // (undocumented) + export type ThenableReference = rtdb.ThenableReference; + const // (undocumented) + enableLogging: typeof rtdb.enableLogging; + const ServerValue: rtdb.ServerValue; +} + +export { DataSnapshot } + +// @public (undocumented) +export const enableLogging: typeof rtdb.enableLogging; + +export { EventType } + +// @public (undocumented) +export function getDatabase(app?: App): Database; + +// @public (undocumented) +export function getDatabaseWithUrl(url: string, app?: App): Database; + +export { OnDisconnect } + +export { Query } + +export { Reference } + +// @public (undocumented) +export const ServerValue: rtdb.ServerValue; + +export { ThenableReference } + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/etc/firebase-admin.instance-id.api.md b/etc/firebase-admin.instance-id.api.md new file mode 100644 index 0000000000..f5929e7efc --- /dev/null +++ b/etc/firebase-admin.instance-id.api.md @@ -0,0 +1,32 @@ +## API Report File for "firebase-admin.instance-id" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function getInstanceId(app?: App): InstanceId; + +// @public +export class InstanceId { + get app(): App; + deleteInstanceId(instanceId: string): Promise; + } + +// @public +export function instanceId(app?: App): instanceId.InstanceId; + +// @public (undocumented) +export namespace instanceId { + // (undocumented) + export type InstanceId = InstanceId; +} + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/generate-reports.js b/generate-reports.js new file mode 100644 index 0000000000..b5325f1e9e --- /dev/null +++ b/generate-reports.js @@ -0,0 +1,95 @@ +/** + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require('path'); +const fs = require('mz/fs'); +const yargs = require('yargs'); +const { Extractor, ExtractorConfig } = require('@microsoft/api-extractor'); + +const { local: localMode } = yargs + .option('local', { + boolean: true, + description: 'Run API Extractor with --local flag', + }) + .version(false) + .help().argv; + +// API Extractor configuration file. +const config = require('./api-extractor.json'); + +// List of module entry points. We generate a separate report for each entry point. +const entryPoints = { + 'firebase-admin': './lib/default-namespace.d.ts', + 'firebase-admin/app': './lib/app/index.d.ts', + 'firebase-admin/auth': './lib/auth/index.d.ts', + 'firebase-admin/database': './lib/database/index.d.ts', + 'firebase-admin/instance-id': './lib/instance-id/index.d.ts', +}; + +const tempConfigFile = 'api-extractor.tmp'; + +async function generateReports() { + for (const key in entryPoints) { + await generateReportForEntryPoint(key); + } +} + +async function generateReportForEntryPoint(key) { + console.log(`\nGenerating API report for ${key}`) + console.log('========================================================\n'); + + const safeName = key.replace('/', '.'); + console.log('Updating configuration for entry point...'); + config.apiReport.reportFileName = `${safeName}.api.md`; + config.mainEntryPointFilePath = entryPoints[key]; + console.log(`Report file name: ${config.apiReport.reportFileName}`); + console.log(`Entry point declaration: ${config.mainEntryPointFilePath}`); + await fs.writeFile(tempConfigFile, JSON.stringify(config)); + + try { + const configFile = ExtractorConfig.loadFile(tempConfigFile); + const extractorConfig = ExtractorConfig.prepare({ + configObject: configFile, + configObjectFullPath: path.resolve(tempConfigFile), + packageJson: { + name: safeName, + }, + packageJsonFullPath: path.resolve('package.json'), + }); + const extractorResult = Extractor.invoke(extractorConfig, { + localBuild: localMode, + showVerboseMessages: true + }); + if (!extractorResult.succeeded) { + throw new Error(`API Extractor completed with ${extractorResult.errorCount} errors` + + ` and ${extractorResult.warningCount} warnings`); + } + + console.error(`API Extractor completed successfully`); + } finally { + await fs.unlink(tempConfigFile); + } +} + +(async () => { + try { + await generateReports(); + } catch (err) { + console.log(err); + process.exit(1); + } +})(); diff --git a/package.json b/package.json index 21b22b48a4..1285586261 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "lint:src": "eslint src/ --ext .ts", "lint:test": "eslint test/ --ext .ts", "apidocs": "node docgen/generate-docs.js --api node", - "api-extractor": "api-extractor run", - "api-extractor:local": "npm run build && api-extractor run --local" + "api-extractor": "node generate-reports.js", + "api-extractor:local": "npm run build && node generate-reports.js --local" }, "nyc": { "extension": [ diff --git a/src/default-namespace.d.ts b/src/default-namespace.d.ts index 3de06b1bbb..188f904275 100644 --- a/src/default-namespace.d.ts +++ b/src/default-namespace.d.ts @@ -16,13 +16,13 @@ export * from './credential/index'; export * from './firebase-namespace-api'; -export * from './auth/index'; -export * from './database/index'; -export * from './firestore/index'; -export * from './instance-id/index'; -export * from './machine-learning/index'; -export * from './messaging/index'; -export * from './project-management/index'; -export * from './remote-config/index'; -export * from './security-rules/index'; -export * from './storage/index'; +export { auth } from './auth/index'; +export { database } from './database/index'; +export { firestore } from './firestore/index'; +export { instanceId } from './instance-id/index'; +export { machineLearning } from './machine-learning/index'; +export { messaging } from './messaging/index'; +export { projectManagement } from './project-management/index'; +export { remoteConfig } from './remote-config/index'; +export { securityRules } from './security-rules/index'; +export { storage } from './storage/index'; From 0168b1539828cd99404f2d6dfce9b9e86c1b20ca Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 2 Feb 2021 13:39:43 -0800 Subject: [PATCH 09/41] feat(firestore): Exposed Firestore APIs from firebase-admin/firestore entry point (#1151) --- etc/firebase-admin.api.md | 2 +- etc/firebase-admin.firestore.api.md | 138 ++++++++++++++++++++++++++++ generate-reports.js | 1 + gulpfile.js | 1 + src/app/firebase-app.ts | 8 +- src/firestore/firestore-internal.ts | 14 +-- src/firestore/index.ts | 48 +++++++++- test/unit/firestore/index.spec.ts | 73 +++++++++++++++ test/unit/index.spec.ts | 1 + 9 files changed, 270 insertions(+), 16 deletions(-) create mode 100644 etc/firebase-admin.firestore.api.md create mode 100644 test/unit/firestore/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index db1e843ede..f67bf14aee 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -334,7 +334,7 @@ export interface FirebaseError { } // @public (undocumented) -export function firestore(app?: app.App): _firestore.Firestore; +export function firestore(app?: App): _firestore.Firestore; // @public (undocumented) export namespace firestore { diff --git a/etc/firebase-admin.firestore.api.md b/etc/firebase-admin.firestore.api.md new file mode 100644 index 0000000000..e060e6d4c2 --- /dev/null +++ b/etc/firebase-admin.firestore.api.md @@ -0,0 +1,138 @@ +## API Report File for "firebase-admin.firestore" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; +import { BulkWriter } from '@google-cloud/firestore'; +import { BulkWriterOptions } from '@google-cloud/firestore'; +import { CollectionGroup } from '@google-cloud/firestore'; +import { CollectionReference } from '@google-cloud/firestore'; +import { DocumentChangeType } from '@google-cloud/firestore'; +import { DocumentData } from '@google-cloud/firestore'; +import { DocumentReference } from '@google-cloud/firestore'; +import { DocumentSnapshot } from '@google-cloud/firestore'; +import { FieldPath } from '@google-cloud/firestore'; +import { FieldValue } from '@google-cloud/firestore'; +import { Firestore } from '@google-cloud/firestore'; +import * as _firestore from '@google-cloud/firestore'; +import { FirestoreDataConverter } from '@google-cloud/firestore'; +import { GeoPoint } from '@google-cloud/firestore'; +import { GrpcStatus } from '@google-cloud/firestore'; +import { Precondition } from '@google-cloud/firestore'; +import { Query } from '@google-cloud/firestore'; +import { QueryDocumentSnapshot } from '@google-cloud/firestore'; +import { QueryPartition } from '@google-cloud/firestore'; +import { QuerySnapshot } from '@google-cloud/firestore'; +import { ReadOptions } from '@google-cloud/firestore'; +import { setLogFunction } from '@google-cloud/firestore'; +import { Settings } from '@google-cloud/firestore'; +import { Timestamp } from '@google-cloud/firestore'; +import { Transaction } from '@google-cloud/firestore'; +import { UpdateData } from '@google-cloud/firestore'; +import { v1 } from '@google-cloud/firestore'; +import { WriteBatch } from '@google-cloud/firestore'; +import { WriteResult } from '@google-cloud/firestore'; + +export { BulkWriter } + +export { BulkWriterOptions } + +export { CollectionGroup } + +export { CollectionReference } + +export { DocumentChangeType } + +export { DocumentData } + +export { DocumentReference } + +export { DocumentSnapshot } + +export { FieldPath } + +export { FieldValue } + +export { Firestore } + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function firestore(app?: App): _firestore.Firestore; + +// @public (undocumented) +export namespace firestore { + import v1beta1 = _firestore.v1beta1; + import v1 = _firestore.v1; + import BulkWriter = _firestore.BulkWriter; + import BulkWriterOptions = _firestore.BulkWriterOptions; + import CollectionGroup = _firestore.CollectionGroup; + import CollectionReference = _firestore.CollectionReference; + import DocumentChangeType = _firestore.DocumentChangeType; + import DocumentData = _firestore.DocumentData; + import DocumentReference = _firestore.DocumentReference; + import DocumentSnapshot = _firestore.DocumentSnapshot; + import FieldPath = _firestore.FieldPath; + import FieldValue = _firestore.FieldValue; + import Firestore = _firestore.Firestore; + import FirestoreDataConverter = _firestore.FirestoreDataConverter; + import GeoPoint = _firestore.GeoPoint; + import GrpcStatus = _firestore.GrpcStatus; + import Precondition = _firestore.Precondition; + import Query = _firestore.Query; + import QueryDocumentSnapshot = _firestore.QueryDocumentSnapshot; + import QueryPartition = _firestore.QueryPartition; + import QuerySnapshot = _firestore.QuerySnapshot; + import ReadOptions = _firestore.ReadOptions; + import Settings = _firestore.Settings; + import Timestamp = _firestore.Timestamp; + import Transaction = _firestore.Transaction; + import UpdateData = _firestore.UpdateData; + import WriteBatch = _firestore.WriteBatch; + import WriteResult = _firestore.WriteResult; + import setLogFunction = _firestore.setLogFunction; +} + +export { FirestoreDataConverter } + +export { GeoPoint } + +// @public (undocumented) +export function getFirestore(app?: App): _firestore.Firestore; + +export { GrpcStatus } + +export { Precondition } + +export { Query } + +export { QueryDocumentSnapshot } + +export { QueryPartition } + +export { QuerySnapshot } + +export { ReadOptions } + +export { setLogFunction } + +export { Settings } + +export { Timestamp } + +export { Transaction } + +export { UpdateData } + +export { v1 } + +export { WriteBatch } + +export { WriteResult } + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/generate-reports.js b/generate-reports.js index b5325f1e9e..7a0c95bdc1 100644 --- a/generate-reports.js +++ b/generate-reports.js @@ -37,6 +37,7 @@ const entryPoints = { 'firebase-admin/app': './lib/app/index.d.ts', 'firebase-admin/auth': './lib/auth/index.d.ts', 'firebase-admin/database': './lib/database/index.d.ts', + 'firebase-admin/firestore': './lib/firestore/index.d.ts', 'firebase-admin/instance-id': './lib/instance-id/index.d.ts', }; diff --git a/gulpfile.js b/gulpfile.js index 7d3580d7cf..b759ed89f9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -90,6 +90,7 @@ gulp.task('compile', function() { 'lib/app/*.d.ts', 'lib/auth/*.d.ts', 'lib/database/*.d.ts', + 'lib/firestore/*.d.ts', 'lib/instance-id/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index c1b748e132..40a3415b4a 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -29,7 +29,6 @@ import { Messaging } from '../messaging/messaging'; import { Storage } from '../storage/storage'; import { Database } from '../database/index'; import { Firestore } from '@google-cloud/firestore'; -import { FirestoreService } from '../firestore/firestore-internal'; import { InstanceId } from '../instance-id/index'; import { ProjectManagement } from '../project-management/project-management'; import { SecurityRules } from '../security-rules/security-rules'; @@ -313,11 +312,8 @@ export class FirebaseApp implements app.App { } public firestore(): Firestore { - const service: FirestoreService = this.ensureService_('firestore', () => { - const firestoreService: typeof FirestoreService = require('../firestore/firestore-internal').FirestoreService; - return new firestoreService(this); - }); - return service.client; + const fn = require('../firestore/index').getFirestore; + return fn(this); } /** diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts index ef83053ce4..7c4df98242 100644 --- a/src/firestore/firestore-internal.ts +++ b/src/firestore/firestore-internal.ts @@ -15,20 +15,20 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; import { FirebaseFirestoreError } from '../utils/error'; import { ServiceAccountCredential, isApplicationDefault } from '../app/credential-internal'; import { Firestore, Settings } from '@google-cloud/firestore'; import * as validator from '../utils/validator'; import * as utils from '../utils/index'; +import { App } from '../app'; export class FirestoreService { - private appInternal: FirebaseApp; + private appInternal: App; private firestoreClient: Firestore; - constructor(app: FirebaseApp) { + constructor(app: App) { this.firestoreClient = initFirestore(app); this.appInternal = app; } @@ -36,9 +36,9 @@ export class FirestoreService { /** * Returns the app associated with this Storage instance. * - * @return {FirebaseApp} The app associated with this Storage instance. + * @return The app associated with this Storage instance. */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } @@ -47,7 +47,7 @@ export class FirestoreService { } } -export function getFirestoreOptions(app: FirebaseApp): Settings { +export function getFirestoreOptions(app: App): Settings { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseFirestoreError({ code: 'invalid-argument', @@ -85,7 +85,7 @@ export function getFirestoreOptions(app: FirebaseApp): Settings { }); } -function initFirestore(app: FirebaseApp): Firestore { +function initFirestore(app: App): Firestore { const options = getFirestoreOptions(app); let firestoreDatabase: typeof Firestore; try { diff --git a/src/firestore/index.ts b/src/firestore/index.ts index 4efe99e053..ebfb3243dc 100644 --- a/src/firestore/index.ts +++ b/src/firestore/index.ts @@ -14,10 +14,54 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; import * as _firestore from '@google-cloud/firestore'; +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { FirestoreService } from './firestore-internal'; -export declare function firestore(app?: app.App): _firestore.Firestore; +export { + BulkWriter, + BulkWriterOptions, + CollectionGroup, + CollectionReference, + DocumentChangeType, + DocumentData, + DocumentReference, + DocumentSnapshot, + FieldPath, + FieldValue, + Firestore, + FirestoreDataConverter, + GeoPoint, + GrpcStatus, + Precondition, + Query, + QueryDocumentSnapshot, + QueryPartition, + QuerySnapshot, + ReadOptions, + Settings, + Timestamp, + Transaction, + UpdateData, + WriteBatch, + WriteResult, + v1, + setLogFunction, +} from '@google-cloud/firestore'; + +export function getFirestore(app?: App): _firestore.Firestore { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + const firestoreService = firebaseApp.getOrInitService( + 'firestore', (app) => new FirestoreService(app)); + return firestoreService.client; +} + +export declare function firestore(app?: App): _firestore.Firestore; /* eslint-disable @typescript-eslint/no-namespace */ export namespace firestore { diff --git a/test/unit/firestore/index.spec.ts b/test/unit/firestore/index.spec.ts new file mode 100644 index 0000000000..5cfd800507 --- /dev/null +++ b/test/unit/firestore/index.spec.ts @@ -0,0 +1,73 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getFirestore, Firestore } from '../../../src/firestore/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Firestore', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to initialize Google Cloud Firestore client with the ' + + 'available credentials. Must initialize the SDK with a certificate credential or ' + + 'application default credentials to use Cloud Firestore API.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getFirestore()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getFirestore(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + expect(() => getFirestore(mockCredentialApp)).to.throw(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getFirestore(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const db1: Firestore = getFirestore(mockApp); + const db2: Firestore = getFirestore(mockApp); + expect(db1).to.equal(db2); + }); + }); +}); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 256c2112aa..f1b5a0d647 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -58,6 +58,7 @@ import './storage/storage.spec'; // Firestore import './firestore/firestore.spec'; +import './firestore/index.spec'; // InstanceId import './instance-id/index.spec'; From b865bf9f3cf67d8fa1986dbfcdb2925c9433235b Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 2 Feb 2021 14:42:26 -0800 Subject: [PATCH 10/41] feat(rc): Exposed RC APIs from firebase-admin/remote-config entry point (#1152) * feat(rc): Exposed RC APIs from firebase-admin/remote-config entry point * Update test/unit/remote-config/index.spec.ts Co-authored-by: Lahiru Maramba Co-authored-by: Lahiru Maramba --- etc/firebase-admin.api.md | 130 +++--- etc/firebase-admin.remote-config.api.md | 152 +++++++ generate-reports.js | 1 + gulpfile.js | 1 + src/app/firebase-app.ts | 8 +- src/remote-config/index.ts | 407 +++--------------- .../remote-config-api-client-internal.ts | 15 +- src/remote-config/remote-config-api.ts | 279 ++++++++++++ src/remote-config/remote-config.ts | 28 +- test/unit/index.spec.ts | 1 + test/unit/remote-config/index.spec.ts | 75 ++++ test/unit/remote-config/remote-config.spec.ts | 14 +- 12 files changed, 650 insertions(+), 461 deletions(-) create mode 100644 etc/firebase-admin.remote-config.api.md create mode 100644 src/remote-config/remote-config-api.ts create mode 100644 test/unit/remote-config/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index f67bf14aee..de8a48d4a9 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -819,86 +819,62 @@ export namespace projectManagement { export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; // @public -export function remoteConfig(app?: app.App): remoteConfig.RemoteConfig; +export function remoteConfig(app?: App): remoteConfig.RemoteConfig; // @public (undocumented) export namespace remoteConfig { - export interface ExplicitParameterValue { - value: string; - } - export interface InAppDefaultValue { - useInAppDefault: boolean; - } - export interface ListVersionsOptions { - endTime?: Date | string; - endVersionNumber?: string | number; - pageSize?: number; - pageToken?: string; - startTime?: Date | string; - } - export interface ListVersionsResult { - nextPageToken?: string; - versions: Version[]; - } - export interface RemoteConfig { - // (undocumented) - app: app.App; - createTemplateFromJSON(json: string): RemoteConfigTemplate; - getTemplate(): Promise; - getTemplateAtVersion(versionNumber: number | string): Promise; - listVersions(options?: ListVersionsOptions): Promise; - publishTemplate(template: RemoteConfigTemplate, options?: { - force: boolean; - }): Promise; - rollback(versionNumber: string | number): Promise; - validateTemplate(template: RemoteConfigTemplate): Promise; - } - export interface RemoteConfigCondition { - expression: string; - name: string; - tagColor?: TagColor; - } - export interface RemoteConfigParameter { - conditionalValues?: { - [key: string]: RemoteConfigParameterValue; - }; - defaultValue?: RemoteConfigParameterValue; - description?: string; - } - export interface RemoteConfigParameterGroup { - description?: string; - parameters: { - [key: string]: RemoteConfigParameter; - }; - } - export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; - export interface RemoteConfigTemplate { - conditions: RemoteConfigCondition[]; - readonly etag: string; - parameterGroups: { - [key: string]: RemoteConfigParameterGroup; - }; - parameters: { - [key: string]: RemoteConfigParameter; - }; - version?: Version; - } - export interface RemoteConfigUser { - email: string; - imageUrl?: string; - name?: string; - } - export type TagColor = 'BLUE' | 'BROWN' | 'CYAN' | 'DEEP_ORANGE' | 'GREEN' | 'INDIGO' | 'LIME' | 'ORANGE' | 'PINK' | 'PURPLE' | 'TEAL'; - export interface Version { - description?: string; - isLegacy?: boolean; - rollbackSource?: string; - updateOrigin?: ('REMOTE_CONFIG_UPDATE_ORIGIN_UNSPECIFIED' | 'CONSOLE' | 'REST_API' | 'ADMIN_SDK_NODE'); - updateTime?: string; - updateType?: ('REMOTE_CONFIG_UPDATE_TYPE_UNSPECIFIED' | 'INCREMENTAL_UPDATE' | 'FORCED_UPDATE' | 'ROLLBACK'); - updateUser?: RemoteConfigUser; - versionNumber?: string; - } + // Warning: (ae-forgotten-export) The symbol "ExplicitParameterValue" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type ExplicitParameterValue = ExplicitParameterValue; + // Warning: (ae-forgotten-export) The symbol "InAppDefaultValue" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type InAppDefaultValue = InAppDefaultValue; + // Warning: (ae-forgotten-export) The symbol "ListVersionsOptions" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type ListVersionsOptions = ListVersionsOptions; + // Warning: (ae-forgotten-export) The symbol "ListVersionsResult" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type ListVersionsResult = ListVersionsResult; + // Warning: (ae-forgotten-export) The symbol "RemoteConfig" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type RemoteConfig = RemoteConfig; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigCondition" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type RemoteConfigCondition = RemoteConfigCondition; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigParameter" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type RemoteConfigParameter = RemoteConfigParameter; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigParameterGroup" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type RemoteConfigParameterGroup = RemoteConfigParameterGroup; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigParameterValue" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type RemoteConfigParameterValue = RemoteConfigParameterValue; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigTemplate" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type RemoteConfigTemplate = RemoteConfigTemplate; + // Warning: (ae-forgotten-export) The symbol "RemoteConfigUser" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type RemoteConfigUser = RemoteConfigUser; + // Warning: (ae-forgotten-export) The symbol "TagColor" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type TagColor = TagColor; + // Warning: (ae-forgotten-export) The symbol "Version" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type Version = Version; } // @public (undocumented) diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md new file mode 100644 index 0000000000..eee1507fba --- /dev/null +++ b/etc/firebase-admin.remote-config.api.md @@ -0,0 +1,152 @@ +## API Report File for "firebase-admin.remote-config" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; + +// @public +export interface ExplicitParameterValue { + value: string; +} + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function getRemoteConfig(app?: App): RemoteConfig; + +// @public +export interface InAppDefaultValue { + useInAppDefault: boolean; +} + +// @public +export interface ListVersionsOptions { + endTime?: Date | string; + endVersionNumber?: string | number; + pageSize?: number; + pageToken?: string; + startTime?: Date | string; +} + +// @public +export interface ListVersionsResult { + nextPageToken?: string; + versions: Version[]; +} + +// @public +export class RemoteConfig { + // (undocumented) + readonly app: App; + createTemplateFromJSON(json: string): RemoteConfigTemplate; + getTemplate(): Promise; + getTemplateAtVersion(versionNumber: number | string): Promise; + listVersions(options?: ListVersionsOptions): Promise; + publishTemplate(template: RemoteConfigTemplate, options?: { + force: boolean; + }): Promise; + rollback(versionNumber: number | string): Promise; + validateTemplate(template: RemoteConfigTemplate): Promise; +} + +// @public +export function remoteConfig(app?: App): remoteConfig.RemoteConfig; + +// @public (undocumented) +export namespace remoteConfig { + // (undocumented) + export type ExplicitParameterValue = ExplicitParameterValue; + // (undocumented) + export type InAppDefaultValue = InAppDefaultValue; + // (undocumented) + export type ListVersionsOptions = ListVersionsOptions; + // (undocumented) + export type ListVersionsResult = ListVersionsResult; + // (undocumented) + export type RemoteConfig = RemoteConfig; + // (undocumented) + export type RemoteConfigCondition = RemoteConfigCondition; + // (undocumented) + export type RemoteConfigParameter = RemoteConfigParameter; + // (undocumented) + export type RemoteConfigParameterGroup = RemoteConfigParameterGroup; + // (undocumented) + export type RemoteConfigParameterValue = RemoteConfigParameterValue; + // (undocumented) + export type RemoteConfigTemplate = RemoteConfigTemplate; + // (undocumented) + export type RemoteConfigUser = RemoteConfigUser; + // (undocumented) + export type TagColor = TagColor; + // (undocumented) + export type Version = Version; +} + +// @public +export interface RemoteConfigCondition { + expression: string; + name: string; + tagColor?: TagColor; +} + +// @public +export interface RemoteConfigParameter { + conditionalValues?: { + [key: string]: RemoteConfigParameterValue; + }; + defaultValue?: RemoteConfigParameterValue; + description?: string; +} + +// @public +export interface RemoteConfigParameterGroup { + description?: string; + parameters: { + [key: string]: RemoteConfigParameter; + }; +} + +// @public +export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; + +// @public +export interface RemoteConfigTemplate { + conditions: RemoteConfigCondition[]; + readonly etag: string; + parameterGroups: { + [key: string]: RemoteConfigParameterGroup; + }; + parameters: { + [key: string]: RemoteConfigParameter; + }; + version?: Version; +} + +// @public +export interface RemoteConfigUser { + email: string; + imageUrl?: string; + name?: string; +} + +// @public +export type TagColor = 'BLUE' | 'BROWN' | 'CYAN' | 'DEEP_ORANGE' | 'GREEN' | 'INDIGO' | 'LIME' | 'ORANGE' | 'PINK' | 'PURPLE' | 'TEAL'; + +// @public +export interface Version { + description?: string; + isLegacy?: boolean; + rollbackSource?: string; + updateOrigin?: ('REMOTE_CONFIG_UPDATE_ORIGIN_UNSPECIFIED' | 'CONSOLE' | 'REST_API' | 'ADMIN_SDK_NODE'); + updateTime?: string; + updateType?: ('REMOTE_CONFIG_UPDATE_TYPE_UNSPECIFIED' | 'INCREMENTAL_UPDATE' | 'FORCED_UPDATE' | 'ROLLBACK'); + updateUser?: RemoteConfigUser; + versionNumber?: string; +} + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/generate-reports.js b/generate-reports.js index 7a0c95bdc1..b9444e9088 100644 --- a/generate-reports.js +++ b/generate-reports.js @@ -39,6 +39,7 @@ const entryPoints = { 'firebase-admin/database': './lib/database/index.d.ts', 'firebase-admin/firestore': './lib/firestore/index.d.ts', 'firebase-admin/instance-id': './lib/instance-id/index.d.ts', + 'firebase-admin/remote-config': './lib/remote-config/index.d.ts', }; const tempConfigFile = 'api-extractor.tmp'; diff --git a/gulpfile.js b/gulpfile.js index b759ed89f9..d15c4acc57 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -92,6 +92,7 @@ gulp.task('compile', function() { 'lib/database/*.d.ts', 'lib/firestore/*.d.ts', 'lib/instance-id/*.d.ts', + 'lib/remote-config/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 40a3415b4a..ff5bfd836f 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -32,7 +32,7 @@ import { Firestore } from '@google-cloud/firestore'; import { InstanceId } from '../instance-id/index'; import { ProjectManagement } from '../project-management/project-management'; import { SecurityRules } from '../security-rules/security-rules'; -import { RemoteConfig } from '../remote-config/remote-config'; +import { RemoteConfig } from '../remote-config/index'; /** * Type representing a callback which is called every time an app lifecycle event occurs. @@ -371,10 +371,8 @@ export class FirebaseApp implements app.App { * @return The RemoteConfig service instance of this app. */ public remoteConfig(): RemoteConfig { - return this.ensureService_('remoteConfig', () => { - const remoteConfigService: typeof RemoteConfig = require('../remote-config/remote-config').RemoteConfig; - return new remoteConfigService(this); - }); + const fn = require('../remote-config/index').getRemoteConfig; + return fn(this); } /** diff --git a/src/remote-config/index.ts b/src/remote-config/index.ts index 668fee3372..b7fd3fc15e 100644 --- a/src/remote-config/index.ts +++ b/src/remote-config/index.ts @@ -14,7 +14,50 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { RemoteConfig } from './remote-config'; + +export { + ExplicitParameterValue, + InAppDefaultValue, + ListVersionsOptions, + ListVersionsResult, + RemoteConfigCondition, + RemoteConfigParameter, + RemoteConfigParameterGroup, + RemoteConfigParameterValue, + RemoteConfigTemplate, + RemoteConfigUser, + TagColor, + Version, +} from './remote-config-api'; +export { RemoteConfig } from './remote-config'; + +export function getRemoteConfig(app?: App): RemoteConfig { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('remoteConfig', (app) => new RemoteConfig(app)); +} + +import { + ExplicitParameterValue as TExplicitParameterValue, + InAppDefaultValue as TInAppDefaultValue, + ListVersionsOptions as TListVersionsOptions, + ListVersionsResult as TListVersionsResult, + RemoteConfigCondition as TRemoteConfigCondition, + RemoteConfigParameter as TRemoteConfigParameter, + RemoteConfigParameterGroup as TRemoteConfigParameterGroup, + RemoteConfigParameterValue as TRemoteConfigParameterValue, + RemoteConfigTemplate as TRemoteConfigTemplate, + RemoteConfigUser as TRemoteConfigUser, + TagColor as TTagColor, + Version as TVersion, +} from './remote-config-api'; +import { RemoteConfig as TRemoteConfig } from './remote-config'; /** * Gets the {@link remoteConfig.RemoteConfig `RemoteConfig`} service for the @@ -45,355 +88,21 @@ import { app } from '../firebase-namespace-api'; * app is provided, or the `RemoteConfig` service associated with the provided * app. */ -export declare function remoteConfig(app?: app.App): remoteConfig.RemoteConfig; +export declare function remoteConfig(app?: App): remoteConfig.RemoteConfig; /* eslint-disable @typescript-eslint/no-namespace */ export namespace remoteConfig { - /** - * Interface representing options for Remote Config list versions operation. - */ - export interface ListVersionsOptions { - /** - * The maximum number of items to return per page. - */ - pageSize?: number; - - /** - * The `nextPageToken` value returned from a previous list versions request, if any. - */ - pageToken?: string; - - /** - * Specifies the newest version number to include in the results. - * If specified, must be greater than zero. Defaults to the newest version. - */ - endVersionNumber?: string | number; - - /** - * Specifies the earliest update time to include in the results. Any entries updated before this - * time are omitted. - */ - startTime?: Date | string; - - /** - * Specifies the latest update time to include in the results. Any entries updated on or after - * this time are omitted. - */ - endTime?: Date | string; - } - - /** - * Interface representing a list of Remote Config template versions. - */ - export interface ListVersionsResult { - /** - * A list of version metadata objects, sorted in reverse chronological order. - */ - versions: Version[]; - - /** - * Token to retrieve the next page of results, or empty if there are no more results - * in the list. - */ - nextPageToken?: string; - } - - /** - * Interface representing a Remote Config condition. - * A condition targets a specific group of users. A list of these conditions make up - * part of a Remote Config template. - */ - export interface RemoteConfigCondition { - - /** - * A non-empty and unique name of this condition. - */ - name: string; - - /** - * The logic of this condition. - * See the documentation on - * {@link https://firebase.google.com/docs/remote-config/condition-reference condition expressions} - * for the expected syntax of this field. - */ - expression: string; - - /** - * The color associated with this condition for display purposes in the Firebase Console. - * Not specifying this value results in the console picking an arbitrary color to associate - * with the condition. - */ - tagColor?: TagColor; - } - - /** - * Interface representing a Remote Config parameter. - * At minimum, a `defaultValue` or a `conditionalValues` entry must be present for the - * parameter to have any effect. - */ - export interface RemoteConfigParameter { - - /** - * The value to set the parameter to, when none of the named conditions evaluate to `true`. - */ - defaultValue?: RemoteConfigParameterValue; - - /** - * A `(condition name, value)` map. The condition name of the highest priority - * (the one listed first in the Remote Config template's conditions list) determines the value of - * this parameter. - */ - conditionalValues?: { [key: string]: RemoteConfigParameterValue }; - - /** - * A description for this parameter. Should not be over 100 characters and may contain any - * Unicode characters. - */ - description?: string; - } - - /** - * Interface representing a Remote Config parameter group. - * Grouping parameters is only for management purposes and does not affect client-side - * fetching of parameter values. - */ - export interface RemoteConfigParameterGroup { - /** - * A description for the group. Its length must be less than or equal to 256 characters. - * A description may contain any Unicode characters. - */ - description?: string; - - /** - * Map of parameter keys to their optional default values and optional conditional values for - * parameters that belong to this group. A parameter only appears once per - * Remote Config template. An ungrouped parameter appears at the top level, whereas a - * parameter organized within a group appears within its group's map of parameters. - */ - parameters: { [key: string]: RemoteConfigParameter }; - } - - /** - * Interface representing an explicit parameter value. - */ - export interface ExplicitParameterValue { - /** - * The `string` value that the parameter is set to. - */ - value: string; - } - - /** - * Interface representing an in-app-default value. - */ - export interface InAppDefaultValue { - /** - * If `true`, the parameter is omitted from the parameter values returned to a client. - */ - useInAppDefault: boolean; - } - - /** - * Type representing a Remote Config parameter value. - * A `RemoteConfigParameterValue` could be either an `ExplicitParameterValue` or - * an `InAppDefaultValue`. - */ - export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; - - /** - * Interface representing a Remote Config template. - */ - export interface RemoteConfigTemplate { - /** - * A list of conditions in descending order by priority. - */ - conditions: RemoteConfigCondition[]; - - /** - * Map of parameter keys to their optional default values and optional conditional values. - */ - parameters: { [key: string]: RemoteConfigParameter }; - - /** - * Map of parameter group names to their parameter group objects. - * A group's name is mutable but must be unique among groups in the Remote Config template. - * The name is limited to 256 characters and intended to be human-readable. Any Unicode - * characters are allowed. - */ - parameterGroups: { [key: string]: RemoteConfigParameterGroup }; - - /** - * ETag of the current Remote Config template (readonly). - */ - readonly etag: string; - - /** - * Version information for the current Remote Config template. - */ - version?: Version; - } - - /** - * Interface representing a Remote Config user. - */ - export interface RemoteConfigUser { - /** - * Email address. Output only. - */ - email: string; - - /** - * Display name. Output only. - */ - name?: string; - - /** - * Image URL. Output only. - */ - imageUrl?: string; - } - - /** - * Colors that are associated with conditions for display purposes. - */ - export type TagColor = 'BLUE' | 'BROWN' | 'CYAN' | 'DEEP_ORANGE' | 'GREEN' | - 'INDIGO' | 'LIME' | 'ORANGE' | 'PINK' | 'PURPLE' | 'TEAL'; - - /** - * Interface representing a Remote Config template version. - * Output only, except for the version description. Contains metadata about a particular - * version of the Remote Config template. All fields are set at the time the specified Remote - * Config template is published. A version's description field may be specified in - * `publishTemplate` calls. - */ - export interface Version { - /** - * The version number of a Remote Config template. - */ - versionNumber?: string; - - /** - * The timestamp of when this version of the Remote Config template was written to the - * Remote Config backend. - */ - updateTime?: string; - - /** - * The origin of the template update action. - */ - updateOrigin?: ('REMOTE_CONFIG_UPDATE_ORIGIN_UNSPECIFIED' | 'CONSOLE' | - 'REST_API' | 'ADMIN_SDK_NODE'); - - /** - * The type of the template update action. - */ - updateType?: ('REMOTE_CONFIG_UPDATE_TYPE_UNSPECIFIED' | - 'INCREMENTAL_UPDATE' | 'FORCED_UPDATE' | 'ROLLBACK'); - - /** - * Aggregation of all metadata fields about the account that performed the update. - */ - updateUser?: RemoteConfigUser; - - /** - * The user-provided description of the corresponding Remote Config template. - */ - description?: string; - - /** - * The version number of the Remote Config template that has become the current version - * due to a rollback. Only present if this version is the result of a rollback. - */ - rollbackSource?: string; - - /** - * Indicates whether this Remote Config template was published before version history was - * supported. - */ - isLegacy?: boolean; - } - - /** - * The Firebase `RemoteConfig` service interface. - */ - export interface RemoteConfig { - app: app.App; - - /** - * Gets the current active version of the {@link remoteConfig.RemoteConfigTemplate - * `RemoteConfigTemplate`} of the project. - * - * @return A promise that fulfills with a `RemoteConfigTemplate`. - */ - getTemplate(): Promise; - - /** - * Gets the requested version of the {@link remoteConfig.RemoteConfigTemplate - * `RemoteConfigTemplate`} of the project. - * - * @param versionNumber Version number of the Remote Config template to look up. - * - * @return A promise that fulfills with a `RemoteConfigTemplate`. - */ - getTemplateAtVersion(versionNumber: number | string): Promise; - - /** - * Validates a {@link remoteConfig.RemoteConfigTemplate `RemoteConfigTemplate`}. - * - * @param template The Remote Config template to be validated. - * @returns A promise that fulfills with the validated `RemoteConfigTemplate`. - */ - validateTemplate(template: RemoteConfigTemplate): Promise; - - /** - * Publishes a Remote Config template. - * - * @param template The Remote Config template to be published. - * @param options Optional options object when publishing a Remote Config template: - * - {boolean} `force` Setting this to `true` forces the Remote Config template to - * be updated and circumvent the ETag. This approach is not recommended - * because it risks causing the loss of updates to your Remote Config - * template if multiple clients are updating the Remote Config template. - * See {@link https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates - * ETag usage and forced updates}. - * - * @return A Promise that fulfills with the published `RemoteConfigTemplate`. - */ - publishTemplate(template: RemoteConfigTemplate, options?: { force: boolean }): Promise; - - /** - * Rolls back a project's published Remote Config template to the specified version. - * A rollback is equivalent to getting a previously published Remote Config - * template and re-publishing it using a force update. - * - * @param versionNumber The version number of the Remote Config template to roll back to. - * The specified version number must be lower than the current version number, and not have - * been deleted due to staleness. Only the last 300 versions are stored. - * All versions that correspond to non-active Remote Config templates (that is, all except the - * template that is being fetched by clients) are also deleted if they are more than 90 days old. - * @return A promise that fulfills with the published `RemoteConfigTemplate`. - */ - rollback(versionNumber: string | number): Promise; - - /** - * Gets a list of Remote Config template versions that have been published, sorted in reverse - * chronological order. Only the last 300 versions are stored. - * All versions that correspond to non-active Remote Config templates (that is, all except the - * template that is being fetched by clients) are also deleted if they are more than 90 days old. - * - * @param options Optional {@link remoteConfig.ListVersionsOptions `ListVersionsOptions`} - * object for getting a list of template versions. - * @return A promise that fulfills with a `ListVersionsResult`. - */ - listVersions(options?: ListVersionsOptions): Promise; - - /** - * Creates and returns a new Remote Config template from a JSON string. - * - * @param json The JSON string to populate a Remote Config template. - * - * @return A new template instance. - */ - createTemplateFromJSON(json: string): RemoteConfigTemplate; - } + export type ExplicitParameterValue = TExplicitParameterValue; + export type InAppDefaultValue = TInAppDefaultValue; + export type ListVersionsOptions = TListVersionsOptions; + export type ListVersionsResult = TListVersionsResult; + export type RemoteConfig = TRemoteConfig; + export type RemoteConfigCondition = TRemoteConfigCondition; + export type RemoteConfigParameter = TRemoteConfigParameter; + export type RemoteConfigParameterGroup = TRemoteConfigParameterGroup; + export type RemoteConfigParameterValue = TRemoteConfigParameterValue; + export type RemoteConfigTemplate = TRemoteConfigTemplate; + export type RemoteConfigUser = TRemoteConfigUser; + export type TagColor = TTagColor; + export type Version = TVersion; } diff --git a/src/remote-config/remote-config-api-client-internal.ts b/src/remote-config/remote-config-api-client-internal.ts index bb7b5a7eed..1e91b497f3 100644 --- a/src/remote-config/remote-config-api-client-internal.ts +++ b/src/remote-config/remote-config-api-client-internal.ts @@ -14,17 +14,14 @@ * limitations under the License. */ -import { remoteConfig } from './index'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, HttpError, AuthorizedHttpClient, HttpResponse } from '../utils/api-request'; import { PrefixedFirebaseError } from '../utils/error'; -import { FirebaseApp } from '../app/firebase-app'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; - -import RemoteConfigTemplate = remoteConfig.RemoteConfigTemplate; -import ListVersionsOptions = remoteConfig.ListVersionsOptions; -import ListVersionsResult = remoteConfig.ListVersionsResult; +import { ListVersionsOptions, ListVersionsResult, RemoteConfigTemplate } from './remote-config-api'; // Remote Config backend constants const FIREBASE_REMOTE_CONFIG_V1_API = 'https://firebaseremoteconfig.googleapis.com/v1'; @@ -41,20 +38,20 @@ const FIREBASE_REMOTE_CONFIG_HEADERS = { /** * Class that facilitates sending requests to the Firebase Remote Config backend API. * - * @private + * @internal */ export class RemoteConfigApiClient { private readonly httpClient: HttpClient; private projectIdPrefix?: string; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseRemoteConfigError( 'invalid-argument', 'First argument passed to admin.remoteConfig() must be a valid Firebase app instance.'); } - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } public getTemplate(): Promise { diff --git a/src/remote-config/remote-config-api.ts b/src/remote-config/remote-config-api.ts new file mode 100644 index 0000000000..9493e855d8 --- /dev/null +++ b/src/remote-config/remote-config-api.ts @@ -0,0 +1,279 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Colors that are associated with conditions for display purposes. + */ +export type TagColor = 'BLUE' | 'BROWN' | 'CYAN' | 'DEEP_ORANGE' | 'GREEN' | + 'INDIGO' | 'LIME' | 'ORANGE' | 'PINK' | 'PURPLE' | 'TEAL'; + +/** + * Interface representing a Remote Config condition. + * A condition targets a specific group of users. A list of these conditions make up + * part of a Remote Config template. + */ +export interface RemoteConfigCondition { + + /** + * A non-empty and unique name of this condition. + */ + name: string; + + /** + * The logic of this condition. + * See the documentation on + * {@link https://firebase.google.com/docs/remote-config/condition-reference condition expressions} + * for the expected syntax of this field. + */ + expression: string; + + /** + * The color associated with this condition for display purposes in the Firebase Console. + * Not specifying this value results in the console picking an arbitrary color to associate + * with the condition. + */ + tagColor?: TagColor; +} + +/** + * Interface representing an explicit parameter value. + */ +export interface ExplicitParameterValue { + /** + * The `string` value that the parameter is set to. + */ + value: string; +} + +/** + * Interface representing an in-app-default value. + */ +export interface InAppDefaultValue { + /** + * If `true`, the parameter is omitted from the parameter values returned to a client. + */ + useInAppDefault: boolean; +} + +/** + * Type representing a Remote Config parameter value. + * A `RemoteConfigParameterValue` could be either an `ExplicitParameterValue` or + * an `InAppDefaultValue`. + */ +export type RemoteConfigParameterValue = ExplicitParameterValue | InAppDefaultValue; + +/** + * Interface representing a Remote Config parameter. + * At minimum, a `defaultValue` or a `conditionalValues` entry must be present for the + * parameter to have any effect. + */ +export interface RemoteConfigParameter { + + /** + * The value to set the parameter to, when none of the named conditions evaluate to `true`. + */ + defaultValue?: RemoteConfigParameterValue; + + /** + * A `(condition name, value)` map. The condition name of the highest priority + * (the one listed first in the Remote Config template's conditions list) determines the value of + * this parameter. + */ + conditionalValues?: { [key: string]: RemoteConfigParameterValue }; + + /** + * A description for this parameter. Should not be over 100 characters and may contain any + * Unicode characters. + */ + description?: string; +} + +/** + * Interface representing a Remote Config parameter group. + * Grouping parameters is only for management purposes and does not affect client-side + * fetching of parameter values. + */ +export interface RemoteConfigParameterGroup { + /** + * A description for the group. Its length must be less than or equal to 256 characters. + * A description may contain any Unicode characters. + */ + description?: string; + + /** + * Map of parameter keys to their optional default values and optional conditional values for + * parameters that belong to this group. A parameter only appears once per + * Remote Config template. An ungrouped parameter appears at the top level, whereas a + * parameter organized within a group appears within its group's map of parameters. + */ + parameters: { [key: string]: RemoteConfigParameter }; +} + +/** + * Interface representing a Remote Config template. + */ +export interface RemoteConfigTemplate { + /** + * A list of conditions in descending order by priority. + */ + conditions: RemoteConfigCondition[]; + + /** + * Map of parameter keys to their optional default values and optional conditional values. + */ + parameters: { [key: string]: RemoteConfigParameter }; + + /** + * Map of parameter group names to their parameter group objects. + * A group's name is mutable but must be unique among groups in the Remote Config template. + * The name is limited to 256 characters and intended to be human-readable. Any Unicode + * characters are allowed. + */ + parameterGroups: { [key: string]: RemoteConfigParameterGroup }; + + /** + * ETag of the current Remote Config template (readonly). + */ + readonly etag: string; + + /** + * Version information for the current Remote Config template. + */ + version?: Version; +} + +/** + * Interface representing a Remote Config user. + */ +export interface RemoteConfigUser { + /** + * Email address. Output only. + */ + email: string; + + /** + * Display name. Output only. + */ + name?: string; + + /** + * Image URL. Output only. + */ + imageUrl?: string; +} + +/** + * Interface representing a Remote Config template version. + * Output only, except for the version description. Contains metadata about a particular + * version of the Remote Config template. All fields are set at the time the specified Remote + * Config template is published. A version's description field may be specified in + * `publishTemplate` calls. + */ +export interface Version { + /** + * The version number of a Remote Config template. + */ + versionNumber?: string; + + /** + * The timestamp of when this version of the Remote Config template was written to the + * Remote Config backend. + */ + updateTime?: string; + + /** + * The origin of the template update action. + */ + updateOrigin?: ('REMOTE_CONFIG_UPDATE_ORIGIN_UNSPECIFIED' | 'CONSOLE' | + 'REST_API' | 'ADMIN_SDK_NODE'); + + /** + * The type of the template update action. + */ + updateType?: ('REMOTE_CONFIG_UPDATE_TYPE_UNSPECIFIED' | + 'INCREMENTAL_UPDATE' | 'FORCED_UPDATE' | 'ROLLBACK'); + + /** + * Aggregation of all metadata fields about the account that performed the update. + */ + updateUser?: RemoteConfigUser; + + /** + * The user-provided description of the corresponding Remote Config template. + */ + description?: string; + + /** + * The version number of the Remote Config template that has become the current version + * due to a rollback. Only present if this version is the result of a rollback. + */ + rollbackSource?: string; + + /** + * Indicates whether this Remote Config template was published before version history was + * supported. + */ + isLegacy?: boolean; +} + +/** + * Interface representing a list of Remote Config template versions. + */ +export interface ListVersionsResult { + /** + * A list of version metadata objects, sorted in reverse chronological order. + */ + versions: Version[]; + + /** + * Token to retrieve the next page of results, or empty if there are no more results + * in the list. + */ + nextPageToken?: string; +} + +/** + * Interface representing options for Remote Config list versions operation. + */ +export interface ListVersionsOptions { + /** + * The maximum number of items to return per page. + */ + pageSize?: number; + + /** + * The `nextPageToken` value returned from a previous list versions request, if any. + */ + pageToken?: string; + + /** + * Specifies the newest version number to include in the results. + * If specified, must be greater than zero. Defaults to the newest version. + */ + endVersionNumber?: string | number; + + /** + * Specifies the earliest update time to include in the results. Any entries updated before this + * time are omitted. + */ + startTime?: Date | string; + + /** + * Specifies the latest update time to include in the results. Any entries updated on or after + * this time are omitted. + */ + endTime?: Date | string; +} diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index c4f9c9fc03..5f2ce6b252 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -14,33 +14,33 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app'; import * as validator from '../utils/validator'; -import { remoteConfig } from './index'; import { FirebaseRemoteConfigError, RemoteConfigApiClient } from './remote-config-api-client-internal'; - -import RemoteConfigTemplate = remoteConfig.RemoteConfigTemplate; -import RemoteConfigParameter = remoteConfig.RemoteConfigParameter; -import RemoteConfigCondition = remoteConfig.RemoteConfigCondition; -import RemoteConfigParameterGroup = remoteConfig.RemoteConfigParameterGroup; -import ListVersionsOptions = remoteConfig.ListVersionsOptions; -import ListVersionsResult = remoteConfig.ListVersionsResult; -import RemoteConfigUser = remoteConfig.RemoteConfigUser; -import Version = remoteConfig.Version; -import RemoteConfigInterface = remoteConfig.RemoteConfig; +import { + ListVersionsOptions, + ListVersionsResult, + RemoteConfigCondition, + RemoteConfigParameter, + RemoteConfigParameterGroup, + RemoteConfigTemplate, + RemoteConfigUser, + Version, +} from './remote-config-api'; /** * Remote Config service bound to the provided app. */ -export class RemoteConfig implements RemoteConfigInterface { +export class RemoteConfig { private readonly client: RemoteConfigApiClient; /** * @param app The app for this RemoteConfig service. * @constructor + * @internal */ - constructor(readonly app: FirebaseApp) { + constructor(readonly app: App) { this.client = new RemoteConfigApiClient(app); } diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index f1b5a0d647..07c8e3435b 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -76,5 +76,6 @@ import './security-rules/security-rules.spec'; import './security-rules/security-rules-api-client.spec'; // RemoteConfig +import './remote-config/index.spec'; import './remote-config/remote-config.spec'; import './remote-config/remote-config-api-client.spec'; diff --git a/test/unit/remote-config/index.spec.ts b/test/unit/remote-config/index.spec.ts new file mode 100644 index 0000000000..f2fd51a141 --- /dev/null +++ b/test/unit/remote-config/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getRemoteConfig, RemoteConfig } from '../../../src/remote-config/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('RemoteConfig', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID. Initialize the SDK ' + + 'with service account credentials, or set project ID as an app option. Alternatively, set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getRemoteConfig()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getRemoteConfig(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const remoteConfig = getRemoteConfig(mockCredentialApp); + return remoteConfig.getTemplate() + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getRemoteConfig(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const rc1: RemoteConfig = getRemoteConfig(mockApp); + const rc2: RemoteConfig = getRemoteConfig(mockApp); + expect(rc1).to.equal(rc2); + }); + }); +}); diff --git a/test/unit/remote-config/remote-config.spec.ts b/test/unit/remote-config/remote-config.spec.ts index 2b25a5b7bc..e2a95aeb47 100644 --- a/test/unit/remote-config/remote-config.spec.ts +++ b/test/unit/remote-config/remote-config.spec.ts @@ -19,21 +19,21 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { RemoteConfig } from '../../../src/remote-config/remote-config'; +import { + RemoteConfig, + RemoteConfigTemplate, + RemoteConfigCondition, + TagColor, + ListVersionsResult, +} from '../../../src/remote-config/index'; import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; -import { remoteConfig } from '../../../src/remote-config/index'; import { FirebaseRemoteConfigError, RemoteConfigApiClient } from '../../../src/remote-config/remote-config-api-client-internal'; import { deepCopy } from '../../../src/utils/deep-copy'; -import RemoteConfigTemplate = remoteConfig.RemoteConfigTemplate; -import RemoteConfigCondition = remoteConfig.RemoteConfigCondition; -import TagColor = remoteConfig.TagColor; -import ListVersionsResult = remoteConfig.ListVersionsResult; - const expect = chai.expect; describe('RemoteConfig', () => { From 4e019f165f8251f34b6ba089d81a2732abcb6c5c Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 4 Feb 2021 11:57:09 -0800 Subject: [PATCH 11/41] feat(fcm): Exposed FCM APIs from firebase-admin/messaging entry point (#1153) * feat(fcm): Exposed FCM APIs from firebase-admin/messaging entry point * Update src/messaging/messaging-api.ts Co-authored-by: Lahiru Maramba Co-authored-by: Lahiru Maramba --- etc/firebase-admin.api.md | 397 ++--- etc/firebase-admin.messaging.api.md | 430 +++++ generate-reports.js | 1 + gulpfile.js | 1 + src/app/firebase-app.ts | 12 +- src/messaging/index.ts | 1422 ++--------------- .../messaging-api-request-internal.ts | 11 +- src/messaging/messaging-api.ts | 1106 +++++++++++++ src/messaging/messaging-internal.ts | 20 +- src/messaging/messaging.ts | 49 +- test/unit/index.spec.ts | 1 + test/unit/messaging/index.spec.ts | 75 + test/unit/messaging/messaging.spec.ts | 16 +- 13 files changed, 1914 insertions(+), 1627 deletions(-) create mode 100644 etc/firebase-admin.messaging.api.md create mode 100644 src/messaging/messaging-api.ts create mode 100644 test/unit/messaging/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index de8a48d4a9..97fffbd50f 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -468,287 +468,142 @@ export namespace machineLearning { } // @public -export function messaging(app?: app.App): messaging.Messaging; +export function messaging(app?: App): messaging.Messaging; // @public (undocumented) export namespace messaging { - export interface AndroidConfig { - collapseKey?: string; - data?: { - [key: string]: string; - }; - fcmOptions?: AndroidFcmOptions; - notification?: AndroidNotification; - priority?: ('high' | 'normal'); - restrictedPackageName?: string; - ttl?: number; - } - export interface AndroidFcmOptions { - analyticsLabel?: string; - } - export interface AndroidNotification { - body?: string; - bodyLocArgs?: string[]; - bodyLocKey?: string; - channelId?: string; - clickAction?: string; - color?: string; - defaultLightSettings?: boolean; - defaultSound?: boolean; - defaultVibrateTimings?: boolean; - eventTimestamp?: Date; - icon?: string; - imageUrl?: string; - lightSettings?: LightSettings; - localOnly?: boolean; - notificationCount?: number; - priority?: ('min' | 'low' | 'default' | 'high' | 'max'); - sound?: string; - sticky?: boolean; - tag?: string; - ticker?: string; - title?: string; - titleLocArgs?: string[]; - titleLocKey?: string; - vibrateTimingsMillis?: number[]; - visibility?: ('private' | 'public' | 'secret'); - } - export interface ApnsConfig { - fcmOptions?: ApnsFcmOptions; - headers?: { - [key: string]: string; - }; - payload?: ApnsPayload; - } - export interface ApnsFcmOptions { - analyticsLabel?: string; - imageUrl?: string; - } - export interface ApnsPayload { - // (undocumented) - [customData: string]: any; - aps: Aps; - } - export interface Aps { - // (undocumented) - [customData: string]: any; - alert?: string | ApsAlert; - badge?: number; - category?: string; - contentAvailable?: boolean; - mutableContent?: boolean; - sound?: string | CriticalSound; - threadId?: string; - } + // Warning: (ae-forgotten-export) The symbol "AndroidConfig" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) - export interface ApsAlert { - // (undocumented) - actionLocKey?: string; - // (undocumented) - body?: string; - // (undocumented) - launchImage?: string; - // (undocumented) - locArgs?: string[]; - // (undocumented) - locKey?: string; - // (undocumented) - subtitle?: string; - // (undocumented) - subtitleLocArgs?: string[]; - // (undocumented) - subtitleLocKey?: string; - // (undocumented) - title?: string; - // (undocumented) - titleLocArgs?: string[]; - // (undocumented) - titleLocKey?: string; - } + export type AndroidConfig = AndroidConfig; + // Warning: (ae-forgotten-export) The symbol "AndroidFcmOptions" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) - export interface BaseMessage { - // (undocumented) - android?: AndroidConfig; - // (undocumented) - apns?: ApnsConfig; - // (undocumented) - data?: { - [key: string]: string; - }; - // (undocumented) - fcmOptions?: FcmOptions; - // (undocumented) - notification?: Notification; - // (undocumented) - webpush?: WebpushConfig; - } - export interface BatchResponse { - failureCount: number; - responses: SendResponse[]; - successCount: number; - } + export type AndroidFcmOptions = AndroidFcmOptions; + // Warning: (ae-forgotten-export) The symbol "AndroidNotification" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) - export interface ConditionMessage extends BaseMessage { - // (undocumented) - condition: string; - } - export interface CriticalSound { - critical?: boolean; - name: string; - volume?: number; - } - export interface DataMessagePayload { - // (undocumented) - [key: string]: string; - } - export interface FcmOptions { - analyticsLabel?: string; - } - export interface LightSettings { - color: string; - lightOffDurationMillis: number; - lightOnDurationMillis: number; - } - export type Message = TokenMessage | TopicMessage | ConditionMessage; + export type AndroidNotification = AndroidNotification; + // Warning: (ae-forgotten-export) The symbol "ApnsConfig" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) - export interface Messaging { - app: app.App; - send(message: Message, dryRun?: boolean): Promise; - sendAll(messages: Array, dryRun?: boolean): Promise; - sendMulticast(message: MulticastMessage, dryRun?: boolean): Promise; - sendToCondition(condition: string, payload: MessagingPayload, options?: MessagingOptions): Promise; - sendToDevice(registrationToken: string | string[], payload: MessagingPayload, options?: MessagingOptions): Promise; - sendToDeviceGroup(notificationKey: string, payload: MessagingPayload, options?: MessagingOptions): Promise; - sendToTopic(topic: string, payload: MessagingPayload, options?: MessagingOptions): Promise; - subscribeToTopic(registrationTokens: string | string[], topic: string): Promise; - unsubscribeFromTopic(registrationTokens: string | string[], topic: string): Promise; - } - export interface MessagingConditionResponse { - messageId: number; - } - export interface MessagingDeviceGroupResponse { - failedRegistrationTokens: string[]; - failureCount: number; - successCount: number; - } + export type ApnsConfig = ApnsConfig; + // Warning: (ae-forgotten-export) The symbol "ApnsFcmOptions" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) - export interface MessagingDeviceResult { - canonicalRegistrationToken?: string; - error?: FirebaseError; - messageId?: string; - } - export interface MessagingDevicesResponse { - // (undocumented) - canonicalRegistrationTokenCount: number; - // (undocumented) - failureCount: number; - // (undocumented) - multicastId: number; - // (undocumented) - results: MessagingDeviceResult[]; - // (undocumented) - successCount: number; - } - export interface MessagingOptions { - // (undocumented) - [key: string]: any | undefined; - collapseKey?: string; - contentAvailable?: boolean; - dryRun?: boolean; - mutableContent?: boolean; - priority?: string; - restrictedPackageName?: string; - timeToLive?: number; - } - export interface MessagingPayload { - data?: DataMessagePayload; - notification?: NotificationMessagePayload; - } - export interface MessagingTopicManagementResponse { - errors: FirebaseArrayIndexError[]; - failureCount: number; - successCount: number; - } - export interface MessagingTopicResponse { - messageId: number; - } - export interface MulticastMessage extends BaseMessage { - // (undocumented) - tokens: string[]; - } - export interface Notification { - body?: string; - imageUrl?: string; - title?: string; - } - export interface NotificationMessagePayload { - // (undocumented) - [key: string]: string | undefined; - badge?: string; - body?: string; - bodyLocArgs?: string; - bodyLocKey?: string; - clickAction?: string; - color?: string; - icon?: string; - sound?: string; - tag?: string; - title?: string; - titleLocArgs?: string; - titleLocKey?: string; - } - export interface SendResponse { - error?: FirebaseError; - messageId?: string; - success: boolean; - } + export type ApnsFcmOptions = ApnsFcmOptions; + // Warning: (ae-forgotten-export) The symbol "ApnsPayload" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) - export interface TokenMessage extends BaseMessage { - // (undocumented) - token: string; - } + export type ApnsPayload = ApnsPayload; + // Warning: (ae-forgotten-export) The symbol "Aps" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) - export interface TopicMessage extends BaseMessage { - // (undocumented) - topic: string; - } - export interface WebpushConfig { - data?: { - [key: string]: string; - }; - fcmOptions?: WebpushFcmOptions; - headers?: { - [key: string]: string; - }; - notification?: WebpushNotification; - } - export interface WebpushFcmOptions { - link?: string; - } - export interface WebpushNotification { - // (undocumented) - [key: string]: any; - actions?: Array<{ - action: string; - icon?: string; - title: string; - }>; - badge?: string; - body?: string; - data?: any; - dir?: 'auto' | 'ltr' | 'rtl'; - icon?: string; - image?: string; - lang?: string; - renotify?: boolean; - requireInteraction?: boolean; - silent?: boolean; - tag?: string; - timestamp?: number; - title?: string; - vibrate?: number | number[]; - } - {}; + export type Aps = Aps; + // Warning: (ae-forgotten-export) The symbol "ApsAlert" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type ApsAlert = ApsAlert; + // Warning: (ae-forgotten-export) The symbol "BatchResponse" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type BatchResponse = BatchResponse; + // Warning: (ae-forgotten-export) The symbol "ConditionMessage" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type ConditionMessage = ConditionMessage; + // Warning: (ae-forgotten-export) The symbol "CriticalSound" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type CriticalSound = CriticalSound; + // Warning: (ae-forgotten-export) The symbol "DataMessagePayload" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type DataMessagePayload = DataMessagePayload; + // Warning: (ae-forgotten-export) The symbol "FcmOptions" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type FcmOptions = FcmOptions; + // Warning: (ae-forgotten-export) The symbol "LightSettings" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type LightSettings = LightSettings; + // Warning: (ae-forgotten-export) The symbol "Message" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type Message = Message; + // Warning: (ae-forgotten-export) The symbol "Messaging" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type Messaging = Messaging; + // Warning: (ae-forgotten-export) The symbol "MessagingConditionResponse" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type MessagingConditionResponse = MessagingConditionResponse; + // Warning: (ae-forgotten-export) The symbol "MessagingDeviceGroupResponse" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type MessagingDeviceGroupResponse = MessagingDeviceGroupResponse; + // Warning: (ae-forgotten-export) The symbol "MessagingDeviceResult" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type MessagingDeviceResult = MessagingDeviceResult; + // Warning: (ae-forgotten-export) The symbol "MessagingDevicesResponse" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type MessagingDevicesResponse = MessagingDevicesResponse; + // Warning: (ae-forgotten-export) The symbol "MessagingOptions" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type MessagingOptions = MessagingOptions; + // Warning: (ae-forgotten-export) The symbol "MessagingPayload" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type MessagingPayload = MessagingPayload; + // Warning: (ae-forgotten-export) The symbol "MessagingTopicManagementResponse" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type MessagingTopicManagementResponse = MessagingTopicManagementResponse; + // Warning: (ae-forgotten-export) The symbol "MessagingTopicResponse" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type MessagingTopicResponse = MessagingTopicResponse; + // Warning: (ae-forgotten-export) The symbol "MulticastMessage" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type MulticastMessage = MulticastMessage; + // Warning: (ae-forgotten-export) The symbol "Notification" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type Notification = Notification; + // Warning: (ae-forgotten-export) The symbol "NotificationMessagePayload" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type NotificationMessagePayload = NotificationMessagePayload; + // Warning: (ae-forgotten-export) The symbol "SendResponse" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type SendResponse = SendResponse; + // Warning: (ae-forgotten-export) The symbol "TokenMessage" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type TokenMessage = TokenMessage; + // Warning: (ae-forgotten-export) The symbol "TopicMessage" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type TopicMessage = TopicMessage; + // Warning: (ae-forgotten-export) The symbol "WebpushConfig" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type WebpushConfig = WebpushConfig; + // Warning: (ae-forgotten-export) The symbol "WebpushFcmOptions" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type WebpushFcmOptions = WebpushFcmOptions; + // Warning: (ae-forgotten-export) The symbol "WebpushNotification" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type WebpushNotification = WebpushNotification; } // @public diff --git a/etc/firebase-admin.messaging.api.md b/etc/firebase-admin.messaging.api.md new file mode 100644 index 0000000000..07eb7c9cab --- /dev/null +++ b/etc/firebase-admin.messaging.api.md @@ -0,0 +1,430 @@ +## API Report File for "firebase-admin.messaging" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; + +// @public +export interface AndroidConfig { + collapseKey?: string; + data?: { + [key: string]: string; + }; + fcmOptions?: AndroidFcmOptions; + notification?: AndroidNotification; + priority?: ('high' | 'normal'); + restrictedPackageName?: string; + ttl?: number; +} + +// @public +export interface AndroidFcmOptions { + analyticsLabel?: string; +} + +// @public +export interface AndroidNotification { + body?: string; + bodyLocArgs?: string[]; + bodyLocKey?: string; + channelId?: string; + clickAction?: string; + color?: string; + defaultLightSettings?: boolean; + defaultSound?: boolean; + defaultVibrateTimings?: boolean; + eventTimestamp?: Date; + icon?: string; + imageUrl?: string; + lightSettings?: LightSettings; + localOnly?: boolean; + notificationCount?: number; + priority?: ('min' | 'low' | 'default' | 'high' | 'max'); + sound?: string; + sticky?: boolean; + tag?: string; + ticker?: string; + title?: string; + titleLocArgs?: string[]; + titleLocKey?: string; + vibrateTimingsMillis?: number[]; + visibility?: ('private' | 'public' | 'secret'); +} + +// @public +export interface ApnsConfig { + fcmOptions?: ApnsFcmOptions; + headers?: { + [key: string]: string; + }; + payload?: ApnsPayload; +} + +// @public +export interface ApnsFcmOptions { + analyticsLabel?: string; + imageUrl?: string; +} + +// @public +export interface ApnsPayload { + // (undocumented) + [customData: string]: any; + aps: Aps; +} + +// @public +export interface Aps { + // (undocumented) + [customData: string]: any; + alert?: string | ApsAlert; + badge?: number; + category?: string; + contentAvailable?: boolean; + mutableContent?: boolean; + sound?: string | CriticalSound; + threadId?: string; +} + +// @public (undocumented) +export interface ApsAlert { + // (undocumented) + actionLocKey?: string; + // (undocumented) + body?: string; + // (undocumented) + launchImage?: string; + // (undocumented) + locArgs?: string[]; + // (undocumented) + locKey?: string; + // (undocumented) + subtitle?: string; + // (undocumented) + subtitleLocArgs?: string[]; + // (undocumented) + subtitleLocKey?: string; + // (undocumented) + title?: string; + // (undocumented) + titleLocArgs?: string[]; + // (undocumented) + titleLocKey?: string; +} + +// @public (undocumented) +export interface BaseMessage { + // (undocumented) + android?: AndroidConfig; + // (undocumented) + apns?: ApnsConfig; + // (undocumented) + data?: { + [key: string]: string; + }; + // (undocumented) + fcmOptions?: FcmOptions; + // (undocumented) + notification?: Notification; + // (undocumented) + webpush?: WebpushConfig; +} + +// @public +export interface BatchResponse { + failureCount: number; + responses: SendResponse[]; + successCount: number; +} + +// @public (undocumented) +export interface ConditionMessage extends BaseMessage { + // (undocumented) + condition: string; +} + +// @public +export interface CriticalSound { + critical?: boolean; + name: string; + volume?: number; +} + +// @public +export interface DataMessagePayload { + // (undocumented) + [key: string]: string; +} + +// @public +export interface FcmOptions { + analyticsLabel?: string; +} + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function getMessaging(app?: App): Messaging; + +// @public +export interface LightSettings { + color: string; + lightOffDurationMillis: number; + lightOnDurationMillis: number; +} + +// @public +export type Message = TokenMessage | TopicMessage | ConditionMessage; + +// @public +export class Messaging { + constructor(app: App); + get app(): App; + send(message: Message, dryRun?: boolean): Promise; + sendAll(messages: Message[], dryRun?: boolean): Promise; + sendMulticast(message: MulticastMessage, dryRun?: boolean): Promise; + sendToCondition(condition: string, payload: MessagingPayload, options?: MessagingOptions): Promise; + sendToDevice(registrationTokenOrTokens: string | string[], payload: MessagingPayload, options?: MessagingOptions): Promise; + sendToDeviceGroup(notificationKey: string, payload: MessagingPayload, options?: MessagingOptions): Promise; + sendToTopic(topic: string, payload: MessagingPayload, options?: MessagingOptions): Promise; + subscribeToTopic(registrationTokenOrTokens: string | string[], topic: string): Promise; + unsubscribeFromTopic(registrationTokenOrTokens: string | string[], topic: string): Promise; + } + +// @public +export function messaging(app?: App): messaging.Messaging; + +// @public (undocumented) +export namespace messaging { + // (undocumented) + export type AndroidConfig = AndroidConfig; + // (undocumented) + export type AndroidFcmOptions = AndroidFcmOptions; + // (undocumented) + export type AndroidNotification = AndroidNotification; + // (undocumented) + export type ApnsConfig = ApnsConfig; + // (undocumented) + export type ApnsFcmOptions = ApnsFcmOptions; + // (undocumented) + export type ApnsPayload = ApnsPayload; + // (undocumented) + export type Aps = Aps; + // (undocumented) + export type ApsAlert = ApsAlert; + // (undocumented) + export type BatchResponse = BatchResponse; + // (undocumented) + export type ConditionMessage = ConditionMessage; + // (undocumented) + export type CriticalSound = CriticalSound; + // (undocumented) + export type DataMessagePayload = DataMessagePayload; + // (undocumented) + export type FcmOptions = FcmOptions; + // (undocumented) + export type LightSettings = LightSettings; + // (undocumented) + export type Message = Message; + // (undocumented) + export type Messaging = Messaging; + // (undocumented) + export type MessagingConditionResponse = MessagingConditionResponse; + // (undocumented) + export type MessagingDeviceGroupResponse = MessagingDeviceGroupResponse; + // (undocumented) + export type MessagingDeviceResult = MessagingDeviceResult; + // (undocumented) + export type MessagingDevicesResponse = MessagingDevicesResponse; + // (undocumented) + export type MessagingOptions = MessagingOptions; + // (undocumented) + export type MessagingPayload = MessagingPayload; + // (undocumented) + export type MessagingTopicManagementResponse = MessagingTopicManagementResponse; + // (undocumented) + export type MessagingTopicResponse = MessagingTopicResponse; + // (undocumented) + export type MulticastMessage = MulticastMessage; + // (undocumented) + export type Notification = Notification; + // (undocumented) + export type NotificationMessagePayload = NotificationMessagePayload; + // (undocumented) + export type SendResponse = SendResponse; + // (undocumented) + export type TokenMessage = TokenMessage; + // (undocumented) + export type TopicMessage = TopicMessage; + // (undocumented) + export type WebpushConfig = WebpushConfig; + // (undocumented) + export type WebpushFcmOptions = WebpushFcmOptions; + // (undocumented) + export type WebpushNotification = WebpushNotification; +} + +// @public +export interface MessagingConditionResponse { + messageId: number; +} + +// @public +export interface MessagingDeviceGroupResponse { + failedRegistrationTokens: string[]; + failureCount: number; + successCount: number; +} + +// @public (undocumented) +export interface MessagingDeviceResult { + canonicalRegistrationToken?: string; + // Warning: (ae-forgotten-export) The symbol "FirebaseError" needs to be exported by the entry point index.d.ts + error?: FirebaseError; + messageId?: string; +} + +// @public +export interface MessagingDevicesResponse { + // (undocumented) + canonicalRegistrationTokenCount: number; + // (undocumented) + failureCount: number; + // (undocumented) + multicastId: number; + // (undocumented) + results: MessagingDeviceResult[]; + // (undocumented) + successCount: number; +} + +// @public +export interface MessagingOptions { + // (undocumented) + [key: string]: any | undefined; + collapseKey?: string; + contentAvailable?: boolean; + dryRun?: boolean; + mutableContent?: boolean; + priority?: string; + restrictedPackageName?: string; + timeToLive?: number; +} + +// @public +export interface MessagingPayload { + data?: DataMessagePayload; + notification?: NotificationMessagePayload; +} + +// @public +export interface MessagingTopicManagementResponse { + // Warning: (ae-forgotten-export) The symbol "FirebaseArrayIndexError" needs to be exported by the entry point index.d.ts + errors: FirebaseArrayIndexError[]; + failureCount: number; + successCount: number; +} + +// @public +export interface MessagingTopicResponse { + messageId: number; +} + +// @public +export interface MulticastMessage extends BaseMessage { + // (undocumented) + tokens: string[]; +} + +// @public +export interface Notification { + body?: string; + imageUrl?: string; + title?: string; +} + +// @public +export interface NotificationMessagePayload { + // (undocumented) + [key: string]: string | undefined; + badge?: string; + body?: string; + bodyLocArgs?: string; + bodyLocKey?: string; + clickAction?: string; + color?: string; + icon?: string; + sound?: string; + tag?: string; + title?: string; + titleLocArgs?: string; + titleLocKey?: string; +} + +// @public +export interface SendResponse { + error?: FirebaseError; + messageId?: string; + success: boolean; +} + +// @public (undocumented) +export interface TokenMessage extends BaseMessage { + // (undocumented) + token: string; +} + +// @public (undocumented) +export interface TopicMessage extends BaseMessage { + // (undocumented) + topic: string; +} + +// @public +export interface WebpushConfig { + data?: { + [key: string]: string; + }; + fcmOptions?: WebpushFcmOptions; + headers?: { + [key: string]: string; + }; + notification?: WebpushNotification; +} + +// @public +export interface WebpushFcmOptions { + link?: string; +} + +// @public +export interface WebpushNotification { + // (undocumented) + [key: string]: any; + actions?: Array<{ + action: string; + icon?: string; + title: string; + }>; + badge?: string; + body?: string; + data?: any; + dir?: 'auto' | 'ltr' | 'rtl'; + icon?: string; + image?: string; + lang?: string; + renotify?: boolean; + requireInteraction?: boolean; + silent?: boolean; + tag?: string; + timestamp?: number; + title?: string; + vibrate?: number | number[]; +} + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/generate-reports.js b/generate-reports.js index b9444e9088..86255822d7 100644 --- a/generate-reports.js +++ b/generate-reports.js @@ -39,6 +39,7 @@ const entryPoints = { 'firebase-admin/database': './lib/database/index.d.ts', 'firebase-admin/firestore': './lib/firestore/index.d.ts', 'firebase-admin/instance-id': './lib/instance-id/index.d.ts', + 'firebase-admin/messaging': './lib/messaging/index.d.ts', 'firebase-admin/remote-config': './lib/remote-config/index.d.ts', }; diff --git a/gulpfile.js b/gulpfile.js index d15c4acc57..a2268bc070 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -92,6 +92,7 @@ gulp.task('compile', function() { 'lib/database/*.d.ts', 'lib/firestore/*.d.ts', 'lib/instance-id/*.d.ts', + 'lib/messaging/*.d.ts', 'lib/remote-config/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index ff5bfd836f..0dbe4c04c6 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -23,12 +23,12 @@ import { deepCopy } from '../utils/deep-copy'; import { FirebaseNamespaceInternals } from './firebase-namespace'; import { AppErrorCodes, FirebaseAppError } from '../utils/error'; -import { Auth } from '../auth/auth'; +import { Auth } from '../auth/index'; import { MachineLearning } from '../machine-learning/machine-learning'; -import { Messaging } from '../messaging/messaging'; +import { Messaging } from '../messaging/index'; import { Storage } from '../storage/storage'; import { Database } from '../database/index'; -import { Firestore } from '@google-cloud/firestore'; +import { Firestore } from '../firestore/index'; import { InstanceId } from '../instance-id/index'; import { ProjectManagement } from '../project-management/project-management'; import { SecurityRules } from '../security-rules/security-rules'; @@ -293,10 +293,8 @@ export class FirebaseApp implements app.App { * @return The Messaging service instance of this app. */ public messaging(): Messaging { - return this.ensureService_('messaging', () => { - const messagingService: typeof Messaging = require('../messaging/messaging').Messaging; - return new messagingService(this); - }); + const fn = require('../messaging/index').getMessaging; + return fn(this); } /** diff --git a/src/messaging/index.ts b/src/messaging/index.ts index 020ee7c875..9c8634cba8 100644 --- a/src/messaging/index.ts +++ b/src/messaging/index.ts @@ -14,7 +14,98 @@ * limitations under the License. */ -import { app, FirebaseError, FirebaseArrayIndexError } from '../firebase-namespace-api'; +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { Messaging } from './messaging'; + +export { + Messaging, +} from './messaging'; + +export { + AndroidConfig, + AndroidFcmOptions, + AndroidNotification, + ApnsConfig, + ApnsFcmOptions, + ApnsPayload, + Aps, + ApsAlert, + BaseMessage, + BatchResponse, + CriticalSound, + ConditionMessage, + FcmOptions, + LightSettings, + Message, + MessagingTopicManagementResponse, + MulticastMessage, + Notification, + SendResponse, + TokenMessage, + TopicMessage, + WebpushConfig, + WebpushFcmOptions, + WebpushNotification, + + // Legacy APIs + DataMessagePayload, + MessagingConditionResponse, + MessagingDeviceGroupResponse, + MessagingDeviceResult, + MessagingDevicesResponse, + MessagingOptions, + MessagingPayload, + MessagingTopicResponse, + NotificationMessagePayload, +} from './messaging-api'; + +export function getMessaging(app?: App): Messaging { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('messaging', (app) => new Messaging(app)); +} + +import { Messaging as TMessaging } from './messaging'; +import { + AndroidConfig as TAndroidConfig, + AndroidFcmOptions as TAndroidFcmOptions, + AndroidNotification as TAndroidNotification, + ApnsConfig as TApnsConfig, + ApnsFcmOptions as TApnsFcmOptions, + ApnsPayload as TApnsPayload, + Aps as TAps, + ApsAlert as TApsAlert, + BatchResponse as TBatchResponse, + CriticalSound as TCriticalSound, + ConditionMessage as TConditionMessage, + FcmOptions as TFcmOptions, + LightSettings as TLightSettings, + Message as TMessage, + MessagingTopicManagementResponse as TMessagingTopicManagementResponse, + MulticastMessage as TMulticastMessage, + Notification as TNotification, + SendResponse as TSendResponse, + TokenMessage as TTokenMessage, + TopicMessage as TTopicMessage, + WebpushConfig as TWebpushConfig, + WebpushFcmOptions as TWebpushFcmOptions, + WebpushNotification as TWebpushNotification, + + // Legacy APIs + DataMessagePayload as TDataMessagePayload, + MessagingConditionResponse as TMessagingConditionResponse, + MessagingDeviceGroupResponse as TMessagingDeviceGroupResponse, + MessagingDeviceResult as TMessagingDeviceResult, + MessagingDevicesResponse as TMessagingDevicesResponse, + MessagingOptions as TMessagingOptions, + MessagingPayload as TMessagingPayload, + MessagingTopicResponse as TMessagingTopicResponse, + NotificationMessagePayload as TNotificationMessagePayload, +} from './messaging-api'; /** * Gets the {@link messaging.Messaging `Messaging`} service for the @@ -45,1299 +136,44 @@ import { app, FirebaseError, FirebaseArrayIndexError } from '../firebase-namespa * app is provided or the `Messaging` service associated with the provided * app. */ -export declare function messaging(app?: app.App): messaging.Messaging; +export declare function messaging(app?: App): messaging.Messaging; /* eslint-disable @typescript-eslint/no-namespace */ export namespace messaging { - interface BaseMessage { - data?: { [key: string]: string }; - notification?: Notification; - android?: AndroidConfig; - webpush?: WebpushConfig; - apns?: ApnsConfig; - fcmOptions?: FcmOptions; - } - - interface TokenMessage extends BaseMessage { - token: string; - } - - interface TopicMessage extends BaseMessage { - topic: string; - } - - interface ConditionMessage extends BaseMessage { - condition: string; - } - - /** - * Payload for the admin.messaging.send() operation. The payload contains all the fields - * in the BaseMessage type, and exactly one of token, topic or condition. - */ - export type Message = TokenMessage | TopicMessage | ConditionMessage; - - /** - * Payload for the admin.messaing.sendMulticase() method. The payload contains all the fields - * in the BaseMessage type, and a list of tokens. - */ - export interface MulticastMessage extends BaseMessage { - tokens: string[]; - } - - /** - * A notification that can be included in {@link messaging.Message}. - */ - export interface Notification { - /** - * The title of the notification. - */ - title?: string; - /** - * The notification body - */ - body?: string; - /** - * URL of an image to be displayed in the notification. - */ - imageUrl?: string; - } - - /** - * Represents platform-independent options for features provided by the FCM SDKs. - */ - export interface FcmOptions { - /** - * The label associated with the message's analytics data. - */ - analyticsLabel?: string; - } - - /** - * Represents the WebPush protocol options that can be included in an - * {@link messaging.Message}. - */ - export interface WebpushConfig { - - /** - * A collection of WebPush headers. Header values must be strings. - * - * See [WebPush specification](https://tools.ietf.org/html/rfc8030#section-5) - * for supported headers. - */ - headers?: { [key: string]: string }; - - /** - * A collection of data fields. - */ - data?: { [key: string]: string }; - - /** - * A WebPush notification payload to be included in the message. - */ - notification?: WebpushNotification; - - /** - * Options for features provided by the FCM SDK for Web. - */ - fcmOptions?: WebpushFcmOptions; - } - - /** Represents options for features provided by the FCM SDK for Web - * (which are not part of the Webpush standard). - */ - export interface WebpushFcmOptions { - - /** - * The link to open when the user clicks on the notification. - * For all URL values, HTTPS is required. - */ - link?: string; - } - - /** - * Represents the WebPush-specific notification options that can be included in - * {@link messaging.WebpushConfig}. This supports most of the standard - * options as defined in the Web Notification - * [specification](https://developer.mozilla.org/en-US/docs/Web/API/notification/Notification). - */ - export interface WebpushNotification { - - /** - * Title text of the notification. - */ - title?: string; - - /** - * An array of notification actions representing the actions - * available to the user when the notification is presented. - */ - actions?: Array<{ - - /** - * An action available to the user when the notification is presented - */ - action: string; - - /** - * Optional icon for a notification action. - */ - icon?: string; - - /** - * Title of the notification action. - */ - title: string; - }>; - - /** - * URL of the image used to represent the notification when there is - * not enough space to display the notification itself. - */ - badge?: string; - - /** - * Body text of the notification. - */ - body?: string; - - /** - * Arbitrary data that you want associated with the notification. - * This can be of any data type. - */ - data?: any; - - /** - * The direction in which to display the notification. Must be one - * of `auto`, `ltr` or `rtl`. - */ - dir?: 'auto' | 'ltr' | 'rtl'; - - /** - * URL to the notification icon. - */ - icon?: string; - - /** - * URL of an image to be displayed in the notification. - */ - image?: string; - - /** - * The notification's language as a BCP 47 language tag. - */ - lang?: string; - - /** - * A boolean specifying whether the user should be notified after a - * new notification replaces an old one. Defaults to false. - */ - renotify?: boolean; - - /** - * Indicates that a notification should remain active until the user - * clicks or dismisses it, rather than closing automatically. - * Defaults to false. - */ - requireInteraction?: boolean; - - /** - * A boolean specifying whether the notification should be silent. - * Defaults to false. - */ - silent?: boolean; - - /** - * An identifying tag for the notification. - */ - tag?: string; - - /** - * Timestamp of the notification. Refer to - * https://developer.mozilla.org/en-US/docs/Web/API/notification/timestamp - * for details. - */ - timestamp?: number; - - /** - * A vibration pattern for the device's vibration hardware to emit - * when the notification fires. - */ - vibrate?: number | number[]; - [key: string]: any; - } - - /** - * Represents the APNs-specific options that can be included in an - * {@link messaging.Message}. Refer to - * [Apple documentation](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html) - * for various headers and payload fields supported by APNs. - */ - export interface ApnsConfig { - /** - * A collection of APNs headers. Header values must be strings. - */ - headers?: { [key: string]: string }; - - /** - * An APNs payload to be included in the message. - */ - payload?: ApnsPayload; - - /** - * Options for features provided by the FCM SDK for iOS. - */ - fcmOptions?: ApnsFcmOptions; - } - - /** - * Represents the payload of an APNs message. Mainly consists of the `aps` - * dictionary. But may also contain other arbitrary custom keys. - */ - export interface ApnsPayload { - - /** - * The `aps` dictionary to be included in the message. - */ - aps: Aps; - [customData: string]: any; - } - - /** - * Represents the [aps dictionary](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * that is part of APNs messages. - */ - export interface Aps { - - /** - * Alert to be included in the message. This may be a string or an object of - * type `admin.messaging.ApsAlert`. - */ - alert?: string | ApsAlert; - - /** - * Badge to be displayed with the message. Set to 0 to remove the badge. When - * not specified, the badge will remain unchanged. - */ - badge?: number; - - /** - * Sound to be played with the message. - */ - sound?: string | CriticalSound; - - /** - * Specifies whether to configure a background update notification. - */ - contentAvailable?: boolean; - - /** - * Specifies whether to set the `mutable-content` property on the message - * so the clients can modify the notification via app extensions. - */ - mutableContent?: boolean; - - /** - * Type of the notification. - */ - category?: string; - - /** - * An app-specific identifier for grouping notifications. - */ - threadId?: string; - [customData: string]: any; - } - - export interface ApsAlert { - title?: string; - subtitle?: string; - body?: string; - locKey?: string; - locArgs?: string[]; - titleLocKey?: string; - titleLocArgs?: string[]; - subtitleLocKey?: string; - subtitleLocArgs?: string[]; - actionLocKey?: string; - launchImage?: string; - } - - /** - * Represents a critical sound configuration that can be included in the - * `aps` dictionary of an APNs payload. - */ - export interface CriticalSound { - - /** - * The critical alert flag. Set to `true` to enable the critical alert. - */ - critical?: boolean; - - /** - * The name of a sound file in the app's main bundle or in the `Library/Sounds` - * folder of the app's container directory. Specify the string "default" to play - * the system sound. - */ - name: string; - - /** - * The volume for the critical alert's sound. Must be a value between 0.0 - * (silent) and 1.0 (full volume). - */ - volume?: number; - } - - /** - * Represents options for features provided by the FCM SDK for iOS. - */ - export interface ApnsFcmOptions { - - /** - * The label associated with the message's analytics data. - */ - analyticsLabel?: string; - - /** - * URL of an image to be displayed in the notification. - */ - imageUrl?: string; - } - - - /** - * Represents the Android-specific options that can be included in an - * {@link messaging.Message}. - */ - export interface AndroidConfig { - - /** - * Collapse key for the message. Collapse key serves as an identifier for a - * group of messages that can be collapsed, so that only the last message gets - * sent when delivery can be resumed. A maximum of four different collapse keys - * may be active at any given time. - */ - collapseKey?: string; - - /** - * Priority of the message. Must be either `normal` or `high`. - */ - priority?: ('high' | 'normal'); - - /** - * Time-to-live duration of the message in milliseconds. - */ - ttl?: number; - - /** - * Package name of the application where the registration tokens must match - * in order to receive the message. - */ - restrictedPackageName?: string; - - /** - * A collection of data fields to be included in the message. All values must - * be strings. When provided, overrides any data fields set on the top-level - * `admin.messaging.Message`.} - */ - data?: { [key: string]: string }; - - /** - * Android notification to be included in the message. - */ - notification?: AndroidNotification; - - /** - * Options for features provided by the FCM SDK for Android. - */ - fcmOptions?: AndroidFcmOptions; - } - - /** - * Represents the Android-specific notification options that can be included in - * {@link messaging.AndroidConfig}. - */ - export interface AndroidNotification { - /** - * Title of the Android notification. When provided, overrides the title set via - * `admin.messaging.Notification`. - */ - title?: string; - - /** - * Body of the Android notification. When provided, overrides the body set via - * `admin.messaging.Notification`. - */ - body?: string; - - /** - * Icon resource for the Android notification. - */ - icon?: string; - - /** - * Notification icon color in `#rrggbb` format. - */ - color?: string; - - /** - * File name of the sound to be played when the device receives the - * notification. - */ - sound?: string; - - /** - * Notification tag. This is an identifier used to replace existing - * notifications in the notification drawer. If not specified, each request - * creates a new notification. - */ - tag?: string; - - /** - * URL of an image to be displayed in the notification. - */ - imageUrl?: string; - - /** - * Action associated with a user click on the notification. If specified, an - * activity with a matching Intent Filter is launched when a user clicks on the - * notification. - */ - clickAction?: string; - - /** - * Key of the body string in the app's string resource to use to localize the - * body text. - * - */ - bodyLocKey?: string; - - /** - * An array of resource keys that will be used in place of the format - * specifiers in `bodyLocKey`. - */ - bodyLocArgs?: string[]; - - /** - * Key of the title string in the app's string resource to use to localize the - * title text. - */ - titleLocKey?: string; - - /** - * An array of resource keys that will be used in place of the format - * specifiers in `titleLocKey`. - */ - titleLocArgs?: string[]; - - /** - * The Android notification channel ID (new in Android O). The app must create - * a channel with this channel ID before any notification with this channel ID - * can be received. If you don't send this channel ID in the request, or if the - * channel ID provided has not yet been created by the app, FCM uses the channel - * ID specified in the app manifest. - */ - channelId?: string; - - /** - * Sets the "ticker" text, which is sent to accessibility services. Prior to - * API level 21 (Lollipop), sets the text that is displayed in the status bar - * when the notification first arrives. - */ - ticker?: string; - - /** - * When set to `false` or unset, the notification is automatically dismissed when - * the user clicks it in the panel. When set to `true`, the notification persists - * even when the user clicks it. - */ - sticky?: boolean; - - /** - * For notifications that inform users about events with an absolute time reference, sets - * the time that the event in the notification occurred. Notifications - * in the panel are sorted by this time. - */ - eventTimestamp?: Date; - - /** - * Sets whether or not this notification is relevant only to the current device. - * Some notifications can be bridged to other devices for remote display, such as - * a Wear OS watch. This hint can be set to recommend this notification not be bridged. - * See [Wear OS guides](https://developer.android.com/training/wearables/notifications/bridger#existing-method-of-preventing-bridging) - */ - localOnly?: boolean; - - /** - * Sets the relative priority for this notification. Low-priority notifications - * may be hidden from the user in certain situations. Note this priority differs - * from `AndroidMessagePriority`. This priority is processed by the client after - * the message has been delivered. Whereas `AndroidMessagePriority` is an FCM concept - * that controls when the message is delivered. - */ - priority?: ('min' | 'low' | 'default' | 'high' | 'max'); - - /** - * Sets the vibration pattern to use. Pass in an array of milliseconds to - * turn the vibrator on or off. The first value indicates the duration to wait before - * turning the vibrator on. The next value indicates the duration to keep the - * vibrator on. Subsequent values alternate between duration to turn the vibrator - * off and to turn the vibrator on. If `vibrate_timings` is set and `default_vibrate_timings` - * is set to `true`, the default value is used instead of the user-specified `vibrate_timings`. - */ - vibrateTimingsMillis?: number[]; - - /** - * If set to `true`, use the Android framework's default vibrate pattern for the - * notification. Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). - * If `default_vibrate_timings` is set to `true` and `vibrate_timings` is also set, - * the default value is used instead of the user-specified `vibrate_timings`. - */ - defaultVibrateTimings?: boolean; - - /** - * If set to `true`, use the Android framework's default sound for the notification. - * Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). - */ - defaultSound?: boolean; - - /** - * Settings to control the notification's LED blinking rate and color if LED is - * available on the device. The total blinking time is controlled by the OS. - */ - lightSettings?: LightSettings; - - /** - * If set to `true`, use the Android framework's default LED light settings - * for the notification. Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). - * If `default_light_settings` is set to `true` and `light_settings` is also set, - * the user-specified `light_settings` is used instead of the default value. - */ - defaultLightSettings?: boolean; - - /** - * Sets the visibility of the notification. Must be either `private`, `public`, - * or `secret`. If unspecified, defaults to `private`. - */ - visibility?: ('private' | 'public' | 'secret'); - - /** - * Sets the number of items this notification represents. May be displayed as a - * badge count for Launchers that support badging. See [`NotificationBadge`(https://developer.android.com/training/notify-user/badges). - * For example, this might be useful if you're using just one notification to - * represent multiple new messages but you want the count here to represent - * the number of total new messages. If zero or unspecified, systems - * that support badging use the default, which is to increment a number - * displayed on the long-press menu each time a new notification arrives. - */ - notificationCount?: number; - } - - /** - * Represents settings to control notification LED that can be included in - * {@link messaging.AndroidNotification}. - */ - export interface LightSettings { - /** - * Required. Sets color of the LED in `#rrggbb` or `#rrggbbaa` format. - */ - color: string; - - /** - * Required. Along with `light_off_duration`, defines the blink rate of LED flashes. - */ - lightOnDurationMillis: number; - - /** - * Required. Along with `light_on_duration`, defines the blink rate of LED flashes. - */ - lightOffDurationMillis: number; - } - - /** - * Represents options for features provided by the FCM SDK for Android. - */ - export interface AndroidFcmOptions { - - /** - * The label associated with the message's analytics data. - */ - analyticsLabel?: string; - } - - /** - * Interface representing an FCM legacy API data message payload. Data - * messages let developers send up to 4KB of custom key-value pairs. The - * keys and values must both be strings. Keys can be any custom string, - * except for the following reserved strings: - * - * * `"from"` - * * Anything starting with `"google."`. - * - * See [Build send requests](/docs/cloud-messaging/send-message) - * for code samples and detailed documentation. - */ - export interface DataMessagePayload { - [key: string]: string; - } - - /** - * Interface representing an FCM legacy API notification message payload. - * Notification messages let developers send up to 4KB of predefined - * key-value pairs. Accepted keys are outlined below. - * - * See [Build send requests](/docs/cloud-messaging/send-message) - * for code samples and detailed documentation. - */ - export interface NotificationMessagePayload { - - /** - * Identifier used to replace existing notifications in the notification drawer. - * - * If not specified, each request creates a new notification. - * - * If specified and a notification with the same tag is already being shown, - * the new notification replaces the existing one in the notification drawer. - * - * **Platforms:** Android - */ - tag?: string; - - /** - * The notification's body text. - * - * **Platforms:** iOS, Android, Web - */ - body?: string; - - /** - * The notification's icon. - * - * **Android:** Sets the notification icon to `myicon` for drawable resource - * `myicon`. If you don't send this key in the request, FCM displays the - * launcher icon specified in your app manifest. - * - * **Web:** The URL to use for the notification's icon. - * - * **Platforms:** Android, Web - */ - icon?: string; - - /** - * The value of the badge on the home screen app icon. - * - * If not specified, the badge is not changed. - * - * If set to `0`, the badge is removed. - * - * **Platforms:** iOS - */ - badge?: string; - - /** - * The notification icon's color, expressed in `#rrggbb` format. - * - * **Platforms:** Android - */ - color?: string; - - /** - * The sound to be played when the device receives a notification. Supports - * "default" for the default notification sound of the device or the filename of a - * sound resource bundled in the app. - * Sound files must reside in `/res/raw/`. - * - * **Platforms:** Android - */ - sound?: string; - - /** - * The notification's title. - * - * **Platforms:** iOS, Android, Web - */ - title?: string; - - /** - * The key to the body string in the app's string resources to use to localize - * the body text to the user's current localization. - * - * **iOS:** Corresponds to `loc-key` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. - * - * **Android:** See - * [String Resources](http://developer.android.com/guide/topics/resources/string-resource.html) * for more information. - * - * **Platforms:** iOS, Android - */ - bodyLocKey?: string; - - /** - * Variable string values to be used in place of the format specifiers in - * `body_loc_key` to use to localize the body text to the user's current - * localization. - * - * The value should be a stringified JSON array. - * - * **iOS:** Corresponds to `loc-args` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. - * - * **Android:** See - * [Formatting and Styling](http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling) - * for more information. - * - * **Platforms:** iOS, Android - */ - bodyLocArgs?: string; - - /** - * Action associated with a user click on the notification. If specified, an - * activity with a matching Intent Filter is launched when a user clicks on the - * notification. - * - * * **Platforms:** Android - */ - clickAction?: string; - - /** - * The key to the title string in the app's string resources to use to localize - * the title text to the user's current localization. - * - * **iOS:** Corresponds to `title-loc-key` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. - * - * **Android:** See - * [String Resources](http://developer.android.com/guide/topics/resources/string-resource.html) - * for more information. - * - * **Platforms:** iOS, Android - */ - titleLocKey?: string; - - /** - * Variable string values to be used in place of the format specifiers in - * `title_loc_key` to use to localize the title text to the user's current - * localization. - * - * The value should be a stringified JSON array. - * - * **iOS:** Corresponds to `title-loc-args` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. - * - * **Android:** See - * [Formatting and Styling](http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling) - * for more information. - * - * **Platforms:** iOS, Android - */ - titleLocArgs?: string; - [key: string]: string | undefined; - } - - /** - * Interface representing a Firebase Cloud Messaging message payload. One or - * both of the `data` and `notification` keys are required. - * - * See - * [Build send requests](/docs/cloud-messaging/send-message) - * for code samples and detailed documentation. - */ - export interface MessagingPayload { - - /** - * The data message payload. - */ - data?: DataMessagePayload; - - /** - * The notification message payload. - */ - notification?: NotificationMessagePayload; - } - - /** - * Interface representing the options that can be provided when sending a - * message via the FCM legacy APIs. - * - * See [Build send requests](/docs/cloud-messaging/send-message) - * for code samples and detailed documentation. - */ - export interface MessagingOptions { - - /** - * Whether or not the message should actually be sent. When set to `true`, - * allows developers to test a request without actually sending a message. When - * set to `false`, the message will be sent. - * - * **Default value:** `false` - */ - dryRun?: boolean; - - /** - * The priority of the message. Valid values are `"normal"` and `"high".` On - * iOS, these correspond to APNs priorities `5` and `10`. - * - * By default, notification messages are sent with high priority, and data - * messages are sent with normal priority. Normal priority optimizes the client - * app's battery consumption and should be used unless immediate delivery is - * required. For messages with normal priority, the app may receive the message - * with unspecified delay. - * - * When a message is sent with high priority, it is sent immediately, and the - * app can wake a sleeping device and open a network connection to your server. - * - * For more information, see - * [Setting the priority of a message](/docs/cloud-messaging/concept-options#setting-the-priority-of-a-message). - * - * **Default value:** `"high"` for notification messages, `"normal"` for data - * messages - */ - priority?: string; - - /** - * How long (in seconds) the message should be kept in FCM storage if the device - * is offline. The maximum time to live supported is four weeks, and the default - * value is also four weeks. For more information, see - * [Setting the lifespan of a message](/docs/cloud-messaging/concept-options#ttl). - * - * **Default value:** `2419200` (representing four weeks, in seconds) - */ - timeToLive?: number; - - /** - * String identifying a group of messages (for example, "Updates Available") - * that can be collapsed, so that only the last message gets sent when delivery - * can be resumed. This is used to avoid sending too many of the same messages - * when the device comes back online or becomes active. - * - * There is no guarantee of the order in which messages get sent. - * - * A maximum of four different collapse keys is allowed at any given time. This - * means FCM server can simultaneously store four different - * send-to-sync messages per client app. If you exceed this number, there is no - * guarantee which four collapse keys the FCM server will keep. - * - * **Default value:** None - */ - collapseKey?: string; - - /** - * On iOS, use this field to represent `mutable-content` in the APNs payload. - * When a notification is sent and this is set to `true`, the content of the - * notification can be modified before it is displayed, using a - * [Notification Service app extension](https://developer.apple.com/reference/usernotifications/unnotificationserviceextension) - * - * On Android and Web, this parameter will be ignored. - * - * **Default value:** `false` - */ - mutableContent?: boolean; - - /** - * On iOS, use this field to represent `content-available` in the APNs payload. - * When a notification or data message is sent and this is set to `true`, an - * inactive client app is awoken. On Android, data messages wake the app by - * default. On Chrome, this flag is currently not supported. - * - * **Default value:** `false` - */ - contentAvailable?: boolean; - - /** - * The package name of the application which the registration tokens must match - * in order to receive the message. - * - * **Default value:** None - */ - restrictedPackageName?: string; - [key: string]: any | undefined; - } - - /* Individual status response payload from single devices */ - export interface MessagingDeviceResult { - /** - * The error that occurred when processing the message for the recipient. - */ - error?: FirebaseError; - - /** - * A unique ID for the successfully processed message. - */ - messageId?: string; - - /** - * The canonical registration token for the client app that the message was - * processed and sent to. You should use this value as the registration token - * for future requests. Otherwise, future messages might be rejected. - */ - canonicalRegistrationToken?: string; - } - - /** - * Interface representing the status of a message sent to an individual device - * via the FCM legacy APIs. - * - * See - * [Send to individual devices](/docs/cloud-messaging/admin/send-messages#send_to_individual_devices) - * for code samples and detailed documentation. - */ - export interface MessagingDevicesResponse { - canonicalRegistrationTokenCount: number; - failureCount: number; - multicastId: number; - results: MessagingDeviceResult[]; - successCount: number; - } - - /** - * Interface representing the server response from the - * {@link messaging.Messaging.sendToDeviceGroup `sendToDeviceGroup()`} - * method. - * - * See - * [Send messages to device groups](/docs/cloud-messaging/send-message?authuser=0#send_messages_to_device_groups) - * for code samples and detailed documentation. - */ - export interface MessagingDeviceGroupResponse { - - /** - * The number of messages that could not be processed and resulted in an error. - */ - successCount: number; - - /** - * The number of messages that could not be processed and resulted in an error. - */ - failureCount: number; - - /** - * An array of registration tokens that failed to receive the message. - */ - failedRegistrationTokens: string[]; - } - - /** - * Interface representing the server response from the legacy - * {@link messaging.Messaging.sendToTopic `sendToTopic()`} method. - * - * See - * [Send to a topic](/docs/cloud-messaging/admin/send-messages#send_to_a_topic) - * for code samples and detailed documentation. - */ - export interface MessagingTopicResponse { - /** - * The message ID for a successfully received request which FCM will attempt to - * deliver to all subscribed devices. - */ - messageId: number; - } - - /** - * Interface representing the server response from the legacy - * {@link messaging.Messaging.sendToCondition `sendToCondition()`} method. - * - * See - * [Send to a condition](/docs/cloud-messaging/admin/send-messages#send_to_a_condition) - * for code samples and detailed documentation. - */ - export interface MessagingConditionResponse { - /** - * The message ID for a successfully received request which FCM will attempt to - * deliver to all subscribed devices. - */ - messageId: number; - } - - /** - * Interface representing the server response from the - * {@link messaging.Messaging.subscribeToTopic `subscribeToTopic()`} and - * {@link messaging.Messaging.unsubscribeFromTopic `unsubscribeFromTopic()`} - * methods. - * - * See - * [Manage topics from the server](/docs/cloud-messaging/manage-topics) - * for code samples and detailed documentation. - */ - export interface MessagingTopicManagementResponse { - /** - * The number of registration tokens that could not be subscribed to the topic - * and resulted in an error. - */ - failureCount: number; - - /** - * The number of registration tokens that were successfully subscribed to the - * topic. - */ - successCount: number; - - /** - * An array of errors corresponding to the provided registration token(s). The - * length of this array will be equal to [`failureCount`](#failureCount). - */ - errors: FirebaseArrayIndexError[]; - } - - /** - * Interface representing the server response from the - * {@link messaging.Messaging.sendAll `sendAll()`} and - * {@link messaging.Messaging.sendMulticast `sendMulticast()`} methods. - */ - export interface BatchResponse { - - /** - * An array of responses, each corresponding to a message. - */ - responses: SendResponse[]; - - /** - * The number of messages that were successfully handed off for sending. - */ - successCount: number; - - /** - * The number of messages that resulted in errors when sending. - */ - failureCount: number; - } - - /** - * Interface representing the status of an individual message that was sent as - * part of a batch request. - */ - export interface SendResponse { - /** - * A boolean indicating if the message was successfully handed off to FCM or - * not. When true, the `messageId` attribute is guaranteed to be set. When - * false, the `error` attribute is guaranteed to be set. - */ - success: boolean; - - /** - * A unique message ID string, if the message was handed off to FCM for - * delivery. - * - */ - messageId?: string; - - /** - * An error, if the message was not handed off to FCM successfully. - */ - error?: FirebaseError; - } - - export interface Messaging { - /** - * The {@link app.App app} associated with the current `Messaging` service - * instance. - * - * @example - * ```javascript - * var app = messaging.app; - * ``` - */ - app: app.App; - - /** - * Sends the given message via FCM. - * - * @param message The message payload. - * @param dryRun Whether to send the message in the dry-run - * (validation only) mode. - * @return A promise fulfilled with a unique message ID - * string after the message has been successfully handed off to the FCM - * service for delivery. - */ - send(message: Message, dryRun?: boolean): Promise; - - /** - * Sends all the messages in the given array via Firebase Cloud Messaging. - * Employs batching to send the entire list as a single RPC call. Compared - * to the `send()` method, this method is a significantly more efficient way - * to send multiple messages. - * - * The responses list obtained from the return value - * corresponds to the order of tokens in the `MulticastMessage`. An error - * from this method indicates a total failure -- i.e. none of the messages in - * the list could be sent. Partial failures are indicated by a `BatchResponse` - * return value. - * - * @param messages A non-empty array - * containing up to 500 messages. - * @param dryRun Whether to send the messages in the dry-run - * (validation only) mode. - * @return A Promise fulfilled with an object representing the result of the - * send operation. - */ - sendAll( - messages: Array, - dryRun?: boolean - ): Promise; - - /** - * Sends the given multicast message to all the FCM registration tokens - * specified in it. - * - * This method uses the `sendAll()` API under the hood to send the given - * message to all the target recipients. The responses list obtained from the - * return value corresponds to the order of tokens in the `MulticastMessage`. - * An error from this method indicates a total failure -- i.e. the message was - * not sent to any of the tokens in the list. Partial failures are indicated by - * a `BatchResponse` return value. - * - * @param message A multicast message - * containing up to 500 tokens. - * @param dryRun Whether to send the message in the dry-run - * (validation only) mode. - * @return A Promise fulfilled with an object representing the result of the - * send operation. - */ - sendMulticast( - message: MulticastMessage, - dryRun?: boolean - ): Promise; - - /** - * Sends an FCM message to a single device corresponding to the provided - * registration token. - * - * See - * [Send to individual devices](/docs/cloud-messaging/admin/legacy-fcm#send_to_individual_devices) - * for code samples and detailed documentation. Takes either a - * `registrationToken` to send to a single device or a - * `registrationTokens` parameter containing an array of tokens to send - * to multiple devices. - * - * @param registrationToken A device registration token or an array of - * device registration tokens to which the message should be sent. - * @param payload The message payload. - * @param options Optional options to - * alter the message. - * - * @return A promise fulfilled with the server's response after the message - * has been sent. - */ - sendToDevice( - registrationToken: string | string[], - payload: MessagingPayload, - options?: MessagingOptions - ): Promise; - - /** - * Sends an FCM message to a device group corresponding to the provided - * notification key. - * - * See - * [Send to a device group](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_device_group) - * for code samples and detailed documentation. - * - * @param notificationKey The notification key for the device group to - * which to send the message. - * @param payload The message payload. - * @param options Optional options to - * alter the message. - * - * @return A promise fulfilled with the server's response after the message - * has been sent. - */ - sendToDeviceGroup( - notificationKey: string, - payload: MessagingPayload, - options?: MessagingOptions - ): Promise; - - /** - * Sends an FCM message to a topic. - * - * See - * [Send to a topic](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_topic) - * for code samples and detailed documentation. - * - * @param topic The topic to which to send the message. - * @param payload The message payload. - * @param options Optional options to - * alter the message. - * - * @return A promise fulfilled with the server's response after the message - * has been sent. - */ - sendToTopic( - topic: string, - payload: MessagingPayload, - options?: MessagingOptions - ): Promise; - - /** - * Sends an FCM message to a condition. - * - * See - * [Send to a condition](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_condition) - * for code samples and detailed documentation. - * - * @param condition The condition determining to which topics to send - * the message. - * @param payload The message payload. - * @param options Optional options to - * alter the message. - * - * @return A promise fulfilled with the server's response after the message - * has been sent. - */ - sendToCondition( - condition: string, - payload: MessagingPayload, - options?: MessagingOptions - ): Promise; - - /** - * Subscribes a device to an FCM topic. - * - * See [Subscribe to a - * topic](/docs/cloud-messaging/manage-topics#suscribe_and_unsubscribe_using_the) - * for code samples and detailed documentation. Optionally, you can provide an - * array of tokens to subscribe multiple devices. - * - * @param registrationTokens A token or array of registration tokens - * for the devices to subscribe to the topic. - * @param topic The topic to which to subscribe. - * - * @return A promise fulfilled with the server's response after the device has been - * subscribed to the topic. - */ - subscribeToTopic( - registrationTokens: string | string[], - topic: string - ): Promise; - - /** - * Unsubscribes a device from an FCM topic. - * - * See [Unsubscribe from a - * topic](/docs/cloud-messaging/admin/manage-topic-subscriptions#unsubscribe_from_a_topic) - * for code samples and detailed documentation. Optionally, you can provide an - * array of tokens to unsubscribe multiple devices. - * - * @param registrationTokens A device registration token or an array of - * device registration tokens to unsubscribe from the topic. - * @param topic The topic from which to unsubscribe. - * - * @return A promise fulfilled with the server's response after the device has been - * unsubscribed from the topic. - */ - unsubscribeFromTopic( - registrationTokens: string | string[], - topic: string - ): Promise; - } + export type Messaging = TMessaging; + + export type AndroidConfig = TAndroidConfig; + export type AndroidFcmOptions = TAndroidFcmOptions; + export type AndroidNotification = TAndroidNotification; + export type ApnsConfig = TApnsConfig; + export type ApnsFcmOptions = TApnsFcmOptions; + export type ApnsPayload = TApnsPayload; + export type Aps = TAps; + export type ApsAlert = TApsAlert; + export type BatchResponse = TBatchResponse; + export type CriticalSound = TCriticalSound; + export type ConditionMessage = TConditionMessage; + export type FcmOptions = TFcmOptions; + export type LightSettings = TLightSettings; + export type Message = TMessage; + export type MessagingTopicManagementResponse = TMessagingTopicManagementResponse; + export type MulticastMessage = TMulticastMessage; + export type Notification = TNotification; + export type SendResponse = TSendResponse; + export type TokenMessage = TTokenMessage; + export type TopicMessage = TTopicMessage; + export type WebpushConfig = TWebpushConfig; + export type WebpushFcmOptions = TWebpushFcmOptions; + export type WebpushNotification = TWebpushNotification; + + // Legacy APIs + export type DataMessagePayload = TDataMessagePayload; + export type MessagingConditionResponse = TMessagingConditionResponse; + export type MessagingDeviceGroupResponse = TMessagingDeviceGroupResponse; + export type MessagingDeviceResult = TMessagingDeviceResult; + export type MessagingDevicesResponse = TMessagingDevicesResponse; + export type MessagingOptions = TMessagingOptions; + export type MessagingPayload = TMessagingPayload; + export type MessagingTopicResponse = TMessagingTopicResponse; + export type NotificationMessagePayload = TNotificationMessagePayload; } diff --git a/src/messaging/messaging-api-request-internal.ts b/src/messaging/messaging-api-request-internal.ts index 1e63369018..31122a3df4 100644 --- a/src/messaging/messaging-api-request-internal.ts +++ b/src/messaging/messaging-api-request-internal.ts @@ -15,17 +15,16 @@ * limitations under the License. */ +import { App } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { HttpMethod, AuthorizedHttpClient, HttpRequestConfig, HttpError, HttpResponse, } from '../utils/api-request'; import { createFirebaseError, getErrorCode } from './messaging-errors-internal'; import { SubRequest, BatchRequestClient } from './batch-request-internal'; -import { messaging } from './index'; import { getSdkVersion } from '../utils/index'; +import { SendResponse, BatchResponse } from './messaging-api'; -import SendResponse = messaging.SendResponse; -import BatchResponse = messaging.BatchResponse; // FCM backend constants const FIREBASE_MESSAGING_TIMEOUT = 10000; @@ -48,11 +47,11 @@ export class FirebaseMessagingRequestHandler { private readonly batchClient: BatchRequestClient; /** - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param app The app used to fetch access tokens to sign API requests. * @constructor */ - constructor(app: FirebaseApp) { - this.httpClient = new AuthorizedHttpClient(app); + constructor(app: App) { + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); this.batchClient = new BatchRequestClient( this.httpClient, FIREBASE_MESSAGING_BATCH_URL, FIREBASE_MESSAGING_HEADERS); } diff --git a/src/messaging/messaging-api.ts b/src/messaging/messaging-api.ts new file mode 100644 index 0000000000..44c68e66eb --- /dev/null +++ b/src/messaging/messaging-api.ts @@ -0,0 +1,1106 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseArrayIndexError, FirebaseError } from '../firebase-namespace-api'; + +export interface BaseMessage { + data?: { [key: string]: string }; + notification?: Notification; + android?: AndroidConfig; + webpush?: WebpushConfig; + apns?: ApnsConfig; + fcmOptions?: FcmOptions; +} + +export interface TokenMessage extends BaseMessage { + token: string; +} + +export interface TopicMessage extends BaseMessage { + topic: string; +} + +export interface ConditionMessage extends BaseMessage { + condition: string; +} + +/** + * Payload for the admin.messaging.send() operation. The payload contains all the fields + * in the BaseMessage type, and exactly one of token, topic or condition. + */ +export type Message = TokenMessage | TopicMessage | ConditionMessage; + +/** + * Payload for the admin.messaing.sendMulticast() method. The payload contains all the fields + * in the BaseMessage type, and a list of tokens. + */ +export interface MulticastMessage extends BaseMessage { + tokens: string[]; +} + +/** + * A notification that can be included in {@link messaging.Message}. + */ +export interface Notification { + /** + * The title of the notification. + */ + title?: string; + /** + * The notification body + */ + body?: string; + /** + * URL of an image to be displayed in the notification. + */ + imageUrl?: string; +} + +/** + * Represents platform-independent options for features provided by the FCM SDKs. + */ +export interface FcmOptions { + /** + * The label associated with the message's analytics data. + */ + analyticsLabel?: string; +} + +/** + * Represents the WebPush protocol options that can be included in an + * {@link messaging.Message}. + */ +export interface WebpushConfig { + + /** + * A collection of WebPush headers. Header values must be strings. + * + * See [WebPush specification](https://tools.ietf.org/html/rfc8030#section-5) + * for supported headers. + */ + headers?: { [key: string]: string }; + + /** + * A collection of data fields. + */ + data?: { [key: string]: string }; + + /** + * A WebPush notification payload to be included in the message. + */ + notification?: WebpushNotification; + + /** + * Options for features provided by the FCM SDK for Web. + */ + fcmOptions?: WebpushFcmOptions; +} + +/** Represents options for features provided by the FCM SDK for Web + * (which are not part of the Webpush standard). + */ +export interface WebpushFcmOptions { + + /** + * The link to open when the user clicks on the notification. + * For all URL values, HTTPS is required. + */ + link?: string; +} + +/** + * Represents the WebPush-specific notification options that can be included in + * {@link messaging.WebpushConfig}. This supports most of the standard + * options as defined in the Web Notification + * [specification](https://developer.mozilla.org/en-US/docs/Web/API/notification/Notification). + */ +export interface WebpushNotification { + + /** + * Title text of the notification. + */ + title?: string; + + /** + * An array of notification actions representing the actions + * available to the user when the notification is presented. + */ + actions?: Array<{ + + /** + * An action available to the user when the notification is presented + */ + action: string; + + /** + * Optional icon for a notification action. + */ + icon?: string; + + /** + * Title of the notification action. + */ + title: string; + }>; + + /** + * URL of the image used to represent the notification when there is + * not enough space to display the notification itself. + */ + badge?: string; + + /** + * Body text of the notification. + */ + body?: string; + + /** + * Arbitrary data that you want associated with the notification. + * This can be of any data type. + */ + data?: any; + + /** + * The direction in which to display the notification. Must be one + * of `auto`, `ltr` or `rtl`. + */ + dir?: 'auto' | 'ltr' | 'rtl'; + + /** + * URL to the notification icon. + */ + icon?: string; + + /** + * URL of an image to be displayed in the notification. + */ + image?: string; + + /** + * The notification's language as a BCP 47 language tag. + */ + lang?: string; + + /** + * A boolean specifying whether the user should be notified after a + * new notification replaces an old one. Defaults to false. + */ + renotify?: boolean; + + /** + * Indicates that a notification should remain active until the user + * clicks or dismisses it, rather than closing automatically. + * Defaults to false. + */ + requireInteraction?: boolean; + + /** + * A boolean specifying whether the notification should be silent. + * Defaults to false. + */ + silent?: boolean; + + /** + * An identifying tag for the notification. + */ + tag?: string; + + /** + * Timestamp of the notification. Refer to + * https://developer.mozilla.org/en-US/docs/Web/API/notification/timestamp + * for details. + */ + timestamp?: number; + + /** + * A vibration pattern for the device's vibration hardware to emit + * when the notification fires. + */ + vibrate?: number | number[]; + [key: string]: any; +} + +/** + * Represents the APNs-specific options that can be included in an + * {@link messaging.Message}. Refer to + * [Apple documentation](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html) + * for various headers and payload fields supported by APNs. + */ +export interface ApnsConfig { + /** + * A collection of APNs headers. Header values must be strings. + */ + headers?: { [key: string]: string }; + + /** + * An APNs payload to be included in the message. + */ + payload?: ApnsPayload; + + /** + * Options for features provided by the FCM SDK for iOS. + */ + fcmOptions?: ApnsFcmOptions; +} + +/** + * Represents the payload of an APNs message. Mainly consists of the `aps` + * dictionary. But may also contain other arbitrary custom keys. + */ +export interface ApnsPayload { + + /** + * The `aps` dictionary to be included in the message. + */ + aps: Aps; + [customData: string]: any; +} + +/** + * Represents the [aps dictionary](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) + * that is part of APNs messages. + */ +export interface Aps { + + /** + * Alert to be included in the message. This may be a string or an object of + * type `admin.messaging.ApsAlert`. + */ + alert?: string | ApsAlert; + + /** + * Badge to be displayed with the message. Set to 0 to remove the badge. When + * not specified, the badge will remain unchanged. + */ + badge?: number; + + /** + * Sound to be played with the message. + */ + sound?: string | CriticalSound; + + /** + * Specifies whether to configure a background update notification. + */ + contentAvailable?: boolean; + + /** + * Specifies whether to set the `mutable-content` property on the message + * so the clients can modify the notification via app extensions. + */ + mutableContent?: boolean; + + /** + * Type of the notification. + */ + category?: string; + + /** + * An app-specific identifier for grouping notifications. + */ + threadId?: string; + [customData: string]: any; +} + +export interface ApsAlert { + title?: string; + subtitle?: string; + body?: string; + locKey?: string; + locArgs?: string[]; + titleLocKey?: string; + titleLocArgs?: string[]; + subtitleLocKey?: string; + subtitleLocArgs?: string[]; + actionLocKey?: string; + launchImage?: string; +} + +/** + * Represents a critical sound configuration that can be included in the + * `aps` dictionary of an APNs payload. + */ +export interface CriticalSound { + + /** + * The critical alert flag. Set to `true` to enable the critical alert. + */ + critical?: boolean; + + /** + * The name of a sound file in the app's main bundle or in the `Library/Sounds` + * folder of the app's container directory. Specify the string "default" to play + * the system sound. + */ + name: string; + + /** + * The volume for the critical alert's sound. Must be a value between 0.0 + * (silent) and 1.0 (full volume). + */ + volume?: number; +} + +/** + * Represents options for features provided by the FCM SDK for iOS. + */ +export interface ApnsFcmOptions { + + /** + * The label associated with the message's analytics data. + */ + analyticsLabel?: string; + + /** + * URL of an image to be displayed in the notification. + */ + imageUrl?: string; +} + + +/** + * Represents the Android-specific options that can be included in an + * {@link messaging.Message}. + */ +export interface AndroidConfig { + + /** + * Collapse key for the message. Collapse key serves as an identifier for a + * group of messages that can be collapsed, so that only the last message gets + * sent when delivery can be resumed. A maximum of four different collapse keys + * may be active at any given time. + */ + collapseKey?: string; + + /** + * Priority of the message. Must be either `normal` or `high`. + */ + priority?: ('high' | 'normal'); + + /** + * Time-to-live duration of the message in milliseconds. + */ + ttl?: number; + + /** + * Package name of the application where the registration tokens must match + * in order to receive the message. + */ + restrictedPackageName?: string; + + /** + * A collection of data fields to be included in the message. All values must + * be strings. When provided, overrides any data fields set on the top-level + * `admin.messaging.Message`.} + */ + data?: { [key: string]: string }; + + /** + * Android notification to be included in the message. + */ + notification?: AndroidNotification; + + /** + * Options for features provided by the FCM SDK for Android. + */ + fcmOptions?: AndroidFcmOptions; +} + +/** + * Represents the Android-specific notification options that can be included in + * {@link messaging.AndroidConfig}. + */ +export interface AndroidNotification { + /** + * Title of the Android notification. When provided, overrides the title set via + * `admin.messaging.Notification`. + */ + title?: string; + + /** + * Body of the Android notification. When provided, overrides the body set via + * `admin.messaging.Notification`. + */ + body?: string; + + /** + * Icon resource for the Android notification. + */ + icon?: string; + + /** + * Notification icon color in `#rrggbb` format. + */ + color?: string; + + /** + * File name of the sound to be played when the device receives the + * notification. + */ + sound?: string; + + /** + * Notification tag. This is an identifier used to replace existing + * notifications in the notification drawer. If not specified, each request + * creates a new notification. + */ + tag?: string; + + /** + * URL of an image to be displayed in the notification. + */ + imageUrl?: string; + + /** + * Action associated with a user click on the notification. If specified, an + * activity with a matching Intent Filter is launched when a user clicks on the + * notification. + */ + clickAction?: string; + + /** + * Key of the body string in the app's string resource to use to localize the + * body text. + * + */ + bodyLocKey?: string; + + /** + * An array of resource keys that will be used in place of the format + * specifiers in `bodyLocKey`. + */ + bodyLocArgs?: string[]; + + /** + * Key of the title string in the app's string resource to use to localize the + * title text. + */ + titleLocKey?: string; + + /** + * An array of resource keys that will be used in place of the format + * specifiers in `titleLocKey`. + */ + titleLocArgs?: string[]; + + /** + * The Android notification channel ID (new in Android O). The app must create + * a channel with this channel ID before any notification with this channel ID + * can be received. If you don't send this channel ID in the request, or if the + * channel ID provided has not yet been created by the app, FCM uses the channel + * ID specified in the app manifest. + */ + channelId?: string; + + /** + * Sets the "ticker" text, which is sent to accessibility services. Prior to + * API level 21 (Lollipop), sets the text that is displayed in the status bar + * when the notification first arrives. + */ + ticker?: string; + + /** + * When set to `false` or unset, the notification is automatically dismissed when + * the user clicks it in the panel. When set to `true`, the notification persists + * even when the user clicks it. + */ + sticky?: boolean; + + /** + * For notifications that inform users about events with an absolute time reference, sets + * the time that the event in the notification occurred. Notifications + * in the panel are sorted by this time. + */ + eventTimestamp?: Date; + + /** + * Sets whether or not this notification is relevant only to the current device. + * Some notifications can be bridged to other devices for remote display, such as + * a Wear OS watch. This hint can be set to recommend this notification not be bridged. + * See [Wear OS guides](https://developer.android.com/training/wearables/notifications/bridger#existing-method-of-preventing-bridging) + */ + localOnly?: boolean; + + /** + * Sets the relative priority for this notification. Low-priority notifications + * may be hidden from the user in certain situations. Note this priority differs + * from `AndroidMessagePriority`. This priority is processed by the client after + * the message has been delivered. Whereas `AndroidMessagePriority` is an FCM concept + * that controls when the message is delivered. + */ + priority?: ('min' | 'low' | 'default' | 'high' | 'max'); + + /** + * Sets the vibration pattern to use. Pass in an array of milliseconds to + * turn the vibrator on or off. The first value indicates the duration to wait before + * turning the vibrator on. The next value indicates the duration to keep the + * vibrator on. Subsequent values alternate between duration to turn the vibrator + * off and to turn the vibrator on. If `vibrate_timings` is set and `default_vibrate_timings` + * is set to `true`, the default value is used instead of the user-specified `vibrate_timings`. + */ + vibrateTimingsMillis?: number[]; + + /** + * If set to `true`, use the Android framework's default vibrate pattern for the + * notification. Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). + * If `default_vibrate_timings` is set to `true` and `vibrate_timings` is also set, + * the default value is used instead of the user-specified `vibrate_timings`. + */ + defaultVibrateTimings?: boolean; + + /** + * If set to `true`, use the Android framework's default sound for the notification. + * Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). + */ + defaultSound?: boolean; + + /** + * Settings to control the notification's LED blinking rate and color if LED is + * available on the device. The total blinking time is controlled by the OS. + */ + lightSettings?: LightSettings; + + /** + * If set to `true`, use the Android framework's default LED light settings + * for the notification. Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). + * If `default_light_settings` is set to `true` and `light_settings` is also set, + * the user-specified `light_settings` is used instead of the default value. + */ + defaultLightSettings?: boolean; + + /** + * Sets the visibility of the notification. Must be either `private`, `public`, + * or `secret`. If unspecified, defaults to `private`. + */ + visibility?: ('private' | 'public' | 'secret'); + + /** + * Sets the number of items this notification represents. May be displayed as a + * badge count for Launchers that support badging. See [`NotificationBadge`(https://developer.android.com/training/notify-user/badges). + * For example, this might be useful if you're using just one notification to + * represent multiple new messages but you want the count here to represent + * the number of total new messages. If zero or unspecified, systems + * that support badging use the default, which is to increment a number + * displayed on the long-press menu each time a new notification arrives. + */ + notificationCount?: number; +} + +/** + * Represents settings to control notification LED that can be included in + * {@link messaging.AndroidNotification}. + */ +export interface LightSettings { + /** + * Required. Sets color of the LED in `#rrggbb` or `#rrggbbaa` format. + */ + color: string; + + /** + * Required. Along with `light_off_duration`, defines the blink rate of LED flashes. + */ + lightOnDurationMillis: number; + + /** + * Required. Along with `light_on_duration`, defines the blink rate of LED flashes. + */ + lightOffDurationMillis: number; +} + +/** + * Represents options for features provided by the FCM SDK for Android. + */ +export interface AndroidFcmOptions { + + /** + * The label associated with the message's analytics data. + */ + analyticsLabel?: string; +} + +/** + * Interface representing an FCM legacy API data message payload. Data + * messages let developers send up to 4KB of custom key-value pairs. The + * keys and values must both be strings. Keys can be any custom string, + * except for the following reserved strings: + * + * * `"from"` + * * Anything starting with `"google."`. + * + * See [Build send requests](/docs/cloud-messaging/send-message) + * for code samples and detailed documentation. + */ +export interface DataMessagePayload { + [key: string]: string; +} + +/** + * Interface representing an FCM legacy API notification message payload. + * Notification messages let developers send up to 4KB of predefined + * key-value pairs. Accepted keys are outlined below. + * + * See [Build send requests](/docs/cloud-messaging/send-message) + * for code samples and detailed documentation. + */ +export interface NotificationMessagePayload { + + /** + * Identifier used to replace existing notifications in the notification drawer. + * + * If not specified, each request creates a new notification. + * + * If specified and a notification with the same tag is already being shown, + * the new notification replaces the existing one in the notification drawer. + * + * **Platforms:** Android + */ + tag?: string; + + /** + * The notification's body text. + * + * **Platforms:** iOS, Android, Web + */ + body?: string; + + /** + * The notification's icon. + * + * **Android:** Sets the notification icon to `myicon` for drawable resource + * `myicon`. If you don't send this key in the request, FCM displays the + * launcher icon specified in your app manifest. + * + * **Web:** The URL to use for the notification's icon. + * + * **Platforms:** Android, Web + */ + icon?: string; + + /** + * The value of the badge on the home screen app icon. + * + * If not specified, the badge is not changed. + * + * If set to `0`, the badge is removed. + * + * **Platforms:** iOS + */ + badge?: string; + + /** + * The notification icon's color, expressed in `#rrggbb` format. + * + * **Platforms:** Android + */ + color?: string; + + /** + * The sound to be played when the device receives a notification. Supports + * "default" for the default notification sound of the device or the filename of a + * sound resource bundled in the app. + * Sound files must reside in `/res/raw/`. + * + * **Platforms:** Android + */ + sound?: string; + + /** + * The notification's title. + * + * **Platforms:** iOS, Android, Web + */ + title?: string; + + /** + * The key to the body string in the app's string resources to use to localize + * the body text to the user's current localization. + * + * **iOS:** Corresponds to `loc-key` in the APNs payload. See + * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) + * and + * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) + * for more information. + * + * **Android:** See + * [String Resources](http://developer.android.com/guide/topics/resources/string-resource.html) * for more information. + * + * **Platforms:** iOS, Android + */ + bodyLocKey?: string; + + /** + * Variable string values to be used in place of the format specifiers in + * `body_loc_key` to use to localize the body text to the user's current + * localization. + * + * The value should be a stringified JSON array. + * + * **iOS:** Corresponds to `loc-args` in the APNs payload. See + * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) + * and + * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) + * for more information. + * + * **Android:** See + * [Formatting and Styling](http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling) + * for more information. + * + * **Platforms:** iOS, Android + */ + bodyLocArgs?: string; + + /** + * Action associated with a user click on the notification. If specified, an + * activity with a matching Intent Filter is launched when a user clicks on the + * notification. + * + * * **Platforms:** Android + */ + clickAction?: string; + + /** + * The key to the title string in the app's string resources to use to localize + * the title text to the user's current localization. + * + * **iOS:** Corresponds to `title-loc-key` in the APNs payload. See + * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) + * and + * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) + * for more information. + * + * **Android:** See + * [String Resources](http://developer.android.com/guide/topics/resources/string-resource.html) + * for more information. + * + * **Platforms:** iOS, Android + */ + titleLocKey?: string; + + /** + * Variable string values to be used in place of the format specifiers in + * `title_loc_key` to use to localize the title text to the user's current + * localization. + * + * The value should be a stringified JSON array. + * + * **iOS:** Corresponds to `title-loc-args` in the APNs payload. See + * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) + * and + * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) + * for more information. + * + * **Android:** See + * [Formatting and Styling](http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling) + * for more information. + * + * **Platforms:** iOS, Android + */ + titleLocArgs?: string; + [key: string]: string | undefined; +} + +/** + * Interface representing a Firebase Cloud Messaging message payload. One or + * both of the `data` and `notification` keys are required. + * + * See + * [Build send requests](/docs/cloud-messaging/send-message) + * for code samples and detailed documentation. + */ +export interface MessagingPayload { + + /** + * The data message payload. + */ + data?: DataMessagePayload; + + /** + * The notification message payload. + */ + notification?: NotificationMessagePayload; +} + +/** + * Interface representing the options that can be provided when sending a + * message via the FCM legacy APIs. + * + * See [Build send requests](/docs/cloud-messaging/send-message) + * for code samples and detailed documentation. + */ +export interface MessagingOptions { + + /** + * Whether or not the message should actually be sent. When set to `true`, + * allows developers to test a request without actually sending a message. When + * set to `false`, the message will be sent. + * + * **Default value:** `false` + */ + dryRun?: boolean; + + /** + * The priority of the message. Valid values are `"normal"` and `"high".` On + * iOS, these correspond to APNs priorities `5` and `10`. + * + * By default, notification messages are sent with high priority, and data + * messages are sent with normal priority. Normal priority optimizes the client + * app's battery consumption and should be used unless immediate delivery is + * required. For messages with normal priority, the app may receive the message + * with unspecified delay. + * + * When a message is sent with high priority, it is sent immediately, and the + * app can wake a sleeping device and open a network connection to your server. + * + * For more information, see + * [Setting the priority of a message](/docs/cloud-messaging/concept-options#setting-the-priority-of-a-message). + * + * **Default value:** `"high"` for notification messages, `"normal"` for data + * messages + */ + priority?: string; + + /** + * How long (in seconds) the message should be kept in FCM storage if the device + * is offline. The maximum time to live supported is four weeks, and the default + * value is also four weeks. For more information, see + * [Setting the lifespan of a message](/docs/cloud-messaging/concept-options#ttl). + * + * **Default value:** `2419200` (representing four weeks, in seconds) + */ + timeToLive?: number; + + /** + * String identifying a group of messages (for example, "Updates Available") + * that can be collapsed, so that only the last message gets sent when delivery + * can be resumed. This is used to avoid sending too many of the same messages + * when the device comes back online or becomes active. + * + * There is no guarantee of the order in which messages get sent. + * + * A maximum of four different collapse keys is allowed at any given time. This + * means FCM server can simultaneously store four different + * send-to-sync messages per client app. If you exceed this number, there is no + * guarantee which four collapse keys the FCM server will keep. + * + * **Default value:** None + */ + collapseKey?: string; + + /** + * On iOS, use this field to represent `mutable-content` in the APNs payload. + * When a notification is sent and this is set to `true`, the content of the + * notification can be modified before it is displayed, using a + * [Notification Service app extension](https://developer.apple.com/reference/usernotifications/unnotificationserviceextension) + * + * On Android and Web, this parameter will be ignored. + * + * **Default value:** `false` + */ + mutableContent?: boolean; + + /** + * On iOS, use this field to represent `content-available` in the APNs payload. + * When a notification or data message is sent and this is set to `true`, an + * inactive client app is awoken. On Android, data messages wake the app by + * default. On Chrome, this flag is currently not supported. + * + * **Default value:** `false` + */ + contentAvailable?: boolean; + + /** + * The package name of the application which the registration tokens must match + * in order to receive the message. + * + * **Default value:** None + */ + restrictedPackageName?: string; + [key: string]: any | undefined; +} + +/* Individual status response payload from single devices */ +export interface MessagingDeviceResult { + /** + * The error that occurred when processing the message for the recipient. + */ + error?: FirebaseError; + + /** + * A unique ID for the successfully processed message. + */ + messageId?: string; + + /** + * The canonical registration token for the client app that the message was + * processed and sent to. You should use this value as the registration token + * for future requests. Otherwise, future messages might be rejected. + */ + canonicalRegistrationToken?: string; +} + +/** + * Interface representing the status of a message sent to an individual device + * via the FCM legacy APIs. + * + * See + * [Send to individual devices](/docs/cloud-messaging/admin/send-messages#send_to_individual_devices) + * for code samples and detailed documentation. + */ +export interface MessagingDevicesResponse { + canonicalRegistrationTokenCount: number; + failureCount: number; + multicastId: number; + results: MessagingDeviceResult[]; + successCount: number; +} + +/** + * Interface representing the server response from the + * {@link messaging.Messaging.sendToDeviceGroup `sendToDeviceGroup()`} + * method. + * + * See + * [Send messages to device groups](/docs/cloud-messaging/send-message?authuser=0#send_messages_to_device_groups) + * for code samples and detailed documentation. + */ +export interface MessagingDeviceGroupResponse { + + /** + * The number of messages that could not be processed and resulted in an error. + */ + successCount: number; + + /** + * The number of messages that could not be processed and resulted in an error. + */ + failureCount: number; + + /** + * An array of registration tokens that failed to receive the message. + */ + failedRegistrationTokens: string[]; +} + +/** + * Interface representing the server response from the legacy + * {@link messaging.Messaging.sendToTopic `sendToTopic()`} method. + * + * See + * [Send to a topic](/docs/cloud-messaging/admin/send-messages#send_to_a_topic) + * for code samples and detailed documentation. + */ +export interface MessagingTopicResponse { + /** + * The message ID for a successfully received request which FCM will attempt to + * deliver to all subscribed devices. + */ + messageId: number; +} + +/** + * Interface representing the server response from the legacy + * {@link messaging.Messaging.sendToCondition `sendToCondition()`} method. + * + * See + * [Send to a condition](/docs/cloud-messaging/admin/send-messages#send_to_a_condition) + * for code samples and detailed documentation. + */ +export interface MessagingConditionResponse { + /** + * The message ID for a successfully received request which FCM will attempt to + * deliver to all subscribed devices. + */ + messageId: number; +} + +/** + * Interface representing the server response from the + * {@link messaging.Messaging.subscribeToTopic `subscribeToTopic()`} and + * {@link messaging.Messaging.unsubscribeFromTopic `unsubscribeFromTopic()`} + * methods. + * + * See + * [Manage topics from the server](/docs/cloud-messaging/manage-topics) + * for code samples and detailed documentation. + */ +export interface MessagingTopicManagementResponse { + /** + * The number of registration tokens that could not be subscribed to the topic + * and resulted in an error. + */ + failureCount: number; + + /** + * The number of registration tokens that were successfully subscribed to the + * topic. + */ + successCount: number; + + /** + * An array of errors corresponding to the provided registration token(s). The + * length of this array will be equal to [`failureCount`](#failureCount). + */ + errors: FirebaseArrayIndexError[]; +} + +/** + * Interface representing the server response from the + * {@link messaging.Messaging.sendAll `sendAll()`} and + * {@link messaging.Messaging.sendMulticast `sendMulticast()`} methods. + */ +export interface BatchResponse { + + /** + * An array of responses, each corresponding to a message. + */ + responses: SendResponse[]; + + /** + * The number of messages that were successfully handed off for sending. + */ + successCount: number; + + /** + * The number of messages that resulted in errors when sending. + */ + failureCount: number; +} + +/** + * Interface representing the status of an individual message that was sent as + * part of a batch request. + */ +export interface SendResponse { + /** + * A boolean indicating if the message was successfully handed off to FCM or + * not. When true, the `messageId` attribute is guaranteed to be set. When + * false, the `error` attribute is guaranteed to be set. + */ + success: boolean; + + /** + * A unique message ID string, if the message was handed off to FCM for + * delivery. + * + */ + messageId?: string; + + /** + * An error, if the message was not handed off to FCM successfully. + */ + error?: FirebaseError; +} diff --git a/src/messaging/messaging-internal.ts b/src/messaging/messaging-internal.ts index 227f894eea..4d68965774 100644 --- a/src/messaging/messaging-internal.ts +++ b/src/messaging/messaging-internal.ts @@ -16,23 +16,13 @@ import { renameProperties } from '../utils/index'; import { MessagingClientErrorCode, FirebaseMessagingError, } from '../utils/error'; -import { messaging } from './index'; import * as validator from '../utils/validator'; -import AndroidConfig = messaging.AndroidConfig; -import AndroidFcmOptions = messaging.AndroidFcmOptions; -import AndroidNotification = messaging.AndroidNotification; -import ApsAlert = messaging.ApsAlert; -import ApnsConfig = messaging.ApnsConfig; -import ApnsFcmOptions = messaging.ApnsFcmOptions; -import ApnsPayload = messaging.ApnsPayload; -import Aps = messaging.Aps; -import CriticalSound = messaging.CriticalSound; -import FcmOptions = messaging.FcmOptions; -import LightSettings = messaging.LightSettings; -import Message = messaging.Message; -import Notification = messaging.Notification; -import WebpushConfig = messaging.WebpushConfig; +import { + AndroidConfig, AndroidFcmOptions, AndroidNotification, ApsAlert, ApnsConfig, + ApnsFcmOptions, ApnsPayload, Aps, CriticalSound, FcmOptions, LightSettings, Message, + Notification, WebpushConfig, +} from './messaging-api'; // Keys which are not allowed in the messaging data payload object. export const BLACKLISTED_DATA_PAYLOAD_KEYS = ['from']; diff --git a/src/messaging/messaging.ts b/src/messaging/messaging.ts index 79da21221a..0c37ee30c5 100644 --- a/src/messaging/messaging.ts +++ b/src/messaging/messaging.ts @@ -15,31 +15,32 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app'; import { deepCopy, deepExtend } from '../utils/deep-copy'; import { SubRequest } from './batch-request-internal'; -import { validateMessage, BLACKLISTED_DATA_PAYLOAD_KEYS, BLACKLISTED_OPTIONS_KEYS } from './messaging-internal'; -import { messaging } from './index'; -import { FirebaseMessagingRequestHandler } from './messaging-api-request-internal'; import { ErrorInfo, MessagingClientErrorCode, FirebaseMessagingError } from '../utils/error'; import * as utils from '../utils'; import * as validator from '../utils/validator'; +import { validateMessage, BLACKLISTED_DATA_PAYLOAD_KEYS, BLACKLISTED_OPTIONS_KEYS } from './messaging-internal'; +import { FirebaseMessagingRequestHandler } from './messaging-api-request-internal'; + +import { + BatchResponse, + Message, + MessagingTopicManagementResponse, + MulticastMessage, + + // Legacy API types + MessagingDevicesResponse, + MessagingDeviceGroupResponse, + MessagingPayload, + MessagingOptions, + MessagingTopicResponse, + MessagingConditionResponse, + DataMessagePayload, + NotificationMessagePayload, +} from './messaging-api'; -import MessagingInterface = messaging.Messaging; -import Message = messaging.Message; -import BatchResponse = messaging.BatchResponse; -import MulticastMessage = messaging.MulticastMessage; -import MessagingTopicManagementResponse = messaging.MessagingTopicManagementResponse; - -// Legacy API types -import MessagingDevicesResponse = messaging.MessagingDevicesResponse; -import MessagingDeviceGroupResponse = messaging.MessagingDeviceGroupResponse; -import MessagingPayload = messaging.MessagingPayload; -import MessagingOptions = messaging.MessagingOptions; -import MessagingTopicResponse = messaging.MessagingTopicResponse; -import MessagingConditionResponse = messaging.MessagingConditionResponse; -import DataMessagePayload = messaging.DataMessagePayload; -import NotificationMessagePayload = messaging.NotificationMessagePayload; /* eslint-disable @typescript-eslint/camelcase */ @@ -188,10 +189,10 @@ function mapRawResponseToTopicManagementResponse(response: object): MessagingTop /** * Messaging service bound to the provided app. */ -export class Messaging implements MessagingInterface { +export class Messaging { private urlPath: string; - private readonly appInternal: FirebaseApp; + private readonly appInternal: App; private readonly messagingRequestHandler: FirebaseMessagingRequestHandler; /** @@ -207,7 +208,7 @@ export class Messaging implements MessagingInterface { * * @return The `Messaging` service for the current app. */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseMessagingError( MessagingClientErrorCode.INVALID_ARGUMENT, @@ -222,9 +223,9 @@ export class Messaging implements MessagingInterface { /** * Returns the app associated with this Messaging instance. * - * @return {FirebaseApp} The app associated with this Messaging instance. + * @return The app associated with this Messaging instance. */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 07c8e3435b..d5107b9275 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -46,6 +46,7 @@ import './database/database.spec'; import './database/index.spec'; // Messaging +import './messaging/index.spec'; import './messaging/messaging.spec'; import './messaging/batch-requests.spec'; diff --git a/test/unit/messaging/index.spec.ts b/test/unit/messaging/index.spec.ts new file mode 100644 index 0000000000..56374ff1a6 --- /dev/null +++ b/test/unit/messaging/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getMessaging, Messaging } from '../../../src/messaging/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Messaging', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID for Messaging. Initialize the SDK ' + + 'with service account credentials or set project ID as an app option. Alternatively set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getMessaging()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getMessaging(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const messaging = getMessaging(mockCredentialApp); + return messaging.send({ topic: 'test' }) + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getMessaging(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const fcm1: Messaging = getMessaging(mockApp); + const fcm2: Messaging = getMessaging(mockApp); + expect(fcm1).to.equal(fcm2); + }); + }); +}); diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index c6c1e69fc4..b322030a18 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -28,7 +28,11 @@ import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { messaging } from '../../../src/messaging/index'; +import { + Message, MessagingOptions, MessagingPayload, MessagingDevicesResponse, + MessagingDeviceGroupResponse, MessagingTopicManagementResponse, BatchResponse, + SendResponse, MulticastMessage, +} from '../../../src/messaging/index'; import { Messaging } from '../../../src/messaging/messaging'; import { BLACKLISTED_OPTIONS_KEYS, BLACKLISTED_DATA_PAYLOAD_KEYS } from '../../../src/messaging/messaging-internal'; import { HttpClient } from '../../../src/utils/api-request'; @@ -40,16 +44,6 @@ chai.use(chaiAsPromised); const expect = chai.expect; -import Message = messaging.Message; -import MessagingOptions = messaging.MessagingOptions; -import MessagingPayload = messaging.MessagingPayload; -import MessagingDevicesResponse = messaging.MessagingDevicesResponse; -import MessagingDeviceGroupResponse = messaging.MessagingDeviceGroupResponse -import MessagingTopicManagementResponse = messaging.MessagingTopicManagementResponse; -import BatchResponse = messaging.BatchResponse; -import SendResponse = messaging.SendResponse; -import MulticastMessage = messaging.MulticastMessage; - // FCM endpoints const FCM_SEND_HOST = 'fcm.googleapis.com'; const FCM_SEND_PATH = '/fcm/send'; From 9c8906587078862bceae26f697adb469266d7d78 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Fri, 5 Feb 2021 12:10:34 -0800 Subject: [PATCH 12/41] feat: Exposed Rules APIs from firebase-admin/security-rules entry point (#1156) --- etc/firebase-admin.api.md | 55 ++--- etc/firebase-admin.security-rules.api.md | 83 +++++++ generate-reports.js | 1 + gulpfile.js | 1 + src/app/firebase-app.ts | 9 +- src/security-rules/index.ts | 221 +++--------------- .../security-rules-api-client-internal.ts | 5 +- src/security-rules/security-rules.ts | 53 +++-- test/unit/index.spec.ts | 1 + test/unit/security-rules/index.spec.ts | 75 ++++++ .../security-rules/security-rules.spec.ts | 2 +- 11 files changed, 263 insertions(+), 243 deletions(-) create mode 100644 etc/firebase-admin.security-rules.api.md create mode 100644 test/unit/security-rules/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 97fffbd50f..be4a99b44b 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -736,43 +736,30 @@ export namespace remoteConfig { export const SDK_VERSION: string; // @public -export function securityRules(app?: app.App): securityRules.SecurityRules; +export function securityRules(app?: App): securityRules.SecurityRules; // @public (undocumented) export namespace securityRules { - export interface Ruleset extends RulesetMetadata { - // (undocumented) - readonly source: RulesFile[]; - } - export interface RulesetMetadata { - readonly createTime: string; - readonly name: string; - } - export interface RulesetMetadataList { - readonly nextPageToken?: string; - readonly rulesets: RulesetMetadata[]; - } - export interface RulesFile { - // (undocumented) - readonly content: string; - // (undocumented) - readonly name: string; - } - export interface SecurityRules { - // (undocumented) - app: app.App; - createRuleset(file: RulesFile): Promise; - createRulesFileFromSource(name: string, source: string | Buffer): RulesFile; - deleteRuleset(name: string): Promise; - getFirestoreRuleset(): Promise; - getRuleset(name: string): Promise; - getStorageRuleset(bucket?: string): Promise; - listRulesetMetadata(pageSize?: number, nextPageToken?: string): Promise; - releaseFirestoreRuleset(ruleset: string | RulesetMetadata): Promise; - releaseFirestoreRulesetFromSource(source: string | Buffer): Promise; - releaseStorageRuleset(ruleset: string | RulesetMetadata, bucket?: string): Promise; - releaseStorageRulesetFromSource(source: string | Buffer, bucket?: string): Promise; - } + // Warning: (ae-forgotten-export) The symbol "Ruleset" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type Ruleset = Ruleset; + // Warning: (ae-forgotten-export) The symbol "RulesetMetadata" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type RulesetMetadata = RulesetMetadata; + // Warning: (ae-forgotten-export) The symbol "RulesetMetadataList" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type RulesetMetadataList = RulesetMetadataList; + // Warning: (ae-forgotten-export) The symbol "RulesFile" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type RulesFile = RulesFile; + // Warning: (ae-forgotten-export) The symbol "SecurityRules" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type SecurityRules = SecurityRules; } // @public (undocumented) diff --git a/etc/firebase-admin.security-rules.api.md b/etc/firebase-admin.security-rules.api.md new file mode 100644 index 0000000000..81310c26c3 --- /dev/null +++ b/etc/firebase-admin.security-rules.api.md @@ -0,0 +1,83 @@ +## API Report File for "firebase-admin.security-rules" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function getSecurityRules(app?: App): SecurityRules; + +// @public +export class Ruleset { + // (undocumented) + readonly createTime: string; + // (undocumented) + readonly name: string; + // (undocumented) + readonly source: RulesFile[]; +} + +// @public +export interface RulesetMetadata { + readonly createTime: string; + readonly name: string; +} + +// @public (undocumented) +export class RulesetMetadataList { + // (undocumented) + readonly nextPageToken?: string; + // (undocumented) + readonly rulesets: RulesetMetadata[]; +} + +// @public +export interface RulesFile { + // (undocumented) + readonly content: string; + // (undocumented) + readonly name: string; +} + +// @public +export class SecurityRules { + // (undocumented) + readonly app: App; + createRuleset(file: RulesFile): Promise; + createRulesFileFromSource(name: string, source: string | Buffer): RulesFile; + deleteRuleset(name: string): Promise; + getFirestoreRuleset(): Promise; + getRuleset(name: string): Promise; + getStorageRuleset(bucket?: string): Promise; + listRulesetMetadata(pageSize?: number, nextPageToken?: string): Promise; + releaseFirestoreRuleset(ruleset: string | RulesetMetadata): Promise; + releaseFirestoreRulesetFromSource(source: string | Buffer): Promise; + releaseStorageRuleset(ruleset: string | RulesetMetadata, bucket?: string): Promise; + releaseStorageRulesetFromSource(source: string | Buffer, bucket?: string): Promise; +} + +// @public +export function securityRules(app?: App): securityRules.SecurityRules; + +// @public (undocumented) +export namespace securityRules { + // (undocumented) + export type Ruleset = Ruleset; + // (undocumented) + export type RulesetMetadata = RulesetMetadata; + // (undocumented) + export type RulesetMetadataList = RulesetMetadataList; + // (undocumented) + export type RulesFile = RulesFile; + // (undocumented) + export type SecurityRules = SecurityRules; +} + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/generate-reports.js b/generate-reports.js index 86255822d7..3184a3845f 100644 --- a/generate-reports.js +++ b/generate-reports.js @@ -40,6 +40,7 @@ const entryPoints = { 'firebase-admin/firestore': './lib/firestore/index.d.ts', 'firebase-admin/instance-id': './lib/instance-id/index.d.ts', 'firebase-admin/messaging': './lib/messaging/index.d.ts', + 'firebase-admin/security-rules': './lib/security-rules/index.d.ts', 'firebase-admin/remote-config': './lib/remote-config/index.d.ts', }; diff --git a/gulpfile.js b/gulpfile.js index a2268bc070..1f1bff4f6c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -93,6 +93,7 @@ gulp.task('compile', function() { 'lib/firestore/*.d.ts', 'lib/instance-id/*.d.ts', 'lib/messaging/*.d.ts', + 'lib/security-rules/*.d.ts', 'lib/remote-config/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 0dbe4c04c6..005061de09 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -31,7 +31,7 @@ import { Database } from '../database/index'; import { Firestore } from '../firestore/index'; import { InstanceId } from '../instance-id/index'; import { ProjectManagement } from '../project-management/project-management'; -import { SecurityRules } from '../security-rules/security-rules'; +import { SecurityRules } from '../security-rules/index'; import { RemoteConfig } from '../remote-config/index'; /** @@ -356,11 +356,8 @@ export class FirebaseApp implements app.App { * @return The SecurityRules service instance of this app. */ public securityRules(): SecurityRules { - return this.ensureService_('security-rules', () => { - const securityRulesService: typeof SecurityRules = - require('../security-rules/security-rules').SecurityRules; - return new securityRulesService(this); - }); + const fn = require('../security-rules/index').getSecurityRules; + return fn(this); } /** diff --git a/src/security-rules/index.ts b/src/security-rules/index.ts index 2871f7873b..0af1ef8feb 100644 --- a/src/security-rules/index.ts +++ b/src/security-rules/index.ts @@ -14,7 +14,34 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { SecurityRules } from './security-rules'; + +export { + RulesFile, + Ruleset, + RulesetMetadata, + RulesetMetadataList, + SecurityRules, +} from './security-rules'; + +import { + RulesFile as TRulesFile, + Ruleset as TRuleset, + RulesetMetadata as TRulesetMetadata, + RulesetMetadataList as TRulesetMetadataList, + SecurityRules as TSecurityRules, +} from './security-rules'; + +export function getSecurityRules(app?: App): SecurityRules { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('securityRules', (app) => new SecurityRules(app)); +} /** * Gets the {@link securityRules.SecurityRules @@ -44,193 +71,13 @@ import { app } from '../firebase-namespace-api'; * @return The default `SecurityRules` service if no app is provided, or the * `SecurityRules` service associated with the provided app. */ -export declare function securityRules(app?: app.App): securityRules.SecurityRules; +export declare function securityRules(app?: App): securityRules.SecurityRules; /* eslint-disable @typescript-eslint/no-namespace */ export namespace securityRules { - /** - * A source file containing some Firebase security rules. The content includes raw - * source code including text formatting, indentation and comments. Use the - * [`securityRules.createRulesFileFromSource()`](securityRules.SecurityRules#createRulesFileFromSource) - * method to create new instances of this type. - */ - export interface RulesFile { - readonly name: string; - readonly content: string; - } - - /** - * Required metadata associated with a ruleset. - */ - export interface RulesetMetadata { - /** - * Name of the `Ruleset` as a short string. This can be directly passed into APIs - * like {@link securityRules.SecurityRules.getRuleset `securityRules.getRuleset()`} - * and {@link securityRules.SecurityRules.deleteRuleset `securityRules.deleteRuleset()`}. - */ - readonly name: string; - /** - * Creation time of the `Ruleset` as a UTC timestamp string. - */ - readonly createTime: string; - } - - /** - * A page of ruleset metadata. - */ - export interface RulesetMetadataList { - /** - * A batch of ruleset metadata. - */ - readonly rulesets: RulesetMetadata[]; - /** - * The next page token if available. This is needed to retrieve the next batch. - */ - readonly nextPageToken?: string; - } - - /** - * A set of Firebase security rules. - */ - export interface Ruleset extends RulesetMetadata { - readonly source: RulesFile[]; - } - - /** - * The Firebase `SecurityRules` service interface. - */ - export interface SecurityRules { - app: app.App; - - /** - * Creates a {@link securityRules.RulesFile `RuleFile`} with the given name - * and source. Throws an error if any of the arguments are invalid. This is a local - * operation, and does not involve any network API calls. - * - * @example - * ```javascript - * const source = '// Some rules source'; - * const rulesFile = admin.securityRules().createRulesFileFromSource( - * 'firestore.rules', source); - * ``` - * - * @param name Name to assign to the rules file. This is usually a short file name that - * helps identify the file in a ruleset. - * @param source Contents of the rules file. - * @return A new rules file instance. - */ - createRulesFileFromSource(name: string, source: string | Buffer): RulesFile; - - /** - * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given - * {@link securityRules.RulesFile `RuleFile`}. - * - * @param file Rules file to include in the new `Ruleset`. - * @returns A promise that fulfills with the newly created `Ruleset`. - */ - createRuleset(file: RulesFile): Promise; - - /** - * Gets the {@link securityRules.Ruleset `Ruleset`} identified by the given - * name. The input name should be the short name string without the project ID - * prefix. For example, to retrieve the `projects/project-id/rulesets/my-ruleset`, - * pass the short name "my-ruleset". Rejects with a `not-found` error if the - * specified `Ruleset` cannot be found. - * - * @param name Name of the `Ruleset` to retrieve. - * @return A promise that fulfills with the specified `Ruleset`. - */ - getRuleset(name: string): Promise; - - /** - * Deletes the {@link securityRules.Ruleset `Ruleset`} identified by the given - * name. The input name should be the short name string without the project ID - * prefix. For example, to delete the `projects/project-id/rulesets/my-ruleset`, - * pass the short name "my-ruleset". Rejects with a `not-found` error if the - * specified `Ruleset` cannot be found. - * - * @param name Name of the `Ruleset` to delete. - * @return A promise that fulfills when the `Ruleset` is deleted. - */ - deleteRuleset(name: string): Promise; - - /** - * Retrieves a page of ruleset metadata. - * - * @param pageSize The page size, 100 if undefined. This is also the maximum allowed - * limit. - * @param nextPageToken The next page token. If not specified, returns rulesets - * starting without any offset. - * @return A promise that fulfills with a page of rulesets. - */ - listRulesetMetadata( - pageSize?: number, nextPageToken?: string): Promise; - - /** - * Gets the {@link securityRules.Ruleset `Ruleset`} currently applied to - * Cloud Firestore. Rejects with a `not-found` error if no ruleset is applied - * on Firestore. - * - * @return A promise that fulfills with the Firestore ruleset. - */ - getFirestoreRuleset(): Promise; - - /** - * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given - * source, and applies it to Cloud Firestore. - * - * @param source Rules source to apply. - * @return A promise that fulfills when the ruleset is created and released. - */ - releaseFirestoreRulesetFromSource(source: string | Buffer): Promise; - - /** - * Applies the specified {@link securityRules.Ruleset `Ruleset`} ruleset - * to Cloud Firestore. - * - * @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object - * containing the name. - * @return A promise that fulfills when the ruleset is released. - */ - releaseFirestoreRuleset(ruleset: string | RulesetMetadata): Promise; - - /** - * Gets the {@link securityRules.Ruleset `Ruleset`} currently applied to a - * Cloud Storage bucket. Rejects with a `not-found` error if no ruleset is applied - * on the bucket. - * - * @param bucket Optional name of the Cloud Storage bucket to be retrieved. If not - * specified, retrieves the ruleset applied on the default bucket configured via - * `AppOptions`. - * @return A promise that fulfills with the Cloud Storage ruleset. - */ - getStorageRuleset(bucket?: string): Promise; - - /** - * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given - * source, and applies it to a Cloud Storage bucket. - * - * @param source Rules source to apply. - * @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If - * not specified, applies the ruleset on the default bucket configured via - * {@link AppOptions `AppOptions`}. - * @return A promise that fulfills when the ruleset is created and released. - */ - releaseStorageRulesetFromSource( - source: string | Buffer, bucket?: string): Promise; - - /** - * Applies the specified {@link securityRules.Ruleset `Ruleset`} ruleset - * to a Cloud Storage bucket. - * - * @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object - * containing the name. - * @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If - * not specified, applies the ruleset on the default bucket configured via - * {@link AppOptions `AppOptions`}. - * @return A promise that fulfills when the ruleset is released. - */ - releaseStorageRuleset( - ruleset: string | RulesetMetadata, bucket?: string): Promise; - } + export type RulesFile = TRulesFile; + export type Ruleset = TRuleset; + export type RulesetMetadata = TRulesetMetadata; + export type RulesetMetadataList = TRulesetMetadataList; + export type SecurityRules = TSecurityRules; } diff --git a/src/security-rules/security-rules-api-client-internal.ts b/src/security-rules/security-rules-api-client-internal.ts index 1719e64316..73369f0918 100644 --- a/src/security-rules/security-rules-api-client-internal.ts +++ b/src/security-rules/security-rules-api-client-internal.ts @@ -20,6 +20,7 @@ import { FirebaseSecurityRulesError, SecurityRulesErrorCode } from './security-r import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app'; const RULES_V1_API = 'https://firebaserules.googleapis.com/v1'; const FIREBASE_VERSION_HEADER = { @@ -59,7 +60,7 @@ export class SecurityRulesApiClient { private readonly httpClient: HttpClient; private projectIdPrefix?: string; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseSecurityRulesError( 'invalid-argument', @@ -67,7 +68,7 @@ export class SecurityRulesApiClient { + 'instance.'); } - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } public getRuleset(name: string): Promise { diff --git a/src/security-rules/security-rules.ts b/src/security-rules/security-rules.ts index 9b624e03d9..bf18627a08 100644 --- a/src/security-rules/security-rules.ts +++ b/src/security-rules/security-rules.ts @@ -14,25 +14,48 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app'; import * as validator from '../utils/validator'; import { SecurityRulesApiClient, RulesetResponse, RulesetContent, ListRulesetsResponse, } from './security-rules-api-client-internal'; import { FirebaseSecurityRulesError } from './security-rules-internal'; -import { securityRules } from './index'; -import RulesFile = securityRules.RulesFile; -import RulesetMetadata = securityRules.RulesetMetadata; -import RulesetMetadataList = securityRules.RulesetMetadataList; -import RulesetInterface = securityRules.Ruleset; -import SecurityRulesInterface = securityRules.SecurityRules; +/** + * A source file containing some Firebase security rules. The content includes raw + * source code including text formatting, indentation and comments. Use the + * [`securityRules.createRulesFileFromSource()`](securityRules.SecurityRules#createRulesFileFromSource) + * method to create new instances of this type. + */ +export interface RulesFile { + readonly name: string; + readonly content: string; +} -class RulesetMetadataListImpl implements RulesetMetadataList { +/** + * Required metadata associated with a ruleset. + */ +export interface RulesetMetadata { + /** + * Name of the `Ruleset` as a short string. This can be directly passed into APIs + * like {@link securityRules.SecurityRules.getRuleset `securityRules.getRuleset()`} + * and {@link securityRules.SecurityRules.deleteRuleset `securityRules.deleteRuleset()`}. + */ + readonly name: string; + /** + * Creation time of the `Ruleset` as a UTC timestamp string. + */ + readonly createTime: string; +} + +export class RulesetMetadataList { public readonly rulesets: RulesetMetadata[]; public readonly nextPageToken?: string; + /** + * @internal + */ constructor(response: ListRulesetsResponse) { if (!validator.isNonNullObject(response) || !validator.isArray(response.rulesets)) { throw new FirebaseSecurityRulesError( @@ -56,12 +79,15 @@ class RulesetMetadataListImpl implements RulesetMetadataList { /** * Represents a set of Firebase security rules. */ -export class Ruleset implements RulesetInterface { +export class Ruleset { public readonly name: string; public readonly createTime: string; public readonly source: RulesFile[]; + /** + * @internal + */ constructor(ruleset: RulesetResponse) { if (!validator.isNonNullObject(ruleset) || !validator.isNonEmptyString(ruleset.name) || @@ -84,7 +110,7 @@ export class Ruleset implements RulesetInterface { * Do not call this constructor directly. Instead, use * [`admin.securityRules()`](securityRules#securityRules). */ -export class SecurityRules implements SecurityRulesInterface { +export class SecurityRules { private static readonly CLOUD_FIRESTORE = 'cloud.firestore'; private static readonly FIREBASE_STORAGE = 'firebase.storage'; @@ -92,10 +118,11 @@ export class SecurityRules implements SecurityRulesInterface { private readonly client: SecurityRulesApiClient; /** - * @param {object} app The app for this SecurityRules service. + * @param app The app for this SecurityRules service. * @constructor + * @internal */ - constructor(readonly app: FirebaseApp) { + constructor(readonly app: App) { this.client = new SecurityRulesApiClient(app); } @@ -302,7 +329,7 @@ export class SecurityRules implements SecurityRulesInterface { public listRulesetMetadata(pageSize = 100, nextPageToken?: string): Promise { return this.client.listRulesets(pageSize, nextPageToken) .then((response) => { - return new RulesetMetadataListImpl(response); + return new RulesetMetadataList(response); }); } diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index d5107b9275..2a07381b09 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -73,6 +73,7 @@ import './project-management/android-app.spec'; import './project-management/ios-app.spec'; // SecurityRules +import './security-rules/index.spec'; import './security-rules/security-rules.spec'; import './security-rules/security-rules-api-client.spec'; diff --git a/test/unit/security-rules/index.spec.ts b/test/unit/security-rules/index.spec.ts new file mode 100644 index 0000000000..ad6a0b05de --- /dev/null +++ b/test/unit/security-rules/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getSecurityRules, SecurityRules } from '../../../src/security-rules/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('SecurityRules', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID. Initialize the SDK ' + + 'with service account credentials, or set project ID as an app option. Alternatively, set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getSecurityRules()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getSecurityRules(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const rules = getSecurityRules(mockCredentialApp); + return rules.getFirestoreRuleset() + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getSecurityRules(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const rules1: SecurityRules = getSecurityRules(mockApp); + const rules2: SecurityRules = getSecurityRules(mockApp); + expect(rules1).to.equal(rules2); + }); + }); +}); diff --git a/test/unit/security-rules/security-rules.spec.ts b/test/unit/security-rules/security-rules.spec.ts index 426d46c6ae..70611d7db5 100644 --- a/test/unit/security-rules/security-rules.spec.ts +++ b/test/unit/security-rules/security-rules.spec.ts @@ -19,7 +19,7 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { SecurityRules } from '../../../src/security-rules/security-rules'; +import { SecurityRules } from '../../../src/security-rules/index'; import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; import { SecurityRulesApiClient, RulesetContent } from '../../../src/security-rules/security-rules-api-client-internal'; From e758b1501f8f99abafe6153e8d0d628218d9f28d Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Mon, 8 Feb 2021 13:02:47 -0800 Subject: [PATCH 13/41] feat: Exposed firebase-admin/project-management entry point (#1157) --- etc/firebase-admin.api.md | 91 ++-- etc/firebase-admin.project-management.api.md | 115 +++++ generate-reports.js | 1 + gulpfile.js | 1 + src/app/firebase-app.ts | 9 +- src/project-management/android-app.ts | 34 +- src/project-management/app-metadata.ts | 92 ++++ src/project-management/index.ts | 396 ++---------------- src/project-management/ios-app.ts | 26 +- ...project-management-api-request-internal.ts | 10 +- src/project-management/project-management.ts | 15 +- test/unit/index.spec.ts | 1 + .../project-management/android-app.spec.ts | 8 +- test/unit/project-management/index.spec.ts | 75 ++++ test/unit/project-management/ios-app.spec.ts | 6 +- .../project-management-api-request.spec.ts | 5 +- .../project-management.spec.ts | 10 +- 17 files changed, 430 insertions(+), 465 deletions(-) create mode 100644 etc/firebase-admin.project-management.api.md create mode 100644 src/project-management/app-metadata.ts create mode 100644 test/unit/project-management/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index be4a99b44b..3017438249 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -607,67 +607,42 @@ export namespace messaging { } // @public -export function projectManagement(app?: app.App): projectManagement.ProjectManagement; +export function projectManagement(app?: App): projectManagement.ProjectManagement; // @public (undocumented) export namespace projectManagement { - export interface AndroidApp { - addShaCertificate(certificateToAdd: ShaCertificate): Promise; - // (undocumented) - appId: string; - deleteShaCertificate(certificateToRemove: ShaCertificate): Promise; - getConfig(): Promise; - getMetadata(): Promise; - getShaCertificates(): Promise; - setDisplayName(newDisplayName: string): Promise; - } - export interface AndroidAppMetadata extends AppMetadata { - packageName: string; - // (undocumented) - platform: AppPlatform.ANDROID; - } - export interface AppMetadata { - appId: string; - displayName?: string; - platform: AppPlatform; - projectId: string; - resourceName: string; - } - export enum AppPlatform { - ANDROID = "ANDROID", - IOS = "IOS", - PLATFORM_UNKNOWN = "PLATFORM_UNKNOWN" - } - export interface IosApp { - // (undocumented) - appId: string; - getConfig(): Promise; - getMetadata(): Promise; - setDisplayName(newDisplayName: string): Promise; - } - export interface IosAppMetadata extends AppMetadata { - bundleId: string; - // (undocumented) - platform: AppPlatform.IOS; - } - export interface ProjectManagement { - androidApp(appId: string): AndroidApp; - // (undocumented) - app: app.App; - createAndroidApp(packageName: string, displayName?: string): Promise; - createIosApp(bundleId: string, displayName?: string): Promise; - iosApp(appId: string): IosApp; - listAndroidApps(): Promise; - listAppMetadata(): Promise; - listIosApps(): Promise; - setDisplayName(newDisplayName: string): Promise; - shaCertificate(shaHash: string): ShaCertificate; - } - export interface ShaCertificate { - certType: ('sha1' | 'sha256'); - resourceName?: string; - shaHash: string; - } + // Warning: (ae-forgotten-export) The symbol "AndroidApp" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type AndroidApp = AndroidApp; + // Warning: (ae-forgotten-export) The symbol "AndroidAppMetadata" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type AndroidAppMetadata = AndroidAppMetadata; + // Warning: (ae-forgotten-export) The symbol "AppMetadata" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type AppMetadata = AppMetadata; + // Warning: (ae-forgotten-export) The symbol "AppPlatform" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type AppPlatform = AppPlatform; + // Warning: (ae-forgotten-export) The symbol "IosApp" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type IosApp = IosApp; + // Warning: (ae-forgotten-export) The symbol "IosAppMetadata" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type IosAppMetadata = IosAppMetadata; + // Warning: (ae-forgotten-export) The symbol "ProjectManagement" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type ProjectManagement = ProjectManagement; + // Warning: (ae-forgotten-export) The symbol "ShaCertificate" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type ShaCertificate = ShaCertificate; } // @public (undocumented) diff --git a/etc/firebase-admin.project-management.api.md b/etc/firebase-admin.project-management.api.md new file mode 100644 index 0000000000..f8d3ebcf34 --- /dev/null +++ b/etc/firebase-admin.project-management.api.md @@ -0,0 +1,115 @@ +## API Report File for "firebase-admin.project-management" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; + +// @public (undocumented) +export class AndroidApp { + addShaCertificate(certificateToAdd: ShaCertificate): Promise; + // (undocumented) + readonly appId: string; + deleteShaCertificate(certificateToDelete: ShaCertificate): Promise; + getConfig(): Promise; + getMetadata(): Promise; + getShaCertificates(): Promise; + setDisplayName(newDisplayName: string): Promise; +} + +// @public +export interface AndroidAppMetadata extends AppMetadata { + packageName: string; + // (undocumented) + platform: AppPlatform.ANDROID; +} + +// @public +export interface AppMetadata { + appId: string; + displayName?: string; + platform: AppPlatform; + projectId: string; + resourceName: string; +} + +// @public +export enum AppPlatform { + ANDROID = "ANDROID", + IOS = "IOS", + PLATFORM_UNKNOWN = "PLATFORM_UNKNOWN" +} + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function getProjectManagement(app?: App): ProjectManagement; + +// @public (undocumented) +export class IosApp { + // (undocumented) + readonly appId: string; + getConfig(): Promise; + getMetadata(): Promise; + setDisplayName(newDisplayName: string): Promise; +} + +// @public +export interface IosAppMetadata extends AppMetadata { + bundleId: string; + // (undocumented) + platform: AppPlatform.IOS; +} + +// @public +export class ProjectManagement { + androidApp(appId: string): AndroidApp; + // (undocumented) + readonly app: App; + createAndroidApp(packageName: string, displayName?: string): Promise; + createIosApp(bundleId: string, displayName?: string): Promise; + iosApp(appId: string): IosApp; + listAndroidApps(): Promise; + listAppMetadata(): Promise; + listIosApps(): Promise; + setDisplayName(newDisplayName: string): Promise; + shaCertificate(shaHash: string): ShaCertificate; + } + +// @public +export function projectManagement(app?: App): projectManagement.ProjectManagement; + +// @public (undocumented) +export namespace projectManagement { + // (undocumented) + export type AndroidApp = AndroidApp; + // (undocumented) + export type AndroidAppMetadata = AndroidAppMetadata; + // (undocumented) + export type AppMetadata = AppMetadata; + // (undocumented) + export type AppPlatform = AppPlatform; + // (undocumented) + export type IosApp = IosApp; + // (undocumented) + export type IosAppMetadata = IosAppMetadata; + // (undocumented) + export type ProjectManagement = ProjectManagement; + // (undocumented) + export type ShaCertificate = ShaCertificate; +} + +// @public +export class ShaCertificate { + readonly certType: ('sha1' | 'sha256'); + // (undocumented) + readonly resourceName?: string | undefined; + // (undocumented) + readonly shaHash: string; +} + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/generate-reports.js b/generate-reports.js index 3184a3845f..aaa4094818 100644 --- a/generate-reports.js +++ b/generate-reports.js @@ -40,6 +40,7 @@ const entryPoints = { 'firebase-admin/firestore': './lib/firestore/index.d.ts', 'firebase-admin/instance-id': './lib/instance-id/index.d.ts', 'firebase-admin/messaging': './lib/messaging/index.d.ts', + 'firebase-admin/project-management': './lib/project-management/index.d.ts', 'firebase-admin/security-rules': './lib/security-rules/index.d.ts', 'firebase-admin/remote-config': './lib/remote-config/index.d.ts', }; diff --git a/gulpfile.js b/gulpfile.js index 1f1bff4f6c..413c530e79 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -93,6 +93,7 @@ gulp.task('compile', function() { 'lib/firestore/*.d.ts', 'lib/instance-id/*.d.ts', 'lib/messaging/*.d.ts', + 'lib/project-management/*.d.ts', 'lib/security-rules/*.d.ts', 'lib/remote-config/*.d.ts', '!lib/utils/index.d.ts', diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 005061de09..95ca40c2bc 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -30,7 +30,7 @@ import { Storage } from '../storage/storage'; import { Database } from '../database/index'; import { Firestore } from '../firestore/index'; import { InstanceId } from '../instance-id/index'; -import { ProjectManagement } from '../project-management/project-management'; +import { ProjectManagement } from '../project-management/index'; import { SecurityRules } from '../security-rules/index'; import { RemoteConfig } from '../remote-config/index'; @@ -343,11 +343,8 @@ export class FirebaseApp implements app.App { * @return The ProjectManagement service instance of this app. */ public projectManagement(): ProjectManagement { - return this.ensureService_('project-management', () => { - const projectManagementService: typeof ProjectManagement = - require('../project-management/project-management').ProjectManagement; - return new projectManagementService(this); - }); + const fn = require('../project-management/index').getProjectManagement; + return fn(this); } /** diff --git a/src/project-management/android-app.ts b/src/project-management/android-app.ts index 8afba57094..808c25c7e4 100644 --- a/src/project-management/android-app.ts +++ b/src/project-management/android-app.ts @@ -17,16 +17,34 @@ import { FirebaseProjectManagementError } from '../utils/error'; import * as validator from '../utils/validator'; import { ProjectManagementRequestHandler, assertServerResponse } from './project-management-api-request-internal'; -import { projectManagement } from './index'; +import { AppMetadata, AppPlatform } from './app-metadata'; -import AndroidAppInterface = projectManagement.AndroidApp; -import AndroidAppMetadata = projectManagement.AndroidAppMetadata; -import AppPlatform = projectManagement.AppPlatform; -import ShaCertificateInterface = projectManagement.ShaCertificate; -export class AndroidApp implements AndroidAppInterface { +/** + * Metadata about a Firebase Android App. + */ +export interface AndroidAppMetadata extends AppMetadata { + + platform: AppPlatform.ANDROID; + + /** + * The canonical package name of the Android App, as would appear in the Google Play Developer + * Console. + * + * @example + * ```javascript + * var packageName = androidAppMetadata.packageName; + * ``` + */ + packageName: string; +} + +export class AndroidApp { private readonly resourceName: string; + /** + * @internal + */ constructor( public readonly appId: string, private readonly requestHandler: ProjectManagementRequestHandler) { @@ -184,7 +202,7 @@ export class AndroidApp implements AndroidAppInterface { * Do not call this constructor directly. Instead, use * [`projectManagement.shaCertificate()`](projectManagement.ProjectManagement#shaCertificate). */ -export class ShaCertificate implements ShaCertificateInterface { +export class ShaCertificate { /** * The SHA certificate type. * @@ -210,6 +228,8 @@ export class ShaCertificate implements ShaCertificateInterface { * ```javascript * var resourceName = shaCertificate.resourceName; * ``` + * + * @internal */ constructor(public readonly shaHash: string, public readonly resourceName?: string) { if (/^[a-fA-F0-9]{40}$/.test(shaHash)) { diff --git a/src/project-management/app-metadata.ts b/src/project-management/app-metadata.ts new file mode 100644 index 0000000000..8fd69255d2 --- /dev/null +++ b/src/project-management/app-metadata.ts @@ -0,0 +1,92 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Platforms with which a Firebase App can be associated. + */ +export enum AppPlatform { + /** + * Unknown state. This is only used for distinguishing unset values. + */ + PLATFORM_UNKNOWN = 'PLATFORM_UNKNOWN', + + /** + * The Firebase App is associated with iOS. + */ + IOS = 'IOS', + + /** + * The Firebase App is associated with Android. + */ + ANDROID = 'ANDROID', +} + +/** + * Metadata about a Firebase app. + */ +export interface AppMetadata { + /** + * The globally unique, Firebase-assigned identifier of the app. + * + * @example + * ```javascript + * var appId = appMetadata.appId; + * ``` + */ + appId: string; + + /** + * The optional user-assigned display name of the app. + * + * @example + * ```javascript + * var displayName = appMetadata.displayName; + * ``` + */ + displayName?: string; + + /** + * The development platform of the app. Supporting Android and iOS app platforms. + * + * @example + * ```javascript + * var platform = AppPlatform.ANDROID; + * ``` + */ + platform: AppPlatform; + + /** + * The globally unique, user-assigned ID of the parent project for the app. + * + * @example + * ```javascript + * var projectId = appMetadata.projectId; + * ``` + */ + projectId: string; + + /** + * The fully-qualified resource name that identifies this app. + * + * This is useful when manually constructing requests for Firebase's public API. + * + * @example + * ```javascript + * var resourceName = androidAppMetadata.resourceName; + * ``` + */ + resourceName: string; +} diff --git a/src/project-management/index.ts b/src/project-management/index.ts index 2c905d6c22..26e7f7aa5e 100644 --- a/src/project-management/index.ts +++ b/src/project-management/index.ts @@ -14,7 +14,23 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { ProjectManagement } from './project-management'; + +export { AppMetadata, AppPlatform } from './app-metadata'; +export { ProjectManagement } from './project-management'; +export { AndroidApp, AndroidAppMetadata, ShaCertificate } from './android-app'; +export { IosApp, IosAppMetadata } from './ios-app'; + +export function getProjectManagement(app?: App): ProjectManagement { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('projectManagement', (app) => new ProjectManagement(app)); +} /** * Gets the {@link projectManagement.ProjectManagement @@ -44,361 +60,31 @@ import { app } from '../firebase-namespace-api'; * @return The default `ProjectManagement` service if no app is provided or the * `ProjectManagement` service associated with the provided app. */ -export declare function projectManagement(app?: app.App): projectManagement.ProjectManagement; +export declare function projectManagement(app?: App): projectManagement.ProjectManagement; + +import { + AppMetadata as TAppMetadata, + AppPlatform as TAppPlatform, +} from './app-metadata'; +import { ProjectManagement as TProjectManagement } from './project-management'; +import { + AndroidApp as TAndroidApp, + AndroidAppMetadata as TAndroidAppMetadata, + ShaCertificate as TShaCertificate, +} from './android-app'; +import { + IosApp as TIosApp, + IosAppMetadata as TIosAppMetadata, +} from './ios-app'; /* eslint-disable @typescript-eslint/no-namespace */ export namespace projectManagement { - /** - * Metadata about a Firebase Android App. - */ - export interface AndroidAppMetadata extends AppMetadata { - - platform: AppPlatform.ANDROID; - - /** - * The canonical package name of the Android App, as would appear in the Google Play Developer - * Console. - * - * @example - * ```javascript - * var packageName = androidAppMetadata.packageName; - * ``` - */ - packageName: string; - } - - /** - * Metadata about a Firebase app. - */ - export interface AppMetadata { - /** - * The globally unique, Firebase-assigned identifier of the app. - * - * @example - * ```javascript - * var appId = appMetadata.appId; - * ``` - */ - appId: string; - - /** - * The optional user-assigned display name of the app. - * - * @example - * ```javascript - * var displayName = appMetadata.displayName; - * ``` - */ - displayName?: string; - - /** - * The development platform of the app. Supporting Android and iOS app platforms. - * - * @example - * ```javascript - * var platform = AppPlatform.ANDROID; - * ``` - */ - platform: AppPlatform; - - /** - * The globally unique, user-assigned ID of the parent project for the app. - * - * @example - * ```javascript - * var projectId = appMetadata.projectId; - * ``` - */ - projectId: string; - - /** - * The fully-qualified resource name that identifies this app. - * - * This is useful when manually constructing requests for Firebase's public API. - * - * @example - * ```javascript - * var resourceName = androidAppMetadata.resourceName; - * ``` - */ - resourceName: string; - } - - /** - * Platforms with which a Firebase App can be associated. - */ - export enum AppPlatform { - /** - * Unknown state. This is only used for distinguishing unset values. - */ - PLATFORM_UNKNOWN = 'PLATFORM_UNKNOWN', - - /** - * The Firebase App is associated with iOS. - */ - IOS = 'IOS', - - /** - * The Firebase App is associated with Android. - */ - ANDROID = 'ANDROID', - } - - /** - * Metadata about a Firebase iOS App. - */ - export interface IosAppMetadata extends AppMetadata { - platform: AppPlatform.IOS; - - /** - * The canonical bundle ID of the iOS App as it would appear in the iOS App Store. - * - * @example - * ```javascript - * var bundleId = iosAppMetadata.bundleId; - *``` - */ - bundleId: string; - } - - /** - * A reference to a Firebase Android app. - * - * Do not call this constructor directly. Instead, use - * [`projectManagement.androidApp()`](projectManagement.ProjectManagement#androidApp). - */ - export interface AndroidApp { - appId: string; - - /** - * Retrieves metadata about this Android app. - * - * @return A promise that resolves to the retrieved metadata about this Android app. - */ - getMetadata(): Promise; - - /** - * Sets the optional user-assigned display name of the app. - * - * @param newDisplayName The new display name to set. - * - * @return A promise that resolves when the display name has been set. - */ - setDisplayName(newDisplayName: string): Promise; - - /** - * Gets the list of SHA certificates associated with this Android app in Firebase. - * - * @return The list of SHA-1 and SHA-256 certificates associated with this Android app in - * Firebase. - */ - getShaCertificates(): Promise; - - /** - * Adds the given SHA certificate to this Android app. - * - * @param certificateToAdd The SHA certificate to add. - * - * @return A promise that resolves when the given certificate - * has been added to the Android app. - */ - addShaCertificate(certificateToAdd: ShaCertificate): Promise; - - /** - * Deletes the specified SHA certificate from this Android app. - * - * @param certificateToDelete The SHA certificate to delete. - * - * @return A promise that resolves when the specified - * certificate has been removed from the Android app. - */ - deleteShaCertificate(certificateToRemove: ShaCertificate): Promise; - - /** - * Gets the configuration artifact associated with this app. - * - * @return A promise that resolves to the Android app's - * Firebase config file, in UTF-8 string format. This string is typically - * intended to be written to a JSON file that gets shipped with your Android - * app. - */ - getConfig(): Promise; - } - - /** - * A reference to a Firebase iOS app. - * - * Do not call this constructor directly. Instead, use - * [`projectManagement.iosApp()`](projectManagement.ProjectManagement#iosApp). - */ - export interface IosApp { - appId: string; - - /** - * Retrieves metadata about this iOS app. - * - * @return {!Promise} A promise that - * resolves to the retrieved metadata about this iOS app. - */ - getMetadata(): Promise; - - /** - * Sets the optional user-assigned display name of the app. - * - * @param newDisplayName The new display name to set. - * - * @return A promise that resolves when the display name has - * been set. - */ - setDisplayName(newDisplayName: string): Promise; - - /** - * Gets the configuration artifact associated with this app. - * - * @return A promise that resolves to the iOS app's Firebase - * config file, in UTF-8 string format. This string is typically intended to - * be written to a plist file that gets shipped with your iOS app. - */ - getConfig(): Promise; - } - - /** - * A SHA-1 or SHA-256 certificate. - * - * Do not call this constructor directly. Instead, use - * [`projectManagement.shaCertificate()`](projectManagement.ProjectManagement#shaCertificate). - */ - export interface ShaCertificate { - - /** - * The SHA certificate type. - * - * @example - * ```javascript - * var certType = shaCertificate.certType; - * ``` - */ - certType: ('sha1' | 'sha256'); - - /** - * The SHA-1 or SHA-256 hash for this certificate. - * - * @example - * ```javascript - * var shaHash = shaCertificate.shaHash; - * ``` - */ - shaHash: string; - - /** - * The fully-qualified resource name that identifies this sha-key. - * - * This is useful when manually constructing requests for Firebase's public API. - * - * @example - * ```javascript - * var resourceName = shaCertificate.resourceName; - * ``` - */ - resourceName?: string; - } - - /** - * The Firebase ProjectManagement service interface. - * - * Do not call this constructor directly. Instead, use - * [`admin.projectManagement()`](projectManagement#projectManagement). - */ - export interface ProjectManagement { - app: app.App; - - /** - * Lists up to 100 Firebase apps associated with this Firebase project. - * - * @return A promise that resolves to the metadata list of the apps. - */ - listAppMetadata(): Promise; - - /** - * Lists up to 100 Firebase Android apps associated with this Firebase project. - * - * @return The list of Android apps. - */ - listAndroidApps(): Promise; - - /** - * Lists up to 100 Firebase iOS apps associated with this Firebase project. - * - * @return The list of iOS apps. - */ - listIosApps(): Promise; - - /** - * Creates an `AndroidApp` object, referencing the specified Android app within - * this Firebase project. - * - * This method does not perform an RPC. - * - * @param appId The `appId` of the Android app to reference. - * - * @return An `AndroidApp` object that references the specified Firebase Android app. - */ - androidApp(appId: string): AndroidApp; - - /** - * Update the display name of this Firebase project. - * - * @param newDisplayName The new display name to be updated. - * - * @return A promise that resolves when the project display name has been updated. - */ - setDisplayName(newDisplayName: string): Promise; - - /** - * Creates an `iOSApp` object, referencing the specified iOS app within - * this Firebase project. - * - * This method does not perform an RPC. - * - * @param appId The `appId` of the iOS app to reference. - * - * @return An `iOSApp` object that references the specified Firebase iOS app. - */ - iosApp(appId: string): IosApp; - - /** - * Creates a `ShaCertificate` object. - * - * This method does not perform an RPC. - * - * @param shaHash The SHA-1 or SHA-256 hash for this certificate. - * - * @return A `ShaCertificate` object contains the specified SHA hash. - */ - shaCertificate(shaHash: string): ShaCertificate; - - /** - * Creates a new Firebase Android app associated with this Firebase project. - * - * @param packageName The canonical package name of the Android App, - * as would appear in the Google Play Developer Console. - * @param displayName An optional user-assigned display name for this - * new app. - * - * @return A promise that resolves to the newly created Android app. - */ - createAndroidApp( - packageName: string, displayName?: string): Promise; - - /** - * Creates a new Firebase iOS app associated with this Firebase project. - * - * @param bundleId The iOS app bundle ID to use for this new app. - * @param displayName An optional user-assigned display name for this - * new app. - * - * @return A promise that resolves to the newly created iOS app. - */ - createIosApp(bundleId: string, displayName?: string): Promise; - } + export type AppMetadata = TAppMetadata; + export type AppPlatform = TAppPlatform; + export type ProjectManagement = TProjectManagement; + export type IosApp = TIosApp; + export type IosAppMetadata = TIosAppMetadata; + export type AndroidApp = TAndroidApp; + export type AndroidAppMetadata = TAndroidAppMetadata; + export type ShaCertificate = TShaCertificate; } diff --git a/src/project-management/ios-app.ts b/src/project-management/ios-app.ts index b29af6326f..9d01ce23c6 100644 --- a/src/project-management/ios-app.ts +++ b/src/project-management/ios-app.ts @@ -17,15 +17,31 @@ import { FirebaseProjectManagementError } from '../utils/error'; import * as validator from '../utils/validator'; import { ProjectManagementRequestHandler, assertServerResponse } from './project-management-api-request-internal'; -import { projectManagement } from './index'; +import { AppMetadata, AppPlatform } from './app-metadata'; -import IosAppInterface = projectManagement.IosApp; -import IosAppMetadata = projectManagement.IosAppMetadata; -import AppPlatform = projectManagement.AppPlatform; +/** + * Metadata about a Firebase iOS App. + */ +export interface IosAppMetadata extends AppMetadata { + platform: AppPlatform.IOS; -export class IosApp implements IosAppInterface { + /** + * The canonical bundle ID of the iOS App as it would appear in the iOS App Store. + * + * @example + * ```javascript + * var bundleId = iosAppMetadata.bundleId; + *``` + */ + bundleId: string; +} + +export class IosApp { private readonly resourceName: string; + /** + * @internal + */ constructor( public readonly appId: string, private readonly requestHandler: ProjectManagementRequestHandler) { diff --git a/src/project-management/project-management-api-request-internal.ts b/src/project-management/project-management-api-request-internal.ts index a24111f424..4f5b876435 100644 --- a/src/project-management/project-management-api-request-internal.ts +++ b/src/project-management/project-management-api-request-internal.ts @@ -14,14 +14,16 @@ * limitations under the License. */ +import { App } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { AuthorizedHttpClient, HttpError, HttpMethod, HttpRequestConfig, ExponentialBackoffPoller, } from '../utils/api-request'; import { FirebaseProjectManagementError, ProjectManagementErrorCode } from '../utils/error'; +import { getSdkVersion } from '../utils/index'; import * as validator from '../utils/validator'; import { ShaCertificate } from './android-app'; -import { getSdkVersion } from '../utils/index'; + /** Project management backend host and port. */ const PROJECT_MANAGEMENT_HOST_AND_PORT = 'firebase.googleapis.com:443'; @@ -112,11 +114,11 @@ export class ProjectManagementRequestHandler { } /** - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param app The app used to fetch access tokens to sign API requests. * @constructor */ - constructor(app: FirebaseApp) { - this.httpClient = new AuthorizedHttpClient(app); + constructor(app: App) { + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } /** diff --git a/src/project-management/project-management.ts b/src/project-management/project-management.ts index 1a7ac03733..5b0d691823 100644 --- a/src/project-management/project-management.ts +++ b/src/project-management/project-management.ts @@ -14,18 +14,14 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app'; import { FirebaseProjectManagementError } from '../utils/error'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; import { AndroidApp, ShaCertificate } from './android-app'; import { IosApp } from './ios-app'; import { ProjectManagementRequestHandler, assertServerResponse } from './project-management-api-request-internal'; -import { projectManagement } from './index'; - -import AppMetadata = projectManagement.AppMetadata; -import AppPlatform = projectManagement.AppPlatform; -import ProjectManagementInterface = projectManagement.ProjectManagement; +import { AppMetadata, AppPlatform } from './app-metadata'; /** * The Firebase ProjectManagement service interface. @@ -33,16 +29,17 @@ import ProjectManagementInterface = projectManagement.ProjectManagement; * Do not call this constructor directly. Instead, use * [`admin.projectManagement()`](projectManagement#projectManagement). */ -export class ProjectManagement implements ProjectManagementInterface { +export class ProjectManagement { private readonly requestHandler: ProjectManagementRequestHandler; private projectId: string; /** - * @param {object} app The app for this ProjectManagement service. + * @param app The app for this ProjectManagement service. * @constructor + * @internal */ - constructor(readonly app: FirebaseApp) { + constructor(readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseProjectManagementError( 'invalid-argument', diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 2a07381b09..7448610fd1 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -67,6 +67,7 @@ import './instance-id/instance-id.spec'; import './instance-id/instance-id-request.spec'; // ProjectManagement +import './project-management/index.spec'; import './project-management/project-management.spec'; import './project-management/project-management-api-request.spec'; import './project-management/android-app.spec'; diff --git a/test/unit/project-management/android-app.spec.ts b/test/unit/project-management/android-app.spec.ts index 1c2d88032f..94ccacbbd3 100644 --- a/test/unit/project-management/android-app.spec.ts +++ b/test/unit/project-management/android-app.spec.ts @@ -20,17 +20,15 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import * as sinon from 'sinon'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { AndroidApp, ShaCertificate } from '../../../src/project-management/android-app'; import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; import { deepCopy } from '../../../src/utils/deep-copy'; import { FirebaseProjectManagementError } from '../../../src/utils/error'; import * as mocks from '../../resources/mocks'; -import { projectManagement } from '../../../src/project-management/index'; - -import AndroidAppMetadata = projectManagement.AndroidAppMetadata; -import AppPlatform = projectManagement.AppPlatform; +import { + AndroidApp, AndroidAppMetadata, AppPlatform, ShaCertificate, +} from '../../../src/project-management/index'; const expect = chai.expect; diff --git a/test/unit/project-management/index.spec.ts b/test/unit/project-management/index.spec.ts new file mode 100644 index 0000000000..6848494c96 --- /dev/null +++ b/test/unit/project-management/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getProjectManagement, ProjectManagement } from '../../../src/project-management/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('ProjectManagement', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID. Initialize the SDK ' + + 'with service account credentials, or set project ID as an app option. Alternatively, set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getProjectManagement()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getProjectManagement(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const client = getProjectManagement(mockCredentialApp); + return client.listAndroidApps() + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getProjectManagement(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const client1: ProjectManagement = getProjectManagement(mockApp); + const client2: ProjectManagement = getProjectManagement(mockApp); + expect(client1).to.equal(client2); + }); + }); +}); diff --git a/test/unit/project-management/ios-app.spec.ts b/test/unit/project-management/ios-app.spec.ts index 53966696d1..f250ed5f10 100644 --- a/test/unit/project-management/ios-app.spec.ts +++ b/test/unit/project-management/ios-app.spec.ts @@ -20,17 +20,13 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import * as sinon from 'sinon'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { IosApp } from '../../../src/project-management/ios-app'; import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; import { deepCopy } from '../../../src/utils/deep-copy'; import { FirebaseProjectManagementError } from '../../../src/utils/error'; import * as mocks from '../../resources/mocks'; -import { projectManagement } from '../../../src/project-management/index'; - -import IosAppMetadata = projectManagement.IosAppMetadata; -import AppPlatform = projectManagement.AppPlatform; +import { AppPlatform, IosApp, IosAppMetadata } from '../../../src/project-management/index'; const expect = chai.expect; diff --git a/test/unit/project-management/project-management-api-request.spec.ts b/test/unit/project-management/project-management-api-request.spec.ts index 0cbd7929cb..772a33ad4c 100644 --- a/test/unit/project-management/project-management-api-request.spec.ts +++ b/test/unit/project-management/project-management-api-request.spec.ts @@ -29,10 +29,7 @@ import { HttpClient } from '../../../src/utils/api-request'; import * as mocks from '../../resources/mocks'; import * as utils from '../utils'; import { getSdkVersion } from '../../../src/utils/index'; -import { ShaCertificate } from '../../../src/project-management/android-app'; -import { projectManagement } from '../../../src/project-management/index'; - -import AppPlatform = projectManagement.AppPlatform; +import { AppPlatform, ShaCertificate } from '../../../src/project-management/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/project-management/project-management.spec.ts b/test/unit/project-management/project-management.spec.ts index a755a5b283..b818bdb1c8 100644 --- a/test/unit/project-management/project-management.spec.ts +++ b/test/unit/project-management/project-management.spec.ts @@ -20,18 +20,14 @@ import * as chai from 'chai'; import * as _ from 'lodash'; import * as sinon from 'sinon'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { AndroidApp } from '../../../src/project-management/android-app'; -import { ProjectManagement } from '../../../src/project-management/project-management'; import { ProjectManagementRequestHandler } from '../../../src/project-management/project-management-api-request-internal'; import { FirebaseProjectManagementError } from '../../../src/utils/error'; import * as mocks from '../../resources/mocks'; -import { projectManagement } from '../../../src/project-management/index'; - -import AppMetadata = projectManagement.AppMetadata; -import AppPlatform = projectManagement.AppPlatform; -import IosApp = projectManagement.IosApp; +import { + AndroidApp, AppMetadata, AppPlatform, IosApp, ProjectManagement, +} from '../../../src/project-management/index'; const expect = chai.expect; From 252b64051541cdfeb963370973b6094def29a05d Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Mon, 8 Feb 2021 16:29:03 -0800 Subject: [PATCH 14/41] feat(storage): Exposed firebase-admin/storage entry point (#1159) --- etc/firebase-admin.api.md | 11 +++-- etc/firebase-admin.storage.api.md | 34 ++++++++++++++ generate-reports.js | 1 + gulpfile.js | 1 + src/app/firebase-app.ts | 8 ++-- src/storage/index.ts | 38 ++++++++-------- src/storage/storage.ts | 15 +++---- test/unit/index.spec.ts | 1 + test/unit/storage/index.spec.ts | 73 +++++++++++++++++++++++++++++++ 9 files changed, 142 insertions(+), 40 deletions(-) create mode 100644 etc/firebase-admin.storage.api.md create mode 100644 test/unit/storage/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 3017438249..ec1830df25 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -748,15 +748,14 @@ export interface ServiceAccount { } // @public -export function storage(app?: app.App): storage.Storage; +export function storage(app?: App): storage.Storage; // @public (undocumented) export namespace storage { - export interface Storage { - app: app.App; - // (undocumented) - bucket(name?: string): Bucket; - } + // Warning: (ae-forgotten-export) The symbol "Storage" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type Storage = Storage; } diff --git a/etc/firebase-admin.storage.api.md b/etc/firebase-admin.storage.api.md new file mode 100644 index 0000000000..d13887d9df --- /dev/null +++ b/etc/firebase-admin.storage.api.md @@ -0,0 +1,34 @@ +## API Report File for "firebase-admin.storage" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; +import { Bucket } from '@google-cloud/storage'; + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function getStorage(app?: App): Storage; + +// @public +export class Storage { + get app(): App; + // (undocumented) + bucket(name?: string): Bucket; + } + +// @public +export function storage(app?: App): storage.Storage; + +// @public (undocumented) +export namespace storage { + // (undocumented) + export type Storage = Storage; +} + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/generate-reports.js b/generate-reports.js index aaa4094818..8f31443727 100644 --- a/generate-reports.js +++ b/generate-reports.js @@ -42,6 +42,7 @@ const entryPoints = { 'firebase-admin/messaging': './lib/messaging/index.d.ts', 'firebase-admin/project-management': './lib/project-management/index.d.ts', 'firebase-admin/security-rules': './lib/security-rules/index.d.ts', + 'firebase-admin/storage': './lib/storage/index.d.ts', 'firebase-admin/remote-config': './lib/remote-config/index.d.ts', }; diff --git a/gulpfile.js b/gulpfile.js index 413c530e79..042b44560f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -95,6 +95,7 @@ gulp.task('compile', function() { 'lib/messaging/*.d.ts', 'lib/project-management/*.d.ts', 'lib/security-rules/*.d.ts', + 'lib/storage/*.d.ts', 'lib/remote-config/*.d.ts', '!lib/utils/index.d.ts', ]; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 95ca40c2bc..5bbcf4ad1b 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -26,7 +26,7 @@ import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { Auth } from '../auth/index'; import { MachineLearning } from '../machine-learning/machine-learning'; import { Messaging } from '../messaging/index'; -import { Storage } from '../storage/storage'; +import { Storage } from '../storage/index'; import { Database } from '../database/index'; import { Firestore } from '../firestore/index'; import { InstanceId } from '../instance-id/index'; @@ -303,10 +303,8 @@ export class FirebaseApp implements app.App { * @return The Storage service instance of this app. */ public storage(): Storage { - return this.ensureService_('storage', () => { - const storageService: typeof Storage = require('../storage/storage').Storage; - return new storageService(this); - }); + const fn = require('../storage/index').getStorage; + return fn(this); } public firestore(): Firestore { diff --git a/src/storage/index.ts b/src/storage/index.ts index 109f54431d..3cc1e82c12 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -14,8 +14,22 @@ * limitations under the License. */ -import { Bucket } from '@google-cloud/storage'; -import { app } from '../firebase-namespace-api'; +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { Storage } from './storage'; + +export { Storage } from './storage'; + +export function getStorage(app?: App): Storage { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('storage', (app) => new Storage(app)); +} + +import { Storage as TStorage } from './storage'; /** * Gets the {@link storage.Storage `Storage`} service for the @@ -39,25 +53,9 @@ import { app } from '../firebase-namespace-api'; * var otherStorage = admin.storage(otherApp); * ``` */ -export declare function storage(app?: app.App): storage.Storage; +export declare function storage(app?: App): storage.Storage; /* eslint-disable @typescript-eslint/no-namespace */ export namespace storage { - /** - * The default `Storage` service if no - * app is provided or the `Storage` service associated with the provided - * app. - */ - export interface Storage { - /** - * Optional app whose `Storage` service to - * return. If not provided, the default `Storage` service will be returned. - */ - app: app.App; - /** - * @returns A [Bucket](https://cloud.google.com/nodejs/docs/reference/storage/latest/Bucket) - * instance as defined in the `@google-cloud/storage` package. - */ - bucket(name?: string): Bucket; - } + export type Storage = TStorage; } diff --git a/src/storage/storage.ts b/src/storage/storage.ts index 94989db24e..d3511dc052 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -15,32 +15,29 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app'; import { FirebaseError } from '../utils/error'; import { ServiceAccountCredential, isApplicationDefault } from '../app/credential-internal'; import { Bucket, Storage as StorageClient } from '@google-cloud/storage'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; -import { storage } from './index'; - -import StorageInterface = storage.Storage; /** * The default `Storage` service if no * app is provided or the `Storage` service associated with the provided * app. */ -export class Storage implements StorageInterface { +export class Storage { - private readonly appInternal: FirebaseApp; + private readonly appInternal: App; private readonly storageClient: StorageClient; /** - * @param {FirebaseApp} app The app for this Storage service. + * @param app The app for this Storage service. * @constructor * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseError({ code: 'storage/invalid-argument', @@ -109,7 +106,7 @@ export class Storage implements StorageInterface { /** * @return The app associated with this Storage instance. */ - get app(): FirebaseApp { + get app(): App { return this.appInternal; } } diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 7448610fd1..8c8862c826 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -56,6 +56,7 @@ import './machine-learning/machine-learning-api-client.spec'; // Storage import './storage/storage.spec'; +import './storage/index.spec'; // Firestore import './firestore/firestore.spec'; diff --git a/test/unit/storage/index.spec.ts b/test/unit/storage/index.spec.ts new file mode 100644 index 0000000000..8251207677 --- /dev/null +++ b/test/unit/storage/index.spec.ts @@ -0,0 +1,73 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getStorage, Storage } from '../../../src/storage/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Storage', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to initialize Google Cloud Storage client with the ' + + 'available credential. Must initialize the SDK with a certificate credential or ' + + 'application default credentials to use Cloud Storage API.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getStorage()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getStorage(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + expect(() => getStorage(mockCredentialApp)).to.throw(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getStorage(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const storage1: Storage = getStorage(mockApp); + const storage2: Storage = getStorage(mockApp); + expect(storage1).to.equal(storage2); + }); + }); +}); From a51d9c2ad0e73a4e2b563012a7658c3cc623d93c Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 9 Feb 2021 12:01:43 -0800 Subject: [PATCH 15/41] feat(ml): Added firebase-admin/machine-learning entry point (#1160) --- etc/firebase-admin.api.md | 95 +++--- etc/firebase-admin.machine-learning.api.md | 131 ++++++++ generate-reports.js | 1 + gulpfile.js | 15 +- src/app/firebase-app.ts | 9 +- src/machine-learning/index.ts | 283 ++++-------------- .../machine-learning-api-client.ts | 64 +++- src/machine-learning/machine-learning.ts | 70 +++-- test/unit/index.spec.ts | 1 + test/unit/machine-learning/index.spec.ts | 75 +++++ .../machine-learning-api-client.spec.ts | 5 +- .../machine-learning/machine-learning.spec.ts | 5 +- 12 files changed, 403 insertions(+), 351 deletions(-) create mode 100644 etc/firebase-admin.machine-learning.api.md create mode 100644 test/unit/machine-learning/index.spec.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index ec1830df25..1340ad4fca 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -398,73 +398,46 @@ export namespace instanceId { } // @public -export function machineLearning(app?: app.App): machineLearning.MachineLearning; +export function machineLearning(app?: App): machineLearning.MachineLearning; // @public (undocumented) export namespace machineLearning { + // Warning: (ae-forgotten-export) The symbol "AutoMLTfliteModelOptions" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) - export interface AutoMLTfliteModelOptions extends ModelOptionsBase { - // (undocumented) - tfliteModel: { - automlModel: string; - }; - } + export type AutoMLTfliteModelOptions = AutoMLTfliteModelOptions; + // Warning: (ae-forgotten-export) The symbol "GcsTfliteModelOptions" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) - export interface GcsTfliteModelOptions extends ModelOptionsBase { - // (undocumented) - tfliteModel: { - gcsTfliteUri: string; - }; - } - export interface ListModelsOptions { - filter?: string; - pageSize?: number; - pageToken?: string; - } - export interface ListModelsResult { - readonly models: Model[]; - readonly pageToken?: string; - } - export interface MachineLearning { - app: app.App; - createModel(model: ModelOptions): Promise; - deleteModel(modelId: string): Promise; - getModel(modelId: string): Promise; - listModels(options?: ListModelsOptions): Promise; - publishModel(modelId: string): Promise; - unpublishModel(modelId: string): Promise; - updateModel(modelId: string, model: ModelOptions): Promise; - } - export interface Model { - readonly createTime: string; - readonly displayName: string; - readonly etag: string; - readonly locked: boolean; - readonly modelHash?: string; - readonly modelId: string; - readonly published: boolean; - readonly tags?: string[]; - readonly tfliteModel?: TFLiteModel; - toJSON(): { - [key: string]: any; - }; - readonly updateTime: string; - readonly validationError?: string; - waitForUnlocked(maxTimeMillis?: number): Promise; - } + export type GcsTfliteModelOptions = GcsTfliteModelOptions; + // Warning: (ae-forgotten-export) The symbol "ListModelsOptions" needs to be exported by the entry point default-namespace.d.ts + // // (undocumented) - export type ModelOptions = ModelOptionsBase | GcsTfliteModelOptions | AutoMLTfliteModelOptions; - export interface ModelOptionsBase { - // (undocumented) - displayName?: string; - // (undocumented) - tags?: string[]; - } - export interface TFLiteModel { - readonly automlModel?: string; - readonly gcsTfliteUri?: string; - readonly sizeBytes: number; - } + export type ListModelsOptions = ListModelsOptions; + // Warning: (ae-forgotten-export) The symbol "ListModelsResult" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type ListModelsResult = ListModelsResult; + // Warning: (ae-forgotten-export) The symbol "MachineLearning" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type MachineLearning = MachineLearning; + // Warning: (ae-forgotten-export) The symbol "Model" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type Model = Model; + // Warning: (ae-forgotten-export) The symbol "ModelOptions" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type ModelOptions = ModelOptions; + // Warning: (ae-forgotten-export) The symbol "ModelOptionsBase" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type ModelOptionsBase = ModelOptionsBase; + // Warning: (ae-forgotten-export) The symbol "TFLiteModel" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type TFLiteModel = TFLiteModel; } // @public diff --git a/etc/firebase-admin.machine-learning.api.md b/etc/firebase-admin.machine-learning.api.md new file mode 100644 index 0000000000..9f1204e7e2 --- /dev/null +++ b/etc/firebase-admin.machine-learning.api.md @@ -0,0 +1,131 @@ +## API Report File for "firebase-admin.machine-learning" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; + +// @public (undocumented) +export interface AutoMLTfliteModelOptions extends ModelOptionsBase { + // (undocumented) + tfliteModel: { + automlModel: string; + }; +} + +// @public (undocumented) +export interface GcsTfliteModelOptions extends ModelOptionsBase { + // (undocumented) + tfliteModel: { + gcsTfliteUri: string; + }; +} + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function getMachineLearning(app?: App): MachineLearning; + +// @public +export interface ListModelsOptions { + filter?: string; + pageSize?: number; + pageToken?: string; +} + +// @public +export interface ListModelsResult { + readonly models: Model[]; + readonly pageToken?: string; +} + +// @public +export class MachineLearning { + get app(): App; + createModel(model: ModelOptions): Promise; + deleteModel(modelId: string): Promise; + getModel(modelId: string): Promise; + listModels(options?: ListModelsOptions): Promise; + publishModel(modelId: string): Promise; + unpublishModel(modelId: string): Promise; + updateModel(modelId: string, model: ModelOptions): Promise; +} + +// @public +export function machineLearning(app?: App): machineLearning.MachineLearning; + +// @public (undocumented) +export namespace machineLearning { + // (undocumented) + export type AutoMLTfliteModelOptions = AutoMLTfliteModelOptions; + // (undocumented) + export type GcsTfliteModelOptions = GcsTfliteModelOptions; + // (undocumented) + export type ListModelsOptions = ListModelsOptions; + // (undocumented) + export type ListModelsResult = ListModelsResult; + // (undocumented) + export type MachineLearning = MachineLearning; + // (undocumented) + export type Model = Model; + // (undocumented) + export type ModelOptions = ModelOptions; + // (undocumented) + export type ModelOptionsBase = ModelOptionsBase; + // (undocumented) + export type TFLiteModel = TFLiteModel; +} + +// @public +export class Model { + // (undocumented) + get createTime(): string; + // (undocumented) + get displayName(): string; + // (undocumented) + get etag(): string; + get locked(): boolean; + // (undocumented) + get modelHash(): string | undefined; + // (undocumented) + get modelId(): string; + // (undocumented) + get published(): boolean; + // (undocumented) + get tags(): string[]; + // (undocumented) + get tfliteModel(): TFLiteModel | undefined; + // (undocumented) + toJSON(): { + [key: string]: any; + }; + // (undocumented) + get updateTime(): string; + // (undocumented) + get validationError(): string | undefined; + waitForUnlocked(maxTimeMillis?: number): Promise; +} + +// @public (undocumented) +export type ModelOptions = ModelOptionsBase | GcsTfliteModelOptions | AutoMLTfliteModelOptions; + +// @public +export interface ModelOptionsBase { + // (undocumented) + displayName?: string; + // (undocumented) + tags?: string[]; +} + +// @public +export interface TFLiteModel { + readonly automlModel?: string; + readonly gcsTfliteUri?: string; + readonly sizeBytes: number; +} + + +// (No @packageDocumentation comment for this package) + +``` diff --git a/generate-reports.js b/generate-reports.js index 8f31443727..4d92f64563 100644 --- a/generate-reports.js +++ b/generate-reports.js @@ -40,6 +40,7 @@ const entryPoints = { 'firebase-admin/firestore': './lib/firestore/index.d.ts', 'firebase-admin/instance-id': './lib/instance-id/index.d.ts', 'firebase-admin/messaging': './lib/messaging/index.d.ts', + 'firebase-admin/machine-learning': './lib/machine-learning/index.d.ts', 'firebase-admin/project-management': './lib/project-management/index.d.ts', 'firebase-admin/security-rules': './lib/security-rules/index.d.ts', 'firebase-admin/storage': './lib/storage/index.d.ts', diff --git a/gulpfile.js b/gulpfile.js index 042b44560f..07184a93f9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -84,20 +84,7 @@ gulp.task('compile', function() { const configuration = [ 'lib/**/*.js', - 'lib/**/index.d.ts', - 'lib/firebase-namespace-api.d.ts', - 'lib/core.d.ts', - 'lib/app/*.d.ts', - 'lib/auth/*.d.ts', - 'lib/database/*.d.ts', - 'lib/firestore/*.d.ts', - 'lib/instance-id/*.d.ts', - 'lib/messaging/*.d.ts', - 'lib/project-management/*.d.ts', - 'lib/security-rules/*.d.ts', - 'lib/storage/*.d.ts', - 'lib/remote-config/*.d.ts', - '!lib/utils/index.d.ts', + 'lib/**/*.d.ts', ]; workflow = workflow.pipe(filter(configuration)); diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 5bbcf4ad1b..23ca6001c7 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -24,7 +24,7 @@ import { FirebaseNamespaceInternals } from './firebase-namespace'; import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { Auth } from '../auth/index'; -import { MachineLearning } from '../machine-learning/machine-learning'; +import { MachineLearning } from '../machine-learning/index'; import { Messaging } from '../messaging/index'; import { Storage } from '../storage/index'; import { Database } from '../database/index'; @@ -328,11 +328,8 @@ export class FirebaseApp implements app.App { * @return The Machine Learning service instance of this app */ public machineLearning(): MachineLearning { - return this.ensureService_('machine-learning', () => { - const machineLearningService: typeof MachineLearning = - require('../machine-learning/machine-learning').MachineLearning; - return new machineLearningService(this); - }); + const fn = require('../machine-learning/index').getMachineLearning; + return fn(this); } /** diff --git a/src/machine-learning/index.ts b/src/machine-learning/index.ts index 7e5a8078cd..c12da2b832 100644 --- a/src/machine-learning/index.ts +++ b/src/machine-learning/index.ts @@ -14,7 +14,46 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { MachineLearning } from './machine-learning'; + +export { + MachineLearning, + ListModelsResult, + Model, + TFLiteModel, +} from './machine-learning'; +export { + AutoMLTfliteModelOptions, + GcsTfliteModelOptions, + ListModelsOptions, + ModelOptions, + ModelOptionsBase, +} from './machine-learning-api-client'; + +export function getMachineLearning(app?: App): MachineLearning { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('machineLearning', (app) => new MachineLearning(app)); +} + +import { + ListModelsResult as TListModelsResult, + MachineLearning as TMachineLearning, + Model as TModel, + TFLiteModel as TTFLiteModel, +} from './machine-learning'; +import { + AutoMLTfliteModelOptions as TAutoMLTfliteModelOptions, + GcsTfliteModelOptions as TGcsTfliteModelOptions, + ListModelsOptions as TListModelsOptions, + ModelOptions as TModelOptions, + ModelOptionsBase as TModelOptionsBase, +} from './machine-learning-api-client'; /** * Gets the {@link machineLearning.MachineLearning `MachineLearning`} service for the @@ -45,238 +84,18 @@ import { app } from '../firebase-namespace-api'; * @return The default `MachineLearning` service if no app is provided or the * `MachineLearning` service associated with the provided app. */ -export declare function machineLearning(app?: app.App): machineLearning.MachineLearning; +export declare function machineLearning(app?: App): machineLearning.MachineLearning; /* eslint-disable @typescript-eslint/no-namespace */ export namespace machineLearning { - /** - * Firebase ML Model input objects - */ - export interface ModelOptionsBase { - displayName?: string; - tags?: string[]; - } - - export interface GcsTfliteModelOptions extends ModelOptionsBase { - tfliteModel: { - gcsTfliteUri: string; - }; - } - - export interface AutoMLTfliteModelOptions extends ModelOptionsBase { - tfliteModel: { - automlModel: string; - }; - } - - export type ModelOptions = ModelOptionsBase | GcsTfliteModelOptions | AutoMLTfliteModelOptions; - - /** - * A TensorFlow Lite Model output object - * - * One of either the `gcsTfliteUri` or `automlModel` properties will be - * defined. - */ - export interface TFLiteModel { - /** The size of the model. */ - readonly sizeBytes: number; - - /** The URI from which the model was originally provided to Firebase. */ - readonly gcsTfliteUri?: string; - /** - * The AutoML model reference from which the model was originally provided - * to Firebase. - */ - readonly automlModel?: string; - } - - /** - * A Firebase ML Model output object - */ - export interface Model { - /** The ID of the model. */ - readonly modelId: string; - - /** - * The model's name. This is the name you use from your app to load the - * model. - */ - readonly displayName: string; - - /** - * The model's tags, which can be used to group or filter models in list - * operations. - */ - readonly tags?: string[]; - - /** The timestamp of the model's creation. */ - readonly createTime: string; - - /** The timestamp of the model's most recent update. */ - readonly updateTime: string; - - /** Error message when model validation fails. */ - readonly validationError?: string; - - /** True if the model is published. */ - readonly published: boolean; - - /** - * The ETag identifier of the current version of the model. This value - * changes whenever you update any of the model's properties. - */ - readonly etag: string; - - /** - * The hash of the model's `tflite` file. This value changes only when - * you upload a new TensorFlow Lite model. - */ - readonly modelHash?: string; - - /** - * True if the model is locked by a server-side operation. You can't make - * changes to a locked model. See {@link waitForUnlocked `waitForUnlocked()`}. - */ - readonly locked: boolean; - - /** - * Wait for the model to be unlocked. - * - * @param {number} maxTimeMillis The maximum time in milliseconds to wait. - * If not specified, a default maximum of 2 minutes is used. - * - * @return {Promise} A promise that resolves when the model is unlocked - * or the maximum wait time has passed. - */ - waitForUnlocked(maxTimeMillis?: number): Promise; - - /** - * Return the model as a JSON object. - */ - toJSON(): {[key: string]: any}; - - /** Metadata about the model's TensorFlow Lite model file. */ - readonly tfliteModel?: TFLiteModel; - } - - /** - * Interface representing options for listing Models. - */ - export interface ListModelsOptions { - /** - * An expression that specifies how to filter the results. - * - * Examples: - * - * ``` - * display_name = your_model - * display_name : experimental_* - * tags: face_detector AND tags: experimental - * state.published = true - * ``` - * - * See https://firebase.google.com/docs/ml/manage-hosted-models#list_your_projects_models - */ - filter?: string; - - /** The number of results to return in each page. */ - pageSize?: number; - - /** A token that specifies the result page to return. */ - pageToken?: string; - } - - /** Response object for a listModels operation. */ - export interface ListModelsResult { - /** A list of models in your project. */ - readonly models: Model[]; - - /** - * A token you can use to retrieve the next page of results. If null, the - * current page is the final page. - */ - readonly pageToken?: string; - } - - - /** - * The Firebase `MachineLearning` service interface. - * - * Do not call this constructor directly. Instead, use - * [`admin.machineLearning()`](admin.machineLearning#machineLearning). - */ - export interface MachineLearning { - /** - * The {@link app.App} associated with the current `MachineLearning` - * service instance. - */ - app: app.App; - - /** - * Creates a model in the current Firebase project. - * - * @param {ModelOptions} model The model to create. - * - * @return {Promise} A Promise fulfilled with the created model. - */ - createModel(model: ModelOptions): Promise; - - /** - * Updates a model's metadata or model file. - * - * @param {string} modelId The ID of the model to update. - * @param {ModelOptions} model The model fields to update. - * - * @return {Promise} A Promise fulfilled with the updated model. - */ - updateModel(modelId: string, model: ModelOptions): Promise; - - /** - * Publishes a Firebase ML model. - * - * A published model can be downloaded to client apps. - * - * @param {string} modelId The ID of the model to publish. - * - * @return {Promise} A Promise fulfilled with the published model. - */ - publishModel(modelId: string): Promise; - - /** - * Unpublishes a Firebase ML model. - * - * @param {string} modelId The ID of the model to unpublish. - * - * @return {Promise} A Promise fulfilled with the unpublished model. - */ - unpublishModel(modelId: string): Promise; - - /** - * Gets the model specified by the given ID. - * - * @param {string} modelId The ID of the model to get. - * - * @return {Promise} A Promise fulfilled with the model object. - */ - getModel(modelId: string): Promise; - - /** - * Lists the current project's models. - * - * @param {ListModelsOptions} options The listing options. - * - * @return {Promise} A promise that - * resolves with the current (filtered) list of models and the next page - * token. For the last page, an empty list of models and no page token - * are returned. - */ - listModels(options?: ListModelsOptions): Promise; - - /** - * Deletes a model from the current project. - * - * @param {string} modelId The ID of the model to delete. - */ - deleteModel(modelId: string): Promise; - } + export type ListModelsResult = TListModelsResult; + export type MachineLearning = TMachineLearning; + export type Model = TModel; + export type TFLiteModel = TTFLiteModel; + + export type AutoMLTfliteModelOptions = TAutoMLTfliteModelOptions; + export type GcsTfliteModelOptions = TGcsTfliteModelOptions; + export type ListModelsOptions = TListModelsOptions; + export type ModelOptions = TModelOptions; + export type ModelOptionsBase = TModelOptionsBase; } diff --git a/src/machine-learning/machine-learning-api-client.ts b/src/machine-learning/machine-learning-api-client.ts index 18a53180cd..d2923a3143 100644 --- a/src/machine-learning/machine-learning-api-client.ts +++ b/src/machine-learning/machine-learning-api-client.ts @@ -14,19 +14,64 @@ * limitations under the License. */ +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, HttpError, AuthorizedHttpClient, ExponentialBackoffPoller } from '../utils/api-request'; import { PrefixedFirebaseError } from '../utils/error'; -import { FirebaseMachineLearningError, MachineLearningErrorCode } from './machine-learning-utils'; import * as utils from '../utils/index'; import * as validator from '../utils/validator'; -import { FirebaseApp } from '../app/firebase-app'; -import { machineLearning } from './index'; +import { FirebaseMachineLearningError, MachineLearningErrorCode } from './machine-learning-utils'; + +/** + * Firebase ML Model input objects + */ +export interface ModelOptionsBase { + displayName?: string; + tags?: string[]; +} + +export interface GcsTfliteModelOptions extends ModelOptionsBase { + tfliteModel: { + gcsTfliteUri: string; + }; +} + +export interface AutoMLTfliteModelOptions extends ModelOptionsBase { + tfliteModel: { + automlModel: string; + }; +} -import GcsTfliteModelOptions = machineLearning.GcsTfliteModelOptions; -import ListModelsOptions = machineLearning.ListModelsOptions; -import ModelOptions = machineLearning.ModelOptions; +export type ModelOptions = ModelOptionsBase | GcsTfliteModelOptions | AutoMLTfliteModelOptions; + +/** + * Interface representing options for listing Models. + */ +export interface ListModelsOptions { + /** + * An expression that specifies how to filter the results. + * + * Examples: + * + * ``` + * display_name = your_model + * display_name : experimental_* + * tags: face_detector AND tags: experimental + * state.published = true + * ``` + * + * See https://firebase.google.com/docs/ml/manage-hosted-models#list_your_projects_models + */ + filter?: string; + + /** The number of results to return in each page. */ + pageSize?: number; + + /** A token that specifies the result page to return. */ + pageToken?: string; +} const ML_V1BETA2_API = 'https://firebaseml.googleapis.com/v1beta2'; const FIREBASE_VERSION_HEADER = { @@ -92,13 +137,14 @@ export interface OperationResponse { /** * Class that facilitates sending requests to the Firebase ML backend API. * - * @private + * @internal */ export class MachineLearningApiClient { + private readonly httpClient: HttpClient; private projectIdPrefix?: string; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseMachineLearningError( 'invalid-argument', @@ -106,7 +152,7 @@ export class MachineLearningApiClient { + 'Firebase app instance.'); } - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } public createModel(model: ModelOptions): Promise { diff --git a/src/machine-learning/machine-learning.ts b/src/machine-learning/machine-learning.ts index 986c0b852e..c150c89e61 100644 --- a/src/machine-learning/machine-learning.ts +++ b/src/machine-learning/machine-learning.ts @@ -14,37 +14,63 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; -import { - MachineLearningApiClient, ModelResponse, ModelUpdateOptions, isGcsTfliteModelOptions -} from './machine-learning-api-client'; +import { App } from '../app'; +import { getStorage } from '../storage/index'; import { FirebaseError } from '../utils/error'; import * as validator from '../utils/validator'; -import { FirebaseMachineLearningError } from './machine-learning-utils'; import { deepCopy } from '../utils/deep-copy'; import * as utils from '../utils'; -import { machineLearning } from './index'; +import { + MachineLearningApiClient, ModelResponse, ModelUpdateOptions, isGcsTfliteModelOptions, + ListModelsOptions, ModelOptions, +} from './machine-learning-api-client'; +import { FirebaseMachineLearningError } from './machine-learning-utils'; -import ListModelsOptions = machineLearning.ListModelsOptions; -import ListModelsResult = machineLearning.ListModelsResult; -import MachineLearningInterface = machineLearning.MachineLearning; -import ModelInterface = machineLearning.Model; -import ModelOptions = machineLearning.ModelOptions; -import TFLiteModel = machineLearning.TFLiteModel; +/** Response object for a listModels operation. */ +export interface ListModelsResult { + /** A list of models in your project. */ + readonly models: Model[]; + + /** + * A token you can use to retrieve the next page of results. If null, the + * current page is the final page. + */ + readonly pageToken?: string; +} + +/** + * A TensorFlow Lite Model output object + * + * One of either the `gcsTfliteUri` or `automlModel` properties will be + * defined. + */ +export interface TFLiteModel { + /** The size of the model. */ + readonly sizeBytes: number; + + /** The URI from which the model was originally provided to Firebase. */ + readonly gcsTfliteUri?: string; + /** + * The AutoML model reference from which the model was originally provided + * to Firebase. + */ + readonly automlModel?: string; +} /** * The Firebase Machine Learning class */ -export class MachineLearning implements MachineLearningInterface { +export class MachineLearning { private readonly client: MachineLearningApiClient; - private readonly appInternal: FirebaseApp; + private readonly appInternal: App; /** - * @param {FirebaseApp} app The app for this ML service. + * @param app The app for this ML service. * @constructor + * @internal */ - constructor(app: FirebaseApp) { + constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseError({ code: 'machine-learning/invalid-argument', @@ -60,9 +86,9 @@ export class MachineLearning implements MachineLearningInterface { /** * Returns the app associated with this ML instance. * - * @return {FirebaseApp} The app associated with this ML instance. + * @return The app associated with this ML instance. */ - public get app(): FirebaseApp { + public get app(): App { return this.appInternal; } @@ -207,7 +233,7 @@ export class MachineLearning implements MachineLearningInterface { } const bucketName = matches[1]; const blobName = matches[2]; - const bucket = this.appInternal.storage().bucket(bucketName); + const bucket = getStorage(this.app).bucket(bucketName); const blob = bucket.file(blobName); return blob.getSignedUrl({ action: 'read', @@ -219,10 +245,13 @@ export class MachineLearning implements MachineLearningInterface { /** * A Firebase ML Model output object. */ -export class Model implements ModelInterface { +export class Model { private model: ModelResponse; private readonly client?: MachineLearningApiClient; + /** + * @internal + */ constructor(model: ModelResponse, client: MachineLearningApiClient) { this.model = Model.validateAndClone(model); this.client = client; @@ -308,7 +337,6 @@ export class Model implements ModelInterface { return jsonModel; } - /** * Wait for the active operations on the model to complete. * @param maxTimeMillis The number of milliseconds to wait for the model to be unlocked. If unspecified, diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 8c8862c826..b6bded8145 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -51,6 +51,7 @@ import './messaging/messaging.spec'; import './messaging/batch-requests.spec'; // Machine Learning +import './machine-learning/index.spec'; import './machine-learning/machine-learning.spec'; import './machine-learning/machine-learning-api-client.spec'; diff --git a/test/unit/machine-learning/index.spec.ts b/test/unit/machine-learning/index.spec.ts new file mode 100644 index 0000000000..1937f5387d --- /dev/null +++ b/test/unit/machine-learning/index.spec.ts @@ -0,0 +1,75 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { App } from '../../../src/app/index'; +import { getMachineLearning, MachineLearning } from '../../../src/machine-learning/index'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('MachineLearning', () => { + let mockApp: App; + let mockCredentialApp: App; + + const noProjectIdError = 'Failed to determine project ID. Initialize the SDK ' + + 'with service account credentials, or set project ID as an app option. Alternatively, set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + }); + + describe('getMachineLearning()', () => { + it('should throw when default app is not available', () => { + expect(() => { + return getMachineLearning(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const client = getMachineLearning(mockCredentialApp); + return client.getModel('test') + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return getMachineLearning(mockApp); + }).not.to.throw(); + }); + + it('should return the same instance for a given app instance', () => { + const client1: MachineLearning = getMachineLearning(mockApp); + const client2: MachineLearning = getMachineLearning(mockApp); + expect(client1).to.equal(client2); + }); + }); +}); diff --git a/test/unit/machine-learning/machine-learning-api-client.spec.ts b/test/unit/machine-learning/machine-learning-api-client.spec.ts index 1dc8a0df35..01f6936869 100644 --- a/test/unit/machine-learning/machine-learning-api-client.spec.ts +++ b/test/unit/machine-learning/machine-learning-api-client.spec.ts @@ -27,10 +27,7 @@ import { FirebaseAppError } from '../../../src/utils/error'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { getSdkVersion } from '../../../src/utils/index'; import { MachineLearningApiClient } from '../../../src/machine-learning/machine-learning-api-client'; -import { machineLearning } from '../../../src/machine-learning/index'; - -import ListModelsOptions = machineLearning.ListModelsOptions; -import ModelOptions = machineLearning.ModelOptions; +import { ListModelsOptions, ModelOptions } from '../../../src/machine-learning/index'; const expect = chai.expect; diff --git a/test/unit/machine-learning/machine-learning.spec.ts b/test/unit/machine-learning/machine-learning.spec.ts index 9c8314fc03..8791ca9c87 100644 --- a/test/unit/machine-learning/machine-learning.spec.ts +++ b/test/unit/machine-learning/machine-learning.spec.ts @@ -21,7 +21,6 @@ import * as chai from 'chai'; import * as sinon from 'sinon'; import { FirebaseApp } from '../../../src/app/firebase-app'; import * as mocks from '../../resources/mocks'; -import { MachineLearning, Model } from '../../../src/machine-learning/machine-learning'; import { MachineLearningApiClient, StatusErrorResponse, @@ -30,9 +29,7 @@ import { } from '../../../src/machine-learning/machine-learning-api-client'; import { FirebaseMachineLearningError } from '../../../src/machine-learning/machine-learning-utils'; import { deepCopy } from '../../../src/utils/deep-copy'; -import { machineLearning } from '../../../src/machine-learning/index'; - -import ModelOptions = machineLearning.ModelOptions; +import { MachineLearning, Model, ModelOptions } from '../../../src/machine-learning/index'; const expect = chai.expect; From 82d6dc108ee1d0ca20f83da22401c406f0c6302f Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 11 Feb 2021 13:51:12 -0800 Subject: [PATCH 16/41] fix: Moved FirebaseError interface to app module (#1164) --- etc/firebase-admin.app.api.md | 14 ++++ src/app/core.ts | 79 +++++++++++++++++++ src/app/index.ts | 2 +- src/auth/user-import-builder.ts | 2 +- src/firebase-namespace-api.ts | 79 ------------------- src/messaging/messaging-api.ts | 2 +- test/unit/auth/auth.spec.ts | 8 +- test/unit/auth/tenant-manager.spec.ts | 5 +- test/unit/auth/tenant.spec.ts | 4 +- test/unit/instance-id/instance-id.spec.ts | 2 +- test/unit/messaging/messaging.spec.ts | 3 +- .../remote-config-api-client.spec.ts | 8 +- test/unit/storage/storage.spec.ts | 2 +- 13 files changed, 110 insertions(+), 100 deletions(-) diff --git a/etc/firebase-admin.app.api.md b/etc/firebase-admin.app.api.md index 96c5a4ea55..08d145df0c 100644 --- a/etc/firebase-admin.app.api.md +++ b/etc/firebase-admin.app.api.md @@ -37,6 +37,20 @@ export interface Credential { // @public (undocumented) export function deleteApp(app: App): Promise; +// @public +export interface FirebaseArrayIndexError { + error: FirebaseError; + index: number; +} + +// @public +export interface FirebaseError { + code: string; + message: string; + stack?: string; + toJSON(): object; +} + // @public (undocumented) export function getApp(name?: string): App; diff --git a/src/app/core.ts b/src/app/core.ts index 397ce189a5..c234b2875f 100644 --- a/src/app/core.ts +++ b/src/app/core.ts @@ -123,3 +123,82 @@ export interface App { */ options: AppOptions; } + +/** + * `FirebaseError` is a subclass of the standard JavaScript `Error` object. In + * addition to a message string and stack trace, it contains a string code. + */ +export interface FirebaseError { + + /** + * Error codes are strings using the following format: `"service/string-code"`. + * Some examples include `"auth/invalid-uid"` and + * `"messaging/invalid-recipient"`. + * + * While the message for a given error can change, the code will remain the same + * between backward-compatible versions of the Firebase SDK. + */ + code: string; + + /** + * An explanatory message for the error that just occurred. + * + * This message is designed to be helpful to you, the developer. Because + * it generally does not convey meaningful information to end users, + * this message should not be displayed in your application. + */ + message: string; + + /** + * A string value containing the execution backtrace when the error originally + * occurred. + * + * This information can be useful to you and can be sent to + * {@link https://firebase.google.com/support/ Firebase Support} to help + * explain the cause of an error. + */ + stack?: string; + + /** + * @return A JSON-serializable representation of this object. + */ + toJSON(): object; +} + +/** + * Composite type which includes both a `FirebaseError` object and an index + * which can be used to get the errored item. + * + * @example + * ```javascript + * var registrationTokens = [token1, token2, token3]; + * admin.messaging().subscribeToTopic(registrationTokens, 'topic-name') + * .then(function(response) { + * if (response.failureCount > 0) { + * console.log("Following devices unsucessfully subscribed to topic:"); + * response.errors.forEach(function(error) { + * var invalidToken = registrationTokens[error.index]; + * console.log(invalidToken, error.error); + * }); + * } else { + * console.log("All devices successfully subscribed to topic:", response); + * } + * }) + * .catch(function(error) { + * console.log("Error subscribing to topic:", error); + * }); + *``` + */ +export interface FirebaseArrayIndexError { + + /** + * The index of the errored item within the original array passed as part of the + * called API method. + */ + index: number; + + /** + * The error object. + */ + error: FirebaseError; +} diff --git a/src/app/index.ts b/src/app/index.ts index 9aeef5b363..e343e4fac2 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -17,7 +17,7 @@ import { getSdkVersion } from '../utils'; -export { App, AppOptions } from './core' +export { App, AppOptions, FirebaseArrayIndexError, FirebaseError } from './core' export { initializeApp, getApp, getApps, deleteApp } from './lifecycle'; export { Credential, ServiceAccount, GoogleOAuthAccessToken } from './credential'; diff --git a/src/auth/user-import-builder.ts b/src/auth/user-import-builder.ts index 72faa5304e..c0e151abaa 100644 --- a/src/auth/user-import-builder.ts +++ b/src/auth/user-import-builder.ts @@ -14,11 +14,11 @@ * limitations under the License. */ +import { FirebaseArrayIndexError } from '../app/index'; import { deepCopy, deepExtend } from '../utils/deep-copy'; import * as utils from '../utils'; import * as validator from '../utils/validator'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; -import { FirebaseArrayIndexError } from '../firebase-namespace-api'; import { UpdateMultiFactorInfoRequest, UpdatePhoneMultiFactorInfoRequest, MultiFactorUpdateSettings } from './auth-config'; diff --git a/src/firebase-namespace-api.ts b/src/firebase-namespace-api.ts index 9a56547118..798d8c426c 100644 --- a/src/firebase-namespace-api.ts +++ b/src/firebase-namespace-api.ts @@ -29,85 +29,6 @@ import { App as AppCore, AppOptions } from './app/index'; export * from './app/index'; -/** - * `FirebaseError` is a subclass of the standard JavaScript `Error` object. In - * addition to a message string and stack trace, it contains a string code. - */ -export interface FirebaseError { - - /** - * Error codes are strings using the following format: `"service/string-code"`. - * Some examples include `"auth/invalid-uid"` and - * `"messaging/invalid-recipient"`. - * - * While the message for a given error can change, the code will remain the same - * between backward-compatible versions of the Firebase SDK. - */ - code: string; - - /** - * An explanatory message for the error that just occurred. - * - * This message is designed to be helpful to you, the developer. Because - * it generally does not convey meaningful information to end users, - * this message should not be displayed in your application. - */ - message: string; - - /** - * A string value containing the execution backtrace when the error originally - * occurred. - * - * This information can be useful to you and can be sent to - * {@link https://firebase.google.com/support/ Firebase Support} to help - * explain the cause of an error. - */ - stack?: string; - - /** - * @return A JSON-serializable representation of this object. - */ - toJSON(): object; -} - -/** - * Composite type which includes both a `FirebaseError` object and an index - * which can be used to get the errored item. - * - * @example - * ```javascript - * var registrationTokens = [token1, token2, token3]; - * admin.messaging().subscribeToTopic(registrationTokens, 'topic-name') - * .then(function(response) { - * if (response.failureCount > 0) { - * console.log("Following devices unsucessfully subscribed to topic:"); - * response.errors.forEach(function(error) { - * var invalidToken = registrationTokens[error.index]; - * console.log(invalidToken, error.error); - * }); - * } else { - * console.log("All devices successfully subscribed to topic:", response); - * } - * }) - * .catch(function(error) { - * console.log("Error subscribing to topic:", error); - * }); - *``` - */ -export interface FirebaseArrayIndexError { - - /** - * The index of the errored item within the original array passed as part of the - * called API method. - */ - index: number; - - /** - * The error object. - */ - error: FirebaseError; -} - // eslint-disable-next-line @typescript-eslint/no-namespace export namespace app { /** diff --git a/src/messaging/messaging-api.ts b/src/messaging/messaging-api.ts index 44c68e66eb..e586ba039d 100644 --- a/src/messaging/messaging-api.ts +++ b/src/messaging/messaging-api.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseArrayIndexError, FirebaseError } from '../firebase-namespace-api'; +import { FirebaseArrayIndexError, FirebaseError } from '../app/index'; export interface BaseMessage { data?: { [key: string]: string }; diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index 406d1c7ee7..d896e75f20 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -27,8 +27,6 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { Auth, TenantAwareAuth, BaseAuth } from '../../../src/auth/index'; -import { UserRecord } from '../../../src/auth/user-record'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { AuthRequestHandler, TenantAwareAuthRequestHandler, AbstractAuthRequestHandler, @@ -41,10 +39,12 @@ import { OIDCConfig, SAMLConfig, OIDCConfigServerResponse, SAMLConfigServerResponse, } from '../../../src/auth/auth-config'; import { deepCopy } from '../../../src/utils/deep-copy'; -import { TenantManager } from '../../../src/auth/tenant-manager'; import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { HttpClient } from '../../../src/utils/api-request'; -import { DecodedIdToken, UpdateRequest, AuthProviderConfigFilter } from '../../../src/auth/index'; +import { + Auth, TenantAwareAuth, BaseAuth, UserRecord, DecodedIdToken, + UpdateRequest, AuthProviderConfigFilter, TenantManager, +} from '../../../src/auth/index'; chai.should(); chai.use(sinonChai); diff --git a/test/unit/auth/tenant-manager.spec.ts b/test/unit/auth/tenant-manager.spec.ts index b769d236b0..720ba32b01 100644 --- a/test/unit/auth/tenant-manager.spec.ts +++ b/test/unit/auth/tenant-manager.spec.ts @@ -25,11 +25,10 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { AuthRequestHandler } from '../../../src/auth/auth-api-request'; -import { Tenant, TenantServerResponse } from '../../../src/auth/tenant'; -import { TenantManager } from '../../../src/auth/tenant-manager'; +import { TenantServerResponse } from '../../../src/auth/tenant'; import { AuthClientErrorCode, FirebaseAuthError } from '../../../src/utils/error'; import { - CreateTenantRequest, UpdateTenantRequest, ListTenantsResult, + CreateTenantRequest, UpdateTenantRequest, ListTenantsResult, Tenant, TenantManager, } from '../../../src/auth/index'; chai.should(); diff --git a/test/unit/auth/tenant.spec.ts b/test/unit/auth/tenant.spec.ts index 31370e17bc..fbb94d3b23 100644 --- a/test/unit/auth/tenant.spec.ts +++ b/test/unit/auth/tenant.spec.ts @@ -21,9 +21,9 @@ import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../../src/utils/deep-copy'; import { EmailSignInConfig, MultiFactorAuthConfig } from '../../../src/auth/auth-config'; -import { Tenant, TenantServerResponse } from '../../../src/auth/tenant'; +import { TenantServerResponse } from '../../../src/auth/tenant'; import { - CreateTenantRequest, UpdateTenantRequest, EmailSignInProviderConfig, + CreateTenantRequest, UpdateTenantRequest, EmailSignInProviderConfig, Tenant, } from '../../../src/auth/index'; chai.should(); diff --git a/test/unit/instance-id/instance-id.spec.ts b/test/unit/instance-id/instance-id.spec.ts index 90bbacdb8d..e6669c7fa8 100644 --- a/test/unit/instance-id/instance-id.spec.ts +++ b/test/unit/instance-id/instance-id.spec.ts @@ -26,7 +26,7 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; -import { InstanceId } from '../../../src/instance-id/instance-id'; +import { InstanceId } from '../../../src/instance-id/index'; import { FirebaseInstanceIdRequestHandler } from '../../../src/instance-id/instance-id-request-internal'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../../../src/utils/error'; diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index b322030a18..3277edeefe 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -31,9 +31,8 @@ import { FirebaseApp } from '../../../src/app/firebase-app'; import { Message, MessagingOptions, MessagingPayload, MessagingDevicesResponse, MessagingDeviceGroupResponse, MessagingTopicManagementResponse, BatchResponse, - SendResponse, MulticastMessage, + SendResponse, MulticastMessage, Messaging, } from '../../../src/messaging/index'; -import { Messaging } from '../../../src/messaging/messaging'; import { BLACKLISTED_OPTIONS_KEYS, BLACKLISTED_DATA_PAYLOAD_KEYS } from '../../../src/messaging/messaging-internal'; import { HttpClient } from '../../../src/utils/api-request'; import { getSdkVersion } from '../../../src/utils/index'; diff --git a/test/unit/remote-config/remote-config-api-client.spec.ts b/test/unit/remote-config/remote-config-api-client.spec.ts index 2d982e8808..70ca7bae97 100644 --- a/test/unit/remote-config/remote-config-api-client.spec.ts +++ b/test/unit/remote-config/remote-config-api-client.spec.ts @@ -19,7 +19,6 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; -import { remoteConfig } from '../../../src/remote-config/index'; import { FirebaseRemoteConfigError, RemoteConfigApiClient @@ -31,10 +30,9 @@ import { FirebaseAppError } from '../../../src/utils/error'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { deepCopy } from '../../../src/utils/deep-copy'; import { getSdkVersion } from '../../../src/utils/index'; - -import RemoteConfigTemplate = remoteConfig.RemoteConfigTemplate; -import Version = remoteConfig.Version; -import ListVersionsResult = remoteConfig.ListVersionsResult; +import { + RemoteConfigTemplate, Version, ListVersionsResult, +} from '../../../src/remote-config/index'; const expect = chai.expect; diff --git a/test/unit/storage/storage.spec.ts b/test/unit/storage/storage.spec.ts index 80cb49ab92..5f59250930 100644 --- a/test/unit/storage/storage.spec.ts +++ b/test/unit/storage/storage.spec.ts @@ -22,7 +22,7 @@ import { expect } from 'chai'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { Storage } from '../../../src/storage/storage'; +import { Storage } from '../../../src/storage/index'; describe('Storage', () => { let mockApp: FirebaseApp; From 5a0f2fb88c52601336cff05da0df80dd8e133e2a Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 16 Feb 2021 10:43:49 -0800 Subject: [PATCH 17/41] fix: Updated documentation for app and instance-id entry points (#1168) * fix: Added API ref docs to firebase-admin/app * fix: Added API docs to firebase-admin/instance-id --- etc/firebase-admin.api.md | 12 +-- etc/firebase-admin.app.api.md | 12 +-- etc/firebase-admin.instance-id.api.md | 2 +- src/app/core.ts | 16 +++- src/app/credential-factory.ts | 112 ++++++++++++++++++++++++++ src/app/credential.ts | 8 ++ src/app/lifecycle.ts | 17 ++++ src/instance-id/index.ts | 30 +++++++ src/instance-id/instance-id.ts | 14 +--- 9 files changed, 195 insertions(+), 28 deletions(-) diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 1340ad4fca..00fe991142 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -10,7 +10,7 @@ import { FirebaseDatabase } from '@firebase/database-types'; import * as _firestore from '@google-cloud/firestore'; import * as rtdb from '@firebase/database-types'; -// @public (undocumented) +// @public export interface App { name: string; options: AppOptions; @@ -46,7 +46,7 @@ export namespace app { } } -// @public (undocumented) +// @public export function applicationDefault(httpAgent?: Agent): Credential; // @public @@ -274,10 +274,10 @@ export namespace auth { export type UserRecord = UserRecord; } -// @public (undocumented) +// @public export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; -// @public (undocumented) +// @public export interface Credential { getAccessToken(): Promise; } @@ -316,7 +316,7 @@ export namespace database { const ServerValue: rtdb.ServerValue; } -// @public (undocumented) +// @public export function deleteApp(app: App): Promise; // @public @@ -618,7 +618,7 @@ export namespace projectManagement { export type ShaCertificate = ShaCertificate; } -// @public (undocumented) +// @public export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; // @public diff --git a/etc/firebase-admin.app.api.md b/etc/firebase-admin.app.api.md index 08d145df0c..1614e2acb3 100644 --- a/etc/firebase-admin.app.api.md +++ b/etc/firebase-admin.app.api.md @@ -6,13 +6,13 @@ import { Agent } from 'http'; -// @public (undocumented) +// @public export interface App { name: string; options: AppOptions; } -// @public (undocumented) +// @public export function applicationDefault(httpAgent?: Agent): Credential; // @public @@ -26,15 +26,15 @@ export interface AppOptions { storageBucket?: string; } -// @public (undocumented) +// @public export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; -// @public (undocumented) +// @public export interface Credential { getAccessToken(): Promise; } -// @public (undocumented) +// @public export function deleteApp(app: App): Promise; // @public @@ -68,7 +68,7 @@ export interface GoogleOAuthAccessToken { // @public (undocumented) export function initializeApp(options?: AppOptions, name?: string): App; -// @public (undocumented) +// @public export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; // @public (undocumented) diff --git a/etc/firebase-admin.instance-id.api.md b/etc/firebase-admin.instance-id.api.md index f5929e7efc..a391fbacc7 100644 --- a/etc/firebase-admin.instance-id.api.md +++ b/etc/firebase-admin.instance-id.api.md @@ -8,7 +8,7 @@ import { Agent } from 'http'; // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @public export function getInstanceId(app?: App): InstanceId; // @public diff --git a/src/app/core.ts b/src/app/core.ts index c234b2875f..a946e8ab41 100644 --- a/src/app/core.ts +++ b/src/app/core.ts @@ -84,6 +84,16 @@ export interface AppOptions { httpAgent?: Agent; } +/** + * A Firebase app holds the initialization information for a collection of + * services. + * + * Do not call this constructor directly. Instead, use + * {@link + * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp + * `admin.initializeApp()`} + * to create an app. + */ export interface App { /** @@ -94,14 +104,14 @@ export interface App { * @example * ```javascript * // The default app's name is "[DEFAULT]" - * admin.initializeApp(defaultAppConfig); + * initializeApp(defaultAppConfig); * console.log(admin.app().name); // "[DEFAULT]" * ``` * * @example * ```javascript * // A named app's name is what you provide to initializeApp() - * var otherApp = admin.initializeApp(otherAppConfig, "other"); + * const otherApp = initializeApp(otherAppConfig, "other"); * console.log(otherApp.name); // "other" * ``` */ @@ -116,7 +126,7 @@ export interface App { * * @example * ```javascript - * var app = admin.initializeApp(config); + * const app = initializeApp(config); * console.log(app.options.credential === config.credential); // true * console.log(app.options.databaseURL === config.databaseURL); // true * ``` diff --git a/src/app/credential-factory.ts b/src/app/credential-factory.ts index a0fdcc37e1..96f92821f8 100644 --- a/src/app/credential-factory.ts +++ b/src/app/credential-factory.ts @@ -26,6 +26,40 @@ let globalAppDefaultCred: Credential | undefined; const globalCertCreds: { [key: string]: ServiceAccountCredential } = {}; const globalRefreshTokenCreds: { [key: string]: RefreshTokenCredential } = {}; +/** + * Returns a credential created from the + * {@link + * https://developers.google.com/identity/protocols/application-default-credentials + * Google Application Default Credentials} + * that grants admin access to Firebase services. This credential can be used + * in the call to + * {@link + * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp + * `admin.initializeApp()`}. + * + * Google Application Default Credentials are available on any Google + * infrastructure, such as Google App Engine and Google Compute Engine. + * + * See + * {@link + * https://firebase.google.com/docs/admin/setup#initialize_the_sdk + * Initialize the SDK} + * for more details. + * + * @example + * ```javascript + * initializeApp({ + * credential: applicationDefault(), + * databaseURL: "https://.firebaseio.com" + * }); + * ``` + * + * @param httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * to be used when retrieving access tokens from Google token servers. + * + * @return A credential authenticated via Google + * Application Default Credentials that can be used to initialize an app. + */ export function applicationDefault(httpAgent?: Agent): Credential { if (typeof globalAppDefaultCred === 'undefined') { globalAppDefaultCred = getApplicationDefault(httpAgent); @@ -33,6 +67,51 @@ export function applicationDefault(httpAgent?: Agent): Credential { return globalAppDefaultCred; } +/** + * Returns a credential created from the provided service account that grants + * admin access to Firebase services. This credential can be used in the call + * to + * {@link + * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp + * `admin.initializeApp()`}. + * + * See + * {@link + * https://firebase.google.com/docs/admin/setup#initialize_the_sdk + * Initialize the SDK} + * for more details. + * + * @example + * ```javascript + * // Providing a path to a service account key JSON file + * const serviceAccount = require("path/to/serviceAccountKey.json"); + * initializeApp({ + * credential: cert(serviceAccount), + * databaseURL: "https://.firebaseio.com" + * }); + * ``` + * + * @example + * ```javascript + * // Providing a service account object inline + * initializeApp({ + * credential: cert({ + * projectId: "", + * clientEmail: "foo@.iam.gserviceaccount.com", + * privateKey: "-----BEGIN PRIVATE KEY----------END PRIVATE KEY-----\n" + * }), + * databaseURL: "https://.firebaseio.com" + * }); + * ``` + * + * @param serviceAccountPathOrObject The path to a service + * account key JSON file or an object representing a service account key. + * @param httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * to be used when retrieving access tokens from Google token servers. + * + * @return A credential authenticated via the + * provided service account that can be used to initialize an app. + */ export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential { const stringifiedServiceAccount = JSON.stringify(serviceAccountPathOrObject); if (!(stringifiedServiceAccount in globalCertCreds)) { @@ -42,6 +121,39 @@ export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAg return globalCertCreds[stringifiedServiceAccount]; } +/** + * Returns a credential created from the provided refresh token that grants + * admin access to Firebase services. This credential can be used in the call + * to + * {@link + * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp + * `admin.initializeApp()`}. + * + * See + * {@link + * https://firebase.google.com/docs/admin/setup#initialize_the_sdk + * Initialize the SDK} + * for more details. + * + * @example + * ```javascript + * // Providing a path to a refresh token JSON file + * const refreshToken = require("path/to/refreshToken.json"); + * initializeApp({ + * credential: refreshToken(refreshToken), + * databaseURL: "https://.firebaseio.com" + * }); + * ``` + * + * @param refreshTokenPathOrObject The path to a Google + * OAuth2 refresh token JSON file or an object representing a Google OAuth2 + * refresh token. + * @param httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * to be used when retrieving access tokens from Google token servers. + * + * @return A credential authenticated via the + * provided service account that can be used to initialize an app. + */ export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential { const stringifiedRefreshToken = JSON.stringify(refreshTokenPathOrObject); if (!(stringifiedRefreshToken in globalRefreshTokenCreds)) { diff --git a/src/app/credential.ts b/src/app/credential.ts index ef4a020494..82e3e1ccc1 100644 --- a/src/app/credential.ts +++ b/src/app/credential.ts @@ -29,6 +29,14 @@ export interface GoogleOAuthAccessToken { expires_in: number; } +/** + * Interface that provides Google OAuth2 access tokens used to authenticate + * with Firebase services. + * + * In most cases, you will not need to implement this yourself and can instead + * use the default implementations provided by + * {@link credential `admin.credential`}. + */ export interface Credential { /** * Returns a Google OAuth2 access token object used to authenticate with diff --git a/src/app/lifecycle.ts b/src/app/lifecycle.ts index 8fc03b6119..ed999e6650 100644 --- a/src/app/lifecycle.ts +++ b/src/app/lifecycle.ts @@ -41,6 +41,23 @@ export function getApps(): App[] { return defaultNamespace.apps; } +/** + * Renders this given `App` unusable and frees the resources of + * all associated services (though it does *not* clean up any backend + * resources). When running the SDK locally, this method + * must be called to ensure graceful termination of the process. + * + * @example + * ```javascript + * deleteApp(app) + * .then(function() { + * console.log("App deleted successfully"); + * }) + * .catch(function(error) { + * console.log("Error deleting app:", error); + * }); + * ``` + */ export function deleteApp(app: App): Promise { if (typeof app !== 'object' || app === null || !('options' in app)) { throw new FirebaseAppError(AppErrorCodes.INVALID_ARGUMENT, 'Invalid app argument.'); diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index 6f40351e7c..ef7a56b997 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -20,6 +20,36 @@ import { InstanceId } from './instance-id'; export { InstanceId }; +/** + * Gets the {@link InstanceId `InstanceId`} service for the + * default app or a given app. + * + * `getInstanceId()` can be called with no arguments to access the default + * app's {@link InstanceId `InstanceId`} service or as + * `getInstanceId(app)` to access the + * {@link InstanceId `InstanceId`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the Instance ID service for the default app + * const defaultInstanceId = getInstanceId(); + * ``` + * + * @example + * ```javascript + * // Get the Instance ID service for a given app + * const otherInstanceId = getInstanceId(otherApp); + *``` + * + * @param app Optional app whose `InstanceId` service to + * return. If not provided, the default `InstanceId` service will be + * returned. + * + * @return The default `InstanceId` service if + * no app is provided or the `InstanceId` service associated with the + * provided app. + */ export function getInstanceId(app?: App): InstanceId { if (typeof app === 'undefined') { app = getApp(); diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index 1af8bdb151..0e7bd0a66a 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -20,18 +20,8 @@ import { FirebaseInstanceIdRequestHandler } from './instance-id-request-internal import * as validator from '../utils/validator'; /** - * Gets the {@link InstanceId `InstanceId`} service for the - * current app. - * - * @example - * ```javascript - * var instanceId = app.instanceId(); - * // The above is shorthand for: - * // var instanceId = admin.instanceId(app); - * ``` - * - * @return The `InstanceId` service for the - * current app. + * The `InstanceId` service enables deleting the Firebase instance IDs + * associated with Firebase client app instances. */ export class InstanceId { From 93a76990141bbd10e2ef1d8366404ce946526535 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 17 Feb 2021 15:57:39 -0800 Subject: [PATCH 18/41] fix(auth): Updated API docs for firebase-admin/auth (#1170) --- etc/firebase-admin.auth.api.md | 57 +--- src/auth/auth-config.ts | 11 +- src/auth/auth-namespace.ts | 21 ++ src/auth/auth.ts | 4 +- src/auth/base-auth.ts | 536 ++++++++++++++++++++++++--------- src/auth/identifier.ts | 8 +- src/auth/tenant-manager.ts | 126 ++++---- src/auth/tenant.ts | 49 ++- src/auth/token-verifier.ts | 4 + src/auth/user-record.ts | 203 +++++++++++-- 10 files changed, 730 insertions(+), 289 deletions(-) diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 7e10a8a3f1..820a30cda7 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -155,14 +155,13 @@ export interface AuthProviderConfigFilter { } // @public -export class BaseAuth { +export abstract class BaseAuth { createCustomToken(uid: string, developerClaims?: object): Promise; createProviderConfig(config: AuthProviderConfig): Promise; createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; createUser(properties: CreateRequest): Promise; deleteProviderConfig(providerId: string): Promise; deleteUser(uid: string): Promise; - // (undocumented) deleteUsers(uids: string[]): Promise; generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise; @@ -205,7 +204,6 @@ export type CreateTenantRequest = UpdateTenantRequest; // @public export interface DecodedIdToken { - // (undocumented) [key: string]: any; aud: string; auth_time: number; @@ -250,7 +248,7 @@ export interface EmailSignInProviderConfig { passwordRequired?: boolean; } -// @public (undocumented) +// @public export function getAuth(app?: App): Auth; // @public @@ -280,7 +278,7 @@ export interface ListUsersResult { users: UserRecord[]; } -// @public (undocumented) +// @public export interface MultiFactorConfig { factorIds?: AuthFactorType[]; state: MultiFactorConfigState; @@ -296,20 +294,15 @@ export interface MultiFactorCreateSettings { // @public export abstract class MultiFactorInfo { - // (undocumented) readonly displayName?: string; - // (undocumented) readonly enrollmentTime?: string; - // (undocumented) readonly factorId: string; - toJSON(): any; - // (undocumented) + toJSON(): object; readonly uid: string; } // @public export class MultiFactorSettings { - // (undocumented) enrolledFactors: MultiFactorInfo[]; toJSON(): any; } @@ -341,9 +334,8 @@ export interface PhoneIdentifier { // @public export class PhoneMultiFactorInfo extends MultiFactorInfo { - // (undocumented) readonly phoneNumber: string; - toJSON(): any; + toJSON(): object; } // @public @@ -381,15 +373,10 @@ export interface SessionCookieOptions { // @public export class Tenant { - // (undocumented) readonly displayName?: string; - // (undocumented) get emailSignInConfig(): EmailSignInProviderConfig | undefined; - // (undocumented) get multiFactorConfig(): MultiFactorConfig | undefined; - // (undocumented) readonly tenantId: string; - // (undocumented) readonly testPhoneNumbers?: { [phoneNumber: string]: string; }; @@ -399,7 +386,6 @@ export class Tenant { // @public export class TenantAwareAuth extends BaseAuth { createSessionCookie(idToken: string, sessionCookieOptions: SessionCookieOptions): Promise; - // (undocumented) readonly tenantId: string; verifyIdToken(idToken: string, checkRevoked?: boolean): Promise; verifySessionCookie(sessionCookie: string, checkRevoked?: boolean): Promise; @@ -505,27 +491,19 @@ export interface UserImportResult { // @public export class UserInfo { - // (undocumented) readonly displayName: string; - // (undocumented) readonly email: string; - // (undocumented) readonly phoneNumber: string; - // (undocumented) readonly photoURL: string; - // (undocumented) readonly providerId: string; toJSON(): object; - // (undocumented) readonly uid: string; } // @public export class UserMetadata { - // (undocumented) readonly creationTime: string; readonly lastRefreshTime: string | null; - // (undocumented) readonly lastSignInTime: string; toJSON(): object; } @@ -548,38 +526,23 @@ export interface UserProviderRequest { // @public export class UserRecord { - // (undocumented) - readonly customClaims: { + readonly customClaims?: { [key: string]: any; }; - // (undocumented) readonly disabled: boolean; - // (undocumented) - readonly displayName: string; - // (undocumented) - readonly email: string; - // (undocumented) + readonly displayName?: string; + readonly email?: string; readonly emailVerified: boolean; - // (undocumented) readonly metadata: UserMetadata; - // (undocumented) readonly multiFactor?: MultiFactorSettings; - // (undocumented) readonly passwordHash?: string; - // (undocumented) readonly passwordSalt?: string; - // (undocumented) - readonly phoneNumber: string; - // (undocumented) - readonly photoURL: string; - // (undocumented) + readonly phoneNumber?: string; + readonly photoURL?: string; readonly providerData: UserInfo[]; - // (undocumented) readonly tenantId?: string | null; toJSON(): object; - // (undocumented) readonly tokensValidAfterTime?: string; - // (undocumented) readonly uid: string; } diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 1449d4191f..be6f2ccb6c 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -406,6 +406,11 @@ export type AuthFactorType = 'phone'; */ export type MultiFactorConfigState = 'ENABLED' | 'DISABLED'; +/** + * Interface representing a multi-factor configuration. + * This can be used to define whether multi-factor authentication is enabled + * or disabled and the list of second factor challenges that are supported. + */ export interface MultiFactorConfig { /** * The multi-factor config state. @@ -584,7 +589,7 @@ export function validateTestPhoneNumbers( } /** - * The email sign in configuration. + * The email sign in provider configuration. */ export interface EmailSignInProviderConfig { /** @@ -1010,7 +1015,7 @@ export class SAMLConfig implements SAMLAuthProviderConfig { /** * The SAMLConfig constructor. * - * @param {any} response The server side response used to initialize the SAMLConfig object. + * @param response The server side response used to initialize the SAMLConfig object. * @constructor */ constructor(response: SAMLConfigServerResponse) { @@ -1211,7 +1216,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { /** * The OIDCConfig constructor. * - * @param {any} response The server side response used to initialize the OIDCConfig object. + * @param response The server side response used to initialize the OIDCConfig object. * @constructor */ constructor(response: OIDCConfigServerResponse) { diff --git a/src/auth/auth-namespace.ts b/src/auth/auth-namespace.ts index d7830bd046..7924bcf479 100644 --- a/src/auth/auth-namespace.ts +++ b/src/auth/auth-namespace.ts @@ -95,6 +95,27 @@ import { UserRecord as TUserRecord, } from './user-record'; +/** + * Gets the {@link auth.Auth `Auth`} service for the default app or a + * given app. + * + * `getAuth()` can be called with no arguments to access the default app's + * {@link auth.Auth `Auth`} service or as `getAuth(app)` to access the + * {@link auth.Auth `Auth`} service associated with a specific app. + * + * @example + * ```javascript + * // Get the Auth service for the default app + * const defaultAuth = getAuth(); + * ``` + * + * @example + * ```javascript + * // Get the Auth service for a given app + * const otherAuth = getAuth(otherApp); + * ``` + * + */ export function getAuth(app?: App): Auth { if (typeof app === 'undefined') { app = getApp(); diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 0378466b76..132631970f 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -49,7 +49,9 @@ export class Auth extends BaseAuth { return this.app_; } - /** @return The current Auth instance's tenant manager. */ + /** + * @return The tenant manager instance associated with the current project. + */ public tenantManager(): TenantManager { return this.tenantManager_; } diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts index 502e4b2f1b..fd6dcd39c2 100644 --- a/src/auth/base-auth.ts +++ b/src/auth/base-auth.ts @@ -109,9 +109,9 @@ export interface SessionCookieOptions { } /** - * Base Auth class. Mainly used for user management APIs. + * Common parent interface for both `Auth` and `TenantAwareAuth` APIs. */ -export class BaseAuth { +export abstract class BaseAuth { /** @internal */ protected readonly tokenGenerator: FirebaseTokenGenerator; @@ -147,29 +147,44 @@ export class BaseAuth { } /** - * Creates a new custom token that can be sent back to a client to use with - * signInWithCustomToken(). + * Creates a new Firebase custom token (JWT) that can be sent back to a client + * device to use to sign in with the client SDKs' `signInWithCustomToken()` + * methods. (Tenant-aware instances will also embed the tenant ID in the + * token.) * - * @param {string} uid The uid to use as the JWT subject. - * @param {object=} developerClaims Optional additional claims to include in the JWT payload. + * See [Create Custom Tokens](/docs/auth/admin/create-custom-tokens) for code + * samples and detailed documentation. * - * @return {Promise} A JWT for the provided payload. + * @param uid The `uid` to use as the custom token's subject. + * @param developerClaims Optional additional claims to include + * in the custom token's payload. + * + * @return A promise fulfilled with a custom token for the + * provided `uid` and payload. */ public createCustomToken(uid: string, developerClaims?: object): Promise { return this.tokenGenerator.createCustomToken(uid, developerClaims); } /** - * Verifies a JWT auth token. Returns a Promise with the tokens claims. Rejects - * the promise if the token could not be verified. If checkRevoked is set to true, - * verifies if the session corresponding to the ID token was revoked. If the corresponding - * user's session was invalidated, an auth/id-token-revoked error is thrown. If not specified - * the check is not applied. + * Verifies a Firebase ID token (JWT). If the token is valid, the promise is + * fulfilled with the token's decoded claims; otherwise, the promise is + * rejected. + * An optional flag can be passed to additionally check whether the ID token + * was revoked. + * + * See [Verify ID Tokens](/docs/auth/admin/verify-id-tokens) for code samples + * and detailed documentation. + * + * @param idToken The ID token to verify. + * @param checkRevoked Whether to check if the ID token was revoked. + * This requires an extra request to the Firebase Auth backend to check + * the `tokensValidAfterTime` time for the corresponding user. + * When not specified, this additional check is not applied. * - * @param {string} idToken The JWT to verify. - * @param {boolean=} checkRevoked Whether to check if the ID token is revoked. - * @return {Promise} A Promise that will be fulfilled after a successful - * verification. + * @return A promise fulfilled with the + * token's decoded claims if the ID token is valid; otherwise, a rejected + * promise. */ public verifyIdToken(idToken: string, checkRevoked = false): Promise { return this.idTokenVerifier.verifyJWT(idToken) @@ -185,11 +200,15 @@ export class BaseAuth { } /** - * Looks up the user identified by the provided user id and returns a promise that is - * fulfilled with a user record for the given user if that user is found. + * Gets the user data for the user corresponding to a given `uid`. + * + * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) + * for code samples and detailed documentation. + * + * @param uid The `uid` corresponding to the user whose data to fetch. * - * @param {string} uid The uid of the user to look up. - * @return {Promise} A promise that resolves with the corresponding user record. + * @return A promise fulfilled with the user + * data corresponding to the provided `uid`. */ public getUser(uid: string): Promise { return this.authRequestHandler.getAccountInfoByUid(uid) @@ -200,11 +219,16 @@ export class BaseAuth { } /** - * Looks up the user identified by the provided email and returns a promise that is - * fulfilled with a user record for the given user if that user is found. + * Gets the user data for the user corresponding to a given email. * - * @param {string} email The email of the user to look up. - * @return {Promise} A promise that resolves with the corresponding user record. + * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) + * for code samples and detailed documentation. + * + * @param email The email corresponding to the user whose data to + * fetch. + * + * @return A promise fulfilled with the user + * data corresponding to the provided email. */ public getUserByEmail(email: string): Promise { return this.authRequestHandler.getAccountInfoByEmail(email) @@ -215,11 +239,17 @@ export class BaseAuth { } /** - * Looks up the user identified by the provided phone number and returns a promise that is - * fulfilled with a user record for the given user if that user is found. + * Gets the user data for the user corresponding to a given phone number. The + * phone number has to conform to the E.164 specification. + * + * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) + * for code samples and detailed documentation. * - * @param {string} phoneNumber The phone number of the user to look up. - * @return {Promise} A promise that resolves with the corresponding user record. + * @param phoneNumber The phone number corresponding to the user whose + * data to fetch. + * + * @return A promise fulfilled with the user + * data corresponding to the provided phone number. */ public getUserByPhoneNumber(phoneNumber: string): Promise { return this.authRequestHandler.getAccountInfoByPhoneNumber(phoneNumber) @@ -236,10 +266,10 @@ export class BaseAuth { * guaranteed to correspond to the nth entry in the input parameters list. * * Only a maximum of 100 identifiers may be supplied. If more than 100 identifiers are supplied, - * this method will immediately throw a FirebaseAuthError. + * this method throws a FirebaseAuthError. * - * @param identifiers The identifiers used to indicate which user records should be returned. Must - * have <= 100 entries. + * @param identifiers The identifiers used to indicate which user records should be returned. + * Must have <= 100 entries. * @return {Promise} A promise that resolves to the corresponding user records. * @throws FirebaseAuthError If any of the identifiers are invalid or if more than 100 * identifiers are specified. @@ -285,16 +315,19 @@ export class BaseAuth { } /** - * Exports a batch of user accounts. Batch size is determined by the maxResults argument. - * Starting point of the batch is determined by the pageToken argument. + * Retrieves a list of users (single batch only) with a size of `maxResults` + * starting from the offset as specified by `pageToken`. This is used to + * retrieve all the users of a specified project in batches. + * + * See [List all users](/docs/auth/admin/manage-users#list_all_users) + * for code samples and detailed documentation. * - * @param {number=} maxResults The page size, 1000 if undefined. This is also the maximum - * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns users starting - * without any offset. - * @return {Promise<{users: UserRecord[], pageToken?: string}>} A promise that resolves with - * the current batch of downloaded users and the next page token. For the last page, an - * empty list of users and no page token are returned. + * @param maxResults The page size, 1000 if undefined. This is also + * the maximum allowed limit. + * @param pageToken The next page token. If not specified, returns + * users starting without any offset. + * @return A promise that resolves with + * the current batch of downloaded users and the next page token. */ public listUsers(maxResults?: number, pageToken?: string): Promise { return this.authRequestHandler.downloadAccount(maxResults, pageToken) @@ -319,10 +352,16 @@ export class BaseAuth { } /** - * Creates a new user with the properties provided. + * Creates a new user. * - * @param {CreateRequest} properties The properties to set on the new user record to be created. - * @return {Promise} A promise that resolves with the newly created user record. + * See [Create a user](/docs/auth/admin/manage-users#create_a_user) for code + * samples and detailed documentation. + * + * @param properties The properties to set on the + * new user record to be created. + * + * @return A promise fulfilled with the user + * data corresponding to the newly created user. */ public createUser(properties: CreateRequest): Promise { return this.authRequestHandler.createNewAccount(properties) @@ -342,11 +381,15 @@ export class BaseAuth { } /** - * Deletes the user identified by the provided user id and returns a promise that is - * fulfilled when the user is found and successfully deleted. + * Deletes an existing user. + * + * See [Delete a user](/docs/auth/admin/manage-users#delete_a_user) for code + * samples and detailed documentation. * - * @param {string} uid The uid of the user to delete. - * @return {Promise} A promise that resolves when the user is successfully deleted. + * @param uid The `uid` corresponding to the user to delete. + * + * @return An empty promise fulfilled once the user has been + * deleted. */ public deleteUser(uid: string): Promise { return this.authRequestHandler.deleteAccount(uid) @@ -355,6 +398,28 @@ export class BaseAuth { }); } + /** + * Deletes the users specified by the given uids. + * + * Deleting a non-existing user won't generate an error (i.e. this method + * is idempotent.) Non-existing users are considered to be successfully + * deleted, and are therefore counted in the + * `DeleteUsersResult.successCount` value. + * + * Only a maximum of 1000 identifiers may be supplied. If more than 1000 + * identifiers are supplied, this method throws a FirebaseAuthError. + * + * This API is currently rate limited at the server to 1 QPS. If you exceed + * this, you may get a quota exceeded error. Therefore, if you want to + * delete more than 1000 users, you may need to add a delay to ensure you + * don't go over this limit. + * + * @param uids The `uids` corresponding to the users to delete. + * + * @return A Promise that resolves to the total number of successful/failed + * deletions, as well as the array of errors that corresponds to the + * failed deletions. + */ public deleteUsers(uids: string[]): Promise { if (!validator.isArray(uids)) { throw new FirebaseAuthError( @@ -400,11 +465,17 @@ export class BaseAuth { } /** - * Updates an existing user with the properties provided. + * Updates an existing user. + * + * See [Update a user](/docs/auth/admin/manage-users#update_a_user) for code + * samples and detailed documentation. + * + * @param uid The `uid` corresponding to the user to update. + * @param properties The properties to update on + * the provided user. * - * @param {string} uid The uid identifier of the user to update. - * @param {UpdateRequest} properties The properties to update on the existing user. - * @return {Promise} A promise that resolves with the modified user record. + * @return A promise fulfilled with the + * updated user data. */ public updateUser(uid: string, properties: UpdateRequest): Promise { return this.authRequestHandler.updateExistingAccount(uid, properties) @@ -415,12 +486,27 @@ export class BaseAuth { } /** - * Sets additional developer claims on an existing user identified by the provided UID. + * Sets additional developer claims on an existing user identified by the + * provided `uid`, typically used to define user roles and levels of + * access. These claims should propagate to all devices where the user is + * already signed in (after token expiration or when token refresh is forced) + * and the next time the user signs in. If a reserved OIDC claim name + * is used (sub, iat, iss, etc), an error is thrown. They are set on the + * authenticated user's ID token JWT. * - * @param {string} uid The user to edit. - * @param {object} customUserClaims The developer claims to set. - * @return {Promise} A promise that resolves when the operation completes - * successfully. + * See + * [Defining user roles and access levels](/docs/auth/admin/custom-claims) + * for code samples and detailed documentation. + * + * @param uid The `uid` of the user to edit. + * @param customUserClaims The developer claims to set. If null is + * passed, existing custom claims are deleted. Passing a custom claims payload + * larger than 1000 bytes will throw an error. Custom claims are added to the + * user's ID token which is transmitted on every authenticated request. + * For profile non-access related user attributes, use database or other + * separate storage systems. + * @return A promise that resolves when the operation completes + * successfully. */ public setCustomUserClaims(uid: string, customUserClaims: object | null): Promise { return this.authRequestHandler.setCustomUserClaims(uid, customUserClaims) @@ -430,14 +516,25 @@ export class BaseAuth { } /** - * Revokes all refresh tokens for the specified user identified by the provided UID. - * In addition to revoking all refresh tokens for a user, all ID tokens issued before - * revocation will also be revoked on the Auth backend. Any request with an ID token - * generated before revocation will be rejected with a token expired error. + * Revokes all refresh tokens for an existing user. + * + * This API will update the user's + * {@link auth.UserRecord.tokensValidAfterTime `tokensValidAfterTime`} to + * the current UTC. It is important that the server on which this is called has + * its clock set correctly and synchronized. * - * @param {string} uid The user whose tokens are to be revoked. - * @return {Promise} A promise that resolves when the operation completes - * successfully. + * While this will revoke all sessions for a specified user and disable any + * new ID tokens for existing sessions from getting minted, existing ID tokens + * may remain active until their natural expiration (one hour). To verify that + * ID tokens are revoked, use + * {@link auth.Auth.verifyIdToken `verifyIdToken(idToken, true)`} + * where `checkRevoked` is set to true. + * + * @param uid The `uid` corresponding to the user whose refresh tokens + * are to be revoked. + * + * @return An empty promise fulfilled once the user's refresh + * tokens have been revoked. */ public revokeRefreshTokens(uid: string): Promise { return this.authRequestHandler.revokeRefreshTokens(uid) @@ -447,33 +544,43 @@ export class BaseAuth { } /** - * Imports the list of users provided to Firebase Auth. This is useful when - * migrating from an external authentication system without having to use the Firebase CLI SDK. - * At most, 1000 users are allowed to be imported one at a time. - * When importing a list of password users, UserImportOptions are required to be specified. + * Imports the provided list of users into Firebase Auth. + * A maximum of 1000 users are allowed to be imported one at a time. + * When importing users with passwords, + * {@link auth.UserImportOptions `UserImportOptions`} are required to be + * specified. + * This operation is optimized for bulk imports and will ignore checks on `uid`, + * `email` and other identifier uniqueness which could result in duplications. * - * @param {UserImportRecord[]} users The list of user records to import to Firebase Auth. - * @param {UserImportOptions=} options The user import options, required when the users provided - * include password credentials. - * @return {Promise} A promise that resolves when the operation completes - * with the result of the import. This includes the number of successful imports, the number - * of failed uploads and their corresponding errors. - */ + * @param users The list of user records to import to Firebase Auth. + * @param options The user import options, required when the users provided include + * password credentials. + * @return A promise that resolves when + * the operation completes with the result of the import. This includes the + * number of successful imports, the number of failed imports and their + * corresponding errors. + */ public importUsers( users: UserImportRecord[], options?: UserImportOptions): Promise { return this.authRequestHandler.uploadAccount(users, options); } /** - * Creates a new Firebase session cookie with the specified options that can be used for - * session management (set as a server side session cookie with custom cookie policy). - * The session cookie JWT will have the same payload claims as the provided ID token. + * Creates a new Firebase session cookie with the specified options. The created + * JWT string can be set as a server-side session cookie with a custom cookie + * policy, and be used for session management. The session cookie JWT will have + * the same payload claims as the provided ID token. * - * @param {string} idToken The Firebase ID token to exchange for a session cookie. - * @param {SessionCookieOptions} sessionCookieOptions The session cookie options which includes - * custom session duration. + * See [Manage Session Cookies](/docs/auth/admin/manage-cookies) for code + * samples and detailed documentation. * - * @return {Promise} A promise that resolves on success with the created session cookie. + * @param idToken The Firebase ID token to exchange for a session + * cookie. + * @param sessionCookieOptions The session + * cookie options which includes custom session duration. + * + * @return A promise that resolves on success with the + * created session cookie. */ public createSessionCookie( idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { @@ -487,16 +594,25 @@ export class BaseAuth { } /** - * Verifies a Firebase session cookie. Returns a Promise with the tokens claims. Rejects - * the promise if the token could not be verified. If checkRevoked is set to true, - * verifies if the session corresponding to the session cookie was revoked. If the corresponding - * user's session was invalidated, an auth/session-cookie-revoked error is thrown. If not - * specified the check is not performed. + * Verifies a Firebase session cookie. Returns a Promise with the cookie claims. + * Rejects the promise if the cookie could not be verified. If `checkRevoked` is + * set to true, verifies if the session corresponding to the session cookie was + * revoked. If the corresponding user's session was revoked, an + * `auth/session-cookie-revoked` error is thrown. If not specified the check is + * not performed. + * + * See [Verify Session Cookies](/docs/auth/admin/manage-cookies#verify_session_cookie_and_check_permissions) + * for code samples and detailed documentation * - * @param {string} sessionCookie The session cookie to verify. - * @param {boolean=} checkRevoked Whether to check if the session cookie is revoked. - * @return {Promise} A Promise that will be fulfilled after a successful - * verification. + * @param sessionCookie The session cookie to verify. + * @param checkForRevocation Whether to check if the session cookie was + * revoked. This requires an extra request to the Firebase Auth backend to + * check the `tokensValidAfterTime` time for the corresponding user. + * When not specified, this additional check is not performed. + * + * @return A promise fulfilled with the + * session cookie's decoded claims if the session cookie is valid; otherwise, + * a rejected promise. */ public verifySessionCookie( sessionCookie: string, checkRevoked = false): Promise { @@ -513,57 +629,175 @@ export class BaseAuth { } /** - * Generates the out of band email action link for password reset flows for the - * email specified using the action code settings provided. - * Returns a promise that resolves with the generated link. + * Generates the out of band email action link to reset a user's password. + * The link is generated for the user with the specified email address. The + * optional {@link auth.ActionCodeSettings `ActionCodeSettings`} object + * defines whether the link is to be handled by a mobile app or browser and the + * additional state information to be passed in the deep link, etc. + * + * @example + * ```javascript + * var actionCodeSettings = { + * url: 'https://www.example.com/?email=user@example.com', + * iOS: { + * bundleId: 'com.example.ios' + * }, + * android: { + * packageName: 'com.example.android', + * installApp: true, + * minimumVersion: '12' + * }, + * handleCodeInApp: true, + * dynamicLinkDomain: 'custom.page.link' + * }; + * admin.auth() + * .generatePasswordResetLink('user@example.com', actionCodeSettings) + * .then(function(link) { + * // The link was successfully generated. + * }) + * .catch(function(error) { + * // Some error occurred, you can inspect the code: error.code + * }); + * ``` * - * @param {string} email The email of the user whose password is to be reset. - * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether - * the link is to be handled by a mobile app and the additional state information to be passed in the - * deep link, etc. - * @return {Promise} A promise that resolves with the password reset link. + * @param email The email address of the user whose password is to be + * reset. + * @param actionCodeSettings The action + * code settings. If specified, the state/continue URL is set as the + * "continueUrl" parameter in the password reset link. The default password + * reset landing page will use this to display a link to go back to the app + * if it is installed. + * If the actionCodeSettings is not specified, no URL is appended to the + * action URL. + * The state URL provided must belong to a domain that is whitelisted by the + * developer in the console. Otherwise an error is thrown. + * Mobile app redirects are only applicable if the developer configures + * and accepts the Firebase Dynamic Links terms of service. + * The Android package name and iOS bundle ID are respected only if they + * are configured in the same Firebase Auth project. + * @return A promise that resolves with the generated link. */ public generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { return this.authRequestHandler.getEmailActionLink('PASSWORD_RESET', email, actionCodeSettings); } /** - * Generates the out of band email action link for email verification flows for the - * email specified using the action code settings provided. - * Returns a promise that resolves with the generated link. + * Generates the out of band email action link to verify the user's ownership + * of the specified email. The + * {@link auth.ActionCodeSettings `ActionCodeSettings`} object provided + * as an argument to this method defines whether the link is to be handled by a + * mobile app or browser along with additional state information to be passed in + * the deep link, etc. + * + * @example + * ```javascript + * var actionCodeSettings = { + * url: 'https://www.example.com/cart?email=user@example.com&cartId=123', + * iOS: { + * bundleId: 'com.example.ios' + * }, + * android: { + * packageName: 'com.example.android', + * installApp: true, + * minimumVersion: '12' + * }, + * handleCodeInApp: true, + * dynamicLinkDomain: 'custom.page.link' + * }; + * admin.auth() + * .generateEmailVerificationLink('user@example.com', actionCodeSettings) + * .then(function(link) { + * // The link was successfully generated. + * }) + * .catch(function(error) { + * // Some error occurred, you can inspect the code: error.code + * }); + * ``` * - * @param {string} email The email of the user to be verified. - * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether - * the link is to be handled by a mobile app and the additional state information to be passed in the - * deep link, etc. - * @return {Promise} A promise that resolves with the email verification link. + * @param email The email account to verify. + * @param actionCodeSettings The action + * code settings. If specified, the state/continue URL is set as the + * "continueUrl" parameter in the email verification link. The default email + * verification landing page will use this to display a link to go back to + * the app if it is installed. + * If the actionCodeSettings is not specified, no URL is appended to the + * action URL. + * The state URL provided must belong to a domain that is whitelisted by the + * developer in the console. Otherwise an error is thrown. + * Mobile app redirects are only applicable if the developer configures + * and accepts the Firebase Dynamic Links terms of service. + * The Android package name and iOS bundle ID are respected only if they + * are configured in the same Firebase Auth project. + * @return A promise that resolves with the generated link. */ public generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { return this.authRequestHandler.getEmailActionLink('VERIFY_EMAIL', email, actionCodeSettings); } /** - * Generates the out of band email action link for email link sign-in flows for the - * email specified using the action code settings provided. - * Returns a promise that resolves with the generated link. + * Generates the out of band email action link to verify the user's ownership + * of the specified email. The + * {@link auth.ActionCodeSettings `ActionCodeSettings`} object provided + * as an argument to this method defines whether the link is to be handled by a + * mobile app or browser along with additional state information to be passed in + * the deep link, etc. * - * @param {string} email The email of the user signing in. - * @param {ActionCodeSettings} actionCodeSettings The required action code setings which defines whether - * the link is to be handled by a mobile app and the additional state information to be passed in the - * deep link, etc. - * @return {Promise} A promise that resolves with the email sign-in link. + * @example + * ```javascript + * var actionCodeSettings = { + * url: 'https://www.example.com/cart?email=user@example.com&cartId=123', + * iOS: { + * bundleId: 'com.example.ios' + * }, + * android: { + * packageName: 'com.example.android', + * installApp: true, + * minimumVersion: '12' + * }, + * handleCodeInApp: true, + * dynamicLinkDomain: 'custom.page.link' + * }; + * admin.auth() + * .generateEmailVerificationLink('user@example.com', actionCodeSettings) + * .then(function(link) { + * // The link was successfully generated. + * }) + * .catch(function(error) { + * // Some error occurred, you can inspect the code: error.code + * }); + * ``` + * + * @param email The email account to verify. + * @param actionCodeSettings The action + * code settings. If specified, the state/continue URL is set as the + * "continueUrl" parameter in the email verification link. The default email + * verification landing page will use this to display a link to go back to + * the app if it is installed. + * If the actionCodeSettings is not specified, no URL is appended to the + * action URL. + * The state URL provided must belong to a domain that is whitelisted by the + * developer in the console. Otherwise an error is thrown. + * Mobile app redirects are only applicable if the developer configures + * and accepts the Firebase Dynamic Links terms of service. + * The Android package name and iOS bundle ID are respected only if they + * are configured in the same Firebase Auth project. + * @return A promise that resolves with the generated link. */ public generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise { return this.authRequestHandler.getEmailActionLink('EMAIL_SIGNIN', email, actionCodeSettings); } /** - * Returns the list of existing provider configuation matching the filter provided. - * At most, 100 provider configs are allowed to be imported at a time. + * Returns the list of existing provider configurations matching the filter + * provided. At most, 100 provider configs can be listed at a time. + * + * SAML and OIDC provider support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the [GCIP documentation](https://cloud.google.com/identity-platform). * - * @param {AuthProviderConfigFilter} options The provider config filter to apply. - * @return {Promise} A promise that resolves with the list of provider configs - * meeting the filter requirements. + * @param options The provider config filter to apply. + * @return A promise that resolves with the list of provider configs meeting the + * filter requirements. */ public listProviderConfigs(options: AuthProviderConfigFilter): Promise { const processResponse = (response: any, providerConfigs: AuthProviderConfig[]): ListProviderConfigResults => { @@ -609,11 +843,19 @@ export class BaseAuth { } /** - * Looks up an Auth provider configuration by ID. - * Returns a promise that resolves with the provider configuration corresponding to the provider ID specified. + * Looks up an Auth provider configuration by the provided ID. + * Returns a promise that resolves with the provider configuration + * corresponding to the provider ID specified. If the specified ID does not + * exist, an `auth/configuration-not-found` error is thrown. * - * @param {string} providerId The provider ID corresponding to the provider config to return. - * @return {Promise} + * SAML and OIDC provider support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the [GCIP documentation](https://cloud.google.com/identity-platform). + * + * @param providerId The provider ID corresponding to the provider + * config to return. + * @return A promise that resolves + * with the configuration corresponding to the provided ID. */ public getProviderConfig(providerId: string): Promise { if (OIDCConfig.isProviderId(providerId)) { @@ -632,9 +874,16 @@ export class BaseAuth { /** * Deletes the provider configuration corresponding to the provider ID passed. + * If the specified ID does not exist, an `auth/configuration-not-found` error + * is thrown. + * + * SAML and OIDC provider support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the [GCIP documentation](https://cloud.google.com/identity-platform). * - * @param {string} providerId The provider ID corresponding to the provider config to delete. - * @return {Promise} A promise that resolves on completion. + * @param providerId The provider ID corresponding to the provider + * config to delete. + * @return A promise that resolves on completion. */ public deleteProviderConfig(providerId: string): Promise { if (OIDCConfig.isProviderId(providerId)) { @@ -646,12 +895,19 @@ export class BaseAuth { } /** - * Returns a promise that resolves with the updated AuthProviderConfig when the provider configuration corresponding - * to the provider ID specified is updated with the specified configuration. + * Returns a promise that resolves with the updated `AuthProviderConfig` + * corresponding to the provider ID specified. + * If the specified ID does not exist, an `auth/configuration-not-found` error + * is thrown. + * + * SAML and OIDC provider support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the [GCIP documentation](https://cloud.google.com/identity-platform). * - * @param {string} providerId The provider ID corresponding to the provider config to update. - * @param {UpdateAuthProviderRequest} updatedConfig The updated configuration. - * @return {Promise} A promise that resolves with the updated provider configuration. + * @param providerId The provider ID corresponding to the provider + * config to update. + * @param updatedConfig The updated configuration. + * @return A promise that resolves with the updated provider configuration. */ public updateProviderConfig( providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise { @@ -676,10 +932,15 @@ export class BaseAuth { } /** - * Returns a promise that resolves with the newly created AuthProviderConfig when the new provider configuration is - * created. - * @param {AuthProviderConfig} config The provider configuration to create. - * @return {Promise} A promise that resolves with the created provider configuration. + * Returns a promise that resolves with the newly created `AuthProviderConfig` + * when the new provider configuration is created. + * + * SAML and OIDC provider support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the [GCIP documentation](https://cloud.google.com/identity-platform). + * + * @param config The provider configuration to create. + * @return A promise that resolves with the created provider configuration. */ public createProviderConfig(config: AuthProviderConfig): Promise { if (!validator.isNonNullObject(config)) { @@ -706,11 +967,10 @@ export class BaseAuth { * Verifies the decoded Firebase issued JWT is not revoked. Returns a promise that resolves * with the decoded claims on success. Rejects the promise with revocation error if revoked. * - * @param {DecodedIdToken} decodedIdToken The JWT's decoded claims. - * @param {ErrorInfo} revocationErrorInfo The revocation error info to throw on revocation + * @param decodedIdToken The JWT's decoded claims. + * @param revocationErrorInfo The revocation error info to throw on revocation * detection. - * @return {Promise} A Promise that will be fulfilled after a successful - * verification. + * @return A Promise that will be fulfilled after a successful verification. */ private verifyDecodedJWTNotRevoked( decodedIdToken: DecodedIdToken, revocationErrorInfo: ErrorInfo): Promise { diff --git a/src/auth/identifier.ts b/src/auth/identifier.ts index 709ff81a06..8db37c36b4 100644 --- a/src/auth/identifier.ts +++ b/src/auth/identifier.ts @@ -17,7 +17,7 @@ /** * Used for looking up an account by uid. * - * See auth.getUsers() + * See `auth.getUsers()` */ export interface UidIdentifier { uid: string; @@ -26,7 +26,7 @@ export interface UidIdentifier { /** * Used for looking up an account by email. * - * See auth.getUsers() + * See `auth.getUsers()` */ export interface EmailIdentifier { email: string; @@ -35,7 +35,7 @@ export interface EmailIdentifier { /** * Used for looking up an account by phone number. * - * See auth.getUsers() + * See `auth.getUsers()` */ export interface PhoneIdentifier { phoneNumber: string; @@ -44,7 +44,7 @@ export interface PhoneIdentifier { /** * Used for looking up an account by federated provider. * - * See auth.getUsers() + * See `auth.getUsers()` */ export interface ProviderIdentifier { providerId: string; diff --git a/src/auth/tenant-manager.ts b/src/auth/tenant-manager.ts index e58f803615..2878ed907c 100644 --- a/src/auth/tenant-manager.ts +++ b/src/auth/tenant-manager.ts @@ -47,16 +47,35 @@ export interface ListTenantsResult { } /** - * The tenant aware Auth class. + * Tenant-aware `Auth` interface used for managing users, configuring SAML/OIDC providers, + * generating email links for password reset, email verification, etc for specific tenants. + * + * Multi-tenancy support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the [GCIP documentation](https://cloud.google.com/identity-platform) + * + * Each tenant contains its own identity providers, settings and sets of users. + * Using `TenantAwareAuth`, users for a specific tenant and corresponding OIDC/SAML + * configurations can also be managed, ID tokens for users signed in to a specific tenant + * can be verified, and email action links can also be generated for users belonging to the + * tenant. + * + * `TenantAwareAuth` instances for a specific `tenantId` can be instantiated by calling + * `auth.tenantManager().authForTenant(tenantId)`. */ export class TenantAwareAuth extends BaseAuth { + /** + * The tenant identifier corresponding to this `TenantAwareAuth` instance. + * All calls to the user management APIs, OIDC/SAML provider management APIs, email link + * generation APIs, etc will only be applied within the scope of this tenant. + */ public readonly tenantId: string; /** * The TenantAwareAuth class constructor. * - * @param {object} app The app that created this tenant. + * @param app The app that created this tenant. * @param tenantId The corresponding tenant ID. * @constructor * @internal @@ -69,16 +88,7 @@ export class TenantAwareAuth extends BaseAuth { } /** - * Verifies a JWT auth token. Returns a Promise with the tokens claims. Rejects - * the promise if the token could not be verified. If checkRevoked is set to true, - * verifies if the session corresponding to the ID token was revoked. If the corresponding - * user's session was invalidated, an auth/id-token-revoked error is thrown. If not specified - * the check is not applied. - * - * @param {string} idToken The JWT to verify. - * @param {boolean=} checkRevoked Whether to check if the ID token is revoked. - * @return {Promise} A Promise that will be fulfilled after a successful - * verification. + * {@inheritdoc BaseAuth.verifyIdToken} */ public verifyIdToken(idToken: string, checkRevoked = false): Promise { return super.verifyIdToken(idToken, checkRevoked) @@ -92,15 +102,7 @@ export class TenantAwareAuth extends BaseAuth { } /** - * Creates a new Firebase session cookie with the specified options that can be used for - * session management (set as a server side session cookie with custom cookie policy). - * The session cookie JWT will have the same payload claims as the provided ID token. - * - * @param {string} idToken The Firebase ID token to exchange for a session cookie. - * @param {SessionCookieOptions} sessionCookieOptions The session cookie options which includes - * custom session duration. - * - * @return {Promise} A promise that resolves on success with the created session cookie. + * {@inheritdoc BaseAuth.createSessionCookie} */ public createSessionCookie( idToken: string, sessionCookieOptions: SessionCookieOptions): Promise { @@ -120,16 +122,7 @@ export class TenantAwareAuth extends BaseAuth { } /** - * Verifies a Firebase session cookie. Returns a Promise with the tokens claims. Rejects - * the promise if the token could not be verified. If checkRevoked is set to true, - * verifies if the session corresponding to the session cookie was revoked. If the corresponding - * user's session was invalidated, an auth/session-cookie-revoked error is thrown. If not - * specified the check is not performed. - * - * @param {string} sessionCookie The session cookie to verify. - * @param {boolean=} checkRevoked Whether to check if the session cookie is revoked. - * @return {Promise} A Promise that will be fulfilled after a successful - * verification. + * {@inheritdoc BaseAuth.verifySessionCookie} */ public verifySessionCookie( sessionCookie: string, checkRevoked = false): Promise { @@ -144,11 +137,15 @@ export class TenantAwareAuth extends BaseAuth { } /** - * Data structure used to help manage tenant related operations. + * Defines the tenant manager used to help manage tenant related operations. * This includes: - * - The ability to create, update, list, get and delete tenants for the underlying project. - * - Getting a TenantAwareAuth instance for running Auth related operations (user mgmt, provider config mgmt, etc) - * in the context of a specified tenant. + *
    + *
  • The ability to create, update, list, get and delete tenants for the underlying + * project.
  • + *
  • Getting a `TenantAwareAuth` instance for running Auth related operations + * (user management, provider configuration management, token verification, + * email link generation, etc) in the context of a specified tenant.
  • + *
*/ export class TenantManager { private readonly authRequestHandler: AuthRequestHandler; @@ -168,10 +165,11 @@ export class TenantManager { } /** - * Returns a TenantAwareAuth instance for the corresponding tenant ID. + * Returns a `TenantAwareAuth` instance bound to the given tenant ID. + * + * @param tenantId The tenant ID whose `TenantAwareAuth` instance is to be returned. * - * @param tenantId The tenant ID whose TenantAwareAuth is to be returned. - * @return The corresponding TenantAwareAuth instance. + * @return The `TenantAwareAuth` instance corresponding to this tenant identifier. */ public authForTenant(tenantId: string): TenantAwareAuth { if (!validator.isNonEmptyString(tenantId)) { @@ -184,11 +182,11 @@ export class TenantManager { } /** - * Looks up the tenant identified by the provided tenant ID and returns a promise that is - * fulfilled with the corresponding tenant if it is found. + * Gets the tenant configuration for the tenant corresponding to a given `tenantId`. * - * @param tenantId The tenant ID of the tenant to look up. - * @return A promise that resolves with the corresponding tenant. + * @param tenantId The tenant identifier corresponding to the tenant whose data to fetch. + * + * @return A promise fulfilled with the tenant configuration to the provided `tenantId`. */ public getTenant(tenantId: string): Promise { return this.authRequestHandler.getTenant(tenantId) @@ -198,16 +196,17 @@ export class TenantManager { } /** - * Exports a batch of tenant accounts. Batch size is determined by the maxResults argument. - * Starting point of the batch is determined by the pageToken argument. + * Retrieves a list of tenants (single batch only) with a size of `maxResults` + * starting from the offset as specified by `pageToken`. This is used to + * retrieve all the tenants of a specified project in batches. + * + * @param maxResults The page size, 1000 if undefined. This is also + * the maximum allowed limit. + * @param pageToken The next page token. If not specified, returns + * tenants starting without any offset. * - * @param maxResults The page size, 1000 if undefined. This is also the maximum - * allowed limit. - * @param pageToken The next page token. If not specified, returns users starting - * without any offset. * @return A promise that resolves with - * the current batch of downloaded tenants and the next page token. For the last page, an - * empty list of tenants and no page token are returned. + * a batch of downloaded tenants and the next page token. */ public listTenants( maxResults?: number, @@ -234,21 +233,25 @@ export class TenantManager { } /** - * Deletes the tenant identified by the provided tenant ID and returns a promise that is - * fulfilled when the tenant is found and successfully deleted. + * Deletes an existing tenant. * - * @param tenantId The tenant ID of the tenant to delete. - * @return A promise that resolves when the tenant is successfully deleted. + * @param tenantId The `tenantId` corresponding to the tenant to delete. + * + * @return An empty promise fulfilled once the tenant has been deleted. */ public deleteTenant(tenantId: string): Promise { return this.authRequestHandler.deleteTenant(tenantId); } /** - * Creates a new tenant with the properties provided. + * Creates a new tenant. + * When creating new tenants, tenants that use separate billing and quota will require their + * own project and must be defined as `full_service`. + * + * @param tenantOptions The properties to set on the new tenant configuration to be created. * - * @param tenantOptions The properties to set on the new tenant to be created. - * @return A promise that resolves with the newly created tenant. + * @return A promise fulfilled with the tenant configuration corresponding to the newly + * created tenant. */ public createTenant(tenantOptions: CreateTenantRequest): Promise { return this.authRequestHandler.createTenant(tenantOptions) @@ -258,11 +261,12 @@ export class TenantManager { } /** - * Updates an existing tenant identified by the tenant ID with the properties provided. + * Updates an existing tenant configuration. + * + * @param tenantId The `tenantId` corresponding to the tenant to delete. + * @param tenantOptions The properties to update on the provided tenant. * - * @param tenantId The tenant identifier of the tenant to update. - * @param tenantOptions The properties to update on the existing tenant. - * @return A promise that resolves with the modified tenant. + * @return A promise fulfilled with the update tenant data. */ public updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise { return this.authRequestHandler.updateTenant(tenantId, tenantOptions) diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index 8bbc0438c8..70b9daeb95 100644 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -75,22 +75,51 @@ export interface TenantServerResponse { } /** - * Tenant class that defines a Firebase Auth tenant. + * Represents a tenant configuration. + * + * Multi-tenancy support requires Google Cloud's Identity Platform + * (GCIP). To learn more about GCIP, including pricing and features, + * see the [GCIP documentation](https://cloud.google.com/identity-platform) + * + * Before multi-tenancy can be used on a Google Cloud Identity Platform project, + * tenants must be allowed on that project via the Cloud Console UI. + * + * A tenant configuration provides information such as the display name, tenant + * identifier and email authentication configuration. + * For OIDC/SAML provider configuration management, `TenantAwareAuth` instances should + * be used instead of a `Tenant` to retrieve the list of configured IdPs on a tenant. + * When configuring these providers, note that tenants will inherit + * whitelisted domains and authenticated redirect URIs of their parent project. + * + * All other settings of a tenant will also be inherited. These will need to be managed + * from the Cloud Console UI. */ export class Tenant { + /** + * The tenant identifier. + */ public readonly tenantId: string; + + /** + * The tenant display name. + */ public readonly displayName?: string; + + /** + * The map containing the test phone number / code pairs for the tenant. + */ + public readonly testPhoneNumbers?: {[phoneNumber: string]: string}; + private readonly emailSignInConfig_?: EmailSignInConfig; private readonly multiFactorConfig_?: MultiFactorAuthConfig; - public readonly testPhoneNumbers?: {[phoneNumber: string]: string}; /** * Builds the corresponding server request for a TenantOptions object. * - * @param {TenantOptions} tenantOptions The properties to convert to a server request. - * @param {boolean} createRequest Whether this is a create request. - * @return {object} The equivalent server request. + * @param tenantOptions The properties to convert to a server request. + * @param createRequest Whether this is a create request. + * @return The equivalent server request. * * @internal */ @@ -224,15 +253,23 @@ export class Tenant { } } + /** + * The email sign in provider configuration. + */ get emailSignInConfig(): EmailSignInProviderConfig | undefined { return this.emailSignInConfig_; } + /** + * The multi-factor auth configuration on the current tenant. + */ get multiFactorConfig(): MultiFactorConfig | undefined { return this.multiFactorConfig_; } - /** @return {object} The plain object representation of the tenant. */ + /** + * @return A JSON-serializable representation of this object. + */ public toJSON(): object { const json = { tenantId: this.tenantId, diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 99c6372687..71ba252c4e 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -167,6 +167,10 @@ export interface DecodedIdToken { * convenience, and is set as the value of the [`sub`](#sub) property. */ uid: string; + + /** + * Other arbitrary claims included in the ID token. + */ [key: string]: any; } diff --git a/src/auth/user-record.ts b/src/auth/user-record.ts index 149b2f89e5..86dadc07f9 100644 --- a/src/auth/user-record.ts +++ b/src/auth/user-record.ts @@ -86,12 +86,28 @@ enum MultiFactorId { } /** - * Abstract class representing a multi-factor info interface. + * Interface representing the common properties of a user enrolled second factor. */ export abstract class MultiFactorInfo { + + /** + * The ID of the enrolled second factor. This ID is unique to the user. + */ public readonly uid: string; + + /** + * The optional display name of the enrolled second factor. + */ public readonly displayName?: string; + + /** + * The type identifier of the second factor. For SMS second factors, this is `phone`. + */ public readonly factorId: string; + + /** + * The optional date the second factor was enrolled, formatted as a UTC string. + */ public readonly enrollmentTime?: string; /** @@ -123,8 +139,10 @@ export abstract class MultiFactorInfo { this.initFromServerResponse(response); } - /** @return The plain object representation. */ - public toJSON(): any { + /** + * @return A JSON-serializable representation of this object. + */ + public toJSON(): object { return { uid: this.uid, displayName: this.displayName, @@ -172,8 +190,14 @@ export abstract class MultiFactorInfo { } } -/** Class representing a phone MultiFactorInfo object. */ +/** + * Interface representing a phone specific user enrolled second factor. + */ export class PhoneMultiFactorInfo extends MultiFactorInfo { + + /** + * The phone number associated with a phone second factor. + */ public readonly phoneNumber: string; /** @@ -188,8 +212,10 @@ export class PhoneMultiFactorInfo extends MultiFactorInfo { utils.addReadonlyGetter(this, 'phoneNumber', response.phoneInfo); } - /** @return The plain object representation. */ - public toJSON(): any { + /** + * {@inheritdoc MultiFactorInfo.toJSON} + */ + public toJSON(): object { return Object.assign( super.toJSON(), { @@ -211,8 +237,15 @@ export class PhoneMultiFactorInfo extends MultiFactorInfo { } } -/** Class representing multi-factor related properties of a user. */ +/** + * The multi-factor related user settings. + */ export class MultiFactorSettings { + + /** + * List of second factors enrolled with the current user. + * Currently only phone second factors are supported. + */ public enrolledFactors: MultiFactorInfo[]; /** @@ -241,7 +274,9 @@ export class MultiFactorSettings { this, 'enrolledFactors', Object.freeze(parsedEnrolledFactors)); } - /** @return The plain object representation. */ + /** + * @return A JSON-serializable representation of this multi-factor object. + */ public toJSON(): any { return { enrolledFactors: this.enrolledFactors.map((info) => info.toJSON()), @@ -250,17 +285,24 @@ export class MultiFactorSettings { } /** - * User metadata class that provides metadata information like user account creation - * and last sign in time. + * Represents a user's metadata. */ export class UserMetadata { + + /** + * The date the user was created, formatted as a UTC string. + */ public readonly creationTime: string; + + /** + * The date the user last signed in, formatted as a UTC string. + */ public readonly lastSignInTime: string; /** - * The time at which the user was last active (ID token refreshed), or null - * if the user was never active. Formatted as a UTC Date string (eg - * 'Sat, 03 Feb 2001 04:05:06 GMT') + * The time at which the user was last active (ID token refreshed), + * formatted as a UTC Date string (eg 'Sat, 03 Feb 2001 04:05:06 GMT'). + * Returns null if the user was never active. */ public readonly lastRefreshTime: string | null; @@ -281,7 +323,9 @@ export class UserMetadata { utils.addReadonlyGetter(this, 'lastRefreshTime', lastRefreshAt); } - /** @return The plain object representation of the user's metadata. */ + /** + * @return A JSON-serializable representation of this object. + */ public toJSON(): object { return { lastSignInTime: this.lastSignInTime, @@ -291,15 +335,39 @@ export class UserMetadata { } /** - * User info class that provides provider user information for different - * Firebase providers like google.com, facebook.com, password, etc. + * Represents a user's info from a third-party identity provider + * such as Google or Facebook. */ export class UserInfo { + + /** + * The user identifier for the linked provider. + */ public readonly uid: string; + + /** + * The display name for the linked provider. + */ public readonly displayName: string; + + /** + * The email for the linked provider. + */ public readonly email: string; + + /** + * The photo URL for the linked provider. + */ public readonly photoURL: string; + + /** + * The linked provider ID (for example, "google.com" for the Google provider). + */ public readonly providerId: string; + + /** + * The phone number for the linked provider. + */ public readonly phoneNumber: string; @@ -325,7 +393,9 @@ export class UserInfo { utils.addReadonlyGetter(this, 'phoneNumber', response.phoneNumber); } - /** @return The plain object representation of the current provider data. */ + /** + * @return A JSON-serializable representation of this object. + */ public toJSON(): object { return { uid: this.uid, @@ -339,28 +409,101 @@ export class UserInfo { } /** - * User record class that defines the Firebase user object populated from - * the Firebase Auth getAccountInfo response. - * - * @param response The server side response returned from the getAccountInfo - * endpoint. - * @constructor + * Represents a user. */ export class UserRecord { + + /** + * The user's `uid`. + */ public readonly uid: string; - public readonly email: string; + + /** + * The user's primary email, if set. + */ + public readonly email?: string; + + /** + * Whether or not the user's primary email is verified. + */ public readonly emailVerified: boolean; - public readonly displayName: string; - public readonly photoURL: string; - public readonly phoneNumber: string; + + /** + * The user's display name. + */ + public readonly displayName?: string; + + /** + * The user's photo URL. + */ + public readonly photoURL?: string; + + /** + * The user's primary phone number, if set. + */ + public readonly phoneNumber?: string; + + /** + * Whether or not the user is disabled: `true` for disabled; `false` for + * enabled. + */ public readonly disabled: boolean; + + /** + * Additional metadata about the user. + */ public readonly metadata: UserMetadata; + + /** + * An array of providers (for example, Google, Facebook) linked to the user. + */ public readonly providerData: UserInfo[]; + + /** + * The user's hashed password (base64-encoded), only if Firebase Auth hashing + * algorithm (SCRYPT) is used. If a different hashing algorithm had been used + * when uploading this user, as is typical when migrating from another Auth + * system, this will be an empty string. If no password is set, this is + * null. This is only available when the user is obtained from + * {@link auth.Auth.listUsers `listUsers()`}. + */ public readonly passwordHash?: string; + + /** + * The user's password salt (base64-encoded), only if Firebase Auth hashing + * algorithm (SCRYPT) is used. If a different hashing algorithm had been used to + * upload this user, typical when migrating from another Auth system, this will + * be an empty string. If no password is set, this is null. This is only + * available when the user is obtained from + * {@link auth.Auth.listUsers `listUsers()`}. + */ public readonly passwordSalt?: string; - public readonly customClaims: {[key: string]: any}; + + /** + * The user's custom claims object if available, typically used to define + * user roles and propagated to an authenticated user's ID token. + * This is set via + * {@link auth.Auth.setCustomUserClaims `setCustomUserClaims()`} + */ + public readonly customClaims?: {[key: string]: any}; + + /** + * The ID of the tenant the user belongs to, if available. + */ public readonly tenantId?: string | null; + + /** + * The date the user's tokens are valid after, formatted as a UTC string. + * This is updated every time the user's refresh token are revoked either + * from the {@link auth.Auth.revokeRefreshTokens `revokeRefreshTokens()`} + * API or from the Firebase Auth backend on big account changes (password + * resets, password or email updates, etc). + */ public readonly tokensValidAfterTime?: string; + + /** + * The multi-factor related properties for the current user, if available. + */ public readonly multiFactor?: MultiFactorSettings; /** @@ -420,7 +563,9 @@ export class UserRecord { } } - /** @return The plain object representation of the user record. */ + /** + * @return A JSON-serializable representation of this object. + */ public toJSON(): object { const json: any = { uid: this.uid, From 0ebb75953c91bad99b18d3645995b4ccc5047b26 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 18 Feb 2021 11:01:33 -0800 Subject: [PATCH 19/41] fix: Updated API docs of remaining modules (#1172) * fix: Updated API docs for fcm and project management APIs * fix: Updated docs for Storage and RTDB modules * fix: Updated docs for ML, RC and rules --- etc/firebase-admin.database.api.md | 8 +- etc/firebase-admin.machine-learning.api.md | 13 +-- etc/firebase-admin.messaging.api.md | 2 +- etc/firebase-admin.project-management.api.md | 6 +- etc/firebase-admin.remote-config.api.md | 2 +- etc/firebase-admin.security-rules.api.md | 10 +- etc/firebase-admin.storage.api.md | 3 +- src/database/index.ts | 65 +++++++++++++ src/machine-learning/index.ts | 29 ++++++ src/machine-learning/machine-learning.ts | 94 ++++++++++++------- src/messaging/index.ts | 29 ++++++ src/messaging/messaging.ts | 8 +- src/project-management/android-app.ts | 7 ++ src/project-management/index.ts | 28 ++++++ src/project-management/ios-app.ts | 9 +- src/remote-config/index.ts | 29 ++++++ src/remote-config/remote-config.ts | 12 +-- src/security-rules/index.ts | 28 ++++++ src/security-rules/security-rules.ts | 98 +++++++++++++------- src/storage/index.ts | 22 +++++ src/storage/storage.ts | 5 +- 21 files changed, 399 insertions(+), 108 deletions(-) diff --git a/etc/firebase-admin.database.api.md b/etc/firebase-admin.database.api.md index 0b8c5b0790..3b9fe43324 100644 --- a/etc/firebase-admin.database.api.md +++ b/etc/firebase-admin.database.api.md @@ -49,15 +49,15 @@ export namespace database { export { DataSnapshot } -// @public (undocumented) +// @public export const enableLogging: typeof rtdb.enableLogging; export { EventType } -// @public (undocumented) +// @public export function getDatabase(app?: App): Database; -// @public (undocumented) +// @public export function getDatabaseWithUrl(url: string, app?: App): Database; export { OnDisconnect } @@ -66,7 +66,7 @@ export { Query } export { Reference } -// @public (undocumented) +// @public export const ServerValue: rtdb.ServerValue; export { ThenableReference } diff --git a/etc/firebase-admin.machine-learning.api.md b/etc/firebase-admin.machine-learning.api.md index 9f1204e7e2..2560737b4a 100644 --- a/etc/firebase-admin.machine-learning.api.md +++ b/etc/firebase-admin.machine-learning.api.md @@ -24,7 +24,7 @@ export interface GcsTfliteModelOptions extends ModelOptionsBase { // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @public export function getMachineLearning(app?: App): MachineLearning; // @public @@ -79,30 +79,19 @@ export namespace machineLearning { // @public export class Model { - // (undocumented) get createTime(): string; - // (undocumented) get displayName(): string; - // (undocumented) get etag(): string; get locked(): boolean; - // (undocumented) get modelHash(): string | undefined; - // (undocumented) get modelId(): string; - // (undocumented) get published(): boolean; - // (undocumented) get tags(): string[]; - // (undocumented) get tfliteModel(): TFLiteModel | undefined; - // (undocumented) toJSON(): { [key: string]: any; }; - // (undocumented) get updateTime(): string; - // (undocumented) get validationError(): string | undefined; waitForUnlocked(maxTimeMillis?: number): Promise; } diff --git a/etc/firebase-admin.messaging.api.md b/etc/firebase-admin.messaging.api.md index 07eb7c9cab..eec7b21f45 100644 --- a/etc/firebase-admin.messaging.api.md +++ b/etc/firebase-admin.messaging.api.md @@ -165,7 +165,7 @@ export interface FcmOptions { // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @public export function getMessaging(app?: App): Messaging; // @public diff --git a/etc/firebase-admin.project-management.api.md b/etc/firebase-admin.project-management.api.md index f8d3ebcf34..eb166be66c 100644 --- a/etc/firebase-admin.project-management.api.md +++ b/etc/firebase-admin.project-management.api.md @@ -6,7 +6,7 @@ import { Agent } from 'http'; -// @public (undocumented) +// @public export class AndroidApp { addShaCertificate(certificateToAdd: ShaCertificate): Promise; // (undocumented) @@ -43,10 +43,10 @@ export enum AppPlatform { // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @public export function getProjectManagement(app?: App): ProjectManagement; -// @public (undocumented) +// @public export class IosApp { // (undocumented) readonly appId: string; diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md index eee1507fba..063c2b4167 100644 --- a/etc/firebase-admin.remote-config.api.md +++ b/etc/firebase-admin.remote-config.api.md @@ -13,7 +13,7 @@ export interface ExplicitParameterValue { // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @public export function getRemoteConfig(app?: App): RemoteConfig; // @public diff --git a/etc/firebase-admin.security-rules.api.md b/etc/firebase-admin.security-rules.api.md index 81310c26c3..bef96e2edf 100644 --- a/etc/firebase-admin.security-rules.api.md +++ b/etc/firebase-admin.security-rules.api.md @@ -8,14 +8,12 @@ import { Agent } from 'http'; // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @public export function getSecurityRules(app?: App): SecurityRules; // @public -export class Ruleset { - // (undocumented) +export class Ruleset implements RulesetMetadata { readonly createTime: string; - // (undocumented) readonly name: string; // (undocumented) readonly source: RulesFile[]; @@ -27,11 +25,9 @@ export interface RulesetMetadata { readonly name: string; } -// @public (undocumented) +// @public export class RulesetMetadataList { - // (undocumented) readonly nextPageToken?: string; - // (undocumented) readonly rulesets: RulesetMetadata[]; } diff --git a/etc/firebase-admin.storage.api.md b/etc/firebase-admin.storage.api.md index d13887d9df..f5049af101 100644 --- a/etc/firebase-admin.storage.api.md +++ b/etc/firebase-admin.storage.api.md @@ -9,13 +9,12 @@ import { Bucket } from '@google-cloud/storage'; // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @public export function getStorage(app?: App): Storage; // @public export class Storage { get app(): App; - // (undocumented) bucket(name?: string): Bucket; } diff --git a/src/database/index.ts b/src/database/index.ts index 4bc350ee05..4cc274cf49 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -35,13 +35,78 @@ export { ThenableReference, } from '@firebase/database-types'; +/** + * [`enableLogging`](https://firebase.google.com/docs/reference/js/firebase.database#enablelogging) + * function from the `@firebase/database` package. + */ export const enableLogging: typeof rtdb.enableLogging = enableLoggingFunc; + +/** + * [`ServerValue`](https://firebase.google.com/docs/reference/js/firebase.database.ServerValue) + * module from the `@firebase/database` package. + */ export const ServerValue: rtdb.ServerValue = serverValueConst; +/** + * Gets the {@link database.Database `Database`} service for the default + * app or a given app. + * + * `getDatabase()` can be called with no arguments to access the default + * app's {@link database.Database `Database`} service or as + * `getDatabase(app)` to access the + * {@link database.Database `Database`} service associated with a specific + * app. + * + * @example + * ```javascript + * // Get the Database service for the default app + * const defaultDatabase = getDatabase(); + * ``` + * + * @example + * ```javascript + * // Get the Database service for a specific app + * const otherDatabase = getDatabase(app); + * ``` + * + * @param App whose `Database` service to + * return. If not provided, the default `Database` service will be returned. + * + * @return The default `Database` service if no app + * is provided or the `Database` service associated with the provided app. + */ export function getDatabase(app?: App): Database { return getDatabaseInstance({ app }); } +/** + * Gets the {@link database.Database `Database`} service for the default + * app or a given app. + * + * `getDatabaseWithUrl()` can be called with no arguments to access the default + * app's {@link database.Database `Database`} service or as + * `getDatabaseWithUrl(app)` to access the + * {@link database.Database `Database`} service associated with a specific + * app. + * + * @example + * ```javascript + * // Get the Database service for the default app + * const defaultDatabase = getDatabaseWithUrl('https://example.firebaseio.com'); + * ``` + * + * @example + * ```javascript + * // Get the Database service for a specific app + * const otherDatabase = getDatabaseWithUrl('https://example.firebaseio.com', app); + * ``` + * + * @param App whose `Database` service to + * return. If not provided, the default `Database` service will be returned. + * + * @return The default `Database` service if no app + * is provided or the `Database` service associated with the provided app. + */ export function getDatabaseWithUrl(url: string, app?: App): Database { return getDatabaseInstance({ url, app }); } diff --git a/src/machine-learning/index.ts b/src/machine-learning/index.ts index c12da2b832..e3cdf83d65 100644 --- a/src/machine-learning/index.ts +++ b/src/machine-learning/index.ts @@ -32,6 +32,35 @@ export { ModelOptionsBase, } from './machine-learning-api-client'; +/** + * Gets the {@link machineLearning.MachineLearning `MachineLearning`} service for the + * default app or a given app. + * + * `getMachineLearning()` can be called with no arguments to access the + * default app's {@link machineLearning.MachineLearning + * `MachineLearning`} service or as `getMachineLearning(app)` to access + * the {@link machineLearning.MachineLearning `MachineLearning`} + * service associated with a specific app. + * + * @example + * ```javascript + * // Get the MachineLearning service for the default app + * const defaultMachineLearning = getMachineLearning(); + * ``` + * + * @example + * ```javascript + * // Get the MachineLearning service for a given app + * const otherMachineLearning = getMachineLearning(otherApp); + * ``` + * + * @param app Optional app whose `MachineLearning` service to + * return. If not provided, the default `MachineLearning` service + * will be returned. + * + * @return The default `MachineLearning` service if no app is provided or the + * `MachineLearning` service associated with the provided app. + */ export function getMachineLearning(app?: App): MachineLearning { if (typeof app === 'undefined') { app = getApp(); diff --git a/src/machine-learning/machine-learning.ts b/src/machine-learning/machine-learning.ts index c150c89e61..5c60438adf 100644 --- a/src/machine-learning/machine-learning.ts +++ b/src/machine-learning/machine-learning.ts @@ -58,7 +58,7 @@ export interface TFLiteModel { } /** - * The Firebase Machine Learning class + * The Firebase `MachineLearning` service interface. */ export class MachineLearning { @@ -84,20 +84,19 @@ export class MachineLearning { } /** - * Returns the app associated with this ML instance. - * - * @return The app associated with this ML instance. + * The {@link app.App} associated with the current `MachineLearning` + * service instance. */ public get app(): App { return this.appInternal; } /** - * Creates a model in Firebase ML. + * Creates a model in the current Firebase project. * - * @param {ModelOptions} model The model to create. + * @param model The model to create. * - * @return {Promise} A Promise fulfilled with the created model. + * @return A Promise fulfilled with the created model. */ public createModel(model: ModelOptions): Promise { return this.signUrlIfPresent(model) @@ -107,12 +106,12 @@ export class MachineLearning { } /** - * Updates a model in Firebase ML. + * Updates a model's metadata or model file. * - * @param {string} modelId The id of the model to update. - * @param {ModelOptions} model The model fields to update. + * @param modelId The ID of the model to update. + * @param model The model fields to update. * - * @return {Promise} A Promise fulfilled with the updated model. + * @return A Promise fulfilled with the updated model. */ public updateModel(modelId: string, model: ModelOptions): Promise { const updateMask = utils.generateUpdateMask(model); @@ -123,33 +122,35 @@ export class MachineLearning { } /** - * Publishes a model in Firebase ML. + * Publishes a Firebase ML model. + * + * A published model can be downloaded to client apps. * - * @param {string} modelId The id of the model to publish. + * @param modelId The ID of the model to publish. * - * @return {Promise} A Promise fulfilled with the published model. + * @return A Promise fulfilled with the published model. */ public publishModel(modelId: string): Promise { return this.setPublishStatus(modelId, true); } /** - * Unpublishes a model in Firebase ML. + * Unpublishes a Firebase ML model. * - * @param {string} modelId The id of the model to unpublish. + * @param modelId The ID of the model to unpublish. * - * @return {Promise} A Promise fulfilled with the unpublished model. + * @return A Promise fulfilled with the unpublished model. */ public unpublishModel(modelId: string): Promise { return this.setPublishStatus(modelId, false); } /** - * Gets a model from Firebase ML. + * Gets the model specified by the given ID. * - * @param {string} modelId The id of the model to get. + * @param modelId The ID of the model to get. * - * @return {Promise} A Promise fulfilled with the unpublished model. + * @return A Promise fulfilled with the model object. */ public getModel(modelId: string): Promise { return this.client.getModel(modelId) @@ -157,14 +158,14 @@ export class MachineLearning { } /** - * Lists models from Firebase ML. + * Lists the current project's models. * - * @param {ListModelsOptions} options The listing options. + * @param options The listing options. * - * @return {Promise<{models: Model[], pageToken?: string}>} A promise that + * @return A promise that * resolves with the current (filtered) list of models and the next page - * token. For the last page, an empty list of models and no page token are - * returned. + * token. For the last page, an empty list of models and no page token + * are returned. */ public listModels(options: ListModelsOptions = {}): Promise { return this.client.listModels(options) @@ -187,9 +188,9 @@ export class MachineLearning { } /** - * Deletes a model from Firebase ML. + * Deletes a model from the current project. * - * @param {string} modelId The id of the model to delete. + * @param modelId The ID of the model to delete. */ public deleteModel(modelId: string): Promise { return this.client.deleteModel(modelId); @@ -257,55 +258,80 @@ export class Model { this.client = client; } + /** The ID of the model. */ get modelId(): string { return extractModelId(this.model.name); } + /** + * The model's name. This is the name you use from your app to load the + * model. + */ get displayName(): string { return this.model.displayName!; } + /** + * The model's tags, which can be used to group or filter models in list + * operations. + */ get tags(): string[] { return this.model.tags || []; } + /** The timestamp of the model's creation. */ get createTime(): string { return new Date(this.model.createTime).toUTCString(); } + /** The timestamp of the model's most recent update. */ get updateTime(): string { return new Date(this.model.updateTime).toUTCString(); } + /** Error message when model validation fails. */ get validationError(): string | undefined { return this.model.state?.validationError?.message; } + /** True if the model is published. */ get published(): boolean { return this.model.state?.published || false; } + /** + * The ETag identifier of the current version of the model. This value + * changes whenever you update any of the model's properties. + */ get etag(): string { return this.model.etag; } + /** + * The hash of the model's `tflite` file. This value changes only when + * you upload a new TensorFlow Lite model. + */ get modelHash(): string | undefined { return this.model.modelHash; } + /** Metadata about the model's TensorFlow Lite model file. */ get tfliteModel(): TFLiteModel | undefined { // Make a copy so people can't directly modify the private this.model object. return deepCopy(this.model.tfliteModel); } /** - * Locked indicates if there are active long running operations on the model. - * Models may not be modified when they are locked. + * True if the model is locked by a server-side operation. You can't make + * changes to a locked model. See {@link waitForUnlocked `waitForUnlocked()`}. */ public get locked(): boolean { return (this.model.activeOperations?.length ?? 0) > 0; } + /** + * Return the model as a JSON object. + */ public toJSON(): {[key: string]: any} { // We can't just return this.model because it has extra fields and // different formats etc. So we build the expected model object. @@ -338,9 +364,13 @@ export class Model { } /** - * Wait for the active operations on the model to complete. - * @param maxTimeMillis The number of milliseconds to wait for the model to be unlocked. If unspecified, - * a default will be used. + * Wait for the model to be unlocked. + * + * @param maxTimeMillis The maximum time in milliseconds to wait. + * If not specified, a default maximum of 2 minutes is used. + * + * @return A promise that resolves when the model is unlocked + * or the maximum wait time has passed. */ public waitForUnlocked(maxTimeMillis?: number): Promise { if ((this.model.activeOperations?.length ?? 0) > 0) { diff --git a/src/messaging/index.ts b/src/messaging/index.ts index 9c8634cba8..5d799cf6b0 100644 --- a/src/messaging/index.ts +++ b/src/messaging/index.ts @@ -60,6 +60,35 @@ export { NotificationMessagePayload, } from './messaging-api'; +/** + * Gets the {@link messaging.Messaging `Messaging`} service for the + * default app or a given app. + * + * `admin.messaging()` can be called with no arguments to access the default + * app's {@link messaging.Messaging `Messaging`} service or as + * `admin.messaging(app)` to access the + * {@link messaging.Messaging `Messaging`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the Messaging service for the default app + * const defaultMessaging = getMessaging(); + * ``` + * + * @example + * ```javascript + * // Get the Messaging service for a given app + * const otherMessaging = getMessaging(otherApp); + * ``` + * + * @param app Optional app whose `Messaging` service to + * return. If not provided, the default `Messaging` service will be returned. + * + * @return The default `Messaging` service if no + * app is provided or the `Messaging` service associated with the provided + * app. + */ export function getMessaging(app?: App): Messaging { if (typeof app === 'undefined') { app = getApp(); diff --git a/src/messaging/messaging.ts b/src/messaging/messaging.ts index 0c37ee30c5..b1803a513e 100644 --- a/src/messaging/messaging.ts +++ b/src/messaging/messaging.ts @@ -221,9 +221,13 @@ export class Messaging { } /** - * Returns the app associated with this Messaging instance. + * The {@link app.App app} associated with the current `Messaging` service + * instance. * - * @return The app associated with this Messaging instance. + * @example + * ```javascript + * var app = messaging.app; + * ``` */ get app(): App { return this.appInternal; diff --git a/src/project-management/android-app.ts b/src/project-management/android-app.ts index 808c25c7e4..5756051c28 100644 --- a/src/project-management/android-app.ts +++ b/src/project-management/android-app.ts @@ -39,7 +39,14 @@ export interface AndroidAppMetadata extends AppMetadata { packageName: string; } +/** + * A reference to a Firebase Android app. + * + * Do not call this constructor directly. Instead, use + * [`projectManagement.androidApp()`](projectManagement.ProjectManagement#androidApp). + */ export class AndroidApp { + private readonly resourceName: string; /** diff --git a/src/project-management/index.ts b/src/project-management/index.ts index 26e7f7aa5e..35cbe3b4c1 100644 --- a/src/project-management/index.ts +++ b/src/project-management/index.ts @@ -23,6 +23,34 @@ export { ProjectManagement } from './project-management'; export { AndroidApp, AndroidAppMetadata, ShaCertificate } from './android-app'; export { IosApp, IosAppMetadata } from './ios-app'; +/** + * Gets the {@link projectManagement.ProjectManagement + * `ProjectManagement`} service for the default app or a given app. + * + * `getProjectManagement()` can be called with no arguments to access the + * default app's {@link projectManagement.ProjectManagement + * `ProjectManagement`} service, or as `getProjectManagement(app)` to access + * the {@link projectManagement.ProjectManagement `ProjectManagement`} + * service associated with a specific app. + * + * @example + * ```javascript + * // Get the ProjectManagement service for the default app + * const defaultProjectManagement = getProjectManagement(); + * ``` + * + * @example + * ```javascript + * // Get the ProjectManagement service for a given app + * const otherProjectManagement = getProjectManagement(otherApp); + * ``` + * + * @param app Optional app whose `ProjectManagement` service + * to return. If not provided, the default `ProjectManagement` service will + * be returned. * + * @return The default `ProjectManagement` service if no app is provided or the + * `ProjectManagement` service associated with the provided app. + */ export function getProjectManagement(app?: App): ProjectManagement { if (typeof app === 'undefined') { app = getApp(); diff --git a/src/project-management/ios-app.ts b/src/project-management/ios-app.ts index 9d01ce23c6..0becebbb02 100644 --- a/src/project-management/ios-app.ts +++ b/src/project-management/ios-app.ts @@ -36,7 +36,14 @@ export interface IosAppMetadata extends AppMetadata { bundleId: string; } +/** + * A reference to a Firebase iOS app. + * + * Do not call this constructor directly. Instead, use + * [`projectManagement.iosApp()`](projectManagement.ProjectManagement#iosApp). + */ export class IosApp { + private readonly resourceName: string; /** @@ -56,7 +63,7 @@ export class IosApp { /** * Retrieves metadata about this iOS app. * - * @return {!Promise} A promise that + * @return A promise that * resolves to the retrieved metadata about this iOS app. */ public getMetadata(): Promise { diff --git a/src/remote-config/index.ts b/src/remote-config/index.ts index b7fd3fc15e..e97803c68f 100644 --- a/src/remote-config/index.ts +++ b/src/remote-config/index.ts @@ -34,6 +34,35 @@ export { } from './remote-config-api'; export { RemoteConfig } from './remote-config'; +/** + * Gets the {@link remoteConfig.RemoteConfig `RemoteConfig`} service for the + * default app or a given app. + * + * `getRemoteConfig()` can be called with no arguments to access the default + * app's {@link remoteConfig.RemoteConfig `RemoteConfig`} service or as + * `getRemoteConfig(app)` to access the + * {@link remoteConfig.RemoteConfig `RemoteConfig`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the `RemoteConfig` service for the default app + * const defaultRemoteConfig = getRemoteConfig(); + * ``` + * + * @example + * ```javascript + * // Get the `RemoteConfig` service for a given app + * const otherRemoteConfig = getRemoteConfig(otherApp); + * ``` + * + * @param app Optional app for which to return the `RemoteConfig` service. + * If not provided, the default `RemoteConfig` service is returned. + * + * @return The default `RemoteConfig` service if no + * app is provided, or the `RemoteConfig` service associated with the provided + * app. + */ export function getRemoteConfig(app?: App): RemoteConfig { if (typeof app === 'undefined') { app = getApp(); diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index 5f2ce6b252..c6b23517ef 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -29,7 +29,7 @@ import { } from './remote-config-api'; /** - * Remote Config service bound to the provided app. + * The Firebase `RemoteConfig` service interface. */ export class RemoteConfig { @@ -234,16 +234,16 @@ class RemoteConfigTemplateImpl implements RemoteConfigTemplate { /** * Gets the ETag of the template. * - * @return {string} The ETag of the Remote Config template. + * @return The ETag of the Remote Config template. */ get etag(): string { return this.etagInternal; } /** - * @return {RemoteConfigTemplate} A JSON-serializable representation of this object. + * @return A JSON-serializable representation of this object. */ - public toJSON(): RemoteConfigTemplate { + public toJSON(): object { return { conditions: this.conditions, parameters: this.parameters, @@ -359,9 +359,9 @@ class VersionImpl implements Version { } /** - * @return {Version} A JSON-serializable representation of this object. + * @return A JSON-serializable representation of this object. */ - public toJSON(): Version { + public toJSON(): object { return { versionNumber: this.versionNumber, updateOrigin: this.updateOrigin, diff --git a/src/security-rules/index.ts b/src/security-rules/index.ts index 0af1ef8feb..72582520d9 100644 --- a/src/security-rules/index.ts +++ b/src/security-rules/index.ts @@ -34,6 +34,34 @@ import { SecurityRules as TSecurityRules, } from './security-rules'; +/** + * Gets the {@link securityRules.SecurityRules + * `SecurityRules`} service for the default app or a given app. + * + * `admin.securityRules()` can be called with no arguments to access the + * default app's {@link securityRules.SecurityRules + * `SecurityRules`} service, or as `admin.securityRules(app)` to access + * the {@link securityRules.SecurityRules `SecurityRules`} + * service associated with a specific app. + * + * @example + * ```javascript + * // Get the SecurityRules service for the default app + * const defaultSecurityRules = getSecurityRules(); + * ``` + * + * @example + * ```javascript + * // Get the SecurityRules service for a given app + * const otherSecurityRules = getSecurityRules(otherApp); + * ``` + * + * @param app Optional app to return the `SecurityRules` service + * for. If not provided, the default `SecurityRules` service + * is returned. + * @return The default `SecurityRules` service if no app is provided, or the + * `SecurityRules` service associated with the provided app. + */ export function getSecurityRules(app?: App): SecurityRules { if (typeof app === 'undefined') { app = getApp(); diff --git a/src/security-rules/security-rules.ts b/src/security-rules/security-rules.ts index bf18627a08..83c49b9e86 100644 --- a/src/security-rules/security-rules.ts +++ b/src/security-rules/security-rules.ts @@ -48,9 +48,19 @@ export interface RulesetMetadata { readonly createTime: string; } +/** + * A page of ruleset metadata. + */ export class RulesetMetadataList { + /** + * A batch of ruleset metadata. + */ public readonly rulesets: RulesetMetadata[]; + + /** + * The next page token if available. This is needed to retrieve the next batch. + */ public readonly nextPageToken?: string; /** @@ -77,12 +87,20 @@ export class RulesetMetadataList { } /** - * Represents a set of Firebase security rules. + * A set of Firebase security rules. */ -export class Ruleset { +export class Ruleset implements RulesetMetadata { + /** + * {@inheritdoc RulesetMetadata.name} + */ public readonly name: string; + + /** + * {@inheritdoc RulesetMetadata.createTime} + */ public readonly createTime: string; + public readonly source: RulesFile[]; /** @@ -106,9 +124,6 @@ export class Ruleset { /** * The Firebase `SecurityRules` service interface. - * - * Do not call this constructor directly. Instead, use - * [`admin.securityRules()`](securityRules#securityRules). */ export class SecurityRules { @@ -127,12 +142,14 @@ export class SecurityRules { } /** - * Gets the Ruleset identified by the given name. The input name should be the short name string without - * the project ID prefix. For example, to retrieve the `projects/project-id/rulesets/my-ruleset`, pass the - * short name "my-ruleset". Rejects with a `not-found` error if the specified Ruleset cannot be found. + * Gets the {@link securityRules.Ruleset `Ruleset`} identified by the given + * name. The input name should be the short name string without the project ID + * prefix. For example, to retrieve the `projects/project-id/rulesets/my-ruleset`, + * pass the short name "my-ruleset". Rejects with a `not-found` error if the + * specified `Ruleset` cannot be found. * - * @param {string} name Name of the Ruleset to retrieve. - * @returns {Promise} A promise that fulfills with the specified Ruleset. + * @param name Name of the `Ruleset` to retrieve. + * @return A promise that fulfills with the specified `Ruleset`. */ public getRuleset(name: string): Promise { return this.client.getRuleset(name) @@ -142,20 +159,22 @@ export class SecurityRules { } /** - * Gets the Ruleset currently applied to Cloud Firestore. Rejects with a `not-found` error if no Ruleset is - * applied on Firestore. + * Gets the {@link securityRules.Ruleset `Ruleset`} currently applied to + * Cloud Firestore. Rejects with a `not-found` error if no ruleset is applied + * on Firestore. * - * @returns {Promise} A promise that fulfills with the Firestore Ruleset. + * @return A promise that fulfills with the Firestore ruleset. */ public getFirestoreRuleset(): Promise { return this.getRulesetForRelease(SecurityRules.CLOUD_FIRESTORE); } /** - * Creates a new ruleset from the given source, and applies it to Cloud Firestore. + * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given + * source, and applies it to Cloud Firestore. * - * @param {string|Buffer} source Rules source to apply. - * @returns {Promise} A promise that fulfills when the ruleset is created and released. + * @param source Rules source to apply. + * @return A promise that fulfills when the ruleset is created and released. */ public releaseFirestoreRulesetFromSource(source: string | Buffer): Promise { return Promise.resolve() @@ -172,23 +191,26 @@ export class SecurityRules { } /** - * Makes the specified ruleset the currently applied ruleset for Cloud Firestore. + * Applies the specified {@link securityRules.Ruleset `Ruleset`} ruleset + * to Cloud Firestore. * - * @param {string|RulesetMetadata} ruleset Name of the ruleset to apply or a RulesetMetadata object containing - * the name. - * @returns {Promise} A promise that fulfills when the ruleset is released. + * @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object + * containing the name. + * @return A promise that fulfills when the ruleset is released. */ public releaseFirestoreRuleset(ruleset: string | RulesetMetadata): Promise { return this.releaseRuleset(ruleset, SecurityRules.CLOUD_FIRESTORE); } /** - * Gets the Ruleset currently applied to a Cloud Storage bucket. Rejects with a `not-found` error if no Ruleset is - * applied on the bucket. + * Gets the {@link securityRules.Ruleset `Ruleset`} currently applied to a + * Cloud Storage bucket. Rejects with a `not-found` error if no ruleset is applied + * on the bucket. * - * @param {string=} bucket Optional name of the Cloud Storage bucket to be retrieved. If not specified, - * retrieves the ruleset applied on the default bucket configured via `AppOptions`. - * @returns {Promise} A promise that fulfills with the Cloud Storage Ruleset. + * @param bucket Optional name of the Cloud Storage bucket to be retrieved. If not + * specified, retrieves the ruleset applied on the default bucket configured via + * `AppOptions`. + * @return A promise that fulfills with the Cloud Storage ruleset. */ public getStorageRuleset(bucket?: string): Promise { return Promise.resolve() @@ -201,12 +223,14 @@ export class SecurityRules { } /** - * Creates a new ruleset from the given source, and applies it to a Cloud Storage bucket. + * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given + * source, and applies it to a Cloud Storage bucket. * - * @param {string|Buffer} source Rules source to apply. - * @param {string=} bucket Optional name of the Cloud Storage bucket to apply the rules on. If not specified, - * applies the ruleset on the default bucket configured via `AppOptions`. - * @returns {Promise} A promise that fulfills when the ruleset is created and released. + * @param source Rules source to apply. + * @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If + * not specified, applies the ruleset on the default bucket configured via + * {@link AppOptions `AppOptions`}. + * @return A promise that fulfills when the ruleset is created and released. */ public releaseStorageRulesetFromSource(source: string | Buffer, bucket?: string): Promise { return Promise.resolve() @@ -226,13 +250,15 @@ export class SecurityRules { } /** - * Makes the specified ruleset the currently applied ruleset for a Cloud Storage bucket. + * Applies the specified {@link securityRules.Ruleset `Ruleset`} ruleset + * to a Cloud Storage bucket. * - * @param {string|RulesetMetadata} ruleset Name of the ruleset to apply or a RulesetMetadata object containing - * the name. - * @param {string=} bucket Optional name of the Cloud Storage bucket to apply the rules on. If not specified, - * applies the ruleset on the default bucket configured via `AppOptions`. - * @returns {Promise} A promise that fulfills when the ruleset is released. + * @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object + * containing the name. + * @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If + * not specified, applies the ruleset on the default bucket configured via + * {@link AppOptions `AppOptions`}. + * @return A promise that fulfills when the ruleset is released. */ public releaseStorageRuleset(ruleset: string | RulesetMetadata, bucket?: string): Promise { return Promise.resolve() diff --git a/src/storage/index.ts b/src/storage/index.ts index 3cc1e82c12..7310c1cf00 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -20,6 +20,28 @@ import { Storage } from './storage'; export { Storage } from './storage'; +/** + * Gets the {@link storage.Storage `Storage`} service for the + * default app or a given app. + * + * `getStorage()` can be called with no arguments to access the default + * app's {@link storage.Storage `Storage`} service or as + * `getStorage(app)` to access the + * {@link storage.Storage `Storage`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the Storage service for the default app + * const defaultStorage = getStorage(); + * ``` + * + * @example + * ```javascript + * // Get the Storage service for a given app + * const otherStorage = getStorage(otherApp); + * ``` + */ export function getStorage(app?: App): Storage { if (typeof app === 'undefined') { app = getApp(); diff --git a/src/storage/storage.ts b/src/storage/storage.ts index d3511dc052..143ea25575 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -84,6 +84,8 @@ export class Storage { } /** + * Gets a reference to a Cloud Storage bucket. + * * @param name Optional name of the bucket to be retrieved. If name is not specified, * retrieves a reference to the default bucket. * @returns A [Bucket](https://cloud.google.com/nodejs/docs/reference/storage/latest/Bucket) @@ -104,7 +106,8 @@ export class Storage { } /** - * @return The app associated with this Storage instance. + * Optional app whose `Storage` service to + * return. If not provided, the default `Storage` service will be returned. */ get app(): App { return this.appInternal; From 56bdc81e2e6ffa732fd502cc14e7bf656a8a7d9c Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Mon, 22 Feb 2021 13:08:04 -0800 Subject: [PATCH 20/41] fix: Updated integration tests to use the modular API surface (#1173) * fix: Updated integration tests to use the modular API surface * fix: Updated integration tests to use new module entry points --- test/integration/app.spec.ts | 109 +++++-- test/integration/auth.spec.ts | 318 ++++++++++---------- test/integration/database.spec.ts | 67 +++-- test/integration/firestore.spec.ts | 48 +-- test/integration/instance-id.spec.ts | 4 +- test/integration/machine-learning.spec.ts | 149 +++++---- test/integration/messaging.spec.ts | 46 +-- test/integration/project-management.spec.ts | 56 ++-- test/integration/remote-config.spec.ts | 40 +-- test/integration/security-rules.spec.ts | 77 +++-- test/integration/setup.ts | 43 +-- test/integration/storage.spec.ts | 8 +- 12 files changed, 529 insertions(+), 436 deletions(-) diff --git a/test/integration/app.spec.ts b/test/integration/app.spec.ts index cce88a52aa..fcbe9d341b 100644 --- a/test/integration/app.spec.ts +++ b/test/integration/app.spec.ts @@ -15,6 +15,8 @@ */ import * as admin from '../../lib/index'; +import { App, deleteApp, getApp, initializeApp } from '../../lib/app/index'; +import { getAuth } from '../../lib/auth/index'; import { expect } from 'chai'; import { defaultApp, nullApp, nonNullApp, databaseUrl, projectId, storageBucket, @@ -27,30 +29,56 @@ describe('admin', () => { expect(storageBucket).to.be.not.empty; }); - it('does not load RTDB by default', () => { - const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; - expect(firebaseRtdb).to.be.undefined; - const rtdbInternal = require.cache[require.resolve('../../lib/database/database-internal')]; - expect(rtdbInternal).to.be.undefined; - }); + describe('Dependency lazy loading', () => { + const tempCache: {[key: string]: any} = {}; + const dependencies = ['@firebase/database', '@google-cloud/firestore']; + let lazyLoadingApp: App; - it('loads RTDB when calling admin.database', () => { - const rtdbNamespace = admin.database; - expect(rtdbNamespace).to.not.be.null; - const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; - expect(firebaseRtdb).to.not.be.undefined; - }); + before(() => { + // Unload dependencies if already loaded. Some of the other test files have imports + // to firebase-admin/database and firebase-admin/firestore, which cause the corresponding + // dependencies to get loaded before the tests are executed. + dependencies.forEach((name) => { + const resolvedName = require.resolve(name); + tempCache[name] = require.cache[resolvedName]; + delete require.cache[resolvedName]; + }); - it('does not load Firestore by default', () => { - const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; - expect(gcloud).to.be.undefined; - }); + // Initialize the SDK + lazyLoadingApp = initializeApp(defaultApp.options, 'lazyLoadingApp'); + }); + + it('does not load RTDB by default', () => { + const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; + expect(firebaseRtdb).to.be.undefined; + }); + + it('loads RTDB when calling admin.database', () => { + const rtdbNamespace = admin.database; + expect(rtdbNamespace).to.not.be.null; + const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; + expect(firebaseRtdb).to.not.be.undefined; + }); - it('loads Firestore when calling admin.firestore', () => { - const firestoreNamespace = admin.firestore; - expect(firestoreNamespace).to.not.be.null; - const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; - expect(gcloud).to.not.be.undefined; + it('does not load Firestore by default', () => { + const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; + expect(gcloud).to.be.undefined; + }); + + it('loads Firestore when calling admin.firestore', () => { + const firestoreNamespace = admin.firestore; + expect(firestoreNamespace).to.not.be.null; + const gcloud = require.cache[require.resolve('@google-cloud/firestore')]; + expect(gcloud).to.not.be.undefined; + }); + + after(() => { + dependencies.forEach((name) => { + const resolvedName = require.resolve(name); + require.cache[resolvedName] = tempCache[name]; + }); + return deleteApp(lazyLoadingApp); + }) }); }); @@ -98,3 +126,42 @@ describe('admin.app', () => { expect(admin.storage(app).app).to.deep.equal(app); }); }); + +describe('getApp', () => { + it('getApp() returns the default App', () => { + const app = getApp(); + expect(app).to.deep.equal(defaultApp); + expect(app.name).to.equal('[DEFAULT]'); + expect(app.options.databaseURL).to.equal(databaseUrl); + expect(app.options.databaseAuthVariableOverride).to.be.undefined; + expect(app.options.storageBucket).to.equal(storageBucket); + }); + + it('getApp("null") returns the App named "null"', () => { + const app = getApp('null'); + expect(app).to.deep.equal(nullApp); + expect(app.name).to.equal('null'); + expect(app.options.databaseURL).to.equal(databaseUrl); + expect(app.options.databaseAuthVariableOverride).to.be.null; + expect(app.options.storageBucket).to.equal(storageBucket); + }); + + it('getApp("nonNull") returns the App named "nonNull"', () => { + const app = getApp('nonNull'); + expect(app).to.deep.equal(nonNullApp); + expect(app.name).to.equal('nonNull'); + expect(app.options.databaseURL).to.equal(databaseUrl); + expect((app.options.databaseAuthVariableOverride as any).uid).to.be.ok; + expect(app.options.storageBucket).to.equal(storageBucket); + }); + + it('namespace services are attached to the default App', () => { + const app = getApp(); + expect(getAuth(app).app).to.deep.equal(app); + }); + + it('namespace services are attached to the named App', () => { + const app = getApp('null'); + expect(getAuth(app).app).to.deep.equal(app); + }); +}); diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 41e278f821..f16b7546fe 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -14,21 +14,25 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; -import * as chai from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; +import url = require('url'); import * as crypto from 'crypto'; import * as bcrypt from 'bcrypt'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; import firebase from '@firebase/app'; import '@firebase/auth'; import { clone } from 'lodash'; +import { User, FirebaseAuth } from '@firebase/auth-types'; import { generateRandomString, projectId, apiKey, noServiceAccountApp, cmdArgs, } from './setup'; -import url = require('url'); import * as mocks from '../resources/mocks'; import { deepExtend, deepCopy } from '../../src/utils/deep-copy'; -import { User, FirebaseAuth } from '@firebase/auth-types'; +import { + AuthProviderConfig, CreateTenantRequest, DeleteUsersResult, PhoneMultiFactorInfo, + TenantAwareAuth, UpdatePhoneMultiFactorInfoRequest, UpdateTenantRequest, UserImportOptions, + UserImportRecord, UserRecord, getAuth, +} from '../../lib/auth/index'; const chalk = require('chalk'); // eslint-disable-line @typescript-eslint/no-var-requires @@ -72,7 +76,7 @@ let deleteQueue = Promise.resolve(); interface UserImportTest { name: string; - importOptions: admin.auth.UserImportOptions; + importOptions: UserImportOptions; rawPassword: string; rawSalt?: string; computePasswordHash(userImportTest: UserImportTest): Buffer; @@ -113,7 +117,7 @@ describe('admin.auth', () => { const newUserData = clone(mockUserData); newUserData.email = generateRandomString(20).toLowerCase() + '@example.com'; newUserData.phoneNumber = testPhoneNumber2; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .then((userRecord) => { uidFromCreateUserWithoutUid = userRecord.uid; expect(typeof userRecord.uid).to.equal('string'); @@ -127,7 +131,7 @@ describe('admin.auth', () => { it('createUser() creates a new user with the specified UID', () => { const newUserData: any = clone(mockUserData); newUserData.uid = newUserUid; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); // Confirm expected email. @@ -159,7 +163,7 @@ describe('admin.auth', () => { enrolledFactors, }, }; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .then((userRecord) => { expect(userRecord.uid).to.equal(newMultiFactorUserUid); // Confirm expected email. @@ -170,7 +174,7 @@ describe('admin.auth', () => { const firstMultiFactor = userRecord.multiFactor!.enrolledFactors[0]; expect(firstMultiFactor.uid).not.to.be.undefined; expect(firstMultiFactor.enrollmentTime).not.to.be.undefined; - expect((firstMultiFactor as admin.auth.PhoneMultiFactorInfo).phoneNumber).to.equal( + expect((firstMultiFactor as PhoneMultiFactorInfo).phoneNumber).to.equal( enrolledFactors[0].phoneNumber); expect(firstMultiFactor.displayName).to.equal(enrolledFactors[0].displayName); expect(firstMultiFactor.factorId).to.equal(enrolledFactors[0].factorId); @@ -178,7 +182,7 @@ describe('admin.auth', () => { const secondMultiFactor = userRecord.multiFactor!.enrolledFactors[1]; expect(secondMultiFactor.uid).not.to.be.undefined; expect(secondMultiFactor.enrollmentTime).not.to.be.undefined; - expect((secondMultiFactor as admin.auth.PhoneMultiFactorInfo).phoneNumber).to.equal( + expect((secondMultiFactor as PhoneMultiFactorInfo).phoneNumber).to.equal( enrolledFactors[1].phoneNumber); expect(secondMultiFactor.displayName).to.equal(enrolledFactors[1].displayName); expect(secondMultiFactor.factorId).to.equal(enrolledFactors[1].factorId); @@ -188,26 +192,26 @@ describe('admin.auth', () => { it('createUser() fails when the UID is already in use', () => { const newUserData: any = clone(mockUserData); newUserData.uid = newUserUid; - return admin.auth().createUser(newUserData) + return getAuth().createUser(newUserData) .should.eventually.be.rejected.and.have.property('code', 'auth/uid-already-exists'); }); it('getUser() returns a user record with the matching UID', () => { - return admin.auth().getUser(newUserUid) + return getAuth().getUser(newUserUid) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); }); it('getUserByEmail() returns a user record with the matching email', () => { - return admin.auth().getUserByEmail(mockUserData.email) + return getAuth().getUserByEmail(mockUserData.email) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); }); it('getUserByPhoneNumber() returns a user record with the matching phone number', () => { - return admin.auth().getUserByPhoneNumber(mockUserData.phoneNumber) + return getAuth().getUserByPhoneNumber(mockUserData.phoneNumber) .then((userRecord) => { expect(userRecord.uid).to.equal(newUserUid); }); @@ -232,7 +236,7 @@ describe('admin.auth', () => { // Also create a user with a provider config. (You can't create a user with // a provider config. But you *can* import one.) - const importUser1: admin.auth.UserImportRecord = { + const importUser1: UserImportRecord = { uid: 'uid4', email: 'user4@example.com', phoneNumber: '+15555550004', @@ -262,8 +266,8 @@ describe('admin.auth', () => { await deleteUsersWithDelay(uidsToDelete); // Create/import users required by these tests - await Promise.all(usersToCreate.map((user) => admin.auth().createUser(user))); - await admin.auth().importUsers([importUser1]); + await Promise.all(usersToCreate.map((user) => getAuth().createUser(user))); + await getAuth().importUsers([importUser1]); }); after(async () => { @@ -273,7 +277,7 @@ describe('admin.auth', () => { }); it('returns users by various identifier types in a single call', async () => { - const users = await admin.auth().getUsers([ + const users = await getAuth().getUsers([ { uid: 'uid1' }, { email: 'user2@example.com' }, { phoneNumber: '+15555550003' }, @@ -286,7 +290,7 @@ describe('admin.auth', () => { }); it('returns found users and ignores non-existing users', async () => { - const users = await admin.auth().getUsers([ + const users = await getAuth().getUsers([ { uid: 'uid1' }, { uid: 'uid_that_doesnt_exist' }, { uid: 'uid3' }, @@ -299,14 +303,14 @@ describe('admin.auth', () => { it('returns nothing when queried for only non-existing users', async () => { const notFoundIds = [{ uid: 'non-existing user' }]; - const users = await admin.auth().getUsers(notFoundIds); + const users = await getAuth().getUsers(notFoundIds); expect(users.users).to.be.empty; expect(users.notFound).to.deep.equal(notFoundIds); }); it('de-dups duplicate users', async () => { - const users = await admin.auth().getUsers([ + const users = await getAuth().getUsers([ { uid: 'uid1' }, { uid: 'uid1' }, ]) @@ -321,7 +325,7 @@ describe('admin.auth', () => { return new Date(s).toUTCString() === s; }; - const newUserRecord = await admin.auth().createUser({ + const newUserRecord = await getAuth().createUser({ uid: 'lastRefreshTimeUser', email: 'lastRefreshTimeUser@example.com', password: 'p4ssword', @@ -341,7 +345,7 @@ describe('admin.auth', () => { let userRecord = null; for (let i = 0; i < 3; i++) { - userRecord = await admin.auth().getUser('lastRefreshTimeUser'); + userRecord = await getAuth().getUser('lastRefreshTimeUser'); if (userRecord.metadata.lastRefreshTime) { break; } @@ -360,25 +364,25 @@ describe('admin.auth', () => { expect(lastRefreshTime).lte(creationTime + 3600 * 1000); }); } finally { - admin.auth().deleteUser('lastRefreshTimeUser'); + getAuth().deleteUser('lastRefreshTimeUser'); } }); }); it('listUsers() returns up to the specified number of users', () => { - const promises: Array> = []; + const promises: Array> = []; uids.forEach((uid) => { const tempUserData = { uid, password: 'password', }; - promises.push(admin.auth().createUser(tempUserData)); + promises.push(getAuth().createUser(tempUserData)); }); return Promise.all(promises) .then(() => { // Return 2 users with the provided page token. // This test will fail if other users are created in between. - return admin.auth().listUsers(2, uids[0]); + return getAuth().listUsers(2, uids[0]); }) .then((listUsersResult) => { // Confirm expected number of users. @@ -424,22 +428,22 @@ describe('admin.auth', () => { .then((idToken) => { currentIdToken = idToken; // Verify that user's ID token while checking for revocation. - return admin.auth().verifyIdToken(currentIdToken, true); + return getAuth().verifyIdToken(currentIdToken, true); }) .then((decodedIdToken) => { // Verification should succeed. Revoke that user's session. return new Promise((resolve) => setTimeout(() => resolve( - admin.auth().revokeRefreshTokens(decodedIdToken.sub), + getAuth().revokeRefreshTokens(decodedIdToken.sub), ), 1000)); }) .then(() => { // verifyIdToken without checking revocation should still succeed. - return admin.auth().verifyIdToken(currentIdToken) + return getAuth().verifyIdToken(currentIdToken) .should.eventually.be.fulfilled; }) .then(() => { // verifyIdToken while checking for revocation should fail. - return admin.auth().verifyIdToken(currentIdToken, true) + return getAuth().verifyIdToken(currentIdToken, true) .should.eventually.be.rejected.and.have.property('code', 'auth/id-token-revoked'); }) .then(() => { @@ -459,16 +463,16 @@ describe('admin.auth', () => { }) .then((idToken) => { // ID token for new session should be valid even with revocation check. - return admin.auth().verifyIdToken(idToken, true) + return getAuth().verifyIdToken(idToken, true) .should.eventually.be.fulfilled; }); }); it('setCustomUserClaims() sets claims that are accessible via user\'s ID token', () => { // Set custom claims on the user. - return admin.auth().setCustomUserClaims(newUserUid, customClaims) + return getAuth().setCustomUserClaims(newUserUid, customClaims) .then(() => { - return admin.auth().getUser(newUserUid); + return getAuth().getUser(newUserUid); }) .then((userRecord) => { // Confirm custom claims set on the UserRecord. @@ -484,7 +488,7 @@ describe('admin.auth', () => { }) .then((idToken) => { // Verify ID token contents. - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }) .then((decodedIdToken: {[key: string]: any}) => { // Confirm expected claims set on the user's ID token. @@ -494,10 +498,10 @@ describe('admin.auth', () => { } } // Test clearing of custom claims. - return admin.auth().setCustomUserClaims(newUserUid, null); + return getAuth().setCustomUserClaims(newUserUid, null); }) .then(() => { - return admin.auth().getUser(newUserUid); + return getAuth().getUser(newUserUid); }) .then((userRecord) => { // Custom claims should be cleared. @@ -508,7 +512,7 @@ describe('admin.auth', () => { }) .then((idToken) => { // Verify ID token contents. - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }) .then((decodedIdToken: {[key: string]: any}) => { // Confirm all custom claims are cleared. @@ -540,7 +544,7 @@ describe('admin.auth', () => { enrollmentTime: now, }, ]; - return admin.auth().updateUser(newUserUid, { + return getAuth().updateUser(newUserUid, { email: updatedEmail, phoneNumber: updatedPhone, emailVerified: true, @@ -561,7 +565,7 @@ describe('admin.auth', () => { expect(actualUserRecord.multiFactor.enrolledFactors.length).to.equal(2); expect(actualUserRecord.multiFactor.enrolledFactors).to.deep.equal(enrolledFactors); // Update list of second factors. - return admin.auth().updateUser(newUserUid, { + return getAuth().updateUser(newUserUid, { multiFactor: { enrolledFactors: [enrolledFactors[0]], }, @@ -572,7 +576,7 @@ describe('admin.auth', () => { const actualUserRecord: {[key: string]: any} = userRecord.toJSON(); expect(actualUserRecord.multiFactor.enrolledFactors[0]).to.deep.equal(enrolledFactors[0]); // Remove all second factors. - return admin.auth().updateUser(newUserUid, { + return getAuth().updateUser(newUserUid, { multiFactor: { enrolledFactors: null, }, @@ -585,33 +589,33 @@ describe('admin.auth', () => { }); it('getUser() fails when called with a non-existing UID', () => { - return admin.auth().getUser(nonexistentUid) + return getAuth().getUser(nonexistentUid) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('getUserByEmail() fails when called with a non-existing email', () => { - return admin.auth().getUserByEmail(nonexistentUid + '@example.com') + return getAuth().getUserByEmail(nonexistentUid + '@example.com') .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('getUserByPhoneNumber() fails when called with a non-existing phone number', () => { - return admin.auth().getUserByPhoneNumber(nonexistentPhoneNumber) + return getAuth().getUserByPhoneNumber(nonexistentPhoneNumber) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('updateUser() fails when called with a non-existing UID', () => { - return admin.auth().updateUser(nonexistentUid, { + return getAuth().updateUser(nonexistentUid, { emailVerified: true, }).should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('deleteUser() fails when called with a non-existing UID', () => { - return admin.auth().deleteUser(nonexistentUid) + return getAuth().deleteUser(nonexistentUid) .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); it('createCustomToken() mints a JWT that can be used to sign in', () => { - return admin.auth().createCustomToken(newUserUid, { + return getAuth().createCustomToken(newUserUid, { isAdmin: true, }) .then((customToken) => { @@ -622,7 +626,7 @@ describe('admin.auth', () => { return user!.getIdToken(); }) .then((idToken) => { - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }) .then((token) => { expect(token.uid).to.equal(newUserUid); @@ -631,7 +635,7 @@ describe('admin.auth', () => { }); it('createCustomToken() can mint JWTs without a service account', () => { - return admin.auth(noServiceAccountApp).createCustomToken(newUserUid, { + return getAuth(noServiceAccountApp).createCustomToken(newUserUid, { isAdmin: true, }) .then((customToken) => { @@ -642,7 +646,7 @@ describe('admin.auth', () => { return user!.getIdToken(); }) .then((idToken) => { - return admin.auth(noServiceAccountApp).verifyIdToken(idToken); + return getAuth(noServiceAccountApp).verifyIdToken(idToken); }) .then((token) => { expect(token.uid).to.equal(newUserUid); @@ -651,7 +655,7 @@ describe('admin.auth', () => { }); it('verifyIdToken() fails when called with an invalid token', () => { - return admin.auth().verifyIdToken('invalid-token') + return getAuth().verifyIdToken('invalid-token') .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); @@ -668,7 +672,7 @@ describe('admin.auth', () => { // Create the test user before running this suite of tests. before(() => { - return admin.auth().createUser(userData); + return getAuth().createUser(userData); }); // Sign out after each test. @@ -683,9 +687,9 @@ describe('admin.auth', () => { it('generatePasswordResetLink() should return a password reset link', () => { // Ensure old password set on created user. - return admin.auth().updateUser(uid, { password: 'password' }) + return getAuth().updateUser(uid, { password: 'password' }) .then(() => { - return admin.auth().generatePasswordResetLink(email, actionCodeSettings); + return getAuth().generatePasswordResetLink(email, actionCodeSettings); }) .then((link) => { const code = getActionCode(link); @@ -705,10 +709,10 @@ describe('admin.auth', () => { it('generateEmailVerificationLink() should return a verification link', () => { // Ensure the user's email is unverified. - return admin.auth().updateUser(uid, { password: 'password', emailVerified: false }) + return getAuth().updateUser(uid, { password: 'password', emailVerified: false }) .then((userRecord) => { expect(userRecord.emailVerified).to.be.false; - return admin.auth().generateEmailVerificationLink(email, actionCodeSettings); + return getAuth().generateEmailVerificationLink(email, actionCodeSettings); }) .then((link) => { const code = getActionCode(link); @@ -726,7 +730,7 @@ describe('admin.auth', () => { }); it('generateSignInWithEmailLink() should return a sign-in link', () => { - return admin.auth().generateSignInWithEmailLink(email, actionCodeSettings) + return getAuth().generateSignInWithEmailLink(email, actionCodeSettings) .then((link) => { expect(getContinueUrl(link)).equal(actionCodeSettings.url); return clientAuth().signInWithEmailLink(email, link); @@ -742,7 +746,7 @@ describe('admin.auth', () => { describe('Tenant management operations', () => { let createdTenantId: string; const createdTenants: string[] = []; - const tenantOptions: admin.auth.CreateTenantRequest = { + const tenantOptions: CreateTenantRequest = { displayName: 'testTenant1', emailSignInConfig: { enabled: true, @@ -819,14 +823,14 @@ describe('admin.auth', () => { const promises: Array> = []; createdTenants.forEach((tenantId) => { promises.push( - admin.auth().tenantManager().deleteTenant(tenantId) + getAuth().tenantManager().deleteTenant(tenantId) .catch(() => {/** Ignore. */})); }); return Promise.all(promises); }); it('createTenant() should resolve with a new tenant', () => { - return admin.auth().tenantManager().createTenant(tenantOptions) + return getAuth().tenantManager().createTenant(tenantOptions) .then((actualTenant) => { createdTenantId = actualTenant.tenantId; createdTenants.push(createdTenantId); @@ -838,7 +842,7 @@ describe('admin.auth', () => { // Sanity check user management + email link generation + custom attribute APIs. // TODO: Confirm behavior in client SDK when it starts supporting it. describe('supports user management, email link generation, custom attribute and token revocation APIs', () => { - let tenantAwareAuth: admin.auth.TenantAwareAuth; + let tenantAwareAuth: TenantAwareAuth; let createdUserUid: string; let lastValidSinceTime: number; const newUserData = clone(mockUserData); @@ -857,7 +861,7 @@ describe('admin.auth', () => { if (!createdTenantId) { this.skip(); } else { - tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); + tenantAwareAuth = getAuth().tenantManager().authForTenant(createdTenantId); } }); @@ -1018,7 +1022,7 @@ describe('admin.auth', () => { // Sanity check OIDC/SAML config management API. describe('SAML management APIs', () => { - let tenantAwareAuth: admin.auth.TenantAwareAuth; + let tenantAwareAuth: TenantAwareAuth; const authProviderConfig = { providerId: randomSamlProviderId(), displayName: 'SAML_DISPLAY_NAME1', @@ -1045,7 +1049,7 @@ describe('admin.auth', () => { if (!createdTenantId) { this.skip(); } else { - tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); + tenantAwareAuth = getAuth().tenantManager().authForTenant(createdTenantId); } }); @@ -1084,7 +1088,7 @@ describe('admin.auth', () => { }); describe('OIDC management APIs', () => { - let tenantAwareAuth: admin.auth.TenantAwareAuth; + let tenantAwareAuth: TenantAwareAuth; const authProviderConfig = { providerId: randomOidcProviderId(), displayName: 'OIDC_DISPLAY_NAME1', @@ -1103,7 +1107,7 @@ describe('admin.auth', () => { if (!createdTenantId) { this.skip(); } else { - tenantAwareAuth = admin.auth().tenantManager().authForTenant(createdTenantId); + tenantAwareAuth = getAuth().tenantManager().authForTenant(createdTenantId); } }); @@ -1142,7 +1146,7 @@ describe('admin.auth', () => { }); it('getTenant() should resolve with expected tenant', () => { - return admin.auth().tenantManager().getTenant(createdTenantId) + return getAuth().tenantManager().getTenant(createdTenantId) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedCreatedTenant); }); @@ -1151,7 +1155,7 @@ describe('admin.auth', () => { it('updateTenant() should resolve with the updated tenant', () => { expectedUpdatedTenant.tenantId = createdTenantId; expectedUpdatedTenant2.tenantId = createdTenantId; - const updatedOptions: admin.auth.UpdateTenantRequest = { + const updatedOptions: UpdateTenantRequest = { displayName: expectedUpdatedTenant.displayName, emailSignInConfig: { enabled: false, @@ -1159,7 +1163,7 @@ describe('admin.auth', () => { multiFactorConfig: deepCopy(expectedUpdatedTenant.multiFactorConfig), testPhoneNumbers: deepCopy(expectedUpdatedTenant.testPhoneNumbers), }; - const updatedOptions2: admin.auth.UpdateTenantRequest = { + const updatedOptions2: UpdateTenantRequest = { emailSignInConfig: { enabled: true, passwordRequired: false, @@ -1168,10 +1172,10 @@ describe('admin.auth', () => { // Test clearing of phone numbers. testPhoneNumbers: null, }; - return admin.auth().tenantManager().updateTenant(createdTenantId, updatedOptions) + return getAuth().tenantManager().updateTenant(createdTenantId, updatedOptions) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedUpdatedTenant); - return admin.auth().tenantManager().updateTenant(createdTenantId, updatedOptions2); + return getAuth().tenantManager().updateTenant(createdTenantId, updatedOptions2); }) .then((actualTenant) => { expect(actualTenant.toJSON()).to.deep.equal(expectedUpdatedTenant2); @@ -1183,7 +1187,7 @@ describe('admin.auth', () => { const tenantOptions2 = deepCopy(tenantOptions); tenantOptions2.displayName = 'testTenant2'; const listAllTenantIds = (tenantIds: string[], nextPageToken?: string): Promise => { - return admin.auth().tenantManager().listTenants(100, nextPageToken) + return getAuth().tenantManager().listTenants(100, nextPageToken) .then((result) => { result.tenants.forEach((tenant) => { tenantIds.push(tenant.tenantId); @@ -1193,7 +1197,7 @@ describe('admin.auth', () => { } }); }; - return admin.auth().tenantManager().createTenant(tenantOptions2) + return getAuth().tenantManager().createTenant(tenantOptions2) .then((actualTenant) => { createdTenants.push(actualTenant.tenantId); // Test listTenants returns the expected tenants. @@ -1208,9 +1212,9 @@ describe('admin.auth', () => { }); it('deleteTenant() should successfully delete the provided tenant', () => { - return admin.auth().tenantManager().deleteTenant(createdTenantId) + return getAuth().tenantManager().deleteTenant(createdTenantId) .then(() => { - return admin.auth().tenantManager().getTenant(createdTenantId); + return getAuth().tenantManager().getTenant(createdTenantId); }) .then(() => { throw new Error('unexpected success'); @@ -1247,14 +1251,14 @@ describe('admin.auth', () => { const removeTempConfigs = (): Promise => { return Promise.all([ - admin.auth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), - admin.auth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), ]); }; // Clean up temp configurations used for test. before(() => { - return removeTempConfigs().then(() => admin.auth().createProviderConfig(authProviderConfig1)); + return removeTempConfigs().then(() => getAuth().createProviderConfig(authProviderConfig1)); }); after(() => { @@ -1262,25 +1266,25 @@ describe('admin.auth', () => { }); it('createProviderConfig() successfully creates a SAML config', () => { - return admin.auth().createProviderConfig(authProviderConfig2) + return getAuth().createProviderConfig(authProviderConfig2) .then((config) => { assertDeepEqualUnordered(authProviderConfig2, config); }); }); it('getProviderConfig() successfully returns the expected SAML config', () => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().getProviderConfig(authProviderConfig1.providerId) .then((config) => { assertDeepEqualUnordered(authProviderConfig1, config); }); }); it('listProviderConfig() successfully returns the list of SAML providers', () => { - const configs: admin.auth.AuthProviderConfig[] = []; + const configs: AuthProviderConfig[] = []; const listProviders: any = (type: 'saml' | 'oidc', maxResults?: number, pageToken?: string) => { - return admin.auth().listProviderConfigs({ type, maxResults, pageToken }) + return getAuth().listProviderConfigs({ type, maxResults, pageToken }) .then((result) => { - result.providerConfigs.forEach((config: admin.auth.AuthProviderConfig) => { + result.providerConfigs.forEach((config: AuthProviderConfig) => { configs.push(config); }); if (result.pageToken) { @@ -1317,7 +1321,7 @@ describe('admin.auth', () => { callbackURL: 'https://projectId3.firebaseapp.com/__/auth/handler', enableRequestSigning: false, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) .then((config) => { const modifiedConfig = deepExtend( { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); @@ -1345,7 +1349,7 @@ describe('admin.auth', () => { callbackURL: 'https://projectId3.firebaseapp.com/__/auth/handler', enableRequestSigning: false, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) .then((config) => { const modifiedConfig = deepExtend( { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); @@ -1354,8 +1358,8 @@ describe('admin.auth', () => { }); it('deleteProviderConfig() successfully deletes an existing SAML config', () => { - return admin.auth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { + return getAuth().getProviderConfig(authProviderConfig1.providerId) .should.eventually.be.rejected.and.have.property('code', 'auth/configuration-not-found'); }); }); @@ -1379,14 +1383,14 @@ describe('admin.auth', () => { const removeTempConfigs = (): Promise => { return Promise.all([ - admin.auth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), - admin.auth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig1.providerId).catch(() => {/* empty */}), + getAuth().deleteProviderConfig(authProviderConfig2.providerId).catch(() => {/* empty */}), ]); }; // Clean up temp configurations used for test. before(() => { - return removeTempConfigs().then(() => admin.auth().createProviderConfig(authProviderConfig1)); + return removeTempConfigs().then(() => getAuth().createProviderConfig(authProviderConfig1)); }); after(() => { @@ -1394,25 +1398,25 @@ describe('admin.auth', () => { }); it('createProviderConfig() successfully creates an OIDC config', () => { - return admin.auth().createProviderConfig(authProviderConfig2) + return getAuth().createProviderConfig(authProviderConfig2) .then((config) => { assertDeepEqualUnordered(authProviderConfig2, config); }); }); it('getProviderConfig() successfully returns the expected OIDC config', () => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().getProviderConfig(authProviderConfig1.providerId) .then((config) => { assertDeepEqualUnordered(authProviderConfig1, config); }); }); it('listProviderConfig() successfully returns the list of OIDC providers', () => { - const configs: admin.auth.AuthProviderConfig[] = []; + const configs: AuthProviderConfig[] = []; const listProviders: any = (type: 'saml' | 'oidc', maxResults?: number, pageToken?: string) => { - return admin.auth().listProviderConfigs({ type, maxResults, pageToken }) + return getAuth().listProviderConfigs({ type, maxResults, pageToken }) .then((result) => { - result.providerConfigs.forEach((config: admin.auth.AuthProviderConfig) => { + result.providerConfigs.forEach((config: AuthProviderConfig) => { configs.push(config); }); if (result.pageToken) { @@ -1445,7 +1449,7 @@ describe('admin.auth', () => { issuer: 'https://oidc.com/issuer3', clientId: 'CLIENT_ID3', }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) .then((config) => { const modifiedConfig = deepExtend( { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); @@ -1465,7 +1469,7 @@ describe('admin.auth', () => { issuer: 'https://oidc.com/issuer4', clientId: 'CLIENT_ID3', }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) .then((config) => { const modifiedConfig = deepExtend( { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); @@ -1474,8 +1478,8 @@ describe('admin.auth', () => { }); it('deleteProviderConfig() successfully deletes an existing OIDC config', () => { - return admin.auth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { - return admin.auth().getProviderConfig(authProviderConfig1.providerId) + return getAuth().deleteProviderConfig(authProviderConfig1.providerId).then(() => { + return getAuth().getProviderConfig(authProviderConfig1.providerId) .should.eventually.be.rejected.and.have.property('code', 'auth/configuration-not-found'); }); }); @@ -1483,17 +1487,17 @@ describe('admin.auth', () => { it('deleteUser() deletes the user with the given UID', () => { return Promise.all([ - admin.auth().deleteUser(newUserUid), - admin.auth().deleteUser(newMultiFactorUserUid), - admin.auth().deleteUser(uidFromCreateUserWithoutUid), + getAuth().deleteUser(newUserUid), + getAuth().deleteUser(newMultiFactorUserUid), + getAuth().deleteUser(uidFromCreateUserWithoutUid), ]).should.eventually.be.fulfilled; }); describe('deleteUsers()', () => { it('deletes users', async () => { - const uid1 = await admin.auth().createUser({}).then((ur) => ur.uid); - const uid2 = await admin.auth().createUser({}).then((ur) => ur.uid); - const uid3 = await admin.auth().createUser({}).then((ur) => ur.uid); + const uid1 = await getAuth().createUser({}).then((ur) => ur.uid); + const uid2 = await getAuth().createUser({}).then((ur) => ur.uid); + const uid3 = await getAuth().createUser({}).then((ur) => ur.uid); const ids = [{ uid: uid1 }, { uid: uid2 }, { uid: uid3 }]; return deleteUsersWithDelay([uid1, uid2, uid3]) @@ -1502,7 +1506,7 @@ describe('admin.auth', () => { expect(deleteUsersResult.failureCount).to.equal(0); expect(deleteUsersResult.errors).to.have.length(0); - return admin.auth().getUsers(ids); + return getAuth().getUsers(ids); }) .then((getUsersResult) => { expect(getUsersResult.users).to.have.length(0); @@ -1511,7 +1515,7 @@ describe('admin.auth', () => { }); it('deletes users that exist even when non-existing users also specified', async () => { - const uid1 = await admin.auth().createUser({}).then((ur) => ur.uid); + const uid1 = await getAuth().createUser({}).then((ur) => ur.uid); const uid2 = 'uid-that-doesnt-exist'; const ids = [{ uid: uid1 }, { uid: uid2 }]; @@ -1521,7 +1525,7 @@ describe('admin.auth', () => { expect(deleteUsersResult.failureCount).to.equal(0); expect(deleteUsersResult.errors).to.have.length(0); - return admin.auth().getUsers(ids); + return getAuth().getUsers(ids); }) .then((getUsersResult) => { expect(getUsersResult.users).to.have.length(0); @@ -1530,7 +1534,7 @@ describe('admin.auth', () => { }); it('is idempotent', async () => { - const uid = await admin.auth().createUser({}).then((ur) => ur.uid); + const uid = await getAuth().createUser({}).then((ur) => ur.uid); return deleteUsersWithDelay([uid]) .then((deleteUsersResult) => { @@ -1557,7 +1561,7 @@ describe('admin.auth', () => { const uid3 = sessionCookieUids[2]; it('creates a valid Firebase session cookie', () => { - return admin.auth().createCustomToken(uid, { admin: true, groupId: '1234' }) + return getAuth().createCustomToken(uid, { admin: true, groupId: '1234' }) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; @@ -1565,7 +1569,7 @@ describe('admin.auth', () => { }) .then((idToken) => { currentIdToken = idToken; - return admin.auth().verifyIdToken(idToken); + return getAuth().verifyIdToken(idToken); }).then((decodedIdTokenClaims) => { expectedExp = Math.floor((new Date().getTime() + expiresIn) / 1000); payloadClaims = decodedIdTokenClaims; @@ -1575,9 +1579,9 @@ describe('admin.auth', () => { delete payloadClaims.iat; expectedIat = Math.floor(new Date().getTime() / 1000); // One day long session cookie. - return admin.auth().createSessionCookie(currentIdToken, { expiresIn }); + return getAuth().createSessionCookie(currentIdToken, { expiresIn }); }) - .then((sessionCookie) => admin.auth().verifySessionCookie(sessionCookie)) + .then((sessionCookie) => getAuth().verifySessionCookie(sessionCookie)) .then((decodedIdToken) => { // Check for expected expiration with +/-5 seconds of variation. expect(decodedIdToken.exp).to.be.within(expectedExp - 5, expectedExp + 5); @@ -1593,7 +1597,7 @@ describe('admin.auth', () => { it('creates a revocable session cookie', () => { let currentSessionCookie: string; - return admin.auth().createCustomToken(uid2) + return getAuth().createCustomToken(uid2) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; @@ -1601,26 +1605,26 @@ describe('admin.auth', () => { }) .then((idToken) => { // One day long session cookie. - return admin.auth().createSessionCookie(idToken, { expiresIn }); + return getAuth().createSessionCookie(idToken, { expiresIn }); }) .then((sessionCookie) => { currentSessionCookie = sessionCookie; return new Promise((resolve) => setTimeout(() => resolve( - admin.auth().revokeRefreshTokens(uid2), + getAuth().revokeRefreshTokens(uid2), ), 1000)); }) .then(() => { - return admin.auth().verifySessionCookie(currentSessionCookie) + return getAuth().verifySessionCookie(currentSessionCookie) .should.eventually.be.fulfilled; }) .then(() => { - return admin.auth().verifySessionCookie(currentSessionCookie, true) + return getAuth().verifySessionCookie(currentSessionCookie, true) .should.eventually.be.rejected.and.have.property('code', 'auth/session-cookie-revoked'); }); }); it('fails when called with a revoked ID token', () => { - return admin.auth().createCustomToken(uid3, { admin: true, groupId: '1234' }) + return getAuth().createCustomToken(uid3, { admin: true, groupId: '1234' }) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; @@ -1629,11 +1633,11 @@ describe('admin.auth', () => { .then((idToken) => { currentIdToken = idToken; return new Promise((resolve) => setTimeout(() => resolve( - admin.auth().revokeRefreshTokens(uid3), + getAuth().revokeRefreshTokens(uid3), ), 1000)); }) .then(() => { - return admin.auth().createSessionCookie(currentIdToken, { expiresIn }) + return getAuth().createSessionCookie(currentIdToken, { expiresIn }) .should.eventually.be.rejected.and.have.property('code', 'auth/id-token-expired'); }); }); @@ -1643,19 +1647,19 @@ describe('admin.auth', () => { describe('verifySessionCookie()', () => { const uid = sessionCookieUids[0]; it('fails when called with an invalid session cookie', () => { - return admin.auth().verifySessionCookie('invalid-token') + return getAuth().verifySessionCookie('invalid-token') .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); it('fails when called with a Firebase ID token', () => { - return admin.auth().createCustomToken(uid) + return getAuth().createCustomToken(uid) .then((customToken) => clientAuth().signInWithCustomToken(customToken)) .then(({ user }) => { expect(user).to.exist; return user!.getIdToken(); }) .then((idToken) => { - return admin.auth().verifySessionCookie(idToken) + return getAuth().verifySessionCookie(idToken) .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); }); @@ -1663,7 +1667,7 @@ describe('admin.auth', () => { describe('importUsers()', () => { const randomUid = 'import_' + generateRandomString(20).toLowerCase(); - let importUserRecord: admin.auth.UserImportRecord; + let importUserRecord: UserImportRecord; const rawPassword = 'password'; const rawSalt = 'NaCl'; // Simulate a user stored using SCRYPT being migrated to Firebase Auth via importUsers. @@ -1875,12 +1879,12 @@ describe('admin.auth', () => { ], }; uids.push(importUserRecord.uid); - return admin.auth().importUsers([importUserRecord]) + return getAuth().importUsers([importUserRecord]) .then((result) => { expect(result.failureCount).to.equal(0); expect(result.successCount).to.equal(1); expect(result.errors.length).to.equal(0); - return admin.auth().getUser(uid); + return getAuth().getUser(uid); }).then((userRecord) => { // The phone number provider will be appended to the list of accounts. importUserRecord.providerData?.push({ @@ -1900,7 +1904,7 @@ describe('admin.auth', () => { const uid = generateRandomString(20).toLowerCase(); const email = uid + '@example.com'; const now = new Date(1476235905000).toUTCString(); - const enrolledFactors: admin.auth.UpdatePhoneMultiFactorInfoRequest[] = [ + const enrolledFactors: UpdatePhoneMultiFactorInfoRequest[] = [ { uid: 'mfaUid1', phoneNumber: '+16505550001', @@ -1941,12 +1945,12 @@ describe('admin.auth', () => { }; uids.push(importUserRecord.uid); - return admin.auth().importUsers([importUserRecord]) + return getAuth().importUsers([importUserRecord]) .then((result) => { expect(result.failureCount).to.equal(0); expect(result.successCount).to.equal(1); expect(result.errors.length).to.equal(0); - return admin.auth().getUser(uid); + return getAuth().getUser(uid); }).then((userRecord) => { // Confirm second factors added to user. const actualUserRecord: {[key: string]: any} = userRecord.toJSON(); @@ -1963,7 +1967,7 @@ describe('admin.auth', () => { { uid: generateRandomString(20).toLowerCase(), phoneNumber: '+1invalid' }, { uid: generateRandomString(20).toLowerCase(), emailVerified: 'invalid' } as any, ]; - return admin.auth().importUsers(users) + return getAuth().importUsers(users) .then((result) => { expect(result.successCount).to.equal(0); expect(result.failureCount).to.equal(4); @@ -1985,18 +1989,18 @@ describe('admin.auth', () => { * Imports the provided user record with the specified hashing options and then * validates the import was successful by signing in to the imported account using * the corresponding plain text password. - * @param {admin.auth.UserImportRecord} importUserRecord The user record to import. - * @param {admin.auth.UserImportOptions} importOptions The import hashing options. - * @param {string} rawPassword The plain unhashed password string. - * @retunr {Promise} A promise that resolved on success. + * @param importUserRecord The user record to import. + * @param importOptions The import hashing options. + * @param rawPassword The plain unhashed password string. + * @retunr A promise that resolved on success. */ function testImportAndSignInUser( - importUserRecord: admin.auth.UserImportRecord, + importUserRecord: UserImportRecord, importOptions: any, rawPassword: string): Promise { const users = [importUserRecord]; // Import the user record. - return admin.auth().importUsers(users, importOptions) + return getAuth().importUsers(users, importOptions) .then((result) => { // Verify the import result. expect(result.failureCount).to.equal(0); @@ -2017,12 +2021,12 @@ function testImportAndSignInUser( /** * Helper function that deletes the user with the specified phone number * if it exists. - * @param {string} phoneNumber The phone number of the user to delete. - * @return {Promise} A promise that resolves when the user is deleted + * @param phoneNumber The phone number of the user to delete. + * @return A promise that resolves when the user is deleted * or is found not to exist. */ function deletePhoneNumberUser(phoneNumber: string): Promise { - return admin.auth().getUserByPhoneNumber(phoneNumber) + return getAuth().getUserByPhoneNumber(phoneNumber) .then((userRecord) => { return safeDelete(userRecord.uid); }) @@ -2038,7 +2042,7 @@ function deletePhoneNumberUser(phoneNumber: string): Promise { * Runs cleanup routine that could affect outcome of tests and removes any * intermediate users created. * - * @return {Promise} A promise that resolves when test preparations are ready. + * @return A promise that resolves when test preparations are ready. */ function cleanup(): Promise { // Delete any existing users that could affect the test outcome. @@ -2061,8 +2065,8 @@ function cleanup(): Promise { /** * Returns the action code corresponding to the link. * - * @param {string} link The link to parse for the action code. - * @return {string} The link's corresponding action code. + * @param link The link to parse for the action code. + * @return The link's corresponding action code. */ function getActionCode(link: string): string { const parsedUrl = new url.URL(link); @@ -2074,8 +2078,8 @@ function getActionCode(link: string): string { /** * Returns the continue URL corresponding to the link. * - * @param {string} link The link to parse for the continue URL. - * @return {string} The link's corresponding continue URL. + * @param link The link to parse for the continue URL. + * @return The link's corresponding continue URL. */ function getContinueUrl(link: string): string { const parsedUrl = new url.URL(link); @@ -2087,8 +2091,8 @@ function getContinueUrl(link: string): string { /** * Returns the tenant ID corresponding to the link. * - * @param {string} link The link to parse for the tenant ID. - * @return {string} The link's corresponding tenant ID. + * @param link The link to parse for the tenant ID. + * @return The link's corresponding tenant ID. */ function getTenantId(link: string): string { const parsedUrl = new url.URL(link); @@ -2102,14 +2106,14 @@ function getTenantId(link: string): string { * requests and throttles them as the Auth backend rate limits this endpoint. * A bulk delete API is being designed to help solve this issue. * - * @param {string} uid The identifier of the user to delete. - * @return {Promise} A promise that resolves when delete operation resolves. + * @param uid The identifier of the user to delete. + * @return A promise that resolves when delete operation resolves. */ function safeDelete(uid: string): Promise { // Wait for delete queue to empty. const deletePromise = deleteQueue .then(() => { - return admin.auth().deleteUser(uid); + return getAuth().deleteUser(uid); }) .catch((error) => { // Suppress user not found error. @@ -2129,14 +2133,14 @@ function safeDelete(uid: string): Promise { * API is rate limited at 1 QPS, and therefore this helper function staggers * subsequent invocations by adding 1 second delay to each call. * - * @param {string[]} uids The list of user identifiers to delete. - * @return {Promise} A promise that resolves when delete operation resolves. + * @param uids The list of user identifiers to delete. + * @return A promise that resolves when delete operation resolves. */ -function deleteUsersWithDelay(uids: string[]): Promise { +function deleteUsersWithDelay(uids: string[]): Promise { return new Promise((resolve) => { setTimeout(resolve, 1000); }).then(() => { - return admin.auth().deleteUsers(uids); + return getAuth().deleteUsers(uids); }); } @@ -2144,8 +2148,8 @@ function deleteUsersWithDelay(uids: string[]): Promise { '.write': 'auth != null', }, }; - return admin.database().setRules(defaultRules); + return getDatabase().setRules(defaultRules); + }); + + it('getDatabase() returns a database client', () => { + const db: Database = getDatabase(); + expect(db).to.not.be.undefined; }); it('admin.database() returns a database client', () => { - const db = admin.database(); - expect(db).to.be.instanceOf((admin.database as any).Database); + const db: admin.database.Database = admin.database(); + expect(db).to.not.be.undefined; + expect(db).to.equal(getDatabase()); }); it('admin.database.ServerValue type is defined', () => { @@ -60,37 +69,36 @@ describe('admin.database', () => { }); it('default App is not blocked by security rules', () => { - return defaultApp.database().ref('blocked').set(admin.database.ServerValue.TIMESTAMP) + return getDatabase(defaultApp).ref('blocked').set(ServerValue.TIMESTAMP) .should.eventually.be.fulfilled; }); it('App with null auth overrides is blocked by security rules', () => { - return nullApp.database().ref('blocked').set(admin.database.ServerValue.TIMESTAMP) + return getDatabase(nullApp).ref('blocked').set(ServerValue.TIMESTAMP) .should.eventually.be.rejectedWith('PERMISSION_DENIED: Permission denied'); }); it('App with non-null auth override is not blocked by security rules', () => { - return nonNullApp.database().ref('blocked').set(admin.database.ServerValue.TIMESTAMP) + return getDatabase(nonNullApp).ref('blocked').set(ServerValue.TIMESTAMP) .should.eventually.be.fulfilled; }); - describe('admin.database().ref()', () => { - let ref: admin.database.Reference; + describe('Reference', () => { + let ref: Reference; before(() => { - ref = admin.database().ref(path); + ref = getDatabase().ref(path); }); it('ref() can be called with ref', () => { - const copy = admin.database().ref(ref); - expect(copy).to.be.instanceof((admin.database as any).Reference); + const copy: Reference = getDatabase().ref(ref); expect(copy.key).to.equal(ref.key); }); it('set() completes successfully', () => { return ref.set({ success: true, - timestamp: admin.database.ServerValue.TIMESTAMP, + timestamp: ServerValue.TIMESTAMP, }).should.eventually.be.fulfilled; }); @@ -115,24 +123,29 @@ describe('admin.database', () => { }); }); - describe('app.database(url).ref()', () => { + describe('getDatabaseWithUrl()', () => { - let refWithUrl: admin.database.Reference; + let refWithUrl: Reference; before(() => { - const app = admin.app(); - refWithUrl = app.database(databaseUrl).ref(path); + refWithUrl = getDatabaseWithUrl(databaseUrl).ref(path); + }); + + it('getDatabaseWithUrl(url) returns a Database client for URL', () => { + const db: Database = getDatabaseWithUrl(databaseUrl); + expect(db).to.not.be.undefined; }); it('app.database(url) returns a Database client for URL', () => { - const db = admin.app().database(databaseUrl); - expect(db).to.be.instanceOf((admin.database as any).Database); + const db: Database = admin.app().database(databaseUrl); + expect(db).to.not.be.undefined; + expect(db).to.equal(getDatabaseWithUrl(databaseUrl)); }); it('set() completes successfully', () => { return refWithUrl.set({ success: true, - timestamp: admin.database.ServerValue.TIMESTAMP, + timestamp: ServerValue.TIMESTAMP, }).should.eventually.be.fulfilled; }); @@ -157,14 +170,14 @@ describe('admin.database', () => { }); }); - it('admin.database().getRules() returns currently defined rules as a string', () => { - return admin.database().getRules().then((result) => { + it('getDatabase().getRules() returns currently defined rules as a string', () => { + return getDatabase().getRules().then((result) => { return expect(result).to.be.not.empty; }); }); - it('admin.database().getRulesJSON() returns currently defined rules as an object', () => { - return admin.database().getRulesJSON().then((result) => { + it('getDatabase().getRulesJSON() returns currently defined rules as an object', () => { + return getDatabase().getRulesJSON().then((result) => { return expect(result).to.be.not.undefined; }); }); @@ -174,8 +187,8 @@ describe('admin.database', () => { // will trigger a TS compilation failure if the RTDB typings were not loaded // correctly. (Marked as export to avoid compilation warning.) export function addValueEventListener( - db: admin.database.Database, - callback: (s: admin.database.DataSnapshot | null) => any): void { - const eventType: admin.database.EventType = 'value'; + db: Database, + callback: (s: DataSnapshot | null) => any): void { + const eventType: EventType = 'value'; db.ref().on(eventType, callback); } diff --git a/test/integration/firestore.spec.ts b/test/integration/firestore.spec.ts index 13ef9131dd..1695bab9af 100644 --- a/test/integration/firestore.spec.ts +++ b/test/integration/firestore.spec.ts @@ -14,10 +14,14 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { clone } from 'lodash'; +import * as admin from '../../lib/index'; +import { + DocumentReference, DocumentSnapshot, FieldValue, Firestore, FirestoreDataConverter, + QueryDocumentSnapshot, Timestamp, getFirestore, setLogFunction, +} from '../../lib/firestore/index'; chai.should(); chai.use(chaiAsPromised); @@ -31,21 +35,27 @@ const mountainView = { describe('admin.firestore', () => { - let reference: admin.firestore.DocumentReference; + let reference: DocumentReference; before(() => { - const db = admin.firestore(); + const db = getFirestore(); reference = db.collection('cities').doc(); }); + it('getFirestore() returns a Firestore client', () => { + const firestore: Firestore = getFirestore(); + expect(firestore).to.not.be.undefined; + }); + it('admin.firestore() returns a Firestore client', () => { - const firestore = admin.firestore(); - expect(firestore).to.be.instanceOf(admin.firestore.Firestore); + const firestore: admin.firestore.Firestore = admin.firestore(); + expect(firestore).to.not.be.undefined; + expect(firestore).to.equal(getFirestore()); }); it('app.firestore() returns a Firestore client', () => { - const firestore = admin.app().firestore(); - expect(firestore).to.be.instanceOf(admin.firestore.Firestore); + const firestore: admin.firestore.Firestore = admin.app().firestore(); + expect(firestore).to.not.be.undefined; }); it('supports basic data access', () => { @@ -66,9 +76,9 @@ describe('admin.firestore', () => { }); }); - it('admin.firestore.FieldValue.serverTimestamp() provides a server-side timestamp', () => { + it('FieldValue.serverTimestamp() provides a server-side timestamp', () => { const expected: any = clone(mountainView); - expected.timestamp = admin.firestore.FieldValue.serverTimestamp(); + expected.timestamp = FieldValue.serverTimestamp(); return reference.set(expected) .then(() => { return reference.get(); @@ -77,7 +87,7 @@ describe('admin.firestore', () => { const data = snapshot.data(); expect(data).to.exist; expect(data!.timestamp).is.not.null; - expect(data!.timestamp).to.be.instanceOf(admin.firestore.Timestamp); + expect(data!.timestamp).to.be.instanceOf(Timestamp); return reference.delete(); }) .should.eventually.be.fulfilled; @@ -118,20 +128,20 @@ describe('admin.firestore', () => { }); it('supports operations with custom type converters', () => { - const converter: admin.firestore.FirestoreDataConverter = { + const converter: FirestoreDataConverter = { toFirestore: (city: City) => { return { name: city.localId, population: city.people, }; }, - fromFirestore: (snap: admin.firestore.QueryDocumentSnapshot) => { + fromFirestore: (snap: QueryDocumentSnapshot) => { return new City(snap.data().name, snap.data().population); } }; const expected: City = new City('Sunnyvale', 153185); - const refWithConverter: admin.firestore.DocumentReference = admin.firestore() + const refWithConverter: DocumentReference = getFirestore() .collection('cities') .doc() .withConverter(converter); @@ -139,15 +149,15 @@ describe('admin.firestore', () => { .then(() => { return refWithConverter.get(); }) - .then((snapshot: admin.firestore.DocumentSnapshot) => { + .then((snapshot: DocumentSnapshot) => { expect(snapshot.data()).to.be.instanceOf(City); return refWithConverter.delete(); }); }); it('supports saving references in documents', () => { - const source = admin.firestore().collection('cities').doc(); - const target = admin.firestore().collection('cities').doc(); + const source = getFirestore().collection('cities').doc(); + const target = getFirestore().collection('cities').doc(); return source.set(mountainView) .then(() => { return target.set({ name: 'Palo Alto', sisterCity: source }); @@ -167,10 +177,10 @@ describe('admin.firestore', () => { .should.eventually.be.fulfilled; }); - it('admin.firestore.setLogFunction() enables logging for the Firestore module', () => { + it('setLogFunction() enables logging for the Firestore module', () => { const logs: string[] = []; - const source = admin.firestore().collection('cities').doc(); - admin.firestore.setLogFunction((log) => { + const source = getFirestore().collection('cities').doc(); + setLogFunction((log) => { logs.push(log); }); return source.set({ name: 'San Francisco' }) diff --git a/test/integration/instance-id.spec.ts b/test/integration/instance-id.spec.ts index a7d6eb6df2..841d110772 100644 --- a/test/integration/instance-id.spec.ts +++ b/test/integration/instance-id.spec.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import { getInstanceId } from '../../lib/instance-id/index'; chai.should(); chai.use(chaiAsPromised); @@ -24,7 +24,7 @@ chai.use(chaiAsPromised); describe('admin.instanceId', () => { it('deleteInstanceId() fails when called with fictive-ID0 instance ID', () => { // instance ids have to conform to /[cdef][A-Za-z0-9_-]{9}[AEIMQUYcgkosw048]/ - return admin.instanceId().deleteInstanceId('fictive-ID0') + return getInstanceId().deleteInstanceId('fictive-ID0') .should.eventually.be .rejectedWith('Instance ID "fictive-ID0": Failed to find the instance ID.'); }); diff --git a/test/integration/machine-learning.spec.ts b/test/integration/machine-learning.spec.ts index f767b7d0ad..900bbaa699 100644 --- a/test/integration/machine-learning.spec.ts +++ b/test/integration/machine-learning.spec.ts @@ -17,12 +17,12 @@ import path = require('path'); import * as chai from 'chai'; -import * as admin from '../../lib/index'; import { projectId } from './setup'; import { Bucket } from '@google-cloud/storage'; - -import AutoMLTfliteModelOptions = admin.machineLearning.AutoMLTfliteModelOptions; -import GcsTfliteModelOptions = admin.machineLearning.GcsTfliteModelOptions; +import { getStorage } from '../../lib/storage/index'; +import { + AutoMLTfliteModelOptions, GcsTfliteModelOptions, Model, ModelOptions, getMachineLearning, +} from '../../lib/machine-learning/index'; const expect = chai.expect; @@ -30,32 +30,31 @@ describe('admin.machineLearning', () => { const modelsToDelete: string[] = []; - function scheduleForDelete(model: admin.machineLearning.Model): void { + function scheduleForDelete(model: Model): void { modelsToDelete.push(model.modelId); } - function unscheduleForDelete(model: admin.machineLearning.Model): void { + function unscheduleForDelete(model: Model): void { modelsToDelete.splice(modelsToDelete.indexOf(model.modelId), 1); } function deleteTempModels(): Promise { const promises: Array> = []; modelsToDelete.forEach((modelId) => { - promises.push(admin.machineLearning().deleteModel(modelId)); + promises.push(getMachineLearning().deleteModel(modelId)); }); modelsToDelete.splice(0, modelsToDelete.length); // Clear out the array. return Promise.all(promises); } - function createTemporaryModel(options?: admin.machineLearning.ModelOptions): - Promise { - let modelOptions: admin.machineLearning.ModelOptions = { + function createTemporaryModel(options?: ModelOptions): Promise { + let modelOptions: ModelOptions = { displayName: 'nodejs_integration_temp_model', }; if (options) { modelOptions = options; } - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); return model; @@ -63,7 +62,7 @@ describe('admin.machineLearning', () => { } function uploadModelToGcs(localFileName: string, gcsFileName: string): Promise { - const bucket: Bucket = admin.storage().bucket(); + const bucket: Bucket = getStorage().bucket(); const tfliteFileName = path.join(__dirname, `../resources/${localFileName}`); return bucket.upload(tfliteFileName, { destination: gcsFileName }) .then(() => { @@ -77,11 +76,11 @@ describe('admin.machineLearning', () => { describe('createModel()', () => { it('creates a new Model without ModelFormat', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-1', tags: ['tag123', 'tag345'] }; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); verifyModel(model, modelOptions); @@ -89,7 +88,7 @@ describe('admin.machineLearning', () => { }); it('creates a new Model with valid GCS TFLite ModelFormat', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-2', tags: ['tag234', 'tag456'], tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, @@ -97,7 +96,7 @@ describe('admin.machineLearning', () => { return uploadModelToGcs('model1.tflite', 'valid_model.tflite') .then((fileName: string) => { modelOptions.tfliteModel!.gcsTfliteUri = fileName; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); verifyModel(model, modelOptions); @@ -114,12 +113,12 @@ describe('admin.machineLearning', () => { this.skip(); return; } - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-automl', tags: ['tagAutoml'], tfliteModel: { automlModel: automlRef } }; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { return model.waitForUnlocked(55000) .then(() => { @@ -132,7 +131,7 @@ describe('admin.machineLearning', () => { it('creates a new Model with invalid ModelFormat', () => { // Upload a file to default gcs bucket - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-create-3', tags: ['tag234', 'tag456'], tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, @@ -140,7 +139,7 @@ describe('admin.machineLearning', () => { return uploadModelToGcs('invalid_model.tflite', 'invalid_model.tflite') .then((fileName: string) => { modelOptions.tfliteModel!.gcsTfliteUri = fileName; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .then((model) => { scheduleForDelete(model); verifyModel(model, modelOptions); @@ -149,39 +148,39 @@ describe('admin.machineLearning', () => { }); it ('rejects with invalid-argument when modelOptions are invalid', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'Invalid Name#*^!', }; - return admin.machineLearning().createModel(modelOptions) + return getMachineLearning().createModel(modelOptions) .should.eventually.be.rejected.and.have.property('code', 'machine-learning/invalid-argument'); }); }); describe('updateModel()', () => { - const UPDATE_NAME: admin.machineLearning.ModelOptions = { + const UPDATE_NAME: ModelOptions = { displayName: 'update-model-new-name', }; it('rejects with not-found when the Model does not exist', () => { const nonExistingId = '00000000'; - return admin.machineLearning().updateModel(nonExistingId, UPDATE_NAME) + return getMachineLearning().updateModel(nonExistingId, UPDATE_NAME) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().updateModel('invalid-model-id', UPDATE_NAME) + return getMachineLearning().updateModel('invalid-model-id', UPDATE_NAME) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it ('rejects with invalid-argument when modelOptions are invalid', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'Invalid Name#*^!', }; return createTemporaryModel({ displayName: 'node-integ-invalid-argument' }) - .then((model) => admin.machineLearning().updateModel(model.modelId, modelOptions) + .then((model) => getMachineLearning().updateModel(model.modelId, modelOptions) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument')); }); @@ -190,10 +189,10 @@ describe('admin.machineLearning', () => { const DISPLAY_NAME = 'node-integ-test-update-1b'; return createTemporaryModel({ displayName: 'node-integ-test-update-1a' }) .then((model) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: DISPLAY_NAME, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { verifyModel(updatedModel, modelOptions); }); @@ -208,10 +207,10 @@ describe('admin.machineLearning', () => { displayName: 'node-integ-test-update-2', tags: ORIGINAL_TAGS, }).then((expectedModel) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { tags: NEW_TAGS, }; - return admin.machineLearning().updateModel(expectedModel.modelId, modelOptions) + return getMachineLearning().updateModel(expectedModel.modelId, modelOptions) .then((actualModel) => { expect(actualModel.tags!.length).to.equal(2); expect(actualModel.tags).to.have.same.members(NEW_TAGS); @@ -224,10 +223,10 @@ describe('admin.machineLearning', () => { createTemporaryModel(), uploadModelToGcs('model1.tflite', 'valid_model.tflite')]) .then(([model, fileName]) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { tfliteModel: { gcsTfliteUri: fileName }, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { verifyModel(updatedModel, modelOptions); }); @@ -247,10 +246,10 @@ describe('admin.machineLearning', () => { this.skip(); return; } - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { tfliteModel: { automlModel: automlRef }, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { return updatedModel.waitForUnlocked(55000) .then(() => { @@ -266,11 +265,11 @@ describe('admin.machineLearning', () => { const TAGS = ['node-integ-tag-1', 'node-integ-tag-2']; return createTemporaryModel({ displayName: 'node-integ-test-update-3a' }) .then((model) => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: DISPLAY_NAME, tags: TAGS, }; - return admin.machineLearning().updateModel(model.modelId, modelOptions) + return getMachineLearning().updateModel(model.modelId, modelOptions) .then((updatedModel) => { expect(updatedModel.displayName).to.equal(DISPLAY_NAME); expect(updatedModel.tags).to.have.same.members(TAGS); @@ -282,19 +281,19 @@ describe('admin.machineLearning', () => { describe('publishModel()', () => { it('should reject when model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().publishModel(nonExistingName) + return getMachineLearning().publishModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().publishModel('invalid-model-id') + return getMachineLearning().publishModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it('publishes the model successfully', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-publish-1', tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, }; @@ -305,7 +304,7 @@ describe('admin.machineLearning', () => { .then((createdModel) => { expect(createdModel.validationError).to.be.undefined; expect(createdModel.published).to.be.false; - return admin.machineLearning().publishModel(createdModel.modelId) + return getMachineLearning().publishModel(createdModel.modelId) .then((publishedModel) => { expect(publishedModel.published).to.be.true; }); @@ -317,19 +316,19 @@ describe('admin.machineLearning', () => { describe('unpublishModel()', () => { it('should reject when model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().unpublishModel(nonExistingName) + return getMachineLearning().unpublishModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().unpublishModel('invalid-model-id') + return getMachineLearning().unpublishModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it('unpublishes the model successfully', () => { - const modelOptions: admin.machineLearning.ModelOptions = { + const modelOptions: ModelOptions = { displayName: 'node-integ-test-unpublish-1', tfliteModel: { gcsTfliteUri: 'this will be replaced below' }, }; @@ -340,10 +339,10 @@ describe('admin.machineLearning', () => { .then((createdModel) => { expect(createdModel.validationError).to.be.undefined; expect(createdModel.published).to.be.false; - return admin.machineLearning().publishModel(createdModel.modelId) + return getMachineLearning().publishModel(createdModel.modelId) .then((publishedModel) => { expect(publishedModel.published).to.be.true; - return admin.machineLearning().unpublishModel(publishedModel.modelId) + return getMachineLearning().unpublishModel(publishedModel.modelId) .then((unpublishedModel) => { expect(unpublishedModel.published).to.be.false; }); @@ -357,13 +356,13 @@ describe('admin.machineLearning', () => { describe('getModel()', () => { it('rejects with not-found when the Model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().getModel(nonExistingName) + return getMachineLearning().getModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the ModelId is invalid', () => { - return admin.machineLearning().getModel('invalid-model-id') + return getMachineLearning().getModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); @@ -371,7 +370,7 @@ describe('admin.machineLearning', () => { it('resolves with existing Model', () => { return createTemporaryModel() .then((expectedModel) => - admin.machineLearning().getModel(expectedModel.modelId) + getMachineLearning().getModel(expectedModel.modelId) .then((actualModel) => { expect(actualModel).to.deep.equal(expectedModel); }), @@ -380,25 +379,25 @@ describe('admin.machineLearning', () => { }); describe('listModels()', () => { - let model1: admin.machineLearning.Model; - let model2: admin.machineLearning.Model; - let model3: admin.machineLearning.Model; + let model1: Model; + let model2: Model; + let model3: Model; before(() => { return Promise.all([ - admin.machineLearning().createModel({ + getMachineLearning().createModel({ displayName: 'node-integ-list1', tags: ['node-integ-tag-1'], }), - admin.machineLearning().createModel({ + getMachineLearning().createModel({ displayName: 'node-integ-list2', tags: ['node-integ-tag-1'], }), - admin.machineLearning().createModel({ + getMachineLearning().createModel({ displayName: 'node-integ-list3', tags: ['node-integ-tag-1'], })]) - .then(([m1, m2, m3]: admin.machineLearning.Model[]) => { + .then(([m1, m2, m3]: Model[]) => { model1 = m1; model2 = m2; model3 = m3; @@ -407,14 +406,14 @@ describe('admin.machineLearning', () => { after(() => { return Promise.all([ - admin.machineLearning().deleteModel(model1.modelId), - admin.machineLearning().deleteModel(model2.modelId), - admin.machineLearning().deleteModel(model3.modelId), + getMachineLearning().deleteModel(model1.modelId), + getMachineLearning().deleteModel(model2.modelId), + getMachineLearning().deleteModel(model3.modelId), ]); }); it('resolves with a list of models', () => { - return admin.machineLearning().listModels({ pageSize: 100 }) + return getMachineLearning().listModels({ pageSize: 100 }) .then((modelList) => { expect(modelList.models.length).to.be.at.least(2); expect(modelList.models).to.deep.include(model1); @@ -424,7 +423,7 @@ describe('admin.machineLearning', () => { }); it('respects page size', () => { - return admin.machineLearning().listModels({ pageSize: 2 }) + return getMachineLearning().listModels({ pageSize: 2 }) .then((modelList) => { expect(modelList.models.length).to.equal(2); expect(modelList.pageToken).not.to.be.empty; @@ -432,7 +431,7 @@ describe('admin.machineLearning', () => { }); it('filters by exact displayName', () => { - return admin.machineLearning().listModels({ filter: 'displayName=node-integ-list1' }) + return getMachineLearning().listModels({ filter: 'displayName=node-integ-list1' }) .then((modelList) => { expect(modelList.models.length).to.equal(1); expect(modelList.models[0]).to.deep.equal(model1); @@ -441,7 +440,7 @@ describe('admin.machineLearning', () => { }); it('filters by displayName prefix', () => { - return admin.machineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 100 }) + return getMachineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 100 }) .then((modelList) => { expect(modelList.models.length).to.be.at.least(3); expect(modelList.models).to.deep.include(model1); @@ -452,7 +451,7 @@ describe('admin.machineLearning', () => { }); it('filters by tag', () => { - return admin.machineLearning().listModels({ filter: 'tags:node-integ-tag-1', pageSize: 100 }) + return getMachineLearning().listModels({ filter: 'tags:node-integ-tag-1', pageSize: 100 }) .then((modelList) => { expect(modelList.models.length).to.be.at.least(3); expect(modelList.models).to.deep.include(model1); @@ -463,11 +462,11 @@ describe('admin.machineLearning', () => { }); it('handles pageTokens properly', () => { - return admin.machineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 2 }) + return getMachineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 2 }) .then((modelList) => { expect(modelList.models.length).to.equal(2); expect(modelList.pageToken).not.to.be.undefined; - return admin.machineLearning().listModels({ + return getMachineLearning().listModels({ filter: 'displayName:node-integ-list*', pageSize: 2, pageToken: modelList.pageToken @@ -480,7 +479,7 @@ describe('admin.machineLearning', () => { }); it('successfully returns an empty list of models', () => { - return admin.machineLearning().listModels({ filter: 'displayName=non-existing-model' }) + return getMachineLearning().listModels({ filter: 'displayName=non-existing-model' }) .then((modelList) => { expect(modelList.models.length).to.equal(0); expect(modelList.pageToken).to.be.undefined; @@ -488,7 +487,7 @@ describe('admin.machineLearning', () => { }); it('rejects with invalid argument if the filter is invalid', () => { - return admin.machineLearning().listModels({ filter: 'invalidFilterItem=foo' }) + return getMachineLearning().listModels({ filter: 'invalidFilterItem=foo' }) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); @@ -497,22 +496,22 @@ describe('admin.machineLearning', () => { describe('deleteModel()', () => { it('rejects with not-found when the Model does not exist', () => { const nonExistingName = '00000000'; - return admin.machineLearning().deleteModel(nonExistingName) + return getMachineLearning().deleteModel(nonExistingName) .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/not-found'); }); it('rejects with invalid-argument when the Model ID is invalid', () => { - return admin.machineLearning().deleteModel('invalid-model-id') + return getMachineLearning().deleteModel('invalid-model-id') .should.eventually.be.rejected.and.have.property( 'code', 'machine-learning/invalid-argument'); }); it('deletes existing Model', () => { return createTemporaryModel().then((model) => { - return admin.machineLearning().deleteModel(model.modelId) + return getMachineLearning().deleteModel(model.modelId) .then(() => { - return admin.machineLearning().getModel(model.modelId) + return getMachineLearning().getModel(model.modelId) .should.eventually.be.rejected.and.have.property('code', 'machine-learning/not-found'); }) .then(() => { @@ -524,7 +523,7 @@ describe('admin.machineLearning', () => { }); -function verifyModel(model: admin.machineLearning.Model, expectedOptions: admin.machineLearning.ModelOptions): void { +function verifyModel(model: Model, expectedOptions: ModelOptions): void { if (expectedOptions.displayName) { expect(model.displayName).to.equal(expectedOptions.displayName); } else { @@ -548,7 +547,7 @@ function verifyModel(model: admin.machineLearning.Model, expectedOptions: admin. } } -function verifyGcsTfliteModel(model: admin.machineLearning.Model, expectedOptions: GcsTfliteModelOptions): void { +function verifyGcsTfliteModel(model: Model, expectedOptions: GcsTfliteModelOptions): void { const expectedGcsTfliteUri = expectedOptions.tfliteModel.gcsTfliteUri; expect(model.tfliteModel!.gcsTfliteUri).to.equal(expectedGcsTfliteUri); if (expectedGcsTfliteUri.endsWith('invalid_model.tflite')) { @@ -560,7 +559,7 @@ function verifyGcsTfliteModel(model: admin.machineLearning.Model, expectedOption } } -function verifyAutomlTfliteModel(model: admin.machineLearning.Model, expectedOptions: AutoMLTfliteModelOptions): void { +function verifyAutomlTfliteModel(model: Model, expectedOptions: AutoMLTfliteModelOptions): void { const expectedAutomlReference = expectedOptions.tfliteModel.automlModel; expect(model.tfliteModel!.automlModel).to.equal(expectedAutomlReference); expect(model.validationError).to.be.undefined; diff --git a/test/integration/messaging.spec.ts b/test/integration/messaging.spec.ts index e67a81602b..f1c027a935 100644 --- a/test/integration/messaging.spec.ts +++ b/test/integration/messaging.spec.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; +import { Message, MulticastMessage, getMessaging } from '../../lib/messaging/index'; chai.should(); chai.use(chaiAsPromised); @@ -38,7 +38,7 @@ const condition = '"test0" in topics || ("test1" in topics && "test2" in topics) const invalidTopic = 'topic-$%#^'; -const message: admin.messaging.Message = { +const message: Message = { data: { foo: 'bar', }, @@ -102,15 +102,15 @@ const options = { describe('admin.messaging', () => { it('send(message, dryRun) returns a message ID', () => { - return admin.messaging().send(message, true) + return getMessaging().send(message, true) .then((name) => { expect(name).matches(/^projects\/.*\/messages\/.*$/); }); }); it('sendAll()', () => { - const messages: admin.messaging.Message[] = [message, message, message]; - return admin.messaging().sendAll(messages, true) + const messages: Message[] = [message, message, message]; + return getMessaging().sendAll(messages, true) .then((response) => { expect(response.responses.length).to.equal(messages.length); expect(response.successCount).to.equal(messages.length); @@ -123,11 +123,11 @@ describe('admin.messaging', () => { }); it('sendAll(500)', () => { - const messages: admin.messaging.Message[] = []; + const messages: Message[] = []; for (let i = 0; i < 500; i++) { messages.push({ topic: `foo-bar-${i % 10}` }); } - return admin.messaging().sendAll(messages, true) + return getMessaging().sendAll(messages, true) .then((response) => { expect(response.responses.length).to.equal(messages.length); expect(response.successCount).to.equal(messages.length); @@ -140,12 +140,12 @@ describe('admin.messaging', () => { }); it('sendMulticast()', () => { - const multicastMessage: admin.messaging.MulticastMessage = { + const multicastMessage: MulticastMessage = { data: message.data, android: message.android, tokens: ['not-a-token', 'also-not-a-token'], }; - return admin.messaging().sendMulticast(multicastMessage, true) + return getMessaging().sendMulticast(multicastMessage, true) .then((response) => { expect(response.responses.length).to.equal(2); expect(response.successCount).to.equal(0); @@ -159,86 +159,86 @@ describe('admin.messaging', () => { }); it('sendToDevice(token) returns a response with multicast ID', () => { - return admin.messaging().sendToDevice(registrationToken, payload, options) + return getMessaging().sendToDevice(registrationToken, payload, options) .then((response) => { expect(typeof response.multicastId).to.equal('number'); }); }); it('sendToDevice(token-list) returns a response with multicat ID', () => { - return admin.messaging().sendToDevice(registrationTokens, payload, options) + return getMessaging().sendToDevice(registrationTokens, payload, options) .then((response) => { expect(typeof response.multicastId).to.equal('number'); }); }); it('sendToDeviceGroup() returns a response with success count', () => { - return admin.messaging().sendToDeviceGroup(notificationKey, payload, options) + return getMessaging().sendToDeviceGroup(notificationKey, payload, options) .then((response) => { expect(typeof response.successCount).to.equal('number'); }); }); it('sendToTopic() returns a response with message ID', () => { - return admin.messaging().sendToTopic(topic, payload, options) + return getMessaging().sendToTopic(topic, payload, options) .then((response) => { expect(typeof response.messageId).to.equal('number'); }); }); it('sendToCondition() returns a response with message ID', () => { - return admin.messaging().sendToCondition(condition, payload, options) + return getMessaging().sendToCondition(condition, payload, options) .then((response) => { expect(typeof response.messageId).to.equal('number'); }); }); it('sendToDevice(token) fails when called with invalid payload', () => { - return admin.messaging().sendToDevice(registrationToken, invalidPayload, options) + return getMessaging().sendToDevice(registrationToken, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToDevice(token-list) fails when called with invalid payload', () => { - return admin.messaging().sendToDevice(registrationTokens, invalidPayload, options) + return getMessaging().sendToDevice(registrationTokens, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToDeviceGroup() fails when called with invalid payload', () => { - return admin.messaging().sendToDeviceGroup(notificationKey, invalidPayload, options) + return getMessaging().sendToDeviceGroup(notificationKey, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToTopic() fails when called with invalid payload', () => { - return admin.messaging().sendToTopic(topic, invalidPayload, options) + return getMessaging().sendToTopic(topic, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('sendToCondition() fails when called with invalid payload', () => { - return admin.messaging().sendToCondition(condition, invalidPayload, options) + return getMessaging().sendToCondition(condition, invalidPayload, options) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-payload'); }); it('subscribeToTopic() returns a response with success count', () => { - return admin.messaging().subscribeToTopic(registrationToken, topic) + return getMessaging().subscribeToTopic(registrationToken, topic) .then((response) => { expect(typeof response.successCount).to.equal('number'); }); }); it('unsubscribeFromTopic() returns a response with success count', () => { - return admin.messaging().unsubscribeFromTopic(registrationToken, topic) + return getMessaging().unsubscribeFromTopic(registrationToken, topic) .then((response) => { expect(typeof response.successCount).to.equal('number'); }); }); it('subscribeToTopic() fails when called with invalid topic', () => { - return admin.messaging().subscribeToTopic(registrationToken, invalidTopic) + return getMessaging().subscribeToTopic(registrationToken, invalidTopic) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-argument'); }); it('unsubscribeFromTopic() fails when called with invalid topic', () => { - return admin.messaging().unsubscribeFromTopic(registrationToken, invalidTopic) + return getMessaging().unsubscribeFromTopic(registrationToken, invalidTopic) .should.eventually.be.rejected.and.have.property('code', 'messaging/invalid-argument'); }); }); diff --git a/test/integration/project-management.spec.ts b/test/integration/project-management.spec.ts index de26f4aaf2..0d24566394 100644 --- a/test/integration/project-management.spec.ts +++ b/test/integration/project-management.spec.ts @@ -17,8 +17,10 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as admin from '../../lib/index'; import { projectId } from './setup'; +import { + AndroidApp, IosApp, ShaCertificate, getProjectManagement, +} from '../../lib/project-management/index'; const APP_NAMESPACE_PREFIX = 'com.adminsdkintegrationtest.a'; const APP_NAMESPACE_SUFFIX_LENGTH = 15; @@ -37,8 +39,8 @@ chai.use(chaiAsPromised); describe('admin.projectManagement', () => { - let androidApp: admin.projectManagement.AndroidApp; - let iosApp: admin.projectManagement.IosApp; + let androidApp: AndroidApp; + let iosApp: IosApp; before(() => { const androidPromise = ensureAndroidApp() @@ -55,7 +57,7 @@ describe('admin.projectManagement', () => { describe('listAndroidApps()', () => { it('successfully lists Android apps', () => { - return admin.projectManagement().listAndroidApps() + return getProjectManagement().listAndroidApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { expect(metadatas.length).to.be.at.least(1); @@ -69,7 +71,7 @@ describe('admin.projectManagement', () => { describe('listIosApps()', () => { it('successfully lists iOS apps', () => { - return admin.projectManagement().listIosApps() + return getProjectManagement().listIosApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { expect(metadatas.length).to.be.at.least(1); @@ -86,14 +88,14 @@ describe('admin.projectManagement', () => { const newDisplayName = generateUniqueProjectDisplayName(); // TODO(caot): verify that project name has been renamed successfully after adding the ability // to get project metadata. - return admin.projectManagement().setDisplayName(newDisplayName) + return getProjectManagement().setDisplayName(newDisplayName) .should.eventually.be.fulfilled; }); }); describe('listAppMetadata()', () => { it('successfully lists metadata of all apps', () => { - return admin.projectManagement().listAppMetadata() + return getProjectManagement().listAppMetadata() .then((metadatas) => { expect(metadatas.length).to.be.at.least(2); const testAppMetadatas = metadatas.filter((metadata) => @@ -158,7 +160,7 @@ describe('admin.projectManagement', () => { .then((certs) => { expect(certs.length).to.equal(0); - const shaCertificate = admin.projectManagement().shaCertificate(SHA_256_HASH); + const shaCertificate = getProjectManagement().shaCertificate(SHA_256_HASH); return androidApp.addShaCertificate(shaCertificate); }) .then(() => androidApp.getShaCertificates()) @@ -179,7 +181,7 @@ describe('admin.projectManagement', () => { it('add a cert and then remove it fails due to missing resourceName', () => { const shaCertificate = - admin.projectManagement().shaCertificate(SHA_256_HASH); + getProjectManagement().shaCertificate(SHA_256_HASH); return androidApp.addShaCertificate(shaCertificate) .then(() => androidApp.deleteShaCertificate(shaCertificate)) .should.eventually.be @@ -211,20 +213,20 @@ describe('admin.projectManagement', () => { /** * Ensures that an Android app owned by these integration tests exist. If not one will be created. * - * @return {Promise} Android app owned by these integration tests. + * @return Android app owned by these integration tests. */ -function ensureAndroidApp(): Promise { - return admin.projectManagement().listAndroidApps() +function ensureAndroidApp(): Promise { + return getProjectManagement().listAndroidApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { const metadataOwnedByTest = metadatas.find((metadata) => isIntegrationTestApp(metadata.packageName)); if (metadataOwnedByTest) { - return admin.projectManagement().androidApp(metadataOwnedByTest.appId); + return getProjectManagement().androidApp(metadataOwnedByTest.appId); } // If no Android app owned by these integration tests was found, then create one. - return admin.projectManagement() + return getProjectManagement() .createAndroidApp(generateUniqueAppNamespace(), generateUniqueAppDisplayName()); }); } @@ -232,20 +234,20 @@ function ensureAndroidApp(): Promise { /** * Ensures that an iOS app owned by these integration tests exist. If not one will be created. * - * @return {Promise} iOS app owned by these integration tests. + * @return iOS app owned by these integration tests. */ -function ensureIosApp(): Promise { - return admin.projectManagement().listIosApps() +function ensureIosApp(): Promise { + return getProjectManagement().listIosApps() .then((apps) => Promise.all(apps.map((app) => app.getMetadata()))) .then((metadatas) => { const metadataOwnedByTest = metadatas.find((metadata) => isIntegrationTestApp(metadata.bundleId)); if (metadataOwnedByTest) { - return admin.projectManagement().iosApp(metadataOwnedByTest.appId); + return getProjectManagement().iosApp(metadataOwnedByTest.appId); } // If no iOS app owned by these integration tests was found, then create one. - return admin.projectManagement() + return getProjectManagement() .createIosApp(generateUniqueAppNamespace(), generateUniqueAppDisplayName()); }); } @@ -253,51 +255,51 @@ function ensureIosApp(): Promise { /** * Deletes all SHA certificates from the specified Android app. */ -function deleteAllShaCertificates(androidApp: admin.projectManagement.AndroidApp): Promise { +function deleteAllShaCertificates(androidApp: AndroidApp): Promise { return androidApp.getShaCertificates() - .then((shaCertificates: admin.projectManagement.ShaCertificate[]) => { + .then((shaCertificates: ShaCertificate[]) => { return Promise.all(shaCertificates.map((cert) => androidApp.deleteShaCertificate(cert))); }) .then(() => undefined); } /** - * @return {string} Dot-separated string that can be used as a unique package name or bundle ID. + * @return Dot-separated string that can be used as a unique package name or bundle ID. */ function generateUniqueAppNamespace(): string { return APP_NAMESPACE_PREFIX + generateRandomString(APP_NAMESPACE_SUFFIX_LENGTH); } /** - * @return {string} Dot-separated string that can be used as a unique app display name. + * @return Dot-separated string that can be used as a unique app display name. */ function generateUniqueAppDisplayName(): string { return APP_DISPLAY_NAME_PREFIX + generateRandomString(APP_DISPLAY_NAME_SUFFIX_LENGTH); } /** - * @return {string} string that can be used as a unique project display name. + * @return string that can be used as a unique project display name. */ function generateUniqueProjectDisplayName(): string { return PROJECT_DISPLAY_NAME_PREFIX + generateRandomString(PROJECT_DISPLAY_NAME_SUFFIX_LENGTH); } /** - * @return {boolean} True if the specified appNamespace belongs to these integration tests. + * @return True if the specified appNamespace belongs to these integration tests. */ function isIntegrationTestApp(appNamespace: string): boolean { return appNamespace ? appNamespace.startsWith(APP_NAMESPACE_PREFIX) : false; } /** - * @return {boolean} True if the specified appDisplayName belongs to these integration tests. + * @return True if the specified appDisplayName belongs to these integration tests. */ function isIntegrationTestAppDisplayName(appDisplayName: string | undefined): boolean { return appDisplayName ? appDisplayName.startsWith(APP_DISPLAY_NAME_PREFIX) : false; } /** - * @return {string} A randomly generated alphanumeric string, of the specified length. + * @return A randomly generated alphanumeric string, of the specified length. */ function generateRandomString(stringLength: number): string { return _.times(stringLength, () => _.random(35).toString(36)).join(''); diff --git a/test/integration/remote-config.spec.ts b/test/integration/remote-config.spec.ts index f8773c1b3c..7a4c6c9a3c 100644 --- a/test/integration/remote-config.spec.ts +++ b/test/integration/remote-config.spec.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../src/utils/deep-copy'; +import { RemoteConfigCondition, RemoteConfigTemplate, getRemoteConfig, } from '../../lib/remote-config/index'; chai.should(); chai.use(chaiAsPromised); @@ -57,7 +57,7 @@ const VALID_PARAMETER_GROUPS = { }, }; -const VALID_CONDITIONS: admin.remoteConfig.RemoteConfigCondition[] = [ +const VALID_CONDITIONS: RemoteConfigCondition[] = [ { name: 'ios', expression: 'device.os == \'ios\'', @@ -74,12 +74,12 @@ const VALID_VERSION = { description: `template description ${Date.now()}`, } -let currentTemplate: admin.remoteConfig.RemoteConfigTemplate; +let currentTemplate: RemoteConfigTemplate; describe('admin.remoteConfig', () => { before(async () => { // obtain the most recent template (etag) to perform operations - currentTemplate = await admin.remoteConfig().getTemplate(); + currentTemplate = await getRemoteConfig().getTemplate(); }); it('verify that the etag is read-only', () => { @@ -95,7 +95,7 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().validateTemplate(currentTemplate) + return getRemoteConfig().validateTemplate(currentTemplate) .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.conditions.length).to.equal(2); @@ -113,7 +113,7 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().validateTemplate(currentTemplate) + return getRemoteConfig().validateTemplate(currentTemplate) .should.eventually.be.rejected.and.have.property('code', 'remote-config/invalid-argument'); }); }); @@ -125,7 +125,7 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().publishTemplate(currentTemplate) + return getRemoteConfig().publishTemplate(currentTemplate) .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.conditions.length).to.equal(2); @@ -143,14 +143,14 @@ describe('admin.remoteConfig', () => { currentTemplate.parameters = VALID_PARAMETERS; currentTemplate.parameterGroups = VALID_PARAMETER_GROUPS; currentTemplate.version = VALID_VERSION; - return admin.remoteConfig().publishTemplate(currentTemplate) + return getRemoteConfig().publishTemplate(currentTemplate) .should.eventually.be.rejected.and.have.property('code', 'remote-config/invalid-argument'); }); }); describe('getTemplate', () => { it('should return the most recently published template', () => { - return admin.remoteConfig().getTemplate() + return getRemoteConfig().getTemplate() .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.conditions.length).to.equal(2); @@ -171,23 +171,23 @@ describe('admin.remoteConfig', () => { describe('getTemplateAtVersion', () => { before(async () => { // obtain the current active template - let activeTemplate = await admin.remoteConfig().getTemplate(); + let activeTemplate = await getRemoteConfig().getTemplate(); // publish a new template to create a new version number activeTemplate.version = { description: versionOneDescription }; - activeTemplate = await admin.remoteConfig().publishTemplate(activeTemplate) + activeTemplate = await getRemoteConfig().publishTemplate(activeTemplate) expect(activeTemplate.version).to.be.not.undefined; versionOneNumber = activeTemplate.version!.versionNumber!; // publish another template to create a second version number activeTemplate.version = { description: versionTwoDescription }; - activeTemplate = await admin.remoteConfig().publishTemplate(activeTemplate) + activeTemplate = await getRemoteConfig().publishTemplate(activeTemplate) expect(activeTemplate.version).to.be.not.undefined; versionTwoNumber = activeTemplate.version!.versionNumber!; }); it('should return the requested template version v1', () => { - return admin.remoteConfig().getTemplateAtVersion(versionOneNumber) + return getRemoteConfig().getTemplateAtVersion(versionOneNumber) .then((template) => { expect(template.etag).matches(/^etag-[0-9]*-[0-9]*$/); expect(template.version).to.be.not.undefined; @@ -199,7 +199,7 @@ describe('admin.remoteConfig', () => { describe('listVersions', () => { it('should return the most recently published 2 versions', () => { - return admin.remoteConfig().listVersions({ + return getRemoteConfig().listVersions({ pageSize: 2, }) .then((response) => { @@ -215,7 +215,7 @@ describe('admin.remoteConfig', () => { describe('rollback', () => { it('verify the most recent template version before rollback to the one prior', () => { - return admin.remoteConfig().getTemplate() + return getRemoteConfig().getTemplate() .then((template) => { expect(template.version).to.be.not.undefined; expect(template.version!.versionNumber).equals(versionTwoNumber); @@ -223,7 +223,7 @@ describe('admin.remoteConfig', () => { }); it('should rollback to the requested version', () => { - return admin.remoteConfig().rollback(versionOneNumber) + return getRemoteConfig().rollback(versionOneNumber) .then((template) => { expect(template.version).to.be.not.undefined; expect(template.version!.updateType).equals('ROLLBACK'); @@ -238,14 +238,14 @@ describe('admin.remoteConfig', () => { INVALID_STRINGS.forEach((invalidJson) => { it(`should throw if the json string is ${JSON.stringify(invalidJson)}`, () => { - expect(() => admin.remoteConfig().createTemplateFromJSON(invalidJson)) + expect(() => getRemoteConfig().createTemplateFromJSON(invalidJson)) .to.throw('JSON string must be a valid non-empty string'); }); }); INVALID_JSON_STRINGS.forEach((invalidJson) => { it(`should throw if the json string is ${JSON.stringify(invalidJson)}`, () => { - expect(() => admin.remoteConfig().createTemplateFromJSON(invalidJson)) + expect(() => getRemoteConfig().createTemplateFromJSON(invalidJson)) .to.throw(/Failed to parse the JSON string/); }); }); @@ -263,14 +263,14 @@ describe('admin.remoteConfig', () => { invalidEtagTemplate.etag = invalidEtag; const jsonString = JSON.stringify(invalidEtagTemplate); it(`should throw if the ETag is ${JSON.stringify(invalidEtag)}`, () => { - expect(() => admin.remoteConfig().createTemplateFromJSON(jsonString)) + expect(() => getRemoteConfig().createTemplateFromJSON(jsonString)) .to.throw(`Invalid Remote Config template: ${jsonString}`); }); }); it('should succeed when a valid json string is provided', () => { const jsonString = JSON.stringify(sourceTemplate); - const newTemplate = admin.remoteConfig().createTemplateFromJSON(jsonString); + const newTemplate = getRemoteConfig().createTemplateFromJSON(jsonString); expect(newTemplate.etag).to.equal(sourceTemplate.etag); expect(() => { (currentTemplate as any).etag = 'new-etag'; diff --git a/test/integration/security-rules.spec.ts b/test/integration/security-rules.spec.ts index 648b74c787..eda5f000f4 100644 --- a/test/integration/security-rules.spec.ts +++ b/test/integration/security-rules.spec.ts @@ -15,8 +15,7 @@ */ import * as chai from 'chai'; - -import * as admin from '../../lib/index'; +import { Ruleset, RulesetMetadata, getSecurityRules } from '../../lib/security-rules/index'; const expect = chai.expect; @@ -47,27 +46,27 @@ describe('admin.securityRules', () => { const rulesetsToDelete: string[] = []; - function scheduleForDelete(ruleset: admin.securityRules.Ruleset): void { + function scheduleForDelete(ruleset: Ruleset): void { rulesetsToDelete.push(ruleset.name); } - function unscheduleForDelete(ruleset: admin.securityRules.Ruleset): void { + function unscheduleForDelete(ruleset: Ruleset): void { rulesetsToDelete.splice(rulesetsToDelete.indexOf(ruleset.name), 1); } function deleteTempRulesets(): Promise { const promises: Array> = []; rulesetsToDelete.forEach((rs) => { - promises.push(admin.securityRules().deleteRuleset(rs)); + promises.push(getSecurityRules().deleteRuleset(rs)); }); rulesetsToDelete.splice(0, rulesetsToDelete.length); // Clear out the array. return Promise.all(promises); } - function createTemporaryRuleset(): Promise { + function createTemporaryRuleset(): Promise { const name = 'firestore.rules'; - const rulesFile = admin.securityRules().createRulesFileFromSource(name, SAMPLE_FIRESTORE_RULES); - return admin.securityRules().createRuleset(rulesFile) + const rulesFile = getSecurityRules().createRulesFileFromSource(name, SAMPLE_FIRESTORE_RULES); + return getSecurityRules().createRuleset(rulesFile) .then((ruleset) => { scheduleForDelete(ruleset); return ruleset; @@ -80,14 +79,14 @@ describe('admin.securityRules', () => { describe('createRulesFileFromSource()', () => { it('creates a RulesFile from the source string', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( RULES_FILE_NAME, SAMPLE_FIRESTORE_RULES); expect(rulesFile.name).to.equal(RULES_FILE_NAME); expect(rulesFile.content).to.equal(SAMPLE_FIRESTORE_RULES); }); it('creates a RulesFile from the source Buffer', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( 'firestore.rules', Buffer.from(SAMPLE_FIRESTORE_RULES, 'utf-8')); expect(rulesFile.name).to.equal(RULES_FILE_NAME); expect(rulesFile.content).to.equal(SAMPLE_FIRESTORE_RULES); @@ -96,9 +95,9 @@ describe('admin.securityRules', () => { describe('createRuleset()', () => { it('creates a new Ruleset from a given RulesFile', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( RULES_FILE_NAME, SAMPLE_FIRESTORE_RULES); - return admin.securityRules().createRuleset(rulesFile) + return getSecurityRules().createRuleset(rulesFile) .then((ruleset) => { scheduleForDelete(ruleset); verifyFirestoreRuleset(ruleset); @@ -106,9 +105,9 @@ describe('admin.securityRules', () => { }); it('rejects with invalid-argument when the source is invalid', () => { - const rulesFile = admin.securityRules().createRulesFileFromSource( + const rulesFile = getSecurityRules().createRulesFileFromSource( RULES_FILE_NAME, 'invalid syntax'); - return admin.securityRules().createRuleset(rulesFile) + return getSecurityRules().createRuleset(rulesFile) .should.eventually.be.rejected.and.have.property('code', 'security-rules/invalid-argument'); }); }); @@ -116,19 +115,19 @@ describe('admin.securityRules', () => { describe('getRuleset()', () => { it('rejects with not-found when the Ruleset does not exist', () => { const nonExistingName = '00000000-1111-2222-3333-444444444444'; - return admin.securityRules().getRuleset(nonExistingName) + return getSecurityRules().getRuleset(nonExistingName) .should.eventually.be.rejected.and.have.property('code', 'security-rules/not-found'); }); it('rejects with invalid-argument when the Ruleset name is invalid', () => { - return admin.securityRules().getRuleset('invalid uuid') + return getSecurityRules().getRuleset('invalid uuid') .should.eventually.be.rejected.and.have.property('code', 'security-rules/invalid-argument'); }); it('resolves with existing Ruleset', () => { return createTemporaryRuleset() .then((expectedRuleset) => - admin.securityRules().getRuleset(expectedRuleset.name) + getSecurityRules().getRuleset(expectedRuleset.name) .then((actualRuleset) => { expect(actualRuleset).to.deep.equal(expectedRuleset); }), @@ -137,15 +136,15 @@ describe('admin.securityRules', () => { }); describe('Cloud Firestore', () => { - let oldRuleset: admin.securityRules.Ruleset | null = null; - let newRuleset: admin.securityRules.Ruleset | null = null; + let oldRuleset: Ruleset | null = null; + let newRuleset: Ruleset | null = null; function revertFirestoreRulesetIfModified(): Promise { if (!newRuleset || !oldRuleset) { return Promise.resolve(); } - return admin.securityRules().releaseFirestoreRuleset(oldRuleset); + return getSecurityRules().releaseFirestoreRuleset(oldRuleset); } afterEach(() => { @@ -153,7 +152,7 @@ describe('admin.securityRules', () => { }); it('getFirestoreRuleset() returns the Ruleset currently in effect', () => { - return admin.securityRules().getFirestoreRuleset() + return getSecurityRules().getFirestoreRuleset() .then((ruleset) => { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); @@ -164,10 +163,10 @@ describe('admin.securityRules', () => { }); it('releaseFirestoreRulesetFromSource() applies the specified Ruleset to Firestore', () => { - return admin.securityRules().getFirestoreRuleset() + return getSecurityRules().getFirestoreRuleset() .then((ruleset) => { oldRuleset = ruleset; - return admin.securityRules().releaseFirestoreRulesetFromSource(SAMPLE_FIRESTORE_RULES); + return getSecurityRules().releaseFirestoreRulesetFromSource(SAMPLE_FIRESTORE_RULES); }) .then((ruleset) => { scheduleForDelete(ruleset); @@ -175,7 +174,7 @@ describe('admin.securityRules', () => { expect(ruleset.name).to.not.equal(oldRuleset!.name); verifyFirestoreRuleset(ruleset); - return admin.securityRules().getFirestoreRuleset(); + return getSecurityRules().getFirestoreRuleset(); }) .then((ruleset) => { expect(ruleset.name).to.equal(newRuleset!.name); @@ -185,15 +184,15 @@ describe('admin.securityRules', () => { }); describe('Cloud Storage', () => { - let oldRuleset: admin.securityRules.Ruleset | null = null; - let newRuleset: admin.securityRules.Ruleset | null = null; + let oldRuleset: Ruleset | null = null; + let newRuleset: Ruleset | null = null; function revertStorageRulesetIfModified(): Promise { if (!newRuleset || !oldRuleset) { return Promise.resolve(); } - return admin.securityRules().releaseStorageRuleset(oldRuleset); + return getSecurityRules().releaseStorageRuleset(oldRuleset); } afterEach(() => { @@ -201,7 +200,7 @@ describe('admin.securityRules', () => { }); it('getStorageRuleset() returns the currently applied Storage rules', () => { - return admin.securityRules().getStorageRuleset() + return getSecurityRules().getStorageRuleset() .then((ruleset) => { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); @@ -212,10 +211,10 @@ describe('admin.securityRules', () => { }); it('releaseStorageRulesetFromSource() applies the specified Ruleset to Storage', () => { - return admin.securityRules().getStorageRuleset() + return getSecurityRules().getStorageRuleset() .then((ruleset) => { oldRuleset = ruleset; - return admin.securityRules().releaseStorageRulesetFromSource(SAMPLE_STORAGE_RULES); + return getSecurityRules().releaseStorageRulesetFromSource(SAMPLE_STORAGE_RULES); }) .then((ruleset) => { scheduleForDelete(ruleset); @@ -225,7 +224,7 @@ describe('admin.securityRules', () => { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); expect(ruleset.createTime).equals(createTime.toUTCString()); - return admin.securityRules().getStorageRuleset(); + return getSecurityRules().getStorageRuleset(); }) .then((ruleset) => { expect(ruleset.name).to.equal(newRuleset!.name); @@ -235,12 +234,10 @@ describe('admin.securityRules', () => { describe('listRulesetMetadata()', () => { it('lists all available Rulesets in pages', () => { - type RulesetMetadata = admin.securityRules.RulesetMetadata; - function listAllRulesets( pageToken?: string, results: RulesetMetadata[] = []): Promise { - return admin.securityRules().listRulesetMetadata(100, pageToken) + return getSecurityRules().listRulesetMetadata(100, pageToken) .then((page) => { results.push(...page.rulesets); if (page.nextPageToken) { @@ -262,7 +259,7 @@ describe('admin.securityRules', () => { }); it('lists the specified number of Rulesets', () => { - return admin.securityRules().listRulesetMetadata(2) + return getSecurityRules().listRulesetMetadata(2) .then((page) => { expect(page.rulesets.length).to.be.at.most(2); expect(page.rulesets.length).to.be.at.least(1); @@ -273,20 +270,20 @@ describe('admin.securityRules', () => { describe('deleteRuleset()', () => { it('rejects with not-found when the Ruleset does not exist', () => { const nonExistingName = '00000000-1111-2222-3333-444444444444'; - return admin.securityRules().deleteRuleset(nonExistingName) + return getSecurityRules().deleteRuleset(nonExistingName) .should.eventually.be.rejected.and.have.property('code', 'security-rules/not-found'); }); it('rejects with invalid-argument when the Ruleset name is invalid', () => { - return admin.securityRules().deleteRuleset('invalid uuid') + return getSecurityRules().deleteRuleset('invalid uuid') .should.eventually.be.rejected.and.have.property('code', 'security-rules/invalid-argument'); }); it('deletes existing Ruleset', () => { return createTemporaryRuleset().then((ruleset) => { - return admin.securityRules().deleteRuleset(ruleset.name) + return getSecurityRules().deleteRuleset(ruleset.name) .then(() => { - return admin.securityRules().getRuleset(ruleset.name) + return getSecurityRules().getRuleset(ruleset.name) .should.eventually.be.rejected.and.have.property('code', 'security-rules/not-found'); }) .then(() => { @@ -296,7 +293,7 @@ describe('admin.securityRules', () => { }); }); - function verifyFirestoreRuleset(ruleset: admin.securityRules.Ruleset): void { + function verifyFirestoreRuleset(ruleset: Ruleset): void { expect(ruleset.name).to.match(RULESET_NAME_PATTERN); const createTime = new Date(ruleset.createTime); expect(ruleset.createTime).equals(createTime.toUTCString()); diff --git a/test/integration/setup.ts b/test/integration/setup.ts index c70362fa89..85bfe5703b 100644 --- a/test/integration/setup.ts +++ b/test/integration/setup.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import fs = require('fs'); import minimist = require('minimist'); import path = require('path'); import { random } from 'lodash'; -import { GoogleOAuthAccessToken } from '../../src/credential/index'; +import { + App, Credential, GoogleOAuthAccessToken, cert, deleteApp, initializeApp, +} from '../../lib/app/index' // eslint-disable-next-line @typescript-eslint/no-var-requires const chalk = require('chalk'); @@ -29,10 +30,10 @@ export let storageBucket: string; export let projectId: string; export let apiKey: string; -export let defaultApp: admin.app.App; -export let nullApp: admin.app.App; -export let nonNullApp: admin.app.App; -export let noServiceAccountApp: admin.app.App; +export let defaultApp: App; +export let nullApp: App; +export let nonNullApp: App; +export let noServiceAccountApp: App; export let cmdArgs: any; @@ -66,21 +67,21 @@ before(() => { databaseUrl = 'https://' + projectId + '.firebaseio.com'; storageBucket = projectId + '.appspot.com'; - defaultApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), + defaultApp = initializeApp({ + credential: cert(serviceAccount), databaseURL: databaseUrl, storageBucket, }); - nullApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), + nullApp = initializeApp({ + credential: cert(serviceAccount), databaseURL: databaseUrl, databaseAuthVariableOverride: null, storageBucket, }, 'null'); - nonNullApp = admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), + nonNullApp = initializeApp({ + credential: cert(serviceAccount), databaseURL: databaseUrl, databaseAuthVariableOverride: { uid: generateRandomString(20), @@ -88,8 +89,8 @@ before(() => { storageBucket, }, 'nonNull'); - noServiceAccountApp = admin.initializeApp({ - credential: new CertificatelessCredential(admin.credential.cert(serviceAccount)), + noServiceAccountApp = initializeApp({ + credential: new CertificatelessCredential(cert(serviceAccount)), serviceAccountId: serviceAccount.client_email, projectId, }, 'noServiceAccount'); @@ -99,17 +100,17 @@ before(() => { after(() => { return Promise.all([ - defaultApp.delete(), - nullApp.delete(), - nonNullApp.delete(), - noServiceAccountApp.delete(), + deleteApp(defaultApp), + deleteApp(nullApp), + deleteApp(nonNullApp), + deleteApp(noServiceAccountApp), ]); }); -class CertificatelessCredential implements admin.credential.Credential { - private readonly delegate: admin.credential.Credential; +class CertificatelessCredential implements Credential { + private readonly delegate: Credential; - constructor(delegate: admin.credential.Credential) { + constructor(delegate: Credential) { this.delegate = delegate; } diff --git a/test/integration/storage.spec.ts b/test/integration/storage.spec.ts index 25b7a39e43..f7467ac9fc 100644 --- a/test/integration/storage.spec.ts +++ b/test/integration/storage.spec.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import * as admin from '../../lib/index'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { Bucket, File } from '@google-cloud/storage'; import { projectId } from './setup'; +import { getStorage } from '../../lib/storage/index'; chai.should(); chai.use(chaiAsPromised); @@ -28,19 +28,19 @@ const expect = chai.expect; describe('admin.storage', () => { it('bucket() returns a handle to the default bucket', () => { - const bucket: Bucket = admin.storage().bucket(); + const bucket: Bucket = getStorage().bucket(); return verifyBucket(bucket, 'storage().bucket()') .should.eventually.be.fulfilled; }); it('bucket(string) returns a handle to the specified bucket', () => { - const bucket: Bucket = admin.storage().bucket(projectId + '.appspot.com'); + const bucket: Bucket = getStorage().bucket(projectId + '.appspot.com'); return verifyBucket(bucket, 'storage().bucket(string)') .should.eventually.be.fulfilled; }); it('bucket(non-existing) returns a handle which can be queried for existence', () => { - const bucket: Bucket = admin.storage().bucket('non.existing'); + const bucket: Bucket = getStorage().bucket('non.existing'); return bucket.exists() .then((data) => { expect(data[0]).to.be.false; From 63be0d3dd9b5783e72b344cf5269bfbff9b59733 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Fri, 5 Mar 2021 14:05:36 -0800 Subject: [PATCH 21/41] chore: Updated package.json file for entry points (#1176) * chore: Added package.json files for all entry points * fix: Added newline at eof * chore: Bumped minimum Node version to 12; Added typeVersions workaround --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 4 +- package.json | 31 ++++- .../typescript/src/example-modular.test.ts | 127 ++++++++++++++++++ .../typescript/src/example.test.ts | 8 +- 5 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 test/integration/typescript/src/example-modular.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b5810ed895..d5b8677630 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x, 14.x] + node-version: [12.x, 14.x] steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4740665e9c..7171414edc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,7 +47,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v1 with: - node-version: 10.x + node-version: 12.x - name: Install and build run: | @@ -115,7 +115,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v1 with: - node-version: 10.x + node-version: 12.x - name: Publish preflight check id: preflight diff --git a/package.json b/package.json index 1285586261..ea4731844d 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "license": "Apache-2.0", "homepage": "https://firebase.google.com/", "engines": { - "node": ">=10.10.0" + "node": ">=12.7.0" }, "scripts": { "build": "gulp build", @@ -55,6 +55,35 @@ "package.json" ], "types": "./lib/index.d.ts", + "typesVersions": { + "*": { + "app": ["lib/app"], + "auth": ["lib/auth"], + "database": ["lib/database"], + "firestore": ["lib/firestore"], + "instance-id": ["lib/instance-id"], + "machine-learning": ["lib/machine-learning"], + "messaging": ["lib/messaging"], + "project-management": ["lib/project-management"], + "remote-config": ["lib/remote-config"], + "security-rules": ["lib/security-rules"], + "storage": ["lib/storage"] + } + }, + "exports": { + ".": "./lib/index.js", + "./app": "./lib/app/index.js", + "./auth": "./lib/auth/index.js", + "./database": "./lib/database/index.js", + "./firestore": "./lib/firestore/index.js", + "./instance-id": "./lib/instance-id/index.js", + "./machine-learning": "./lib/machine-learning/index.js", + "./messaging": "./lib/messaging/index.js", + "./project-management": "./lib/project-management/index.js", + "./remote-config": "./lib/remote-config/index.js", + "./security-rules": "./lib/security-rules/index.js", + "./storage": "./lib/storage/index.js" + }, "dependencies": { "@firebase/database": "^0.8.1", "@firebase/database-types": "^0.6.1", diff --git a/test/integration/typescript/src/example-modular.test.ts b/test/integration/typescript/src/example-modular.test.ts new file mode 100644 index 0000000000..8f607e448a --- /dev/null +++ b/test/integration/typescript/src/example-modular.test.ts @@ -0,0 +1,127 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; + +import { cert, deleteApp, initializeApp, App } from 'firebase-admin/app'; +import { getAuth, Auth } from 'firebase-admin/auth'; +import { getDatabase, getDatabaseWithUrl, Database, ServerValue } from 'firebase-admin/database'; +import { getFirestore, DocumentReference, Firestore, FieldValue } from 'firebase-admin/firestore'; +import { getInstanceId, InstanceId } from 'firebase-admin/instance-id'; +import { getMachineLearning, MachineLearning } from 'firebase-admin/machine-learning'; +import { getMessaging, Messaging } from 'firebase-admin/messaging'; +import { getProjectManagement, ProjectManagement } from 'firebase-admin/project-management'; +import { getRemoteConfig, RemoteConfig } from 'firebase-admin/remote-config'; +import { getSecurityRules, SecurityRules } from 'firebase-admin/security-rules'; +import { getStorage, Storage } from 'firebase-admin/storage'; + +import { Bucket } from '@google-cloud/storage'; + + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const serviceAccount = require('../mock.key.json'); + +describe('Modular API', () => { + let app: App; + + before(() => { + app = initializeApp({ + credential: cert(serviceAccount), + databaseURL: 'https://mock.firebaseio.com' + }, 'TestApp'); + }); + + after(() => { + return deleteApp(app); + }); + + it('Should return an initialized App', () => { + expect(app.name).to.equal('TestApp'); + }); + + it('Should return an Auth client', () => { + const client = getAuth(app); + expect(client).to.be.instanceOf(Auth); + }); + + it('Should return a Messaging client', () => { + const client = getMessaging(app); + expect(client).to.be.instanceOf(Messaging); + }); + + it('Should return a ProjectManagement client', () => { + const client = getProjectManagement(app); + expect(client).to.be.instanceOf(ProjectManagement); + }); + + it('Should return a SecurityRules client', () => { + const client = getSecurityRules(app); + expect(client).to.be.instanceOf(SecurityRules); + }); + + it('Should return a Database client', () => { + const db: Database = getDatabase(app); + expect(db).to.be.not.undefined; + expect(typeof db.getRules).to.equal('function'); + }); + + it('Should return a Database client for URL', () => { + const db: Database = getDatabaseWithUrl('https://other-mock.firebaseio.com', app); + expect(db).to.be.not.undefined; + expect(typeof db.getRules).to.equal('function'); + }); + + it('Should return a Database ServerValue', () => { + expect(ServerValue.increment(1)).to.be.not.undefined; + }); + + it('Should return a Cloud Storage client', () => { + const storage = getStorage(app); + expect(storage).to.be.instanceOf(Storage) + const bucket: Bucket = storage.bucket('TestBucket'); + expect(bucket.name).to.equal('TestBucket'); + }); + + it('Should return a Firestore client', () => { + const firestore = getFirestore(app); + expect(firestore).to.be.instanceOf(Firestore); + }); + + it('Should return a Firestore FieldValue', () => { + expect(FieldValue.increment(1)).to.be.not.undefined; + }); + + it('Should return a DocumentReference', () => { + const ref = getFirestore(app).collection('test').doc(); + expect(ref).to.be.instanceOf(DocumentReference); + }); + + it('Should return an InstanceId client', () => { + const client = getInstanceId(app); + expect(client).to.be.instanceOf(InstanceId); + }); + + it('Should return a MachineLearning client', () => { + const client = getMachineLearning(app); + expect(client).to.be.instanceOf(MachineLearning); + }); + + it('Should return a RemoteConfig client', () => { + const client = getRemoteConfig(app); + expect(client).to.be.instanceOf(RemoteConfig); + }); +}); diff --git a/test/integration/typescript/src/example.test.ts b/test/integration/typescript/src/example.test.ts index 5aa63c083d..2d97196477 100644 --- a/test/integration/typescript/src/example.test.ts +++ b/test/integration/typescript/src/example.test.ts @@ -25,8 +25,12 @@ import * as admin from 'firebase-admin'; // eslint-disable-next-line @typescript-eslint/no-var-requires const serviceAccount = require('../mock.key.json'); -describe('Init App', () => { - const app: admin.app.App = initApp(serviceAccount, 'TestApp'); +describe('Legacy API', () => { + let app: admin.app.App; + + before(() => { + app = initApp(serviceAccount, 'TestApp'); + }); after(() => { return app.delete(); From 0ed3e1ce4665198ec32350cab3f2622bf397f139 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 24 Mar 2021 17:59:56 -0700 Subject: [PATCH 22/41] fix: Separated service namespaces from module entry points (#1197) --- etc/firebase-admin.api.md | 33 +---- etc/firebase-admin.auth.api.md | 109 --------------- etc/firebase-admin.database.api.md | 28 +--- etc/firebase-admin.firestore.api.md | 40 +----- etc/firebase-admin.instance-id.api.md | 9 -- etc/firebase-admin.machine-learning.api.md | 25 ---- etc/firebase-admin.messaging.api.md | 73 ---------- etc/firebase-admin.project-management.api.md | 23 ---- etc/firebase-admin.remote-config.api.md | 33 ----- etc/firebase-admin.security-rules.api.md | 17 --- etc/firebase-admin.storage.api.md | 9 -- src/app/firebase-namespace.ts | 16 +-- src/auth/auth-namespace.ts | 34 +---- src/auth/base-auth.ts | 3 +- src/auth/index.ts | 36 ++++- src/database/database-namespace.ts | 71 ++++++++++ src/database/index.ts | 53 -------- src/default-namespace.d.ts | 11 -- src/firebase-namespace-api.ts | 36 +++-- src/firestore/firestore-namespace.ts | 57 ++++++++ src/firestore/index.ts | 39 ------ src/instance-id/index.ts | 39 ------ src/instance-id/instance-id-namespace.ts | 39 ++++++ src/machine-learning/index.ts | 59 --------- .../machine-learning-namespace.ts | 75 +++++++++++ src/messaging/index.ts | 109 --------------- src/messaging/messaging-namespace.ts | 125 ++++++++++++++++++ src/project-management/index.ts | 57 -------- .../project-management-namespace.ts | 73 ++++++++++ src/remote-config/index.ts | 64 --------- src/remote-config/remote-config-namespace.ts | 80 +++++++++++ src/security-rules/index.ts | 47 ------- .../security-rules-namespace.ts | 63 +++++++++ src/storage/index.ts | 31 ----- src/storage/storage-namespace.ts | 47 +++++++ src/utils/error.ts | 2 +- test/unit/app/firebase-app.spec.ts | 15 +-- test/unit/app/firebase-namespace.spec.ts | 16 +-- 38 files changed, 714 insertions(+), 982 deletions(-) create mode 100644 src/database/database-namespace.ts create mode 100644 src/firestore/firestore-namespace.ts create mode 100644 src/instance-id/instance-id-namespace.ts create mode 100644 src/machine-learning/machine-learning-namespace.ts create mode 100644 src/messaging/messaging-namespace.ts create mode 100644 src/project-management/project-management-namespace.ts create mode 100644 src/remote-config/remote-config-namespace.ts create mode 100644 src/security-rules/security-rules-namespace.ts create mode 100644 src/storage/storage-namespace.ts diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 00fe991142..5b4b580cdd 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -46,11 +46,9 @@ export namespace app { } } -// @public -export function applicationDefault(httpAgent?: Agent): Credential; - // @public export interface AppOptions { + // Warning: (ae-forgotten-export) The symbol "Credential" needs to be exported by the entry point default-namespace.d.ts credential?: Credential; databaseAuthVariableOverride?: object | null; databaseURL?: string; @@ -274,20 +272,15 @@ export namespace auth { export type UserRecord = UserRecord; } -// @public -export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential; - -// @public -export interface Credential { - getAccessToken(): Promise; -} - // @public (undocumented) export namespace credential { export type Credential = Credential; - const applicationDefault: typeof applicationDefault; - const cert: typeof cert; - const refreshToken: typeof refreshToken; + const // Warning: (ae-forgotten-export) The symbol "applicationDefault" needs to be exported by the entry point default-namespace.d.ts + applicationDefault: typeof applicationDefault; + const // Warning: (ae-forgotten-export) The symbol "cert" needs to be exported by the entry point default-namespace.d.ts + cert: typeof cert; + const // Warning: (ae-forgotten-export) The symbol "refreshToken" needs to be exported by the entry point default-namespace.d.ts + refreshToken: typeof refreshToken; } // @public @@ -316,9 +309,6 @@ export namespace database { const ServerValue: rtdb.ServerValue; } -// @public -export function deleteApp(app: App): Promise; - // @public export interface FirebaseArrayIndexError { error: FirebaseError; @@ -369,12 +359,6 @@ export namespace firestore { import setLogFunction = _firestore.setLogFunction; } -// @public (undocumented) -export function getApp(name?: string): App; - -// @public (undocumented) -export function getApps(): App[]; - // @public export interface GoogleOAuthAccessToken { // (undocumented) @@ -618,9 +602,6 @@ export namespace projectManagement { export type ShaCertificate = ShaCertificate; } -// @public -export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; - // @public export function remoteConfig(app?: App): remoteConfig.RemoteConfig; diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 820a30cda7..768cd26b8a 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -28,115 +28,6 @@ export class Auth extends BaseAuth { tenantManager(): TenantManager; } -// @public -export function auth(app?: App): auth.Auth; - -// @public (undocumented) -export namespace auth { - // (undocumented) - export type ActionCodeSettings = ActionCodeSettings; - // (undocumented) - export type Auth = Auth; - // (undocumented) - export type AuthFactorType = AuthFactorType; - // (undocumented) - export type AuthProviderConfig = AuthProviderConfig; - // (undocumented) - export type AuthProviderConfigFilter = AuthProviderConfigFilter; - // (undocumented) - export type BaseAuth = BaseAuth; - // (undocumented) - export type CreateMultiFactorInfoRequest = CreateMultiFactorInfoRequest; - // (undocumented) - export type CreatePhoneMultiFactorInfoRequest = CreatePhoneMultiFactorInfoRequest; - // (undocumented) - export type CreateRequest = CreateRequest; - // (undocumented) - export type CreateTenantRequest = CreateTenantRequest; - // (undocumented) - export type DecodedIdToken = DecodedIdToken; - // (undocumented) - export type DeleteUsersResult = DeleteUsersResult; - // (undocumented) - export type EmailIdentifier = EmailIdentifier; - // (undocumented) - export type EmailSignInProviderConfig = EmailSignInProviderConfig; - // (undocumented) - export type GetUsersResult = GetUsersResult; - // (undocumented) - export type HashAlgorithmType = HashAlgorithmType; - // (undocumented) - export type ListProviderConfigResults = ListProviderConfigResults; - // (undocumented) - export type ListTenantsResult = ListTenantsResult; - // (undocumented) - export type ListUsersResult = ListUsersResult; - // (undocumented) - export type MultiFactorConfig = MultiFactorConfig; - // (undocumented) - export type MultiFactorConfigState = MultiFactorConfigState; - // (undocumented) - export type MultiFactorCreateSettings = MultiFactorCreateSettings; - // (undocumented) - export type MultiFactorInfo = MultiFactorInfo; - // (undocumented) - export type MultiFactorSettings = MultiFactorSettings; - // (undocumented) - export type MultiFactorUpdateSettings = MultiFactorUpdateSettings; - // (undocumented) - export type OIDCAuthProviderConfig = OIDCAuthProviderConfig; - // (undocumented) - export type OIDCUpdateAuthProviderRequest = OIDCUpdateAuthProviderRequest; - // (undocumented) - export type PhoneIdentifier = PhoneIdentifier; - // (undocumented) - export type PhoneMultiFactorInfo = PhoneMultiFactorInfo; - // (undocumented) - export type ProviderIdentifier = ProviderIdentifier; - // (undocumented) - export type SAMLAuthProviderConfig = SAMLAuthProviderConfig; - // (undocumented) - export type SAMLUpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest; - // (undocumented) - export type SessionCookieOptions = SessionCookieOptions; - // (undocumented) - export type Tenant = Tenant; - // (undocumented) - export type TenantAwareAuth = TenantAwareAuth; - // (undocumented) - export type TenantManager = TenantManager; - // (undocumented) - export type UidIdentifier = UidIdentifier; - // (undocumented) - export type UpdateAuthProviderRequest = UpdateAuthProviderRequest; - // (undocumented) - export type UpdateMultiFactorInfoRequest = UpdateMultiFactorInfoRequest; - // (undocumented) - export type UpdatePhoneMultiFactorInfoRequest = UpdatePhoneMultiFactorInfoRequest; - // (undocumented) - export type UpdateRequest = UpdateRequest; - // (undocumented) - export type UpdateTenantRequest = UpdateTenantRequest; - // (undocumented) - export type UserIdentifier = UserIdentifier; - // (undocumented) - export type UserImportOptions = UserImportOptions; - // (undocumented) - export type UserImportRecord = UserImportRecord; - // (undocumented) - export type UserImportResult = UserImportResult; - // (undocumented) - export type UserInfo = UserInfo; - // (undocumented) - export type UserMetadata = UserMetadata; - // (undocumented) - export type UserMetadataRequest = UserMetadataRequest; - // (undocumented) - export type UserProviderRequest = UserProviderRequest; - // (undocumented) - export type UserRecord = UserRecord; -} - // @public export type AuthFactorType = 'phone'; diff --git a/etc/firebase-admin.database.api.md b/etc/firebase-admin.database.api.md index 3b9fe43324..f76fd77fe4 100644 --- a/etc/firebase-admin.database.api.md +++ b/etc/firebase-admin.database.api.md @@ -21,32 +21,6 @@ export interface Database extends FirebaseDatabase { setRules(source: string | Buffer | object): Promise; } -// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts -// -// @public -export function database(app?: App): database.Database; - -// @public (undocumented) -export namespace database { - // (undocumented) - export type Database = Database; - // (undocumented) - export type DataSnapshot = rtdb.DataSnapshot; - // (undocumented) - export type EventType = rtdb.EventType; - // (undocumented) - export type OnDisconnect = rtdb.OnDisconnect; - // (undocumented) - export type Query = rtdb.Query; - // (undocumented) - export type Reference = rtdb.Reference; - // (undocumented) - export type ThenableReference = rtdb.ThenableReference; - const // (undocumented) - enableLogging: typeof rtdb.enableLogging; - const ServerValue: rtdb.ServerValue; -} - export { DataSnapshot } // @public @@ -54,6 +28,8 @@ export const enableLogging: typeof rtdb.enableLogging; export { EventType } +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// // @public export function getDatabase(app?: App): Database; diff --git a/etc/firebase-admin.firestore.api.md b/etc/firebase-admin.firestore.api.md index e060e6d4c2..e1c0fbe0fc 100644 --- a/etc/firebase-admin.firestore.api.md +++ b/etc/firebase-admin.firestore.api.md @@ -57,48 +57,12 @@ export { FieldValue } export { Firestore } -// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export function firestore(app?: App): _firestore.Firestore; - -// @public (undocumented) -export namespace firestore { - import v1beta1 = _firestore.v1beta1; - import v1 = _firestore.v1; - import BulkWriter = _firestore.BulkWriter; - import BulkWriterOptions = _firestore.BulkWriterOptions; - import CollectionGroup = _firestore.CollectionGroup; - import CollectionReference = _firestore.CollectionReference; - import DocumentChangeType = _firestore.DocumentChangeType; - import DocumentData = _firestore.DocumentData; - import DocumentReference = _firestore.DocumentReference; - import DocumentSnapshot = _firestore.DocumentSnapshot; - import FieldPath = _firestore.FieldPath; - import FieldValue = _firestore.FieldValue; - import Firestore = _firestore.Firestore; - import FirestoreDataConverter = _firestore.FirestoreDataConverter; - import GeoPoint = _firestore.GeoPoint; - import GrpcStatus = _firestore.GrpcStatus; - import Precondition = _firestore.Precondition; - import Query = _firestore.Query; - import QueryDocumentSnapshot = _firestore.QueryDocumentSnapshot; - import QueryPartition = _firestore.QueryPartition; - import QuerySnapshot = _firestore.QuerySnapshot; - import ReadOptions = _firestore.ReadOptions; - import Settings = _firestore.Settings; - import Timestamp = _firestore.Timestamp; - import Transaction = _firestore.Transaction; - import UpdateData = _firestore.UpdateData; - import WriteBatch = _firestore.WriteBatch; - import WriteResult = _firestore.WriteResult; - import setLogFunction = _firestore.setLogFunction; -} - export { FirestoreDataConverter } export { GeoPoint } +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// // @public (undocumented) export function getFirestore(app?: App): _firestore.Firestore; diff --git a/etc/firebase-admin.instance-id.api.md b/etc/firebase-admin.instance-id.api.md index a391fbacc7..81528b204c 100644 --- a/etc/firebase-admin.instance-id.api.md +++ b/etc/firebase-admin.instance-id.api.md @@ -17,15 +17,6 @@ export class InstanceId { deleteInstanceId(instanceId: string): Promise; } -// @public -export function instanceId(app?: App): instanceId.InstanceId; - -// @public (undocumented) -export namespace instanceId { - // (undocumented) - export type InstanceId = InstanceId; -} - // (No @packageDocumentation comment for this package) diff --git a/etc/firebase-admin.machine-learning.api.md b/etc/firebase-admin.machine-learning.api.md index 2560737b4a..70b0b778ab 100644 --- a/etc/firebase-admin.machine-learning.api.md +++ b/etc/firebase-admin.machine-learning.api.md @@ -52,31 +52,6 @@ export class MachineLearning { updateModel(modelId: string, model: ModelOptions): Promise; } -// @public -export function machineLearning(app?: App): machineLearning.MachineLearning; - -// @public (undocumented) -export namespace machineLearning { - // (undocumented) - export type AutoMLTfliteModelOptions = AutoMLTfliteModelOptions; - // (undocumented) - export type GcsTfliteModelOptions = GcsTfliteModelOptions; - // (undocumented) - export type ListModelsOptions = ListModelsOptions; - // (undocumented) - export type ListModelsResult = ListModelsResult; - // (undocumented) - export type MachineLearning = MachineLearning; - // (undocumented) - export type Model = Model; - // (undocumented) - export type ModelOptions = ModelOptions; - // (undocumented) - export type ModelOptionsBase = ModelOptionsBase; - // (undocumented) - export type TFLiteModel = TFLiteModel; -} - // @public export class Model { get createTime(): string; diff --git a/etc/firebase-admin.messaging.api.md b/etc/firebase-admin.messaging.api.md index eec7b21f45..361a44b70e 100644 --- a/etc/firebase-admin.messaging.api.md +++ b/etc/firebase-admin.messaging.api.md @@ -193,79 +193,6 @@ export class Messaging { unsubscribeFromTopic(registrationTokenOrTokens: string | string[], topic: string): Promise; } -// @public -export function messaging(app?: App): messaging.Messaging; - -// @public (undocumented) -export namespace messaging { - // (undocumented) - export type AndroidConfig = AndroidConfig; - // (undocumented) - export type AndroidFcmOptions = AndroidFcmOptions; - // (undocumented) - export type AndroidNotification = AndroidNotification; - // (undocumented) - export type ApnsConfig = ApnsConfig; - // (undocumented) - export type ApnsFcmOptions = ApnsFcmOptions; - // (undocumented) - export type ApnsPayload = ApnsPayload; - // (undocumented) - export type Aps = Aps; - // (undocumented) - export type ApsAlert = ApsAlert; - // (undocumented) - export type BatchResponse = BatchResponse; - // (undocumented) - export type ConditionMessage = ConditionMessage; - // (undocumented) - export type CriticalSound = CriticalSound; - // (undocumented) - export type DataMessagePayload = DataMessagePayload; - // (undocumented) - export type FcmOptions = FcmOptions; - // (undocumented) - export type LightSettings = LightSettings; - // (undocumented) - export type Message = Message; - // (undocumented) - export type Messaging = Messaging; - // (undocumented) - export type MessagingConditionResponse = MessagingConditionResponse; - // (undocumented) - export type MessagingDeviceGroupResponse = MessagingDeviceGroupResponse; - // (undocumented) - export type MessagingDeviceResult = MessagingDeviceResult; - // (undocumented) - export type MessagingDevicesResponse = MessagingDevicesResponse; - // (undocumented) - export type MessagingOptions = MessagingOptions; - // (undocumented) - export type MessagingPayload = MessagingPayload; - // (undocumented) - export type MessagingTopicManagementResponse = MessagingTopicManagementResponse; - // (undocumented) - export type MessagingTopicResponse = MessagingTopicResponse; - // (undocumented) - export type MulticastMessage = MulticastMessage; - // (undocumented) - export type Notification = Notification; - // (undocumented) - export type NotificationMessagePayload = NotificationMessagePayload; - // (undocumented) - export type SendResponse = SendResponse; - // (undocumented) - export type TokenMessage = TokenMessage; - // (undocumented) - export type TopicMessage = TopicMessage; - // (undocumented) - export type WebpushConfig = WebpushConfig; - // (undocumented) - export type WebpushFcmOptions = WebpushFcmOptions; - // (undocumented) - export type WebpushNotification = WebpushNotification; -} - // @public export interface MessagingConditionResponse { messageId: number; diff --git a/etc/firebase-admin.project-management.api.md b/etc/firebase-admin.project-management.api.md index eb166be66c..4b69129073 100644 --- a/etc/firebase-admin.project-management.api.md +++ b/etc/firebase-admin.project-management.api.md @@ -77,29 +77,6 @@ export class ProjectManagement { shaCertificate(shaHash: string): ShaCertificate; } -// @public -export function projectManagement(app?: App): projectManagement.ProjectManagement; - -// @public (undocumented) -export namespace projectManagement { - // (undocumented) - export type AndroidApp = AndroidApp; - // (undocumented) - export type AndroidAppMetadata = AndroidAppMetadata; - // (undocumented) - export type AppMetadata = AppMetadata; - // (undocumented) - export type AppPlatform = AppPlatform; - // (undocumented) - export type IosApp = IosApp; - // (undocumented) - export type IosAppMetadata = IosAppMetadata; - // (undocumented) - export type ProjectManagement = ProjectManagement; - // (undocumented) - export type ShaCertificate = ShaCertificate; -} - // @public export class ShaCertificate { readonly certType: ('sha1' | 'sha256'); diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md index 063c2b4167..d85e53d66f 100644 --- a/etc/firebase-admin.remote-config.api.md +++ b/etc/firebase-admin.remote-config.api.md @@ -51,39 +51,6 @@ export class RemoteConfig { validateTemplate(template: RemoteConfigTemplate): Promise; } -// @public -export function remoteConfig(app?: App): remoteConfig.RemoteConfig; - -// @public (undocumented) -export namespace remoteConfig { - // (undocumented) - export type ExplicitParameterValue = ExplicitParameterValue; - // (undocumented) - export type InAppDefaultValue = InAppDefaultValue; - // (undocumented) - export type ListVersionsOptions = ListVersionsOptions; - // (undocumented) - export type ListVersionsResult = ListVersionsResult; - // (undocumented) - export type RemoteConfig = RemoteConfig; - // (undocumented) - export type RemoteConfigCondition = RemoteConfigCondition; - // (undocumented) - export type RemoteConfigParameter = RemoteConfigParameter; - // (undocumented) - export type RemoteConfigParameterGroup = RemoteConfigParameterGroup; - // (undocumented) - export type RemoteConfigParameterValue = RemoteConfigParameterValue; - // (undocumented) - export type RemoteConfigTemplate = RemoteConfigTemplate; - // (undocumented) - export type RemoteConfigUser = RemoteConfigUser; - // (undocumented) - export type TagColor = TagColor; - // (undocumented) - export type Version = Version; -} - // @public export interface RemoteConfigCondition { expression: string; diff --git a/etc/firebase-admin.security-rules.api.md b/etc/firebase-admin.security-rules.api.md index bef96e2edf..9fb95e3493 100644 --- a/etc/firebase-admin.security-rules.api.md +++ b/etc/firebase-admin.security-rules.api.md @@ -56,23 +56,6 @@ export class SecurityRules { releaseStorageRulesetFromSource(source: string | Buffer, bucket?: string): Promise; } -// @public -export function securityRules(app?: App): securityRules.SecurityRules; - -// @public (undocumented) -export namespace securityRules { - // (undocumented) - export type Ruleset = Ruleset; - // (undocumented) - export type RulesetMetadata = RulesetMetadata; - // (undocumented) - export type RulesetMetadataList = RulesetMetadataList; - // (undocumented) - export type RulesFile = RulesFile; - // (undocumented) - export type SecurityRules = SecurityRules; -} - // (No @packageDocumentation comment for this package) diff --git a/etc/firebase-admin.storage.api.md b/etc/firebase-admin.storage.api.md index f5049af101..b0b7199eb7 100644 --- a/etc/firebase-admin.storage.api.md +++ b/etc/firebase-admin.storage.api.md @@ -18,15 +18,6 @@ export class Storage { bucket(name?: string): Bucket; } -// @public -export function storage(app?: App): storage.Storage; - -// @public (undocumented) -export namespace storage { - // (undocumented) - export type Storage = Storage; -} - // (No @packageDocumentation comment for this package) diff --git a/src/app/firebase-namespace.ts b/src/app/firebase-namespace.ts index 502a864929..f7c0280d53 100644 --- a/src/app/firebase-namespace.ts +++ b/src/app/firebase-namespace.ts @@ -18,22 +18,14 @@ import fs = require('fs'); import { AppErrorCodes, FirebaseAppError } from '../utils/error'; -import { AppOptions, app } from '../firebase-namespace-api'; +import { + app, auth, messaging, machineLearning, storage, firestore, database, + instanceId, projectManagement, securityRules , remoteConfig, AppOptions, +} from '../firebase-namespace-api'; import { FirebaseApp } from './firebase-app'; import { cert, refreshToken, applicationDefault } from './credential-factory'; import { getApplicationDefault } from './credential-internal'; -import { auth } from '../auth/index'; -import { database } from '../database/index'; -import { firestore } from '../firestore/index'; -import { instanceId } from '../instance-id/index'; -import { machineLearning } from '../machine-learning/index'; -import { messaging } from '../messaging/index'; -import { projectManagement } from '../project-management/index'; -import { remoteConfig } from '../remote-config/index'; -import { securityRules } from '../security-rules/index'; -import { storage } from '../storage/index'; - import * as validator from '../utils/validator'; import { getSdkVersion } from '../utils/index'; diff --git a/src/auth/auth-namespace.ts b/src/auth/auth-namespace.ts index 7924bcf479..59c7e31211 100644 --- a/src/auth/auth-namespace.ts +++ b/src/auth/auth-namespace.ts @@ -14,9 +14,7 @@ * limitations under the License. */ -import { App, getApp } from '../app/index'; -import { Auth } from './auth'; -import { FirebaseApp } from '../app/firebase-app'; +import { App } from '../app/index'; // Import all public types with aliases, and re-export from the auth namespace. @@ -95,36 +93,6 @@ import { UserRecord as TUserRecord, } from './user-record'; -/** - * Gets the {@link auth.Auth `Auth`} service for the default app or a - * given app. - * - * `getAuth()` can be called with no arguments to access the default app's - * {@link auth.Auth `Auth`} service or as `getAuth(app)` to access the - * {@link auth.Auth `Auth`} service associated with a specific app. - * - * @example - * ```javascript - * // Get the Auth service for the default app - * const defaultAuth = getAuth(); - * ``` - * - * @example - * ```javascript - * // Get the Auth service for a given app - * const otherAuth = getAuth(otherApp); - * ``` - * - */ -export function getAuth(app?: App): Auth { - if (typeof app === 'undefined') { - app = getApp(); - } - - const firebaseApp: FirebaseApp = app as FirebaseApp; - return firebaseApp.getOrInitService('auth', (app) => new Auth(app)); -} - /** * Gets the {@link auth.Auth `Auth`} service for the default app or a * given app. diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts index fd6dcd39c2..c3a4f406e5 100644 --- a/src/auth/base-auth.ts +++ b/src/auth/base-auth.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import { App } from '../app'; -import { FirebaseArrayIndexError } from '../firebase-namespace-api'; +import { App, FirebaseArrayIndexError } from '../app'; import { AuthClientErrorCode, ErrorInfo, FirebaseAuthError } from '../utils/error'; import * as validator from '../utils/validator'; diff --git a/src/auth/index.ts b/src/auth/index.ts index 619abf366c..403deafedf 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -14,6 +14,40 @@ * limitations under the License. */ +import { App, getApp } from '../app/index'; +import { FirebaseApp } from '../app/firebase-app'; +import { Auth } from './auth'; + +/** + * Gets the {@link auth.Auth `Auth`} service for the default app or a + * given app. + * + * `getAuth()` can be called with no arguments to access the default app's + * {@link auth.Auth `Auth`} service or as `getAuth(app)` to access the + * {@link auth.Auth `Auth`} service associated with a specific app. + * + * @example + * ```javascript + * // Get the Auth service for the default app + * const defaultAuth = getAuth(); + * ``` + * + * @example + * ```javascript + * // Get the Auth service for a given app + * const otherAuth = getAuth(otherApp); + * ``` + * + */ +export function getAuth(app?: App): Auth { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('auth', (app) => new Auth(app)); +} + export { ActionCodeSettings } from './action-code-settings-builder'; export { @@ -90,5 +124,3 @@ export { UserMetadata, UserRecord, } from './user-record'; - -export { getAuth, auth } from './auth-namespace'; diff --git a/src/database/database-namespace.ts b/src/database/database-namespace.ts new file mode 100644 index 0000000000..b8882f8997 --- /dev/null +++ b/src/database/database-namespace.ts @@ -0,0 +1,71 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as rtdb from '@firebase/database-types'; +import { App } from '../app'; +import { Database as TDatabase } from './database'; + +/** + * Gets the {@link database.Database `Database`} service for the default + * app or a given app. + * + * `admin.database()` can be called with no arguments to access the default + * app's {@link database.Database `Database`} service or as + * `admin.database(app)` to access the + * {@link database.Database `Database`} service associated with a specific + * app. + * + * `admin.database` is also a namespace that can be used to access global + * constants and methods associated with the `Database` service. + * + * @example + * ```javascript + * // Get the Database service for the default app + * var defaultDatabase = admin.database(); + * ``` + * + * @example + * ```javascript + * // Get the Database service for a specific app + * var otherDatabase = admin.database(app); + * ``` + * + * @param App whose `Database` service to + * return. If not provided, the default `Database` service will be returned. + * + * @return The default `Database` service if no app + * is provided or the `Database` service associated with the provided app. + */ +export declare function database(app?: App): database.Database; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace database { + export type Database = TDatabase; + export type DataSnapshot = rtdb.DataSnapshot; + export type EventType = rtdb.EventType; + export type OnDisconnect = rtdb.OnDisconnect; + export type Query = rtdb.Query; + export type Reference = rtdb.Reference; + export type ThenableReference = rtdb.ThenableReference; + + export declare const enableLogging: typeof rtdb.enableLogging; + + /** + * [`ServerValue`](https://firebase.google.com/docs/reference/js/firebase.database.ServerValue) + * module from the `@firebase/database` package. + */ + export declare const ServerValue: rtdb.ServerValue; +} diff --git a/src/database/index.ts b/src/database/index.ts index 4cc274cf49..060a68c679 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -23,7 +23,6 @@ import { import { App, getApp } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { Database, DatabaseService } from './database'; -import { Database as TDatabase } from './database'; export { Database }; export { @@ -121,55 +120,3 @@ function getDatabaseInstance(options: { url?: string; app?: App }): Database { const dbService = firebaseApp.getOrInitService('database', (app) => new DatabaseService(app)); return dbService.getDatabase(options.url); } - -/** - * Gets the {@link database.Database `Database`} service for the default - * app or a given app. - * - * `admin.database()` can be called with no arguments to access the default - * app's {@link database.Database `Database`} service or as - * `admin.database(app)` to access the - * {@link database.Database `Database`} service associated with a specific - * app. - * - * `admin.database` is also a namespace that can be used to access global - * constants and methods associated with the `Database` service. - * - * @example - * ```javascript - * // Get the Database service for the default app - * var defaultDatabase = admin.database(); - * ``` - * - * @example - * ```javascript - * // Get the Database service for a specific app - * var otherDatabase = admin.database(app); - * ``` - * - * @param App whose `Database` service to - * return. If not provided, the default `Database` service will be returned. - * - * @return The default `Database` service if no app - * is provided or the `Database` service associated with the provided app. - */ -export declare function database(app?: App): database.Database; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace database { - export type Database = TDatabase; - export type DataSnapshot = rtdb.DataSnapshot; - export type EventType = rtdb.EventType; - export type OnDisconnect = rtdb.OnDisconnect; - export type Query = rtdb.Query; - export type Reference = rtdb.Reference; - export type ThenableReference = rtdb.ThenableReference; - - export declare const enableLogging: typeof rtdb.enableLogging; - - /** - * [`ServerValue`](https://firebase.google.com/docs/reference/js/firebase.database.ServerValue) - * module from the `@firebase/database` package. - */ - export declare const ServerValue: rtdb.ServerValue; -} diff --git a/src/default-namespace.d.ts b/src/default-namespace.d.ts index 188f904275..22ea79d419 100644 --- a/src/default-namespace.d.ts +++ b/src/default-namespace.d.ts @@ -14,15 +14,4 @@ * limitations under the License. */ -export * from './credential/index'; export * from './firebase-namespace-api'; -export { auth } from './auth/index'; -export { database } from './database/index'; -export { firestore } from './firestore/index'; -export { instanceId } from './instance-id/index'; -export { machineLearning } from './machine-learning/index'; -export { messaging } from './messaging/index'; -export { projectManagement } from './project-management/index'; -export { remoteConfig } from './remote-config/index'; -export { securityRules } from './security-rules/index'; -export { storage } from './storage/index'; diff --git a/src/firebase-namespace-api.ts b/src/firebase-namespace-api.ts index 798d8c426c..4bfe9298a2 100644 --- a/src/firebase-namespace-api.ts +++ b/src/firebase-namespace-api.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2020 Google Inc. + * Copyright 2021 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,20 +14,20 @@ * limitations under the License. */ -import { auth } from './auth/index'; -import { database } from './database/index'; -import { firestore } from './firestore/index'; -import { instanceId } from './instance-id/index'; -import { machineLearning } from './machine-learning/index'; -import { messaging } from './messaging/index'; -import { projectManagement } from './project-management/index'; -import { remoteConfig } from './remote-config/index'; -import { securityRules } from './security-rules/index'; -import { storage } from './storage/index'; +import { auth } from './auth/auth-namespace'; +import { database } from './database/database-namespace'; +import { firestore } from './firestore/firestore-namespace'; +import { instanceId } from './instance-id/instance-id-namespace'; +import { machineLearning } from './machine-learning/machine-learning-namespace'; +import { messaging } from './messaging/messaging-namespace'; +import { projectManagement } from './project-management/project-management-namespace'; +import { remoteConfig } from './remote-config/remote-config-namespace'; +import { securityRules } from './security-rules/security-rules-namespace'; +import { storage } from './storage/storage-namespace'; import { App as AppCore, AppOptions } from './app/index'; -export * from './app/index'; +export { App, AppOptions, FirebaseError, FirebaseArrayIndexError } from './app/index'; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace app { @@ -74,6 +74,18 @@ export namespace app { } } +export * from './credential/index'; +export { auth } from './auth/auth-namespace'; +export { database } from './database/database-namespace'; +export { firestore } from './firestore/firestore-namespace'; +export { instanceId } from './instance-id/instance-id-namespace'; +export { machineLearning } from './machine-learning/machine-learning-namespace'; +export { messaging } from './messaging/messaging-namespace'; +export { projectManagement } from './project-management/project-management-namespace'; +export { remoteConfig } from './remote-config/remote-config-namespace'; +export { securityRules } from './security-rules/security-rules-namespace'; +export { storage } from './storage/storage-namespace'; + // Declare other top-level members of the admin namespace below. Unfortunately, there's no // compile-time mechanism to ensure that the FirebaseNamespace class actually provides these // signatures. But this part of the API is quite small and stable. It should be easy enough to diff --git a/src/firestore/firestore-namespace.ts b/src/firestore/firestore-namespace.ts new file mode 100644 index 0000000000..5f1cef8cfa --- /dev/null +++ b/src/firestore/firestore-namespace.ts @@ -0,0 +1,57 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as _firestore from '@google-cloud/firestore'; +import { App } from '../app'; + +export declare function firestore(app?: App): _firestore.Firestore; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace firestore { + /* eslint-disable @typescript-eslint/no-unused-vars */ + // See https://github.com/typescript-eslint/typescript-eslint/issues/363 + export import v1beta1 = _firestore.v1beta1; + export import v1 = _firestore.v1; + + export import BulkWriter = _firestore.BulkWriter; + export import BulkWriterOptions = _firestore.BulkWriterOptions; + export import CollectionGroup = _firestore.CollectionGroup; + export import CollectionReference = _firestore.CollectionReference; + export import DocumentChangeType = _firestore.DocumentChangeType; + export import DocumentData = _firestore.DocumentData; + export import DocumentReference = _firestore.DocumentReference; + export import DocumentSnapshot = _firestore.DocumentSnapshot; + export import FieldPath = _firestore.FieldPath; + export import FieldValue = _firestore.FieldValue; + export import Firestore = _firestore.Firestore; + export import FirestoreDataConverter = _firestore.FirestoreDataConverter; + export import GeoPoint = _firestore.GeoPoint; + export import GrpcStatus = _firestore.GrpcStatus; + export import Precondition = _firestore.Precondition; + export import Query = _firestore.Query; + export import QueryDocumentSnapshot = _firestore.QueryDocumentSnapshot; + export import QueryPartition = _firestore.QueryPartition; + export import QuerySnapshot = _firestore.QuerySnapshot; + export import ReadOptions = _firestore.ReadOptions; + export import Settings = _firestore.Settings; + export import Timestamp = _firestore.Timestamp; + export import Transaction = _firestore.Transaction; + export import UpdateData = _firestore.UpdateData; + export import WriteBatch = _firestore.WriteBatch; + export import WriteResult = _firestore.WriteResult; + + export import setLogFunction = _firestore.setLogFunction; +} diff --git a/src/firestore/index.ts b/src/firestore/index.ts index ebfb3243dc..50f2562c18 100644 --- a/src/firestore/index.ts +++ b/src/firestore/index.ts @@ -60,42 +60,3 @@ export function getFirestore(app?: App): _firestore.Firestore { 'firestore', (app) => new FirestoreService(app)); return firestoreService.client; } - -export declare function firestore(app?: App): _firestore.Firestore; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace firestore { - /* eslint-disable @typescript-eslint/no-unused-vars */ - // See https://github.com/typescript-eslint/typescript-eslint/issues/363 - export import v1beta1 = _firestore.v1beta1; - export import v1 = _firestore.v1; - - export import BulkWriter = _firestore.BulkWriter; - export import BulkWriterOptions = _firestore.BulkWriterOptions; - export import CollectionGroup = _firestore.CollectionGroup; - export import CollectionReference = _firestore.CollectionReference; - export import DocumentChangeType = _firestore.DocumentChangeType; - export import DocumentData = _firestore.DocumentData; - export import DocumentReference = _firestore.DocumentReference; - export import DocumentSnapshot = _firestore.DocumentSnapshot; - export import FieldPath = _firestore.FieldPath; - export import FieldValue = _firestore.FieldValue; - export import Firestore = _firestore.Firestore; - export import FirestoreDataConverter = _firestore.FirestoreDataConverter; - export import GeoPoint = _firestore.GeoPoint; - export import GrpcStatus = _firestore.GrpcStatus; - export import Precondition = _firestore.Precondition; - export import Query = _firestore.Query; - export import QueryDocumentSnapshot = _firestore.QueryDocumentSnapshot; - export import QueryPartition = _firestore.QueryPartition; - export import QuerySnapshot = _firestore.QuerySnapshot; - export import ReadOptions = _firestore.ReadOptions; - export import Settings = _firestore.Settings; - export import Timestamp = _firestore.Timestamp; - export import Transaction = _firestore.Transaction; - export import UpdateData = _firestore.UpdateData; - export import WriteBatch = _firestore.WriteBatch; - export import WriteResult = _firestore.WriteResult; - - export import setLogFunction = _firestore.setLogFunction; -} diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index ef7a56b997..c6ce147f80 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -58,42 +58,3 @@ export function getInstanceId(app?: App): InstanceId { const firebaseApp: FirebaseApp = app as FirebaseApp; return firebaseApp.getOrInitService('instanceId', (app) => new InstanceId(app)); } - -/** - * Gets the {@link instanceId.InstanceId `InstanceId`} service for the - * default app or a given app. - * - * `admin.instanceId()` can be called with no arguments to access the default - * app's {@link instanceId.InstanceId `InstanceId`} service or as - * `admin.instanceId(app)` to access the - * {@link instanceId.InstanceId `InstanceId`} service associated with a - * specific app. - * - * @example - * ```javascript - * // Get the Instance ID service for the default app - * var defaultInstanceId = admin.instanceId(); - * ``` - * - * @example - * ```javascript - * // Get the Instance ID service for a given app - * var otherInstanceId = admin.instanceId(otherApp); - *``` - * - * @param app Optional app whose `InstanceId` service to - * return. If not provided, the default `InstanceId` service will be - * returned. - * - * @return The default `InstanceId` service if - * no app is provided or the `InstanceId` service associated with the - * provided app. - */ -export declare function instanceId(app?: App): instanceId.InstanceId; - -import { InstanceId as TInstanceId } from './instance-id'; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace instanceId { - export type InstanceId = TInstanceId; -} diff --git a/src/instance-id/instance-id-namespace.ts b/src/instance-id/instance-id-namespace.ts new file mode 100644 index 0000000000..74b367a595 --- /dev/null +++ b/src/instance-id/instance-id-namespace.ts @@ -0,0 +1,39 @@ +import { App } from '../app/index'; +import { InstanceId as TInstanceId } from './instance-id'; + +/** + * Gets the {@link instanceId.InstanceId `InstanceId`} service for the + * default app or a given app. + * + * `admin.instanceId()` can be called with no arguments to access the default + * app's {@link instanceId.InstanceId `InstanceId`} service or as + * `admin.instanceId(app)` to access the + * {@link instanceId.InstanceId `InstanceId`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the Instance ID service for the default app + * var defaultInstanceId = admin.instanceId(); + * ``` + * + * @example + * ```javascript + * // Get the Instance ID service for a given app + * var otherInstanceId = admin.instanceId(otherApp); + *``` + * + * @param app Optional app whose `InstanceId` service to + * return. If not provided, the default `InstanceId` service will be + * returned. + * + * @return The default `InstanceId` service if + * no app is provided or the `InstanceId` service associated with the + * provided app. + */ +export declare function instanceId(app?: App): instanceId.InstanceId; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace instanceId { + export type InstanceId = TInstanceId; +} diff --git a/src/machine-learning/index.ts b/src/machine-learning/index.ts index e3cdf83d65..279273d301 100644 --- a/src/machine-learning/index.ts +++ b/src/machine-learning/index.ts @@ -69,62 +69,3 @@ export function getMachineLearning(app?: App): MachineLearning { const firebaseApp: FirebaseApp = app as FirebaseApp; return firebaseApp.getOrInitService('machineLearning', (app) => new MachineLearning(app)); } - -import { - ListModelsResult as TListModelsResult, - MachineLearning as TMachineLearning, - Model as TModel, - TFLiteModel as TTFLiteModel, -} from './machine-learning'; -import { - AutoMLTfliteModelOptions as TAutoMLTfliteModelOptions, - GcsTfliteModelOptions as TGcsTfliteModelOptions, - ListModelsOptions as TListModelsOptions, - ModelOptions as TModelOptions, - ModelOptionsBase as TModelOptionsBase, -} from './machine-learning-api-client'; - -/** - * Gets the {@link machineLearning.MachineLearning `MachineLearning`} service for the - * default app or a given app. - * - * `admin.machineLearning()` can be called with no arguments to access the - * default app's {@link machineLearning.MachineLearning - * `MachineLearning`} service or as `admin.machineLearning(app)` to access - * the {@link machineLearning.MachineLearning `MachineLearning`} - * service associated with a specific app. - * - * @example - * ```javascript - * // Get the MachineLearning service for the default app - * var defaultMachineLearning = admin.machineLearning(); - * ``` - * - * @example - * ```javascript - * // Get the MachineLearning service for a given app - * var otherMachineLearning = admin.machineLearning(otherApp); - * ``` - * - * @param app Optional app whose `MachineLearning` service to - * return. If not provided, the default `MachineLearning` service - * will be returned. - * - * @return The default `MachineLearning` service if no app is provided or the - * `MachineLearning` service associated with the provided app. - */ -export declare function machineLearning(app?: App): machineLearning.MachineLearning; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace machineLearning { - export type ListModelsResult = TListModelsResult; - export type MachineLearning = TMachineLearning; - export type Model = TModel; - export type TFLiteModel = TTFLiteModel; - - export type AutoMLTfliteModelOptions = TAutoMLTfliteModelOptions; - export type GcsTfliteModelOptions = TGcsTfliteModelOptions; - export type ListModelsOptions = TListModelsOptions; - export type ModelOptions = TModelOptions; - export type ModelOptionsBase = TModelOptionsBase; -} diff --git a/src/machine-learning/machine-learning-namespace.ts b/src/machine-learning/machine-learning-namespace.ts new file mode 100644 index 0000000000..021b6ca6aa --- /dev/null +++ b/src/machine-learning/machine-learning-namespace.ts @@ -0,0 +1,75 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App } from '../app'; +import { + ListModelsResult as TListModelsResult, + MachineLearning as TMachineLearning, + Model as TModel, + TFLiteModel as TTFLiteModel, +} from './machine-learning'; +import { + AutoMLTfliteModelOptions as TAutoMLTfliteModelOptions, + GcsTfliteModelOptions as TGcsTfliteModelOptions, + ListModelsOptions as TListModelsOptions, + ModelOptions as TModelOptions, + ModelOptionsBase as TModelOptionsBase, +} from './machine-learning-api-client'; + +/** + * Gets the {@link machineLearning.MachineLearning `MachineLearning`} service for the + * default app or a given app. + * + * `admin.machineLearning()` can be called with no arguments to access the + * default app's {@link machineLearning.MachineLearning + * `MachineLearning`} service or as `admin.machineLearning(app)` to access + * the {@link machineLearning.MachineLearning `MachineLearning`} + * service associated with a specific app. + * + * @example + * ```javascript + * // Get the MachineLearning service for the default app + * var defaultMachineLearning = admin.machineLearning(); + * ``` + * + * @example + * ```javascript + * // Get the MachineLearning service for a given app + * var otherMachineLearning = admin.machineLearning(otherApp); + * ``` + * + * @param app Optional app whose `MachineLearning` service to + * return. If not provided, the default `MachineLearning` service + * will be returned. + * + * @return The default `MachineLearning` service if no app is provided or the + * `MachineLearning` service associated with the provided app. + */ +export declare function machineLearning(app?: App): machineLearning.MachineLearning; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace machineLearning { + export type ListModelsResult = TListModelsResult; + export type MachineLearning = TMachineLearning; + export type Model = TModel; + export type TFLiteModel = TTFLiteModel; + + export type AutoMLTfliteModelOptions = TAutoMLTfliteModelOptions; + export type GcsTfliteModelOptions = TGcsTfliteModelOptions; + export type ListModelsOptions = TListModelsOptions; + export type ModelOptions = TModelOptions; + export type ModelOptionsBase = TModelOptionsBase; +} diff --git a/src/messaging/index.ts b/src/messaging/index.ts index 5d799cf6b0..b64559e9f7 100644 --- a/src/messaging/index.ts +++ b/src/messaging/index.ts @@ -97,112 +97,3 @@ export function getMessaging(app?: App): Messaging { const firebaseApp: FirebaseApp = app as FirebaseApp; return firebaseApp.getOrInitService('messaging', (app) => new Messaging(app)); } - -import { Messaging as TMessaging } from './messaging'; -import { - AndroidConfig as TAndroidConfig, - AndroidFcmOptions as TAndroidFcmOptions, - AndroidNotification as TAndroidNotification, - ApnsConfig as TApnsConfig, - ApnsFcmOptions as TApnsFcmOptions, - ApnsPayload as TApnsPayload, - Aps as TAps, - ApsAlert as TApsAlert, - BatchResponse as TBatchResponse, - CriticalSound as TCriticalSound, - ConditionMessage as TConditionMessage, - FcmOptions as TFcmOptions, - LightSettings as TLightSettings, - Message as TMessage, - MessagingTopicManagementResponse as TMessagingTopicManagementResponse, - MulticastMessage as TMulticastMessage, - Notification as TNotification, - SendResponse as TSendResponse, - TokenMessage as TTokenMessage, - TopicMessage as TTopicMessage, - WebpushConfig as TWebpushConfig, - WebpushFcmOptions as TWebpushFcmOptions, - WebpushNotification as TWebpushNotification, - - // Legacy APIs - DataMessagePayload as TDataMessagePayload, - MessagingConditionResponse as TMessagingConditionResponse, - MessagingDeviceGroupResponse as TMessagingDeviceGroupResponse, - MessagingDeviceResult as TMessagingDeviceResult, - MessagingDevicesResponse as TMessagingDevicesResponse, - MessagingOptions as TMessagingOptions, - MessagingPayload as TMessagingPayload, - MessagingTopicResponse as TMessagingTopicResponse, - NotificationMessagePayload as TNotificationMessagePayload, -} from './messaging-api'; - -/** - * Gets the {@link messaging.Messaging `Messaging`} service for the - * default app or a given app. - * - * `admin.messaging()` can be called with no arguments to access the default - * app's {@link messaging.Messaging `Messaging`} service or as - * `admin.messaging(app)` to access the - * {@link messaging.Messaging `Messaging`} service associated with a - * specific app. - * - * @example - * ```javascript - * // Get the Messaging service for the default app - * var defaultMessaging = admin.messaging(); - * ``` - * - * @example - * ```javascript - * // Get the Messaging service for a given app - * var otherMessaging = admin.messaging(otherApp); - * ``` - * - * @param app Optional app whose `Messaging` service to - * return. If not provided, the default `Messaging` service will be returned. - * - * @return The default `Messaging` service if no - * app is provided or the `Messaging` service associated with the provided - * app. - */ -export declare function messaging(app?: App): messaging.Messaging; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace messaging { - export type Messaging = TMessaging; - - export type AndroidConfig = TAndroidConfig; - export type AndroidFcmOptions = TAndroidFcmOptions; - export type AndroidNotification = TAndroidNotification; - export type ApnsConfig = TApnsConfig; - export type ApnsFcmOptions = TApnsFcmOptions; - export type ApnsPayload = TApnsPayload; - export type Aps = TAps; - export type ApsAlert = TApsAlert; - export type BatchResponse = TBatchResponse; - export type CriticalSound = TCriticalSound; - export type ConditionMessage = TConditionMessage; - export type FcmOptions = TFcmOptions; - export type LightSettings = TLightSettings; - export type Message = TMessage; - export type MessagingTopicManagementResponse = TMessagingTopicManagementResponse; - export type MulticastMessage = TMulticastMessage; - export type Notification = TNotification; - export type SendResponse = TSendResponse; - export type TokenMessage = TTokenMessage; - export type TopicMessage = TTopicMessage; - export type WebpushConfig = TWebpushConfig; - export type WebpushFcmOptions = TWebpushFcmOptions; - export type WebpushNotification = TWebpushNotification; - - // Legacy APIs - export type DataMessagePayload = TDataMessagePayload; - export type MessagingConditionResponse = TMessagingConditionResponse; - export type MessagingDeviceGroupResponse = TMessagingDeviceGroupResponse; - export type MessagingDeviceResult = TMessagingDeviceResult; - export type MessagingDevicesResponse = TMessagingDevicesResponse; - export type MessagingOptions = TMessagingOptions; - export type MessagingPayload = TMessagingPayload; - export type MessagingTopicResponse = TMessagingTopicResponse; - export type NotificationMessagePayload = TNotificationMessagePayload; -} diff --git a/src/messaging/messaging-namespace.ts b/src/messaging/messaging-namespace.ts new file mode 100644 index 0000000000..b49fdb564a --- /dev/null +++ b/src/messaging/messaging-namespace.ts @@ -0,0 +1,125 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App } from '../app'; +import { Messaging as TMessaging } from './messaging'; +import { + AndroidConfig as TAndroidConfig, + AndroidFcmOptions as TAndroidFcmOptions, + AndroidNotification as TAndroidNotification, + ApnsConfig as TApnsConfig, + ApnsFcmOptions as TApnsFcmOptions, + ApnsPayload as TApnsPayload, + Aps as TAps, + ApsAlert as TApsAlert, + BatchResponse as TBatchResponse, + CriticalSound as TCriticalSound, + ConditionMessage as TConditionMessage, + FcmOptions as TFcmOptions, + LightSettings as TLightSettings, + Message as TMessage, + MessagingTopicManagementResponse as TMessagingTopicManagementResponse, + MulticastMessage as TMulticastMessage, + Notification as TNotification, + SendResponse as TSendResponse, + TokenMessage as TTokenMessage, + TopicMessage as TTopicMessage, + WebpushConfig as TWebpushConfig, + WebpushFcmOptions as TWebpushFcmOptions, + WebpushNotification as TWebpushNotification, + + // Legacy APIs + DataMessagePayload as TDataMessagePayload, + MessagingConditionResponse as TMessagingConditionResponse, + MessagingDeviceGroupResponse as TMessagingDeviceGroupResponse, + MessagingDeviceResult as TMessagingDeviceResult, + MessagingDevicesResponse as TMessagingDevicesResponse, + MessagingOptions as TMessagingOptions, + MessagingPayload as TMessagingPayload, + MessagingTopicResponse as TMessagingTopicResponse, + NotificationMessagePayload as TNotificationMessagePayload, +} from './messaging-api'; + +/** + * Gets the {@link messaging.Messaging `Messaging`} service for the + * default app or a given app. + * + * `admin.messaging()` can be called with no arguments to access the default + * app's {@link messaging.Messaging `Messaging`} service or as + * `admin.messaging(app)` to access the + * {@link messaging.Messaging `Messaging`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the Messaging service for the default app + * var defaultMessaging = admin.messaging(); + * ``` + * + * @example + * ```javascript + * // Get the Messaging service for a given app + * var otherMessaging = admin.messaging(otherApp); + * ``` + * + * @param app Optional app whose `Messaging` service to + * return. If not provided, the default `Messaging` service will be returned. + * + * @return The default `Messaging` service if no + * app is provided or the `Messaging` service associated with the provided + * app. + */ +export declare function messaging(app?: App): messaging.Messaging; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace messaging { + export type Messaging = TMessaging; + + export type AndroidConfig = TAndroidConfig; + export type AndroidFcmOptions = TAndroidFcmOptions; + export type AndroidNotification = TAndroidNotification; + export type ApnsConfig = TApnsConfig; + export type ApnsFcmOptions = TApnsFcmOptions; + export type ApnsPayload = TApnsPayload; + export type Aps = TAps; + export type ApsAlert = TApsAlert; + export type BatchResponse = TBatchResponse; + export type CriticalSound = TCriticalSound; + export type ConditionMessage = TConditionMessage; + export type FcmOptions = TFcmOptions; + export type LightSettings = TLightSettings; + export type Message = TMessage; + export type MessagingTopicManagementResponse = TMessagingTopicManagementResponse; + export type MulticastMessage = TMulticastMessage; + export type Notification = TNotification; + export type SendResponse = TSendResponse; + export type TokenMessage = TTokenMessage; + export type TopicMessage = TTopicMessage; + export type WebpushConfig = TWebpushConfig; + export type WebpushFcmOptions = TWebpushFcmOptions; + export type WebpushNotification = TWebpushNotification; + + // Legacy APIs + export type DataMessagePayload = TDataMessagePayload; + export type MessagingConditionResponse = TMessagingConditionResponse; + export type MessagingDeviceGroupResponse = TMessagingDeviceGroupResponse; + export type MessagingDeviceResult = TMessagingDeviceResult; + export type MessagingDevicesResponse = TMessagingDevicesResponse; + export type MessagingOptions = TMessagingOptions; + export type MessagingPayload = TMessagingPayload; + export type MessagingTopicResponse = TMessagingTopicResponse; + export type NotificationMessagePayload = TNotificationMessagePayload; +} diff --git a/src/project-management/index.ts b/src/project-management/index.ts index 35cbe3b4c1..97ff5c203c 100644 --- a/src/project-management/index.ts +++ b/src/project-management/index.ts @@ -59,60 +59,3 @@ export function getProjectManagement(app?: App): ProjectManagement { const firebaseApp: FirebaseApp = app as FirebaseApp; return firebaseApp.getOrInitService('projectManagement', (app) => new ProjectManagement(app)); } - -/** - * Gets the {@link projectManagement.ProjectManagement - * `ProjectManagement`} service for the default app or a given app. - * - * `admin.projectManagement()` can be called with no arguments to access the - * default app's {@link projectManagement.ProjectManagement - * `ProjectManagement`} service, or as `admin.projectManagement(app)` to access - * the {@link projectManagement.ProjectManagement `ProjectManagement`} - * service associated with a specific app. - * - * @example - * ```javascript - * // Get the ProjectManagement service for the default app - * var defaultProjectManagement = admin.projectManagement(); - * ``` - * - * @example - * ```javascript - * // Get the ProjectManagement service for a given app - * var otherProjectManagement = admin.projectManagement(otherApp); - * ``` - * - * @param app Optional app whose `ProjectManagement` service - * to return. If not provided, the default `ProjectManagement` service will - * be returned. * - * @return The default `ProjectManagement` service if no app is provided or the - * `ProjectManagement` service associated with the provided app. - */ -export declare function projectManagement(app?: App): projectManagement.ProjectManagement; - -import { - AppMetadata as TAppMetadata, - AppPlatform as TAppPlatform, -} from './app-metadata'; -import { ProjectManagement as TProjectManagement } from './project-management'; -import { - AndroidApp as TAndroidApp, - AndroidAppMetadata as TAndroidAppMetadata, - ShaCertificate as TShaCertificate, -} from './android-app'; -import { - IosApp as TIosApp, - IosAppMetadata as TIosAppMetadata, -} from './ios-app'; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace projectManagement { - export type AppMetadata = TAppMetadata; - export type AppPlatform = TAppPlatform; - export type ProjectManagement = TProjectManagement; - export type IosApp = TIosApp; - export type IosAppMetadata = TIosAppMetadata; - export type AndroidApp = TAndroidApp; - export type AndroidAppMetadata = TAndroidAppMetadata; - export type ShaCertificate = TShaCertificate; -} diff --git a/src/project-management/project-management-namespace.ts b/src/project-management/project-management-namespace.ts new file mode 100644 index 0000000000..e37903cda8 --- /dev/null +++ b/src/project-management/project-management-namespace.ts @@ -0,0 +1,73 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App } from '../app'; +import { + AppMetadata as TAppMetadata, + AppPlatform as TAppPlatform, +} from './app-metadata'; +import { ProjectManagement as TProjectManagement } from './project-management'; +import { + AndroidApp as TAndroidApp, + AndroidAppMetadata as TAndroidAppMetadata, + ShaCertificate as TShaCertificate, +} from './android-app'; +import { + IosApp as TIosApp, + IosAppMetadata as TIosAppMetadata, +} from './ios-app'; + +/** + * Gets the {@link projectManagement.ProjectManagement + * `ProjectManagement`} service for the default app or a given app. + * + * `admin.projectManagement()` can be called with no arguments to access the + * default app's {@link projectManagement.ProjectManagement + * `ProjectManagement`} service, or as `admin.projectManagement(app)` to access + * the {@link projectManagement.ProjectManagement `ProjectManagement`} + * service associated with a specific app. + * + * @example + * ```javascript + * // Get the ProjectManagement service for the default app + * var defaultProjectManagement = admin.projectManagement(); + * ``` + * + * @example + * ```javascript + * // Get the ProjectManagement service for a given app + * var otherProjectManagement = admin.projectManagement(otherApp); + * ``` + * + * @param app Optional app whose `ProjectManagement` service + * to return. If not provided, the default `ProjectManagement` service will + * be returned. * + * @return The default `ProjectManagement` service if no app is provided or the + * `ProjectManagement` service associated with the provided app. + */ +export declare function projectManagement(app?: App): projectManagement.ProjectManagement; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace projectManagement { + export type AppMetadata = TAppMetadata; + export type AppPlatform = TAppPlatform; + export type ProjectManagement = TProjectManagement; + export type IosApp = TIosApp; + export type IosAppMetadata = TIosAppMetadata; + export type AndroidApp = TAndroidApp; + export type AndroidAppMetadata = TAndroidAppMetadata; + export type ShaCertificate = TShaCertificate; +} diff --git a/src/remote-config/index.ts b/src/remote-config/index.ts index e97803c68f..0d0d6e6612 100644 --- a/src/remote-config/index.ts +++ b/src/remote-config/index.ts @@ -71,67 +71,3 @@ export function getRemoteConfig(app?: App): RemoteConfig { const firebaseApp: FirebaseApp = app as FirebaseApp; return firebaseApp.getOrInitService('remoteConfig', (app) => new RemoteConfig(app)); } - -import { - ExplicitParameterValue as TExplicitParameterValue, - InAppDefaultValue as TInAppDefaultValue, - ListVersionsOptions as TListVersionsOptions, - ListVersionsResult as TListVersionsResult, - RemoteConfigCondition as TRemoteConfigCondition, - RemoteConfigParameter as TRemoteConfigParameter, - RemoteConfigParameterGroup as TRemoteConfigParameterGroup, - RemoteConfigParameterValue as TRemoteConfigParameterValue, - RemoteConfigTemplate as TRemoteConfigTemplate, - RemoteConfigUser as TRemoteConfigUser, - TagColor as TTagColor, - Version as TVersion, -} from './remote-config-api'; -import { RemoteConfig as TRemoteConfig } from './remote-config'; - -/** - * Gets the {@link remoteConfig.RemoteConfig `RemoteConfig`} service for the - * default app or a given app. - * - * `admin.remoteConfig()` can be called with no arguments to access the default - * app's {@link remoteConfig.RemoteConfig `RemoteConfig`} service or as - * `admin.remoteConfig(app)` to access the - * {@link remoteConfig.RemoteConfig `RemoteConfig`} service associated with a - * specific app. - * - * @example - * ```javascript - * // Get the `RemoteConfig` service for the default app - * var defaultRemoteConfig = admin.remoteConfig(); - * ``` - * - * @example - * ```javascript - * // Get the `RemoteConfig` service for a given app - * var otherRemoteConfig = admin.remoteConfig(otherApp); - * ``` - * - * @param app Optional app for which to return the `RemoteConfig` service. - * If not provided, the default `RemoteConfig` service is returned. - * - * @return The default `RemoteConfig` service if no - * app is provided, or the `RemoteConfig` service associated with the provided - * app. - */ -export declare function remoteConfig(app?: App): remoteConfig.RemoteConfig; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace remoteConfig { - export type ExplicitParameterValue = TExplicitParameterValue; - export type InAppDefaultValue = TInAppDefaultValue; - export type ListVersionsOptions = TListVersionsOptions; - export type ListVersionsResult = TListVersionsResult; - export type RemoteConfig = TRemoteConfig; - export type RemoteConfigCondition = TRemoteConfigCondition; - export type RemoteConfigParameter = TRemoteConfigParameter; - export type RemoteConfigParameterGroup = TRemoteConfigParameterGroup; - export type RemoteConfigParameterValue = TRemoteConfigParameterValue; - export type RemoteConfigTemplate = TRemoteConfigTemplate; - export type RemoteConfigUser = TRemoteConfigUser; - export type TagColor = TTagColor; - export type Version = TVersion; -} diff --git a/src/remote-config/remote-config-namespace.ts b/src/remote-config/remote-config-namespace.ts new file mode 100644 index 0000000000..d50fcb0aee --- /dev/null +++ b/src/remote-config/remote-config-namespace.ts @@ -0,0 +1,80 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App } from '../app'; +import { + ExplicitParameterValue as TExplicitParameterValue, + InAppDefaultValue as TInAppDefaultValue, + ListVersionsOptions as TListVersionsOptions, + ListVersionsResult as TListVersionsResult, + RemoteConfigCondition as TRemoteConfigCondition, + RemoteConfigParameter as TRemoteConfigParameter, + RemoteConfigParameterGroup as TRemoteConfigParameterGroup, + RemoteConfigParameterValue as TRemoteConfigParameterValue, + RemoteConfigTemplate as TRemoteConfigTemplate, + RemoteConfigUser as TRemoteConfigUser, + TagColor as TTagColor, + Version as TVersion, +} from './remote-config-api'; +import { RemoteConfig as TRemoteConfig } from './remote-config'; + +/** + * Gets the {@link remoteConfig.RemoteConfig `RemoteConfig`} service for the + * default app or a given app. + * + * `admin.remoteConfig()` can be called with no arguments to access the default + * app's {@link remoteConfig.RemoteConfig `RemoteConfig`} service or as + * `admin.remoteConfig(app)` to access the + * {@link remoteConfig.RemoteConfig `RemoteConfig`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the `RemoteConfig` service for the default app + * var defaultRemoteConfig = admin.remoteConfig(); + * ``` + * + * @example + * ```javascript + * // Get the `RemoteConfig` service for a given app + * var otherRemoteConfig = admin.remoteConfig(otherApp); + * ``` + * + * @param app Optional app for which to return the `RemoteConfig` service. + * If not provided, the default `RemoteConfig` service is returned. + * + * @return The default `RemoteConfig` service if no + * app is provided, or the `RemoteConfig` service associated with the provided + * app. + */ +export declare function remoteConfig(app?: App): remoteConfig.RemoteConfig; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace remoteConfig { + export type ExplicitParameterValue = TExplicitParameterValue; + export type InAppDefaultValue = TInAppDefaultValue; + export type ListVersionsOptions = TListVersionsOptions; + export type ListVersionsResult = TListVersionsResult; + export type RemoteConfig = TRemoteConfig; + export type RemoteConfigCondition = TRemoteConfigCondition; + export type RemoteConfigParameter = TRemoteConfigParameter; + export type RemoteConfigParameterGroup = TRemoteConfigParameterGroup; + export type RemoteConfigParameterValue = TRemoteConfigParameterValue; + export type RemoteConfigTemplate = TRemoteConfigTemplate; + export type RemoteConfigUser = TRemoteConfigUser; + export type TagColor = TTagColor; + export type Version = TVersion; +} diff --git a/src/security-rules/index.ts b/src/security-rules/index.ts index 72582520d9..3537f6bf6a 100644 --- a/src/security-rules/index.ts +++ b/src/security-rules/index.ts @@ -26,14 +26,6 @@ export { SecurityRules, } from './security-rules'; -import { - RulesFile as TRulesFile, - Ruleset as TRuleset, - RulesetMetadata as TRulesetMetadata, - RulesetMetadataList as TRulesetMetadataList, - SecurityRules as TSecurityRules, -} from './security-rules'; - /** * Gets the {@link securityRules.SecurityRules * `SecurityRules`} service for the default app or a given app. @@ -70,42 +62,3 @@ export function getSecurityRules(app?: App): SecurityRules { const firebaseApp: FirebaseApp = app as FirebaseApp; return firebaseApp.getOrInitService('securityRules', (app) => new SecurityRules(app)); } - -/** - * Gets the {@link securityRules.SecurityRules - * `SecurityRules`} service for the default app or a given app. - * - * `admin.securityRules()` can be called with no arguments to access the - * default app's {@link securityRules.SecurityRules - * `SecurityRules`} service, or as `admin.securityRules(app)` to access - * the {@link securityRules.SecurityRules `SecurityRules`} - * service associated with a specific app. - * - * @example - * ```javascript - * // Get the SecurityRules service for the default app - * var defaultSecurityRules = admin.securityRules(); - * ``` - * - * @example - * ```javascript - * // Get the SecurityRules service for a given app - * var otherSecurityRules = admin.securityRules(otherApp); - * ``` - * - * @param app Optional app to return the `SecurityRules` service - * for. If not provided, the default `SecurityRules` service - * is returned. - * @return The default `SecurityRules` service if no app is provided, or the - * `SecurityRules` service associated with the provided app. - */ -export declare function securityRules(app?: App): securityRules.SecurityRules; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace securityRules { - export type RulesFile = TRulesFile; - export type Ruleset = TRuleset; - export type RulesetMetadata = TRulesetMetadata; - export type RulesetMetadataList = TRulesetMetadataList; - export type SecurityRules = TSecurityRules; -} diff --git a/src/security-rules/security-rules-namespace.ts b/src/security-rules/security-rules-namespace.ts new file mode 100644 index 0000000000..6556440f54 --- /dev/null +++ b/src/security-rules/security-rules-namespace.ts @@ -0,0 +1,63 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App } from '../app'; +import { + RulesFile as TRulesFile, + Ruleset as TRuleset, + RulesetMetadata as TRulesetMetadata, + RulesetMetadataList as TRulesetMetadataList, + SecurityRules as TSecurityRules, +} from './security-rules'; + +/** + * Gets the {@link securityRules.SecurityRules + * `SecurityRules`} service for the default app or a given app. + * + * `admin.securityRules()` can be called with no arguments to access the + * default app's {@link securityRules.SecurityRules + * `SecurityRules`} service, or as `admin.securityRules(app)` to access + * the {@link securityRules.SecurityRules `SecurityRules`} + * service associated with a specific app. + * + * @example + * ```javascript + * // Get the SecurityRules service for the default app + * var defaultSecurityRules = admin.securityRules(); + * ``` + * + * @example + * ```javascript + * // Get the SecurityRules service for a given app + * var otherSecurityRules = admin.securityRules(otherApp); + * ``` + * + * @param app Optional app to return the `SecurityRules` service + * for. If not provided, the default `SecurityRules` service + * is returned. + * @return The default `SecurityRules` service if no app is provided, or the + * `SecurityRules` service associated with the provided app. + */ +export declare function securityRules(app?: App): securityRules.SecurityRules; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace securityRules { + export type RulesFile = TRulesFile; + export type Ruleset = TRuleset; + export type RulesetMetadata = TRulesetMetadata; + export type RulesetMetadataList = TRulesetMetadataList; + export type SecurityRules = TSecurityRules; +} diff --git a/src/storage/index.ts b/src/storage/index.ts index 7310c1cf00..6eeca2a107 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -50,34 +50,3 @@ export function getStorage(app?: App): Storage { const firebaseApp: FirebaseApp = app as FirebaseApp; return firebaseApp.getOrInitService('storage', (app) => new Storage(app)); } - -import { Storage as TStorage } from './storage'; - -/** - * Gets the {@link storage.Storage `Storage`} service for the - * default app or a given app. - * - * `admin.storage()` can be called with no arguments to access the default - * app's {@link storage.Storage `Storage`} service or as - * `admin.storage(app)` to access the - * {@link storage.Storage `Storage`} service associated with a - * specific app. - * - * @example - * ```javascript - * // Get the Storage service for the default app - * var defaultStorage = admin.storage(); - * ``` - * - * @example - * ```javascript - * // Get the Storage service for a given app - * var otherStorage = admin.storage(otherApp); - * ``` - */ -export declare function storage(app?: App): storage.Storage; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace storage { - export type Storage = TStorage; -} diff --git a/src/storage/storage-namespace.ts b/src/storage/storage-namespace.ts new file mode 100644 index 0000000000..1e78d076d6 --- /dev/null +++ b/src/storage/storage-namespace.ts @@ -0,0 +1,47 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App } from '../app'; +import { Storage as TStorage } from './storage'; + +/** + * Gets the {@link storage.Storage `Storage`} service for the + * default app or a given app. + * + * `admin.storage()` can be called with no arguments to access the default + * app's {@link storage.Storage `Storage`} service or as + * `admin.storage(app)` to access the + * {@link storage.Storage `Storage`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the Storage service for the default app + * var defaultStorage = admin.storage(); + * ``` + * + * @example + * ```javascript + * // Get the Storage service for a given app + * var otherStorage = admin.storage(otherApp); + * ``` + */ +export declare function storage(app?: App): storage.Storage; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace storage { + export type Storage = TStorage; +} diff --git a/src/utils/error.ts b/src/utils/error.ts index 232bed0981..3f8fa28495 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseError as FireabseErrorInterface } from '../firebase-namespace-api'; +import { FirebaseError as FireabseErrorInterface } from '../app'; import { deepCopy } from '../utils/deep-copy'; /** diff --git a/test/unit/app/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts index e3aaba5687..fe111993fd 100644 --- a/test/unit/app/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -32,17 +32,10 @@ import { FirebaseApp, FirebaseAccessToken } from '../../../src/app/firebase-app' import { FirebaseNamespace, FirebaseNamespaceInternals, FIREBASE_CONFIG_VAR } from '../../../src/app/firebase-namespace'; - -import { auth } from '../../../src/auth/index'; -import { messaging } from '../../../src/messaging/index'; -import { machineLearning } from '../../../src/machine-learning/index'; -import { storage } from '../../../src/storage/index'; -import { firestore } from '../../../src/firestore/index'; -import { database } from '../../../src/database/index'; -import { instanceId } from '../../../src/instance-id/index'; -import { projectManagement } from '../../../src/project-management/index'; -import { securityRules } from '../../../src/security-rules/index'; -import { remoteConfig } from '../../../src/remote-config/index'; +import { + auth, messaging, machineLearning, storage, firestore, database, + instanceId, projectManagement, securityRules , remoteConfig, +} from '../../../src/firebase-namespace-api'; import { FirebaseAppError, AppErrorCodes } from '../../../src/utils/error'; import Auth = auth.Auth; diff --git a/test/unit/app/firebase-namespace.spec.ts b/test/unit/app/firebase-namespace.spec.ts index a3aeb7b99c..e2daa62729 100644 --- a/test/unit/app/firebase-namespace.spec.ts +++ b/test/unit/app/firebase-namespace.spec.ts @@ -47,18 +47,10 @@ import { } from '@google-cloud/firestore'; import { getSdkVersion } from '../../../src/utils/index'; -import { app } from '../../../src/firebase-namespace-api'; -import { auth } from '../../../src/auth/index'; -import { messaging } from '../../../src/messaging/index'; -import { machineLearning } from '../../../src/machine-learning/index'; -import { storage } from '../../../src/storage/index'; -import { firestore } from '../../../src/firestore/index'; -import { database } from '../../../src/database/index'; -import { instanceId } from '../../../src/instance-id/index'; -import { projectManagement } from '../../../src/project-management/index'; -import { securityRules } from '../../../src/security-rules/index'; -import { remoteConfig } from '../../../src/remote-config/index'; - +import { + app, auth, messaging, machineLearning, storage, firestore, database, + instanceId, projectManagement, securityRules , remoteConfig, +} from '../../../src/firebase-namespace-api'; import { Auth as AuthImpl } from '../../../src/auth/auth'; import { InstanceId as InstanceIdImpl } from '../../../src/instance-id/instance-id'; import { MachineLearning as MachineLearningImpl } from '../../../src/machine-learning/machine-learning'; From bec70e485c43977f180c73598e4c40522174d34b Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 7 Apr 2021 16:19:14 -0700 Subject: [PATCH 23/41] fix(auth): Fixing some public API signatures to be consistent with the current API (#1221) * fix(auth): Making UserMetadata.lastRefreshTime field optional * fix(auth): Fixing the toJSON signature on MultiFactorSettings --- etc/firebase-admin.auth.api.md | 4 ++-- src/auth/user-record.ts | 5 +++-- test/unit/auth/user-record.spec.ts | 9 +++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 768cd26b8a..82ac60b019 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -195,7 +195,7 @@ export abstract class MultiFactorInfo { // @public export class MultiFactorSettings { enrolledFactors: MultiFactorInfo[]; - toJSON(): any; + toJSON(): object; } // @public @@ -394,7 +394,7 @@ export class UserInfo { // @public export class UserMetadata { readonly creationTime: string; - readonly lastRefreshTime: string | null; + readonly lastRefreshTime?: string | null; readonly lastSignInTime: string; toJSON(): object; } diff --git a/src/auth/user-record.ts b/src/auth/user-record.ts index 86dadc07f9..da55540b56 100644 --- a/src/auth/user-record.ts +++ b/src/auth/user-record.ts @@ -78,6 +78,7 @@ export interface GetAccountInfoUserResponse { mfaInfo?: MultiFactorInfoResponse[]; createdAt?: string; lastLoginAt?: string; + lastRefreshAt?: string; [key: string]: any; } @@ -277,7 +278,7 @@ export class MultiFactorSettings { /** * @return A JSON-serializable representation of this multi-factor object. */ - public toJSON(): any { + public toJSON(): object { return { enrolledFactors: this.enrolledFactors.map((info) => info.toJSON()), }; @@ -304,7 +305,7 @@ export class UserMetadata { * formatted as a UTC Date string (eg 'Sat, 03 Feb 2001 04:05:06 GMT'). * Returns null if the user was never active. */ - public readonly lastRefreshTime: string | null; + public readonly lastRefreshTime?: string | null; /** * @param response The server side response returned from the getAccountInfo diff --git a/test/unit/auth/user-record.spec.ts b/test/unit/auth/user-record.spec.ts index 2e1e3286c9..cca0af6185 100644 --- a/test/unit/auth/user-record.spec.ts +++ b/test/unit/auth/user-record.spec.ts @@ -685,6 +685,15 @@ describe('UserMetadata', () => { it('should return expected lastRefreshTime', () => { expect(actualMetadata.lastRefreshTime).to.equal(new Date(expectedLastRefreshAt).toUTCString()) }); + + it('should return null when lastRefreshTime is not available', () => { + const metadata: UserMetadata = new UserMetadata({ + localId: 'uid123', + lastLoginAt: expectedLastLoginAt.toString(), + createdAt: expectedCreatedAt.toString(), + }); + expect(metadata.lastRefreshTime).to.be.null; + }); }); describe('toJSON', () => { From 1e4286b445bd8a5e08fc74235ff2f5c0e10f2737 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Mon, 12 Apr 2021 13:09:30 -0700 Subject: [PATCH 24/41] Version bumped to alpha0 release (#1225) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea4731844d..aa065969fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-admin", - "version": "9.4.2", + "version": "9.100.0-alpha.0", "description": "Firebase admin SDK for Node.js", "author": "Firebase (https://firebase.google.com/)", "license": "Apache-2.0", From 9ffb196b56e4851f93841290310c64e50b4a5e0d Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 27 Apr 2021 10:33:56 -0700 Subject: [PATCH 25/41] fix: Removed App from the top-level admin namespace (#1236) --- etc/firebase-admin.api.md | 7 +------ src/firebase-namespace-api.ts | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 5b4b580cdd..c465c6e45b 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -10,17 +10,12 @@ import { FirebaseDatabase } from '@firebase/database-types'; import * as _firestore from '@google-cloud/firestore'; import * as rtdb from '@firebase/database-types'; -// @public -export interface App { - name: string; - options: AppOptions; -} - // @public (undocumented) export function app(name?: string): app.App; // @public (undocumented) export namespace app { + // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point default-namespace.d.ts export interface App extends App { // (undocumented) auth(): auth.Auth; diff --git a/src/firebase-namespace-api.ts b/src/firebase-namespace-api.ts index 4bfe9298a2..cb685e3893 100644 --- a/src/firebase-namespace-api.ts +++ b/src/firebase-namespace-api.ts @@ -27,7 +27,7 @@ import { storage } from './storage/storage-namespace'; import { App as AppCore, AppOptions } from './app/index'; -export { App, AppOptions, FirebaseError, FirebaseArrayIndexError } from './app/index'; +export { AppOptions, FirebaseError, FirebaseArrayIndexError } from './app/index'; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace app { From e2f3fe0a22c01e49542ab935ff6dc57ee4aa26bb Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 27 Apr 2021 16:42:04 -0700 Subject: [PATCH 26/41] chore: Merged main branch into modular-sdk (#1235) * chore: Merged main branch into modular-sdk * Adding missing commit from #1148 * fix: Removed redundant comment --- .github/actions/send-email/README.md | 59 + .github/actions/send-email/action.yml | 44 + .github/actions/send-email/dist/index.js | 2105 ++++++++++++++++++ .github/actions/send-email/index.js | 82 + .github/actions/send-email/package-lock.json | 173 ++ .github/actions/send-email/package.json | 23 + .github/workflows/ci.yml | 13 +- .github/workflows/nightly.yml | 101 + CONTRIBUTING.md | 31 +- docgen/content-sources/node/HOME.md | 2 +- docgen/content-sources/node/toc.yaml | 2 + docgen/generate-docs.js | 18 +- etc/firebase-admin.auth.api.md | 16 + package-lock.json | 6 +- package.json | 2 +- src/app/firebase-app.ts | 210 +- src/auth/auth-api-request.ts | 76 +- src/auth/auth-config.ts | 57 + src/auth/base-auth.ts | 125 +- src/auth/index.ts | 1 + src/auth/tenant.ts | 15 + src/auth/token-verifier.ts | 262 +-- src/database/database.ts | 68 +- src/storage/storage.ts | 4 + src/utils/error.ts | 4 +- src/utils/jwt.ts | 275 +++ test/integration/auth.spec.ts | 470 +++- test/integration/database.spec.ts | 24 +- test/integration/setup.ts | 78 +- test/unit/app/firebase-app.spec.ts | 201 -- test/unit/auth/auth-api-request.spec.ts | 6 + test/unit/auth/auth.spec.ts | 442 +++- test/unit/auth/tenant-manager.spec.ts | 3 + test/unit/auth/tenant.spec.ts | 2 + test/unit/auth/token-verifier.spec.ts | 347 +-- test/unit/database/database.spec.ts | 296 ++- test/unit/index.spec.ts | 1 + test/unit/messaging/messaging.spec.ts | 35 +- test/unit/storage/storage.spec.ts | 20 + test/unit/utils/jwt.spec.ts | 541 +++++ 40 files changed, 5227 insertions(+), 1013 deletions(-) create mode 100644 .github/actions/send-email/README.md create mode 100644 .github/actions/send-email/action.yml create mode 100644 .github/actions/send-email/dist/index.js create mode 100644 .github/actions/send-email/index.js create mode 100644 .github/actions/send-email/package-lock.json create mode 100644 .github/actions/send-email/package.json create mode 100644 .github/workflows/nightly.yml create mode 100644 src/utils/jwt.ts create mode 100644 test/unit/utils/jwt.spec.ts diff --git a/.github/actions/send-email/README.md b/.github/actions/send-email/README.md new file mode 100644 index 0000000000..ab3f93a154 --- /dev/null +++ b/.github/actions/send-email/README.md @@ -0,0 +1,59 @@ +# Send Email GitHub Action + +This is a minimalistic GitHub Action for sending emails using the Mailgun API. +Specify the Mailgun API key along with the Mailgun domain and message to +be sent. + +## Inputs + +### `api-key` + +**Required** Mailgun API key. + +### `domain` + +**Required** Mailgun domain name. + +### `from` + +**Required** Sender's email address. Ex: 'Hello User ' (defaults to 'user@{domain}`). + +### `to` + +**Required** Recipient's email address. You can use commas to separate multiple recipients. + +### `cc` + +Email addresses to Cc. + +### `subject` + +**Required** Message subject. + +### `text` + +Text body of the message. + +### `html` + +HTML body of the message. + +## Example usage + +``` +- name: Send Email + uses: firebase/firebase-admin-node/.github/actions/send-email + with: + api-key: ${{ secrets.MAILGUN_API_KEY }} + domain: ${{ secrets.MAILGUN_DOMAIN }} + from: 'User ' + html: '

Testing some Mailgun awesomness!

' + to: 'foo@example.com' +``` + +## Implementation + +This Action uses the `mailgun.js` NPM package to send Emails. + +When making a code change remember to run `npm run pack` to rebuild the +`dist/index.js` file which is the executable of this Action. diff --git a/.github/actions/send-email/action.yml b/.github/actions/send-email/action.yml new file mode 100644 index 0000000000..65956f9a12 --- /dev/null +++ b/.github/actions/send-email/action.yml @@ -0,0 +1,44 @@ +# Copyright 2021 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: 'Send email Action' +description: 'Send emails using Mailgun from GitHub Actions workflows.' +inputs: + api-key: + description: Mailgun API key. + required: true + domain: + description: Mailgun domain name. + required: true + from: + description: Senders name and email address. + required: true + to: + description: Recipient's email address. You can use commas to separate multiple recipients. + required: true + cc: + description: Email addresses to Cc. + required: false + subject: + description: Message subject. + required: true + text: + description: Text body of the message. + required: false + html: + description: HTML body of the message. + required: false +runs: + using: 'node12' + main: 'dist/index.js' diff --git a/.github/actions/send-email/dist/index.js b/.github/actions/send-email/dist/index.js new file mode 100644 index 0000000000..d1817b6db8 --- /dev/null +++ b/.github/actions/send-email/dist/index.js @@ -0,0 +1,2105 @@ +module.exports = +/******/ (function(modules, runtime) { // webpackBootstrap +/******/ "use strict"; +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ __webpack_require__.ab = __dirname + "/"; +/******/ +/******/ // the startup function +/******/ function startup() { +/******/ // Load entry module and return exports +/******/ return __webpack_require__(104); +/******/ }; +/******/ +/******/ // run startup +/******/ return startup(); +/******/ }) +/************************************************************************/ +/******/ ({ + +/***/ 2: +/***/ (function(module, __unusedexports, __webpack_require__) { + +var abort = __webpack_require__(762) + , async = __webpack_require__(792) + ; + +// API +module.exports = terminator; + +/** + * Terminates jobs in the attached state context + * + * @this AsyncKitState# + * @param {function} callback - final callback to invoke after termination + */ +function terminator(callback) +{ + if (!Object.keys(this.jobs).length) + { + return; + } + + // fast forward iteration index + this.index = this.size; + + // abort jobs + abort(this); + + // send back results we have so far + async(callback)(null, this.results); +} + + +/***/ }), + +/***/ 70: +/***/ (function(module) { + +// populates missing values +module.exports = function(dst, src) { + + Object.keys(src).forEach(function(prop) + { + dst[prop] = dst[prop] || src[prop]; + }); + + return dst; +}; + + +/***/ }), + +/***/ 82: +/***/ (function(__unusedmodule, exports) { + +"use strict"; + +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); +} +exports.toCommandValue = toCommandValue; +//# sourceMappingURL=utils.js.map + +/***/ }), + +/***/ 87: +/***/ (function(module) { + +module.exports = require("os"); + +/***/ }), + +/***/ 89: +/***/ (function(module, __unusedexports, __webpack_require__) { + +var iterate = __webpack_require__(258) + , initState = __webpack_require__(125) + , terminator = __webpack_require__(2) + ; + +// Public API +module.exports = parallel; + +/** + * Runs iterator over provided array elements in parallel + * + * @param {array|object} list - array or object (named list) to iterate over + * @param {function} iterator - iterator to run + * @param {function} callback - invoked when all elements processed + * @returns {function} - jobs terminator + */ +function parallel(list, iterator, callback) +{ + var state = initState(list); + + while (state.index < (state['keyedList'] || list).length) + { + iterate(list, iterator, state, function(error, result) + { + if (error) + { + callback(error, result); + return; + } + + // looks like it's the last one + if (Object.keys(state.jobs).length === 0) + { + callback(null, state.results); + return; + } + }); + + state.index++; + } + + return terminator.bind(state, callback); +} + + +/***/ }), + +/***/ 102: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +// For internal use, subject to change. +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +const fs = __importStar(__webpack_require__(747)); +const os = __importStar(__webpack_require__(87)); +const utils_1 = __webpack_require__(82); +function issueCommand(command, message) { + const filePath = process.env[`GITHUB_${command}`]; + if (!filePath) { + throw new Error(`Unable to find environment variable for file command ${command}`); + } + if (!fs.existsSync(filePath)) { + throw new Error(`Missing file at path: ${filePath}`); + } + fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, { + encoding: 'utf8' + }); +} +exports.issueCommand = issueCommand; +//# sourceMappingURL=file-command.js.map + +/***/ }), + +/***/ 104: +/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) { + +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const core = __webpack_require__(470); +const formData = __webpack_require__(416); +const Mailgun = __webpack_require__(618); + +const mailgun = new Mailgun(formData); +const optionalFields = ['cc', 'text', 'html']; + +function loadConfig() { + return { + apiKey: core.getInput('api-key'), + domain: core.getInput('domain'), + to: core.getInput('to'), + from: core.getInput('from'), + cc: core.getInput('cc'), + subject: core.getInput('subject'), + text: core.getInput('text'), + html: core.getInput('html'), + } +} + +function validate(config) { + for (param in config) { + if (optionalFields.includes(param)) { + continue; + } + validateRequiredParameter(config[param], `'${param}'`); + } +} + +function validateRequiredParameter(value, name) { + if (!isNonEmptyString(value)) { + throw new Error(`${name} must be a non-empty string.`); + } +} + +function sendEmail(config) { + const mg = mailgun.client({ + username: 'api', + key: config.apiKey, + }); + + return mg.messages + .create(config.domain, { + from: config.from, + to: config.to, + cc: config.cc, + subject: config.subject, + text: config.text, + html: config.html, + }) + .then((resp) => { + core.setOutput('response', resp.message); + return; + }) + .catch((err) => { + core.setFailed(err.message); + }); +} + +function isNonEmptyString(value) { + return typeof value === 'string' && value !== ''; +} + +const config = loadConfig(); +validate(config); +sendEmail(config); + + +/***/ }), + +/***/ 118: +/***/ (function(module) { + +module.exports = {"application/1d-interleaved-parityfec":{"source":"iana"},"application/3gpdash-qoe-report+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/3gpp-ims+xml":{"source":"iana","compressible":true},"application/a2l":{"source":"iana"},"application/activemessage":{"source":"iana"},"application/activity+json":{"source":"iana","compressible":true},"application/alto-costmap+json":{"source":"iana","compressible":true},"application/alto-costmapfilter+json":{"source":"iana","compressible":true},"application/alto-directory+json":{"source":"iana","compressible":true},"application/alto-endpointcost+json":{"source":"iana","compressible":true},"application/alto-endpointcostparams+json":{"source":"iana","compressible":true},"application/alto-endpointprop+json":{"source":"iana","compressible":true},"application/alto-endpointpropparams+json":{"source":"iana","compressible":true},"application/alto-error+json":{"source":"iana","compressible":true},"application/alto-networkmap+json":{"source":"iana","compressible":true},"application/alto-networkmapfilter+json":{"source":"iana","compressible":true},"application/alto-updatestreamcontrol+json":{"source":"iana","compressible":true},"application/alto-updatestreamparams+json":{"source":"iana","compressible":true},"application/aml":{"source":"iana"},"application/andrew-inset":{"source":"iana","extensions":["ez"]},"application/applefile":{"source":"iana"},"application/applixware":{"source":"apache","extensions":["aw"]},"application/atf":{"source":"iana"},"application/atfx":{"source":"iana"},"application/atom+xml":{"source":"iana","compressible":true,"extensions":["atom"]},"application/atomcat+xml":{"source":"iana","compressible":true,"extensions":["atomcat"]},"application/atomdeleted+xml":{"source":"iana","compressible":true,"extensions":["atomdeleted"]},"application/atomicmail":{"source":"iana"},"application/atomsvc+xml":{"source":"iana","compressible":true,"extensions":["atomsvc"]},"application/atsc-dwd+xml":{"source":"iana","compressible":true,"extensions":["dwd"]},"application/atsc-dynamic-event-message":{"source":"iana"},"application/atsc-held+xml":{"source":"iana","compressible":true,"extensions":["held"]},"application/atsc-rdt+json":{"source":"iana","compressible":true},"application/atsc-rsat+xml":{"source":"iana","compressible":true,"extensions":["rsat"]},"application/atxml":{"source":"iana"},"application/auth-policy+xml":{"source":"iana","compressible":true},"application/bacnet-xdd+zip":{"source":"iana","compressible":false},"application/batch-smtp":{"source":"iana"},"application/bdoc":{"compressible":false,"extensions":["bdoc"]},"application/beep+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/calendar+json":{"source":"iana","compressible":true},"application/calendar+xml":{"source":"iana","compressible":true,"extensions":["xcs"]},"application/call-completion":{"source":"iana"},"application/cals-1840":{"source":"iana"},"application/cap+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/cbor":{"source":"iana"},"application/cbor-seq":{"source":"iana"},"application/cccex":{"source":"iana"},"application/ccmp+xml":{"source":"iana","compressible":true},"application/ccxml+xml":{"source":"iana","compressible":true,"extensions":["ccxml"]},"application/cdfx+xml":{"source":"iana","compressible":true,"extensions":["cdfx"]},"application/cdmi-capability":{"source":"iana","extensions":["cdmia"]},"application/cdmi-container":{"source":"iana","extensions":["cdmic"]},"application/cdmi-domain":{"source":"iana","extensions":["cdmid"]},"application/cdmi-object":{"source":"iana","extensions":["cdmio"]},"application/cdmi-queue":{"source":"iana","extensions":["cdmiq"]},"application/cdni":{"source":"iana"},"application/cea":{"source":"iana"},"application/cea-2018+xml":{"source":"iana","compressible":true},"application/cellml+xml":{"source":"iana","compressible":true},"application/cfw":{"source":"iana"},"application/clue+xml":{"source":"iana","compressible":true},"application/clue_info+xml":{"source":"iana","compressible":true},"application/cms":{"source":"iana"},"application/cnrp+xml":{"source":"iana","compressible":true},"application/coap-group+json":{"source":"iana","compressible":true},"application/coap-payload":{"source":"iana"},"application/commonground":{"source":"iana"},"application/conference-info+xml":{"source":"iana","compressible":true},"application/cose":{"source":"iana"},"application/cose-key":{"source":"iana"},"application/cose-key-set":{"source":"iana"},"application/cpl+xml":{"source":"iana","compressible":true},"application/csrattrs":{"source":"iana"},"application/csta+xml":{"source":"iana","compressible":true},"application/cstadata+xml":{"source":"iana","compressible":true},"application/csvm+json":{"source":"iana","compressible":true},"application/cu-seeme":{"source":"apache","extensions":["cu"]},"application/cwt":{"source":"iana"},"application/cybercash":{"source":"iana"},"application/dart":{"compressible":true},"application/dash+xml":{"source":"iana","compressible":true,"extensions":["mpd"]},"application/dashdelta":{"source":"iana"},"application/davmount+xml":{"source":"iana","compressible":true,"extensions":["davmount"]},"application/dca-rft":{"source":"iana"},"application/dcd":{"source":"iana"},"application/dec-dx":{"source":"iana"},"application/dialog-info+xml":{"source":"iana","compressible":true},"application/dicom":{"source":"iana"},"application/dicom+json":{"source":"iana","compressible":true},"application/dicom+xml":{"source":"iana","compressible":true},"application/dii":{"source":"iana"},"application/dit":{"source":"iana"},"application/dns":{"source":"iana"},"application/dns+json":{"source":"iana","compressible":true},"application/dns-message":{"source":"iana"},"application/docbook+xml":{"source":"apache","compressible":true,"extensions":["dbk"]},"application/dots+cbor":{"source":"iana"},"application/dskpp+xml":{"source":"iana","compressible":true},"application/dssc+der":{"source":"iana","extensions":["dssc"]},"application/dssc+xml":{"source":"iana","compressible":true,"extensions":["xdssc"]},"application/dvcs":{"source":"iana"},"application/ecmascript":{"source":"iana","compressible":true,"extensions":["ecma","es"]},"application/edi-consent":{"source":"iana"},"application/edi-x12":{"source":"iana","compressible":false},"application/edifact":{"source":"iana","compressible":false},"application/efi":{"source":"iana"},"application/emergencycalldata.comment+xml":{"source":"iana","compressible":true},"application/emergencycalldata.control+xml":{"source":"iana","compressible":true},"application/emergencycalldata.deviceinfo+xml":{"source":"iana","compressible":true},"application/emergencycalldata.ecall.msd":{"source":"iana"},"application/emergencycalldata.providerinfo+xml":{"source":"iana","compressible":true},"application/emergencycalldata.serviceinfo+xml":{"source":"iana","compressible":true},"application/emergencycalldata.subscriberinfo+xml":{"source":"iana","compressible":true},"application/emergencycalldata.veds+xml":{"source":"iana","compressible":true},"application/emma+xml":{"source":"iana","compressible":true,"extensions":["emma"]},"application/emotionml+xml":{"source":"iana","compressible":true,"extensions":["emotionml"]},"application/encaprtp":{"source":"iana"},"application/epp+xml":{"source":"iana","compressible":true},"application/epub+zip":{"source":"iana","compressible":false,"extensions":["epub"]},"application/eshop":{"source":"iana"},"application/exi":{"source":"iana","extensions":["exi"]},"application/expect-ct-report+json":{"source":"iana","compressible":true},"application/fastinfoset":{"source":"iana"},"application/fastsoap":{"source":"iana"},"application/fdt+xml":{"source":"iana","compressible":true,"extensions":["fdt"]},"application/fhir+json":{"source":"iana","charset":"UTF-8","compressible":true},"application/fhir+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/fido.trusted-apps+json":{"compressible":true},"application/fits":{"source":"iana"},"application/flexfec":{"source":"iana"},"application/font-sfnt":{"source":"iana"},"application/font-tdpfr":{"source":"iana","extensions":["pfr"]},"application/font-woff":{"source":"iana","compressible":false},"application/framework-attributes+xml":{"source":"iana","compressible":true},"application/geo+json":{"source":"iana","compressible":true,"extensions":["geojson"]},"application/geo+json-seq":{"source":"iana"},"application/geopackage+sqlite3":{"source":"iana"},"application/geoxacml+xml":{"source":"iana","compressible":true},"application/gltf-buffer":{"source":"iana"},"application/gml+xml":{"source":"iana","compressible":true,"extensions":["gml"]},"application/gpx+xml":{"source":"apache","compressible":true,"extensions":["gpx"]},"application/gxf":{"source":"apache","extensions":["gxf"]},"application/gzip":{"source":"iana","compressible":false,"extensions":["gz"]},"application/h224":{"source":"iana"},"application/held+xml":{"source":"iana","compressible":true},"application/hjson":{"extensions":["hjson"]},"application/http":{"source":"iana"},"application/hyperstudio":{"source":"iana","extensions":["stk"]},"application/ibe-key-request+xml":{"source":"iana","compressible":true},"application/ibe-pkg-reply+xml":{"source":"iana","compressible":true},"application/ibe-pp-data":{"source":"iana"},"application/iges":{"source":"iana"},"application/im-iscomposing+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/index":{"source":"iana"},"application/index.cmd":{"source":"iana"},"application/index.obj":{"source":"iana"},"application/index.response":{"source":"iana"},"application/index.vnd":{"source":"iana"},"application/inkml+xml":{"source":"iana","compressible":true,"extensions":["ink","inkml"]},"application/iotp":{"source":"iana"},"application/ipfix":{"source":"iana","extensions":["ipfix"]},"application/ipp":{"source":"iana"},"application/isup":{"source":"iana"},"application/its+xml":{"source":"iana","compressible":true,"extensions":["its"]},"application/java-archive":{"source":"apache","compressible":false,"extensions":["jar","war","ear"]},"application/java-serialized-object":{"source":"apache","compressible":false,"extensions":["ser"]},"application/java-vm":{"source":"apache","compressible":false,"extensions":["class"]},"application/javascript":{"source":"iana","charset":"UTF-8","compressible":true,"extensions":["js","mjs"]},"application/jf2feed+json":{"source":"iana","compressible":true},"application/jose":{"source":"iana"},"application/jose+json":{"source":"iana","compressible":true},"application/jrd+json":{"source":"iana","compressible":true},"application/json":{"source":"iana","charset":"UTF-8","compressible":true,"extensions":["json","map"]},"application/json-patch+json":{"source":"iana","compressible":true},"application/json-seq":{"source":"iana"},"application/json5":{"extensions":["json5"]},"application/jsonml+json":{"source":"apache","compressible":true,"extensions":["jsonml"]},"application/jwk+json":{"source":"iana","compressible":true},"application/jwk-set+json":{"source":"iana","compressible":true},"application/jwt":{"source":"iana"},"application/kpml-request+xml":{"source":"iana","compressible":true},"application/kpml-response+xml":{"source":"iana","compressible":true},"application/ld+json":{"source":"iana","compressible":true,"extensions":["jsonld"]},"application/lgr+xml":{"source":"iana","compressible":true,"extensions":["lgr"]},"application/link-format":{"source":"iana"},"application/load-control+xml":{"source":"iana","compressible":true},"application/lost+xml":{"source":"iana","compressible":true,"extensions":["lostxml"]},"application/lostsync+xml":{"source":"iana","compressible":true},"application/lpf+zip":{"source":"iana","compressible":false},"application/lxf":{"source":"iana"},"application/mac-binhex40":{"source":"iana","extensions":["hqx"]},"application/mac-compactpro":{"source":"apache","extensions":["cpt"]},"application/macwriteii":{"source":"iana"},"application/mads+xml":{"source":"iana","compressible":true,"extensions":["mads"]},"application/manifest+json":{"charset":"UTF-8","compressible":true,"extensions":["webmanifest"]},"application/marc":{"source":"iana","extensions":["mrc"]},"application/marcxml+xml":{"source":"iana","compressible":true,"extensions":["mrcx"]},"application/mathematica":{"source":"iana","extensions":["ma","nb","mb"]},"application/mathml+xml":{"source":"iana","compressible":true,"extensions":["mathml"]},"application/mathml-content+xml":{"source":"iana","compressible":true},"application/mathml-presentation+xml":{"source":"iana","compressible":true},"application/mbms-associated-procedure-description+xml":{"source":"iana","compressible":true},"application/mbms-deregister+xml":{"source":"iana","compressible":true},"application/mbms-envelope+xml":{"source":"iana","compressible":true},"application/mbms-msk+xml":{"source":"iana","compressible":true},"application/mbms-msk-response+xml":{"source":"iana","compressible":true},"application/mbms-protection-description+xml":{"source":"iana","compressible":true},"application/mbms-reception-report+xml":{"source":"iana","compressible":true},"application/mbms-register+xml":{"source":"iana","compressible":true},"application/mbms-register-response+xml":{"source":"iana","compressible":true},"application/mbms-schedule+xml":{"source":"iana","compressible":true},"application/mbms-user-service-description+xml":{"source":"iana","compressible":true},"application/mbox":{"source":"iana","extensions":["mbox"]},"application/media-policy-dataset+xml":{"source":"iana","compressible":true},"application/media_control+xml":{"source":"iana","compressible":true},"application/mediaservercontrol+xml":{"source":"iana","compressible":true,"extensions":["mscml"]},"application/merge-patch+json":{"source":"iana","compressible":true},"application/metalink+xml":{"source":"apache","compressible":true,"extensions":["metalink"]},"application/metalink4+xml":{"source":"iana","compressible":true,"extensions":["meta4"]},"application/mets+xml":{"source":"iana","compressible":true,"extensions":["mets"]},"application/mf4":{"source":"iana"},"application/mikey":{"source":"iana"},"application/mipc":{"source":"iana"},"application/mmt-aei+xml":{"source":"iana","compressible":true,"extensions":["maei"]},"application/mmt-usd+xml":{"source":"iana","compressible":true,"extensions":["musd"]},"application/mods+xml":{"source":"iana","compressible":true,"extensions":["mods"]},"application/moss-keys":{"source":"iana"},"application/moss-signature":{"source":"iana"},"application/mosskey-data":{"source":"iana"},"application/mosskey-request":{"source":"iana"},"application/mp21":{"source":"iana","extensions":["m21","mp21"]},"application/mp4":{"source":"iana","extensions":["mp4s","m4p"]},"application/mpeg4-generic":{"source":"iana"},"application/mpeg4-iod":{"source":"iana"},"application/mpeg4-iod-xmt":{"source":"iana"},"application/mrb-consumer+xml":{"source":"iana","compressible":true,"extensions":["xdf"]},"application/mrb-publish+xml":{"source":"iana","compressible":true,"extensions":["xdf"]},"application/msc-ivr+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/msc-mixer+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/msword":{"source":"iana","compressible":false,"extensions":["doc","dot"]},"application/mud+json":{"source":"iana","compressible":true},"application/multipart-core":{"source":"iana"},"application/mxf":{"source":"iana","extensions":["mxf"]},"application/n-quads":{"source":"iana","extensions":["nq"]},"application/n-triples":{"source":"iana","extensions":["nt"]},"application/nasdata":{"source":"iana"},"application/news-checkgroups":{"source":"iana","charset":"US-ASCII"},"application/news-groupinfo":{"source":"iana","charset":"US-ASCII"},"application/news-transmission":{"source":"iana"},"application/nlsml+xml":{"source":"iana","compressible":true},"application/node":{"source":"iana","extensions":["cjs"]},"application/nss":{"source":"iana"},"application/ocsp-request":{"source":"iana"},"application/ocsp-response":{"source":"iana"},"application/octet-stream":{"source":"iana","compressible":false,"extensions":["bin","dms","lrf","mar","so","dist","distz","pkg","bpk","dump","elc","deploy","exe","dll","deb","dmg","iso","img","msi","msp","msm","buffer"]},"application/oda":{"source":"iana","extensions":["oda"]},"application/odm+xml":{"source":"iana","compressible":true},"application/odx":{"source":"iana"},"application/oebps-package+xml":{"source":"iana","compressible":true,"extensions":["opf"]},"application/ogg":{"source":"iana","compressible":false,"extensions":["ogx"]},"application/omdoc+xml":{"source":"apache","compressible":true,"extensions":["omdoc"]},"application/onenote":{"source":"apache","extensions":["onetoc","onetoc2","onetmp","onepkg"]},"application/oscore":{"source":"iana"},"application/oxps":{"source":"iana","extensions":["oxps"]},"application/p2p-overlay+xml":{"source":"iana","compressible":true,"extensions":["relo"]},"application/parityfec":{"source":"iana"},"application/passport":{"source":"iana"},"application/patch-ops-error+xml":{"source":"iana","compressible":true,"extensions":["xer"]},"application/pdf":{"source":"iana","compressible":false,"extensions":["pdf"]},"application/pdx":{"source":"iana"},"application/pem-certificate-chain":{"source":"iana"},"application/pgp-encrypted":{"source":"iana","compressible":false,"extensions":["pgp"]},"application/pgp-keys":{"source":"iana"},"application/pgp-signature":{"source":"iana","extensions":["asc","sig"]},"application/pics-rules":{"source":"apache","extensions":["prf"]},"application/pidf+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/pidf-diff+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/pkcs10":{"source":"iana","extensions":["p10"]},"application/pkcs12":{"source":"iana"},"application/pkcs7-mime":{"source":"iana","extensions":["p7m","p7c"]},"application/pkcs7-signature":{"source":"iana","extensions":["p7s"]},"application/pkcs8":{"source":"iana","extensions":["p8"]},"application/pkcs8-encrypted":{"source":"iana"},"application/pkix-attr-cert":{"source":"iana","extensions":["ac"]},"application/pkix-cert":{"source":"iana","extensions":["cer"]},"application/pkix-crl":{"source":"iana","extensions":["crl"]},"application/pkix-pkipath":{"source":"iana","extensions":["pkipath"]},"application/pkixcmp":{"source":"iana","extensions":["pki"]},"application/pls+xml":{"source":"iana","compressible":true,"extensions":["pls"]},"application/poc-settings+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/postscript":{"source":"iana","compressible":true,"extensions":["ai","eps","ps"]},"application/ppsp-tracker+json":{"source":"iana","compressible":true},"application/problem+json":{"source":"iana","compressible":true},"application/problem+xml":{"source":"iana","compressible":true},"application/provenance+xml":{"source":"iana","compressible":true,"extensions":["provx"]},"application/prs.alvestrand.titrax-sheet":{"source":"iana"},"application/prs.cww":{"source":"iana","extensions":["cww"]},"application/prs.hpub+zip":{"source":"iana","compressible":false},"application/prs.nprend":{"source":"iana"},"application/prs.plucker":{"source":"iana"},"application/prs.rdf-xml-crypt":{"source":"iana"},"application/prs.xsf+xml":{"source":"iana","compressible":true},"application/pskc+xml":{"source":"iana","compressible":true,"extensions":["pskcxml"]},"application/pvd+json":{"source":"iana","compressible":true},"application/qsig":{"source":"iana"},"application/raml+yaml":{"compressible":true,"extensions":["raml"]},"application/raptorfec":{"source":"iana"},"application/rdap+json":{"source":"iana","compressible":true},"application/rdf+xml":{"source":"iana","compressible":true,"extensions":["rdf","owl"]},"application/reginfo+xml":{"source":"iana","compressible":true,"extensions":["rif"]},"application/relax-ng-compact-syntax":{"source":"iana","extensions":["rnc"]},"application/remote-printing":{"source":"iana"},"application/reputon+json":{"source":"iana","compressible":true},"application/resource-lists+xml":{"source":"iana","compressible":true,"extensions":["rl"]},"application/resource-lists-diff+xml":{"source":"iana","compressible":true,"extensions":["rld"]},"application/rfc+xml":{"source":"iana","compressible":true},"application/riscos":{"source":"iana"},"application/rlmi+xml":{"source":"iana","compressible":true},"application/rls-services+xml":{"source":"iana","compressible":true,"extensions":["rs"]},"application/route-apd+xml":{"source":"iana","compressible":true,"extensions":["rapd"]},"application/route-s-tsid+xml":{"source":"iana","compressible":true,"extensions":["sls"]},"application/route-usd+xml":{"source":"iana","compressible":true,"extensions":["rusd"]},"application/rpki-ghostbusters":{"source":"iana","extensions":["gbr"]},"application/rpki-manifest":{"source":"iana","extensions":["mft"]},"application/rpki-publication":{"source":"iana"},"application/rpki-roa":{"source":"iana","extensions":["roa"]},"application/rpki-updown":{"source":"iana"},"application/rsd+xml":{"source":"apache","compressible":true,"extensions":["rsd"]},"application/rss+xml":{"source":"apache","compressible":true,"extensions":["rss"]},"application/rtf":{"source":"iana","compressible":true,"extensions":["rtf"]},"application/rtploopback":{"source":"iana"},"application/rtx":{"source":"iana"},"application/samlassertion+xml":{"source":"iana","compressible":true},"application/samlmetadata+xml":{"source":"iana","compressible":true},"application/sbe":{"source":"iana"},"application/sbml+xml":{"source":"iana","compressible":true,"extensions":["sbml"]},"application/scaip+xml":{"source":"iana","compressible":true},"application/scim+json":{"source":"iana","compressible":true},"application/scvp-cv-request":{"source":"iana","extensions":["scq"]},"application/scvp-cv-response":{"source":"iana","extensions":["scs"]},"application/scvp-vp-request":{"source":"iana","extensions":["spq"]},"application/scvp-vp-response":{"source":"iana","extensions":["spp"]},"application/sdp":{"source":"iana","extensions":["sdp"]},"application/secevent+jwt":{"source":"iana"},"application/senml+cbor":{"source":"iana"},"application/senml+json":{"source":"iana","compressible":true},"application/senml+xml":{"source":"iana","compressible":true,"extensions":["senmlx"]},"application/senml-etch+cbor":{"source":"iana"},"application/senml-etch+json":{"source":"iana","compressible":true},"application/senml-exi":{"source":"iana"},"application/sensml+cbor":{"source":"iana"},"application/sensml+json":{"source":"iana","compressible":true},"application/sensml+xml":{"source":"iana","compressible":true,"extensions":["sensmlx"]},"application/sensml-exi":{"source":"iana"},"application/sep+xml":{"source":"iana","compressible":true},"application/sep-exi":{"source":"iana"},"application/session-info":{"source":"iana"},"application/set-payment":{"source":"iana"},"application/set-payment-initiation":{"source":"iana","extensions":["setpay"]},"application/set-registration":{"source":"iana"},"application/set-registration-initiation":{"source":"iana","extensions":["setreg"]},"application/sgml":{"source":"iana"},"application/sgml-open-catalog":{"source":"iana"},"application/shf+xml":{"source":"iana","compressible":true,"extensions":["shf"]},"application/sieve":{"source":"iana","extensions":["siv","sieve"]},"application/simple-filter+xml":{"source":"iana","compressible":true},"application/simple-message-summary":{"source":"iana"},"application/simplesymbolcontainer":{"source":"iana"},"application/sipc":{"source":"iana"},"application/slate":{"source":"iana"},"application/smil":{"source":"iana"},"application/smil+xml":{"source":"iana","compressible":true,"extensions":["smi","smil"]},"application/smpte336m":{"source":"iana"},"application/soap+fastinfoset":{"source":"iana"},"application/soap+xml":{"source":"iana","compressible":true},"application/sparql-query":{"source":"iana","extensions":["rq"]},"application/sparql-results+xml":{"source":"iana","compressible":true,"extensions":["srx"]},"application/spirits-event+xml":{"source":"iana","compressible":true},"application/sql":{"source":"iana"},"application/srgs":{"source":"iana","extensions":["gram"]},"application/srgs+xml":{"source":"iana","compressible":true,"extensions":["grxml"]},"application/sru+xml":{"source":"iana","compressible":true,"extensions":["sru"]},"application/ssdl+xml":{"source":"apache","compressible":true,"extensions":["ssdl"]},"application/ssml+xml":{"source":"iana","compressible":true,"extensions":["ssml"]},"application/stix+json":{"source":"iana","compressible":true},"application/swid+xml":{"source":"iana","compressible":true,"extensions":["swidtag"]},"application/tamp-apex-update":{"source":"iana"},"application/tamp-apex-update-confirm":{"source":"iana"},"application/tamp-community-update":{"source":"iana"},"application/tamp-community-update-confirm":{"source":"iana"},"application/tamp-error":{"source":"iana"},"application/tamp-sequence-adjust":{"source":"iana"},"application/tamp-sequence-adjust-confirm":{"source":"iana"},"application/tamp-status-query":{"source":"iana"},"application/tamp-status-response":{"source":"iana"},"application/tamp-update":{"source":"iana"},"application/tamp-update-confirm":{"source":"iana"},"application/tar":{"compressible":true},"application/taxii+json":{"source":"iana","compressible":true},"application/td+json":{"source":"iana","compressible":true},"application/tei+xml":{"source":"iana","compressible":true,"extensions":["tei","teicorpus"]},"application/tetra_isi":{"source":"iana"},"application/thraud+xml":{"source":"iana","compressible":true,"extensions":["tfi"]},"application/timestamp-query":{"source":"iana"},"application/timestamp-reply":{"source":"iana"},"application/timestamped-data":{"source":"iana","extensions":["tsd"]},"application/tlsrpt+gzip":{"source":"iana"},"application/tlsrpt+json":{"source":"iana","compressible":true},"application/tnauthlist":{"source":"iana"},"application/toml":{"compressible":true,"extensions":["toml"]},"application/trickle-ice-sdpfrag":{"source":"iana"},"application/trig":{"source":"iana"},"application/ttml+xml":{"source":"iana","compressible":true,"extensions":["ttml"]},"application/tve-trigger":{"source":"iana"},"application/tzif":{"source":"iana"},"application/tzif-leap":{"source":"iana"},"application/ulpfec":{"source":"iana"},"application/urc-grpsheet+xml":{"source":"iana","compressible":true},"application/urc-ressheet+xml":{"source":"iana","compressible":true,"extensions":["rsheet"]},"application/urc-targetdesc+xml":{"source":"iana","compressible":true},"application/urc-uisocketdesc+xml":{"source":"iana","compressible":true},"application/vcard+json":{"source":"iana","compressible":true},"application/vcard+xml":{"source":"iana","compressible":true},"application/vemmi":{"source":"iana"},"application/vividence.scriptfile":{"source":"apache"},"application/vnd.1000minds.decision-model+xml":{"source":"iana","compressible":true,"extensions":["1km"]},"application/vnd.3gpp-prose+xml":{"source":"iana","compressible":true},"application/vnd.3gpp-prose-pc3ch+xml":{"source":"iana","compressible":true},"application/vnd.3gpp-v2x-local-service-information":{"source":"iana"},"application/vnd.3gpp.access-transfer-events+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.bsf+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.gmop+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mc-signalling-ear":{"source":"iana"},"application/vnd.3gpp.mcdata-affiliation-command+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcdata-info+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcdata-payload":{"source":"iana"},"application/vnd.3gpp.mcdata-service-config+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcdata-signalling":{"source":"iana"},"application/vnd.3gpp.mcdata-ue-config+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcdata-user-profile+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcptt-affiliation-command+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcptt-floor-request+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcptt-info+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcptt-location-info+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcptt-mbms-usage-info+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcptt-service-config+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcptt-signed+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcptt-ue-config+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcptt-ue-init-config+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcptt-user-profile+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcvideo-affiliation-command+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcvideo-affiliation-info+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcvideo-info+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcvideo-location-info+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcvideo-mbms-usage-info+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcvideo-service-config+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcvideo-transmission-request+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcvideo-ue-config+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mcvideo-user-profile+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.mid-call+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.pic-bw-large":{"source":"iana","extensions":["plb"]},"application/vnd.3gpp.pic-bw-small":{"source":"iana","extensions":["psb"]},"application/vnd.3gpp.pic-bw-var":{"source":"iana","extensions":["pvb"]},"application/vnd.3gpp.sms":{"source":"iana"},"application/vnd.3gpp.sms+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.srvcc-ext+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.srvcc-info+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.state-and-event-info+xml":{"source":"iana","compressible":true},"application/vnd.3gpp.ussd+xml":{"source":"iana","compressible":true},"application/vnd.3gpp2.bcmcsinfo+xml":{"source":"iana","compressible":true},"application/vnd.3gpp2.sms":{"source":"iana"},"application/vnd.3gpp2.tcap":{"source":"iana","extensions":["tcap"]},"application/vnd.3lightssoftware.imagescal":{"source":"iana"},"application/vnd.3m.post-it-notes":{"source":"iana","extensions":["pwn"]},"application/vnd.accpac.simply.aso":{"source":"iana","extensions":["aso"]},"application/vnd.accpac.simply.imp":{"source":"iana","extensions":["imp"]},"application/vnd.acucobol":{"source":"iana","extensions":["acu"]},"application/vnd.acucorp":{"source":"iana","extensions":["atc","acutc"]},"application/vnd.adobe.air-application-installer-package+zip":{"source":"apache","compressible":false,"extensions":["air"]},"application/vnd.adobe.flash.movie":{"source":"iana"},"application/vnd.adobe.formscentral.fcdt":{"source":"iana","extensions":["fcdt"]},"application/vnd.adobe.fxp":{"source":"iana","extensions":["fxp","fxpl"]},"application/vnd.adobe.partial-upload":{"source":"iana"},"application/vnd.adobe.xdp+xml":{"source":"iana","compressible":true,"extensions":["xdp"]},"application/vnd.adobe.xfdf":{"source":"iana","extensions":["xfdf"]},"application/vnd.aether.imp":{"source":"iana"},"application/vnd.afpc.afplinedata":{"source":"iana"},"application/vnd.afpc.afplinedata-pagedef":{"source":"iana"},"application/vnd.afpc.foca-charset":{"source":"iana"},"application/vnd.afpc.foca-codedfont":{"source":"iana"},"application/vnd.afpc.foca-codepage":{"source":"iana"},"application/vnd.afpc.modca":{"source":"iana"},"application/vnd.afpc.modca-formdef":{"source":"iana"},"application/vnd.afpc.modca-mediummap":{"source":"iana"},"application/vnd.afpc.modca-objectcontainer":{"source":"iana"},"application/vnd.afpc.modca-overlay":{"source":"iana"},"application/vnd.afpc.modca-pagesegment":{"source":"iana"},"application/vnd.ah-barcode":{"source":"iana"},"application/vnd.ahead.space":{"source":"iana","extensions":["ahead"]},"application/vnd.airzip.filesecure.azf":{"source":"iana","extensions":["azf"]},"application/vnd.airzip.filesecure.azs":{"source":"iana","extensions":["azs"]},"application/vnd.amadeus+json":{"source":"iana","compressible":true},"application/vnd.amazon.ebook":{"source":"apache","extensions":["azw"]},"application/vnd.amazon.mobi8-ebook":{"source":"iana"},"application/vnd.americandynamics.acc":{"source":"iana","extensions":["acc"]},"application/vnd.amiga.ami":{"source":"iana","extensions":["ami"]},"application/vnd.amundsen.maze+xml":{"source":"iana","compressible":true},"application/vnd.android.ota":{"source":"iana"},"application/vnd.android.package-archive":{"source":"apache","compressible":false,"extensions":["apk"]},"application/vnd.anki":{"source":"iana"},"application/vnd.anser-web-certificate-issue-initiation":{"source":"iana","extensions":["cii"]},"application/vnd.anser-web-funds-transfer-initiation":{"source":"apache","extensions":["fti"]},"application/vnd.antix.game-component":{"source":"iana","extensions":["atx"]},"application/vnd.apache.thrift.binary":{"source":"iana"},"application/vnd.apache.thrift.compact":{"source":"iana"},"application/vnd.apache.thrift.json":{"source":"iana"},"application/vnd.api+json":{"source":"iana","compressible":true},"application/vnd.aplextor.warrp+json":{"source":"iana","compressible":true},"application/vnd.apothekende.reservation+json":{"source":"iana","compressible":true},"application/vnd.apple.installer+xml":{"source":"iana","compressible":true,"extensions":["mpkg"]},"application/vnd.apple.keynote":{"source":"iana","extensions":["keynote"]},"application/vnd.apple.mpegurl":{"source":"iana","extensions":["m3u8"]},"application/vnd.apple.numbers":{"source":"iana","extensions":["numbers"]},"application/vnd.apple.pages":{"source":"iana","extensions":["pages"]},"application/vnd.apple.pkpass":{"compressible":false,"extensions":["pkpass"]},"application/vnd.arastra.swi":{"source":"iana"},"application/vnd.aristanetworks.swi":{"source":"iana","extensions":["swi"]},"application/vnd.artisan+json":{"source":"iana","compressible":true},"application/vnd.artsquare":{"source":"iana"},"application/vnd.astraea-software.iota":{"source":"iana","extensions":["iota"]},"application/vnd.audiograph":{"source":"iana","extensions":["aep"]},"application/vnd.autopackage":{"source":"iana"},"application/vnd.avalon+json":{"source":"iana","compressible":true},"application/vnd.avistar+xml":{"source":"iana","compressible":true},"application/vnd.balsamiq.bmml+xml":{"source":"iana","compressible":true,"extensions":["bmml"]},"application/vnd.balsamiq.bmpr":{"source":"iana"},"application/vnd.banana-accounting":{"source":"iana"},"application/vnd.bbf.usp.error":{"source":"iana"},"application/vnd.bbf.usp.msg":{"source":"iana"},"application/vnd.bbf.usp.msg+json":{"source":"iana","compressible":true},"application/vnd.bekitzur-stech+json":{"source":"iana","compressible":true},"application/vnd.bint.med-content":{"source":"iana"},"application/vnd.biopax.rdf+xml":{"source":"iana","compressible":true},"application/vnd.blink-idb-value-wrapper":{"source":"iana"},"application/vnd.blueice.multipass":{"source":"iana","extensions":["mpm"]},"application/vnd.bluetooth.ep.oob":{"source":"iana"},"application/vnd.bluetooth.le.oob":{"source":"iana"},"application/vnd.bmi":{"source":"iana","extensions":["bmi"]},"application/vnd.bpf":{"source":"iana"},"application/vnd.bpf3":{"source":"iana"},"application/vnd.businessobjects":{"source":"iana","extensions":["rep"]},"application/vnd.byu.uapi+json":{"source":"iana","compressible":true},"application/vnd.cab-jscript":{"source":"iana"},"application/vnd.canon-cpdl":{"source":"iana"},"application/vnd.canon-lips":{"source":"iana"},"application/vnd.capasystems-pg+json":{"source":"iana","compressible":true},"application/vnd.cendio.thinlinc.clientconf":{"source":"iana"},"application/vnd.century-systems.tcp_stream":{"source":"iana"},"application/vnd.chemdraw+xml":{"source":"iana","compressible":true,"extensions":["cdxml"]},"application/vnd.chess-pgn":{"source":"iana"},"application/vnd.chipnuts.karaoke-mmd":{"source":"iana","extensions":["mmd"]},"application/vnd.ciedi":{"source":"iana"},"application/vnd.cinderella":{"source":"iana","extensions":["cdy"]},"application/vnd.cirpack.isdn-ext":{"source":"iana"},"application/vnd.citationstyles.style+xml":{"source":"iana","compressible":true,"extensions":["csl"]},"application/vnd.claymore":{"source":"iana","extensions":["cla"]},"application/vnd.cloanto.rp9":{"source":"iana","extensions":["rp9"]},"application/vnd.clonk.c4group":{"source":"iana","extensions":["c4g","c4d","c4f","c4p","c4u"]},"application/vnd.cluetrust.cartomobile-config":{"source":"iana","extensions":["c11amc"]},"application/vnd.cluetrust.cartomobile-config-pkg":{"source":"iana","extensions":["c11amz"]},"application/vnd.coffeescript":{"source":"iana"},"application/vnd.collabio.xodocuments.document":{"source":"iana"},"application/vnd.collabio.xodocuments.document-template":{"source":"iana"},"application/vnd.collabio.xodocuments.presentation":{"source":"iana"},"application/vnd.collabio.xodocuments.presentation-template":{"source":"iana"},"application/vnd.collabio.xodocuments.spreadsheet":{"source":"iana"},"application/vnd.collabio.xodocuments.spreadsheet-template":{"source":"iana"},"application/vnd.collection+json":{"source":"iana","compressible":true},"application/vnd.collection.doc+json":{"source":"iana","compressible":true},"application/vnd.collection.next+json":{"source":"iana","compressible":true},"application/vnd.comicbook+zip":{"source":"iana","compressible":false},"application/vnd.comicbook-rar":{"source":"iana"},"application/vnd.commerce-battelle":{"source":"iana"},"application/vnd.commonspace":{"source":"iana","extensions":["csp"]},"application/vnd.contact.cmsg":{"source":"iana","extensions":["cdbcmsg"]},"application/vnd.coreos.ignition+json":{"source":"iana","compressible":true},"application/vnd.cosmocaller":{"source":"iana","extensions":["cmc"]},"application/vnd.crick.clicker":{"source":"iana","extensions":["clkx"]},"application/vnd.crick.clicker.keyboard":{"source":"iana","extensions":["clkk"]},"application/vnd.crick.clicker.palette":{"source":"iana","extensions":["clkp"]},"application/vnd.crick.clicker.template":{"source":"iana","extensions":["clkt"]},"application/vnd.crick.clicker.wordbank":{"source":"iana","extensions":["clkw"]},"application/vnd.criticaltools.wbs+xml":{"source":"iana","compressible":true,"extensions":["wbs"]},"application/vnd.cryptii.pipe+json":{"source":"iana","compressible":true},"application/vnd.crypto-shade-file":{"source":"iana"},"application/vnd.ctc-posml":{"source":"iana","extensions":["pml"]},"application/vnd.ctct.ws+xml":{"source":"iana","compressible":true},"application/vnd.cups-pdf":{"source":"iana"},"application/vnd.cups-postscript":{"source":"iana"},"application/vnd.cups-ppd":{"source":"iana","extensions":["ppd"]},"application/vnd.cups-raster":{"source":"iana"},"application/vnd.cups-raw":{"source":"iana"},"application/vnd.curl":{"source":"iana"},"application/vnd.curl.car":{"source":"apache","extensions":["car"]},"application/vnd.curl.pcurl":{"source":"apache","extensions":["pcurl"]},"application/vnd.cyan.dean.root+xml":{"source":"iana","compressible":true},"application/vnd.cybank":{"source":"iana"},"application/vnd.d2l.coursepackage1p0+zip":{"source":"iana","compressible":false},"application/vnd.dart":{"source":"iana","compressible":true,"extensions":["dart"]},"application/vnd.data-vision.rdz":{"source":"iana","extensions":["rdz"]},"application/vnd.datapackage+json":{"source":"iana","compressible":true},"application/vnd.dataresource+json":{"source":"iana","compressible":true},"application/vnd.dbf":{"source":"iana"},"application/vnd.debian.binary-package":{"source":"iana"},"application/vnd.dece.data":{"source":"iana","extensions":["uvf","uvvf","uvd","uvvd"]},"application/vnd.dece.ttml+xml":{"source":"iana","compressible":true,"extensions":["uvt","uvvt"]},"application/vnd.dece.unspecified":{"source":"iana","extensions":["uvx","uvvx"]},"application/vnd.dece.zip":{"source":"iana","extensions":["uvz","uvvz"]},"application/vnd.denovo.fcselayout-link":{"source":"iana","extensions":["fe_launch"]},"application/vnd.desmume.movie":{"source":"iana"},"application/vnd.dir-bi.plate-dl-nosuffix":{"source":"iana"},"application/vnd.dm.delegation+xml":{"source":"iana","compressible":true},"application/vnd.dna":{"source":"iana","extensions":["dna"]},"application/vnd.document+json":{"source":"iana","compressible":true},"application/vnd.dolby.mlp":{"source":"apache","extensions":["mlp"]},"application/vnd.dolby.mobile.1":{"source":"iana"},"application/vnd.dolby.mobile.2":{"source":"iana"},"application/vnd.doremir.scorecloud-binary-document":{"source":"iana"},"application/vnd.dpgraph":{"source":"iana","extensions":["dpg"]},"application/vnd.dreamfactory":{"source":"iana","extensions":["dfac"]},"application/vnd.drive+json":{"source":"iana","compressible":true},"application/vnd.ds-keypoint":{"source":"apache","extensions":["kpxx"]},"application/vnd.dtg.local":{"source":"iana"},"application/vnd.dtg.local.flash":{"source":"iana"},"application/vnd.dtg.local.html":{"source":"iana"},"application/vnd.dvb.ait":{"source":"iana","extensions":["ait"]},"application/vnd.dvb.dvbisl+xml":{"source":"iana","compressible":true},"application/vnd.dvb.dvbj":{"source":"iana"},"application/vnd.dvb.esgcontainer":{"source":"iana"},"application/vnd.dvb.ipdcdftnotifaccess":{"source":"iana"},"application/vnd.dvb.ipdcesgaccess":{"source":"iana"},"application/vnd.dvb.ipdcesgaccess2":{"source":"iana"},"application/vnd.dvb.ipdcesgpdd":{"source":"iana"},"application/vnd.dvb.ipdcroaming":{"source":"iana"},"application/vnd.dvb.iptv.alfec-base":{"source":"iana"},"application/vnd.dvb.iptv.alfec-enhancement":{"source":"iana"},"application/vnd.dvb.notif-aggregate-root+xml":{"source":"iana","compressible":true},"application/vnd.dvb.notif-container+xml":{"source":"iana","compressible":true},"application/vnd.dvb.notif-generic+xml":{"source":"iana","compressible":true},"application/vnd.dvb.notif-ia-msglist+xml":{"source":"iana","compressible":true},"application/vnd.dvb.notif-ia-registration-request+xml":{"source":"iana","compressible":true},"application/vnd.dvb.notif-ia-registration-response+xml":{"source":"iana","compressible":true},"application/vnd.dvb.notif-init+xml":{"source":"iana","compressible":true},"application/vnd.dvb.pfr":{"source":"iana"},"application/vnd.dvb.service":{"source":"iana","extensions":["svc"]},"application/vnd.dxr":{"source":"iana"},"application/vnd.dynageo":{"source":"iana","extensions":["geo"]},"application/vnd.dzr":{"source":"iana"},"application/vnd.easykaraoke.cdgdownload":{"source":"iana"},"application/vnd.ecdis-update":{"source":"iana"},"application/vnd.ecip.rlp":{"source":"iana"},"application/vnd.ecowin.chart":{"source":"iana","extensions":["mag"]},"application/vnd.ecowin.filerequest":{"source":"iana"},"application/vnd.ecowin.fileupdate":{"source":"iana"},"application/vnd.ecowin.series":{"source":"iana"},"application/vnd.ecowin.seriesrequest":{"source":"iana"},"application/vnd.ecowin.seriesupdate":{"source":"iana"},"application/vnd.efi.img":{"source":"iana"},"application/vnd.efi.iso":{"source":"iana"},"application/vnd.emclient.accessrequest+xml":{"source":"iana","compressible":true},"application/vnd.enliven":{"source":"iana","extensions":["nml"]},"application/vnd.enphase.envoy":{"source":"iana"},"application/vnd.eprints.data+xml":{"source":"iana","compressible":true},"application/vnd.epson.esf":{"source":"iana","extensions":["esf"]},"application/vnd.epson.msf":{"source":"iana","extensions":["msf"]},"application/vnd.epson.quickanime":{"source":"iana","extensions":["qam"]},"application/vnd.epson.salt":{"source":"iana","extensions":["slt"]},"application/vnd.epson.ssf":{"source":"iana","extensions":["ssf"]},"application/vnd.ericsson.quickcall":{"source":"iana"},"application/vnd.espass-espass+zip":{"source":"iana","compressible":false},"application/vnd.eszigno3+xml":{"source":"iana","compressible":true,"extensions":["es3","et3"]},"application/vnd.etsi.aoc+xml":{"source":"iana","compressible":true},"application/vnd.etsi.asic-e+zip":{"source":"iana","compressible":false},"application/vnd.etsi.asic-s+zip":{"source":"iana","compressible":false},"application/vnd.etsi.cug+xml":{"source":"iana","compressible":true},"application/vnd.etsi.iptvcommand+xml":{"source":"iana","compressible":true},"application/vnd.etsi.iptvdiscovery+xml":{"source":"iana","compressible":true},"application/vnd.etsi.iptvprofile+xml":{"source":"iana","compressible":true},"application/vnd.etsi.iptvsad-bc+xml":{"source":"iana","compressible":true},"application/vnd.etsi.iptvsad-cod+xml":{"source":"iana","compressible":true},"application/vnd.etsi.iptvsad-npvr+xml":{"source":"iana","compressible":true},"application/vnd.etsi.iptvservice+xml":{"source":"iana","compressible":true},"application/vnd.etsi.iptvsync+xml":{"source":"iana","compressible":true},"application/vnd.etsi.iptvueprofile+xml":{"source":"iana","compressible":true},"application/vnd.etsi.mcid+xml":{"source":"iana","compressible":true},"application/vnd.etsi.mheg5":{"source":"iana"},"application/vnd.etsi.overload-control-policy-dataset+xml":{"source":"iana","compressible":true},"application/vnd.etsi.pstn+xml":{"source":"iana","compressible":true},"application/vnd.etsi.sci+xml":{"source":"iana","compressible":true},"application/vnd.etsi.simservs+xml":{"source":"iana","compressible":true},"application/vnd.etsi.timestamp-token":{"source":"iana"},"application/vnd.etsi.tsl+xml":{"source":"iana","compressible":true},"application/vnd.etsi.tsl.der":{"source":"iana"},"application/vnd.eudora.data":{"source":"iana"},"application/vnd.evolv.ecig.profile":{"source":"iana"},"application/vnd.evolv.ecig.settings":{"source":"iana"},"application/vnd.evolv.ecig.theme":{"source":"iana"},"application/vnd.exstream-empower+zip":{"source":"iana","compressible":false},"application/vnd.exstream-package":{"source":"iana"},"application/vnd.ezpix-album":{"source":"iana","extensions":["ez2"]},"application/vnd.ezpix-package":{"source":"iana","extensions":["ez3"]},"application/vnd.f-secure.mobile":{"source":"iana"},"application/vnd.fastcopy-disk-image":{"source":"iana"},"application/vnd.fdf":{"source":"iana","extensions":["fdf"]},"application/vnd.fdsn.mseed":{"source":"iana","extensions":["mseed"]},"application/vnd.fdsn.seed":{"source":"iana","extensions":["seed","dataless"]},"application/vnd.ffsns":{"source":"iana"},"application/vnd.ficlab.flb+zip":{"source":"iana","compressible":false},"application/vnd.filmit.zfc":{"source":"iana"},"application/vnd.fints":{"source":"iana"},"application/vnd.firemonkeys.cloudcell":{"source":"iana"},"application/vnd.flographit":{"source":"iana","extensions":["gph"]},"application/vnd.fluxtime.clip":{"source":"iana","extensions":["ftc"]},"application/vnd.font-fontforge-sfd":{"source":"iana"},"application/vnd.framemaker":{"source":"iana","extensions":["fm","frame","maker","book"]},"application/vnd.frogans.fnc":{"source":"iana","extensions":["fnc"]},"application/vnd.frogans.ltf":{"source":"iana","extensions":["ltf"]},"application/vnd.fsc.weblaunch":{"source":"iana","extensions":["fsc"]},"application/vnd.fujitsu.oasys":{"source":"iana","extensions":["oas"]},"application/vnd.fujitsu.oasys2":{"source":"iana","extensions":["oa2"]},"application/vnd.fujitsu.oasys3":{"source":"iana","extensions":["oa3"]},"application/vnd.fujitsu.oasysgp":{"source":"iana","extensions":["fg5"]},"application/vnd.fujitsu.oasysprs":{"source":"iana","extensions":["bh2"]},"application/vnd.fujixerox.art-ex":{"source":"iana"},"application/vnd.fujixerox.art4":{"source":"iana"},"application/vnd.fujixerox.ddd":{"source":"iana","extensions":["ddd"]},"application/vnd.fujixerox.docuworks":{"source":"iana","extensions":["xdw"]},"application/vnd.fujixerox.docuworks.binder":{"source":"iana","extensions":["xbd"]},"application/vnd.fujixerox.docuworks.container":{"source":"iana"},"application/vnd.fujixerox.hbpl":{"source":"iana"},"application/vnd.fut-misnet":{"source":"iana"},"application/vnd.futoin+cbor":{"source":"iana"},"application/vnd.futoin+json":{"source":"iana","compressible":true},"application/vnd.fuzzysheet":{"source":"iana","extensions":["fzs"]},"application/vnd.genomatix.tuxedo":{"source":"iana","extensions":["txd"]},"application/vnd.gentics.grd+json":{"source":"iana","compressible":true},"application/vnd.geo+json":{"source":"iana","compressible":true},"application/vnd.geocube+xml":{"source":"iana","compressible":true},"application/vnd.geogebra.file":{"source":"iana","extensions":["ggb"]},"application/vnd.geogebra.tool":{"source":"iana","extensions":["ggt"]},"application/vnd.geometry-explorer":{"source":"iana","extensions":["gex","gre"]},"application/vnd.geonext":{"source":"iana","extensions":["gxt"]},"application/vnd.geoplan":{"source":"iana","extensions":["g2w"]},"application/vnd.geospace":{"source":"iana","extensions":["g3w"]},"application/vnd.gerber":{"source":"iana"},"application/vnd.globalplatform.card-content-mgt":{"source":"iana"},"application/vnd.globalplatform.card-content-mgt-response":{"source":"iana"},"application/vnd.gmx":{"source":"iana","extensions":["gmx"]},"application/vnd.google-apps.document":{"compressible":false,"extensions":["gdoc"]},"application/vnd.google-apps.presentation":{"compressible":false,"extensions":["gslides"]},"application/vnd.google-apps.spreadsheet":{"compressible":false,"extensions":["gsheet"]},"application/vnd.google-earth.kml+xml":{"source":"iana","compressible":true,"extensions":["kml"]},"application/vnd.google-earth.kmz":{"source":"iana","compressible":false,"extensions":["kmz"]},"application/vnd.gov.sk.e-form+xml":{"source":"iana","compressible":true},"application/vnd.gov.sk.e-form+zip":{"source":"iana","compressible":false},"application/vnd.gov.sk.xmldatacontainer+xml":{"source":"iana","compressible":true},"application/vnd.grafeq":{"source":"iana","extensions":["gqf","gqs"]},"application/vnd.gridmp":{"source":"iana"},"application/vnd.groove-account":{"source":"iana","extensions":["gac"]},"application/vnd.groove-help":{"source":"iana","extensions":["ghf"]},"application/vnd.groove-identity-message":{"source":"iana","extensions":["gim"]},"application/vnd.groove-injector":{"source":"iana","extensions":["grv"]},"application/vnd.groove-tool-message":{"source":"iana","extensions":["gtm"]},"application/vnd.groove-tool-template":{"source":"iana","extensions":["tpl"]},"application/vnd.groove-vcard":{"source":"iana","extensions":["vcg"]},"application/vnd.hal+json":{"source":"iana","compressible":true},"application/vnd.hal+xml":{"source":"iana","compressible":true,"extensions":["hal"]},"application/vnd.handheld-entertainment+xml":{"source":"iana","compressible":true,"extensions":["zmm"]},"application/vnd.hbci":{"source":"iana","extensions":["hbci"]},"application/vnd.hc+json":{"source":"iana","compressible":true},"application/vnd.hcl-bireports":{"source":"iana"},"application/vnd.hdt":{"source":"iana"},"application/vnd.heroku+json":{"source":"iana","compressible":true},"application/vnd.hhe.lesson-player":{"source":"iana","extensions":["les"]},"application/vnd.hp-hpgl":{"source":"iana","extensions":["hpgl"]},"application/vnd.hp-hpid":{"source":"iana","extensions":["hpid"]},"application/vnd.hp-hps":{"source":"iana","extensions":["hps"]},"application/vnd.hp-jlyt":{"source":"iana","extensions":["jlt"]},"application/vnd.hp-pcl":{"source":"iana","extensions":["pcl"]},"application/vnd.hp-pclxl":{"source":"iana","extensions":["pclxl"]},"application/vnd.httphone":{"source":"iana"},"application/vnd.hydrostatix.sof-data":{"source":"iana","extensions":["sfd-hdstx"]},"application/vnd.hyper+json":{"source":"iana","compressible":true},"application/vnd.hyper-item+json":{"source":"iana","compressible":true},"application/vnd.hyperdrive+json":{"source":"iana","compressible":true},"application/vnd.hzn-3d-crossword":{"source":"iana"},"application/vnd.ibm.afplinedata":{"source":"iana"},"application/vnd.ibm.electronic-media":{"source":"iana"},"application/vnd.ibm.minipay":{"source":"iana","extensions":["mpy"]},"application/vnd.ibm.modcap":{"source":"iana","extensions":["afp","listafp","list3820"]},"application/vnd.ibm.rights-management":{"source":"iana","extensions":["irm"]},"application/vnd.ibm.secure-container":{"source":"iana","extensions":["sc"]},"application/vnd.iccprofile":{"source":"iana","extensions":["icc","icm"]},"application/vnd.ieee.1905":{"source":"iana"},"application/vnd.igloader":{"source":"iana","extensions":["igl"]},"application/vnd.imagemeter.folder+zip":{"source":"iana","compressible":false},"application/vnd.imagemeter.image+zip":{"source":"iana","compressible":false},"application/vnd.immervision-ivp":{"source":"iana","extensions":["ivp"]},"application/vnd.immervision-ivu":{"source":"iana","extensions":["ivu"]},"application/vnd.ims.imsccv1p1":{"source":"iana"},"application/vnd.ims.imsccv1p2":{"source":"iana"},"application/vnd.ims.imsccv1p3":{"source":"iana"},"application/vnd.ims.lis.v2.result+json":{"source":"iana","compressible":true},"application/vnd.ims.lti.v2.toolconsumerprofile+json":{"source":"iana","compressible":true},"application/vnd.ims.lti.v2.toolproxy+json":{"source":"iana","compressible":true},"application/vnd.ims.lti.v2.toolproxy.id+json":{"source":"iana","compressible":true},"application/vnd.ims.lti.v2.toolsettings+json":{"source":"iana","compressible":true},"application/vnd.ims.lti.v2.toolsettings.simple+json":{"source":"iana","compressible":true},"application/vnd.informedcontrol.rms+xml":{"source":"iana","compressible":true},"application/vnd.informix-visionary":{"source":"iana"},"application/vnd.infotech.project":{"source":"iana"},"application/vnd.infotech.project+xml":{"source":"iana","compressible":true},"application/vnd.innopath.wamp.notification":{"source":"iana"},"application/vnd.insors.igm":{"source":"iana","extensions":["igm"]},"application/vnd.intercon.formnet":{"source":"iana","extensions":["xpw","xpx"]},"application/vnd.intergeo":{"source":"iana","extensions":["i2g"]},"application/vnd.intertrust.digibox":{"source":"iana"},"application/vnd.intertrust.nncp":{"source":"iana"},"application/vnd.intu.qbo":{"source":"iana","extensions":["qbo"]},"application/vnd.intu.qfx":{"source":"iana","extensions":["qfx"]},"application/vnd.iptc.g2.catalogitem+xml":{"source":"iana","compressible":true},"application/vnd.iptc.g2.conceptitem+xml":{"source":"iana","compressible":true},"application/vnd.iptc.g2.knowledgeitem+xml":{"source":"iana","compressible":true},"application/vnd.iptc.g2.newsitem+xml":{"source":"iana","compressible":true},"application/vnd.iptc.g2.newsmessage+xml":{"source":"iana","compressible":true},"application/vnd.iptc.g2.packageitem+xml":{"source":"iana","compressible":true},"application/vnd.iptc.g2.planningitem+xml":{"source":"iana","compressible":true},"application/vnd.ipunplugged.rcprofile":{"source":"iana","extensions":["rcprofile"]},"application/vnd.irepository.package+xml":{"source":"iana","compressible":true,"extensions":["irp"]},"application/vnd.is-xpr":{"source":"iana","extensions":["xpr"]},"application/vnd.isac.fcs":{"source":"iana","extensions":["fcs"]},"application/vnd.iso11783-10+zip":{"source":"iana","compressible":false},"application/vnd.jam":{"source":"iana","extensions":["jam"]},"application/vnd.japannet-directory-service":{"source":"iana"},"application/vnd.japannet-jpnstore-wakeup":{"source":"iana"},"application/vnd.japannet-payment-wakeup":{"source":"iana"},"application/vnd.japannet-registration":{"source":"iana"},"application/vnd.japannet-registration-wakeup":{"source":"iana"},"application/vnd.japannet-setstore-wakeup":{"source":"iana"},"application/vnd.japannet-verification":{"source":"iana"},"application/vnd.japannet-verification-wakeup":{"source":"iana"},"application/vnd.jcp.javame.midlet-rms":{"source":"iana","extensions":["rms"]},"application/vnd.jisp":{"source":"iana","extensions":["jisp"]},"application/vnd.joost.joda-archive":{"source":"iana","extensions":["joda"]},"application/vnd.jsk.isdn-ngn":{"source":"iana"},"application/vnd.kahootz":{"source":"iana","extensions":["ktz","ktr"]},"application/vnd.kde.karbon":{"source":"iana","extensions":["karbon"]},"application/vnd.kde.kchart":{"source":"iana","extensions":["chrt"]},"application/vnd.kde.kformula":{"source":"iana","extensions":["kfo"]},"application/vnd.kde.kivio":{"source":"iana","extensions":["flw"]},"application/vnd.kde.kontour":{"source":"iana","extensions":["kon"]},"application/vnd.kde.kpresenter":{"source":"iana","extensions":["kpr","kpt"]},"application/vnd.kde.kspread":{"source":"iana","extensions":["ksp"]},"application/vnd.kde.kword":{"source":"iana","extensions":["kwd","kwt"]},"application/vnd.kenameaapp":{"source":"iana","extensions":["htke"]},"application/vnd.kidspiration":{"source":"iana","extensions":["kia"]},"application/vnd.kinar":{"source":"iana","extensions":["kne","knp"]},"application/vnd.koan":{"source":"iana","extensions":["skp","skd","skt","skm"]},"application/vnd.kodak-descriptor":{"source":"iana","extensions":["sse"]},"application/vnd.las":{"source":"iana"},"application/vnd.las.las+json":{"source":"iana","compressible":true},"application/vnd.las.las+xml":{"source":"iana","compressible":true,"extensions":["lasxml"]},"application/vnd.laszip":{"source":"iana"},"application/vnd.leap+json":{"source":"iana","compressible":true},"application/vnd.liberty-request+xml":{"source":"iana","compressible":true},"application/vnd.llamagraphics.life-balance.desktop":{"source":"iana","extensions":["lbd"]},"application/vnd.llamagraphics.life-balance.exchange+xml":{"source":"iana","compressible":true,"extensions":["lbe"]},"application/vnd.logipipe.circuit+zip":{"source":"iana","compressible":false},"application/vnd.loom":{"source":"iana"},"application/vnd.lotus-1-2-3":{"source":"iana","extensions":["123"]},"application/vnd.lotus-approach":{"source":"iana","extensions":["apr"]},"application/vnd.lotus-freelance":{"source":"iana","extensions":["pre"]},"application/vnd.lotus-notes":{"source":"iana","extensions":["nsf"]},"application/vnd.lotus-organizer":{"source":"iana","extensions":["org"]},"application/vnd.lotus-screencam":{"source":"iana","extensions":["scm"]},"application/vnd.lotus-wordpro":{"source":"iana","extensions":["lwp"]},"application/vnd.macports.portpkg":{"source":"iana","extensions":["portpkg"]},"application/vnd.mapbox-vector-tile":{"source":"iana"},"application/vnd.marlin.drm.actiontoken+xml":{"source":"iana","compressible":true},"application/vnd.marlin.drm.conftoken+xml":{"source":"iana","compressible":true},"application/vnd.marlin.drm.license+xml":{"source":"iana","compressible":true},"application/vnd.marlin.drm.mdcf":{"source":"iana"},"application/vnd.mason+json":{"source":"iana","compressible":true},"application/vnd.maxmind.maxmind-db":{"source":"iana"},"application/vnd.mcd":{"source":"iana","extensions":["mcd"]},"application/vnd.medcalcdata":{"source":"iana","extensions":["mc1"]},"application/vnd.mediastation.cdkey":{"source":"iana","extensions":["cdkey"]},"application/vnd.meridian-slingshot":{"source":"iana"},"application/vnd.mfer":{"source":"iana","extensions":["mwf"]},"application/vnd.mfmp":{"source":"iana","extensions":["mfm"]},"application/vnd.micro+json":{"source":"iana","compressible":true},"application/vnd.micrografx.flo":{"source":"iana","extensions":["flo"]},"application/vnd.micrografx.igx":{"source":"iana","extensions":["igx"]},"application/vnd.microsoft.portable-executable":{"source":"iana"},"application/vnd.microsoft.windows.thumbnail-cache":{"source":"iana"},"application/vnd.miele+json":{"source":"iana","compressible":true},"application/vnd.mif":{"source":"iana","extensions":["mif"]},"application/vnd.minisoft-hp3000-save":{"source":"iana"},"application/vnd.mitsubishi.misty-guard.trustweb":{"source":"iana"},"application/vnd.mobius.daf":{"source":"iana","extensions":["daf"]},"application/vnd.mobius.dis":{"source":"iana","extensions":["dis"]},"application/vnd.mobius.mbk":{"source":"iana","extensions":["mbk"]},"application/vnd.mobius.mqy":{"source":"iana","extensions":["mqy"]},"application/vnd.mobius.msl":{"source":"iana","extensions":["msl"]},"application/vnd.mobius.plc":{"source":"iana","extensions":["plc"]},"application/vnd.mobius.txf":{"source":"iana","extensions":["txf"]},"application/vnd.mophun.application":{"source":"iana","extensions":["mpn"]},"application/vnd.mophun.certificate":{"source":"iana","extensions":["mpc"]},"application/vnd.motorola.flexsuite":{"source":"iana"},"application/vnd.motorola.flexsuite.adsi":{"source":"iana"},"application/vnd.motorola.flexsuite.fis":{"source":"iana"},"application/vnd.motorola.flexsuite.gotap":{"source":"iana"},"application/vnd.motorola.flexsuite.kmr":{"source":"iana"},"application/vnd.motorola.flexsuite.ttc":{"source":"iana"},"application/vnd.motorola.flexsuite.wem":{"source":"iana"},"application/vnd.motorola.iprm":{"source":"iana"},"application/vnd.mozilla.xul+xml":{"source":"iana","compressible":true,"extensions":["xul"]},"application/vnd.ms-3mfdocument":{"source":"iana"},"application/vnd.ms-artgalry":{"source":"iana","extensions":["cil"]},"application/vnd.ms-asf":{"source":"iana"},"application/vnd.ms-cab-compressed":{"source":"iana","extensions":["cab"]},"application/vnd.ms-color.iccprofile":{"source":"apache"},"application/vnd.ms-excel":{"source":"iana","compressible":false,"extensions":["xls","xlm","xla","xlc","xlt","xlw"]},"application/vnd.ms-excel.addin.macroenabled.12":{"source":"iana","extensions":["xlam"]},"application/vnd.ms-excel.sheet.binary.macroenabled.12":{"source":"iana","extensions":["xlsb"]},"application/vnd.ms-excel.sheet.macroenabled.12":{"source":"iana","extensions":["xlsm"]},"application/vnd.ms-excel.template.macroenabled.12":{"source":"iana","extensions":["xltm"]},"application/vnd.ms-fontobject":{"source":"iana","compressible":true,"extensions":["eot"]},"application/vnd.ms-htmlhelp":{"source":"iana","extensions":["chm"]},"application/vnd.ms-ims":{"source":"iana","extensions":["ims"]},"application/vnd.ms-lrm":{"source":"iana","extensions":["lrm"]},"application/vnd.ms-office.activex+xml":{"source":"iana","compressible":true},"application/vnd.ms-officetheme":{"source":"iana","extensions":["thmx"]},"application/vnd.ms-opentype":{"source":"apache","compressible":true},"application/vnd.ms-outlook":{"compressible":false,"extensions":["msg"]},"application/vnd.ms-package.obfuscated-opentype":{"source":"apache"},"application/vnd.ms-pki.seccat":{"source":"apache","extensions":["cat"]},"application/vnd.ms-pki.stl":{"source":"apache","extensions":["stl"]},"application/vnd.ms-playready.initiator+xml":{"source":"iana","compressible":true},"application/vnd.ms-powerpoint":{"source":"iana","compressible":false,"extensions":["ppt","pps","pot"]},"application/vnd.ms-powerpoint.addin.macroenabled.12":{"source":"iana","extensions":["ppam"]},"application/vnd.ms-powerpoint.presentation.macroenabled.12":{"source":"iana","extensions":["pptm"]},"application/vnd.ms-powerpoint.slide.macroenabled.12":{"source":"iana","extensions":["sldm"]},"application/vnd.ms-powerpoint.slideshow.macroenabled.12":{"source":"iana","extensions":["ppsm"]},"application/vnd.ms-powerpoint.template.macroenabled.12":{"source":"iana","extensions":["potm"]},"application/vnd.ms-printdevicecapabilities+xml":{"source":"iana","compressible":true},"application/vnd.ms-printing.printticket+xml":{"source":"apache","compressible":true},"application/vnd.ms-printschematicket+xml":{"source":"iana","compressible":true},"application/vnd.ms-project":{"source":"iana","extensions":["mpp","mpt"]},"application/vnd.ms-tnef":{"source":"iana"},"application/vnd.ms-windows.devicepairing":{"source":"iana"},"application/vnd.ms-windows.nwprinting.oob":{"source":"iana"},"application/vnd.ms-windows.printerpairing":{"source":"iana"},"application/vnd.ms-windows.wsd.oob":{"source":"iana"},"application/vnd.ms-wmdrm.lic-chlg-req":{"source":"iana"},"application/vnd.ms-wmdrm.lic-resp":{"source":"iana"},"application/vnd.ms-wmdrm.meter-chlg-req":{"source":"iana"},"application/vnd.ms-wmdrm.meter-resp":{"source":"iana"},"application/vnd.ms-word.document.macroenabled.12":{"source":"iana","extensions":["docm"]},"application/vnd.ms-word.template.macroenabled.12":{"source":"iana","extensions":["dotm"]},"application/vnd.ms-works":{"source":"iana","extensions":["wps","wks","wcm","wdb"]},"application/vnd.ms-wpl":{"source":"iana","extensions":["wpl"]},"application/vnd.ms-xpsdocument":{"source":"iana","compressible":false,"extensions":["xps"]},"application/vnd.msa-disk-image":{"source":"iana"},"application/vnd.mseq":{"source":"iana","extensions":["mseq"]},"application/vnd.msign":{"source":"iana"},"application/vnd.multiad.creator":{"source":"iana"},"application/vnd.multiad.creator.cif":{"source":"iana"},"application/vnd.music-niff":{"source":"iana"},"application/vnd.musician":{"source":"iana","extensions":["mus"]},"application/vnd.muvee.style":{"source":"iana","extensions":["msty"]},"application/vnd.mynfc":{"source":"iana","extensions":["taglet"]},"application/vnd.ncd.control":{"source":"iana"},"application/vnd.ncd.reference":{"source":"iana"},"application/vnd.nearst.inv+json":{"source":"iana","compressible":true},"application/vnd.nervana":{"source":"iana"},"application/vnd.netfpx":{"source":"iana"},"application/vnd.neurolanguage.nlu":{"source":"iana","extensions":["nlu"]},"application/vnd.nimn":{"source":"iana"},"application/vnd.nintendo.nitro.rom":{"source":"iana"},"application/vnd.nintendo.snes.rom":{"source":"iana"},"application/vnd.nitf":{"source":"iana","extensions":["ntf","nitf"]},"application/vnd.noblenet-directory":{"source":"iana","extensions":["nnd"]},"application/vnd.noblenet-sealer":{"source":"iana","extensions":["nns"]},"application/vnd.noblenet-web":{"source":"iana","extensions":["nnw"]},"application/vnd.nokia.catalogs":{"source":"iana"},"application/vnd.nokia.conml+wbxml":{"source":"iana"},"application/vnd.nokia.conml+xml":{"source":"iana","compressible":true},"application/vnd.nokia.iptv.config+xml":{"source":"iana","compressible":true},"application/vnd.nokia.isds-radio-presets":{"source":"iana"},"application/vnd.nokia.landmark+wbxml":{"source":"iana"},"application/vnd.nokia.landmark+xml":{"source":"iana","compressible":true},"application/vnd.nokia.landmarkcollection+xml":{"source":"iana","compressible":true},"application/vnd.nokia.n-gage.ac+xml":{"source":"iana","compressible":true,"extensions":["ac"]},"application/vnd.nokia.n-gage.data":{"source":"iana","extensions":["ngdat"]},"application/vnd.nokia.n-gage.symbian.install":{"source":"iana","extensions":["n-gage"]},"application/vnd.nokia.ncd":{"source":"iana"},"application/vnd.nokia.pcd+wbxml":{"source":"iana"},"application/vnd.nokia.pcd+xml":{"source":"iana","compressible":true},"application/vnd.nokia.radio-preset":{"source":"iana","extensions":["rpst"]},"application/vnd.nokia.radio-presets":{"source":"iana","extensions":["rpss"]},"application/vnd.novadigm.edm":{"source":"iana","extensions":["edm"]},"application/vnd.novadigm.edx":{"source":"iana","extensions":["edx"]},"application/vnd.novadigm.ext":{"source":"iana","extensions":["ext"]},"application/vnd.ntt-local.content-share":{"source":"iana"},"application/vnd.ntt-local.file-transfer":{"source":"iana"},"application/vnd.ntt-local.ogw_remote-access":{"source":"iana"},"application/vnd.ntt-local.sip-ta_remote":{"source":"iana"},"application/vnd.ntt-local.sip-ta_tcp_stream":{"source":"iana"},"application/vnd.oasis.opendocument.chart":{"source":"iana","extensions":["odc"]},"application/vnd.oasis.opendocument.chart-template":{"source":"iana","extensions":["otc"]},"application/vnd.oasis.opendocument.database":{"source":"iana","extensions":["odb"]},"application/vnd.oasis.opendocument.formula":{"source":"iana","extensions":["odf"]},"application/vnd.oasis.opendocument.formula-template":{"source":"iana","extensions":["odft"]},"application/vnd.oasis.opendocument.graphics":{"source":"iana","compressible":false,"extensions":["odg"]},"application/vnd.oasis.opendocument.graphics-template":{"source":"iana","extensions":["otg"]},"application/vnd.oasis.opendocument.image":{"source":"iana","extensions":["odi"]},"application/vnd.oasis.opendocument.image-template":{"source":"iana","extensions":["oti"]},"application/vnd.oasis.opendocument.presentation":{"source":"iana","compressible":false,"extensions":["odp"]},"application/vnd.oasis.opendocument.presentation-template":{"source":"iana","extensions":["otp"]},"application/vnd.oasis.opendocument.spreadsheet":{"source":"iana","compressible":false,"extensions":["ods"]},"application/vnd.oasis.opendocument.spreadsheet-template":{"source":"iana","extensions":["ots"]},"application/vnd.oasis.opendocument.text":{"source":"iana","compressible":false,"extensions":["odt"]},"application/vnd.oasis.opendocument.text-master":{"source":"iana","extensions":["odm"]},"application/vnd.oasis.opendocument.text-template":{"source":"iana","extensions":["ott"]},"application/vnd.oasis.opendocument.text-web":{"source":"iana","extensions":["oth"]},"application/vnd.obn":{"source":"iana"},"application/vnd.ocf+cbor":{"source":"iana"},"application/vnd.oci.image.manifest.v1+json":{"source":"iana","compressible":true},"application/vnd.oftn.l10n+json":{"source":"iana","compressible":true},"application/vnd.oipf.contentaccessdownload+xml":{"source":"iana","compressible":true},"application/vnd.oipf.contentaccessstreaming+xml":{"source":"iana","compressible":true},"application/vnd.oipf.cspg-hexbinary":{"source":"iana"},"application/vnd.oipf.dae.svg+xml":{"source":"iana","compressible":true},"application/vnd.oipf.dae.xhtml+xml":{"source":"iana","compressible":true},"application/vnd.oipf.mippvcontrolmessage+xml":{"source":"iana","compressible":true},"application/vnd.oipf.pae.gem":{"source":"iana"},"application/vnd.oipf.spdiscovery+xml":{"source":"iana","compressible":true},"application/vnd.oipf.spdlist+xml":{"source":"iana","compressible":true},"application/vnd.oipf.ueprofile+xml":{"source":"iana","compressible":true},"application/vnd.oipf.userprofile+xml":{"source":"iana","compressible":true},"application/vnd.olpc-sugar":{"source":"iana","extensions":["xo"]},"application/vnd.oma-scws-config":{"source":"iana"},"application/vnd.oma-scws-http-request":{"source":"iana"},"application/vnd.oma-scws-http-response":{"source":"iana"},"application/vnd.oma.bcast.associated-procedure-parameter+xml":{"source":"iana","compressible":true},"application/vnd.oma.bcast.drm-trigger+xml":{"source":"iana","compressible":true},"application/vnd.oma.bcast.imd+xml":{"source":"iana","compressible":true},"application/vnd.oma.bcast.ltkm":{"source":"iana"},"application/vnd.oma.bcast.notification+xml":{"source":"iana","compressible":true},"application/vnd.oma.bcast.provisioningtrigger":{"source":"iana"},"application/vnd.oma.bcast.sgboot":{"source":"iana"},"application/vnd.oma.bcast.sgdd+xml":{"source":"iana","compressible":true},"application/vnd.oma.bcast.sgdu":{"source":"iana"},"application/vnd.oma.bcast.simple-symbol-container":{"source":"iana"},"application/vnd.oma.bcast.smartcard-trigger+xml":{"source":"iana","compressible":true},"application/vnd.oma.bcast.sprov+xml":{"source":"iana","compressible":true},"application/vnd.oma.bcast.stkm":{"source":"iana"},"application/vnd.oma.cab-address-book+xml":{"source":"iana","compressible":true},"application/vnd.oma.cab-feature-handler+xml":{"source":"iana","compressible":true},"application/vnd.oma.cab-pcc+xml":{"source":"iana","compressible":true},"application/vnd.oma.cab-subs-invite+xml":{"source":"iana","compressible":true},"application/vnd.oma.cab-user-prefs+xml":{"source":"iana","compressible":true},"application/vnd.oma.dcd":{"source":"iana"},"application/vnd.oma.dcdc":{"source":"iana"},"application/vnd.oma.dd2+xml":{"source":"iana","compressible":true,"extensions":["dd2"]},"application/vnd.oma.drm.risd+xml":{"source":"iana","compressible":true},"application/vnd.oma.group-usage-list+xml":{"source":"iana","compressible":true},"application/vnd.oma.lwm2m+json":{"source":"iana","compressible":true},"application/vnd.oma.lwm2m+tlv":{"source":"iana"},"application/vnd.oma.pal+xml":{"source":"iana","compressible":true},"application/vnd.oma.poc.detailed-progress-report+xml":{"source":"iana","compressible":true},"application/vnd.oma.poc.final-report+xml":{"source":"iana","compressible":true},"application/vnd.oma.poc.groups+xml":{"source":"iana","compressible":true},"application/vnd.oma.poc.invocation-descriptor+xml":{"source":"iana","compressible":true},"application/vnd.oma.poc.optimized-progress-report+xml":{"source":"iana","compressible":true},"application/vnd.oma.push":{"source":"iana"},"application/vnd.oma.scidm.messages+xml":{"source":"iana","compressible":true},"application/vnd.oma.xcap-directory+xml":{"source":"iana","compressible":true},"application/vnd.omads-email+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/vnd.omads-file+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/vnd.omads-folder+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/vnd.omaloc-supl-init":{"source":"iana"},"application/vnd.onepager":{"source":"iana"},"application/vnd.onepagertamp":{"source":"iana"},"application/vnd.onepagertamx":{"source":"iana"},"application/vnd.onepagertat":{"source":"iana"},"application/vnd.onepagertatp":{"source":"iana"},"application/vnd.onepagertatx":{"source":"iana"},"application/vnd.openblox.game+xml":{"source":"iana","compressible":true,"extensions":["obgx"]},"application/vnd.openblox.game-binary":{"source":"iana"},"application/vnd.openeye.oeb":{"source":"iana"},"application/vnd.openofficeorg.extension":{"source":"apache","extensions":["oxt"]},"application/vnd.openstreetmap.data+xml":{"source":"iana","compressible":true,"extensions":["osm"]},"application/vnd.openxmlformats-officedocument.custom-properties+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.customxmlproperties+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.drawing+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.drawingml.chart+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.extended-properties+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.comments+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.presentation":{"source":"iana","compressible":false,"extensions":["pptx"]},"application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.presprops+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.slide":{"source":"iana","extensions":["sldx"]},"application/vnd.openxmlformats-officedocument.presentationml.slide+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.slideshow":{"source":"iana","extensions":["ppsx"]},"application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.tags+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.template":{"source":"iana","extensions":["potx"]},"application/vnd.openxmlformats-officedocument.presentationml.template.main+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":{"source":"iana","compressible":false,"extensions":["xlsx"]},"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.template":{"source":"iana","extensions":["xltx"]},"application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.theme+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.themeoverride+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.vmldrawing":{"source":"iana"},"application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.document":{"source":"iana","compressible":false,"extensions":["docx"]},"application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.template":{"source":"iana","extensions":["dotx"]},"application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-package.core-properties+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml":{"source":"iana","compressible":true},"application/vnd.openxmlformats-package.relationships+xml":{"source":"iana","compressible":true},"application/vnd.oracle.resource+json":{"source":"iana","compressible":true},"application/vnd.orange.indata":{"source":"iana"},"application/vnd.osa.netdeploy":{"source":"iana"},"application/vnd.osgeo.mapguide.package":{"source":"iana","extensions":["mgp"]},"application/vnd.osgi.bundle":{"source":"iana"},"application/vnd.osgi.dp":{"source":"iana","extensions":["dp"]},"application/vnd.osgi.subsystem":{"source":"iana","extensions":["esa"]},"application/vnd.otps.ct-kip+xml":{"source":"iana","compressible":true},"application/vnd.oxli.countgraph":{"source":"iana"},"application/vnd.pagerduty+json":{"source":"iana","compressible":true},"application/vnd.palm":{"source":"iana","extensions":["pdb","pqa","oprc"]},"application/vnd.panoply":{"source":"iana"},"application/vnd.paos.xml":{"source":"iana"},"application/vnd.patentdive":{"source":"iana"},"application/vnd.patientecommsdoc":{"source":"iana"},"application/vnd.pawaafile":{"source":"iana","extensions":["paw"]},"application/vnd.pcos":{"source":"iana"},"application/vnd.pg.format":{"source":"iana","extensions":["str"]},"application/vnd.pg.osasli":{"source":"iana","extensions":["ei6"]},"application/vnd.piaccess.application-licence":{"source":"iana"},"application/vnd.picsel":{"source":"iana","extensions":["efif"]},"application/vnd.pmi.widget":{"source":"iana","extensions":["wg"]},"application/vnd.poc.group-advertisement+xml":{"source":"iana","compressible":true},"application/vnd.pocketlearn":{"source":"iana","extensions":["plf"]},"application/vnd.powerbuilder6":{"source":"iana","extensions":["pbd"]},"application/vnd.powerbuilder6-s":{"source":"iana"},"application/vnd.powerbuilder7":{"source":"iana"},"application/vnd.powerbuilder7-s":{"source":"iana"},"application/vnd.powerbuilder75":{"source":"iana"},"application/vnd.powerbuilder75-s":{"source":"iana"},"application/vnd.preminet":{"source":"iana"},"application/vnd.previewsystems.box":{"source":"iana","extensions":["box"]},"application/vnd.proteus.magazine":{"source":"iana","extensions":["mgz"]},"application/vnd.psfs":{"source":"iana"},"application/vnd.publishare-delta-tree":{"source":"iana","extensions":["qps"]},"application/vnd.pvi.ptid1":{"source":"iana","extensions":["ptid"]},"application/vnd.pwg-multiplexed":{"source":"iana"},"application/vnd.pwg-xhtml-print+xml":{"source":"iana","compressible":true},"application/vnd.qualcomm.brew-app-res":{"source":"iana"},"application/vnd.quarantainenet":{"source":"iana"},"application/vnd.quark.quarkxpress":{"source":"iana","extensions":["qxd","qxt","qwd","qwt","qxl","qxb"]},"application/vnd.quobject-quoxdocument":{"source":"iana"},"application/vnd.radisys.moml+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-audit+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-audit-conf+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-audit-conn+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-audit-dialog+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-audit-stream+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-conf+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-dialog+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-dialog-base+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-dialog-fax-detect+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-dialog-fax-sendrecv+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-dialog-group+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-dialog-speech+xml":{"source":"iana","compressible":true},"application/vnd.radisys.msml-dialog-transform+xml":{"source":"iana","compressible":true},"application/vnd.rainstor.data":{"source":"iana"},"application/vnd.rapid":{"source":"iana"},"application/vnd.rar":{"source":"iana"},"application/vnd.realvnc.bed":{"source":"iana","extensions":["bed"]},"application/vnd.recordare.musicxml":{"source":"iana","extensions":["mxl"]},"application/vnd.recordare.musicxml+xml":{"source":"iana","compressible":true,"extensions":["musicxml"]},"application/vnd.renlearn.rlprint":{"source":"iana"},"application/vnd.restful+json":{"source":"iana","compressible":true},"application/vnd.rig.cryptonote":{"source":"iana","extensions":["cryptonote"]},"application/vnd.rim.cod":{"source":"apache","extensions":["cod"]},"application/vnd.rn-realmedia":{"source":"apache","extensions":["rm"]},"application/vnd.rn-realmedia-vbr":{"source":"apache","extensions":["rmvb"]},"application/vnd.route66.link66+xml":{"source":"iana","compressible":true,"extensions":["link66"]},"application/vnd.rs-274x":{"source":"iana"},"application/vnd.ruckus.download":{"source":"iana"},"application/vnd.s3sms":{"source":"iana"},"application/vnd.sailingtracker.track":{"source":"iana","extensions":["st"]},"application/vnd.sar":{"source":"iana"},"application/vnd.sbm.cid":{"source":"iana"},"application/vnd.sbm.mid2":{"source":"iana"},"application/vnd.scribus":{"source":"iana"},"application/vnd.sealed.3df":{"source":"iana"},"application/vnd.sealed.csf":{"source":"iana"},"application/vnd.sealed.doc":{"source":"iana"},"application/vnd.sealed.eml":{"source":"iana"},"application/vnd.sealed.mht":{"source":"iana"},"application/vnd.sealed.net":{"source":"iana"},"application/vnd.sealed.ppt":{"source":"iana"},"application/vnd.sealed.tiff":{"source":"iana"},"application/vnd.sealed.xls":{"source":"iana"},"application/vnd.sealedmedia.softseal.html":{"source":"iana"},"application/vnd.sealedmedia.softseal.pdf":{"source":"iana"},"application/vnd.seemail":{"source":"iana","extensions":["see"]},"application/vnd.sema":{"source":"iana","extensions":["sema"]},"application/vnd.semd":{"source":"iana","extensions":["semd"]},"application/vnd.semf":{"source":"iana","extensions":["semf"]},"application/vnd.shade-save-file":{"source":"iana"},"application/vnd.shana.informed.formdata":{"source":"iana","extensions":["ifm"]},"application/vnd.shana.informed.formtemplate":{"source":"iana","extensions":["itp"]},"application/vnd.shana.informed.interchange":{"source":"iana","extensions":["iif"]},"application/vnd.shana.informed.package":{"source":"iana","extensions":["ipk"]},"application/vnd.shootproof+json":{"source":"iana","compressible":true},"application/vnd.shopkick+json":{"source":"iana","compressible":true},"application/vnd.shp":{"source":"iana"},"application/vnd.shx":{"source":"iana"},"application/vnd.sigrok.session":{"source":"iana"},"application/vnd.simtech-mindmapper":{"source":"iana","extensions":["twd","twds"]},"application/vnd.siren+json":{"source":"iana","compressible":true},"application/vnd.smaf":{"source":"iana","extensions":["mmf"]},"application/vnd.smart.notebook":{"source":"iana"},"application/vnd.smart.teacher":{"source":"iana","extensions":["teacher"]},"application/vnd.snesdev-page-table":{"source":"iana"},"application/vnd.software602.filler.form+xml":{"source":"iana","compressible":true,"extensions":["fo"]},"application/vnd.software602.filler.form-xml-zip":{"source":"iana"},"application/vnd.solent.sdkm+xml":{"source":"iana","compressible":true,"extensions":["sdkm","sdkd"]},"application/vnd.spotfire.dxp":{"source":"iana","extensions":["dxp"]},"application/vnd.spotfire.sfs":{"source":"iana","extensions":["sfs"]},"application/vnd.sqlite3":{"source":"iana"},"application/vnd.sss-cod":{"source":"iana"},"application/vnd.sss-dtf":{"source":"iana"},"application/vnd.sss-ntf":{"source":"iana"},"application/vnd.stardivision.calc":{"source":"apache","extensions":["sdc"]},"application/vnd.stardivision.draw":{"source":"apache","extensions":["sda"]},"application/vnd.stardivision.impress":{"source":"apache","extensions":["sdd"]},"application/vnd.stardivision.math":{"source":"apache","extensions":["smf"]},"application/vnd.stardivision.writer":{"source":"apache","extensions":["sdw","vor"]},"application/vnd.stardivision.writer-global":{"source":"apache","extensions":["sgl"]},"application/vnd.stepmania.package":{"source":"iana","extensions":["smzip"]},"application/vnd.stepmania.stepchart":{"source":"iana","extensions":["sm"]},"application/vnd.street-stream":{"source":"iana"},"application/vnd.sun.wadl+xml":{"source":"iana","compressible":true,"extensions":["wadl"]},"application/vnd.sun.xml.calc":{"source":"apache","extensions":["sxc"]},"application/vnd.sun.xml.calc.template":{"source":"apache","extensions":["stc"]},"application/vnd.sun.xml.draw":{"source":"apache","extensions":["sxd"]},"application/vnd.sun.xml.draw.template":{"source":"apache","extensions":["std"]},"application/vnd.sun.xml.impress":{"source":"apache","extensions":["sxi"]},"application/vnd.sun.xml.impress.template":{"source":"apache","extensions":["sti"]},"application/vnd.sun.xml.math":{"source":"apache","extensions":["sxm"]},"application/vnd.sun.xml.writer":{"source":"apache","extensions":["sxw"]},"application/vnd.sun.xml.writer.global":{"source":"apache","extensions":["sxg"]},"application/vnd.sun.xml.writer.template":{"source":"apache","extensions":["stw"]},"application/vnd.sus-calendar":{"source":"iana","extensions":["sus","susp"]},"application/vnd.svd":{"source":"iana","extensions":["svd"]},"application/vnd.swiftview-ics":{"source":"iana"},"application/vnd.symbian.install":{"source":"apache","extensions":["sis","sisx"]},"application/vnd.syncml+xml":{"source":"iana","charset":"UTF-8","compressible":true,"extensions":["xsm"]},"application/vnd.syncml.dm+wbxml":{"source":"iana","charset":"UTF-8","extensions":["bdm"]},"application/vnd.syncml.dm+xml":{"source":"iana","charset":"UTF-8","compressible":true,"extensions":["xdm"]},"application/vnd.syncml.dm.notification":{"source":"iana"},"application/vnd.syncml.dmddf+wbxml":{"source":"iana"},"application/vnd.syncml.dmddf+xml":{"source":"iana","charset":"UTF-8","compressible":true,"extensions":["ddf"]},"application/vnd.syncml.dmtnds+wbxml":{"source":"iana"},"application/vnd.syncml.dmtnds+xml":{"source":"iana","charset":"UTF-8","compressible":true},"application/vnd.syncml.ds.notification":{"source":"iana"},"application/vnd.tableschema+json":{"source":"iana","compressible":true},"application/vnd.tao.intent-module-archive":{"source":"iana","extensions":["tao"]},"application/vnd.tcpdump.pcap":{"source":"iana","extensions":["pcap","cap","dmp"]},"application/vnd.think-cell.ppttc+json":{"source":"iana","compressible":true},"application/vnd.tmd.mediaflex.api+xml":{"source":"iana","compressible":true},"application/vnd.tml":{"source":"iana"},"application/vnd.tmobile-livetv":{"source":"iana","extensions":["tmo"]},"application/vnd.tri.onesource":{"source":"iana"},"application/vnd.trid.tpt":{"source":"iana","extensions":["tpt"]},"application/vnd.triscape.mxs":{"source":"iana","extensions":["mxs"]},"application/vnd.trueapp":{"source":"iana","extensions":["tra"]},"application/vnd.truedoc":{"source":"iana"},"application/vnd.ubisoft.webplayer":{"source":"iana"},"application/vnd.ufdl":{"source":"iana","extensions":["ufd","ufdl"]},"application/vnd.uiq.theme":{"source":"iana","extensions":["utz"]},"application/vnd.umajin":{"source":"iana","extensions":["umj"]},"application/vnd.unity":{"source":"iana","extensions":["unityweb"]},"application/vnd.uoml+xml":{"source":"iana","compressible":true,"extensions":["uoml"]},"application/vnd.uplanet.alert":{"source":"iana"},"application/vnd.uplanet.alert-wbxml":{"source":"iana"},"application/vnd.uplanet.bearer-choice":{"source":"iana"},"application/vnd.uplanet.bearer-choice-wbxml":{"source":"iana"},"application/vnd.uplanet.cacheop":{"source":"iana"},"application/vnd.uplanet.cacheop-wbxml":{"source":"iana"},"application/vnd.uplanet.channel":{"source":"iana"},"application/vnd.uplanet.channel-wbxml":{"source":"iana"},"application/vnd.uplanet.list":{"source":"iana"},"application/vnd.uplanet.list-wbxml":{"source":"iana"},"application/vnd.uplanet.listcmd":{"source":"iana"},"application/vnd.uplanet.listcmd-wbxml":{"source":"iana"},"application/vnd.uplanet.signal":{"source":"iana"},"application/vnd.uri-map":{"source":"iana"},"application/vnd.valve.source.material":{"source":"iana"},"application/vnd.vcx":{"source":"iana","extensions":["vcx"]},"application/vnd.vd-study":{"source":"iana"},"application/vnd.vectorworks":{"source":"iana"},"application/vnd.vel+json":{"source":"iana","compressible":true},"application/vnd.verimatrix.vcas":{"source":"iana"},"application/vnd.veryant.thin":{"source":"iana"},"application/vnd.ves.encrypted":{"source":"iana"},"application/vnd.vidsoft.vidconference":{"source":"iana"},"application/vnd.visio":{"source":"iana","extensions":["vsd","vst","vss","vsw"]},"application/vnd.visionary":{"source":"iana","extensions":["vis"]},"application/vnd.vividence.scriptfile":{"source":"iana"},"application/vnd.vsf":{"source":"iana","extensions":["vsf"]},"application/vnd.wap.sic":{"source":"iana"},"application/vnd.wap.slc":{"source":"iana"},"application/vnd.wap.wbxml":{"source":"iana","charset":"UTF-8","extensions":["wbxml"]},"application/vnd.wap.wmlc":{"source":"iana","extensions":["wmlc"]},"application/vnd.wap.wmlscriptc":{"source":"iana","extensions":["wmlsc"]},"application/vnd.webturbo":{"source":"iana","extensions":["wtb"]},"application/vnd.wfa.p2p":{"source":"iana"},"application/vnd.wfa.wsc":{"source":"iana"},"application/vnd.windows.devicepairing":{"source":"iana"},"application/vnd.wmc":{"source":"iana"},"application/vnd.wmf.bootstrap":{"source":"iana"},"application/vnd.wolfram.mathematica":{"source":"iana"},"application/vnd.wolfram.mathematica.package":{"source":"iana"},"application/vnd.wolfram.player":{"source":"iana","extensions":["nbp"]},"application/vnd.wordperfect":{"source":"iana","extensions":["wpd"]},"application/vnd.wqd":{"source":"iana","extensions":["wqd"]},"application/vnd.wrq-hp3000-labelled":{"source":"iana"},"application/vnd.wt.stf":{"source":"iana","extensions":["stf"]},"application/vnd.wv.csp+wbxml":{"source":"iana"},"application/vnd.wv.csp+xml":{"source":"iana","compressible":true},"application/vnd.wv.ssp+xml":{"source":"iana","compressible":true},"application/vnd.xacml+json":{"source":"iana","compressible":true},"application/vnd.xara":{"source":"iana","extensions":["xar"]},"application/vnd.xfdl":{"source":"iana","extensions":["xfdl"]},"application/vnd.xfdl.webform":{"source":"iana"},"application/vnd.xmi+xml":{"source":"iana","compressible":true},"application/vnd.xmpie.cpkg":{"source":"iana"},"application/vnd.xmpie.dpkg":{"source":"iana"},"application/vnd.xmpie.plan":{"source":"iana"},"application/vnd.xmpie.ppkg":{"source":"iana"},"application/vnd.xmpie.xlim":{"source":"iana"},"application/vnd.yamaha.hv-dic":{"source":"iana","extensions":["hvd"]},"application/vnd.yamaha.hv-script":{"source":"iana","extensions":["hvs"]},"application/vnd.yamaha.hv-voice":{"source":"iana","extensions":["hvp"]},"application/vnd.yamaha.openscoreformat":{"source":"iana","extensions":["osf"]},"application/vnd.yamaha.openscoreformat.osfpvg+xml":{"source":"iana","compressible":true,"extensions":["osfpvg"]},"application/vnd.yamaha.remote-setup":{"source":"iana"},"application/vnd.yamaha.smaf-audio":{"source":"iana","extensions":["saf"]},"application/vnd.yamaha.smaf-phrase":{"source":"iana","extensions":["spf"]},"application/vnd.yamaha.through-ngn":{"source":"iana"},"application/vnd.yamaha.tunnel-udpencap":{"source":"iana"},"application/vnd.yaoweme":{"source":"iana"},"application/vnd.yellowriver-custom-menu":{"source":"iana","extensions":["cmp"]},"application/vnd.youtube.yt":{"source":"iana"},"application/vnd.zul":{"source":"iana","extensions":["zir","zirz"]},"application/vnd.zzazz.deck+xml":{"source":"iana","compressible":true,"extensions":["zaz"]},"application/voicexml+xml":{"source":"iana","compressible":true,"extensions":["vxml"]},"application/voucher-cms+json":{"source":"iana","compressible":true},"application/vq-rtcpxr":{"source":"iana"},"application/wasm":{"compressible":true,"extensions":["wasm"]},"application/watcherinfo+xml":{"source":"iana","compressible":true},"application/webpush-options+json":{"source":"iana","compressible":true},"application/whoispp-query":{"source":"iana"},"application/whoispp-response":{"source":"iana"},"application/widget":{"source":"iana","extensions":["wgt"]},"application/winhlp":{"source":"apache","extensions":["hlp"]},"application/wita":{"source":"iana"},"application/wordperfect5.1":{"source":"iana"},"application/wsdl+xml":{"source":"iana","compressible":true,"extensions":["wsdl"]},"application/wspolicy+xml":{"source":"iana","compressible":true,"extensions":["wspolicy"]},"application/x-7z-compressed":{"source":"apache","compressible":false,"extensions":["7z"]},"application/x-abiword":{"source":"apache","extensions":["abw"]},"application/x-ace-compressed":{"source":"apache","extensions":["ace"]},"application/x-amf":{"source":"apache"},"application/x-apple-diskimage":{"source":"apache","extensions":["dmg"]},"application/x-arj":{"compressible":false,"extensions":["arj"]},"application/x-authorware-bin":{"source":"apache","extensions":["aab","x32","u32","vox"]},"application/x-authorware-map":{"source":"apache","extensions":["aam"]},"application/x-authorware-seg":{"source":"apache","extensions":["aas"]},"application/x-bcpio":{"source":"apache","extensions":["bcpio"]},"application/x-bdoc":{"compressible":false,"extensions":["bdoc"]},"application/x-bittorrent":{"source":"apache","extensions":["torrent"]},"application/x-blorb":{"source":"apache","extensions":["blb","blorb"]},"application/x-bzip":{"source":"apache","compressible":false,"extensions":["bz"]},"application/x-bzip2":{"source":"apache","compressible":false,"extensions":["bz2","boz"]},"application/x-cbr":{"source":"apache","extensions":["cbr","cba","cbt","cbz","cb7"]},"application/x-cdlink":{"source":"apache","extensions":["vcd"]},"application/x-cfs-compressed":{"source":"apache","extensions":["cfs"]},"application/x-chat":{"source":"apache","extensions":["chat"]},"application/x-chess-pgn":{"source":"apache","extensions":["pgn"]},"application/x-chrome-extension":{"extensions":["crx"]},"application/x-cocoa":{"source":"nginx","extensions":["cco"]},"application/x-compress":{"source":"apache"},"application/x-conference":{"source":"apache","extensions":["nsc"]},"application/x-cpio":{"source":"apache","extensions":["cpio"]},"application/x-csh":{"source":"apache","extensions":["csh"]},"application/x-deb":{"compressible":false},"application/x-debian-package":{"source":"apache","extensions":["deb","udeb"]},"application/x-dgc-compressed":{"source":"apache","extensions":["dgc"]},"application/x-director":{"source":"apache","extensions":["dir","dcr","dxr","cst","cct","cxt","w3d","fgd","swa"]},"application/x-doom":{"source":"apache","extensions":["wad"]},"application/x-dtbncx+xml":{"source":"apache","compressible":true,"extensions":["ncx"]},"application/x-dtbook+xml":{"source":"apache","compressible":true,"extensions":["dtb"]},"application/x-dtbresource+xml":{"source":"apache","compressible":true,"extensions":["res"]},"application/x-dvi":{"source":"apache","compressible":false,"extensions":["dvi"]},"application/x-envoy":{"source":"apache","extensions":["evy"]},"application/x-eva":{"source":"apache","extensions":["eva"]},"application/x-font-bdf":{"source":"apache","extensions":["bdf"]},"application/x-font-dos":{"source":"apache"},"application/x-font-framemaker":{"source":"apache"},"application/x-font-ghostscript":{"source":"apache","extensions":["gsf"]},"application/x-font-libgrx":{"source":"apache"},"application/x-font-linux-psf":{"source":"apache","extensions":["psf"]},"application/x-font-pcf":{"source":"apache","extensions":["pcf"]},"application/x-font-snf":{"source":"apache","extensions":["snf"]},"application/x-font-speedo":{"source":"apache"},"application/x-font-sunos-news":{"source":"apache"},"application/x-font-type1":{"source":"apache","extensions":["pfa","pfb","pfm","afm"]},"application/x-font-vfont":{"source":"apache"},"application/x-freearc":{"source":"apache","extensions":["arc"]},"application/x-futuresplash":{"source":"apache","extensions":["spl"]},"application/x-gca-compressed":{"source":"apache","extensions":["gca"]},"application/x-glulx":{"source":"apache","extensions":["ulx"]},"application/x-gnumeric":{"source":"apache","extensions":["gnumeric"]},"application/x-gramps-xml":{"source":"apache","extensions":["gramps"]},"application/x-gtar":{"source":"apache","extensions":["gtar"]},"application/x-gzip":{"source":"apache"},"application/x-hdf":{"source":"apache","extensions":["hdf"]},"application/x-httpd-php":{"compressible":true,"extensions":["php"]},"application/x-install-instructions":{"source":"apache","extensions":["install"]},"application/x-iso9660-image":{"source":"apache","extensions":["iso"]},"application/x-java-archive-diff":{"source":"nginx","extensions":["jardiff"]},"application/x-java-jnlp-file":{"source":"apache","compressible":false,"extensions":["jnlp"]},"application/x-javascript":{"compressible":true},"application/x-keepass2":{"extensions":["kdbx"]},"application/x-latex":{"source":"apache","compressible":false,"extensions":["latex"]},"application/x-lua-bytecode":{"extensions":["luac"]},"application/x-lzh-compressed":{"source":"apache","extensions":["lzh","lha"]},"application/x-makeself":{"source":"nginx","extensions":["run"]},"application/x-mie":{"source":"apache","extensions":["mie"]},"application/x-mobipocket-ebook":{"source":"apache","extensions":["prc","mobi"]},"application/x-mpegurl":{"compressible":false},"application/x-ms-application":{"source":"apache","extensions":["application"]},"application/x-ms-shortcut":{"source":"apache","extensions":["lnk"]},"application/x-ms-wmd":{"source":"apache","extensions":["wmd"]},"application/x-ms-wmz":{"source":"apache","extensions":["wmz"]},"application/x-ms-xbap":{"source":"apache","extensions":["xbap"]},"application/x-msaccess":{"source":"apache","extensions":["mdb"]},"application/x-msbinder":{"source":"apache","extensions":["obd"]},"application/x-mscardfile":{"source":"apache","extensions":["crd"]},"application/x-msclip":{"source":"apache","extensions":["clp"]},"application/x-msdos-program":{"extensions":["exe"]},"application/x-msdownload":{"source":"apache","extensions":["exe","dll","com","bat","msi"]},"application/x-msmediaview":{"source":"apache","extensions":["mvb","m13","m14"]},"application/x-msmetafile":{"source":"apache","extensions":["wmf","wmz","emf","emz"]},"application/x-msmoney":{"source":"apache","extensions":["mny"]},"application/x-mspublisher":{"source":"apache","extensions":["pub"]},"application/x-msschedule":{"source":"apache","extensions":["scd"]},"application/x-msterminal":{"source":"apache","extensions":["trm"]},"application/x-mswrite":{"source":"apache","extensions":["wri"]},"application/x-netcdf":{"source":"apache","extensions":["nc","cdf"]},"application/x-ns-proxy-autoconfig":{"compressible":true,"extensions":["pac"]},"application/x-nzb":{"source":"apache","extensions":["nzb"]},"application/x-perl":{"source":"nginx","extensions":["pl","pm"]},"application/x-pilot":{"source":"nginx","extensions":["prc","pdb"]},"application/x-pkcs12":{"source":"apache","compressible":false,"extensions":["p12","pfx"]},"application/x-pkcs7-certificates":{"source":"apache","extensions":["p7b","spc"]},"application/x-pkcs7-certreqresp":{"source":"apache","extensions":["p7r"]},"application/x-pki-message":{"source":"iana"},"application/x-rar-compressed":{"source":"apache","compressible":false,"extensions":["rar"]},"application/x-redhat-package-manager":{"source":"nginx","extensions":["rpm"]},"application/x-research-info-systems":{"source":"apache","extensions":["ris"]},"application/x-sea":{"source":"nginx","extensions":["sea"]},"application/x-sh":{"source":"apache","compressible":true,"extensions":["sh"]},"application/x-shar":{"source":"apache","extensions":["shar"]},"application/x-shockwave-flash":{"source":"apache","compressible":false,"extensions":["swf"]},"application/x-silverlight-app":{"source":"apache","extensions":["xap"]},"application/x-sql":{"source":"apache","extensions":["sql"]},"application/x-stuffit":{"source":"apache","compressible":false,"extensions":["sit"]},"application/x-stuffitx":{"source":"apache","extensions":["sitx"]},"application/x-subrip":{"source":"apache","extensions":["srt"]},"application/x-sv4cpio":{"source":"apache","extensions":["sv4cpio"]},"application/x-sv4crc":{"source":"apache","extensions":["sv4crc"]},"application/x-t3vm-image":{"source":"apache","extensions":["t3"]},"application/x-tads":{"source":"apache","extensions":["gam"]},"application/x-tar":{"source":"apache","compressible":true,"extensions":["tar"]},"application/x-tcl":{"source":"apache","extensions":["tcl","tk"]},"application/x-tex":{"source":"apache","extensions":["tex"]},"application/x-tex-tfm":{"source":"apache","extensions":["tfm"]},"application/x-texinfo":{"source":"apache","extensions":["texinfo","texi"]},"application/x-tgif":{"source":"apache","extensions":["obj"]},"application/x-ustar":{"source":"apache","extensions":["ustar"]},"application/x-virtualbox-hdd":{"compressible":true,"extensions":["hdd"]},"application/x-virtualbox-ova":{"compressible":true,"extensions":["ova"]},"application/x-virtualbox-ovf":{"compressible":true,"extensions":["ovf"]},"application/x-virtualbox-vbox":{"compressible":true,"extensions":["vbox"]},"application/x-virtualbox-vbox-extpack":{"compressible":false,"extensions":["vbox-extpack"]},"application/x-virtualbox-vdi":{"compressible":true,"extensions":["vdi"]},"application/x-virtualbox-vhd":{"compressible":true,"extensions":["vhd"]},"application/x-virtualbox-vmdk":{"compressible":true,"extensions":["vmdk"]},"application/x-wais-source":{"source":"apache","extensions":["src"]},"application/x-web-app-manifest+json":{"compressible":true,"extensions":["webapp"]},"application/x-www-form-urlencoded":{"source":"iana","compressible":true},"application/x-x509-ca-cert":{"source":"iana","extensions":["der","crt","pem"]},"application/x-x509-ca-ra-cert":{"source":"iana"},"application/x-x509-next-ca-cert":{"source":"iana"},"application/x-xfig":{"source":"apache","extensions":["fig"]},"application/x-xliff+xml":{"source":"apache","compressible":true,"extensions":["xlf"]},"application/x-xpinstall":{"source":"apache","compressible":false,"extensions":["xpi"]},"application/x-xz":{"source":"apache","extensions":["xz"]},"application/x-zmachine":{"source":"apache","extensions":["z1","z2","z3","z4","z5","z6","z7","z8"]},"application/x400-bp":{"source":"iana"},"application/xacml+xml":{"source":"iana","compressible":true},"application/xaml+xml":{"source":"apache","compressible":true,"extensions":["xaml"]},"application/xcap-att+xml":{"source":"iana","compressible":true,"extensions":["xav"]},"application/xcap-caps+xml":{"source":"iana","compressible":true,"extensions":["xca"]},"application/xcap-diff+xml":{"source":"iana","compressible":true,"extensions":["xdf"]},"application/xcap-el+xml":{"source":"iana","compressible":true,"extensions":["xel"]},"application/xcap-error+xml":{"source":"iana","compressible":true,"extensions":["xer"]},"application/xcap-ns+xml":{"source":"iana","compressible":true,"extensions":["xns"]},"application/xcon-conference-info+xml":{"source":"iana","compressible":true},"application/xcon-conference-info-diff+xml":{"source":"iana","compressible":true},"application/xenc+xml":{"source":"iana","compressible":true,"extensions":["xenc"]},"application/xhtml+xml":{"source":"iana","compressible":true,"extensions":["xhtml","xht"]},"application/xhtml-voice+xml":{"source":"apache","compressible":true},"application/xliff+xml":{"source":"iana","compressible":true,"extensions":["xlf"]},"application/xml":{"source":"iana","compressible":true,"extensions":["xml","xsl","xsd","rng"]},"application/xml-dtd":{"source":"iana","compressible":true,"extensions":["dtd"]},"application/xml-external-parsed-entity":{"source":"iana"},"application/xml-patch+xml":{"source":"iana","compressible":true},"application/xmpp+xml":{"source":"iana","compressible":true},"application/xop+xml":{"source":"iana","compressible":true,"extensions":["xop"]},"application/xproc+xml":{"source":"apache","compressible":true,"extensions":["xpl"]},"application/xslt+xml":{"source":"iana","compressible":true,"extensions":["xslt"]},"application/xspf+xml":{"source":"apache","compressible":true,"extensions":["xspf"]},"application/xv+xml":{"source":"iana","compressible":true,"extensions":["mxml","xhvml","xvml","xvm"]},"application/yang":{"source":"iana","extensions":["yang"]},"application/yang-data+json":{"source":"iana","compressible":true},"application/yang-data+xml":{"source":"iana","compressible":true},"application/yang-patch+json":{"source":"iana","compressible":true},"application/yang-patch+xml":{"source":"iana","compressible":true},"application/yin+xml":{"source":"iana","compressible":true,"extensions":["yin"]},"application/zip":{"source":"iana","compressible":false,"extensions":["zip"]},"application/zlib":{"source":"iana"},"application/zstd":{"source":"iana"},"audio/1d-interleaved-parityfec":{"source":"iana"},"audio/32kadpcm":{"source":"iana"},"audio/3gpp":{"source":"iana","compressible":false,"extensions":["3gpp"]},"audio/3gpp2":{"source":"iana"},"audio/aac":{"source":"iana"},"audio/ac3":{"source":"iana"},"audio/adpcm":{"source":"apache","extensions":["adp"]},"audio/amr":{"source":"iana"},"audio/amr-wb":{"source":"iana"},"audio/amr-wb+":{"source":"iana"},"audio/aptx":{"source":"iana"},"audio/asc":{"source":"iana"},"audio/atrac-advanced-lossless":{"source":"iana"},"audio/atrac-x":{"source":"iana"},"audio/atrac3":{"source":"iana"},"audio/basic":{"source":"iana","compressible":false,"extensions":["au","snd"]},"audio/bv16":{"source":"iana"},"audio/bv32":{"source":"iana"},"audio/clearmode":{"source":"iana"},"audio/cn":{"source":"iana"},"audio/dat12":{"source":"iana"},"audio/dls":{"source":"iana"},"audio/dsr-es201108":{"source":"iana"},"audio/dsr-es202050":{"source":"iana"},"audio/dsr-es202211":{"source":"iana"},"audio/dsr-es202212":{"source":"iana"},"audio/dv":{"source":"iana"},"audio/dvi4":{"source":"iana"},"audio/eac3":{"source":"iana"},"audio/encaprtp":{"source":"iana"},"audio/evrc":{"source":"iana"},"audio/evrc-qcp":{"source":"iana"},"audio/evrc0":{"source":"iana"},"audio/evrc1":{"source":"iana"},"audio/evrcb":{"source":"iana"},"audio/evrcb0":{"source":"iana"},"audio/evrcb1":{"source":"iana"},"audio/evrcnw":{"source":"iana"},"audio/evrcnw0":{"source":"iana"},"audio/evrcnw1":{"source":"iana"},"audio/evrcwb":{"source":"iana"},"audio/evrcwb0":{"source":"iana"},"audio/evrcwb1":{"source":"iana"},"audio/evs":{"source":"iana"},"audio/flexfec":{"source":"iana"},"audio/fwdred":{"source":"iana"},"audio/g711-0":{"source":"iana"},"audio/g719":{"source":"iana"},"audio/g722":{"source":"iana"},"audio/g7221":{"source":"iana"},"audio/g723":{"source":"iana"},"audio/g726-16":{"source":"iana"},"audio/g726-24":{"source":"iana"},"audio/g726-32":{"source":"iana"},"audio/g726-40":{"source":"iana"},"audio/g728":{"source":"iana"},"audio/g729":{"source":"iana"},"audio/g7291":{"source":"iana"},"audio/g729d":{"source":"iana"},"audio/g729e":{"source":"iana"},"audio/gsm":{"source":"iana"},"audio/gsm-efr":{"source":"iana"},"audio/gsm-hr-08":{"source":"iana"},"audio/ilbc":{"source":"iana"},"audio/ip-mr_v2.5":{"source":"iana"},"audio/isac":{"source":"apache"},"audio/l16":{"source":"iana"},"audio/l20":{"source":"iana"},"audio/l24":{"source":"iana","compressible":false},"audio/l8":{"source":"iana"},"audio/lpc":{"source":"iana"},"audio/melp":{"source":"iana"},"audio/melp1200":{"source":"iana"},"audio/melp2400":{"source":"iana"},"audio/melp600":{"source":"iana"},"audio/mhas":{"source":"iana"},"audio/midi":{"source":"apache","extensions":["mid","midi","kar","rmi"]},"audio/mobile-xmf":{"source":"iana","extensions":["mxmf"]},"audio/mp3":{"compressible":false,"extensions":["mp3"]},"audio/mp4":{"source":"iana","compressible":false,"extensions":["m4a","mp4a"]},"audio/mp4a-latm":{"source":"iana"},"audio/mpa":{"source":"iana"},"audio/mpa-robust":{"source":"iana"},"audio/mpeg":{"source":"iana","compressible":false,"extensions":["mpga","mp2","mp2a","mp3","m2a","m3a"]},"audio/mpeg4-generic":{"source":"iana"},"audio/musepack":{"source":"apache"},"audio/ogg":{"source":"iana","compressible":false,"extensions":["oga","ogg","spx"]},"audio/opus":{"source":"iana"},"audio/parityfec":{"source":"iana"},"audio/pcma":{"source":"iana"},"audio/pcma-wb":{"source":"iana"},"audio/pcmu":{"source":"iana"},"audio/pcmu-wb":{"source":"iana"},"audio/prs.sid":{"source":"iana"},"audio/qcelp":{"source":"iana"},"audio/raptorfec":{"source":"iana"},"audio/red":{"source":"iana"},"audio/rtp-enc-aescm128":{"source":"iana"},"audio/rtp-midi":{"source":"iana"},"audio/rtploopback":{"source":"iana"},"audio/rtx":{"source":"iana"},"audio/s3m":{"source":"apache","extensions":["s3m"]},"audio/silk":{"source":"apache","extensions":["sil"]},"audio/smv":{"source":"iana"},"audio/smv-qcp":{"source":"iana"},"audio/smv0":{"source":"iana"},"audio/sp-midi":{"source":"iana"},"audio/speex":{"source":"iana"},"audio/t140c":{"source":"iana"},"audio/t38":{"source":"iana"},"audio/telephone-event":{"source":"iana"},"audio/tetra_acelp":{"source":"iana"},"audio/tetra_acelp_bb":{"source":"iana"},"audio/tone":{"source":"iana"},"audio/uemclip":{"source":"iana"},"audio/ulpfec":{"source":"iana"},"audio/usac":{"source":"iana"},"audio/vdvi":{"source":"iana"},"audio/vmr-wb":{"source":"iana"},"audio/vnd.3gpp.iufp":{"source":"iana"},"audio/vnd.4sb":{"source":"iana"},"audio/vnd.audiokoz":{"source":"iana"},"audio/vnd.celp":{"source":"iana"},"audio/vnd.cisco.nse":{"source":"iana"},"audio/vnd.cmles.radio-events":{"source":"iana"},"audio/vnd.cns.anp1":{"source":"iana"},"audio/vnd.cns.inf1":{"source":"iana"},"audio/vnd.dece.audio":{"source":"iana","extensions":["uva","uvva"]},"audio/vnd.digital-winds":{"source":"iana","extensions":["eol"]},"audio/vnd.dlna.adts":{"source":"iana"},"audio/vnd.dolby.heaac.1":{"source":"iana"},"audio/vnd.dolby.heaac.2":{"source":"iana"},"audio/vnd.dolby.mlp":{"source":"iana"},"audio/vnd.dolby.mps":{"source":"iana"},"audio/vnd.dolby.pl2":{"source":"iana"},"audio/vnd.dolby.pl2x":{"source":"iana"},"audio/vnd.dolby.pl2z":{"source":"iana"},"audio/vnd.dolby.pulse.1":{"source":"iana"},"audio/vnd.dra":{"source":"iana","extensions":["dra"]},"audio/vnd.dts":{"source":"iana","extensions":["dts"]},"audio/vnd.dts.hd":{"source":"iana","extensions":["dtshd"]},"audio/vnd.dts.uhd":{"source":"iana"},"audio/vnd.dvb.file":{"source":"iana"},"audio/vnd.everad.plj":{"source":"iana"},"audio/vnd.hns.audio":{"source":"iana"},"audio/vnd.lucent.voice":{"source":"iana","extensions":["lvp"]},"audio/vnd.ms-playready.media.pya":{"source":"iana","extensions":["pya"]},"audio/vnd.nokia.mobile-xmf":{"source":"iana"},"audio/vnd.nortel.vbk":{"source":"iana"},"audio/vnd.nuera.ecelp4800":{"source":"iana","extensions":["ecelp4800"]},"audio/vnd.nuera.ecelp7470":{"source":"iana","extensions":["ecelp7470"]},"audio/vnd.nuera.ecelp9600":{"source":"iana","extensions":["ecelp9600"]},"audio/vnd.octel.sbc":{"source":"iana"},"audio/vnd.presonus.multitrack":{"source":"iana"},"audio/vnd.qcelp":{"source":"iana"},"audio/vnd.rhetorex.32kadpcm":{"source":"iana"},"audio/vnd.rip":{"source":"iana","extensions":["rip"]},"audio/vnd.rn-realaudio":{"compressible":false},"audio/vnd.sealedmedia.softseal.mpeg":{"source":"iana"},"audio/vnd.vmx.cvsd":{"source":"iana"},"audio/vnd.wave":{"compressible":false},"audio/vorbis":{"source":"iana","compressible":false},"audio/vorbis-config":{"source":"iana"},"audio/wav":{"compressible":false,"extensions":["wav"]},"audio/wave":{"compressible":false,"extensions":["wav"]},"audio/webm":{"source":"apache","compressible":false,"extensions":["weba"]},"audio/x-aac":{"source":"apache","compressible":false,"extensions":["aac"]},"audio/x-aiff":{"source":"apache","extensions":["aif","aiff","aifc"]},"audio/x-caf":{"source":"apache","compressible":false,"extensions":["caf"]},"audio/x-flac":{"source":"apache","extensions":["flac"]},"audio/x-m4a":{"source":"nginx","extensions":["m4a"]},"audio/x-matroska":{"source":"apache","extensions":["mka"]},"audio/x-mpegurl":{"source":"apache","extensions":["m3u"]},"audio/x-ms-wax":{"source":"apache","extensions":["wax"]},"audio/x-ms-wma":{"source":"apache","extensions":["wma"]},"audio/x-pn-realaudio":{"source":"apache","extensions":["ram","ra"]},"audio/x-pn-realaudio-plugin":{"source":"apache","extensions":["rmp"]},"audio/x-realaudio":{"source":"nginx","extensions":["ra"]},"audio/x-tta":{"source":"apache"},"audio/x-wav":{"source":"apache","extensions":["wav"]},"audio/xm":{"source":"apache","extensions":["xm"]},"chemical/x-cdx":{"source":"apache","extensions":["cdx"]},"chemical/x-cif":{"source":"apache","extensions":["cif"]},"chemical/x-cmdf":{"source":"apache","extensions":["cmdf"]},"chemical/x-cml":{"source":"apache","extensions":["cml"]},"chemical/x-csml":{"source":"apache","extensions":["csml"]},"chemical/x-pdb":{"source":"apache"},"chemical/x-xyz":{"source":"apache","extensions":["xyz"]},"font/collection":{"source":"iana","extensions":["ttc"]},"font/otf":{"source":"iana","compressible":true,"extensions":["otf"]},"font/sfnt":{"source":"iana"},"font/ttf":{"source":"iana","compressible":true,"extensions":["ttf"]},"font/woff":{"source":"iana","extensions":["woff"]},"font/woff2":{"source":"iana","extensions":["woff2"]},"image/aces":{"source":"iana","extensions":["exr"]},"image/apng":{"compressible":false,"extensions":["apng"]},"image/avci":{"source":"iana"},"image/avcs":{"source":"iana"},"image/bmp":{"source":"iana","compressible":true,"extensions":["bmp"]},"image/cgm":{"source":"iana","extensions":["cgm"]},"image/dicom-rle":{"source":"iana","extensions":["drle"]},"image/emf":{"source":"iana","extensions":["emf"]},"image/fits":{"source":"iana","extensions":["fits"]},"image/g3fax":{"source":"iana","extensions":["g3"]},"image/gif":{"source":"iana","compressible":false,"extensions":["gif"]},"image/heic":{"source":"iana","extensions":["heic"]},"image/heic-sequence":{"source":"iana","extensions":["heics"]},"image/heif":{"source":"iana","extensions":["heif"]},"image/heif-sequence":{"source":"iana","extensions":["heifs"]},"image/hej2k":{"source":"iana","extensions":["hej2"]},"image/hsj2":{"source":"iana","extensions":["hsj2"]},"image/ief":{"source":"iana","extensions":["ief"]},"image/jls":{"source":"iana","extensions":["jls"]},"image/jp2":{"source":"iana","compressible":false,"extensions":["jp2","jpg2"]},"image/jpeg":{"source":"iana","compressible":false,"extensions":["jpeg","jpg","jpe"]},"image/jph":{"source":"iana","extensions":["jph"]},"image/jphc":{"source":"iana","extensions":["jhc"]},"image/jpm":{"source":"iana","compressible":false,"extensions":["jpm"]},"image/jpx":{"source":"iana","compressible":false,"extensions":["jpx","jpf"]},"image/jxr":{"source":"iana","extensions":["jxr"]},"image/jxra":{"source":"iana","extensions":["jxra"]},"image/jxrs":{"source":"iana","extensions":["jxrs"]},"image/jxs":{"source":"iana","extensions":["jxs"]},"image/jxsc":{"source":"iana","extensions":["jxsc"]},"image/jxsi":{"source":"iana","extensions":["jxsi"]},"image/jxss":{"source":"iana","extensions":["jxss"]},"image/ktx":{"source":"iana","extensions":["ktx"]},"image/naplps":{"source":"iana"},"image/pjpeg":{"compressible":false},"image/png":{"source":"iana","compressible":false,"extensions":["png"]},"image/prs.btif":{"source":"iana","extensions":["btif"]},"image/prs.pti":{"source":"iana","extensions":["pti"]},"image/pwg-raster":{"source":"iana"},"image/sgi":{"source":"apache","extensions":["sgi"]},"image/svg+xml":{"source":"iana","compressible":true,"extensions":["svg","svgz"]},"image/t38":{"source":"iana","extensions":["t38"]},"image/tiff":{"source":"iana","compressible":false,"extensions":["tif","tiff"]},"image/tiff-fx":{"source":"iana","extensions":["tfx"]},"image/vnd.adobe.photoshop":{"source":"iana","compressible":true,"extensions":["psd"]},"image/vnd.airzip.accelerator.azv":{"source":"iana","extensions":["azv"]},"image/vnd.cns.inf2":{"source":"iana"},"image/vnd.dece.graphic":{"source":"iana","extensions":["uvi","uvvi","uvg","uvvg"]},"image/vnd.djvu":{"source":"iana","extensions":["djvu","djv"]},"image/vnd.dvb.subtitle":{"source":"iana","extensions":["sub"]},"image/vnd.dwg":{"source":"iana","extensions":["dwg"]},"image/vnd.dxf":{"source":"iana","extensions":["dxf"]},"image/vnd.fastbidsheet":{"source":"iana","extensions":["fbs"]},"image/vnd.fpx":{"source":"iana","extensions":["fpx"]},"image/vnd.fst":{"source":"iana","extensions":["fst"]},"image/vnd.fujixerox.edmics-mmr":{"source":"iana","extensions":["mmr"]},"image/vnd.fujixerox.edmics-rlc":{"source":"iana","extensions":["rlc"]},"image/vnd.globalgraphics.pgb":{"source":"iana"},"image/vnd.microsoft.icon":{"source":"iana","extensions":["ico"]},"image/vnd.mix":{"source":"iana"},"image/vnd.mozilla.apng":{"source":"iana"},"image/vnd.ms-dds":{"extensions":["dds"]},"image/vnd.ms-modi":{"source":"iana","extensions":["mdi"]},"image/vnd.ms-photo":{"source":"apache","extensions":["wdp"]},"image/vnd.net-fpx":{"source":"iana","extensions":["npx"]},"image/vnd.radiance":{"source":"iana"},"image/vnd.sealed.png":{"source":"iana"},"image/vnd.sealedmedia.softseal.gif":{"source":"iana"},"image/vnd.sealedmedia.softseal.jpg":{"source":"iana"},"image/vnd.svf":{"source":"iana"},"image/vnd.tencent.tap":{"source":"iana","extensions":["tap"]},"image/vnd.valve.source.texture":{"source":"iana","extensions":["vtf"]},"image/vnd.wap.wbmp":{"source":"iana","extensions":["wbmp"]},"image/vnd.xiff":{"source":"iana","extensions":["xif"]},"image/vnd.zbrush.pcx":{"source":"iana","extensions":["pcx"]},"image/webp":{"source":"apache","extensions":["webp"]},"image/wmf":{"source":"iana","extensions":["wmf"]},"image/x-3ds":{"source":"apache","extensions":["3ds"]},"image/x-cmu-raster":{"source":"apache","extensions":["ras"]},"image/x-cmx":{"source":"apache","extensions":["cmx"]},"image/x-freehand":{"source":"apache","extensions":["fh","fhc","fh4","fh5","fh7"]},"image/x-icon":{"source":"apache","compressible":true,"extensions":["ico"]},"image/x-jng":{"source":"nginx","extensions":["jng"]},"image/x-mrsid-image":{"source":"apache","extensions":["sid"]},"image/x-ms-bmp":{"source":"nginx","compressible":true,"extensions":["bmp"]},"image/x-pcx":{"source":"apache","extensions":["pcx"]},"image/x-pict":{"source":"apache","extensions":["pic","pct"]},"image/x-portable-anymap":{"source":"apache","extensions":["pnm"]},"image/x-portable-bitmap":{"source":"apache","extensions":["pbm"]},"image/x-portable-graymap":{"source":"apache","extensions":["pgm"]},"image/x-portable-pixmap":{"source":"apache","extensions":["ppm"]},"image/x-rgb":{"source":"apache","extensions":["rgb"]},"image/x-tga":{"source":"apache","extensions":["tga"]},"image/x-xbitmap":{"source":"apache","extensions":["xbm"]},"image/x-xcf":{"compressible":false},"image/x-xpixmap":{"source":"apache","extensions":["xpm"]},"image/x-xwindowdump":{"source":"apache","extensions":["xwd"]},"message/cpim":{"source":"iana"},"message/delivery-status":{"source":"iana"},"message/disposition-notification":{"source":"iana","extensions":["disposition-notification"]},"message/external-body":{"source":"iana"},"message/feedback-report":{"source":"iana"},"message/global":{"source":"iana","extensions":["u8msg"]},"message/global-delivery-status":{"source":"iana","extensions":["u8dsn"]},"message/global-disposition-notification":{"source":"iana","extensions":["u8mdn"]},"message/global-headers":{"source":"iana","extensions":["u8hdr"]},"message/http":{"source":"iana","compressible":false},"message/imdn+xml":{"source":"iana","compressible":true},"message/news":{"source":"iana"},"message/partial":{"source":"iana","compressible":false},"message/rfc822":{"source":"iana","compressible":true,"extensions":["eml","mime"]},"message/s-http":{"source":"iana"},"message/sip":{"source":"iana"},"message/sipfrag":{"source":"iana"},"message/tracking-status":{"source":"iana"},"message/vnd.si.simp":{"source":"iana"},"message/vnd.wfa.wsc":{"source":"iana","extensions":["wsc"]},"model/3mf":{"source":"iana","extensions":["3mf"]},"model/gltf+json":{"source":"iana","compressible":true,"extensions":["gltf"]},"model/gltf-binary":{"source":"iana","compressible":true,"extensions":["glb"]},"model/iges":{"source":"iana","compressible":false,"extensions":["igs","iges"]},"model/mesh":{"source":"iana","compressible":false,"extensions":["msh","mesh","silo"]},"model/mtl":{"source":"iana","extensions":["mtl"]},"model/obj":{"source":"iana","extensions":["obj"]},"model/stl":{"source":"iana","extensions":["stl"]},"model/vnd.collada+xml":{"source":"iana","compressible":true,"extensions":["dae"]},"model/vnd.dwf":{"source":"iana","extensions":["dwf"]},"model/vnd.flatland.3dml":{"source":"iana"},"model/vnd.gdl":{"source":"iana","extensions":["gdl"]},"model/vnd.gs-gdl":{"source":"apache"},"model/vnd.gs.gdl":{"source":"iana"},"model/vnd.gtw":{"source":"iana","extensions":["gtw"]},"model/vnd.moml+xml":{"source":"iana","compressible":true},"model/vnd.mts":{"source":"iana","extensions":["mts"]},"model/vnd.opengex":{"source":"iana","extensions":["ogex"]},"model/vnd.parasolid.transmit.binary":{"source":"iana","extensions":["x_b"]},"model/vnd.parasolid.transmit.text":{"source":"iana","extensions":["x_t"]},"model/vnd.rosette.annotated-data-model":{"source":"iana"},"model/vnd.usdz+zip":{"source":"iana","compressible":false,"extensions":["usdz"]},"model/vnd.valve.source.compiled-map":{"source":"iana","extensions":["bsp"]},"model/vnd.vtu":{"source":"iana","extensions":["vtu"]},"model/vrml":{"source":"iana","compressible":false,"extensions":["wrl","vrml"]},"model/x3d+binary":{"source":"apache","compressible":false,"extensions":["x3db","x3dbz"]},"model/x3d+fastinfoset":{"source":"iana","extensions":["x3db"]},"model/x3d+vrml":{"source":"apache","compressible":false,"extensions":["x3dv","x3dvz"]},"model/x3d+xml":{"source":"iana","compressible":true,"extensions":["x3d","x3dz"]},"model/x3d-vrml":{"source":"iana","extensions":["x3dv"]},"multipart/alternative":{"source":"iana","compressible":false},"multipart/appledouble":{"source":"iana"},"multipart/byteranges":{"source":"iana"},"multipart/digest":{"source":"iana"},"multipart/encrypted":{"source":"iana","compressible":false},"multipart/form-data":{"source":"iana","compressible":false},"multipart/header-set":{"source":"iana"},"multipart/mixed":{"source":"iana"},"multipart/multilingual":{"source":"iana"},"multipart/parallel":{"source":"iana"},"multipart/related":{"source":"iana","compressible":false},"multipart/report":{"source":"iana"},"multipart/signed":{"source":"iana","compressible":false},"multipart/vnd.bint.med-plus":{"source":"iana"},"multipart/voice-message":{"source":"iana"},"multipart/x-mixed-replace":{"source":"iana"},"text/1d-interleaved-parityfec":{"source":"iana"},"text/cache-manifest":{"source":"iana","compressible":true,"extensions":["appcache","manifest"]},"text/calendar":{"source":"iana","extensions":["ics","ifb"]},"text/calender":{"compressible":true},"text/cmd":{"compressible":true},"text/coffeescript":{"extensions":["coffee","litcoffee"]},"text/css":{"source":"iana","charset":"UTF-8","compressible":true,"extensions":["css"]},"text/csv":{"source":"iana","compressible":true,"extensions":["csv"]},"text/csv-schema":{"source":"iana"},"text/directory":{"source":"iana"},"text/dns":{"source":"iana"},"text/ecmascript":{"source":"iana"},"text/encaprtp":{"source":"iana"},"text/enriched":{"source":"iana"},"text/flexfec":{"source":"iana"},"text/fwdred":{"source":"iana"},"text/grammar-ref-list":{"source":"iana"},"text/html":{"source":"iana","compressible":true,"extensions":["html","htm","shtml"]},"text/jade":{"extensions":["jade"]},"text/javascript":{"source":"iana","compressible":true},"text/jcr-cnd":{"source":"iana"},"text/jsx":{"compressible":true,"extensions":["jsx"]},"text/less":{"compressible":true,"extensions":["less"]},"text/markdown":{"source":"iana","compressible":true,"extensions":["markdown","md"]},"text/mathml":{"source":"nginx","extensions":["mml"]},"text/mdx":{"compressible":true,"extensions":["mdx"]},"text/mizar":{"source":"iana"},"text/n3":{"source":"iana","charset":"UTF-8","compressible":true,"extensions":["n3"]},"text/parameters":{"source":"iana","charset":"UTF-8"},"text/parityfec":{"source":"iana"},"text/plain":{"source":"iana","compressible":true,"extensions":["txt","text","conf","def","list","log","in","ini"]},"text/provenance-notation":{"source":"iana","charset":"UTF-8"},"text/prs.fallenstein.rst":{"source":"iana"},"text/prs.lines.tag":{"source":"iana","extensions":["dsc"]},"text/prs.prop.logic":{"source":"iana"},"text/raptorfec":{"source":"iana"},"text/red":{"source":"iana"},"text/rfc822-headers":{"source":"iana"},"text/richtext":{"source":"iana","compressible":true,"extensions":["rtx"]},"text/rtf":{"source":"iana","compressible":true,"extensions":["rtf"]},"text/rtp-enc-aescm128":{"source":"iana"},"text/rtploopback":{"source":"iana"},"text/rtx":{"source":"iana"},"text/sgml":{"source":"iana","extensions":["sgml","sgm"]},"text/shex":{"extensions":["shex"]},"text/slim":{"extensions":["slim","slm"]},"text/strings":{"source":"iana"},"text/stylus":{"extensions":["stylus","styl"]},"text/t140":{"source":"iana"},"text/tab-separated-values":{"source":"iana","compressible":true,"extensions":["tsv"]},"text/troff":{"source":"iana","extensions":["t","tr","roff","man","me","ms"]},"text/turtle":{"source":"iana","charset":"UTF-8","extensions":["ttl"]},"text/ulpfec":{"source":"iana"},"text/uri-list":{"source":"iana","compressible":true,"extensions":["uri","uris","urls"]},"text/vcard":{"source":"iana","compressible":true,"extensions":["vcard"]},"text/vnd.a":{"source":"iana"},"text/vnd.abc":{"source":"iana"},"text/vnd.ascii-art":{"source":"iana"},"text/vnd.curl":{"source":"iana","extensions":["curl"]},"text/vnd.curl.dcurl":{"source":"apache","extensions":["dcurl"]},"text/vnd.curl.mcurl":{"source":"apache","extensions":["mcurl"]},"text/vnd.curl.scurl":{"source":"apache","extensions":["scurl"]},"text/vnd.debian.copyright":{"source":"iana","charset":"UTF-8"},"text/vnd.dmclientscript":{"source":"iana"},"text/vnd.dvb.subtitle":{"source":"iana","extensions":["sub"]},"text/vnd.esmertec.theme-descriptor":{"source":"iana","charset":"UTF-8"},"text/vnd.ficlab.flt":{"source":"iana"},"text/vnd.fly":{"source":"iana","extensions":["fly"]},"text/vnd.fmi.flexstor":{"source":"iana","extensions":["flx"]},"text/vnd.gml":{"source":"iana"},"text/vnd.graphviz":{"source":"iana","extensions":["gv"]},"text/vnd.hgl":{"source":"iana"},"text/vnd.in3d.3dml":{"source":"iana","extensions":["3dml"]},"text/vnd.in3d.spot":{"source":"iana","extensions":["spot"]},"text/vnd.iptc.newsml":{"source":"iana"},"text/vnd.iptc.nitf":{"source":"iana"},"text/vnd.latex-z":{"source":"iana"},"text/vnd.motorola.reflex":{"source":"iana"},"text/vnd.ms-mediapackage":{"source":"iana"},"text/vnd.net2phone.commcenter.command":{"source":"iana"},"text/vnd.radisys.msml-basic-layout":{"source":"iana"},"text/vnd.senx.warpscript":{"source":"iana"},"text/vnd.si.uricatalogue":{"source":"iana"},"text/vnd.sosi":{"source":"iana"},"text/vnd.sun.j2me.app-descriptor":{"source":"iana","charset":"UTF-8","extensions":["jad"]},"text/vnd.trolltech.linguist":{"source":"iana","charset":"UTF-8"},"text/vnd.wap.si":{"source":"iana"},"text/vnd.wap.sl":{"source":"iana"},"text/vnd.wap.wml":{"source":"iana","extensions":["wml"]},"text/vnd.wap.wmlscript":{"source":"iana","extensions":["wmls"]},"text/vtt":{"source":"iana","charset":"UTF-8","compressible":true,"extensions":["vtt"]},"text/x-asm":{"source":"apache","extensions":["s","asm"]},"text/x-c":{"source":"apache","extensions":["c","cc","cxx","cpp","h","hh","dic"]},"text/x-component":{"source":"nginx","extensions":["htc"]},"text/x-fortran":{"source":"apache","extensions":["f","for","f77","f90"]},"text/x-gwt-rpc":{"compressible":true},"text/x-handlebars-template":{"extensions":["hbs"]},"text/x-java-source":{"source":"apache","extensions":["java"]},"text/x-jquery-tmpl":{"compressible":true},"text/x-lua":{"extensions":["lua"]},"text/x-markdown":{"compressible":true,"extensions":["mkd"]},"text/x-nfo":{"source":"apache","extensions":["nfo"]},"text/x-opml":{"source":"apache","extensions":["opml"]},"text/x-org":{"compressible":true,"extensions":["org"]},"text/x-pascal":{"source":"apache","extensions":["p","pas"]},"text/x-processing":{"compressible":true,"extensions":["pde"]},"text/x-sass":{"extensions":["sass"]},"text/x-scss":{"extensions":["scss"]},"text/x-setext":{"source":"apache","extensions":["etx"]},"text/x-sfv":{"source":"apache","extensions":["sfv"]},"text/x-suse-ymp":{"compressible":true,"extensions":["ymp"]},"text/x-uuencode":{"source":"apache","extensions":["uu"]},"text/x-vcalendar":{"source":"apache","extensions":["vcs"]},"text/x-vcard":{"source":"apache","extensions":["vcf"]},"text/xml":{"source":"iana","compressible":true,"extensions":["xml"]},"text/xml-external-parsed-entity":{"source":"iana"},"text/yaml":{"extensions":["yaml","yml"]},"video/1d-interleaved-parityfec":{"source":"iana"},"video/3gpp":{"source":"iana","extensions":["3gp","3gpp"]},"video/3gpp-tt":{"source":"iana"},"video/3gpp2":{"source":"iana","extensions":["3g2"]},"video/bmpeg":{"source":"iana"},"video/bt656":{"source":"iana"},"video/celb":{"source":"iana"},"video/dv":{"source":"iana"},"video/encaprtp":{"source":"iana"},"video/flexfec":{"source":"iana"},"video/h261":{"source":"iana","extensions":["h261"]},"video/h263":{"source":"iana","extensions":["h263"]},"video/h263-1998":{"source":"iana"},"video/h263-2000":{"source":"iana"},"video/h264":{"source":"iana","extensions":["h264"]},"video/h264-rcdo":{"source":"iana"},"video/h264-svc":{"source":"iana"},"video/h265":{"source":"iana"},"video/iso.segment":{"source":"iana"},"video/jpeg":{"source":"iana","extensions":["jpgv"]},"video/jpeg2000":{"source":"iana"},"video/jpm":{"source":"apache","extensions":["jpm","jpgm"]},"video/mj2":{"source":"iana","extensions":["mj2","mjp2"]},"video/mp1s":{"source":"iana"},"video/mp2p":{"source":"iana"},"video/mp2t":{"source":"iana","extensions":["ts"]},"video/mp4":{"source":"iana","compressible":false,"extensions":["mp4","mp4v","mpg4"]},"video/mp4v-es":{"source":"iana"},"video/mpeg":{"source":"iana","compressible":false,"extensions":["mpeg","mpg","mpe","m1v","m2v"]},"video/mpeg4-generic":{"source":"iana"},"video/mpv":{"source":"iana"},"video/nv":{"source":"iana"},"video/ogg":{"source":"iana","compressible":false,"extensions":["ogv"]},"video/parityfec":{"source":"iana"},"video/pointer":{"source":"iana"},"video/quicktime":{"source":"iana","compressible":false,"extensions":["qt","mov"]},"video/raptorfec":{"source":"iana"},"video/raw":{"source":"iana"},"video/rtp-enc-aescm128":{"source":"iana"},"video/rtploopback":{"source":"iana"},"video/rtx":{"source":"iana"},"video/smpte291":{"source":"iana"},"video/smpte292m":{"source":"iana"},"video/ulpfec":{"source":"iana"},"video/vc1":{"source":"iana"},"video/vc2":{"source":"iana"},"video/vnd.cctv":{"source":"iana"},"video/vnd.dece.hd":{"source":"iana","extensions":["uvh","uvvh"]},"video/vnd.dece.mobile":{"source":"iana","extensions":["uvm","uvvm"]},"video/vnd.dece.mp4":{"source":"iana"},"video/vnd.dece.pd":{"source":"iana","extensions":["uvp","uvvp"]},"video/vnd.dece.sd":{"source":"iana","extensions":["uvs","uvvs"]},"video/vnd.dece.video":{"source":"iana","extensions":["uvv","uvvv"]},"video/vnd.directv.mpeg":{"source":"iana"},"video/vnd.directv.mpeg-tts":{"source":"iana"},"video/vnd.dlna.mpeg-tts":{"source":"iana"},"video/vnd.dvb.file":{"source":"iana","extensions":["dvb"]},"video/vnd.fvt":{"source":"iana","extensions":["fvt"]},"video/vnd.hns.video":{"source":"iana"},"video/vnd.iptvforum.1dparityfec-1010":{"source":"iana"},"video/vnd.iptvforum.1dparityfec-2005":{"source":"iana"},"video/vnd.iptvforum.2dparityfec-1010":{"source":"iana"},"video/vnd.iptvforum.2dparityfec-2005":{"source":"iana"},"video/vnd.iptvforum.ttsavc":{"source":"iana"},"video/vnd.iptvforum.ttsmpeg2":{"source":"iana"},"video/vnd.motorola.video":{"source":"iana"},"video/vnd.motorola.videop":{"source":"iana"},"video/vnd.mpegurl":{"source":"iana","extensions":["mxu","m4u"]},"video/vnd.ms-playready.media.pyv":{"source":"iana","extensions":["pyv"]},"video/vnd.nokia.interleaved-multimedia":{"source":"iana"},"video/vnd.nokia.mp4vr":{"source":"iana"},"video/vnd.nokia.videovoip":{"source":"iana"},"video/vnd.objectvideo":{"source":"iana"},"video/vnd.radgamettools.bink":{"source":"iana"},"video/vnd.radgamettools.smacker":{"source":"iana"},"video/vnd.sealed.mpeg1":{"source":"iana"},"video/vnd.sealed.mpeg4":{"source":"iana"},"video/vnd.sealed.swf":{"source":"iana"},"video/vnd.sealedmedia.softseal.mov":{"source":"iana"},"video/vnd.uvvu.mp4":{"source":"iana","extensions":["uvu","uvvu"]},"video/vnd.vivo":{"source":"iana","extensions":["viv"]},"video/vnd.youtube.yt":{"source":"iana"},"video/vp8":{"source":"iana"},"video/webm":{"source":"apache","compressible":false,"extensions":["webm"]},"video/x-f4v":{"source":"apache","extensions":["f4v"]},"video/x-fli":{"source":"apache","extensions":["fli"]},"video/x-flv":{"source":"apache","compressible":false,"extensions":["flv"]},"video/x-m4v":{"source":"apache","extensions":["m4v"]},"video/x-matroska":{"source":"apache","compressible":false,"extensions":["mkv","mk3d","mks"]},"video/x-mng":{"source":"apache","extensions":["mng"]},"video/x-ms-asf":{"source":"apache","extensions":["asf","asx"]},"video/x-ms-vob":{"source":"apache","extensions":["vob"]},"video/x-ms-wm":{"source":"apache","extensions":["wm"]},"video/x-ms-wmv":{"source":"apache","compressible":false,"extensions":["wmv"]},"video/x-ms-wmx":{"source":"apache","extensions":["wmx"]},"video/x-ms-wvx":{"source":"apache","extensions":["wvx"]},"video/x-msvideo":{"source":"apache","extensions":["avi"]},"video/x-sgi-movie":{"source":"apache","extensions":["movie"]},"video/x-smv":{"source":"apache","extensions":["smv"]},"x-conference/x-cooltalk":{"source":"apache","extensions":["ice"]},"x-shader/x-fragment":{"compressible":true},"x-shader/x-vertex":{"compressible":true}}; + +/***/ }), + +/***/ 125: +/***/ (function(module) { + +// API +module.exports = state; + +/** + * Creates initial state object + * for iteration over list + * + * @param {array|object} list - list to iterate over + * @param {function|null} sortMethod - function to use for keys sort, + * or `null` to keep them as is + * @returns {object} - initial state object + */ +function state(list, sortMethod) +{ + var isNamedList = !Array.isArray(list) + , initState = + { + index : 0, + keyedList: isNamedList || sortMethod ? Object.keys(list) : null, + jobs : {}, + results : isNamedList ? {} : [], + size : isNamedList ? Object.keys(list).length : list.length + } + ; + + if (sortMethod) + { + // sort array keys based on it's values + // sort object's keys just on own merit + initState.keyedList.sort(isNamedList ? sortMethod : function(a, b) + { + return sortMethod(list[a], list[b]); + }); + } + + return initState; +} + + +/***/ }), + +/***/ 128: +/***/ (function(module, __unusedexports, __webpack_require__) { + +/*! + * mime-db + * Copyright(c) 2014 Jonathan Ong + * MIT Licensed + */ + +/** + * Module exports. + */ + +module.exports = __webpack_require__(118) + + +/***/ }), + +/***/ 154: +/***/ (function(module) { + +module.exports = defer; + +/** + * Runs provided function on next iteration of the event loop + * + * @param {function} fn - function to run + */ +function defer(fn) +{ + var nextTick = typeof setImmediate == 'function' + ? setImmediate + : ( + typeof process == 'object' && typeof process.nextTick == 'function' + ? process.nextTick + : null + ); + + if (nextTick) + { + nextTick(fn); + } + else + { + setTimeout(fn, 0); + } +} + + +/***/ }), + +/***/ 164: +/***/ (function(module, __unusedexports, __webpack_require__) { + +var Stream = __webpack_require__(413).Stream; +var util = __webpack_require__(669); + +module.exports = DelayedStream; +function DelayedStream() { + this.source = null; + this.dataSize = 0; + this.maxDataSize = 1024 * 1024; + this.pauseStream = true; + + this._maxDataSizeExceeded = false; + this._released = false; + this._bufferedEvents = []; +} +util.inherits(DelayedStream, Stream); + +DelayedStream.create = function(source, options) { + var delayedStream = new this(); + + options = options || {}; + for (var option in options) { + delayedStream[option] = options[option]; + } + + delayedStream.source = source; + + var realEmit = source.emit; + source.emit = function() { + delayedStream._handleEmit(arguments); + return realEmit.apply(source, arguments); + }; + + source.on('error', function() {}); + if (delayedStream.pauseStream) { + source.pause(); + } + + return delayedStream; +}; + +Object.defineProperty(DelayedStream.prototype, 'readable', { + configurable: true, + enumerable: true, + get: function() { + return this.source.readable; + } +}); + +DelayedStream.prototype.setEncoding = function() { + return this.source.setEncoding.apply(this.source, arguments); +}; + +DelayedStream.prototype.resume = function() { + if (!this._released) { + this.release(); + } + + this.source.resume(); +}; + +DelayedStream.prototype.pause = function() { + this.source.pause(); +}; + +DelayedStream.prototype.release = function() { + this._released = true; + + this._bufferedEvents.forEach(function(args) { + this.emit.apply(this, args); + }.bind(this)); + this._bufferedEvents = []; +}; + +DelayedStream.prototype.pipe = function() { + var r = Stream.prototype.pipe.apply(this, arguments); + this.resume(); + return r; +}; + +DelayedStream.prototype._handleEmit = function(args) { + if (this._released) { + this.emit.apply(this, args); + return; + } + + if (args[0] === 'data') { + this.dataSize += args[1].length; + this._checkIfMaxDataSizeExceeded(); + } + + this._bufferedEvents.push(args); +}; + +DelayedStream.prototype._checkIfMaxDataSizeExceeded = function() { + if (this._maxDataSizeExceeded) { + return; + } + + if (this.dataSize <= this.maxDataSize) { + return; + } + + this._maxDataSizeExceeded = true; + var message = + 'DelayedStream#maxDataSize of ' + this.maxDataSize + ' bytes exceeded.' + this.emit('error', new Error(message)); +}; + + +/***/ }), + +/***/ 176: +/***/ (function(module, __unusedexports, __webpack_require__) { + +var serialOrdered = __webpack_require__(275); + +// Public API +module.exports = serial; + +/** + * Runs iterator over provided array elements in series + * + * @param {array|object} list - array or object (named list) to iterate over + * @param {function} iterator - iterator to run + * @param {function} callback - invoked when all elements processed + * @returns {function} - jobs terminator + */ +function serial(list, iterator, callback) +{ + return serialOrdered(list, iterator, null, callback); +} + + +/***/ }), + +/***/ 211: +/***/ (function(module) { + +module.exports = require("https"); + +/***/ }), + +/***/ 213: +/***/ (function(module, __unusedexports, __webpack_require__) { + +var util = __webpack_require__(669); +var Stream = __webpack_require__(413).Stream; +var DelayedStream = __webpack_require__(164); + +module.exports = CombinedStream; +function CombinedStream() { + this.writable = false; + this.readable = true; + this.dataSize = 0; + this.maxDataSize = 2 * 1024 * 1024; + this.pauseStreams = true; + + this._released = false; + this._streams = []; + this._currentStream = null; + this._insideLoop = false; + this._pendingNext = false; +} +util.inherits(CombinedStream, Stream); + +CombinedStream.create = function(options) { + var combinedStream = new this(); + + options = options || {}; + for (var option in options) { + combinedStream[option] = options[option]; + } + + return combinedStream; +}; + +CombinedStream.isStreamLike = function(stream) { + return (typeof stream !== 'function') + && (typeof stream !== 'string') + && (typeof stream !== 'boolean') + && (typeof stream !== 'number') + && (!Buffer.isBuffer(stream)); +}; + +CombinedStream.prototype.append = function(stream) { + var isStreamLike = CombinedStream.isStreamLike(stream); + + if (isStreamLike) { + if (!(stream instanceof DelayedStream)) { + var newStream = DelayedStream.create(stream, { + maxDataSize: Infinity, + pauseStream: this.pauseStreams, + }); + stream.on('data', this._checkDataSize.bind(this)); + stream = newStream; + } + + this._handleErrors(stream); + + if (this.pauseStreams) { + stream.pause(); + } + } + + this._streams.push(stream); + return this; +}; + +CombinedStream.prototype.pipe = function(dest, options) { + Stream.prototype.pipe.call(this, dest, options); + this.resume(); + return dest; +}; + +CombinedStream.prototype._getNext = function() { + this._currentStream = null; + + if (this._insideLoop) { + this._pendingNext = true; + return; // defer call + } + + this._insideLoop = true; + try { + do { + this._pendingNext = false; + this._realGetNext(); + } while (this._pendingNext); + } finally { + this._insideLoop = false; + } +}; + +CombinedStream.prototype._realGetNext = function() { + var stream = this._streams.shift(); + + + if (typeof stream == 'undefined') { + this.end(); + return; + } + + if (typeof stream !== 'function') { + this._pipeNext(stream); + return; + } + + var getStream = stream; + getStream(function(stream) { + var isStreamLike = CombinedStream.isStreamLike(stream); + if (isStreamLike) { + stream.on('data', this._checkDataSize.bind(this)); + this._handleErrors(stream); + } + + this._pipeNext(stream); + }.bind(this)); +}; + +CombinedStream.prototype._pipeNext = function(stream) { + this._currentStream = stream; + + var isStreamLike = CombinedStream.isStreamLike(stream); + if (isStreamLike) { + stream.on('end', this._getNext.bind(this)); + stream.pipe(this, {end: false}); + return; + } + + var value = stream; + this.write(value); + this._getNext(); +}; + +CombinedStream.prototype._handleErrors = function(stream) { + var self = this; + stream.on('error', function(err) { + self._emitError(err); + }); +}; + +CombinedStream.prototype.write = function(data) { + this.emit('data', data); +}; + +CombinedStream.prototype.pause = function() { + if (!this.pauseStreams) { + return; + } + + if(this.pauseStreams && this._currentStream && typeof(this._currentStream.pause) == 'function') this._currentStream.pause(); + this.emit('pause'); +}; + +CombinedStream.prototype.resume = function() { + if (!this._released) { + this._released = true; + this.writable = true; + this._getNext(); + } + + if(this.pauseStreams && this._currentStream && typeof(this._currentStream.resume) == 'function') this._currentStream.resume(); + this.emit('resume'); +}; + +CombinedStream.prototype.end = function() { + this._reset(); + this.emit('end'); +}; + +CombinedStream.prototype.destroy = function() { + this._reset(); + this.emit('close'); +}; + +CombinedStream.prototype._reset = function() { + this.writable = false; + this._streams = []; + this._currentStream = null; +}; + +CombinedStream.prototype._checkDataSize = function() { + this._updateDataSize(); + if (this.dataSize <= this.maxDataSize) { + return; + } + + var message = + 'DelayedStream#maxDataSize of ' + this.maxDataSize + ' bytes exceeded.'; + this._emitError(new Error(message)); +}; + +CombinedStream.prototype._updateDataSize = function() { + this.dataSize = 0; + + var self = this; + this._streams.forEach(function(stream) { + if (!stream.dataSize) { + return; + } + + self.dataSize += stream.dataSize; + }); + + if (this._currentStream && this._currentStream.dataSize) { + this.dataSize += this._currentStream.dataSize; + } +}; + +CombinedStream.prototype._emitError = function(err) { + this._reset(); + this.emit('error', err); +}; + + +/***/ }), + +/***/ 258: +/***/ (function(module, __unusedexports, __webpack_require__) { + +var async = __webpack_require__(792) + , abort = __webpack_require__(762) + ; + +// API +module.exports = iterate; + +/** + * Iterates over each job object + * + * @param {array|object} list - array or object (named list) to iterate over + * @param {function} iterator - iterator to run + * @param {object} state - current job status + * @param {function} callback - invoked when all elements processed + */ +function iterate(list, iterator, state, callback) +{ + // store current index + var key = state['keyedList'] ? state['keyedList'][state.index] : state.index; + + state.jobs[key] = runJob(iterator, key, list[key], function(error, output) + { + // don't repeat yourself + // skip secondary callbacks + if (!(key in state.jobs)) + { + return; + } + + // clean up jobs + delete state.jobs[key]; + + if (error) + { + // don't process rest of the results + // stop still active jobs + // and reset the list + abort(state); + } + else + { + state.results[key] = output; + } + + // return salvaged results + callback(error, state.results); + }); +} + +/** + * Runs iterator over provided job element + * + * @param {function} iterator - iterator to invoke + * @param {string|number} key - key/index of the element in the list of jobs + * @param {mixed} item - job description + * @param {function} callback - invoked after iterator is done with the job + * @returns {function|mixed} - job abort function or something else + */ +function runJob(iterator, key, item, callback) +{ + var aborter; + + // allow shortcut if iterator expects only two arguments + if (iterator.length == 2) + { + aborter = iterator(item, async(callback)); + } + // otherwise go with full three arguments + else + { + aborter = iterator(item, key, async(callback)); + } + + return aborter; +} + + +/***/ }), + +/***/ 275: +/***/ (function(module, __unusedexports, __webpack_require__) { + +var iterate = __webpack_require__(258) + , initState = __webpack_require__(125) + , terminator = __webpack_require__(2) + ; + +// Public API +module.exports = serialOrdered; +// sorting helpers +module.exports.ascending = ascending; +module.exports.descending = descending; + +/** + * Runs iterator over provided sorted array elements in series + * + * @param {array|object} list - array or object (named list) to iterate over + * @param {function} iterator - iterator to run + * @param {function} sortMethod - custom sort function + * @param {function} callback - invoked when all elements processed + * @returns {function} - jobs terminator + */ +function serialOrdered(list, iterator, sortMethod, callback) +{ + var state = initState(list, sortMethod); + + iterate(list, iterator, state, function iteratorHandler(error, result) + { + if (error) + { + callback(error, result); + return; + } + + state.index++; + + // are we there yet? + if (state.index < (state['keyedList'] || list).length) + { + iterate(list, iterator, state, iteratorHandler); + return; + } + + // done here + callback(null, state.results); + }); + + return terminator.bind(state, callback); +} + +/* + * -- Sort methods + */ + +/** + * sort helper to sort array elements in ascending order + * + * @param {mixed} a - an item to compare + * @param {mixed} b - an item to compare + * @returns {number} - comparison result + */ +function ascending(a, b) +{ + return a < b ? -1 : a > b ? 1 : 0; +} + +/** + * sort helper to sort array elements in descending order + * + * @param {mixed} a - an item to compare + * @param {mixed} b - an item to compare + * @returns {number} - comparison result + */ +function descending(a, b) +{ + return -1 * ascending(a, b); +} + + +/***/ }), + +/***/ 294: +/***/ (function(module, __unusedexports, __webpack_require__) { + +module.exports = +{ + parallel : __webpack_require__(89), + serial : __webpack_require__(176), + serialOrdered : __webpack_require__(275) +}; + + +/***/ }), + +/***/ 413: +/***/ (function(module) { + +module.exports = require("stream"); + +/***/ }), + +/***/ 416: +/***/ (function(module, __unusedexports, __webpack_require__) { + +var CombinedStream = __webpack_require__(213); +var util = __webpack_require__(669); +var path = __webpack_require__(622); +var http = __webpack_require__(605); +var https = __webpack_require__(211); +var parseUrl = __webpack_require__(835).parse; +var fs = __webpack_require__(747); +var mime = __webpack_require__(769); +var asynckit = __webpack_require__(294); +var populate = __webpack_require__(70); + +// Public API +module.exports = FormData; + +// make it a Stream +util.inherits(FormData, CombinedStream); + +/** + * Create readable "multipart/form-data" streams. + * Can be used to submit forms + * and file uploads to other web applications. + * + * @constructor + * @param {Object} options - Properties to be added/overriden for FormData and CombinedStream + */ +function FormData(options) { + if (!(this instanceof FormData)) { + return new FormData(); + } + + this._overheadLength = 0; + this._valueLength = 0; + this._valuesToMeasure = []; + + CombinedStream.call(this); + + options = options || {}; + for (var option in options) { + this[option] = options[option]; + } +} + +FormData.LINE_BREAK = '\r\n'; +FormData.DEFAULT_CONTENT_TYPE = 'application/octet-stream'; + +FormData.prototype.append = function(field, value, options) { + + options = options || {}; + + // allow filename as single option + if (typeof options == 'string') { + options = {filename: options}; + } + + var append = CombinedStream.prototype.append.bind(this); + + // all that streamy business can't handle numbers + if (typeof value == 'number') { + value = '' + value; + } + + // https://github.com/felixge/node-form-data/issues/38 + if (util.isArray(value)) { + // Please convert your array into string + // the way web server expects it + this._error(new Error('Arrays are not supported.')); + return; + } + + var header = this._multiPartHeader(field, value, options); + var footer = this._multiPartFooter(); + + append(header); + append(value); + append(footer); + + // pass along options.knownLength + this._trackLength(header, value, options); +}; + +FormData.prototype._trackLength = function(header, value, options) { + var valueLength = 0; + + // used w/ getLengthSync(), when length is known. + // e.g. for streaming directly from a remote server, + // w/ a known file a size, and not wanting to wait for + // incoming file to finish to get its size. + if (options.knownLength != null) { + valueLength += +options.knownLength; + } else if (Buffer.isBuffer(value)) { + valueLength = value.length; + } else if (typeof value === 'string') { + valueLength = Buffer.byteLength(value); + } + + this._valueLength += valueLength; + + // @check why add CRLF? does this account for custom/multiple CRLFs? + this._overheadLength += + Buffer.byteLength(header) + + FormData.LINE_BREAK.length; + + // empty or either doesn't have path or not an http response + if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) )) { + return; + } + + // no need to bother with the length + if (!options.knownLength) { + this._valuesToMeasure.push(value); + } +}; + +FormData.prototype._lengthRetriever = function(value, callback) { + + if (value.hasOwnProperty('fd')) { + + // take read range into a account + // `end` = Infinity –> read file till the end + // + // TODO: Looks like there is bug in Node fs.createReadStream + // it doesn't respect `end` options without `start` options + // Fix it when node fixes it. + // https://github.com/joyent/node/issues/7819 + if (value.end != undefined && value.end != Infinity && value.start != undefined) { + + // when end specified + // no need to calculate range + // inclusive, starts with 0 + callback(null, value.end + 1 - (value.start ? value.start : 0)); + + // not that fast snoopy + } else { + // still need to fetch file size from fs + fs.stat(value.path, function(err, stat) { + + var fileSize; + + if (err) { + callback(err); + return; + } + + // update final size based on the range options + fileSize = stat.size - (value.start ? value.start : 0); + callback(null, fileSize); + }); + } + + // or http response + } else if (value.hasOwnProperty('httpVersion')) { + callback(null, +value.headers['content-length']); + + // or request stream http://github.com/mikeal/request + } else if (value.hasOwnProperty('httpModule')) { + // wait till response come back + value.on('response', function(response) { + value.pause(); + callback(null, +response.headers['content-length']); + }); + value.resume(); + + // something else + } else { + callback('Unknown stream'); + } +}; + +FormData.prototype._multiPartHeader = function(field, value, options) { + // custom header specified (as string)? + // it becomes responsible for boundary + // (e.g. to handle extra CRLFs on .NET servers) + if (typeof options.header == 'string') { + return options.header; + } + + var contentDisposition = this._getContentDisposition(value, options); + var contentType = this._getContentType(value, options); + + var contents = ''; + var headers = { + // add custom disposition as third element or keep it two elements if not + 'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []), + // if no content type. allow it to be empty array + 'Content-Type': [].concat(contentType || []) + }; + + // allow custom headers. + if (typeof options.header == 'object') { + populate(headers, options.header); + } + + var header; + for (var prop in headers) { + if (!headers.hasOwnProperty(prop)) continue; + header = headers[prop]; + + // skip nullish headers. + if (header == null) { + continue; + } + + // convert all headers to arrays. + if (!Array.isArray(header)) { + header = [header]; + } + + // add non-empty headers. + if (header.length) { + contents += prop + ': ' + header.join('; ') + FormData.LINE_BREAK; + } + } + + return '--' + this.getBoundary() + FormData.LINE_BREAK + contents + FormData.LINE_BREAK; +}; + +FormData.prototype._getContentDisposition = function(value, options) { + + var filename + , contentDisposition + ; + + if (typeof options.filepath === 'string') { + // custom filepath for relative paths + filename = path.normalize(options.filepath).replace(/\\/g, '/'); + } else if (options.filename || value.name || value.path) { + // custom filename take precedence + // formidable and the browser add a name property + // fs- and request- streams have path property + filename = path.basename(options.filename || value.name || value.path); + } else if (value.readable && value.hasOwnProperty('httpVersion')) { + // or try http response + filename = path.basename(value.client._httpMessage.path || ''); + } + + if (filename) { + contentDisposition = 'filename="' + filename + '"'; + } + + return contentDisposition; +}; + +FormData.prototype._getContentType = function(value, options) { + + // use custom content-type above all + var contentType = options.contentType; + + // or try `name` from formidable, browser + if (!contentType && value.name) { + contentType = mime.lookup(value.name); + } + + // or try `path` from fs-, request- streams + if (!contentType && value.path) { + contentType = mime.lookup(value.path); + } + + // or if it's http-reponse + if (!contentType && value.readable && value.hasOwnProperty('httpVersion')) { + contentType = value.headers['content-type']; + } + + // or guess it from the filepath or filename + if (!contentType && (options.filepath || options.filename)) { + contentType = mime.lookup(options.filepath || options.filename); + } + + // fallback to the default content type if `value` is not simple value + if (!contentType && typeof value == 'object') { + contentType = FormData.DEFAULT_CONTENT_TYPE; + } + + return contentType; +}; + +FormData.prototype._multiPartFooter = function() { + return function(next) { + var footer = FormData.LINE_BREAK; + + var lastPart = (this._streams.length === 0); + if (lastPart) { + footer += this._lastBoundary(); + } + + next(footer); + }.bind(this); +}; + +FormData.prototype._lastBoundary = function() { + return '--' + this.getBoundary() + '--' + FormData.LINE_BREAK; +}; + +FormData.prototype.getHeaders = function(userHeaders) { + var header; + var formHeaders = { + 'content-type': 'multipart/form-data; boundary=' + this.getBoundary() + }; + + for (header in userHeaders) { + if (userHeaders.hasOwnProperty(header)) { + formHeaders[header.toLowerCase()] = userHeaders[header]; + } + } + + return formHeaders; +}; + +FormData.prototype.getBoundary = function() { + if (!this._boundary) { + this._generateBoundary(); + } + + return this._boundary; +}; + +FormData.prototype.getBuffer = function() { + var dataBuffer = new Buffer.alloc( 0 ); + var boundary = this.getBoundary(); + + // Create the form content. Add Line breaks to the end of data. + for (var i = 0, len = this._streams.length; i < len; i++) { + if (typeof this._streams[i] !== 'function') { + + // Add content to the buffer. + if(Buffer.isBuffer(this._streams[i])) { + dataBuffer = Buffer.concat( [dataBuffer, this._streams[i]]); + }else { + dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(this._streams[i])]); + } + + // Add break after content. + if (typeof this._streams[i] !== 'string' || this._streams[i].substring( 2, boundary.length + 2 ) !== boundary) { + dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(FormData.LINE_BREAK)] ); + } + } + } + + // Add the footer and return the Buffer object. + return Buffer.concat( [dataBuffer, Buffer.from(this._lastBoundary())] ); +}; + +FormData.prototype._generateBoundary = function() { + // This generates a 50 character boundary similar to those used by Firefox. + // They are optimized for boyer-moore parsing. + var boundary = '--------------------------'; + for (var i = 0; i < 24; i++) { + boundary += Math.floor(Math.random() * 10).toString(16); + } + + this._boundary = boundary; +}; + +// Note: getLengthSync DOESN'T calculate streams length +// As workaround one can calculate file size manually +// and add it as knownLength option +FormData.prototype.getLengthSync = function() { + var knownLength = this._overheadLength + this._valueLength; + + // Don't get confused, there are 3 "internal" streams for each keyval pair + // so it basically checks if there is any value added to the form + if (this._streams.length) { + knownLength += this._lastBoundary().length; + } + + // https://github.com/form-data/form-data/issues/40 + if (!this.hasKnownLength()) { + // Some async length retrievers are present + // therefore synchronous length calculation is false. + // Please use getLength(callback) to get proper length + this._error(new Error('Cannot calculate proper length in synchronous way.')); + } + + return knownLength; +}; + +// Public API to check if length of added values is known +// https://github.com/form-data/form-data/issues/196 +// https://github.com/form-data/form-data/issues/262 +FormData.prototype.hasKnownLength = function() { + var hasKnownLength = true; + + if (this._valuesToMeasure.length) { + hasKnownLength = false; + } + + return hasKnownLength; +}; + +FormData.prototype.getLength = function(cb) { + var knownLength = this._overheadLength + this._valueLength; + + if (this._streams.length) { + knownLength += this._lastBoundary().length; + } + + if (!this._valuesToMeasure.length) { + process.nextTick(cb.bind(this, null, knownLength)); + return; + } + + asynckit.parallel(this._valuesToMeasure, this._lengthRetriever, function(err, values) { + if (err) { + cb(err); + return; + } + + values.forEach(function(length) { + knownLength += length; + }); + + cb(null, knownLength); + }); +}; + +FormData.prototype.submit = function(params, cb) { + var request + , options + , defaults = {method: 'post'} + ; + + // parse provided url if it's string + // or treat it as options object + if (typeof params == 'string') { + + params = parseUrl(params); + options = populate({ + port: params.port, + path: params.pathname, + host: params.hostname, + protocol: params.protocol + }, defaults); + + // use custom params + } else { + + options = populate(params, defaults); + // if no port provided use default one + if (!options.port) { + options.port = options.protocol == 'https:' ? 443 : 80; + } + } + + // put that good code in getHeaders to some use + options.headers = this.getHeaders(params.headers); + + // https if specified, fallback to http in any other case + if (options.protocol == 'https:') { + request = https.request(options); + } else { + request = http.request(options); + } + + // get content length and fire away + this.getLength(function(err, length) { + if (err) { + this._error(err); + return; + } + + // add content length + request.setHeader('Content-Length', length); + + this.pipe(request); + if (cb) { + request.on('error', cb); + request.on('response', cb.bind(this, null)); + } + }.bind(this)); + + return request; +}; + +FormData.prototype._error = function(err) { + if (!this.error) { + this.error = err; + this.pause(); + this.emit('error', err); + } +}; + +FormData.prototype.toString = function () { + return '[object FormData]'; +}; + + +/***/ }), + +/***/ 417: +/***/ (function(module) { + +module.exports = require("crypto"); + +/***/ }), + +/***/ 431: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const os = __importStar(__webpack_require__(87)); +const utils_1 = __webpack_require__(82); +/** + * Commands + * + * Command Format: + * ::name key=value,key=value::message + * + * Examples: + * ::warning::This is the message + * ::set-env name=MY_VAR::some value + */ +function issueCommand(command, properties, message) { + const cmd = new Command(command, properties, message); + process.stdout.write(cmd.toString() + os.EOL); +} +exports.issueCommand = issueCommand; +function issue(name, message = '') { + issueCommand(name, {}, message); +} +exports.issue = issue; +const CMD_STRING = '::'; +class Command { + constructor(command, properties, message) { + if (!command) { + command = 'missing.command'; + } + this.command = command; + this.properties = properties; + this.message = message; + } + toString() { + let cmdStr = CMD_STRING + this.command; + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += ' '; + let first = true; + for (const key in this.properties) { + if (this.properties.hasOwnProperty(key)) { + const val = this.properties[key]; + if (val) { + if (first) { + first = false; + } + else { + cmdStr += ','; + } + cmdStr += `${key}=${escapeProperty(val)}`; + } + } + } + } + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; + return cmdStr; + } +} +function escapeData(s) { + return utils_1.toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} +function escapeProperty(s) { + return utils_1.toCommandValue(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); +} +//# sourceMappingURL=command.js.map + +/***/ }), + +/***/ 470: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const command_1 = __webpack_require__(431); +const file_command_1 = __webpack_require__(102); +const utils_1 = __webpack_require__(82); +const os = __importStar(__webpack_require__(87)); +const path = __importStar(__webpack_require__(622)); +/** + * The code to exit an action + */ +var ExitCode; +(function (ExitCode) { + /** + * A code indicating that the action was successful + */ + ExitCode[ExitCode["Success"] = 0] = "Success"; + /** + * A code indicating that the action was a failure + */ + ExitCode[ExitCode["Failure"] = 1] = "Failure"; +})(ExitCode = exports.ExitCode || (exports.ExitCode = {})); +//----------------------------------------------------------------------- +// Variables +//----------------------------------------------------------------------- +/** + * Sets env variable for this action and future actions in the job + * @param name the name of the variable to set + * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function exportVariable(name, val) { + const convertedVal = utils_1.toCommandValue(val); + process.env[name] = convertedVal; + const filePath = process.env['GITHUB_ENV'] || ''; + if (filePath) { + const delimiter = '_GitHubActionsFileCommandDelimeter_'; + const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`; + file_command_1.issueCommand('ENV', commandValue); + } + else { + command_1.issueCommand('set-env', { name }, convertedVal); + } +} +exports.exportVariable = exportVariable; +/** + * Registers a secret which will get masked from logs + * @param secret value of the secret + */ +function setSecret(secret) { + command_1.issueCommand('add-mask', {}, secret); +} +exports.setSecret = setSecret; +/** + * Prepends inputPath to the PATH (for this action and future actions) + * @param inputPath + */ +function addPath(inputPath) { + const filePath = process.env['GITHUB_PATH'] || ''; + if (filePath) { + file_command_1.issueCommand('PATH', inputPath); + } + else { + command_1.issueCommand('add-path', {}, inputPath); + } + process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; +} +exports.addPath = addPath; +/** + * Gets the value of an input. The value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string + */ +function getInput(name, options) { + const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; + if (options && options.required && !val) { + throw new Error(`Input required and not supplied: ${name}`); + } + return val.trim(); +} +exports.getInput = getInput; +/** + * Sets the value of an output. + * + * @param name name of the output to set + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function setOutput(name, value) { + command_1.issueCommand('set-output', { name }, value); +} +exports.setOutput = setOutput; +/** + * Enables or disables the echoing of commands into stdout for the rest of the step. + * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. + * + */ +function setCommandEcho(enabled) { + command_1.issue('echo', enabled ? 'on' : 'off'); +} +exports.setCommandEcho = setCommandEcho; +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- +/** + * Sets the action status to failed. + * When the action exits it will be with an exit code of 1 + * @param message add error issue message + */ +function setFailed(message) { + process.exitCode = ExitCode.Failure; + error(message); +} +exports.setFailed = setFailed; +//----------------------------------------------------------------------- +// Logging Commands +//----------------------------------------------------------------------- +/** + * Gets whether Actions Step Debug is on or not + */ +function isDebug() { + return process.env['RUNNER_DEBUG'] === '1'; +} +exports.isDebug = isDebug; +/** + * Writes debug message to user log + * @param message debug message + */ +function debug(message) { + command_1.issueCommand('debug', {}, message); +} +exports.debug = debug; +/** + * Adds an error issue + * @param message error issue message. Errors will be converted to string via toString() + */ +function error(message) { + command_1.issue('error', message instanceof Error ? message.toString() : message); +} +exports.error = error; +/** + * Adds an warning issue + * @param message warning issue message. Errors will be converted to string via toString() + */ +function warning(message) { + command_1.issue('warning', message instanceof Error ? message.toString() : message); +} +exports.warning = warning; +/** + * Writes info to log with console.log. + * @param message info message + */ +function info(message) { + process.stdout.write(message + os.EOL); +} +exports.info = info; +/** + * Begin an output group. + * + * Output until the next `groupEnd` will be foldable in this group + * + * @param name The name of the output group + */ +function startGroup(name) { + command_1.issue('group', name); +} +exports.startGroup = startGroup; +/** + * End an output group. + */ +function endGroup() { + command_1.issue('endgroup'); +} +exports.endGroup = endGroup; +/** + * Wrap an asynchronous function call in a group. + * + * Returns the same type as the function itself. + * + * @param name The name of the group + * @param fn The function to wrap in the group + */ +function group(name, fn) { + return __awaiter(this, void 0, void 0, function* () { + startGroup(name); + let result; + try { + result = yield fn(); + } + finally { + endGroup(); + } + return result; + }); +} +exports.group = group; +//----------------------------------------------------------------------- +// Wrapper action state +//----------------------------------------------------------------------- +/** + * Saves state for current action, the state can only be retrieved by this action's post job execution. + * + * @param name name of the state to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function saveState(name, value) { + command_1.issueCommand('save-state', { name }, value); +} +exports.saveState = saveState; +/** + * Gets the value of an state set by this action's main execution. + * + * @param name name of the state to get + * @returns string + */ +function getState(name) { + return process.env[`STATE_${name}`] || ''; +} +exports.getState = getState; +//# sourceMappingURL=core.js.map + +/***/ }), + +/***/ 605: +/***/ (function(module) { + +module.exports = require("http"); + +/***/ }), + +/***/ 618: +/***/ (function(module, __unusedexports, __webpack_require__) { + +/*! For license information please see mailgun.js.LICENSE.txt */ +!function(e,t){ true?module.exports=t():undefined}(this,(function(){return(()=>{var e={271:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o=r(185);class n extends o.EventTarget{constructor(){throw super(),new TypeError("AbortSignal cannot be constructed directly")}get aborted(){const e=s.get(this);if("boolean"!=typeof e)throw new TypeError("Expected 'this' to be an 'AbortSignal' object, but got "+(null===this?"null":typeof this));return e}}o.defineEventAttribute(n.prototype,"abort");const s=new WeakMap;Object.defineProperties(n.prototype,{aborted:{enumerable:!0}}),"function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag&&Object.defineProperty(n.prototype,Symbol.toStringTag,{configurable:!0,value:"AbortSignal"});class i{constructor(){a.set(this,function(){const e=Object.create(n.prototype);return o.EventTarget.call(e),s.set(e,!1),e}())}get signal(){return u(this)}abort(){var e;e=u(this),!1===s.get(e)&&(s.set(e,!0),e.dispatchEvent({type:"abort"}))}}const a=new WeakMap;function u(e){const t=a.get(e);if(null==t)throw new TypeError("Expected 'this' to be an 'AbortController' object, but got "+(null===e?"null":typeof e));return t}Object.defineProperties(i.prototype,{signal:{enumerable:!0},abort:{enumerable:!0}}),"function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag&&Object.defineProperty(i.prototype,Symbol.toStringTag,{configurable:!0,value:"AbortController"}),t.AbortController=i,t.AbortSignal=n,t.default=i,e.exports=i,e.exports.AbortController=e.exports.default=i,e.exports.AbortSignal=n},990:function(e,t,r){"use strict";var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});var n=o(r(765)),s=function(){function e(e){this.formData=e}return e.prototype.client=function(e){return new n.default(e,this.formData)},e}();t.default=s},765:function(e,t,r){"use strict";var o=this&&this.__assign||function(){return(o=Object.assign||function(e){for(var t,r=1,o=arguments.length;r{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o=r(78),n=(r(955),function(){function e(e){this.request=e}return e.prototype._parsePageNumber=function(e){return e.split("/").pop()},e.prototype._parsePage=function(e,t){return{id:e,number:this._parsePageNumber(t),url:t}},e.prototype._parsePageLinks=function(e){var t=this;return Object.entries(e.body.paging).reduce((function(e,r){var o=r[0],n=r[1];return e[o]=t._parsePage(o,n),e}),{})},e.prototype._parseEventList=function(e){return{items:e.body.items,pages:this._parsePageLinks(e)}},e.prototype.get=function(e,t){var r,n=this;return t&&t.page?(r=o("/v2",e,"events",t.page),delete t.page):r=o("/v2",e,"events"),this.request.get(r,t).then((function(e){return n._parseEventList(e)}))},e}());t.default=n},853:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(955);var o=function(){function e(e){this.request=e}return e.prototype.list=function(e){var t=this;return this.request.get("/v1/ip_pools",e).then((function(e){return t.parseIpPoolsResponse(e)}))},e.prototype.create=function(e){return this.request.post("/v1/ip_pools",e).then((function(e){return null==e?void 0:e.body}))},e.prototype.update=function(e,t){return this.request.patch("/v1/ip_pools/"+e,t).then((function(e){return null==e?void 0:e.body}))},e.prototype.delete=function(e,t){return this.request.delete("/v1/ip_pools/"+e,t).then((function(e){return null==e?void 0:e.body}))},e.prototype.parseIpPoolsResponse=function(e){return e.body.ip_pools},e}();t.default=o},580:(e,t,r)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),r(955);var o=function(){function e(e){this.request=e}return e.prototype.list=function(e){var t=this;return this.request.get("/v3/ips",e).then((function(e){return t.parseIpsResponse(e)}))},e.prototype.get=function(e){var t=this;return this.request.get("/v3/ips/"+e).then((function(e){return t.parseIpsResponse(e)}))},e.prototype.parseIpsResponse=function(e){return e.body},e}();t.default=o},616:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.request=e}return e.prototype._parseResponse=function(e){return e.body?e.body:e},e.prototype.create=function(e,t){return t.message?this.request.postMulti("/v3/"+e+"/messages.mime",t).then(this._parseResponse):this.request.postMulti("/v3/"+e+"/messages",t).then(this._parseResponse)},e}();t.default=r},726:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.request=e}return e.prototype.get=function(e,t){var r={};return Array.isArray(e)&&(e=e.join(",")),r.addresses=e,t&&(r.syntax_only=!1),this.request.get("/v3/address/parse",r).then((function(e){return e.body}))},e}();t.default=r},955:function(e,t,r){"use strict";var o=this&&this.__assign||function(){return(o=Object.assign||function(e){for(var t,r=1,o=arguments.length;r0&&n[n.length-1])||6!==s[0]&&2!==s[0])){i=0;continue}if(3===s[0]&&(!n||s[1]>n[0]&&s[1]0&&(f.searchParams=r.query,delete f.query),[4,l.default(u.default(this.url,t),o({method:e.toLocaleUpperCase(),headers:i,throwHttpErrors:!1},f))];case 1:return(null==(p=s.sent())?void 0:p.ok)?[3,6]:(null==p?void 0:p.body)&&d(p.body)?[4,(m=p.body,_=[],new Promise((function(e,t){m.on("data",(function(e){return _.push(e)})),m.on("error",t),m.on("end",(function(){return e(Buffer.concat(_).toString("utf8"))}))})))]:[3,3];case 2:return y=s.sent(),[3,5];case 3:return[4,null==p?void 0:p.json()];case 4:y=s.sent(),s.label=5;case 5:throw h=y,new c.default({status:null==p?void 0:p.status,statusText:null==p?void 0:p.statusText,body:{message:h}});case 6:return b={},[4,null==p?void 0:p.json()];case 7:return[2,(b.body=s.sent(),b.status=null==p?void 0:p.status,b)]}var m,_}))}))},e.prototype.query=function(e,t,r,n){return this.request(e,t,o({query:r},n))},e.prototype.command=function(e,t,r,n){return this.request(e,t,o({headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r},n))},e.prototype.get=function(e,t,r){return this.query("get",e,t,r)},e.prototype.head=function(e,t,r){return this.query("head",e,t,r)},e.prototype.options=function(e,t,r){return this.query("options",e,t,r)},e.prototype.post=function(e,t,r){return this.command("post",e,t,r)},e.prototype.postMulti=function(e,t){var r=new this.formData;return Object.keys(t).filter((function(e){return t[e]})).forEach((function(e){if("attachment"!==e)Array.isArray(t[e])?t[e].forEach((function(t){r.append(e,t)})):r.append(e,t[e]);else{var o=t.attachment;if(Array.isArray(o))o.forEach((function(t){var o=t.data?t.data:t,n=f(t);r.append(e,o,n)}));else{var n=d(o)?o:o.data,s=f(o);r.append(e,n,s)}}})),this.command("post",e,r,{headers:{"Content-Type":null}})},e.prototype.put=function(e,t,r){return this.command("put",e,t,r)},e.prototype.patch=function(e,t,r){return this.command("patch",e,t,r)},e.prototype.delete=function(e,t,r){return this.command("delete",e,t,r)},e}();t.default=p},893:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.request=e}return e.prototype.list=function(e){return this.request.get("/v3/routes",e).then((function(e){return e.body.items}))},e.prototype.get=function(e){return this.request.get("/v3/routes/"+e).then((function(e){return e.body.route}))},e.prototype.create=function(e){return this.request.post("/v3/routes",e).then((function(e){return e.body.route}))},e.prototype.update=function(e,t){return this.request.put("/v3/routes/"+e,t).then((function(e){return e.body}))},e.prototype.destroy=function(e){return this.request.delete("/v3/routes/"+e).then((function(e){return e.body}))},e}();t.default=r},154:function(e,t,r){"use strict";var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});var n=o(r(78)),s=function(e){this.start=new Date(e.start),this.end=new Date(e.end),this.resolution=e.resolution,this.stats=e.stats.map((function(e){return e.time=new Date(e.time),e}))},i=function(){function e(e){this.request=e}return e.prototype._parseStats=function(e){return new s(e.body)},e.prototype.getDomain=function(e,t){return this.request.get(n.default("/v3",e,"stats/total"),t).then(this._parseStats)},e.prototype.getAccount=function(e){return this.request.get("/v3/stats/total",e).then(this._parseStats)},e}();t.default=i},526:function(e,t,r){"use strict";var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});var n=o(r(835)),s=o(r(78)),i={headers:{"Content-Type":"application/json"}},a=function(e){this.type="bounces",this.address=e.address,this.code=+e.code,this.error=e.error,this.created_at=new Date(e.created_at)},u=function(e){this.type="complaints",this.address=e.address,this.created_at=new Date(e.created_at)},l=function(e){this.type="unsubscribes",this.address=e.address,this.tags=e.tags,this.created_at=new Date(e.created_at)},c=function(){function e(e){this.request=e,this.models={bounces:a,complaints:u,unsubscribes:l}}return e.prototype._parsePage=function(e,t){var r=n.default.parse(t,!0).query;return{id:e,page:r.page,address:r.address,url:t}},e.prototype._parsePageLinks=function(e){var t=this;return Object.entries(e.body.paging).reduce((function(e,r){var o=r[0],n=r[1];return e[o]=t._parsePage(o,n),e}),{})},e.prototype._parseList=function(e,t){var r={};return r.items=e.body.items.map((function(e){return new t(e)})),r.pages=this._parsePageLinks(e),r},e.prototype._parseItem=function(e,t){return new t(e.body)},e.prototype.list=function(e,t,r){var o=this,n=this.models[t];return this.request.get(s.default("v3",e,t),r).then((function(e){return o._parseList(e,n)}))},e.prototype.get=function(e,t,r){var o=this,n=this.models[t];return this.request.get(s.default("v3",e,t,encodeURIComponent(r))).then((function(e){return o._parseItem(e,n)}))},e.prototype.create=function(e,t,r){return Array.isArray(r)||(r=[r]),this.request.post(s.default("v3",e,t),r,i).then((function(e){return e.body}))},e.prototype.destroy=function(e,t,r){return this.request.delete(s.default("v3",e,t,encodeURIComponent(r))).then((function(e){return e.body}))},e}();t.default=c,e.exports=c},335:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=function(){function e(e){this.request=e}return e.prototype.get=function(e){return this.request.get("/v3/address/validate",{address:e}).then((function(e){return e.body}))},e}();t.default=r},632:function(e,t,r){"use strict";var o=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0});var n=o(r(78)),s=function(e,t){this.id=e,this.url=t.url},i=function(){function e(e){this.request=e}return e.prototype._parseWebhookList=function(e){return e.body.webhooks},e.prototype._parseWebhookWithID=function(e){return function(t){return new s(e,t.body.webhook)}},e.prototype._parseWebhookTest=function(e){return{code:e.body.code,message:e.body.message}},e.prototype.list=function(e,t){return this.request.get(n.default("/v2/domains",e,"webhooks"),t).then(this._parseWebhookList)},e.prototype.get=function(e,t){return this.request.get(n.default("/v2/domains",e,"webhooks",t)).then(this._parseWebhookWithID(t))},e.prototype.create=function(e,t,r,o){return o?this.request.put(n.default("/v2/domains",e,"webhooks",t,"test"),{url:r}).then(this._parseWebhookTest):this.request.post(n.default("/v2/domains",e,"webhooks"),{id:t,url:r}).then(this._parseWebhookWithID(t))},e.prototype.update=function(e,t,r){return this.request.put(n.default("/v2/domains",e,"webhooks",t),{url:r}).then(this._parseWebhookWithID(t))},e.prototype.destroy=function(e,t){return this.request.delete(n.default("/v2/domains",e,"webhooks",t)).then(this._parseWebhookWithID(t))},e}();t.default=i},706:e=>{!function(){"use strict";e.exports=function(e){return(e instanceof Buffer?e:Buffer.from(e.toString(),"binary")).toString("base64")}}()},175:e=>{"use strict";e.exports=function(e){if(!/^data:/i.test(e))throw new TypeError('`uri` does not appear to be a Data URI (must begin with "data:")');const t=(e=e.replace(/\r?\n/g,"")).indexOf(",");if(-1===t||t<=4)throw new TypeError("malformed data: URI");const r=e.substring(5,t).split(";");let o="",n=!1;const s=r[0]||"text/plain";let i=s;for(let e=1;e{"use strict";Object.defineProperty(t,"__esModule",{value:!0});const r=new WeakMap,o=new WeakMap;function n(e){const t=r.get(e);return console.assert(null!=t,"'this' is expected an Event object, but got",e),t}function s(e){null==e.passiveListener?e.event.cancelable&&(e.canceled=!0,"function"==typeof e.event.preventDefault&&e.event.preventDefault()):"undefined"!=typeof console&&"function"==typeof console.error&&console.error("Unable to preventDefault inside passive event listener invocation.",e.passiveListener)}function i(e,t){r.set(this,{eventTarget:e,event:t,eventPhase:2,currentTarget:e,canceled:!1,stopped:!1,immediateStopped:!1,passiveListener:null,timeStamp:t.timeStamp||Date.now()}),Object.defineProperty(this,"isTrusted",{value:!1,enumerable:!0});const o=Object.keys(t);for(let e=0;e0){const e=new Array(arguments.length);for(let t=0;t{const{Readable:o}=r(413),n=new WeakMap;class s{constructor(e=[],t={type:""}){let r=0;const o=e.map((e=>{let t;return t=e instanceof Buffer?e:ArrayBuffer.isView(e)?Buffer.from(e.buffer,e.byteOffset,e.byteLength):e instanceof ArrayBuffer?Buffer.from(e):e instanceof s?e:Buffer.from("string"==typeof e?e:String(e)),r+=t.length||t.size||0,t})),i=void 0===t.type?"":String(t.type).toLowerCase();n.set(this,{type:/[^\u0020-\u007E]/.test(i)?"":i,size:r,parts:o})}get size(){return n.get(this).size}get type(){return n.get(this).type}async text(){return Buffer.from(await this.arrayBuffer()).toString()}async arrayBuffer(){const e=new Uint8Array(this.size);let t=0;for await(const r of this.stream())e.set(r,t),t+=r.length;return e.buffer}stream(){return o.from(async function*(e){for(const t of e)"stream"in t?yield*t.stream():yield t}(n.get(this).parts))}slice(e=0,t=this.size,r=""){const{size:o}=this;let i=e<0?Math.max(o+e,0):Math.min(e,o),a=t<0?Math.max(o+t,0):Math.min(t,o);const u=Math.max(a-i,0),l=n.get(this).parts.values(),c=[];let d=0;for(const e of l){const t=ArrayBuffer.isView(e)?e.byteLength:e.size;if(i&&t<=i)i-=t,a-=t;else{const r=e.slice(i,Math.min(t,a));if(c.push(r),d+=ArrayBuffer.isView(r)?r.byteLength:r.size,i=0,d>=u)break}}const f=new s([],{type:r});return Object.assign(n.get(f),{size:u,parts:c}),f}get[Symbol.toStringTag](){return"Blob"}static[Symbol.hasInstance](e){return"object"==typeof e&&"function"==typeof e.stream&&0===e.stream.length&&"function"==typeof e.constructor&&/^(Blob|File)$/.test(e[Symbol.toStringTag])}}Object.defineProperties(s.prototype,{size:{enumerable:!0},type:{enumerable:!0},slice:{enumerable:!0}}),e.exports=s},556:(e,t,r)=>{"use strict";const o=r(711),n=r(271);if(global.fetch||(global.fetch=(e,t)=>o(e,{highWaterMark:1e7,...t})),global.Headers||(global.Headers=o.Headers),global.Request||(global.Request=o.Request),global.Response||(global.Response=o.Response),global.AbortController||(global.AbortController=n),!global.ReadableStream)try{global.ReadableStream=r(377)}catch(e){}e.exports=r(721)},721:function(e){var t;t=function(){"use strict";const e={},t=e=>"undefined"!=typeof self&&self&&e in self?self:"undefined"!=typeof window&&window&&e in window?window:"undefined"!=typeof global&&global&&e in global?global:"undefined"!=typeof globalThis&&globalThis?globalThis:void 0,r=["Headers","Request","Response","ReadableStream","fetch","AbortController","FormData"];for(const o of r)Object.defineProperty(e,o,{get(){const e=t(o),r=e&&e[o];return"function"==typeof r?r.bind(e):r}});const o=e=>null!==e&&"object"==typeof e,n="function"==typeof e.AbortController,s="function"==typeof e.ReadableStream,i="function"==typeof e.FormData,a=(t,r)=>{const o=new e.Headers(t||{}),n=r instanceof e.Headers,s=new e.Headers(r||{});for(const[e,t]of s)n&&"undefined"===t||void 0===t?o.delete(e):o.set(e,t);return o},u=(...e)=>{let t={},r={};for(const n of e){if(Array.isArray(n))Array.isArray(t)||(t=[]),t=[...t,...n];else if(o(n)){for(let[e,r]of Object.entries(n))o(r)&&e in t&&(r=u(t[e],r)),t={...t,[e]:r};o(n.headers)&&(r=a(r,n.headers))}t.headers=r}return t},l=["get","post","put","patch","head","delete"],c={json:"application/json",text:"text/*",formData:"multipart/form-data",arrayBuffer:"*/*",blob:"*/*"},d=[413,429,503],f=Symbol("stop");class p extends Error{constructor(e){super(e.statusText||String(0===e.status||e.status?e.status:"Unknown response error")),this.name="HTTPError",this.response=e}}class h extends Error{constructor(e){super("Request timed out"),this.name="TimeoutError",this.request=e}}const y=e=>new Promise((t=>setTimeout(t,e))),b=e=>l.includes(e)?e.toUpperCase():e,m={limit:2,methods:["get","put","head","delete","options","trace"],statusCodes:[408,413,429,500,502,503,504],afterStatusCodes:d},_=(e={})=>{if("number"==typeof e)return{...m,limit:e};if(e.methods&&!Array.isArray(e.methods))throw new Error("retry.methods must be an array");if(e.statusCodes&&!Array.isArray(e.statusCodes))throw new Error("retry.statusCodes must be an array");return{...m,...e,afterStatusCodes:d}},g=2147483647;class w{constructor(t,r={}){if(this._retryCount=0,this._input=t,this._options={credentials:this._input.credentials||"same-origin",...r,headers:a(this._input.headers,r.headers),hooks:u({beforeRequest:[],beforeRetry:[],afterResponse:[]},r.hooks),method:b(r.method||this._input.method),prefixUrl:String(r.prefixUrl||""),retry:_(r.retry),throwHttpErrors:!1!==r.throwHttpErrors,timeout:void 0===r.timeout?1e4:r.timeout,fetch:r.fetch||e.fetch},"string"!=typeof this._input&&!(this._input instanceof URL||this._input instanceof e.Request))throw new TypeError("`input` must be a string, URL, or Request");if(this._options.prefixUrl&&"string"==typeof this._input){if(this._input.startsWith("/"))throw new Error("`input` must not begin with a slash when using `prefixUrl`");this._options.prefixUrl.endsWith("/")||(this._options.prefixUrl+="/"),this._input=this._options.prefixUrl+this._input}if(n&&(this.abortController=new e.AbortController,this._options.signal&&this._options.signal.addEventListener("abort",(()=>{this.abortController.abort()})),this._options.signal=this.abortController.signal),this.request=new e.Request(this._input,this._options),this._options.searchParams){const t="?"+new URLSearchParams(this._options.searchParams).toString(),r=this.request.url.replace(/(?:\?.*?)?(?=#|$)/,t);!(i&&this._options.body instanceof e.FormData||this._options.body instanceof URLSearchParams)||this._options.headers&&this._options.headers["content-type"]||this.request.headers.delete("content-type"),this.request=new e.Request(new e.Request(r,this.request),this._options)}void 0!==this._options.json&&(this._options.body=JSON.stringify(this._options.json),this.request.headers.set("content-type","application/json"),this.request=new e.Request(this.request,{body:this._options.body}));const o=async()=>{if(this._options.timeout>g)throw new RangeError("The `timeout` option cannot be greater than 2147483647");await y(1);let t=await this._fetch();for(const r of this._options.hooks.afterResponse){const o=await r(this.request,this._options,this._decorateResponse(t.clone()));o instanceof e.Response&&(t=o)}if(this._decorateResponse(t),!t.ok&&this._options.throwHttpErrors)throw new p(t);if(this._options.onDownloadProgress){if("function"!=typeof this._options.onDownloadProgress)throw new TypeError("The `onDownloadProgress` option must be a function");if(!s)throw new Error("Streams are not supported in your environment. `ReadableStream` is missing.");return this._stream(t.clone(),this._options.onDownloadProgress)}return t},l=this._options.retry.methods.includes(this.request.method.toLowerCase())?this._retry(o):o();for(const[e,t]of Object.entries(c))l[e]=async()=>{this.request.headers.set("accept",this.request.headers.get("accept")||t);const o=(await l).clone();if("json"===e){if(204===o.status)return"";if(r.parseJson)return r.parseJson(await o.text())}return o[e]()};return l}_calculateRetryDelay(e){if(this._retryCount++,this._retryCountthis._options.retry.maxRetryAfter?0:e}if(413===e.response.status)return 0}return.3*2**(this._retryCount-1)*1e3}return 0}_decorateResponse(e){return this._options.parseJson&&(e.json=async()=>this._options.parseJson(await e.text())),e}async _retry(e){try{return await e()}catch(t){const r=Math.min(this._calculateRetryDelay(t),g);if(0!==r&&this._retryCount>0){await y(r);for(const e of this._options.hooks.beforeRetry)if(await e({request:this.request,options:this._options,error:t,retryCount:this._retryCount})===f)return;return this._retry(e)}if(this._options.throwHttpErrors)throw t}}async _fetch(){for(const e of this._options.hooks.beforeRequest){const t=await e(this.request,this._options);if(t instanceof Request){this.request=t;break}if(t instanceof Response)return t}return!1===this._options.timeout?this._options.fetch(this.request.clone()):(e=this.request.clone(),t=this.abortController,r=this._options,new Promise(((o,n)=>{const s=setTimeout((()=>{t&&t.abort(),n(new h(e))}),r.timeout);r.fetch(e).then(o).catch(n).then((()=>{clearTimeout(s)}))})));var e,t,r}_stream(t,r){const o=Number(t.headers.get("content-length"))||0;let n=0;return new e.Response(new e.ReadableStream({start(e){const s=t.body.getReader();r&&r({percent:0,transferredBytes:0,totalBytes:o},new Uint8Array),async function t(){const{done:i,value:a}=await s.read();i?e.close():(r&&(n+=a.byteLength,r({percent:0===o?0:n/o,transferredBytes:n,totalBytes:o},a)),e.enqueue(a),t())}()}}))}}const v=(...e)=>{for(const t of e)if((!o(t)||Array.isArray(t))&&void 0!==t)throw new TypeError("The `options` argument must be an object");return u({},...e)},S=e=>{const t=(t,r)=>new w(t,v(e,r));for(const r of l)t[r]=(t,o)=>new w(t,v(e,o,{method:r}));return t.HTTPError=p,t.TimeoutError=h,t.create=e=>S(v(e)),t.extend=t=>S(v(e,t)),t.stop=f,t};return S()},e.exports=t()},711:(e,t,r)=>{"use strict";t=e.exports=I;const o=r(605),n=r(211),s=r(761),i=r(413),a=r(175),u=r(669),l=r(30),c=r(417),d=r(835);class f extends Error{constructor(e,t){super(e),Error.captureStackTrace(this,this.constructor),this.type=t}get name(){return this.constructor.name}get[Symbol.toStringTag](){return this.constructor.name}}class p extends f{constructor(e,t,r){super(e,t),r&&(this.code=this.errno=r.code,this.erroredSysCall=r.syscall)}}const h=Symbol.toStringTag,y=e=>"object"==typeof e&&"function"==typeof e.append&&"function"==typeof e.delete&&"function"==typeof e.get&&"function"==typeof e.getAll&&"function"==typeof e.has&&"function"==typeof e.set&&"function"==typeof e.sort&&"URLSearchParams"===e[h],b=e=>"object"==typeof e&&"function"==typeof e.arrayBuffer&&"string"==typeof e.type&&"function"==typeof e.stream&&"function"==typeof e.constructor&&/^(Blob|File)$/.test(e[h]);function m(e){return"object"==typeof e&&"function"==typeof e.append&&"function"==typeof e.set&&"function"==typeof e.get&&"function"==typeof e.getAll&&"function"==typeof e.delete&&"function"==typeof e.keys&&"function"==typeof e.values&&"function"==typeof e.entries&&"function"==typeof e.constructor&&"FormData"===e[h]}const _="\r\n",g="-".repeat(2),w=Buffer.byteLength(_),v=e=>`${g}${e}${g}${_.repeat(2)}`;function S(e,t,r){let o="";return o+=`${g}${e}\r\n`,o+=`Content-Disposition: form-data; name="${t}"`,b(r)&&(o+=`; filename="${r.name}"\r\n`,o+=`Content-Type: ${r.type||"application/octet-stream"}`),`${o}${_.repeat(2)}`}const T=Symbol("Body internals");class R{constructor(e,{size:t=0}={}){let r=null;null===e?e=null:y(e)?e=Buffer.from(e.toString()):b(e)||Buffer.isBuffer(e)||(u.types.isAnyArrayBuffer(e)?e=Buffer.from(e):ArrayBuffer.isView(e)?e=Buffer.from(e.buffer,e.byteOffset,e.byteLength):e instanceof i||(m(e)?(r=`NodeFetchFormDataBoundary${c.randomBytes(8).toString("hex")}`,e=i.Readable.from(async function*(e,t){for(const[r,o]of e)yield S(t,r,o),b(o)?yield*o.stream():yield o,yield _;yield v(t)}(e,r))):e=Buffer.from(String(e)))),this[T]={body:e,boundary:r,disturbed:!1,error:null},this.size=t,e instanceof i&&e.on("error",(e=>{const t=e instanceof f?e:new p(`Invalid response body while trying to fetch ${this.url}: ${e.message}`,"system",e);this[T].error=t}))}get body(){return this[T].body}get bodyUsed(){return this[T].disturbed}async arrayBuffer(){const{buffer:e,byteOffset:t,byteLength:r}=await q(this);return e.slice(t,t+r)}async blob(){const e=this.headers&&this.headers.get("content-type")||this[T].body&&this[T].body.type||"",t=await this.buffer();return new l([t],{type:e})}async json(){const e=await q(this);return JSON.parse(e.toString())}async text(){return(await q(this)).toString()}buffer(){return q(this)}}async function q(e){if(e[T].disturbed)throw new TypeError(`body used already for: ${e.url}`);if(e[T].disturbed=!0,e[T].error)throw e[T].error;let{body:t}=e;if(null===t)return Buffer.alloc(0);if(b(t)&&(t=t.stream()),Buffer.isBuffer(t))return t;if(!(t instanceof i))return Buffer.alloc(0);const r=[];let o=0;try{for await(const n of t){if(e.size>0&&o+n.length>e.size){const r=new p(`content size at ${e.url} over limit: ${e.size}`,"max-size");throw t.destroy(r),r}o+=n.length,r.push(n)}}catch(t){throw t instanceof f?t:new p(`Invalid response body while trying to fetch ${e.url}: ${t.message}`,"system",t)}if(!0!==t.readableEnded&&!0!==t._readableState.ended)throw new p(`Premature close of server response while trying to fetch ${e.url}`);try{return r.every((e=>"string"==typeof e))?Buffer.from(r.join("")):Buffer.concat(r,o)}catch(t){throw new p(`Could not create Buffer from response body for ${e.url}: ${t.message}`,"system",t)}}Object.defineProperties(R.prototype,{body:{enumerable:!0},bodyUsed:{enumerable:!0},arrayBuffer:{enumerable:!0},blob:{enumerable:!0},json:{enumerable:!0},text:{enumerable:!0}});const P=(e,t)=>{let r,o,{body:n}=e;if(e.bodyUsed)throw new Error("cannot clone body after it is used");return n instanceof i&&"function"!=typeof n.getBoundary&&(r=new i.PassThrough({highWaterMark:t}),o=new i.PassThrough({highWaterMark:t}),n.pipe(r),n.pipe(o),e[T].body=r,n=o),n},E=(e,t)=>null===e?null:"string"==typeof e?"text/plain;charset=UTF-8":y(e)?"application/x-www-form-urlencoded;charset=UTF-8":b(e)?e.type||null:Buffer.isBuffer(e)||u.types.isAnyArrayBuffer(e)||ArrayBuffer.isView(e)?null:e&&"function"==typeof e.getBoundary?`multipart/form-data;boundary=${e.getBoundary()}`:m(e)?`multipart/form-data; boundary=${t[T].boundary}`:e instanceof i?null:"text/plain;charset=UTF-8",j="function"==typeof o.validateHeaderName?o.validateHeaderName:e=>{if(!/^[\^`\-\w!#$%&'*+.|~]+$/.test(e)){const t=new TypeError(`Header name must be a valid HTTP token [${e}]`);throw Object.defineProperty(t,"code",{value:"ERR_INVALID_HTTP_TOKEN"}),t}},k="function"==typeof o.validateHeaderValue?o.validateHeaderValue:(e,t)=>{if(/[^\t\u0020-\u007E\u0080-\u00FF]/.test(t)){const t=new TypeError(`Invalid character in header content ["${e}"]`);throw Object.defineProperty(t,"code",{value:"ERR_INVALID_CHAR"}),t}};class O extends URLSearchParams{constructor(e){let t=[];if(e instanceof O){const r=e.raw();for(const[e,o]of Object.entries(r))t.push(...o.map((t=>[e,t])))}else if(null==e);else{if("object"!=typeof e||u.types.isBoxedPrimitive(e))throw new TypeError("Failed to construct 'Headers': The provided value is not of type '(sequence> or record)");{const r=e[Symbol.iterator];if(null==r)t.push(...Object.entries(e));else{if("function"!=typeof r)throw new TypeError("Header pairs must be iterable");t=[...e].map((e=>{if("object"!=typeof e||u.types.isBoxedPrimitive(e))throw new TypeError("Each header pair must be an iterable object");return[...e]})).map((e=>{if(2!==e.length)throw new TypeError("Each header pair must be a name/value tuple");return[...e]}))}}}return t=t.length>0?t.map((([e,t])=>(j(e),k(e,String(t)),[String(e).toLowerCase(),String(t)]))):void 0,super(t),new Proxy(this,{get(e,t,r){switch(t){case"append":case"set":return(e,o)=>(j(e),k(e,String(o)),URLSearchParams.prototype[t].call(r,String(e).toLowerCase(),String(o)));case"delete":case"has":case"getAll":return e=>(j(e),URLSearchParams.prototype[t].call(r,String(e).toLowerCase()));case"keys":return()=>(e.sort(),new Set(URLSearchParams.prototype.keys.call(e)).keys());default:return Reflect.get(e,t,r)}}})}get[Symbol.toStringTag](){return this.constructor.name}toString(){return Object.prototype.toString.call(this)}get(e){const t=this.getAll(e);if(0===t.length)return null;let r=t.join(", ");return/^content-encoding$/i.test(e)&&(r=r.toLowerCase()),r}forEach(e){for(const t of this.keys())e(this.get(t),t)}*values(){for(const e of this.keys())yield this.get(e)}*entries(){for(const e of this.keys())yield[e,this.get(e)]}[Symbol.iterator](){return this.entries()}raw(){return[...this.keys()].reduce(((e,t)=>(e[t]=this.getAll(t),e)),{})}[Symbol.for("nodejs.util.inspect.custom")](){return[...this.keys()].reduce(((e,t)=>{const r=this.getAll(t);return e[t]="host"===t?r[0]:r.length>1?r:r[0],e}),{})}}Object.defineProperties(O.prototype,["get","entries","forEach","values"].reduce(((e,t)=>(e[t]={enumerable:!0},e)),{}));const C=new Set([301,302,303,307,308]),A=e=>C.has(e),B=Symbol("Response internals");class x extends R{constructor(e=null,t={}){super(e,t);const r=t.status||200,o=new O(t.headers);if(null!==e&&!o.has("Content-Type")){const t=E(e);t&&o.append("Content-Type",t)}this[B]={url:t.url,status:r,statusText:t.statusText||"",headers:o,counter:t.counter,highWaterMark:t.highWaterMark}}get url(){return this[B].url||""}get status(){return this[B].status}get ok(){return this[B].status>=200&&this[B].status<300}get redirected(){return this[B].counter>0}get statusText(){return this[B].statusText}get headers(){return this[B].headers}get highWaterMark(){return this[B].highWaterMark}clone(){return new x(P(this,this.highWaterMark),{url:this.url,status:this.status,statusText:this.statusText,headers:this.headers,ok:this.ok,redirected:this.redirected,size:this.size})}static redirect(e,t=302){if(!A(t))throw new RangeError('Failed to execute "redirect" on "response": Invalid status code');return new x(null,{headers:{location:new URL(e).toString()},status:t})}get[Symbol.toStringTag](){return"Response"}}Object.defineProperties(x.prototype,{url:{enumerable:!0},status:{enumerable:!0},ok:{enumerable:!0},redirected:{enumerable:!0},statusText:{enumerable:!0},headers:{enumerable:!0},clone:{enumerable:!0}});const W=Symbol("Request internals"),L=e=>"object"==typeof e&&"object"==typeof e[W];class z extends R{constructor(e,t={}){let r;L(e)?r=new URL(e.url):(r=new URL(e),e={});let o=t.method||e.method||"GET";if(o=o.toUpperCase(),(null!=t.body||L(e))&&null!==e.body&&("GET"===o||"HEAD"===o))throw new TypeError("Request with GET/HEAD method cannot have body");const n=t.body?t.body:L(e)&&null!==e.body?P(e):null;super(n,{size:t.size||e.size||0});const s=new O(t.headers||e.headers||{});if(null!==n&&!s.has("Content-Type")){const e=E(n,this);e&&s.append("Content-Type",e)}let i=L(e)?e.signal:null;if("signal"in t&&(i=t.signal),null!==i&&("object"!=typeof(a=i)||"AbortSignal"!==a[h]))throw new TypeError("Expected signal to be an instanceof AbortSignal");var a;this[W]={method:o,redirect:t.redirect||e.redirect||"follow",headers:s,parsedURL:r,signal:i},this.follow=void 0===t.follow?void 0===e.follow?20:e.follow:t.follow,this.compress=void 0===t.compress?void 0===e.compress||e.compress:t.compress,this.counter=t.counter||e.counter||0,this.agent=t.agent||e.agent,this.highWaterMark=t.highWaterMark||e.highWaterMark||16384,this.insecureHTTPParser=t.insecureHTTPParser||e.insecureHTTPParser||!1}get method(){return this[W].method}get url(){return d.format(this[W].parsedURL)}get headers(){return this[W].headers}get redirect(){return this[W].redirect}get signal(){return this[W].signal}clone(){return new z(this)}get[Symbol.toStringTag](){return"Request"}}Object.defineProperties(z.prototype,{method:{enumerable:!0},url:{enumerable:!0},headers:{enumerable:!0},redirect:{enumerable:!0},clone:{enumerable:!0},signal:{enumerable:!0}});class M extends f{constructor(e,t="aborted"){super(e,t)}}const $=new Set(["data:","http:","https:"]);async function I(e,t){return new Promise(((r,u)=>{const l=new z(e,t),c=(e=>{const{parsedURL:t}=e[W],r=new O(e[W].headers);r.has("Accept")||r.set("Accept","*/*");let o=null;if(null===e.body&&/^(post|put)$/i.test(e.method)&&(o="0"),null!==e.body){const t=(e=>{const{body:t}=e;return null===t?0:b(t)?t.size:Buffer.isBuffer(t)?t.length:t&&"function"==typeof t.getLengthSync?t.hasKnownLength&&t.hasKnownLength()?t.getLengthSync():null:m(t)?function(e,t){let r=0;for(const[o,n]of e)r+=Buffer.byteLength(S(t,o,n)),b(n)?r+=n.size:r+=Buffer.byteLength(String(n)),r+=w;return r+=Buffer.byteLength(v(t)),r}(e[T].boundary):null})(e);"number"!=typeof t||Number.isNaN(t)||(o=String(t))}o&&r.set("Content-Length",o),r.has("User-Agent")||r.set("User-Agent","node-fetch"),e.compress&&!r.has("Accept-Encoding")&&r.set("Accept-Encoding","gzip,deflate,br");let{agent:n}=e;"function"==typeof n&&(n=n(t)),r.has("Connection")||n||r.set("Connection","close");const s=(e=>{if(e.search)return e.search;const t=e.href.length-1,r=e.hash||("#"===e.href[t]?"#":"");return"?"===e.href[t-r.length]?"?":""})(t);return{path:t.pathname+s,pathname:t.pathname,hostname:t.hostname,protocol:t.protocol,port:t.port,hash:t.hash,search:t.search,query:t.query,href:t.href,method:e.method,headers:r[Symbol.for("nodejs.util.inspect.custom")](),insecureHTTPParser:e.insecureHTTPParser,agent:n}})(l);if(!$.has(c.protocol))throw new TypeError(`node-fetch cannot load ${e}. URL scheme "${c.protocol.replace(/:$/,"")}" is not supported.`);if("data:"===c.protocol){const e=a(l.url),t=new x(e,{headers:{"Content-Type":e.typeFull}});return void r(t)}const d=("https:"===c.protocol?n:o).request,{signal:f}=l;let h=null;const y=()=>{const e=new M("The operation was aborted.");u(e),l.body&&l.body instanceof i.Readable&&l.body.destroy(e),h&&h.body&&h.body.emit("error",e)};if(f&&f.aborted)return void y();const _=()=>{y(),R()},g=d(c);f&&f.addEventListener("abort",_);const R=()=>{g.abort(),f&&f.removeEventListener("abort",_)};g.on("error",(e=>{u(new p(`request to ${l.url} failed, reason: ${e.message}`,"system",e)),R()})),g.on("response",(e=>{g.setTimeout(0);const o=function(e=[]){return new O(e.reduce(((e,t,r,o)=>(r%2==0&&e.push(o.slice(r,r+2)),e)),[]).filter((([e,t])=>{try{return j(e),k(e,String(t)),!0}catch{return!1}})))}(e.rawHeaders);if(A(e.statusCode)){const n=o.get("Location"),s=null===n?null:new URL(n,l.url);switch(l.redirect){case"error":return u(new p(`uri requested responds with a redirect, redirect mode is set to error: ${l.url}`,"no-redirect")),void R();case"manual":if(null!==s)try{o.set("Location",s)}catch(e){u(e)}break;case"follow":{if(null===s)break;if(l.counter>=l.follow)return u(new p(`maximum redirect reached at: ${l.url}`,"max-redirect")),void R();const o={headers:new O(l.headers),follow:l.follow,counter:l.counter+1,agent:l.agent,compress:l.compress,method:l.method,body:l.body,signal:l.signal,size:l.size};return 303!==e.statusCode&&l.body&&t.body instanceof i.Readable?(u(new p("Cannot follow redirect with body being a readable stream","unsupported-redirect")),void R()):(303!==e.statusCode&&(301!==e.statusCode&&302!==e.statusCode||"POST"!==l.method)||(o.method="GET",o.body=void 0,o.headers.delete("content-length")),r(I(new z(s,o))),void R())}}}e.once("end",(()=>{f&&f.removeEventListener("abort",_)}));let n=i.pipeline(e,new i.PassThrough,(e=>{u(e)}));process.version<"v12.10"&&e.on("aborted",_);const a={url:l.url,status:e.statusCode,statusText:e.statusMessage,headers:o,size:l.size,counter:l.counter,highWaterMark:l.highWaterMark},c=o.get("Content-Encoding");if(!l.compress||"HEAD"===l.method||null===c||204===e.statusCode||304===e.statusCode)return h=new x(n,a),void r(h);const d={flush:s.Z_SYNC_FLUSH,finishFlush:s.Z_SYNC_FLUSH};if("gzip"===c||"x-gzip"===c)return n=i.pipeline(n,s.createGunzip(d),(e=>{u(e)})),h=new x(n,a),void r(h);if("deflate"!==c&&"x-deflate"!==c){if("br"===c)return n=i.pipeline(n,s.createBrotliDecompress(),(e=>{u(e)})),h=new x(n,a),void r(h);h=new x(n,a),r(h)}else i.pipeline(e,new i.PassThrough,(e=>{u(e)})).once("data",(e=>{n=8==(15&e[0])?i.pipeline(n,s.createInflate(),(e=>{u(e)})):i.pipeline(n,s.createInflateRaw(),(e=>{u(e)})),h=new x(n,a),r(h)}))})),((e,{body:t})=>{null===t?e.end():b(t)?t.stream().pipe(e):Buffer.isBuffer(t)?(e.write(t),e.end()):t.pipe(e)})(g,l)}))}t.AbortError=M,t.FetchError=p,t.Headers=O,t.Request=z,t.Response=x,t.default=I,t.isRedirect=A},78:e=>{function t(e){return e.replace(/[\/]+/g,"/").replace(/\/\?/g,"?").replace(/\/\#/g,"#").replace(/\:\//g,"://")}e.exports=function(){var e=[].slice.call(arguments,0).join("/");return t(e)}},377:(e,t,r)=>{"use strict";r.r(t),r.d(t,{ByteLengthQueuingStrategy:()=>pr,CountQueuingStrategy:()=>mr,ReadableByteStreamController:()=>ye,ReadableStream:()=>rr,ReadableStreamBYOBReader:()=>ze,ReadableStreamBYOBRequest:()=>he,ReadableStreamDefaultController:()=>Mt,ReadableStreamDefaultReader:()=>X,TransformStream:()=>Tr,TransformStreamDefaultController:()=>jr,WritableStream:()=>Ge,WritableStreamDefaultController:()=>yt,WritableStreamDefaultWriter:()=>ut});const o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol:e=>`Symbol(${e})`;function n(){}const s="undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:void 0;function i(e){return"object"==typeof e&&null!==e||"function"==typeof e}const a=n,u=Promise,l=Promise.prototype.then,c=Promise.resolve.bind(u),d=Promise.reject.bind(u);function f(e){return new u(e)}function p(e){return c(e)}function h(e){return d(e)}function y(e,t,r){return l.call(e,t,r)}function b(e,t,r){y(y(e,t,r),void 0,a)}function m(e,t){b(e,t)}function _(e,t){b(e,void 0,t)}function g(e,t,r){return y(e,t,r)}function w(e){y(e,void 0,a)}const v=(()=>{const e=s&&s.queueMicrotask;if("function"==typeof e)return e;const t=p(void 0);return e=>y(t,e)})();function S(e,t,r){if("function"!=typeof e)throw new TypeError("Argument is not a function");return Function.prototype.apply.call(e,t,r)}function T(e,t,r){try{return p(S(e,t,r))}catch(e){return h(e)}}class R{constructor(){this._cursor=0,this._size=0,this._front={_elements:[],_next:void 0},this._back=this._front,this._cursor=0,this._size=0}get length(){return this._size}push(e){const t=this._back;let r=t;16383===t._elements.length&&(r={_elements:[],_next:void 0}),t._elements.push(e),r!==t&&(this._back=r,t._next=r),++this._size}shift(){const e=this._front;let t=e;const r=this._cursor;let o=r+1;const n=e._elements,s=n[r];return 16384===o&&(t=e._next,o=0),--this._size,this._cursor=o,e!==t&&(this._front=t),n[r]=void 0,s}forEach(e){let t=this._cursor,r=this._front,o=r._elements;for(;!(t===o.length&&void 0===r._next||t===o.length&&(r=r._next,o=r._elements,t=0,0===o.length));)e(o[t]),++t}peek(){const e=this._front,t=this._cursor;return e._elements[t]}}function q(e,t){e._ownerReadableStream=t,t._reader=e,"readable"===t._state?k(e):"closed"===t._state?function(e){k(e),A(e)}(e):O(e,t._storedError)}function P(e,t){return ar(e._ownerReadableStream,t)}function E(e){"readable"===e._ownerReadableStream._state?C(e,new TypeError("Reader was released and can no longer be used to monitor the stream's closedness")):function(e,t){O(e,new TypeError("Reader was released and can no longer be used to monitor the stream's closedness"))}(e),e._ownerReadableStream._reader=void 0,e._ownerReadableStream=void 0}function j(e){return new TypeError("Cannot "+e+" a stream using a released reader")}function k(e){e._closedPromise=f(((t,r)=>{e._closedPromise_resolve=t,e._closedPromise_reject=r}))}function O(e,t){k(e),C(e,t)}function C(e,t){void 0!==e._closedPromise_reject&&(w(e._closedPromise),e._closedPromise_reject(t),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0)}function A(e){void 0!==e._closedPromise_resolve&&(e._closedPromise_resolve(void 0),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0)}const B=o("[[AbortSteps]]"),x=o("[[ErrorSteps]]"),W=o("[[CancelSteps]]"),L=o("[[PullSteps]]"),z=Number.isFinite||function(e){return"number"==typeof e&&isFinite(e)},M=Math.trunc||function(e){return e<0?Math.ceil(e):Math.floor(e)};function $(e,t){if(void 0!==e&&"object"!=typeof(r=e)&&"function"!=typeof r)throw new TypeError(`${t} is not an object.`);var r}function I(e,t){if("function"!=typeof e)throw new TypeError(`${t} is not a function.`)}function D(e,t){if(!function(e){return"object"==typeof e&&null!==e||"function"==typeof e}(e))throw new TypeError(`${t} is not an object.`)}function F(e,t,r){if(void 0===e)throw new TypeError(`Parameter ${t} is required in '${r}'.`)}function U(e,t,r){if(void 0===e)throw new TypeError(`${t} is required in '${r}'.`)}function H(e){return Number(e)}function N(e){return 0===e?0:e}function V(e,t){const r=Number.MAX_SAFE_INTEGER;let o=Number(e);if(o=N(o),!z(o))throw new TypeError(`${t} is not a finite number`);if(o=function(e){return N(M(e))}(o),o<0||o>r)throw new TypeError(`${t} is outside the accepted range of 0 to ${r}, inclusive`);return z(o)&&0!==o?o:0}function Q(e,t){if(!sr(e))throw new TypeError(`${t} is not a ReadableStream.`)}function Y(e){return new X(e)}function G(e,t){e._reader._readRequests.push(t)}function J(e,t,r){const o=e._reader._readRequests.shift();r?o._closeSteps():o._chunkSteps(t)}function K(e){return e._reader._readRequests.length}function Z(e){const t=e._reader;return void 0!==t&&!!ee(t)}class X{constructor(e){if(F(e,1,"ReadableStreamDefaultReader"),Q(e,"First parameter"),ir(e))throw new TypeError("This stream has already been locked for exclusive reading by another reader");q(this,e),this._readRequests=new R}get closed(){return ee(this)?this._closedPromise:h(re("closed"))}cancel(e){return ee(this)?void 0===this._ownerReadableStream?h(j("cancel")):P(this,e):h(re("cancel"))}read(){if(!ee(this))return h(re("read"));if(void 0===this._ownerReadableStream)return h(j("read from"));let e,t;const r=f(((r,o)=>{e=r,t=o}));return te(this,{_chunkSteps:t=>e({value:t,done:!1}),_closeSteps:()=>e({value:void 0,done:!0}),_errorSteps:e=>t(e)}),r}releaseLock(){if(!ee(this))throw re("releaseLock");if(void 0!==this._ownerReadableStream){if(this._readRequests.length>0)throw new TypeError("Tried to release a reader lock when that reader has pending read() calls un-settled");E(this)}}}function ee(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_readRequests")}function te(e,t){const r=e._ownerReadableStream;r._disturbed=!0,"closed"===r._state?t._closeSteps():"errored"===r._state?t._errorSteps(r._storedError):r._readableStreamController[L](t)}function re(e){return new TypeError(`ReadableStreamDefaultReader.prototype.${e} can only be used on a ReadableStreamDefaultReader`)}Object.defineProperties(X.prototype,{cancel:{enumerable:!0},read:{enumerable:!0},releaseLock:{enumerable:!0},closed:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(X.prototype,o.toStringTag,{value:"ReadableStreamDefaultReader",configurable:!0});const oe=Object.getPrototypeOf(Object.getPrototypeOf((async function*(){})).prototype);class ne{constructor(e,t){this._ongoingPromise=void 0,this._isFinished=!1,this._reader=e,this._preventCancel=t}next(){const e=()=>this._nextSteps();return this._ongoingPromise=this._ongoingPromise?g(this._ongoingPromise,e,e):e(),this._ongoingPromise}return(e){const t=()=>this._returnSteps(e);return this._ongoingPromise?g(this._ongoingPromise,t,t):t()}_nextSteps(){if(this._isFinished)return Promise.resolve({value:void 0,done:!0});const e=this._reader;if(void 0===e._ownerReadableStream)return h(j("iterate"));let t,r;const o=f(((e,o)=>{t=e,r=o}));return te(e,{_chunkSteps:e=>{this._ongoingPromise=void 0,v((()=>t({value:e,done:!1})))},_closeSteps:()=>{this._ongoingPromise=void 0,this._isFinished=!0,E(e),t({value:void 0,done:!0})},_errorSteps:t=>{this._ongoingPromise=void 0,this._isFinished=!0,E(e),r(t)}}),o}_returnSteps(e){if(this._isFinished)return Promise.resolve({value:e,done:!0});this._isFinished=!0;const t=this._reader;if(void 0===t._ownerReadableStream)return h(j("finish iterating"));if(!this._preventCancel){const r=P(t,e);return E(t),g(r,(()=>({value:e,done:!0})))}return E(t),p({value:e,done:!0})}}const se={next(){return ie(this)?this._asyncIteratorImpl.next():h(ae("next"))},return(e){return ie(this)?this._asyncIteratorImpl.return(e):h(ae("return"))}};function ie(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_asyncIteratorImpl")}function ae(e){return new TypeError(`ReadableStreamAsyncIterator.${e} can only be used on a ReadableSteamAsyncIterator`)}void 0!==oe&&Object.setPrototypeOf(se,oe);const ue=Number.isNaN||function(e){return e!=e};function le(e){return!!function(e){return"number"==typeof e&&(!ue(e)&&!(e<0))}(e)&&e!==1/0}function ce(e){const t=e._queue.shift();return e._queueTotalSize-=t.size,e._queueTotalSize<0&&(e._queueTotalSize=0),t.value}function de(e,t,r){if(!le(r=Number(r)))throw new RangeError("Size must be a finite, non-NaN, non-negative number.");e._queue.push({value:t,size:r}),e._queueTotalSize+=r}function fe(e){e._queue=new R,e._queueTotalSize=0}function pe(e){return e.slice()}class he{constructor(){throw new TypeError("Illegal constructor")}get view(){if(!me(this))throw Ae("view");return this._view}respond(e){if(!me(this))throw Ae("respond");if(F(e,1,"respond"),e=V(e,"First parameter"),void 0===this._associatedReadableByteStreamController)throw new TypeError("This BYOB request has been invalidated");this._view.buffer,function(e,t){if(!le(t=Number(t)))throw new RangeError("bytesWritten must be a finite");Ee(e,t)}(this._associatedReadableByteStreamController,e)}respondWithNewView(e){if(!me(this))throw Ae("respondWithNewView");if(F(e,1,"respondWithNewView"),!ArrayBuffer.isView(e))throw new TypeError("You can only respond with array buffer views");if(0===e.byteLength)throw new TypeError("chunk must have non-zero byteLength");if(0===e.buffer.byteLength)throw new TypeError("chunk's buffer must have non-zero byteLength");if(void 0===this._associatedReadableByteStreamController)throw new TypeError("This BYOB request has been invalidated");!function(e,t){const r=e._pendingPullIntos.peek();if(r.byteOffset+r.bytesFilled!==t.byteOffset)throw new RangeError("The region specified by view does not match byobRequest");if(r.byteLength!==t.byteLength)throw new RangeError("The buffer of view has different capacity than byobRequest");r.buffer=t.buffer,Ee(e,t.byteLength)}(this._associatedReadableByteStreamController,e)}}Object.defineProperties(he.prototype,{respond:{enumerable:!0},respondWithNewView:{enumerable:!0},view:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(he.prototype,o.toStringTag,{value:"ReadableStreamBYOBRequest",configurable:!0});class ye{constructor(){throw new TypeError("Illegal constructor")}get byobRequest(){if(!be(this))throw Be("byobRequest");if(null===this._byobRequest&&this._pendingPullIntos.length>0){const e=this._pendingPullIntos.peek(),t=new Uint8Array(e.buffer,e.byteOffset+e.bytesFilled,e.byteLength-e.bytesFilled),r=Object.create(he.prototype);!function(e,t,r){e._associatedReadableByteStreamController=t,e._view=r}(r,this,t),this._byobRequest=r}return this._byobRequest}get desiredSize(){if(!be(this))throw Be("desiredSize");return Ce(this)}close(){if(!be(this))throw Be("close");if(this._closeRequested)throw new TypeError("The stream has already been closed; do not close it again!");const e=this._controlledReadableByteStream._state;if("readable"!==e)throw new TypeError(`The stream (in ${e} state) is not in the readable state and cannot be closed`);!function(e){const t=e._controlledReadableByteStream;if(!e._closeRequested&&"readable"===t._state)if(e._queueTotalSize>0)e._closeRequested=!0;else{if(e._pendingPullIntos.length>0&&e._pendingPullIntos.peek().bytesFilled>0){const t=new TypeError("Insufficient bytes to fill elements in the given buffer");throw Oe(e,t),t}ke(e),ur(t)}}(this)}enqueue(e){if(!be(this))throw Be("enqueue");if(F(e,1,"enqueue"),!ArrayBuffer.isView(e))throw new TypeError("chunk must be an array buffer view");if(0===e.byteLength)throw new TypeError("chunk must have non-zero byteLength");if(0===e.buffer.byteLength)throw new TypeError("chunk's buffer must have non-zero byteLength");if(this._closeRequested)throw new TypeError("stream is closed or draining");const t=this._controlledReadableByteStream._state;if("readable"!==t)throw new TypeError(`The stream (in ${t} state) is not in the readable state and cannot be enqueued to`);!function(e,t){const r=e._controlledReadableByteStream;if(e._closeRequested||"readable"!==r._state)return;const o=t.buffer,n=t.byteOffset,s=t.byteLength,i=o;Z(r)?0===K(r)?ve(e,i,n,s):J(r,new Uint8Array(i,n,s),!1):Le(r)?(ve(e,i,n,s),Pe(e)):ve(e,i,n,s),_e(e)}(this,e)}error(e){if(!be(this))throw Be("error");Oe(this,e)}[W](e){this._pendingPullIntos.length>0&&(this._pendingPullIntos.peek().bytesFilled=0),fe(this);const t=this._cancelAlgorithm(e);return ke(this),t}[L](e){const t=this._controlledReadableByteStream;if(this._queueTotalSize>0){const t=this._queue.shift();this._queueTotalSize-=t.byteLength,Re(this);const r=new Uint8Array(t.buffer,t.byteOffset,t.byteLength);return void e._chunkSteps(r)}const r=this._autoAllocateChunkSize;if(void 0!==r){let t;try{t=new ArrayBuffer(r)}catch(t){return void e._errorSteps(t)}const o={buffer:t,byteOffset:0,byteLength:r,bytesFilled:0,elementSize:1,viewConstructor:Uint8Array,readerType:"default"};this._pendingPullIntos.push(o)}G(t,e),_e(this)}}function be(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_controlledReadableByteStream")}function me(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_associatedReadableByteStreamController")}function _e(e){(function(e){const t=e._controlledReadableByteStream;return"readable"===t._state&&(!e._closeRequested&&(!!e._started&&(!!(Z(t)&&K(t)>0)||(!!(Le(t)&&We(t)>0)||Ce(e)>0))))})(e)&&(e._pulling?e._pullAgain=!0:(e._pulling=!0,b(e._pullAlgorithm(),(()=>{e._pulling=!1,e._pullAgain&&(e._pullAgain=!1,_e(e))}),(t=>{Oe(e,t)}))))}function ge(e,t){let r=!1;"closed"===e._state&&(r=!0);const o=we(t);"default"===t.readerType?J(e,o,r):function(e,t,r){const o=e._reader._readIntoRequests.shift();r?o._closeSteps(t):o._chunkSteps(t)}(e,o,r)}function we(e){const t=e.bytesFilled,r=e.elementSize;return new e.viewConstructor(e.buffer,e.byteOffset,t/r)}function ve(e,t,r,o){e._queue.push({buffer:t,byteOffset:r,byteLength:o}),e._queueTotalSize+=o}function Se(e,t){const r=t.elementSize,o=t.bytesFilled-t.bytesFilled%r,n=Math.min(e._queueTotalSize,t.byteLength-t.bytesFilled),s=t.bytesFilled+n,i=s-s%r;let a=n,u=!1;i>o&&(a=i-t.bytesFilled,u=!0);const l=e._queue;for(;a>0;){const r=l.peek(),o=Math.min(a,r.byteLength),n=t.byteOffset+t.bytesFilled;c=t.buffer,d=n,f=r.buffer,p=r.byteOffset,h=o,new Uint8Array(c).set(new Uint8Array(f,p,h),d),r.byteLength===o?l.shift():(r.byteOffset+=o,r.byteLength-=o),e._queueTotalSize-=o,Te(e,o,t),a-=o}var c,d,f,p,h;return u}function Te(e,t,r){qe(e),r.bytesFilled+=t}function Re(e){0===e._queueTotalSize&&e._closeRequested?(ke(e),ur(e._controlledReadableByteStream)):_e(e)}function qe(e){null!==e._byobRequest&&(e._byobRequest._associatedReadableByteStreamController=void 0,e._byobRequest._view=null,e._byobRequest=null)}function Pe(e){for(;e._pendingPullIntos.length>0;){if(0===e._queueTotalSize)return;const t=e._pendingPullIntos.peek();Se(e,t)&&(je(e),ge(e._controlledReadableByteStream,t))}}function Ee(e,t){const r=e._pendingPullIntos.peek();if("closed"===e._controlledReadableByteStream._state){if(0!==t)throw new TypeError("bytesWritten must be 0 when calling respond() on a closed stream");!function(e,t){t.buffer=t.buffer;const r=e._controlledReadableByteStream;if(Le(r))for(;We(r)>0;)ge(r,je(e))}(e,r)}else!function(e,t,r){if(r.bytesFilled+t>r.byteLength)throw new RangeError("bytesWritten out of range");if(Te(e,t,r),r.bytesFilled0){const t=r.byteOffset+r.bytesFilled,n=r.buffer.slice(t-o,t);ve(e,n,0,n.byteLength)}r.buffer=r.buffer,r.bytesFilled-=o,ge(e._controlledReadableByteStream,r),Pe(e)}(e,t,r);_e(e)}function je(e){const t=e._pendingPullIntos.shift();return qe(e),t}function ke(e){e._pullAlgorithm=void 0,e._cancelAlgorithm=void 0}function Oe(e,t){const r=e._controlledReadableByteStream;"readable"===r._state&&(function(e){qe(e),e._pendingPullIntos=new R}(e),fe(e),ke(e),lr(r,t))}function Ce(e){const t=e._controlledReadableByteStream._state;return"errored"===t?null:"closed"===t?0:e._strategyHWM-e._queueTotalSize}function Ae(e){return new TypeError(`ReadableStreamBYOBRequest.prototype.${e} can only be used on a ReadableStreamBYOBRequest`)}function Be(e){return new TypeError(`ReadableByteStreamController.prototype.${e} can only be used on a ReadableByteStreamController`)}function xe(e,t){e._reader._readIntoRequests.push(t)}function We(e){return e._reader._readIntoRequests.length}function Le(e){const t=e._reader;return void 0!==t&&!!Me(t)}Object.defineProperties(ye.prototype,{close:{enumerable:!0},enqueue:{enumerable:!0},error:{enumerable:!0},byobRequest:{enumerable:!0},desiredSize:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(ye.prototype,o.toStringTag,{value:"ReadableByteStreamController",configurable:!0});class ze{constructor(e){if(F(e,1,"ReadableStreamBYOBReader"),Q(e,"First parameter"),ir(e))throw new TypeError("This stream has already been locked for exclusive reading by another reader");if(!be(e._readableStreamController))throw new TypeError("Cannot construct a ReadableStreamBYOBReader for a stream not constructed with a byte source");q(this,e),this._readIntoRequests=new R}get closed(){return Me(this)?this._closedPromise:h($e("closed"))}cancel(e){return Me(this)?void 0===this._ownerReadableStream?h(j("cancel")):P(this,e):h($e("cancel"))}read(e){if(!Me(this))return h($e("read"));if(!ArrayBuffer.isView(e))return h(new TypeError("view must be an array buffer view"));if(0===e.byteLength)return h(new TypeError("view must have non-zero byteLength"));if(0===e.buffer.byteLength)return h(new TypeError("view's buffer must have non-zero byteLength"));if(void 0===this._ownerReadableStream)return h(j("read from"));let t,r;const o=f(((e,o)=>{t=e,r=o}));return function(e,t,r){const o=e._ownerReadableStream;o._disturbed=!0,"errored"===o._state?r._errorSteps(o._storedError):function(e,t,r){const o=e._controlledReadableByteStream;let n=1;t.constructor!==DataView&&(n=t.constructor.BYTES_PER_ELEMENT);const s=t.constructor,i={buffer:t.buffer,byteOffset:t.byteOffset,byteLength:t.byteLength,bytesFilled:0,elementSize:n,viewConstructor:s,readerType:"byob"};if(e._pendingPullIntos.length>0)return e._pendingPullIntos.push(i),void xe(o,r);if("closed"!==o._state){if(e._queueTotalSize>0){if(Se(e,i)){const t=we(i);return Re(e),void r._chunkSteps(t)}if(e._closeRequested){const t=new TypeError("Insufficient bytes to fill elements in the given buffer");return Oe(e,t),void r._errorSteps(t)}}e._pendingPullIntos.push(i),xe(o,r),_e(e)}else{const e=new s(i.buffer,i.byteOffset,0);r._closeSteps(e)}}(o._readableStreamController,t,r)}(this,e,{_chunkSteps:e=>t({value:e,done:!1}),_closeSteps:e=>t({value:e,done:!0}),_errorSteps:e=>r(e)}),o}releaseLock(){if(!Me(this))throw $e("releaseLock");if(void 0!==this._ownerReadableStream){if(this._readIntoRequests.length>0)throw new TypeError("Tried to release a reader lock when that reader has pending read() calls un-settled");E(this)}}}function Me(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_readIntoRequests")}function $e(e){return new TypeError(`ReadableStreamBYOBReader.prototype.${e} can only be used on a ReadableStreamBYOBReader`)}function Ie(e,t){const{highWaterMark:r}=e;if(void 0===r)return t;if(ue(r)||r<0)throw new RangeError("Invalid highWaterMark");return r}function De(e){const{size:t}=e;return t||(()=>1)}function Fe(e,t){$(e,t);const r=null==e?void 0:e.highWaterMark,o=null==e?void 0:e.size;return{highWaterMark:void 0===r?void 0:H(r),size:void 0===o?void 0:Ue(o,`${t} has member 'size' that`)}}function Ue(e,t){return I(e,t),t=>H(e(t))}function He(e,t,r){return I(e,r),r=>T(e,t,[r])}function Ne(e,t,r){return I(e,r),()=>T(e,t,[])}function Ve(e,t,r){return I(e,r),r=>S(e,t,[r])}function Qe(e,t,r){return I(e,r),(r,o)=>T(e,t,[r,o])}function Ye(e,t){if(!Ze(e))throw new TypeError(`${t} is not a WritableStream.`)}Object.defineProperties(ze.prototype,{cancel:{enumerable:!0},read:{enumerable:!0},releaseLock:{enumerable:!0},closed:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(ze.prototype,o.toStringTag,{value:"ReadableStreamBYOBReader",configurable:!0});class Ge{constructor(e={},t={}){void 0===e?e=null:D(e,"First parameter");const r=Fe(t,"Second parameter"),o=function(e,t){$(e,t);const r=null==e?void 0:e.abort,o=null==e?void 0:e.close,n=null==e?void 0:e.start,s=null==e?void 0:e.type,i=null==e?void 0:e.write;return{abort:void 0===r?void 0:He(r,e,`${t} has member 'abort' that`),close:void 0===o?void 0:Ne(o,e,`${t} has member 'close' that`),start:void 0===n?void 0:Ve(n,e,`${t} has member 'start' that`),write:void 0===i?void 0:Qe(i,e,`${t} has member 'write' that`),type:s}}(e,"First parameter");if(Ke(this),void 0!==o.type)throw new RangeError("Invalid type is specified");const n=De(r);!function(e,t,r,o){const n=Object.create(yt.prototype);let s=()=>{},i=()=>p(void 0),a=()=>p(void 0),u=()=>p(void 0);void 0!==t.start&&(s=()=>t.start(n)),void 0!==t.write&&(i=e=>t.write(e,n)),void 0!==t.close&&(a=()=>t.close()),void 0!==t.abort&&(u=e=>t.abort(e)),bt(e,n,s,i,a,u,r,o)}(this,o,Ie(r,1),n)}get locked(){if(!Ze(this))throw Tt("locked");return Xe(this)}abort(e){return Ze(this)?Xe(this)?h(new TypeError("Cannot abort a stream that already has a writer")):et(this,e):h(Tt("abort"))}close(){return Ze(this)?Xe(this)?h(new TypeError("Cannot close a stream that already has a writer")):st(this)?h(new TypeError("Cannot close an already-closing stream")):tt(this):h(Tt("close"))}getWriter(){if(!Ze(this))throw Tt("getWriter");return Je(this)}}function Je(e){return new ut(e)}function Ke(e){e._state="writable",e._storedError=void 0,e._writer=void 0,e._writableStreamController=void 0,e._writeRequests=new R,e._inFlightWriteRequest=void 0,e._closeRequest=void 0,e._inFlightCloseRequest=void 0,e._pendingAbortRequest=void 0,e._backpressure=!1}function Ze(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_writableStreamController")}function Xe(e){return void 0!==e._writer}function et(e,t){const r=e._state;if("closed"===r||"errored"===r)return p(void 0);if(void 0!==e._pendingAbortRequest)return e._pendingAbortRequest._promise;let o=!1;"erroring"===r&&(o=!0,t=void 0);const n=f(((r,n)=>{e._pendingAbortRequest={_promise:void 0,_resolve:r,_reject:n,_reason:t,_wasAlreadyErroring:o}}));return e._pendingAbortRequest._promise=n,o||ot(e,t),n}function tt(e){const t=e._state;if("closed"===t||"errored"===t)return h(new TypeError(`The stream (in ${t} state) is not in the writable state and cannot be closed`));const r=f(((t,r)=>{const o={_resolve:t,_reject:r};e._closeRequest=o})),o=e._writer;var n;return void 0!==o&&e._backpressure&&"writable"===t&&xt(o),de(n=e._writableStreamController,ht,0),gt(n),r}function rt(e,t){"writable"!==e._state?nt(e):ot(e,t)}function ot(e,t){const r=e._writableStreamController;e._state="erroring",e._storedError=t;const o=e._writer;void 0!==o&&dt(o,t),!function(e){return void 0!==e._inFlightWriteRequest||void 0!==e._inFlightCloseRequest}(e)&&r._started&&nt(e)}function nt(e){e._state="errored",e._writableStreamController[x]();const t=e._storedError;if(e._writeRequests.forEach((e=>{e._reject(t)})),e._writeRequests=new R,void 0===e._pendingAbortRequest)return void it(e);const r=e._pendingAbortRequest;if(e._pendingAbortRequest=void 0,r._wasAlreadyErroring)return r._reject(t),void it(e);b(e._writableStreamController[B](r._reason),(()=>{r._resolve(),it(e)}),(t=>{r._reject(t),it(e)}))}function st(e){return void 0!==e._closeRequest||void 0!==e._inFlightCloseRequest}function it(e){void 0!==e._closeRequest&&(e._closeRequest._reject(e._storedError),e._closeRequest=void 0);const t=e._writer;void 0!==t&&jt(t,e._storedError)}function at(e,t){const r=e._writer;void 0!==r&&t!==e._backpressure&&(t?function(e){Ot(e)}(r):xt(r)),e._backpressure=t}Object.defineProperties(Ge.prototype,{abort:{enumerable:!0},close:{enumerable:!0},getWriter:{enumerable:!0},locked:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(Ge.prototype,o.toStringTag,{value:"WritableStream",configurable:!0});class ut{constructor(e){if(F(e,1,"WritableStreamDefaultWriter"),Ye(e,"First parameter"),Xe(e))throw new TypeError("This stream has already been locked for exclusive writing by another writer");this._ownerWritableStream=e,e._writer=this;const t=e._state;if("writable"===t)!st(e)&&e._backpressure?Ot(this):At(this),Pt(this);else if("erroring"===t)Ct(this,e._storedError),Pt(this);else if("closed"===t)At(this),Pt(this),kt(this);else{const t=e._storedError;Ct(this,t),Et(this,t)}}get closed(){return lt(this)?this._closedPromise:h(Rt("closed"))}get desiredSize(){if(!lt(this))throw Rt("desiredSize");if(void 0===this._ownerWritableStream)throw qt("desiredSize");return function(e){const t=e._ownerWritableStream,r=t._state;return"errored"===r||"erroring"===r?null:"closed"===r?0:_t(t._writableStreamController)}(this)}get ready(){return lt(this)?this._readyPromise:h(Rt("ready"))}abort(e){return lt(this)?void 0===this._ownerWritableStream?h(qt("abort")):function(e,t){return et(e._ownerWritableStream,t)}(this,e):h(Rt("abort"))}close(){if(!lt(this))return h(Rt("close"));const e=this._ownerWritableStream;return void 0===e?h(qt("close")):st(e)?h(new TypeError("Cannot close an already-closing stream")):ct(this)}releaseLock(){if(!lt(this))throw Rt("releaseLock");void 0!==this._ownerWritableStream&&ft(this)}write(e){return lt(this)?void 0===this._ownerWritableStream?h(qt("write to")):pt(this,e):h(Rt("write"))}}function lt(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_ownerWritableStream")}function ct(e){return tt(e._ownerWritableStream)}function dt(e,t){"pending"===e._readyPromiseState?Bt(e,t):function(e,t){Ct(e,t)}(e,t)}function ft(e){const t=e._ownerWritableStream,r=new TypeError("Writer was released and can no longer be used to monitor the stream's closedness");dt(e,r),function(e,t){"pending"===e._closedPromiseState?jt(e,t):function(e,t){Et(e,t)}(e,t)}(e,r),t._writer=void 0,e._ownerWritableStream=void 0}function pt(e,t){const r=e._ownerWritableStream,o=r._writableStreamController,n=function(e,t){try{return e._strategySizeAlgorithm(t)}catch(t){return wt(e,t),1}}(o,t);if(r!==e._ownerWritableStream)return h(qt("write to"));const s=r._state;if("errored"===s)return h(r._storedError);if(st(r)||"closed"===s)return h(new TypeError("The stream is closing or closed and cannot be written to"));if("erroring"===s)return h(r._storedError);const i=function(e){return f(((t,r)=>{const o={_resolve:t,_reject:r};e._writeRequests.push(o)}))}(r);return function(e,t,r){try{de(e,t,r)}catch(t){return void wt(e,t)}const o=e._controlledWritableStream;st(o)||"writable"!==o._state||at(o,vt(e)),gt(e)}(o,t,n),i}Object.defineProperties(ut.prototype,{abort:{enumerable:!0},close:{enumerable:!0},releaseLock:{enumerable:!0},write:{enumerable:!0},closed:{enumerable:!0},desiredSize:{enumerable:!0},ready:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(ut.prototype,o.toStringTag,{value:"WritableStreamDefaultWriter",configurable:!0});const ht={};class yt{constructor(){throw new TypeError("Illegal constructor")}error(e){if(!i(t=this)||!Object.prototype.hasOwnProperty.call(t,"_controlledWritableStream"))throw new TypeError("WritableStreamDefaultController.prototype.error can only be used on a WritableStreamDefaultController");var t;"writable"===this._controlledWritableStream._state&&St(this,e)}[B](e){const t=this._abortAlgorithm(e);return mt(this),t}[x](){fe(this)}}function bt(e,t,r,o,n,s,i,a){t._controlledWritableStream=e,e._writableStreamController=t,t._queue=void 0,t._queueTotalSize=void 0,fe(t),t._started=!1,t._strategySizeAlgorithm=a,t._strategyHWM=i,t._writeAlgorithm=o,t._closeAlgorithm=n,t._abortAlgorithm=s;const u=vt(t);at(e,u),b(p(r()),(()=>{t._started=!0,gt(t)}),(r=>{t._started=!0,rt(e,r)}))}function mt(e){e._writeAlgorithm=void 0,e._closeAlgorithm=void 0,e._abortAlgorithm=void 0,e._strategySizeAlgorithm=void 0}function _t(e){return e._strategyHWM-e._queueTotalSize}function gt(e){const t=e._controlledWritableStream;if(!e._started)return;if(void 0!==t._inFlightWriteRequest)return;if("erroring"===t._state)return void nt(t);if(0===e._queue.length)return;const r=e._queue.peek().value;r===ht?function(e){const t=e._controlledWritableStream;(function(e){e._inFlightCloseRequest=e._closeRequest,e._closeRequest=void 0})(t),ce(e);const r=e._closeAlgorithm();mt(e),b(r,(()=>{!function(e){e._inFlightCloseRequest._resolve(void 0),e._inFlightCloseRequest=void 0,"erroring"===e._state&&(e._storedError=void 0,void 0!==e._pendingAbortRequest&&(e._pendingAbortRequest._resolve(),e._pendingAbortRequest=void 0)),e._state="closed";const t=e._writer;void 0!==t&&kt(t)}(t)}),(e=>{!function(e,t){e._inFlightCloseRequest._reject(t),e._inFlightCloseRequest=void 0,void 0!==e._pendingAbortRequest&&(e._pendingAbortRequest._reject(t),e._pendingAbortRequest=void 0),rt(e,t)}(t,e)}))}(e):function(e,t){const r=e._controlledWritableStream;!function(e){e._inFlightWriteRequest=e._writeRequests.shift()}(r),b(e._writeAlgorithm(t),(()=>{!function(e){e._inFlightWriteRequest._resolve(void 0),e._inFlightWriteRequest=void 0}(r);const t=r._state;if(ce(e),!st(r)&&"writable"===t){const t=vt(e);at(r,t)}gt(e)}),(t=>{"writable"===r._state&&mt(e),function(e,t){e._inFlightWriteRequest._reject(t),e._inFlightWriteRequest=void 0,rt(e,t)}(r,t)}))}(e,r)}function wt(e,t){"writable"===e._controlledWritableStream._state&&St(e,t)}function vt(e){return _t(e)<=0}function St(e,t){const r=e._controlledWritableStream;mt(e),ot(r,t)}function Tt(e){return new TypeError(`WritableStream.prototype.${e} can only be used on a WritableStream`)}function Rt(e){return new TypeError(`WritableStreamDefaultWriter.prototype.${e} can only be used on a WritableStreamDefaultWriter`)}function qt(e){return new TypeError("Cannot "+e+" a stream using a released writer")}function Pt(e){e._closedPromise=f(((t,r)=>{e._closedPromise_resolve=t,e._closedPromise_reject=r,e._closedPromiseState="pending"}))}function Et(e,t){Pt(e),jt(e,t)}function jt(e,t){void 0!==e._closedPromise_reject&&(w(e._closedPromise),e._closedPromise_reject(t),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0,e._closedPromiseState="rejected")}function kt(e){void 0!==e._closedPromise_resolve&&(e._closedPromise_resolve(void 0),e._closedPromise_resolve=void 0,e._closedPromise_reject=void 0,e._closedPromiseState="resolved")}function Ot(e){e._readyPromise=f(((t,r)=>{e._readyPromise_resolve=t,e._readyPromise_reject=r})),e._readyPromiseState="pending"}function Ct(e,t){Ot(e),Bt(e,t)}function At(e){Ot(e),xt(e)}function Bt(e,t){void 0!==e._readyPromise_reject&&(w(e._readyPromise),e._readyPromise_reject(t),e._readyPromise_resolve=void 0,e._readyPromise_reject=void 0,e._readyPromiseState="rejected")}function xt(e){void 0!==e._readyPromise_resolve&&(e._readyPromise_resolve(void 0),e._readyPromise_resolve=void 0,e._readyPromise_reject=void 0,e._readyPromiseState="fulfilled")}Object.defineProperties(yt.prototype,{error:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(yt.prototype,o.toStringTag,{value:"WritableStreamDefaultController",configurable:!0});const Wt="undefined"!=typeof DOMException?DOMException:void 0,Lt=function(e){if("function"!=typeof e&&"object"!=typeof e)return!1;try{return new e,!0}catch(e){return!1}}(Wt)?Wt:function(){const e=function(e,t){this.message=e||"",this.name=t||"Error",Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)};return e.prototype=Object.create(Error.prototype),Object.defineProperty(e.prototype,"constructor",{value:e,writable:!0,configurable:!0}),e}();function zt(e,t,r,o,s,i){const a=Y(e),u=Je(t);e._disturbed=!0;let l=!1,c=p(void 0);return f(((d,g)=>{let v;if(void 0!==i){if(v=()=>{const r=new Lt("Aborted","AbortError"),n=[];o||n.push((()=>"writable"===t._state?et(t,r):p(void 0))),s||n.push((()=>"readable"===e._state?ar(e,r):p(void 0))),j((()=>Promise.all(n.map((e=>e())))),!0,r)},i.aborted)return void v();i.addEventListener("abort",v)}var S,T,R;if(P(e,a._closedPromise,(e=>{o?k(!0,e):j((()=>et(t,e)),!0,e)})),P(t,u._closedPromise,(t=>{s?k(!0,t):j((()=>ar(e,t)),!0,t)})),S=e,T=a._closedPromise,R=()=>{r?k():j((()=>function(e){const t=e._ownerWritableStream,r=t._state;return st(t)||"closed"===r?p(void 0):"errored"===r?h(t._storedError):ct(e)}(u)))},"closed"===S._state?R():m(T,R),st(t)||"closed"===t._state){const t=new TypeError("the destination writable stream closed before all data could be piped to it");s?k(!0,t):j((()=>ar(e,t)),!0,t)}function q(){const e=c;return y(c,(()=>e!==c?q():void 0))}function P(e,t,r){"errored"===e._state?r(e._storedError):_(t,r)}function j(e,r,o){function n(){b(e(),(()=>O(r,o)),(e=>O(!0,e)))}l||(l=!0,"writable"!==t._state||st(t)?n():m(q(),n))}function k(e,r){l||(l=!0,"writable"!==t._state||st(t)?O(e,r):m(q(),(()=>O(e,r))))}function O(e,t){ft(u),E(a),void 0!==i&&i.removeEventListener("abort",v),e?g(t):d(void 0)}w(f(((e,t)=>{!function r(o){o?e():y(l?p(!0):y(u._readyPromise,(()=>f(((e,t)=>{te(a,{_chunkSteps:t=>{c=y(pt(u,t),void 0,n),e(!1)},_closeSteps:()=>e(!0),_errorSteps:t})})))),r,t)}(!1)})))}))}class Mt{constructor(){throw new TypeError("Illegal constructor")}get desiredSize(){if(!$t(this))throw Gt("desiredSize");return Vt(this)}close(){if(!$t(this))throw Gt("close");if(!Qt(this))throw new TypeError("The stream is not in a state that permits close");Ut(this)}enqueue(e){if(!$t(this))throw Gt("enqueue");if(!Qt(this))throw new TypeError("The stream is not in a state that permits enqueue");return Ht(this,e)}error(e){if(!$t(this))throw Gt("error");Nt(this,e)}[W](e){fe(this);const t=this._cancelAlgorithm(e);return Ft(this),t}[L](e){const t=this._controlledReadableStream;if(this._queue.length>0){const r=ce(this);this._closeRequested&&0===this._queue.length?(Ft(this),ur(t)):It(this),e._chunkSteps(r)}else G(t,e),It(this)}}function $t(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_controlledReadableStream")}function It(e){Dt(e)&&(e._pulling?e._pullAgain=!0:(e._pulling=!0,b(e._pullAlgorithm(),(()=>{e._pulling=!1,e._pullAgain&&(e._pullAgain=!1,It(e))}),(t=>{Nt(e,t)}))))}function Dt(e){const t=e._controlledReadableStream;return!!Qt(e)&&(!!e._started&&(!!(ir(t)&&K(t)>0)||Vt(e)>0))}function Ft(e){e._pullAlgorithm=void 0,e._cancelAlgorithm=void 0,e._strategySizeAlgorithm=void 0}function Ut(e){if(!Qt(e))return;const t=e._controlledReadableStream;e._closeRequested=!0,0===e._queue.length&&(Ft(e),ur(t))}function Ht(e,t){if(!Qt(e))return;const r=e._controlledReadableStream;if(ir(r)&&K(r)>0)J(r,t,!1);else{let r;try{r=e._strategySizeAlgorithm(t)}catch(t){throw Nt(e,t),t}try{de(e,t,r)}catch(t){throw Nt(e,t),t}}It(e)}function Nt(e,t){const r=e._controlledReadableStream;"readable"===r._state&&(fe(e),Ft(e),lr(r,t))}function Vt(e){const t=e._controlledReadableStream._state;return"errored"===t?null:"closed"===t?0:e._strategyHWM-e._queueTotalSize}function Qt(e){const t=e._controlledReadableStream._state;return!e._closeRequested&&"readable"===t}function Yt(e,t,r,o,n,s,i){t._controlledReadableStream=e,t._queue=void 0,t._queueTotalSize=void 0,fe(t),t._started=!1,t._closeRequested=!1,t._pullAgain=!1,t._pulling=!1,t._strategySizeAlgorithm=i,t._strategyHWM=s,t._pullAlgorithm=o,t._cancelAlgorithm=n,e._readableStreamController=t,b(p(r()),(()=>{t._started=!0,It(t)}),(e=>{Nt(t,e)}))}function Gt(e){return new TypeError(`ReadableStreamDefaultController.prototype.${e} can only be used on a ReadableStreamDefaultController`)}function Jt(e,t,r){return I(e,r),r=>T(e,t,[r])}function Kt(e,t,r){return I(e,r),r=>T(e,t,[r])}function Zt(e,t,r){return I(e,r),r=>S(e,t,[r])}function Xt(e,t){if("bytes"!=(e=`${e}`))throw new TypeError(`${t} '${e}' is not a valid enumeration value for ReadableStreamType`);return e}function er(e,t){if("byob"!=(e=`${e}`))throw new TypeError(`${t} '${e}' is not a valid enumeration value for ReadableStreamReaderMode`);return e}function tr(e,t){$(e,t);const r=null==e?void 0:e.preventAbort,o=null==e?void 0:e.preventCancel,n=null==e?void 0:e.preventClose,s=null==e?void 0:e.signal;return void 0!==s&&function(e,t){if(!function(e){if("object"!=typeof e||null===e)return!1;try{return"boolean"==typeof e.aborted}catch(e){return!1}}(e))throw new TypeError(`${t} is not an AbortSignal.`)}(s,`${t} has member 'signal' that`),{preventAbort:Boolean(r),preventCancel:Boolean(o),preventClose:Boolean(n),signal:s}}Object.defineProperties(Mt.prototype,{close:{enumerable:!0},enqueue:{enumerable:!0},error:{enumerable:!0},desiredSize:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(Mt.prototype,o.toStringTag,{value:"ReadableStreamDefaultController",configurable:!0});class rr{constructor(e={},t={}){void 0===e?e=null:D(e,"First parameter");const r=Fe(t,"Second parameter"),o=function(e,t){$(e,t);const r=e,o=null==r?void 0:r.autoAllocateChunkSize,n=null==r?void 0:r.cancel,s=null==r?void 0:r.pull,i=null==r?void 0:r.start,a=null==r?void 0:r.type;return{autoAllocateChunkSize:void 0===o?void 0:V(o,`${t} has member 'autoAllocateChunkSize' that`),cancel:void 0===n?void 0:Jt(n,r,`${t} has member 'cancel' that`),pull:void 0===s?void 0:Kt(s,r,`${t} has member 'pull' that`),start:void 0===i?void 0:Zt(i,r,`${t} has member 'start' that`),type:void 0===a?void 0:Xt(a,`${t} has member 'type' that`)}}(e,"First parameter");if(nr(this),"bytes"===o.type){if(void 0!==r.size)throw new RangeError("The strategy for a byte stream cannot have a size function");!function(e,t,r){const o=Object.create(ye.prototype);let n=()=>{},s=()=>p(void 0),i=()=>p(void 0);void 0!==t.start&&(n=()=>t.start(o)),void 0!==t.pull&&(s=()=>t.pull(o)),void 0!==t.cancel&&(i=e=>t.cancel(e));const a=t.autoAllocateChunkSize;!function(e,t,r,o,n,s,i){t._controlledReadableByteStream=e,t._pullAgain=!1,t._pulling=!1,t._byobRequest=null,t._queue=t._queueTotalSize=void 0,fe(t),t._closeRequested=!1,t._started=!1,t._strategyHWM=s,t._pullAlgorithm=o,t._cancelAlgorithm=n,t._autoAllocateChunkSize=i,t._pendingPullIntos=new R,e._readableStreamController=t,b(p(r()),(()=>{t._started=!0,_e(t)}),(e=>{Oe(t,e)}))}(e,o,n,s,i,r,a)}(this,o,Ie(r,0))}else{const e=De(r);!function(e,t,r,o){const n=Object.create(Mt.prototype);let s=()=>{},i=()=>p(void 0),a=()=>p(void 0);void 0!==t.start&&(s=()=>t.start(n)),void 0!==t.pull&&(i=()=>t.pull(n)),void 0!==t.cancel&&(a=e=>t.cancel(e)),Yt(e,n,s,i,a,r,o)}(this,o,Ie(r,1),e)}}get locked(){if(!sr(this))throw cr("locked");return ir(this)}cancel(e){return sr(this)?ir(this)?h(new TypeError("Cannot cancel a stream that already has a reader")):ar(this,e):h(cr("cancel"))}getReader(e){if(!sr(this))throw cr("getReader");return void 0===function(e,t){$(e,t);const r=null==e?void 0:e.mode;return{mode:void 0===r?void 0:er(r,`${t} has member 'mode' that`)}}(e,"First parameter").mode?Y(this):new ze(this)}pipeThrough(e,t={}){if(!sr(this))throw cr("pipeThrough");F(e,1,"pipeThrough");const r=function(e,t){$(e,t);const r=null==e?void 0:e.readable;U(r,"readable","ReadableWritablePair"),Q(r,`${t} has member 'readable' that`);const o=null==e?void 0:e.writable;return U(o,"writable","ReadableWritablePair"),Ye(o,`${t} has member 'writable' that`),{readable:r,writable:o}}(e,"First parameter"),o=tr(t,"Second parameter");if(ir(this))throw new TypeError("ReadableStream.prototype.pipeThrough cannot be used on a locked ReadableStream");if(Xe(r.writable))throw new TypeError("ReadableStream.prototype.pipeThrough cannot be used on a locked WritableStream");return w(zt(this,r.writable,o.preventClose,o.preventAbort,o.preventCancel,o.signal)),r.readable}pipeTo(e,t={}){if(!sr(this))return h(cr("pipeTo"));if(void 0===e)return h("Parameter 1 is required in 'pipeTo'.");if(!Ze(e))return h(new TypeError("ReadableStream.prototype.pipeTo's first argument must be a WritableStream"));let r;try{r=tr(t,"Second parameter")}catch(e){return h(e)}return ir(this)?h(new TypeError("ReadableStream.prototype.pipeTo cannot be used on a locked ReadableStream")):Xe(e)?h(new TypeError("ReadableStream.prototype.pipeTo cannot be used on a locked WritableStream")):zt(this,e,r.preventClose,r.preventAbort,r.preventCancel,r.signal)}tee(){if(!sr(this))throw cr("tee");const e=function(e,t){const r=Y(e);let o,n,s,i,a,u=!1,l=!1,c=!1;const d=f((e=>{a=e}));function h(){return u||(u=!0,te(r,{_chunkSteps:e=>{v((()=>{u=!1;const t=e,r=e;l||Ht(s._readableStreamController,t),c||Ht(i._readableStreamController,r),a(void 0)}))},_closeSteps:()=>{u=!1,l||Ut(s._readableStreamController),c||Ut(i._readableStreamController)},_errorSteps:()=>{u=!1}})),p(void 0)}function y(){}return s=or(y,h,(function(t){if(l=!0,o=t,c){const t=pe([o,n]),r=ar(e,t);a(r)}return d})),i=or(y,h,(function(t){if(c=!0,n=t,l){const t=pe([o,n]),r=ar(e,t);a(r)}return d})),_(r._closedPromise,(e=>{Nt(s._readableStreamController,e),Nt(i._readableStreamController,e),a(void 0)})),[s,i]}(this);return pe(e)}values(e){if(!sr(this))throw cr("values");return function(e,t){const r=Y(e),o=new ne(r,t),n=Object.create(se);return n._asyncIteratorImpl=o,n}(this,function(e,t){$(e,"First parameter");const r=null==e?void 0:e.preventCancel;return{preventCancel:Boolean(r)}}(e).preventCancel)}}function or(e,t,r,o=1,n=(()=>1)){const s=Object.create(rr.prototype);return nr(s),Yt(s,Object.create(Mt.prototype),e,t,r,o,n),s}function nr(e){e._state="readable",e._reader=void 0,e._storedError=void 0,e._disturbed=!1}function sr(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_readableStreamController")}function ir(e){return void 0!==e._reader}function ar(e,t){return e._disturbed=!0,"closed"===e._state?p(void 0):"errored"===e._state?h(e._storedError):(ur(e),g(e._readableStreamController[W](t),n))}function ur(e){e._state="closed";const t=e._reader;void 0!==t&&(ee(t)&&(t._readRequests.forEach((e=>{e._closeSteps()})),t._readRequests=new R),A(t))}function lr(e,t){e._state="errored",e._storedError=t;const r=e._reader;void 0!==r&&(ee(r)?(r._readRequests.forEach((e=>{e._errorSteps(t)})),r._readRequests=new R):(r._readIntoRequests.forEach((e=>{e._errorSteps(t)})),r._readIntoRequests=new R),C(r,t))}function cr(e){return new TypeError(`ReadableStream.prototype.${e} can only be used on a ReadableStream`)}function dr(e,t){$(e,t);const r=null==e?void 0:e.highWaterMark;return U(r,"highWaterMark","QueuingStrategyInit"),{highWaterMark:H(r)}}Object.defineProperties(rr.prototype,{cancel:{enumerable:!0},getReader:{enumerable:!0},pipeThrough:{enumerable:!0},pipeTo:{enumerable:!0},tee:{enumerable:!0},values:{enumerable:!0},locked:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(rr.prototype,o.toStringTag,{value:"ReadableStream",configurable:!0}),"symbol"==typeof o.asyncIterator&&Object.defineProperty(rr.prototype,o.asyncIterator,{value:rr.prototype.values,writable:!0,configurable:!0});const fr=function(e){return e.byteLength};class pr{constructor(e){F(e,1,"ByteLengthQueuingStrategy"),e=dr(e,"First parameter"),this._byteLengthQueuingStrategyHighWaterMark=e.highWaterMark}get highWaterMark(){if(!yr(this))throw hr("highWaterMark");return this._byteLengthQueuingStrategyHighWaterMark}get size(){if(!yr(this))throw hr("size");return fr}}function hr(e){return new TypeError(`ByteLengthQueuingStrategy.prototype.${e} can only be used on a ByteLengthQueuingStrategy`)}function yr(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_byteLengthQueuingStrategyHighWaterMark")}Object.defineProperties(pr.prototype,{highWaterMark:{enumerable:!0},size:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(pr.prototype,o.toStringTag,{value:"ByteLengthQueuingStrategy",configurable:!0});const br=function(){return 1};class mr{constructor(e){F(e,1,"CountQueuingStrategy"),e=dr(e,"First parameter"),this._countQueuingStrategyHighWaterMark=e.highWaterMark}get highWaterMark(){if(!gr(this))throw _r("highWaterMark");return this._countQueuingStrategyHighWaterMark}get size(){if(!gr(this))throw _r("size");return br}}function _r(e){return new TypeError(`CountQueuingStrategy.prototype.${e} can only be used on a CountQueuingStrategy`)}function gr(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_countQueuingStrategyHighWaterMark")}function wr(e,t,r){return I(e,r),r=>T(e,t,[r])}function vr(e,t,r){return I(e,r),r=>S(e,t,[r])}function Sr(e,t,r){return I(e,r),(r,o)=>T(e,t,[r,o])}Object.defineProperties(mr.prototype,{highWaterMark:{enumerable:!0},size:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(mr.prototype,o.toStringTag,{value:"CountQueuingStrategy",configurable:!0});class Tr{constructor(e={},t={},r={}){void 0===e&&(e=null);const o=Fe(t,"Second parameter"),n=Fe(r,"Third parameter"),s=function(e,t){$(e,t);const r=null==e?void 0:e.flush,o=null==e?void 0:e.readableType,n=null==e?void 0:e.start,s=null==e?void 0:e.transform,i=null==e?void 0:e.writableType;return{flush:void 0===r?void 0:wr(r,e,`${t} has member 'flush' that`),readableType:o,start:void 0===n?void 0:vr(n,e,`${t} has member 'start' that`),transform:void 0===s?void 0:Sr(s,e,`${t} has member 'transform' that`),writableType:i}}(e,"First parameter");if(void 0!==s.readableType)throw new RangeError("Invalid readableType specified");if(void 0!==s.writableType)throw new RangeError("Invalid writableType specified");const i=Ie(n,0),a=De(n),u=Ie(o,1),l=De(o);let c;!function(e,t,r,o,n,s){function i(){return t}e._writable=function(e,t,r,o,n=1,s=(()=>1)){const i=Object.create(Ge.prototype);return Ke(i),bt(i,Object.create(yt.prototype),e,t,r,o,n,s),i}(i,(function(t){return function(e,t){const r=e._transformStreamController;return e._backpressure?g(e._backpressureChangePromise,(()=>{const o=e._writable;if("erroring"===o._state)throw o._storedError;return Ar(r,t)})):Ar(r,t)}(e,t)}),(function(){return function(e){const t=e._readable,r=e._transformStreamController,o=r._flushAlgorithm();return Or(r),g(o,(()=>{if("errored"===t._state)throw t._storedError;Ut(t._readableStreamController)}),(r=>{throw qr(e,r),t._storedError}))}(e)}),(function(t){return function(e,t){return qr(e,t),p(void 0)}(e,t)}),r,o),e._readable=or(i,(function(){return function(e){return Er(e,!1),e._backpressureChangePromise}(e)}),(function(t){return Pr(e,t),p(void 0)}),n,s),e._backpressure=void 0,e._backpressureChangePromise=void 0,e._backpressureChangePromise_resolve=void 0,Er(e,!0),e._transformStreamController=void 0}(this,f((e=>{c=e})),u,l,i,a),function(e,t){const r=Object.create(jr.prototype);let o=e=>{try{return Cr(r,e),p(void 0)}catch(e){return h(e)}},n=()=>p(void 0);void 0!==t.transform&&(o=e=>t.transform(e,r)),void 0!==t.flush&&(n=()=>t.flush(r)),function(e,t,r,o){t._controlledTransformStream=e,e._transformStreamController=t,t._transformAlgorithm=r,t._flushAlgorithm=o}(e,r,o,n)}(this,s),void 0!==s.start?c(s.start(this._transformStreamController)):c(void 0)}get readable(){if(!Rr(this))throw xr("readable");return this._readable}get writable(){if(!Rr(this))throw xr("writable");return this._writable}}function Rr(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_transformStreamController")}function qr(e,t){Nt(e._readable._readableStreamController,t),Pr(e,t)}function Pr(e,t){Or(e._transformStreamController),wt(e._writable._writableStreamController,t),e._backpressure&&Er(e,!1)}function Er(e,t){void 0!==e._backpressureChangePromise&&e._backpressureChangePromise_resolve(),e._backpressureChangePromise=f((t=>{e._backpressureChangePromise_resolve=t})),e._backpressure=t}Object.defineProperties(Tr.prototype,{readable:{enumerable:!0},writable:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(Tr.prototype,o.toStringTag,{value:"TransformStream",configurable:!0});class jr{constructor(){throw new TypeError("Illegal constructor")}get desiredSize(){if(!kr(this))throw Br("desiredSize");return Vt(this._controlledTransformStream._readable._readableStreamController)}enqueue(e){if(!kr(this))throw Br("enqueue");Cr(this,e)}error(e){if(!kr(this))throw Br("error");var t;t=e,qr(this._controlledTransformStream,t)}terminate(){if(!kr(this))throw Br("terminate");!function(e){const t=e._controlledTransformStream;Ut(t._readable._readableStreamController);Pr(t,new TypeError("TransformStream terminated"))}(this)}}function kr(e){return!!i(e)&&!!Object.prototype.hasOwnProperty.call(e,"_controlledTransformStream")}function Or(e){e._transformAlgorithm=void 0,e._flushAlgorithm=void 0}function Cr(e,t){const r=e._controlledTransformStream,o=r._readable._readableStreamController;if(!Qt(o))throw new TypeError("Readable side is not in a state that permits enqueue");try{Ht(o,t)}catch(e){throw Pr(r,e),r._readable._storedError}(function(e){return!Dt(e)})(o)!==r._backpressure&&Er(r,!0)}function Ar(e,t){return g(e._transformAlgorithm(t),void 0,(t=>{throw qr(e._controlledTransformStream,t),t}))}function Br(e){return new TypeError(`TransformStreamDefaultController.prototype.${e} can only be used on a TransformStreamDefaultController`)}function xr(e){return new TypeError(`TransformStream.prototype.${e} can only be used on a TransformStream`)}Object.defineProperties(jr.prototype,{enqueue:{enumerable:!0},error:{enumerable:!0},terminate:{enumerable:!0},desiredSize:{enumerable:!0}}),"symbol"==typeof o.toStringTag&&Object.defineProperty(jr.prototype,o.toStringTag,{value:"TransformStreamDefaultController",configurable:!0})},417:e=>{"use strict";e.exports=__webpack_require__(417)},605:e=>{"use strict";e.exports=__webpack_require__(605)},211:e=>{"use strict";e.exports=__webpack_require__(211)},413:e=>{"use strict";e.exports=__webpack_require__(413)},835:e=>{"use strict";e.exports=__webpack_require__(835)},669:e=>{"use strict";e.exports=__webpack_require__(669)},761:e=>{"use strict";e.exports=__webpack_require__(761)}},t={};function r(o){if(t[o])return t[o].exports;var n=t[o]={exports:{}};return e[o].call(n.exports,n,n.exports,r),n.exports}return r.d=(e,t)=>{for(var o in t)r.o(t,o)&&!r.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:t[o]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r(990)})().default})); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9tYWlsZ3VuL3dlYnBhY2svdW5pdmVyc2FsTW9kdWxlRGVmaW5pdGlvbiIsIndlYnBhY2s6Ly9tYWlsZ3VuLy4vbm9kZV9tb2R1bGVzL2Fib3J0LWNvbnRyb2xsZXIvZGlzdC9hYm9ydC1jb250cm9sbGVyLmpzIiwid2VicGFjazovL21haWxndW4vLi9pbmRleC50cyIsIndlYnBhY2s6Ly9tYWlsZ3VuLy4vbGliL2NsaWVudC50cyIsIndlYnBhY2s6Ly9tYWlsZ3VuLy4vbGliL2RvbWFpbnMudHMiLCJ3ZWJwYWNrOi8vbWFpbGd1bi8uL2xpYi9lcnJvci50cyIsIndlYnBhY2s6Ly9tYWlsZ3VuLy4vbGliL2V2ZW50cy50cyIsIndlYnBhY2s6Ly9tYWlsZ3VuLy4vbGliL2lwLXBvb2xzLnRzIiwid2VicGFjazovL21haWxndW4vLi9saWIvaXBzLnRzIiwid2VicGFjazovL21haWxndW4vLi9saWIvbWVzc2FnZXMudHMiLCJ3ZWJwYWNrOi8vbWFpbGd1bi8uL2xpYi9wYXJzZS50cyIsIndlYnBhY2s6Ly9tYWlsZ3VuLy4vbGliL3JlcXVlc3QudHMiLCJ3ZWJwYWNrOi8vbWFpbGd1bi8uL2xpYi9yb3V0ZXMudHMiLCJ3ZWJwYWNrOi8vbWFpbGd1bi8uL2xpYi9zdGF0cy50cyIsIndlYnBhY2s6Ly9tYWlsZ3VuLy4vbGliL3N1cHByZXNzaW9ucy50cyIsIndlYnBhY2s6Ly9tYWlsZ3VuLy4vbGliL3ZhbGlkYXRlLnRzIiwid2VicGFjazovL21haWxndW4vLi9saWIvd2ViaG9va3MudHMiLCJ3ZWJwYWNrOi8vbWFpbGd1bi8uL25vZGVfbW9kdWxlcy9idG9hL2luZGV4LmpzIiwid2VicGFjazovL21haWxndW4vLi9ub2RlX21vZHVsZXMvZGF0YS11cmktdG8tYnVmZmVyL2Rpc3Qvc3JjL2luZGV4LmpzIiwid2VicGFjazovL21haWxndW4vLi9ub2RlX21vZHVsZXMvZXZlbnQtdGFyZ2V0LXNoaW0vZGlzdC9ldmVudC10YXJnZXQtc2hpbS5qcyIsIndlYnBhY2s6Ly9tYWlsZ3VuLy4vbm9kZV9tb2R1bGVzL2ZldGNoLWJsb2IvaW5kZXguanMiLCJ3ZWJwYWNrOi8vbWFpbGd1bi8uL25vZGVfbW9kdWxlcy9reS11bml2ZXJzYWwvaW5kZXguanMiLCJ3ZWJwYWNrOi8vbWFpbGd1bi8uL25vZGVfbW9kdWxlcy9reS91bWQuanMiLCJ3ZWJwYWNrOi8vbWFpbGd1bi8uL25vZGVfbW9kdWxlcy9ub2RlLWZldGNoL2Rpc3QvaW5kZXguY2pzIiwid2VicGFjazovL21haWxndW4vLi9ub2RlX21vZHVsZXMvdXJsLWpvaW4vbGliL3VybC1qb2luLmpzIiwid2VicGFjazovL21haWxndW4vLi9ub2RlX21vZHVsZXMvd2ViLXN0cmVhbXMtcG9seWZpbGwvZGlzdC9wb255ZmlsbC5lczIwMTgubWpzIiwid2VicGFjazovL21haWxndW4vZXh0ZXJuYWwgXCJjcnlwdG9cIiIsIndlYnBhY2s6Ly9tYWlsZ3VuL2V4dGVybmFsIFwiaHR0cFwiIiwid2VicGFjazovL21haWxndW4vZXh0ZXJuYWwgXCJodHRwc1wiIiwid2VicGFjazovL21haWxndW4vZXh0ZXJuYWwgXCJzdHJlYW1cIiIsIndlYnBhY2s6Ly9tYWlsZ3VuL2V4dGVybmFsIFwidXJsXCIiLCJ3ZWJwYWNrOi8vbWFpbGd1bi9leHRlcm5hbCBcInV0aWxcIiIsIndlYnBhY2s6Ly9tYWlsZ3VuL2V4dGVybmFsIFwiemxpYlwiIiwid2VicGFjazovL21haWxndW4vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vbWFpbGd1bi93ZWJwYWNrL3N0YXJ0dXAiLCJ3ZWJwYWNrOi8vbWFpbGd1bi93ZWJwYWNrL3J1bnRpbWUvZGVmaW5lIHByb3BlcnR5IGdldHRlcnMiLCJ3ZWJwYWNrOi8vbWFpbGd1bi93ZWJwYWNrL3J1bnRpbWUvaGFzT3duUHJvcGVydHkgc2hvcnRoYW5kIiwid2VicGFjazovL21haWxndW4vd2VicGFjay9ydW50aW1lL21ha2UgbmFtZXNwYWNlIG9iamVjdCJdLCJuYW1lcyI6WyJyb290IiwiZmFjdG9yeSIsImV4cG9ydHMiLCJtb2R1bGUiLCJkZWZpbmUiLCJhbWQiLCJ0aGlzIiwiT2JqZWN0IiwiZGVmaW5lUHJvcGVydHkiLCJ2YWx1ZSIsImV2ZW50VGFyZ2V0U2hpbSIsIkFib3J0U2lnbmFsIiwiRXZlbnRUYXJnZXQiLCJzdXBlciIsIlR5cGVFcnJvciIsImFib3J0ZWQiLCJhYm9ydGVkRmxhZ3MiLCJnZXQiLCJkZWZpbmVFdmVudEF0dHJpYnV0ZSIsInByb3RvdHlwZSIsIldlYWtNYXAiLCJkZWZpbmVQcm9wZXJ0aWVzIiwiZW51bWVyYWJsZSIsIlN5bWJvbCIsInRvU3RyaW5nVGFnIiwiY29uZmlndXJhYmxlIiwiQWJvcnRDb250cm9sbGVyIiwic2lnbmFscyIsInNldCIsInNpZ25hbCIsImNyZWF0ZSIsImNhbGwiLCJjcmVhdGVBYm9ydFNpZ25hbCIsImdldFNpZ25hbCIsImRpc3BhdGNoRXZlbnQiLCJ0eXBlIiwiY29udHJvbGxlciIsImFib3J0IiwiZGVmYXVsdCIsIkZvcm1EYXRhIiwiZm9ybURhdGEiLCJjbGllbnQiLCJvcHRpb25zIiwiY29uZmlnIiwidXJsIiwidXNlcm5hbWUiLCJFcnJvciIsImtleSIsInJlcXVlc3QiLCJkb21haW5zIiwid2ViaG9va3MiLCJldmVudHMiLCJzdGF0cyIsInN1cHByZXNzaW9ucyIsIm1lc3NhZ2VzIiwicm91dGVzIiwiaXBzIiwiaXBfcG9vbHMiLCJwdWJsaWNfa2V5IiwicHVibGljX3JlcXVlc3QiLCJ2YWxpZGF0ZSIsInBhcnNlIiwiZGF0YSIsInJlY2VpdmluZyIsInNlbmRpbmciLCJuYW1lIiwicmVxdWlyZV90bHMiLCJza2lwX3ZlcmlmaWNhdGlvbiIsInN0YXRlIiwid2lsZGNhcmQiLCJzcGFtX2FjdGlvbiIsImNyZWF0ZWRfYXQiLCJzbXRwX3Bhc3N3b3JkIiwic210cF9sb2dpbiIsInJlY2VpdmluZ19kbnNfcmVjb3JkcyIsInNlbmRpbmdfZG5zX3JlY29yZHMiLCJfcGFyc2VNZXNzYWdlIiwicmVzcG9uc2UiLCJib2R5IiwiX3BhcnNlRG9tYWluTGlzdCIsIml0ZW1zIiwibWFwIiwiaXRlbSIsIkRvbWFpbiIsIl9wYXJzZURvbWFpbiIsImRvbWFpbiIsIl9wYXJzZVRyYWNraW5nU2V0dGluZ3MiLCJ0cmFja2luZyIsIl9wYXJzZVRyYWNraW5nVXBkYXRlIiwibGlzdCIsInF1ZXJ5IiwidGhlbiIsInBvc3QiLCJkZXN0cm95IiwiZGVsZXRlIiwiZ2V0VHJhY2tpbmciLCJ1cGRhdGVUcmFja2luZyIsInB1dCIsImdldElwcyIsImFzc2lnbklwIiwiaXAiLCJkZWxldGVJcCIsImxpbmtJcFBvb2wiLCJwb29sX2lkIiwidW5saW5rSXBQb2xsIiwic3RhdHVzIiwic3RhdHVzVGV4dCIsIm1lc3NhZ2UiLCJib2R5TWVzc2FnZSIsImVycm9yIiwic3RhY2siLCJkZXRhaWxzIiwidXJsam9pbiIsIl9wYXJzZVBhZ2VOdW1iZXIiLCJzcGxpdCIsInBvcCIsIl9wYXJzZVBhZ2UiLCJpZCIsIm51bWJlciIsIl9wYXJzZVBhZ2VMaW5rcyIsImVudHJpZXMiLCJwYWdpbmciLCJyZWR1Y2UiLCJhY2MiLCJfcGFyc2VFdmVudExpc3QiLCJwYWdlcyIsInBhZ2UiLCJwYXJzZUlwUG9vbHNSZXNwb25zZSIsInVwZGF0ZSIsInBvb2xJZCIsInBhdGNoIiwicGFyc2VJcHNSZXNwb25zZSIsIl9wYXJzZVJlc3BvbnNlIiwicG9zdE11bHRpIiwiYWRkcmVzc2VzIiwiZW5hYmxlRG5zRXNwQ2hlY2tzIiwiQXJyYXkiLCJpc0FycmF5Iiwiam9pbiIsInN5bnRheF9vbmx5IiwiaXNTdHJlYW0iLCJhdHRhY2htZW50IiwicGlwZSIsImdldEF0dGFjaG1lbnRPcHRpb25zIiwiZmlsZW5hbWUiLCJjb250ZW50VHlwZSIsImtub3duTGVuZ3RoIiwiaGVhZGVycyIsIm1ldGhvZCIsImJhc2ljIiwiQXV0aG9yaXphdGlvbiIsInBhcmFtcyIsImdldE93blByb3BlcnR5TmFtZXMiLCJsZW5ndGgiLCJzZWFyY2hQYXJhbXMiLCJ0b0xvY2FsZVVwcGVyQ2FzZSIsInRocm93SHR0cEVycm9ycyIsIm9rIiwic3RyZWFtIiwiY2h1bmtzIiwiUHJvbWlzZSIsInJlc29sdmUiLCJyZWplY3QiLCJvbiIsImNodW5rIiwicHVzaCIsIkJ1ZmZlciIsImNvbmNhdCIsInRvU3RyaW5nIiwianNvbiIsImNvbW1hbmQiLCJoZWFkIiwia2V5cyIsImZpbHRlciIsImZvckVhY2giLCJhcHBlbmQiLCJvYmoiLCJSZXF1ZXN0Iiwic3RhcnQiLCJEYXRlIiwiZW5kIiwicmVzb2x1dGlvbiIsInN0YXQiLCJ0aW1lIiwiX3BhcnNlU3RhdHMiLCJTdGF0cyIsImdldERvbWFpbiIsImdldEFjY291bnQiLCJjcmVhdGVPcHRpb25zIiwiYWRkcmVzcyIsImNvZGUiLCJ0YWdzIiwibW9kZWxzIiwiYm91bmNlcyIsIkJvdW5jZSIsImNvbXBsYWludHMiLCJDb21wbGFpbnQiLCJ1bnN1YnNjcmliZXMiLCJVbnN1YnNjcmliZSIsInBhZ2VVcmwiLCJfcGFyc2VMaXN0IiwiTW9kZWwiLCJkIiwiX3BhcnNlSXRlbSIsIm1vZGVsIiwiZW5jb2RlVVJJQ29tcG9uZW50IiwiU3VwcHJlc3Npb25DbGllbnQiLCJfcGFyc2VXZWJob29rTGlzdCIsIl9wYXJzZVdlYmhvb2tXaXRoSUQiLCJXZWJob29rIiwid2ViaG9vayIsIl9wYXJzZVdlYmhvb2tUZXN0IiwidGVzdCIsInN0ciIsImZyb20iLCJ1cmkiLCJmaXJzdENvbW1hIiwicmVwbGFjZSIsImluZGV4T2YiLCJtZXRhIiwic3Vic3RyaW5nIiwiY2hhcnNldCIsImJhc2U2NCIsInR5cGVGdWxsIiwiaSIsImVuY29kaW5nIiwidW5lc2NhcGUiLCJidWZmZXIiLCJwcml2YXRlRGF0YSIsIndyYXBwZXJzIiwicGQiLCJldmVudCIsInJldHYiLCJjb25zb2xlIiwiYXNzZXJ0Iiwic2V0Q2FuY2VsRmxhZyIsInBhc3NpdmVMaXN0ZW5lciIsImNhbmNlbGFibGUiLCJjYW5jZWxlZCIsInByZXZlbnREZWZhdWx0IiwiRXZlbnQiLCJldmVudFRhcmdldCIsImV2ZW50UGhhc2UiLCJjdXJyZW50VGFyZ2V0Iiwic3RvcHBlZCIsImltbWVkaWF0ZVN0b3BwZWQiLCJ0aW1lU3RhbXAiLCJub3ciLCJkZWZpbmVSZWRpcmVjdERlc2NyaXB0b3IiLCJkZWZpbmVDYWxsRGVzY3JpcHRvciIsImFwcGx5IiwiYXJndW1lbnRzIiwiZ2V0V3JhcHBlciIsInByb3RvIiwid3JhcHBlciIsIkJhc2VFdmVudCIsIkN1c3RvbUV2ZW50IiwiY29uc3RydWN0b3IiLCJ3cml0YWJsZSIsImlzRnVuYyIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsImRlZmluZVdyYXBwZXIiLCJnZXRQcm90b3R5cGVPZiIsImlzU3RvcHBlZCIsInNldFBhc3NpdmVMaXN0ZW5lciIsInN0b3BQcm9wYWdhdGlvbiIsInN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbiIsIkJvb2xlYW4iLCJidWJibGVzIiwiY29tcG9zZWQiLCJjYW5jZWxCdWJibGUiLCJ3aW5kb3ciLCJzZXRQcm90b3R5cGVPZiIsImxpc3RlbmVyc01hcCIsImlzT2JqZWN0IiwieCIsImdldExpc3RlbmVycyIsImxpc3RlbmVycyIsImV2ZW50VGFyZ2V0UHJvdG90eXBlIiwiZXZlbnROYW1lIiwibm9kZSIsImxpc3RlbmVyVHlwZSIsImxpc3RlbmVyIiwibmV4dCIsInByZXYiLCJuZXdOb2RlIiwicGFzc2l2ZSIsIm9uY2UiLCJkZWZpbmVFdmVudEF0dHJpYnV0ZURlc2NyaXB0b3IiLCJkZWZpbmVDdXN0b21FdmVudFRhcmdldCIsImV2ZW50TmFtZXMiLCJDdXN0b21FdmVudFRhcmdldCIsInR5cGVzIiwiTWFwIiwib3B0aW9uc0lzT2JqIiwiY2FwdHVyZSIsInVuZGVmaW5lZCIsIndyYXBwZWRFdmVudCIsIndyYXBFdmVudCIsImVyciIsImhhbmRsZUV2ZW50Iiwic2V0RXZlbnRQaGFzZSIsInNldEN1cnJlbnRUYXJnZXQiLCJkZWZhdWx0UHJldmVudGVkIiwiUmVhZGFibGUiLCJ3bSIsIkJsb2IiLCJibG9iUGFydHMiLCJzaXplIiwicGFydHMiLCJlbGVtZW50IiwiQXJyYXlCdWZmZXIiLCJpc1ZpZXciLCJieXRlT2Zmc2V0IiwiYnl0ZUxlbmd0aCIsIlN0cmluZyIsInRvTG93ZXJDYXNlIiwiYXJyYXlCdWZmZXIiLCJVaW50OEFycmF5Iiwib2Zmc2V0IiwiYXN5bmMiLCJwYXJ0IiwicmVhZCIsInJlbGF0aXZlU3RhcnQiLCJNYXRoIiwibWF4IiwibWluIiwicmVsYXRpdmVFbmQiLCJzcGFuIiwidmFsdWVzIiwiYWRkZWQiLCJzbGljZSIsImJsb2IiLCJhc3NpZ24iLCJoYXNJbnN0YW5jZSIsIm9iamVjdCIsImZldGNoIiwiZ2xvYmFsIiwiaGlnaFdhdGVyTWFyayIsIkhlYWRlcnMiLCJSZXNwb25zZSIsIlJlYWRhYmxlU3RyZWFtIiwiXyIsImdsb2JhbHMiLCJnZXRHbG9iYWwiLCJwcm9wZXJ0eSIsInNlbGYiLCJnbG9iYWxUaGlzIiwiZ2xvYmFsUHJvcGVydGllcyIsImdsb2JhbE9iamVjdCIsImJpbmQiLCJzdXBwb3J0c0Fib3J0Q29udHJvbGxlciIsInN1cHBvcnRzU3RyZWFtcyIsInN1cHBvcnRzRm9ybURhdGEiLCJtZXJnZUhlYWRlcnMiLCJzb3VyY2UxIiwic291cmNlMiIsInJlc3VsdCIsImlzSGVhZGVyc0luc3RhbmNlIiwic291cmNlIiwiZGVlcE1lcmdlIiwic291cmNlcyIsInJldHVyblZhbHVlIiwicmVxdWVzdE1ldGhvZHMiLCJyZXNwb25zZVR5cGVzIiwidGV4dCIsInJldHJ5QWZ0ZXJTdGF0dXNDb2RlcyIsInN0b3AiLCJIVFRQRXJyb3IiLCJUaW1lb3V0RXJyb3IiLCJkZWxheSIsIm1zIiwic2V0VGltZW91dCIsIm5vcm1hbGl6ZVJlcXVlc3RNZXRob2QiLCJpbnB1dCIsImluY2x1ZGVzIiwidG9VcHBlckNhc2UiLCJkZWZhdWx0UmV0cnlPcHRpb25zIiwibGltaXQiLCJtZXRob2RzIiwic3RhdHVzQ29kZXMiLCJhZnRlclN0YXR1c0NvZGVzIiwibm9ybWFsaXplUmV0cnlPcHRpb25zIiwicmV0cnkiLCJtYXhTYWZlVGltZW91dCIsIkt5IiwiX3JldHJ5Q291bnQiLCJfaW5wdXQiLCJfb3B0aW9ucyIsImNyZWRlbnRpYWxzIiwiaG9va3MiLCJiZWZvcmVSZXF1ZXN0IiwiYmVmb3JlUmV0cnkiLCJhZnRlclJlc3BvbnNlIiwicHJlZml4VXJsIiwidGltZW91dCIsIlVSTCIsInN0YXJ0c1dpdGgiLCJlbmRzV2l0aCIsImFib3J0Q29udHJvbGxlciIsImFkZEV2ZW50TGlzdGVuZXIiLCJVUkxTZWFyY2hQYXJhbXMiLCJKU09OIiwic3RyaW5naWZ5IiwiZm4iLCJSYW5nZUVycm9yIiwiX2ZldGNoIiwiaG9vayIsIm1vZGlmaWVkUmVzcG9uc2UiLCJfZGVjb3JhdGVSZXNwb25zZSIsImNsb25lIiwib25Eb3dubG9hZFByb2dyZXNzIiwiX3N0cmVhbSIsIl9yZXRyeSIsIm1pbWVUeXBlIiwicGFyc2VKc29uIiwicmV0cnlBZnRlciIsImFmdGVyIiwiTnVtYmVyIiwiaXNOYU4iLCJtYXhSZXRyeUFmdGVyIiwiX2NhbGN1bGF0ZVJldHJ5RGVsYXkiLCJyZXRyeUNvdW50IiwidGltZW91dElEIiwiY2F0Y2giLCJjbGVhclRpbWVvdXQiLCJ0b3RhbEJ5dGVzIiwidHJhbnNmZXJyZWRCeXRlcyIsInJlYWRlciIsImdldFJlYWRlciIsInBlcmNlbnQiLCJkb25lIiwiY2xvc2UiLCJlbnF1ZXVlIiwidmFsaWRhdGVBbmRNZXJnZSIsImNyZWF0ZUluc3RhbmNlIiwiZGVmYXVsdHMiLCJreSIsIm5ld0RlZmF1bHRzIiwiZXh0ZW5kIiwiaHR0cCIsImh0dHBzIiwiemxpYiIsIlN0cmVhbSIsImRhdGFVcmlUb0J1ZmZlciIsInV0aWwiLCJjcnlwdG8iLCJGZXRjaEJhc2VFcnJvciIsImNhcHR1cmVTdGFja1RyYWNlIiwiRmV0Y2hFcnJvciIsInN5c3RlbUVycm9yIiwiZXJybm8iLCJlcnJvcmVkU3lzQ2FsbCIsInN5c2NhbGwiLCJOQU1FIiwiaXNVUkxTZWFyY2hQYXJhbWV0ZXJzIiwiZ2V0QWxsIiwiaGFzIiwic29ydCIsImlzQmxvYiIsImlzRm9ybURhdGEiLCJjYXJyaWFnZSIsImRhc2hlcyIsInJlcGVhdCIsImNhcnJpYWdlTGVuZ3RoIiwiZ2V0Rm9vdGVyIiwiYm91bmRhcnkiLCJnZXRIZWFkZXIiLCJmaWVsZCIsImhlYWRlciIsIklOVEVSTkFMUyIsIkJvZHkiLCJpc0J1ZmZlciIsImlzQW55QXJyYXlCdWZmZXIiLCJyYW5kb21CeXRlcyIsImZvcm0iLCJmb3JtRGF0YUl0ZXJhdG9yIiwiZGlzdHVyYmVkIiwiY29uc3VtZUJvZHkiLCJjdCIsImJ1ZiIsImFsbG9jIiwiYWNjdW0iLCJhY2N1bUJ5dGVzIiwicmVhZGFibGVFbmRlZCIsIl9yZWFkYWJsZVN0YXRlIiwiZW5kZWQiLCJldmVyeSIsImMiLCJib2R5VXNlZCIsImluc3RhbmNlIiwicDEiLCJwMiIsImdldEJvdW5kYXJ5IiwiUGFzc1Rocm91Z2giLCJleHRyYWN0Q29udGVudFR5cGUiLCJ2YWxpZGF0ZUhlYWRlck5hbWUiLCJ2YWxpZGF0ZUhlYWRlclZhbHVlIiwiaW5pdCIsInJhdyIsImlzQm94ZWRQcmltaXRpdmUiLCJpdGVyYXRvciIsInBhaXIiLCJQcm94eSIsInRhcmdldCIsInAiLCJyZWNlaXZlciIsIlNldCIsIlJlZmxlY3QiLCJjYWxsYmFjayIsImZvciIsInJlZGlyZWN0U3RhdHVzIiwiaXNSZWRpcmVjdCIsIklOVEVSTkFMUyQxIiwiY291bnRlciIsInJlZGlyZWN0ZWQiLCJsb2NhdGlvbiIsIklOVEVSTkFMUyQyIiwiaXNSZXF1ZXN0IiwicGFyc2VkVVJMIiwiaW5wdXRCb2R5IiwicmVkaXJlY3QiLCJmb2xsb3ciLCJjb21wcmVzcyIsImFnZW50IiwiaW5zZWN1cmVIVFRQUGFyc2VyIiwiZm9ybWF0IiwiQWJvcnRFcnJvciIsInN1cHBvcnRlZFNjaGVtYXMiLCJvcHRpb25zXyIsImNvbnRlbnRMZW5ndGhWYWx1ZSIsImdldExlbmd0aFN5bmMiLCJoYXNLbm93bkxlbmd0aCIsImdldEZvcm1EYXRhTGVuZ3RoIiwiZ2V0VG90YWxCeXRlcyIsInNlYXJjaCIsImxhc3RPZmZzZXQiLCJocmVmIiwiaGFzaCIsImdldFNlYXJjaCIsInBhdGgiLCJwYXRobmFtZSIsImhvc3RuYW1lIiwicHJvdG9jb2wiLCJwb3J0IiwiZ2V0Tm9kZVJlcXVlc3RPcHRpb25zIiwic2VuZCIsImVtaXQiLCJhYm9ydEFuZEZpbmFsaXplIiwiZmluYWxpemUiLCJyZXF1ZXN0XyIsInJlbW92ZUV2ZW50TGlzdGVuZXIiLCJyZXNwb25zZV8iLCJpbmRleCIsImFycmF5IiwiZnJvbVJhd0hlYWRlcnMiLCJyYXdIZWFkZXJzIiwic3RhdHVzQ29kZSIsImxvY2F0aW9uVVJMIiwicmVxdWVzdE9wdGlvbnMiLCJwaXBlbGluZSIsInByb2Nlc3MiLCJ2ZXJzaW9uIiwicmVzcG9uc2VPcHRpb25zIiwic3RhdHVzTWVzc2FnZSIsImNvZGluZ3MiLCJ6bGliT3B0aW9ucyIsImZsdXNoIiwiWl9TWU5DX0ZMVVNIIiwiZmluaXNoRmx1c2giLCJjcmVhdGVHdW56aXAiLCJjcmVhdGVCcm90bGlEZWNvbXByZXNzIiwiY3JlYXRlSW5mbGF0ZSIsImNyZWF0ZUluZmxhdGVSYXciLCJkZXN0Iiwid3JpdGUiLCJ3cml0ZVRvU3RyZWFtIiwibm9ybWFsaXplIiwiam9pbmVkIiwiU3ltYm9sUG9seWZpbGwiLCJkZXNjcmlwdGlvbiIsIm5vb3AiLCJ0eXBlSXNPYmplY3QiLCJyZXRocm93QXNzZXJ0aW9uRXJyb3JSZWplY3Rpb24iLCJvcmlnaW5hbFByb21pc2UiLCJvcmlnaW5hbFByb21pc2VUaGVuIiwib3JpZ2luYWxQcm9taXNlUmVzb2x2ZSIsIm9yaWdpbmFsUHJvbWlzZVJlamVjdCIsIm5ld1Byb21pc2UiLCJleGVjdXRvciIsInByb21pc2VSZXNvbHZlZFdpdGgiLCJwcm9taXNlUmVqZWN0ZWRXaXRoIiwicmVhc29uIiwiUGVyZm9ybVByb21pc2VUaGVuIiwicHJvbWlzZSIsIm9uRnVsZmlsbGVkIiwib25SZWplY3RlZCIsInVwb25Qcm9taXNlIiwidXBvbkZ1bGZpbGxtZW50IiwidXBvblJlamVjdGlvbiIsInRyYW5zZm9ybVByb21pc2VXaXRoIiwiZnVsZmlsbG1lbnRIYW5kbGVyIiwicmVqZWN0aW9uSGFuZGxlciIsInNldFByb21pc2VJc0hhbmRsZWRUb1RydWUiLCJxdWV1ZU1pY3JvdGFzayIsImdsb2JhbFF1ZXVlTWljcm90YXNrIiwicmVzb2x2ZWRQcm9taXNlIiwicmVmbGVjdENhbGwiLCJGIiwiViIsImFyZ3MiLCJGdW5jdGlvbiIsInByb21pc2VDYWxsIiwiU2ltcGxlUXVldWUiLCJfY3Vyc29yIiwiX3NpemUiLCJfZnJvbnQiLCJfZWxlbWVudHMiLCJfbmV4dCIsIl9iYWNrIiwib2xkQmFjayIsIm5ld0JhY2siLCJRVUVVRV9NQVhfQVJSQVlfU0laRSIsIm9sZEZyb250IiwibmV3RnJvbnQiLCJvbGRDdXJzb3IiLCJuZXdDdXJzb3IiLCJlbGVtZW50cyIsImZyb250IiwiY3Vyc29yIiwiUmVhZGFibGVTdHJlYW1SZWFkZXJHZW5lcmljSW5pdGlhbGl6ZSIsIl9vd25lclJlYWRhYmxlU3RyZWFtIiwiX3JlYWRlciIsIl9zdGF0ZSIsImRlZmF1bHRSZWFkZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZSIsImRlZmF1bHRSZWFkZXJDbG9zZWRQcm9taXNlUmVzb2x2ZSIsImRlZmF1bHRSZWFkZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZUFzUmVzb2x2ZWQiLCJkZWZhdWx0UmVhZGVyQ2xvc2VkUHJvbWlzZUluaXRpYWxpemVBc1JlamVjdGVkIiwiX3N0b3JlZEVycm9yIiwiUmVhZGFibGVTdHJlYW1SZWFkZXJHZW5lcmljQ2FuY2VsIiwiUmVhZGFibGVTdHJlYW1DYW5jZWwiLCJSZWFkYWJsZVN0cmVhbVJlYWRlckdlbmVyaWNSZWxlYXNlIiwiZGVmYXVsdFJlYWRlckNsb3NlZFByb21pc2VSZWplY3QiLCJkZWZhdWx0UmVhZGVyQ2xvc2VkUHJvbWlzZVJlc2V0VG9SZWplY3RlZCIsInJlYWRlckxvY2tFeGNlcHRpb24iLCJfY2xvc2VkUHJvbWlzZSIsIl9jbG9zZWRQcm9taXNlX3Jlc29sdmUiLCJfY2xvc2VkUHJvbWlzZV9yZWplY3QiLCJBYm9ydFN0ZXBzIiwiRXJyb3JTdGVwcyIsIkNhbmNlbFN0ZXBzIiwiUHVsbFN0ZXBzIiwiTnVtYmVySXNGaW5pdGUiLCJpc0Zpbml0ZSIsIk1hdGhUcnVuYyIsInRydW5jIiwidiIsImNlaWwiLCJmbG9vciIsImFzc2VydERpY3Rpb25hcnkiLCJjb250ZXh0IiwiYXNzZXJ0RnVuY3Rpb24iLCJhc3NlcnRPYmplY3QiLCJhc3NlcnRSZXF1aXJlZEFyZ3VtZW50IiwicG9zaXRpb24iLCJhc3NlcnRSZXF1aXJlZEZpZWxkIiwiY29udmVydFVucmVzdHJpY3RlZERvdWJsZSIsImNlbnNvck5lZ2F0aXZlWmVybyIsImNvbnZlcnRVbnNpZ25lZExvbmdMb25nV2l0aEVuZm9yY2VSYW5nZSIsInVwcGVyQm91bmQiLCJNQVhfU0FGRV9JTlRFR0VSIiwiaW50ZWdlclBhcnQiLCJhc3NlcnRSZWFkYWJsZVN0cmVhbSIsIklzUmVhZGFibGVTdHJlYW0iLCJBY3F1aXJlUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyIiwiUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyIiwiUmVhZGFibGVTdHJlYW1BZGRSZWFkUmVxdWVzdCIsInJlYWRSZXF1ZXN0IiwiX3JlYWRSZXF1ZXN0cyIsIlJlYWRhYmxlU3RyZWFtRnVsZmlsbFJlYWRSZXF1ZXN0Iiwic2hpZnQiLCJfY2xvc2VTdGVwcyIsIl9jaHVua1N0ZXBzIiwiUmVhZGFibGVTdHJlYW1HZXROdW1SZWFkUmVxdWVzdHMiLCJSZWFkYWJsZVN0cmVhbUhhc0RlZmF1bHRSZWFkZXIiLCJJc1JlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlciIsIklzUmVhZGFibGVTdHJlYW1Mb2NrZWQiLCJkZWZhdWx0UmVhZGVyQnJhbmRDaGVja0V4Y2VwdGlvbiIsInJlc29sdmVQcm9taXNlIiwicmVqZWN0UHJvbWlzZSIsIlJlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlclJlYWQiLCJfZXJyb3JTdGVwcyIsImUiLCJoYXNPd25Qcm9wZXJ0eSIsIl9kaXN0dXJiZWQiLCJfcmVhZGFibGVTdHJlYW1Db250cm9sbGVyIiwiY2FuY2VsIiwicmVsZWFzZUxvY2siLCJjbG9zZWQiLCJBc3luY0l0ZXJhdG9yUHJvdG90eXBlIiwiUmVhZGFibGVTdHJlYW1Bc3luY0l0ZXJhdG9ySW1wbCIsInByZXZlbnRDYW5jZWwiLCJfb25nb2luZ1Byb21pc2UiLCJfaXNGaW5pc2hlZCIsIl9wcmV2ZW50Q2FuY2VsIiwibmV4dFN0ZXBzIiwiX25leHRTdGVwcyIsInJldHVyblN0ZXBzIiwiX3JldHVyblN0ZXBzIiwiUmVhZGFibGVTdHJlYW1Bc3luY0l0ZXJhdG9yUHJvdG90eXBlIiwiSXNSZWFkYWJsZVN0cmVhbUFzeW5jSXRlcmF0b3IiLCJfYXN5bmNJdGVyYXRvckltcGwiLCJzdHJlYW1Bc3luY0l0ZXJhdG9yQnJhbmRDaGVja0V4Y2VwdGlvbiIsInJldHVybiIsIk51bWJlcklzTmFOIiwiSXNGaW5pdGVOb25OZWdhdGl2ZU51bWJlciIsIklzTm9uTmVnYXRpdmVOdW1iZXIiLCJJbmZpbml0eSIsIkRlcXVldWVWYWx1ZSIsImNvbnRhaW5lciIsIl9xdWV1ZSIsIl9xdWV1ZVRvdGFsU2l6ZSIsIkVucXVldWVWYWx1ZVdpdGhTaXplIiwiUmVzZXRRdWV1ZSIsIkNyZWF0ZUFycmF5RnJvbUxpc3QiLCJSZWFkYWJsZVN0cmVhbUJZT0JSZXF1ZXN0IiwiSXNSZWFkYWJsZVN0cmVhbUJZT0JSZXF1ZXN0IiwiYnlvYlJlcXVlc3RCcmFuZENoZWNrRXhjZXB0aW9uIiwiX3ZpZXciLCJieXRlc1dyaXR0ZW4iLCJfYXNzb2NpYXRlZFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXIiLCJSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyUmVzcG9uZEludGVybmFsIiwiUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclJlc3BvbmQiLCJ2aWV3IiwiZmlyc3REZXNjcmlwdG9yIiwiX3BlbmRpbmdQdWxsSW50b3MiLCJwZWVrIiwiYnl0ZXNGaWxsZWQiLCJSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyUmVzcG9uZFdpdGhOZXdWaWV3IiwicmVzcG9uZCIsInJlc3BvbmRXaXRoTmV3VmlldyIsIlJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXIiLCJJc1JlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXIiLCJieXRlU3RyZWFtQ29udHJvbGxlckJyYW5kQ2hlY2tFeGNlcHRpb24iLCJfYnlvYlJlcXVlc3QiLCJieW9iUmVxdWVzdCIsIlNldFVwUmVhZGFibGVTdHJlYW1CWU9CUmVxdWVzdCIsIlJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJHZXREZXNpcmVkU2l6ZSIsIl9jbG9zZVJlcXVlc3RlZCIsIl9jb250cm9sbGVkUmVhZGFibGVCeXRlU3RyZWFtIiwiUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckVycm9yIiwiUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNsZWFyQWxnb3JpdGhtcyIsIlJlYWRhYmxlU3RyZWFtQ2xvc2UiLCJSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyQ2xvc2UiLCJ0cmFuc2ZlcnJlZEJ1ZmZlciIsIlJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJFbnF1ZXVlQ2h1bmtUb1F1ZXVlIiwiUmVhZGFibGVTdHJlYW1IYXNCWU9CUmVhZGVyIiwiUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclByb2Nlc3NQdWxsSW50b0Rlc2NyaXB0b3JzVXNpbmdRdWV1ZSIsIlJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJDYWxsUHVsbElmTmVlZGVkIiwiUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckVucXVldWUiLCJfY2FuY2VsQWxnb3JpdGhtIiwiZW50cnkiLCJSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVySGFuZGxlUXVldWVEcmFpbiIsImF1dG9BbGxvY2F0ZUNodW5rU2l6ZSIsIl9hdXRvQWxsb2NhdGVDaHVua1NpemUiLCJidWZmZXJFIiwicHVsbEludG9EZXNjcmlwdG9yIiwiZWxlbWVudFNpemUiLCJ2aWV3Q29uc3RydWN0b3IiLCJyZWFkZXJUeXBlIiwiX3N0YXJ0ZWQiLCJSZWFkYWJsZVN0cmVhbUdldE51bVJlYWRJbnRvUmVxdWVzdHMiLCJSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyU2hvdWxkQ2FsbFB1bGwiLCJfcHVsbGluZyIsIl9wdWxsQWdhaW4iLCJfcHVsbEFsZ29yaXRobSIsIlJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJDb21taXRQdWxsSW50b0Rlc2NyaXB0b3IiLCJmaWxsZWRWaWV3IiwiUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNvbnZlcnRQdWxsSW50b0Rlc2NyaXB0b3IiLCJyZWFkSW50b1JlcXVlc3QiLCJfcmVhZEludG9SZXF1ZXN0cyIsIlJlYWRhYmxlU3RyZWFtRnVsZmlsbFJlYWRJbnRvUmVxdWVzdCIsIlJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJGaWxsUHVsbEludG9EZXNjcmlwdG9yRnJvbVF1ZXVlIiwiY3VycmVudEFsaWduZWRCeXRlcyIsIm1heEJ5dGVzVG9Db3B5IiwibWF4Qnl0ZXNGaWxsZWQiLCJtYXhBbGlnbmVkQnl0ZXMiLCJ0b3RhbEJ5dGVzVG9Db3B5UmVtYWluaW5nIiwicmVhZHkiLCJxdWV1ZSIsImhlYWRPZlF1ZXVlIiwiYnl0ZXNUb0NvcHkiLCJkZXN0U3RhcnQiLCJkZXN0T2Zmc2V0Iiwic3JjIiwic3JjT2Zmc2V0IiwibiIsIlJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJGaWxsSGVhZFB1bGxJbnRvRGVzY3JpcHRvciIsIlJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJJbnZhbGlkYXRlQllPQlJlcXVlc3QiLCJSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyU2hpZnRQZW5kaW5nUHVsbEludG8iLCJSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyUmVzcG9uZEluQ2xvc2VkU3RhdGUiLCJyZW1haW5kZXJTaXplIiwicmVtYWluZGVyIiwiUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclJlc3BvbmRJblJlYWRhYmxlU3RhdGUiLCJkZXNjcmlwdG9yIiwiUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNsZWFyUGVuZGluZ1B1bGxJbnRvcyIsIlJlYWRhYmxlU3RyZWFtRXJyb3IiLCJfc3RyYXRlZ3lIV00iLCJSZWFkYWJsZVN0cmVhbUFkZFJlYWRJbnRvUmVxdWVzdCIsIklzUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyIiwiZGVzaXJlZFNpemUiLCJSZWFkYWJsZVN0cmVhbUJZT0JSZWFkZXIiLCJieW9iUmVhZGVyQnJhbmRDaGVja0V4Y2VwdGlvbiIsIkRhdGFWaWV3IiwiQllURVNfUEVSX0VMRU1FTlQiLCJjdG9yIiwiZW1wdHlWaWV3IiwiUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclB1bGxJbnRvIiwiUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyUmVhZCIsIkV4dHJhY3RIaWdoV2F0ZXJNYXJrIiwic3RyYXRlZ3kiLCJkZWZhdWx0SFdNIiwiRXh0cmFjdFNpemVBbGdvcml0aG0iLCJjb252ZXJ0UXVldWluZ1N0cmF0ZWd5IiwiY29udmVydFF1ZXVpbmdTdHJhdGVneVNpemUiLCJjb252ZXJ0VW5kZXJseWluZ1NpbmtBYm9ydENhbGxiYWNrIiwib3JpZ2luYWwiLCJjb252ZXJ0VW5kZXJseWluZ1NpbmtDbG9zZUNhbGxiYWNrIiwiY29udmVydFVuZGVybHlpbmdTaW5rU3RhcnRDYWxsYmFjayIsImNvbnZlcnRVbmRlcmx5aW5nU2lua1dyaXRlQ2FsbGJhY2siLCJhc3NlcnRXcml0YWJsZVN0cmVhbSIsIklzV3JpdGFibGVTdHJlYW0iLCJXcml0YWJsZVN0cmVhbSIsInJhd1VuZGVybHlpbmdTaW5rIiwicmF3U3RyYXRlZ3kiLCJ1bmRlcmx5aW5nU2luayIsImNvbnZlcnRVbmRlcmx5aW5nU2luayIsIkluaXRpYWxpemVXcml0YWJsZVN0cmVhbSIsInNpemVBbGdvcml0aG0iLCJXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyIiwic3RhcnRBbGdvcml0aG0iLCJ3cml0ZUFsZ29yaXRobSIsImNsb3NlQWxnb3JpdGhtIiwiYWJvcnRBbGdvcml0aG0iLCJTZXRVcFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIiLCJTZXRVcFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJGcm9tVW5kZXJseWluZ1NpbmsiLCJzdHJlYW1CcmFuZENoZWNrRXhjZXB0aW9uIiwiSXNXcml0YWJsZVN0cmVhbUxvY2tlZCIsIldyaXRhYmxlU3RyZWFtQWJvcnQiLCJXcml0YWJsZVN0cmVhbUNsb3NlUXVldWVkT3JJbkZsaWdodCIsIldyaXRhYmxlU3RyZWFtQ2xvc2UiLCJBY3F1aXJlV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyIiwiV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyIiwiX3dyaXRlciIsIl93cml0YWJsZVN0cmVhbUNvbnRyb2xsZXIiLCJfd3JpdGVSZXF1ZXN0cyIsIl9pbkZsaWdodFdyaXRlUmVxdWVzdCIsIl9jbG9zZVJlcXVlc3QiLCJfaW5GbGlnaHRDbG9zZVJlcXVlc3QiLCJfcGVuZGluZ0Fib3J0UmVxdWVzdCIsIl9iYWNrcHJlc3N1cmUiLCJfcHJvbWlzZSIsIndhc0FscmVhZHlFcnJvcmluZyIsIl9yZXNvbHZlIiwiX3JlamVjdCIsIl9yZWFzb24iLCJfd2FzQWxyZWFkeUVycm9yaW5nIiwiV3JpdGFibGVTdHJlYW1TdGFydEVycm9yaW5nIiwiY2xvc2VSZXF1ZXN0Iiwid3JpdGVyIiwiZGVmYXVsdFdyaXRlclJlYWR5UHJvbWlzZVJlc29sdmUiLCJjbG9zZVNlbnRpbmVsIiwiV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckFkdmFuY2VRdWV1ZUlmTmVlZGVkIiwiV3JpdGFibGVTdHJlYW1EZWFsV2l0aFJlamVjdGlvbiIsIldyaXRhYmxlU3RyZWFtRmluaXNoRXJyb3JpbmciLCJXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXJFbnN1cmVSZWFkeVByb21pc2VSZWplY3RlZCIsIldyaXRhYmxlU3RyZWFtSGFzT3BlcmF0aW9uTWFya2VkSW5GbGlnaHQiLCJzdG9yZWRFcnJvciIsIndyaXRlUmVxdWVzdCIsIldyaXRhYmxlU3RyZWFtUmVqZWN0Q2xvc2VBbmRDbG9zZWRQcm9taXNlSWZOZWVkZWQiLCJhYm9ydFJlcXVlc3QiLCJkZWZhdWx0V3JpdGVyQ2xvc2VkUHJvbWlzZVJlamVjdCIsIldyaXRhYmxlU3RyZWFtVXBkYXRlQmFja3ByZXNzdXJlIiwiYmFja3ByZXNzdXJlIiwiZGVmYXVsdFdyaXRlclJlYWR5UHJvbWlzZUluaXRpYWxpemUiLCJkZWZhdWx0V3JpdGVyUmVhZHlQcm9taXNlUmVzZXQiLCJnZXRXcml0ZXIiLCJsb2NrZWQiLCJfb3duZXJXcml0YWJsZVN0cmVhbSIsImRlZmF1bHRXcml0ZXJSZWFkeVByb21pc2VJbml0aWFsaXplQXNSZXNvbHZlZCIsImRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZSIsImRlZmF1bHRXcml0ZXJSZWFkeVByb21pc2VJbml0aWFsaXplQXNSZWplY3RlZCIsImRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlUmVzb2x2ZSIsImRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZUFzUmVqZWN0ZWQiLCJJc1dyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlciIsImRlZmF1bHRXcml0ZXJCcmFuZENoZWNrRXhjZXB0aW9uIiwiZGVmYXVsdFdyaXRlckxvY2tFeGNlcHRpb24iLCJXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyR2V0RGVzaXJlZFNpemUiLCJXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXJHZXREZXNpcmVkU2l6ZSIsIl9yZWFkeVByb21pc2UiLCJXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXJBYm9ydCIsIldyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlckNsb3NlIiwiV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyUmVsZWFzZSIsIldyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlcldyaXRlIiwiX3JlYWR5UHJvbWlzZVN0YXRlIiwiZGVmYXVsdFdyaXRlclJlYWR5UHJvbWlzZVJlamVjdCIsImRlZmF1bHRXcml0ZXJSZWFkeVByb21pc2VSZXNldFRvUmVqZWN0ZWQiLCJyZWxlYXNlZEVycm9yIiwiX2Nsb3NlZFByb21pc2VTdGF0ZSIsImRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlUmVzZXRUb1JlamVjdGVkIiwiV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyRW5zdXJlQ2xvc2VkUHJvbWlzZVJlamVjdGVkIiwiY2h1bmtTaXplIiwiX3N0cmF0ZWd5U2l6ZUFsZ29yaXRobSIsImNodW5rU2l6ZUUiLCJXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRXJyb3JJZk5lZWRlZCIsIldyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJHZXRDaHVua1NpemUiLCJXcml0YWJsZVN0cmVhbUFkZFdyaXRlUmVxdWVzdCIsImVucXVldWVFIiwiX2NvbnRyb2xsZWRXcml0YWJsZVN0cmVhbSIsIldyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJHZXRCYWNrcHJlc3N1cmUiLCJXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyV3JpdGUiLCJXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRXJyb3IiLCJfYWJvcnRBbGdvcml0aG0iLCJXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2xlYXJBbGdvcml0aG1zIiwiX3dyaXRlQWxnb3JpdGhtIiwiX2Nsb3NlQWxnb3JpdGhtIiwiciIsIldyaXRhYmxlU3RyZWFtTWFya0Nsb3NlUmVxdWVzdEluRmxpZ2h0Iiwic2lua0Nsb3NlUHJvbWlzZSIsIldyaXRhYmxlU3RyZWFtRmluaXNoSW5GbGlnaHRDbG9zZSIsIldyaXRhYmxlU3RyZWFtRmluaXNoSW5GbGlnaHRDbG9zZVdpdGhFcnJvciIsIldyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJQcm9jZXNzQ2xvc2UiLCJXcml0YWJsZVN0cmVhbU1hcmtGaXJzdFdyaXRlUmVxdWVzdEluRmxpZ2h0IiwiV3JpdGFibGVTdHJlYW1GaW5pc2hJbkZsaWdodFdyaXRlIiwiV3JpdGFibGVTdHJlYW1GaW5pc2hJbkZsaWdodFdyaXRlV2l0aEVycm9yIiwiV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlclByb2Nlc3NXcml0ZSIsIl9yZWFkeVByb21pc2VfcmVzb2x2ZSIsIl9yZWFkeVByb21pc2VfcmVqZWN0IiwiTmF0aXZlRE9NRXhjZXB0aW9uIiwiRE9NRXhjZXB0aW9uIiwiRE9NRXhjZXB0aW9uJDEiLCJfYSIsImlzRE9NRXhjZXB0aW9uQ29uc3RydWN0b3IiLCJjcmVhdGVET01FeGNlcHRpb25Qb2x5ZmlsbCIsIlJlYWRhYmxlU3RyZWFtUGlwZVRvIiwicHJldmVudENsb3NlIiwicHJldmVudEFib3J0Iiwic2h1dHRpbmdEb3duIiwiY3VycmVudFdyaXRlIiwiYWN0aW9ucyIsInNodXRkb3duV2l0aEFjdGlvbiIsImFsbCIsImFjdGlvbiIsImlzT3JCZWNvbWVzRXJyb3JlZCIsInNodXRkb3duIiwiV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyQ2xvc2VXaXRoRXJyb3JQcm9wYWdhdGlvbiIsImRlc3RDbG9zZWQiLCJ3YWl0Rm9yV3JpdGVzVG9GaW5pc2giLCJvbGRDdXJyZW50V3JpdGUiLCJvcmlnaW5hbElzRXJyb3IiLCJvcmlnaW5hbEVycm9yIiwiZG9UaGVSZXN0IiwibmV3RXJyb3IiLCJpc0Vycm9yIiwicmVzb2x2ZUxvb3AiLCJyZWplY3RMb29wIiwicmVzb2x2ZVJlYWQiLCJyZWplY3RSZWFkIiwiUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlciIsIklzUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlciIsImRlZmF1bHRDb250cm9sbGVyQnJhbmRDaGVja0V4Y2VwdGlvbiIsIlJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJHZXREZXNpcmVkU2l6ZSIsIlJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDYW5DbG9zZU9yRW5xdWV1ZSIsIlJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbG9zZSIsIlJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFbnF1ZXVlIiwiUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckVycm9yIiwiUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckNsZWFyQWxnb3JpdGhtcyIsIl9jb250cm9sbGVkUmVhZGFibGVTdHJlYW0iLCJSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2FsbFB1bGxJZk5lZWRlZCIsIlJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJTaG91bGRDYWxsUHVsbCIsIlNldFVwUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlciIsInB1bGxBbGdvcml0aG0iLCJjYW5jZWxBbGdvcml0aG0iLCJjb252ZXJ0VW5kZXJseWluZ1NvdXJjZUNhbmNlbENhbGxiYWNrIiwiY29udmVydFVuZGVybHlpbmdTb3VyY2VQdWxsQ2FsbGJhY2siLCJjb252ZXJ0VW5kZXJseWluZ1NvdXJjZVN0YXJ0Q2FsbGJhY2siLCJjb252ZXJ0UmVhZGFibGVTdHJlYW1UeXBlIiwiY29udmVydFJlYWRhYmxlU3RyZWFtUmVhZGVyTW9kZSIsIm1vZGUiLCJjb252ZXJ0UGlwZU9wdGlvbnMiLCJpc0Fib3J0U2lnbmFsIiwiYXNzZXJ0QWJvcnRTaWduYWwiLCJyYXdVbmRlcmx5aW5nU291cmNlIiwidW5kZXJseWluZ1NvdXJjZSIsInB1bGwiLCJjb252ZXJ0VW5kZXJseWluZ0RlZmF1bHRPckJ5dGVTb3VyY2UiLCJJbml0aWFsaXplUmVhZGFibGVTdHJlYW0iLCJ1bmRlcmx5aW5nQnl0ZVNvdXJjZSIsIlNldFVwUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlciIsIlNldFVwUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckZyb21VbmRlcmx5aW5nU291cmNlIiwiU2V0VXBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRnJvbVVuZGVybHlpbmdTb3VyY2UiLCJzdHJlYW1CcmFuZENoZWNrRXhjZXB0aW9uJDEiLCJyYXdPcHRpb25zIiwiY29udmVydFJlYWRlck9wdGlvbnMiLCJyYXdUcmFuc2Zvcm0iLCJ0cmFuc2Zvcm0iLCJyZWFkYWJsZSIsImNvbnZlcnRSZWFkYWJsZVdyaXRhYmxlUGFpciIsImRlc3RpbmF0aW9uIiwiYnJhbmNoZXMiLCJjbG9uZUZvckJyYW5jaDIiLCJyZWFzb24xIiwicmVhc29uMiIsImJyYW5jaDEiLCJicmFuY2gyIiwicmVzb2x2ZUNhbmNlbFByb21pc2UiLCJyZWFkaW5nIiwiY2FuY2VsZWQxIiwiY2FuY2VsZWQyIiwiY2FuY2VsUHJvbWlzZSIsInZhbHVlMSIsInZhbHVlMiIsIkNyZWF0ZVJlYWRhYmxlU3RyZWFtIiwiY29tcG9zaXRlUmVhc29uIiwiY2FuY2VsUmVzdWx0IiwiUmVhZGFibGVTdHJlYW1UZWUiLCJpbXBsIiwiQWNxdWlyZVJlYWRhYmxlU3RyZWFtQXN5bmNJdGVyYXRvciIsImNvbnZlcnRJdGVyYXRvck9wdGlvbnMiLCJjb252ZXJ0UXVldWluZ1N0cmF0ZWd5SW5pdCIsInBpcGVUaHJvdWdoIiwicGlwZVRvIiwidGVlIiwiYXN5bmNJdGVyYXRvciIsImJ5dGVMZW5ndGhTaXplRnVuY3Rpb24iLCJCeXRlTGVuZ3RoUXVldWluZ1N0cmF0ZWd5IiwiX2J5dGVMZW5ndGhRdWV1aW5nU3RyYXRlZ3lIaWdoV2F0ZXJNYXJrIiwiSXNCeXRlTGVuZ3RoUXVldWluZ1N0cmF0ZWd5IiwiYnl0ZUxlbmd0aEJyYW5kQ2hlY2tFeGNlcHRpb24iLCJjb3VudFNpemVGdW5jdGlvbiIsIkNvdW50UXVldWluZ1N0cmF0ZWd5IiwiX2NvdW50UXVldWluZ1N0cmF0ZWd5SGlnaFdhdGVyTWFyayIsIklzQ291bnRRdWV1aW5nU3RyYXRlZ3kiLCJjb3VudEJyYW5kQ2hlY2tFeGNlcHRpb24iLCJjb252ZXJ0VHJhbnNmb3JtZXJGbHVzaENhbGxiYWNrIiwiY29udmVydFRyYW5zZm9ybWVyU3RhcnRDYWxsYmFjayIsImNvbnZlcnRUcmFuc2Zvcm1lclRyYW5zZm9ybUNhbGxiYWNrIiwiVHJhbnNmb3JtU3RyZWFtIiwicmF3VHJhbnNmb3JtZXIiLCJyYXdXcml0YWJsZVN0cmF0ZWd5IiwicmF3UmVhZGFibGVTdHJhdGVneSIsIndyaXRhYmxlU3RyYXRlZ3kiLCJyZWFkYWJsZVN0cmF0ZWd5IiwidHJhbnNmb3JtZXIiLCJyZWFkYWJsZVR5cGUiLCJ3cml0YWJsZVR5cGUiLCJjb252ZXJ0VHJhbnNmb3JtZXIiLCJyZWFkYWJsZUhpZ2hXYXRlck1hcmsiLCJyZWFkYWJsZVNpemVBbGdvcml0aG0iLCJ3cml0YWJsZUhpZ2hXYXRlck1hcmsiLCJ3cml0YWJsZVNpemVBbGdvcml0aG0iLCJzdGFydFByb21pc2VfcmVzb2x2ZSIsInN0YXJ0UHJvbWlzZSIsIl93cml0YWJsZSIsIkNyZWF0ZVdyaXRhYmxlU3RyZWFtIiwiX3RyYW5zZm9ybVN0cmVhbUNvbnRyb2xsZXIiLCJfYmFja3ByZXNzdXJlQ2hhbmdlUHJvbWlzZSIsIlRyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyUGVyZm9ybVRyYW5zZm9ybSIsIlRyYW5zZm9ybVN0cmVhbURlZmF1bHRTaW5rV3JpdGVBbGdvcml0aG0iLCJfcmVhZGFibGUiLCJmbHVzaFByb21pc2UiLCJfZmx1c2hBbGdvcml0aG0iLCJUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0Q29udHJvbGxlckNsZWFyQWxnb3JpdGhtcyIsIlRyYW5zZm9ybVN0cmVhbUVycm9yIiwiVHJhbnNmb3JtU3RyZWFtRGVmYXVsdFNpbmtDbG9zZUFsZ29yaXRobSIsIlRyYW5zZm9ybVN0cmVhbURlZmF1bHRTaW5rQWJvcnRBbGdvcml0aG0iLCJUcmFuc2Zvcm1TdHJlYW1TZXRCYWNrcHJlc3N1cmUiLCJUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0U291cmNlUHVsbEFsZ29yaXRobSIsIlRyYW5zZm9ybVN0cmVhbUVycm9yV3JpdGFibGVBbmRVbmJsb2NrV3JpdGUiLCJfYmFja3ByZXNzdXJlQ2hhbmdlUHJvbWlzZV9yZXNvbHZlIiwiSW5pdGlhbGl6ZVRyYW5zZm9ybVN0cmVhbSIsIlRyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyIiwidHJhbnNmb3JtQWxnb3JpdGhtIiwiVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFbnF1ZXVlIiwidHJhbnNmb3JtUmVzdWx0RSIsImZsdXNoQWxnb3JpdGhtIiwiX2NvbnRyb2xsZWRUcmFuc2Zvcm1TdHJlYW0iLCJfdHJhbnNmb3JtQWxnb3JpdGhtIiwiU2V0VXBUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0Q29udHJvbGxlciIsIlNldFVwVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJGcm9tVHJhbnNmb3JtZXIiLCJJc1RyYW5zZm9ybVN0cmVhbSIsInN0cmVhbUJyYW5kQ2hlY2tFeGNlcHRpb24kMiIsIklzVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIiLCJkZWZhdWx0Q29udHJvbGxlckJyYW5kQ2hlY2tFeGNlcHRpb24kMSIsIlRyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyVGVybWluYXRlIiwicmVhZGFibGVDb250cm9sbGVyIiwiUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckhhc0JhY2twcmVzc3VyZSIsInRlcm1pbmF0ZSIsInJlcXVpcmUiLCJfX3dlYnBhY2tfbW9kdWxlX2NhY2hlX18iLCJfX3dlYnBhY2tfcmVxdWlyZV9fIiwibW9kdWxlSWQiLCJfX3dlYnBhY2tfbW9kdWxlc19fIiwiZGVmaW5pdGlvbiIsIm8iLCJwcm9wIl0sIm1hcHBpbmdzIjoiO0NBQUEsU0FBMkNBLEVBQU1DLEdBQzFCLGlCQUFaQyxTQUEwQyxpQkFBWEMsT0FDeENBLE9BQU9ELFFBQVVELElBQ1EsbUJBQVhHLFFBQXlCQSxPQUFPQyxJQUM5Q0QsT0FBTyxHQUFJSCxHQUNlLGlCQUFaQyxRQUNkQSxRQUFpQixRQUFJRCxJQUVyQkQsRUFBYyxRQUFJQyxJQVJwQixDQVNHSyxNQUFNLFdBQ1QsTSx3Q0NKQUMsT0FBT0MsZUFBZU4sRUFBUyxhQUEvQixDQUErQ08sT0FBTyxJQUV0RCxJQUFJQyxFQUFrQixFQUFRLEtBTTlCLE1BQU1DLFVBQW9CRCxFQUFnQkUsWUFJdEMsY0FFSSxNQURBQyxRQUNNLElBQUlDLFVBQVUsOENBS3hCLGNBQ0ksTUFBTUMsRUFBVUMsRUFBYUMsSUFBSVgsTUFDakMsR0FBdUIsa0JBQVpTLEVBQ1AsTUFBTSxJQUFJRCxVQUFVLDJEQUFtRSxPQUFUUixLQUFnQixjQUFnQkEsT0FFbEgsT0FBT1MsR0FHZkwsRUFBZ0JRLHFCQUFxQlAsRUFBWVEsVUFBVyxTQXVCNUQsTUFBTUgsRUFBZSxJQUFJSSxRQUV6QmIsT0FBT2MsaUJBQWlCVixFQUFZUSxVQUFXLENBQzNDSixRQUFTLENBQUVPLFlBQVksS0FHTCxtQkFBWEMsUUFBdUQsaUJBQXZCQSxPQUFPQyxhQUM5Q2pCLE9BQU9DLGVBQWVHLEVBQVlRLFVBQVdJLE9BQU9DLFlBQWEsQ0FDN0RDLGNBQWMsRUFDZGhCLE1BQU8sZ0JBUWYsTUFBTWlCLEVBSUYsY0FDSUMsRUFBUUMsSUFBSXRCLEtBekNwQixXQUNJLE1BQU11QixFQUFTdEIsT0FBT3VCLE9BQU9uQixFQUFZUSxXQUd6QyxPQUZBVCxFQUFnQkUsWUFBWW1CLEtBQUtGLEdBQ2pDYixFQUFhWSxJQUFJQyxHQUFRLEdBQ2xCQSxFQXFDZUcsSUFLdEIsYUFDSSxPQUFPQyxFQUFVM0IsTUFLckIsUUEzQ0osSUFBcUJ1QixJQTRDREksRUFBVTNCLE9BM0NPLElBQTdCVSxFQUFhQyxJQUFJWSxLQUdyQmIsRUFBYVksSUFBSUMsR0FBUSxHQUN6QkEsRUFBT0ssY0FBYyxDQUFFQyxLQUFNLFlBNkNqQyxNQUFNUixFQUFVLElBQUlQLFFBSXBCLFNBQVNhLEVBQVVHLEdBQ2YsTUFBTVAsRUFBU0YsRUFBUVYsSUFBSW1CLEdBQzNCLEdBQWMsTUFBVlAsRUFDQSxNQUFNLElBQUlmLFVBQVUsK0RBQTZFLE9BQWZzQixFQUFzQixjQUFnQkEsSUFFNUgsT0FBT1AsRUFHWHRCLE9BQU9jLGlCQUFpQkssRUFBZ0JQLFVBQVcsQ0FDL0NVLE9BQVEsQ0FBRVAsWUFBWSxHQUN0QmUsTUFBTyxDQUFFZixZQUFZLEtBRUgsbUJBQVhDLFFBQXVELGlCQUF2QkEsT0FBT0MsYUFDOUNqQixPQUFPQyxlQUFla0IsRUFBZ0JQLFVBQVdJLE9BQU9DLFlBQWEsQ0FDakVDLGNBQWMsRUFDZGhCLE1BQU8sb0JBSWZQLEVBQVF3QixnQkFBa0JBLEVBQzFCeEIsRUFBUVMsWUFBY0EsRUFDdEJULEVBQVFvQyxRQUFVWixFQUVsQnZCLEVBQU9ELFFBQVV3QixFQUNqQnZCLEVBQU9ELFFBQVF3QixnQkFBa0J2QixFQUFPRCxRQUFQLFFBQTRCd0IsRUFDN0R2QixFQUFPRCxRQUFRUyxZQUFjQSxHLHNLQzdIN0IsZ0JBR0EsYUFHRSxXQUFZNEIsR0FDVmpDLEtBQUtrQyxTQUFXRCxFQU1wQixPQUhFLFlBQUFFLE9BQUEsU0FBT0MsR0FDTCxPQUFPLElBQUksVUFBT0EsRUFBU3BDLEtBQUtrQyxXQUVwQyxFQVZBLEcsdVpDSEEsZ0JBSUEsV0FDQSxZQUNBLFlBQ0EsWUFDQSxZQUNBLFlBQ0EsWUFDQSxZQUNBLFlBQ0EsWUFDQSxZLFVBa0JFLFNBQVlFLEVBQWtCRixHQUM1QixJQUFNRyxFQUF5QixLQUFLRCxHQU1wQyxHQUpLQyxFQUFPQyxNQUNWRCxFQUFPQyxJQUFNLDRCQUdWRCxFQUFPRSxTQUNWLE1BQU0sSUFBSUMsTUFBTSxvQ0FHbEIsSUFBS0gsRUFBT0ksSUFDVixNQUFNLElBQUlELE1BQU0sK0JBSWxCeEMsS0FBSzBDLFFBQVUsSUFBSSxVQUFRTCxFQUFRSCxHQUVuQ2xDLEtBQUsyQyxRQUFVLElBQUksVUFBYTNDLEtBQUswQyxTQUNyQzFDLEtBQUs0QyxTQUFXLElBQUksVUFBYzVDLEtBQUswQyxTQUN2QzFDLEtBQUs2QyxPQUFTLElBQUksVUFBWTdDLEtBQUswQyxTQUNuQzFDLEtBQUs4QyxNQUFRLElBQUksVUFBWTlDLEtBQUswQyxTQUNsQzFDLEtBQUsrQyxhQUFlLElBQUksVUFBa0IvQyxLQUFLMEMsU0FDL0MxQyxLQUFLZ0QsU0FBVyxJQUFJLFVBQWVoRCxLQUFLMEMsU0FDeEMxQyxLQUFLaUQsT0FBUyxJQUFJLFVBQWFqRCxLQUFLMEMsU0FDcEMxQyxLQUFLa0QsSUFBTSxJQUFJLFVBQVVsRCxLQUFLMEMsU0FDOUIxQyxLQUFLbUQsU0FBVyxJQUFJLFVBQWNuRCxLQUFLMEMsU0FFbkNMLEVBQU9lLGFBQ1RmLEVBQU9JLElBQU1KLEVBQU9lLFdBRXBCcEQsS0FBS3FELGVBQWlCLElBQUksVUFBUWhCLEVBQVFILEdBQzFDbEMsS0FBS3NELFNBQVcsSUFBSSxVQUFldEQsS0FBS3FELGdCQUN4Q3JELEtBQUt1RCxNQUFRLElBQUksVUFBWXZELEtBQUtxRCxtQixxS0NqRXhDLGVBZ0JBLEVBY0UsU0FBWUcsRUFBa0JDLEVBQWlCQyxHQUM3QzFELEtBQUsyRCxLQUFPSCxFQUFLRyxLQUNqQjNELEtBQUs0RCxZQUFjSixFQUFLSSxZQUN4QjVELEtBQUs2RCxrQkFBb0JMLEVBQUtLLGtCQUM5QjdELEtBQUs4RCxNQUFRTixFQUFLTSxNQUNsQjlELEtBQUsrRCxTQUFXUCxFQUFLTyxTQUNyQi9ELEtBQUtnRSxZQUFjUixFQUFLUSxZQUN4QmhFLEtBQUtpRSxXQUFhVCxFQUFLUyxXQUN2QmpFLEtBQUtrRSxjQUFnQlYsRUFBS1UsY0FDMUJsRSxLQUFLbUUsV0FBYVgsRUFBS1csV0FDdkJuRSxLQUFLNkIsS0FBTzJCLEVBQUszQixLQUVqQjdCLEtBQUtvRSxzQkFBd0JYLEdBQWEsS0FDMUN6RCxLQUFLcUUsb0JBQXNCWCxHQUFXLE1BSTFDLGFBR0UsV0FBWWhCLEdBQ1YxQyxLQUFLMEMsUUFBVUEsRUF5Rm5CLE9BdEZFLFlBQUE0QixjQUFBLFNBQWNDLEdBQ1osT0FBT0EsRUFBU0MsTUFHbEIsWUFBQUMsaUJBQUEsU0FBaUJGLEdBQ2YsT0FBT0EsRUFBU0MsS0FBS0UsTUFBTUMsS0FBSSxTQUFVQyxHQUN2QyxPQUFPLElBQUlDLEVBQU9ELE9BSXRCLFlBQUFFLGFBQUEsU0FBYVAsR0FPWCxPQUFPLElBQUlNLEVBQ1ROLEVBQVNDLEtBQUtPLE9BQ2RSLEVBQVNDLEtBQUtKLHNCQUNkRyxFQUFTQyxLQUFLSCxzQkFJbEIsWUFBQVcsdUJBQUEsU0FBdUJULEdBQ3JCLE9BQU9BLEVBQVNDLEtBQUtTLFVBR3ZCLFlBQUFDLHFCQUFBLFNBQXFCWCxHQUNuQixPQUFPQSxFQUFTQyxNQUdsQixZQUFBVyxLQUFBLFNBQUtDLEdBQ0gsT0FBT3BGLEtBQUswQyxRQUFRL0IsSUFBSSxjQUFleUUsR0FDcENDLEtBQUtyRixLQUFLeUUsbUJBR2YsWUFBQTlELElBQUEsU0FBSW9FLEdBQ0YsT0FBTy9FLEtBQUswQyxRQUFRL0IsSUFBSSxlQUFlb0UsR0FDcENNLEtBQUtyRixLQUFLOEUsZUFHZixZQUFBdEQsT0FBQSxTQUFPZ0MsR0FDTCxPQUFPeEQsS0FBSzBDLFFBQVE0QyxLQUFLLGNBQWU5QixHQUNyQzZCLEtBQUtyRixLQUFLOEUsZUFHZixZQUFBUyxRQUFBLFNBQVFSLEdBQ04sT0FBTy9FLEtBQUswQyxRQUFROEMsT0FBTyxlQUFlVCxHQUN2Q00sS0FBS3JGLEtBQUtzRSxnQkFLZixZQUFBbUIsWUFBQSxTQUFZVixHQUNWLE9BQU8vRSxLQUFLMEMsUUFBUS9CLElBQUksVUFBUSxjQUFlb0UsRUFBUSxhQUNwRE0sS0FBS3JGLEtBQUtnRix5QkFHZixZQUFBVSxlQUFBLFNBQWVYLEVBQWdCbEQsRUFBYzJCLEdBQzNDLE9BQU94RCxLQUFLMEMsUUFBUWlELElBQUksVUFBUSxjQUFlWixFQUFRLFdBQVlsRCxHQUFPMkIsR0FDdkU2QixLQUFLckYsS0FBS2tGLHVCQUtmLFlBQUFVLE9BQUEsU0FBT2IsR0FDTCxPQUFPL0UsS0FBSzBDLFFBQVEvQixJQUFJLFVBQVEsY0FBZW9FLEVBQVEsUUFDcERNLE1BQUssU0FBQ2QsR0FBdUMsYUFBbUIsUUFBbkIsRUFBS0EsYUFBUSxFQUFSQSxFQUFVQyxZQUFJLGVBQUVFLFVBR3ZFLFlBQUFtQixTQUFBLFNBQVNkLEVBQWdCZSxHQUN2QixPQUFPOUYsS0FBSzBDLFFBQVE0QyxLQUFLLFVBQVEsY0FBZVAsRUFBUSxPQUFRLENBQUVlLEdBQUUsS0FHdEUsWUFBQUMsU0FBQSxTQUFTaEIsRUFBZ0JlLEdBQ3ZCLE9BQU85RixLQUFLMEMsUUFBUThDLE9BQU8sVUFBUSxjQUFlVCxFQUFRLE1BQU9lLEtBR25FLFlBQUFFLFdBQUEsU0FBV2pCLEVBQWdCa0IsR0FDekIsT0FBT2pHLEtBQUswQyxRQUFRNEMsS0FBSyxVQUFRLGNBQWVQLEVBQVEsT0FBUSxDQUFFa0IsUUFBTyxLQUczRSxZQUFBQyxhQUFBLFNBQWFuQixFQUFnQmtCLEVBQWlCSCxHQUM1QyxPQUFPOUYsS0FBSzBDLFFBQVE4QyxPQUFPLFVBQVEsY0FBZVQsRUFBUSxNQUFPLFdBQVksQ0FBRWtCLFFBQU8sRUFBRUgsR0FBRSxLQUU5RixFQTdGQSxHLG1jQzdDQSxrQkFLRSxXQUFZLEcsSUFDVkssRUFBTSxTQUNOQyxFQUFVLGFBQ1ZDLEVBQU8sVUFDUCxJQUFBN0IsWUFBSSxJQUFHLEtBQUUsRUFKWCxPQU1tQjhCLEVBQXVCOUIsRUFBWixRQUFFK0IsRUFBVS9CLEVBQUwsTSxPQUNuQyxnQkFBTyxNQUVGZ0MsTUFBUSxLQUNiLEVBQUtMLE9BQVNBLEVBQ2QsRUFBS0UsUUFBVUEsR0FBV0UsR0FBU0gsRUFDbkMsRUFBS0ssUUFBVUgsRSxFQUVuQixPQW5Cc0MsT0FtQnRDLEVBbkJBLENBQXNDOUQsTyx5RkNGdEMsSUFBTWtFLEVBQVUsRUFBUSxJQUl4QixHQUZrQixFQUFRLEtBRTFCLFdBR0UsV0FBWWhFLEdBQ1YxQyxLQUFLMEMsUUFBVUEsRUF3Q25CLE9BckNFLFlBQUFpRSxpQkFBQSxTQUFpQnJFLEdBQ2YsT0FBT0EsRUFBSXNFLE1BQU0sS0FBS0MsT0FHeEIsWUFBQUMsV0FBQSxTQUFXQyxFQUFZekUsR0FDckIsTUFBTyxDQUFFeUUsR0FBRSxFQUFFQyxPQUFRaEgsS0FBSzJHLGlCQUFpQnJFLEdBQU1BLElBQUcsSUFHdEQsWUFBQTJFLGdCQUFBLFNBQWdCMUMsR0FBaEIsV0FFRSxPQURjdEUsT0FBT2lILFFBQVEzQyxFQUFTQyxLQUFLMkMsUUFDOUJDLFFBQ1gsU0FBQ0MsRUFBVSxHLElBQUNOLEVBQUUsS0FBRXpFLEVBQUcsS0FFakIsT0FEQStFLEVBQUlOLEdBQU0sRUFBS0QsV0FBV0MsRUFBSXpFLEdBQ3ZCK0UsSUFDTixLQUdQLFlBQUFDLGdCQUFBLFNBQWdCL0MsR0FDZCxNQUFPLENBQ0xHLE1BQU9ILEVBQVNDLEtBQUtFLE1BQ3JCNkMsTUFBT3ZILEtBQUtpSCxnQkFBZ0IxQyxLQUloQyxZQUFBNUQsSUFBQSxTQUFJb0UsRUFBZ0JLLEdBQXBCLElBQ005QyxFQUROLE9BVUUsT0FQSThDLEdBQVNBLEVBQU1vQyxNQUNqQmxGLEVBQU1vRSxFQUFRLE1BQU8zQixFQUFRLFNBQVVLLEVBQU1vQyxhQUN0Q3BDLEVBQU1vQyxNQUVibEYsRUFBTW9FLEVBQVEsTUFBTzNCLEVBQVEsVUFHeEIvRSxLQUFLMEMsUUFBUS9CLElBQUkyQixFQUFLOEMsR0FDMUJDLE1BQUssU0FBQ2QsR0FBb0QsU0FBSytDLGdCQUFMLE9BRWpFLEVBNUNBLEkseUZDSmtCLEVBQVEsS0FBMUIsSUFJQSxhQUdFLFdBQVk1RSxHQUNWMUMsS0FBSzBDLFFBQVVBLEVBMEJuQixPQXZCRSxZQUFBeUMsS0FBQSxTQUFLQyxHQUFMLFdBQ0UsT0FBT3BGLEtBQUswQyxRQUFRL0IsSUFBSSxlQUFnQnlFLEdBQ3JDQyxNQUFLLFNBQUNkLEdBQThELFNBQUtrRCxxQkFBTCxPQUd6RSxZQUFBakcsT0FBQSxTQUFPZ0MsR0FDTCxPQUFPeEQsS0FBSzBDLFFBQVE0QyxLQUFLLGVBQWdCOUIsR0FDdEM2QixNQUFLLFNBQUNkLEdBQXdELE9BQUtBLGFBQVEsRUFBUkEsRUFBVUMsU0FHbEYsWUFBQWtELE9BQUEsU0FBT0MsRUFBZ0JuRSxHQUNyQixPQUFPeEQsS0FBSzBDLFFBQVFrRixNQUFNLGdCQUFnQkQsRUFBVW5FLEdBQ2pENkIsTUFBSyxTQUFDZCxHQUF1QixPQUFLQSxhQUFRLEVBQVJBLEVBQVVDLFNBR2pELFlBQUFnQixPQUFBLFNBQU9tQyxFQUFnQm5FLEdBQ3JCLE9BQU94RCxLQUFLMEMsUUFBUThDLE9BQU8sZ0JBQWdCbUMsRUFBVW5FLEdBQ2xENkIsTUFBSyxTQUFDZCxHQUF1QixPQUFLQSxhQUFRLEVBQVJBLEVBQVVDLFNBR3pDLFlBQUFpRCxxQkFBUixTQUE2QmxELEdBQzNCLE9BQU9BLEVBQVNDLEtBQUtyQixVQUV6QixFQTlCQSxHLHlGQ0prQixFQUFRLEtBQTFCLElBR0EsYUFHRSxXQUFZVCxHQUNWMUMsS0FBSzBDLFFBQVVBLEVBZ0JuQixPQWJFLFlBQUF5QyxLQUFBLFNBQUtDLEdBQUwsV0FDRSxPQUFPcEYsS0FBSzBDLFFBQVEvQixJQUFJLFVBQVd5RSxHQUNoQ0MsTUFBSyxTQUFDZCxHQUE0QyxTQUFLc0QsaUJBQUwsT0FHdkQsWUFBQWxILElBQUEsU0FBSW1GLEdBQUosV0FDRSxPQUFPOUYsS0FBSzBDLFFBQVEvQixJQUFJLFdBQVdtRixHQUNoQ1QsTUFBSyxTQUFDZCxHQUErQixTQUFLc0QsaUJBQUwsT0FHbEMsWUFBQUEsaUJBQVIsU0FBeUJ0RCxHQUN2QixPQUFPQSxFQUFTQyxNQUVwQixFQXBCQSxHLHVGQ0RBLGlCQUdFLFdBQVk5QixHQUNWMUMsS0FBSzBDLFFBQVVBLEVBb0JuQixPQWpCRSxZQUFBb0YsZUFBQSxTQUFldkQsR0FDYixPQUFJQSxFQUFTQyxLQUNKRCxFQUFTQyxLQUdYRCxHQUdULFlBQUEvQyxPQUFBLFNBQU91RCxFQUFnQnZCLEdBQ3JCLE9BQUlBLEVBQUs2QyxRQUNBckcsS0FBSzBDLFFBQVFxRixVQUFVLE9BQU9oRCxFQUFNLGlCQUFrQnZCLEdBQzVENkIsS0FBS3JGLEtBQUs4SCxnQkFHTjlILEtBQUswQyxRQUFRcUYsVUFBVSxPQUFPaEQsRUFBTSxZQUFhdkIsR0FDckQ2QixLQUFLckYsS0FBSzhILGlCQUVqQixFQXhCQSxHLHVGQ0FBLGlCQUdFLFdBQVlwRixHQUNWMUMsS0FBSzBDLFFBQVVBLEVBbUJuQixPQWhCRSxZQUFBL0IsSUFBQSxTQUFJcUgsRUFBOEJDLEdBQ2hDLElBQU03QyxFQUFRLEdBWWQsT0FWSThDLE1BQU1DLFFBQVFILEtBQ2hCQSxFQUFZQSxFQUFVSSxLQUFLLE1BRzdCaEQsRUFBTTRDLFVBQVlBLEVBRWRDLElBQ0Y3QyxFQUFNaUQsYUFBYyxHQUdmckksS0FBSzBDLFFBQVEvQixJQUFJLG9CQUFxQnlFLEdBQzFDQyxNQUFLLFNBQUNkLEdBQWEsT0FBQUEsRUFBQSxTQUUxQixFQXZCQSxHLGt4RENEQSxnQkFDQSxXQUNBLFlBRUEsWUFJTStELEVBQVcsU0FBQ0MsR0FBb0IsTUFBc0IsaUJBQWZBLEdBQVAsbUJBQXlDQSxFQUFXQyxNQUVwRkMsRUFBdUIsU0FBQzdELEdBQzVCLEdBQW9CLGlCQUFUQSxHQUFxQjBELEVBQVMxRCxHQUFPLE1BQU8sR0FHckQsSUFBQThELEVBR0U5RCxFQUhNLFNBQ1IrRCxFQUVFL0QsRUFGUyxZQUNYZ0UsRUFDRWhFLEVBRFMsWUFHYixnQkFDTThELEVBQVcsQ0FBRUEsU0FBUSxHQUFLLENBQUVBLFNBQVUsU0FDdENDLEdBQWUsQ0FBRUEsWUFBVyxJQUM1QkMsR0FBZSxDQUFFQSxZQUFXLEtBYXBDLGFBT0UsV0FBWXhHLEVBQXlCRixHQUNuQ2xDLEtBQUt1QyxTQUFXSCxFQUFRRyxTQUN4QnZDLEtBQUt5QyxJQUFNTCxFQUFRSyxJQUNuQnpDLEtBQUtzQyxJQUFNRixFQUFRRSxJQUNuQnRDLEtBQUs2SSxRQUFVekcsRUFBUXlHLFNBQVcsR0FDbEM3SSxLQUFLa0MsU0FBV0EsRUFvSXBCLE9BaklRLFlBQUFRLFFBQU4sU0FBY29HLEVBQWdCeEcsRUFBYUYsRyw0R0FzQnhCLE9BckJYMkcsRUFBUSxVQUFRL0ksS0FBS3VDLFNBQVEsSUFBSXZDLEtBQUt5QyxLQUN0Q29HLEVBQVUsRUFBSCxHQUNYRyxjQUFlLFNBQVNELEdBQ3JCL0ksS0FBSzZJLFNBQ0x6RyxhQUFPLEVBQVBBLEVBQVN5RyxTQUdQekcsa0JBQVN5RyxRQUVYQSxFQUFRLHdCQUVKQSxFQUFRLGdCQUdYSSxFQUFTLEVBQUgsR0FBUTdHLElBRWhCQSxhQUFPLEVBQVBBLEVBQVNnRCxRQUFTbkYsT0FBT2lKLG9CQUFvQjlHLGFBQU8sRUFBUEEsRUFBU2dELE9BQU8rRCxPQUFTLElBQ3hFRixFQUFPRyxhQUFlaEgsRUFBUWdELGFBQ3ZCNkQsRUFBTzdELE9BR0MsR0FBTSxVQUNyQixVQUFRcEYsS0FBS3NDLElBQUtBLEdBQUksR0FFcEJ3RyxPQUFRQSxFQUFPTyxvQkFDZlIsUUFBTyxFQUNQUyxpQkFBaUIsR0FDZEwsSyxjQUlGMUUsT0FWQ0EsRUFBVyxlQVVKLEVBQVJBLEVBQVVnRixJQUFYLE9BQ2NoRixhQUFRLEVBQVJBLEVBQVVDLE9BQVE4RCxFQUFTL0QsRUFBU0MsTUFDaEQsSUExRGNnRixFQTBET2pGLEVBQVNDLEtBekRoQ2lGLEVBQWMsR0FDYixJQUFJQyxTQUFRLFNBQUNDLEVBQVNDLEdBQzNCSixFQUFPSyxHQUFHLFFBQVEsU0FBQ0MsR0FBZSxPQUFBTCxFQUFPTSxLQUFQRCxNQUNsQ04sRUFBT0ssR0FBRyxRQUFTRCxHQUNuQkosRUFBT0ssR0FBRyxPQUFPLFdBQU0sT0FBQUYsRUFBUUssT0FBT0MsT0FBT1IsR0FBUVMsU0FBOUIsaUJBb0RMLE0sY0FDWixXLGFBQ0EsU0FBTTNGLGFBQVEsRUFBUkEsRUFBVTRGLFEsT0FBaEIsVyxpQkFFSixNQUpNOUQsRUFBVSxFQUlWLElBQUksVUFBUyxDQUNqQkYsT0FBUTVCLGFBQVEsRUFBUkEsRUFBVTRCLE9BQ2xCQyxXQUFZN0IsYUFBUSxFQUFSQSxFQUFVNkIsV0FDdEI1QixLQUFNLENBQUU2QixRQUFPLEssT0FLWCxPLEtBQUEsR0FBTTlCLGFBQVEsRUFBUkEsRUFBVTRGLFEsT0FEeEIsVUFDRSxFQUFBM0YsS0FBTSxTQUNOLEVBQUEyQixPQUFRNUIsYUFBUSxFQUFSQSxFQUFVNEIsT0FDbEIsSUF2RWlCLElBQUNxRCxFQUNoQkMsU0F5RU4sWUFBQXJFLE1BQUEsU0FBTTBELEVBQWdCeEcsRUFBYThDLEVBQVloRCxHQUM3QyxPQUFPcEMsS0FBSzBDLFFBQVFvRyxFQUFReEcsRUFBRyxHQUFJOEMsTUFBSyxHQUFLaEQsS0FHL0MsWUFBQWdJLFFBQUEsU0FBUXRCLEVBQWdCeEcsRUFBYWtCLEVBQVdwQixHQUM5QyxPQUFPcEMsS0FBSzBDLFFBQVFvRyxFQUFReEcsRUFBRyxHQUM3QnVHLFFBQVMsQ0FBRSxlQUFnQixxQ0FDM0JyRSxLQUFNaEIsR0FDSHBCLEtBSVAsWUFBQXpCLElBQUEsU0FBSTJCLEVBQWE4QyxFQUFhaEQsR0FDNUIsT0FBT3BDLEtBQUtvRixNQUFNLE1BQU85QyxFQUFLOEMsRUFBT2hELElBR3ZDLFlBQUFpSSxLQUFBLFNBQUsvSCxFQUFhOEMsRUFBWWhELEdBQzVCLE9BQU9wQyxLQUFLb0YsTUFBTSxPQUFROUMsRUFBSzhDLEVBQU9oRCxJQUd4QyxZQUFBQSxRQUFBLFNBQVFFLEVBQWE4QyxFQUFZaEQsR0FDL0IsT0FBT3BDLEtBQUtvRixNQUFNLFVBQVc5QyxFQUFLOEMsRUFBT2hELElBRzNDLFlBQUFrRCxLQUFBLFNBQUtoRCxFQUFha0IsRUFBV3BCLEdBQzNCLE9BQU9wQyxLQUFLb0ssUUFBUSxPQUFROUgsRUFBS2tCLEVBQU1wQixJQUd6QyxZQUFBMkYsVUFBQSxTQUFVekYsRUFBYWtCLEdBRXJCLElBQU10QixFQUFxQixJQUFJbEMsS0FBS2tDLFNBbUNwQyxPQTlCQWpDLE9BQU9xSyxLQUFLOUcsR0FDVCtHLFFBQU8sU0FBVTlILEdBQU8sT0FBT2UsRUFBS2YsTUFDcEMrSCxTQUFRLFNBQVUvSCxHQUNqQixHQUFZLGVBQVJBLEVBa0JBeUYsTUFBTUMsUUFBUTNFLEVBQUtmLElBQ3JCZSxFQUFLZixHQUFLK0gsU0FBUSxTQUFVNUYsR0FDMUIxQyxFQUFTdUksT0FBT2hJLEVBQUttQyxNQUd2QjFDLEVBQVN1SSxPQUFPaEksRUFBS2UsRUFBS2YsUUF2QjVCLENBQ0UsSUFBTWlJLEVBQU1sSCxFQUFLK0UsV0FFakIsR0FBSUwsTUFBTUMsUUFBUXVDLEdBQ2hCQSxFQUFJRixTQUFRLFNBQVU1RixHQUNwQixJQUFNcEIsRUFBT29CLEVBQUtwQixLQUFPb0IsRUFBS3BCLEtBQU9vQixFQUMvQnhDLEVBQVVxRyxFQUFxQjdELEdBQ3BDMUMsRUFBaUJ1SSxPQUFPaEksRUFBS2UsRUFBTXBCLFVBRWpDLENBQ0wsSUFBTSxFQUFPa0csRUFBU29DLEdBQU9BLEVBQU1BLEVBQUlsSCxLQUNqQ3BCLEVBQVVxRyxFQUFxQmlDLEdBQ3BDeEksRUFBaUJ1SSxPQUFPaEksRUFBSyxFQUFNTCxRQWVyQ3BDLEtBQUtvSyxRQUFRLE9BQVE5SCxFQUFLSixFQWxDYixDQUNsQjJHLFFBQVMsQ0FBRSxlQUFnQixTQW9DL0IsWUFBQWxELElBQUEsU0FBSXJELEVBQWFrQixFQUFXcEIsR0FDMUIsT0FBT3BDLEtBQUtvSyxRQUFRLE1BQU85SCxFQUFLa0IsRUFBTXBCLElBR3hDLFlBQUF3RixNQUFBLFNBQU10RixFQUFha0IsRUFBV3BCLEdBQzVCLE9BQU9wQyxLQUFLb0ssUUFBUSxRQUFTOUgsRUFBS2tCLEVBQU1wQixJQUcxQyxZQUFBb0QsT0FBQSxTQUFPbEQsRUFBYWtCLEVBQVlwQixHQUM5QixPQUFPcEMsS0FBS29LLFFBQVEsU0FBVTlILEVBQUtrQixFQUFNcEIsSUFFN0MsRUFoSkEsR0FrSkEsVUFBZXVJLEcsMEVDcExmLGlCQUdFLFdBQVlqSSxHQUNWMUMsS0FBSzBDLFFBQVVBLEVBMkJuQixPQXhCRSxZQUFBeUMsS0FBQSxTQUFLQyxHQUNILE9BQU9wRixLQUFLMEMsUUFBUS9CLElBQUksYUFBY3lFLEdBQ25DQyxNQUFLLFNBQUNkLEdBQWEsT0FBQUEsRUFBU0MsS0FBVCxVQUd4QixZQUFBN0QsSUFBQSxTQUFJb0csR0FDRixPQUFPL0csS0FBSzBDLFFBQVEvQixJQUFJLGNBQWNvRyxHQUNuQzFCLE1BQUssU0FBQ2QsR0FBYSxPQUFBQSxFQUFTQyxLQUFULFVBR3hCLFlBQUFoRCxPQUFBLFNBQU9nQyxHQUNMLE9BQU94RCxLQUFLMEMsUUFBUTRDLEtBQUssYUFBYzlCLEdBQ3BDNkIsTUFBSyxTQUFDZCxHQUFhLE9BQUFBLEVBQVNDLEtBQVQsVUFHeEIsWUFBQWtELE9BQUEsU0FBT1gsRUFBWXZELEdBQ2pCLE9BQU94RCxLQUFLMEMsUUFBUWlELElBQUksY0FBY29CLEVBQU12RCxHQUN6QzZCLE1BQUssU0FBQ2QsR0FBYSxPQUFBQSxFQUFBLFNBR3hCLFlBQUFnQixRQUFBLFNBQVF3QixHQUNOLE9BQU8vRyxLQUFLMEMsUUFBUThDLE9BQU8sY0FBY3VCLEdBQ3RDMUIsTUFBSyxTQUFDZCxHQUFhLE9BQUFBLEVBQUEsU0FFMUIsRUEvQkEsRyxtTENGQSxlQUlBLEVBTUUsU0FBWWYsR0FDVnhELEtBQUs0SyxNQUFRLElBQUlDLEtBQUtySCxFQUFLb0gsT0FDM0I1SyxLQUFLOEssSUFBTSxJQUFJRCxLQUFLckgsRUFBS3NILEtBQ3pCOUssS0FBSytLLFdBQWF2SCxFQUFLdUgsV0FDdkIvSyxLQUFLOEMsTUFBUVUsRUFBS1YsTUFBTTZCLEtBQUksU0FBVXFHLEdBRXBDLE9BREFBLEVBQUtDLEtBQU8sSUFBSUosS0FBS0csRUFBS0MsTUFDbkJELE1BS2IsYUFHRSxXQUFZdEksR0FDVjFDLEtBQUswQyxRQUFVQSxFQWdCbkIsT0FiRSxZQUFBd0ksWUFBQSxTQUFZM0csR0FDVixPQUFPLElBQUk0RyxFQUFNNUcsRUFBU0MsT0FHNUIsWUFBQTRHLFVBQUEsU0FBVXJHLEVBQWdCSyxHQUN4QixPQUFPcEYsS0FBSzBDLFFBQVEvQixJQUFJLFVBQVEsTUFBT29FLEVBQVEsZUFBZ0JLLEdBQzVEQyxLQUFLckYsS0FBS2tMLGNBR2YsWUFBQUcsV0FBQSxTQUFXakcsR0FDVCxPQUFPcEYsS0FBSzBDLFFBQVEvQixJQUFJLGtCQUFtQnlFLEdBQ3hDQyxLQUFLckYsS0FBS2tMLGNBRWpCLEVBcEJBLEcsbUxDckJBLGdCQUNBLFdBT01JLEVBQWdCLENBQ3BCekMsUUFBUyxDQUFFLGVBQWdCLHFCQUc3QixFQU9FLFNBQVlyRixHQUNWeEQsS0FBSzZCLEtBQU8sVUFDWjdCLEtBQUt1TCxRQUFVL0gsRUFBSytILFFBQ3BCdkwsS0FBS3dMLE1BQVFoSSxFQUFLZ0ksS0FDbEJ4TCxLQUFLdUcsTUFBUS9DLEVBQUsrQyxNQUNsQnZHLEtBQUtpRSxXQUFhLElBQUk0RyxLQUFLckgsRUFBS1MsYUFJcEMsRUFLRSxTQUFZVCxHQUNWeEQsS0FBSzZCLEtBQU8sYUFDWjdCLEtBQUt1TCxRQUFVL0gsRUFBSytILFFBQ3BCdkwsS0FBS2lFLFdBQWEsSUFBSTRHLEtBQUtySCxFQUFLUyxhQUlwQyxFQU1FLFNBQVlULEdBQ1Z4RCxLQUFLNkIsS0FBTyxlQUNaN0IsS0FBS3VMLFFBQVUvSCxFQUFLK0gsUUFDcEJ2TCxLQUFLeUwsS0FBT2pJLEVBQUtpSSxLQUNqQnpMLEtBQUtpRSxXQUFhLElBQUk0RyxLQUFLckgsRUFBS1MsYUFJcEMsYUFRRSxXQUFZdkIsR0FDVjFDLEtBQUswQyxRQUFVQSxFQUNmMUMsS0FBSzBMLE9BQVMsQ0FDWkMsUUFBU0MsRUFDVEMsV0FBWUMsRUFDWkMsYUFBY0MsR0F1RXBCLE9BbkVFLFlBQUFsRixXQUFBLFNBQVdDLEVBQVlrRixHQUNyQixJQUNRN0csRUFEVSxVQUFJN0IsTUFBTTBJLEdBQVMsR0FDeEIsTUFFYixNQUFPLENBQ0xsRixHQUFFLEVBQ0ZTLEtBQU1wQyxFQUFNb0MsS0FDWitELFFBQVNuRyxFQUFNbUcsUUFDZmpKLElBQUsySixJQUlULFlBQUFoRixnQkFBQSxTQUFnQjFDLEdBQWhCLFdBRUUsT0FEY3RFLE9BQU9pSCxRQUFRM0MsRUFBU0MsS0FBSzJDLFFBQzlCQyxRQUNYLFNBQUNDLEVBQVUsRyxJQUFDTixFQUFFLEtBQUV6RSxFQUFHLEtBRWpCLE9BREErRSxFQUFJTixHQUFNLEVBQUtELFdBQVdDLEVBQUl6RSxHQUN2QitFLElBQ04sS0FHUCxZQUFBNkUsV0FBQSxTQUFXM0gsRUFBaUQ0SCxHQUMxRCxJQUFNM0ksRUFBTyxHQU1iLE9BSkFBLEVBQUtrQixNQUFRSCxFQUFTQyxLQUFLRSxNQUFNQyxLQUFJLFNBQUN5SCxHQUFXLFdBQUlELEVBQUosTUFFakQzSSxFQUFLK0QsTUFBUXZILEtBQUtpSCxnQkFBZ0IxQyxHQUUzQmYsR0FHVCxZQUFBNkksV0FBQSxTQUFXOUgsRUFBeUI0SCxHQUNsQyxPQUFPLElBQUlBLEVBQU01SCxFQUFTQyxPQUc1QixZQUFBVyxLQUFBLFNBQUtKLEVBQWdCbEQsRUFBY3VELEdBQW5DLFdBQ1FrSCxFQUFTdE0sS0FBSzBMLE9BQWU3SixHQUVuQyxPQUFPN0IsS0FBSzBDLFFBQ1QvQixJQUFJLFVBQVEsS0FBTW9FLEVBQVFsRCxHQUFPdUQsR0FDakNDLE1BQUssU0FBQ2QsR0FBb0QsU0FBSzJILFdBQVczSCxFQUFoQixPQUcvRCxZQUFBNUQsSUFBQSxTQUFJb0UsRUFBZ0JsRCxFQUFjMEosR0FBbEMsV0FDUWUsRUFBU3RNLEtBQUswTCxPQUFlN0osR0FFbkMsT0FBTzdCLEtBQUswQyxRQUNUL0IsSUFBSSxVQUFRLEtBQU1vRSxFQUFRbEQsRUFBTTBLLG1CQUFtQmhCLEtBQ25EbEcsTUFBSyxTQUFDZCxHQUE0QixTQUFLOEgsV0FBVzlILEVBQWhCLE9BR3ZDLFlBQUEvQyxPQUFBLFNBQU91RCxFQUFnQmxELEVBQWMyQixHQU1uQyxPQUpLMEUsTUFBTUMsUUFBUTNFLEtBQ2pCQSxFQUFPLENBQUNBLElBR0h4RCxLQUFLMEMsUUFDWDRDLEtBQUssVUFBUSxLQUFNUCxFQUFRbEQsR0FBTzJCLEVBQU04SCxHQUN4Q2pHLE1BQUssU0FBQ2QsR0FBNEIsT0FBQUEsRUFBQSxTQUdyQyxZQUFBZ0IsUUFBQSxTQUFRUixFQUFnQmxELEVBQWMwSixHQUNwQyxPQUFPdkwsS0FBSzBDLFFBQ1g4QyxPQUFPLFVBQVEsS0FBTVQsRUFBUWxELEVBQU0wSyxtQkFBbUJoQixLQUN0RGxHLE1BQUssU0FBQ2QsR0FBNEIsT0FBQUEsRUFBQSxTQUV2QyxFQXBGQSxHLFlBc0ZBMUUsRUFBT0QsUUFBVTRNLEcsMEVDeklqQixpQkFHRSxXQUFZOUosR0FDVjFDLEtBQUswQyxRQUFVQSxFQU9uQixPQUpFLFlBQUEvQixJQUFBLFNBQUk0SyxHQUNGLE9BQU92TCxLQUFLMEMsUUFBUS9CLElBQUksdUJBQXdCLENBQUU0SyxRQUFPLElBQ3REbEcsTUFBSyxTQUFDZCxHQUFhLE9BQUFBLEVBQUEsU0FFMUIsRUFYQSxHLG1MQ0hBLGVBR0EsRUFJRSxTQUFZd0MsRUFBWXZELEdBQ3RCeEQsS0FBSytHLEdBQUtBLEVBQ1YvRyxLQUFLc0MsSUFBTWtCLEVBQUtsQixLQUlwQixhQUdFLFdBQVlJLEdBQ1YxQyxLQUFLMEMsUUFBVUEsRUE4Q25CLE9BM0NFLFlBQUErSixrQkFBQSxTQUFrQmxJLEdBQ2hCLE9BQU9BLEVBQVNDLEtBQUs1QixVQUd2QixZQUFBOEosb0JBQUEsU0FBb0IzRixHQUNsQixPQUFPLFNBQVV4QyxHQUNmLE9BQU8sSUFBSW9JLEVBQVE1RixFQUFJeEMsRUFBU0MsS0FBS29JLFdBSXpDLFlBQUFDLGtCQUFBLFNBQWtCdEksR0FDaEIsTUFBTyxDQUFFaUgsS0FBTWpILEVBQVNDLEtBQUtnSCxLQUFNbkYsUUFBUzlCLEVBQVNDLEtBQUs2QixVQUc1RCxZQUFBbEIsS0FBQSxTQUFLSixFQUFnQkssR0FDbkIsT0FBT3BGLEtBQUswQyxRQUFRL0IsSUFBSSxVQUFRLGNBQWVvRSxFQUFRLFlBQWFLLEdBQ2pFQyxLQUFLckYsS0FBS3lNLG9CQUdmLFlBQUE5TCxJQUFBLFNBQUlvRSxFQUFnQmdDLEdBQ2xCLE9BQU8vRyxLQUFLMEMsUUFBUS9CLElBQUksVUFBUSxjQUFlb0UsRUFBUSxXQUFZZ0MsSUFDaEUxQixLQUFLckYsS0FBSzBNLG9CQUFvQjNGLEtBR25DLFlBQUF2RixPQUFBLFNBQU91RCxFQUFnQmdDLEVBQVl6RSxFQUFhd0ssR0FDOUMsT0FBSUEsRUFDSzlNLEtBQUswQyxRQUFRaUQsSUFBSSxVQUFRLGNBQWVaLEVBQVEsV0FBWWdDLEVBQUksUUFBUyxDQUFFekUsSUFBRyxJQUNsRitDLEtBQUtyRixLQUFLNk0sbUJBR1I3TSxLQUFLMEMsUUFBUTRDLEtBQUssVUFBUSxjQUFlUCxFQUFRLFlBQWEsQ0FBRWdDLEdBQUUsRUFBRXpFLElBQUcsSUFDM0UrQyxLQUFLckYsS0FBSzBNLG9CQUFvQjNGLEtBR25DLFlBQUFXLE9BQUEsU0FBTzNDLEVBQWdCZ0MsRUFBWXpFLEdBQ2pDLE9BQU90QyxLQUFLMEMsUUFBUWlELElBQUksVUFBUSxjQUFlWixFQUFRLFdBQVlnQyxHQUFLLENBQUV6RSxJQUFHLElBQzFFK0MsS0FBS3JGLEtBQUswTSxvQkFBb0IzRixLQUduQyxZQUFBeEIsUUFBQSxTQUFRUixFQUFnQmdDLEdBQ3RCLE9BQU8vRyxLQUFLMEMsUUFBUThDLE9BQU8sVUFBUSxjQUFlVCxFQUFRLFdBQVlnQyxJQUNuRTFCLEtBQUtyRixLQUFLME0sb0JBQW9CM0YsS0FFckMsRUFsREEsRyxzQkNiQyxXQUNDLGFBY0FsSCxFQUFPRCxRQVpQLFNBQWNtTixHQVNaLE9BTklBLGFBQWUvQyxPQUNSK0MsRUFFQS9DLE9BQU9nRCxLQUFLRCxFQUFJN0MsV0FBWSxXQUd6QkEsU0FBUyxXQVozQixJLHFCQ29EQXJLLEVBQU9ELFFBNUNQLFNBQXlCcU4sR0FDckIsSUFBSyxVQUFVSCxLQUFLRyxHQUNoQixNQUFNLElBQUl6TSxVQUFVLG9FQUt4QixNQUFNME0sR0FGTkQsRUFBTUEsRUFBSUUsUUFBUSxTQUFVLEtBRUxDLFFBQVEsS0FDL0IsSUFBb0IsSUFBaEJGLEdBQXFCQSxHQUFjLEVBQ25DLE1BQU0sSUFBSTFNLFVBQVUsdUJBR3hCLE1BQU02TSxFQUFPSixFQUFJSyxVQUFVLEVBQUdKLEdBQVl0RyxNQUFNLEtBQ2hELElBQUkyRyxFQUFVLEdBQ1ZDLEdBQVMsRUFDYixNQUFNM0wsRUFBT3dMLEVBQUssSUFBTSxhQUN4QixJQUFJSSxFQUFXNUwsRUFDZixJQUFLLElBQUk2TCxFQUFJLEVBQUdBLEVBQUlMLEVBQUtsRSxPQUFRdUUsSUFDYixXQUFaTCxFQUFLSyxHQUNMRixHQUFTLEdBR1RDLEdBQVksSUFBSUosRUFBS0ssS0FDZSxJQUFoQ0wsRUFBS0ssR0FBR04sUUFBUSxjQUNoQkcsRUFBVUYsRUFBS0ssR0FBR0osVUFBVSxLQUtuQ0QsRUFBSyxJQUFPRSxFQUFRcEUsU0FDckJzRSxHQUFZLG9CQUNaRixFQUFVLFlBR2QsTUFBTUksRUFBV0gsRUFBUyxTQUFXLFFBQy9CaEssRUFBT29LLFNBQVNYLEVBQUlLLFVBQVVKLEVBQWEsSUFDM0NXLEVBQVM3RCxPQUFPZ0QsS0FBS3hKLEVBQU1tSyxHQU1qQyxPQUpBRSxFQUFPaE0sS0FBT0EsRUFDZGdNLEVBQU9KLFNBQVdBLEVBRWxCSSxFQUFPTixRQUFVQSxFQUNWTSxJLHlCQzNDWDVOLE9BQU9DLGVBQWVOLEVBQVMsYUFBL0IsQ0FBK0NPLE9BQU8sSUFxQnRELE1BQU0yTixFQUFjLElBQUloTixRQU9sQmlOLEVBQVcsSUFBSWpOLFFBUXJCLFNBQVNrTixFQUFHQyxHQUNSLE1BQU1DLEVBQU9KLEVBQVluTixJQUFJc04sR0FNN0IsT0FMQUUsUUFBUUMsT0FDSSxNQUFSRixFQUNBLDhDQUNBRCxHQUVHQyxFQU9YLFNBQVNHLEVBQWM3SyxHQUNTLE1BQXhCQSxFQUFLOEssZ0JBWUo5SyxFQUFLeUssTUFBTU0sYUFJaEIvSyxFQUFLZ0wsVUFBVyxFQUN5QixtQkFBOUJoTCxFQUFLeUssTUFBTVEsZ0JBQ2xCakwsRUFBS3lLLE1BQU1RLGtCQWhCWSxvQkFBWk4sU0FDa0IsbUJBQWxCQSxRQUFRNUgsT0FFZjRILFFBQVE1SCxNQUNKLHFFQUNBL0MsRUFBSzhLLGlCQXlCckIsU0FBU0ksRUFBTUMsRUFBYVYsR0FDeEJILEVBQVl4TSxJQUFJdEIsS0FBTSxDQUNsQjJPLGNBQ0FWLFFBQ0FXLFdBQVksRUFDWkMsY0FBZUYsRUFDZkgsVUFBVSxFQUNWTSxTQUFTLEVBQ1RDLGtCQUFrQixFQUNsQlQsZ0JBQWlCLEtBQ2pCVSxVQUFXZixFQUFNZSxXQUFhbkUsS0FBS29FLFFBSXZDaFAsT0FBT0MsZUFBZUYsS0FBTSxZQUFhLENBQUVHLE9BQU8sRUFBT2EsWUFBWSxJQUdyRSxNQUFNc0osRUFBT3JLLE9BQU9xSyxLQUFLMkQsR0FDekIsSUFBSyxJQUFJUCxFQUFJLEVBQUdBLEVBQUlwRCxFQUFLbkIsU0FBVXVFLEVBQUcsQ0FDbEMsTUFBTWpMLEVBQU02SCxFQUFLb0QsR0FDWGpMLEtBQU96QyxNQUNUQyxPQUFPQyxlQUFlRixLQUFNeUMsRUFBS3lNLEVBQXlCek0sS0F5T3RFLFNBQVN5TSxFQUF5QnpNLEdBQzlCLE1BQU8sQ0FDSCxNQUNJLE9BQU91TCxFQUFHaE8sTUFBTWlPLE1BQU14TCxJQUUxQixJQUFJdEMsR0FDQTZOLEVBQUdoTyxNQUFNaU8sTUFBTXhMLEdBQU90QyxHQUUxQmdCLGNBQWMsRUFDZEgsWUFBWSxHQVVwQixTQUFTbU8sRUFBcUIxTSxHQUMxQixNQUFPLENBQ0gsUUFDSSxNQUFNd0wsRUFBUUQsRUFBR2hPLE1BQU1pTyxNQUN2QixPQUFPQSxFQUFNeEwsR0FBSzJNLE1BQU1uQixFQUFPb0IsWUFFbkNsTyxjQUFjLEVBQ2RILFlBQVksR0FtRHBCLFNBQVNzTyxFQUFXQyxHQUNoQixHQUFhLE1BQVRBLEdBQWlCQSxJQUFVdFAsT0FBT1ksVUFDbEMsT0FBTzZOLEVBR1gsSUFBSWMsRUFBVXpCLEVBQVNwTixJQUFJNE8sR0FLM0IsT0FKZSxNQUFYQyxJQUNBQSxFQS9DUixTQUF1QkMsRUFBV0YsR0FDOUIsTUFBTWpGLEVBQU9ySyxPQUFPcUssS0FBS2lGLEdBQ3pCLEdBQW9CLElBQWhCakYsRUFBS25CLE9BQ0wsT0FBT3NHLEVBSVgsU0FBU0MsRUFBWWYsRUFBYVYsR0FDOUJ3QixFQUFVaE8sS0FBS3pCLEtBQU0yTyxFQUFhVixHQUd0Q3lCLEVBQVk3TyxVQUFZWixPQUFPdUIsT0FBT2lPLEVBQVU1TyxVQUFXLENBQ3ZEOE8sWUFBYSxDQUFFeFAsTUFBT3VQLEVBQWF2TyxjQUFjLEVBQU15TyxVQUFVLEtBSXJFLElBQUssSUFBSWxDLEVBQUksRUFBR0EsRUFBSXBELEVBQUtuQixTQUFVdUUsRUFBRyxDQUNsQyxNQUFNakwsRUFBTTZILEVBQUtvRCxHQUNqQixLQUFNakwsS0FBT2dOLEVBQVU1TyxXQUFZLENBQy9CLE1BQ01nUCxFQUFxQyxtQkFEeEI1UCxPQUFPNlAseUJBQXlCUCxFQUFPOU0sR0FDekJ0QyxNQUNqQ0YsT0FBT0MsZUFDSHdQLEVBQVk3TyxVQUNaNEIsRUFDQW9OLEVBQ01WLEVBQXFCMU0sR0FDckJ5TSxFQUF5QnpNLEtBSzNDLE9BQU9pTixFQWdCT0ssQ0FBY1QsRUFBV3JQLE9BQU8rUCxlQUFlVCxJQUFTQSxHQUNsRXhCLEVBQVN6TSxJQUFJaU8sRUFBT0MsSUFFakJBLEVBcUJYLFNBQVNTLEVBQVVoQyxHQUNmLE9BQU9ELEVBQUdDLEdBQU9jLGlCQWdDckIsU0FBU21CLEVBQW1CakMsRUFBT0ssR0FDL0JOLEVBQUdDLEdBQU9LLGdCQUFrQkEsRUFqWGhDSSxFQUFNN04sVUFBWSxDQUtkLFdBQ0ksT0FBT21OLEVBQUdoTyxNQUFNaU8sTUFBTXBNLE1BTzFCLGFBQ0ksT0FBT21NLEVBQUdoTyxNQUFNMk8sYUFPcEIsb0JBQ0ksT0FBT1gsRUFBR2hPLE1BQU02TyxlQU1wQixlQUNJLE1BQU1BLEVBQWdCYixFQUFHaE8sTUFBTTZPLGNBQy9CLE9BQXFCLE1BQWpCQSxFQUNPLEdBRUosQ0FBQ0EsSUFPWixXQUNJLE9BQU8sR0FPWCxzQkFDSSxPQUFPLEdBT1gsZ0JBQ0ksT0FBTyxHQU9YLHFCQUNJLE9BQU8sR0FPWCxpQkFDSSxPQUFPYixFQUFHaE8sTUFBTTRPLFlBT3BCLGtCQUNJLE1BQU1wTCxFQUFPd0ssRUFBR2hPLE1BRWhCd0QsRUFBS3NMLFNBQVUsRUFDMkIsbUJBQS9CdEwsRUFBS3lLLE1BQU1rQyxpQkFDbEIzTSxFQUFLeUssTUFBTWtDLG1CQVFuQiwyQkFDSSxNQUFNM00sRUFBT3dLLEVBQUdoTyxNQUVoQndELEVBQUtzTCxTQUFVLEVBQ2Z0TCxFQUFLdUwsa0JBQW1CLEVBQzJCLG1CQUF4Q3ZMLEVBQUt5SyxNQUFNbUMsMEJBQ2xCNU0sRUFBS3lLLE1BQU1tQyw0QkFRbkIsY0FDSSxPQUFPQyxRQUFRckMsRUFBR2hPLE1BQU1pTyxNQUFNcUMsVUFPbEMsaUJBQ0ksT0FBT0QsUUFBUXJDLEVBQUdoTyxNQUFNaU8sTUFBTU0sYUFPbEMsaUJBQ0lGLEVBQWNMLEVBQUdoTyxRQU9yQix1QkFDSSxPQUFPZ08sRUFBR2hPLE1BQU13TyxVQU9wQixlQUNJLE9BQU82QixRQUFRckMsRUFBR2hPLE1BQU1pTyxNQUFNc0MsV0FPbEMsZ0JBQ0ksT0FBT3ZDLEVBQUdoTyxNQUFNZ1AsV0FRcEIsaUJBQ0ksT0FBT2hCLEVBQUdoTyxNQUFNMk8sYUFRcEIsbUJBQ0ksT0FBT1gsRUFBR2hPLE1BQU04TyxTQUVwQixpQkFBaUIzTyxHQUNiLElBQUtBLEVBQ0QsT0FFSixNQUFNcUQsRUFBT3dLLEVBQUdoTyxNQUVoQndELEVBQUtzTCxTQUFVLEVBQ3dCLGtCQUE1QnRMLEVBQUt5SyxNQUFNdUMsZUFDbEJoTixFQUFLeUssTUFBTXVDLGNBQWUsSUFTbEMsa0JBQ0ksT0FBUXhDLEVBQUdoTyxNQUFNd08sVUFFckIsZ0JBQWdCck8sR0FDUEEsR0FDRGtPLEVBQWNMLEVBQUdoTyxRQVd6QixlQU1KQyxPQUFPQyxlQUFld08sRUFBTTdOLFVBQVcsY0FBZSxDQUNsRFYsTUFBT3VPLEVBQ1B2TixjQUFjLEVBQ2R5TyxVQUFVLElBSVEsb0JBQVhhLGFBQWtELElBQWpCQSxPQUFPL0IsUUFDL0N6TyxPQUFPeVEsZUFBZWhDLEVBQU03TixVQUFXNFAsT0FBTy9CLE1BQU03TixXQUdwRGtOLEVBQVN6TSxJQUFJbVAsT0FBTy9CLE1BQU03TixVQUFXNk4sSUF3S3pDLE1BQU1pQyxFQUFlLElBQUk3UCxRQVl6QixTQUFTOFAsRUFBU0MsR0FDZCxPQUFhLE9BQU5BLEdBQTJCLGlCQUFOQSxFQVNoQyxTQUFTQyxFQUFhbkMsR0FDbEIsTUFBTW9DLEVBQVlKLEVBQWFoUSxJQUFJZ08sR0FDbkMsR0FBaUIsTUFBYm9DLEVBQ0EsTUFBTSxJQUFJdlEsVUFDTixvRUFHUixPQUFPdVEsRUE0RVgsU0FBU25RLEVBQXFCb1EsRUFBc0JDLEdBQ2hEaFIsT0FBT0MsZUFDSDhRLEVBQ0EsS0FBS0MsSUF0RWIsU0FBd0NBLEdBQ3BDLE1BQU8sQ0FDSCxNQUVJLElBQUlDLEVBRGNKLEVBQWE5USxNQUNWVyxJQUFJc1EsR0FDekIsS0FBZSxNQUFSQyxHQUFjLENBQ2pCLEdBdkNFLElBdUNFQSxFQUFLQyxhQUNMLE9BQU9ELEVBQUtFLFNBRWhCRixFQUFPQSxFQUFLRyxLQUVoQixPQUFPLE1BR1gsSUFBSUQsR0FDd0IsbUJBQWJBLEdBQTRCUixFQUFTUSxLQUM1Q0EsRUFBVyxNQUVmLE1BQU1MLEVBQVlELEVBQWE5USxNQUcvQixJQUFJc1IsRUFBTyxLQUNQSixFQUFPSCxFQUFVcFEsSUFBSXNRLEdBQ3pCLEtBQWUsTUFBUkMsR0F4REQsSUF5REVBLEVBQUtDLGFBRVEsT0FBVEcsRUFDQUEsRUFBS0QsS0FBT0gsRUFBS0csS0FDSSxPQUFkSCxFQUFLRyxLQUNaTixFQUFVelAsSUFBSTJQLEVBQVdDLEVBQUtHLE1BRTlCTixFQUFVdkwsT0FBT3lMLEdBR3JCSyxFQUFPSixFQUdYQSxFQUFPQSxFQUFLRyxLQUloQixHQUFpQixPQUFiRCxFQUFtQixDQUNuQixNQUFNRyxFQUFVLENBQ1pILFdBQ0FELGFBN0VGLEVBOEVFSyxTQUFTLEVBQ1RDLE1BQU0sRUFDTkosS0FBTSxNQUVHLE9BQVRDLEVBQ0FQLEVBQVV6UCxJQUFJMlAsRUFBV00sR0FFekJELEVBQUtELEtBQU9FLElBSXhCcFEsY0FBYyxFQUNkSCxZQUFZLEdBY1owUSxDQUErQlQsSUFVdkMsU0FBU1UsRUFBd0JDLEdBRTdCLFNBQVNDLElBQ0x2UixFQUFZbUIsS0FBS3pCLE1BR3JCNlIsRUFBa0JoUixVQUFZWixPQUFPdUIsT0FBT2xCLEVBQVlPLFVBQVcsQ0FDL0Q4TyxZQUFhLENBQ1R4UCxNQUFPMFIsRUFDUDFRLGNBQWMsRUFDZHlPLFVBQVUsS0FJbEIsSUFBSyxJQUFJbEMsRUFBSSxFQUFHQSxFQUFJa0UsRUFBV3pJLFNBQVV1RSxFQUNyQzlNLEVBQXFCaVIsRUFBa0JoUixVQUFXK1EsRUFBV2xFLElBR2pFLE9BQU9tRSxFQWdCWCxTQUFTdlIsSUFFTCxLQUFJTixnQkFBZ0JNLEdBQXBCLENBSUEsR0FBeUIsSUFBckIrTyxVQUFVbEcsUUFBZ0JqQixNQUFNQyxRQUFRa0gsVUFBVSxJQUNsRCxPQUFPc0MsRUFBd0J0QyxVQUFVLElBRTdDLEdBQUlBLFVBQVVsRyxPQUFTLEVBQUcsQ0FDdEIsTUFBTTJJLEVBQVEsSUFBSTVKLE1BQU1tSCxVQUFVbEcsUUFDbEMsSUFBSyxJQUFJdUUsRUFBSSxFQUFHQSxFQUFJMkIsVUFBVWxHLFNBQVV1RSxFQUNwQ29FLEVBQU1wRSxHQUFLMkIsVUFBVTNCLEdBRXpCLE9BQU9pRSxFQUF3QkcsR0FFbkMsTUFBTSxJQUFJdFIsVUFBVSxxQ0FiaEJtUSxFQUFhclAsSUFBSXRCLEtBQU0sSUFBSStSLEtBa0JuQ3pSLEVBQVlPLFVBQVksQ0FRcEIsaUJBQWlCb1EsRUFBV0csRUFBVWhQLEdBQ2xDLEdBQWdCLE1BQVpnUCxFQUNBLE9BRUosR0FBd0IsbUJBQWJBLElBQTRCUixFQUFTUSxHQUM1QyxNQUFNLElBQUk1USxVQUFVLGlEQUd4QixNQUFNdVEsRUFBWUQsRUFBYTlRLE1BQ3pCZ1MsRUFBZXBCLEVBQVN4TyxHQUl4QitPLEdBSFVhLEVBQ1YzQixRQUFRak8sRUFBUTZQLFNBQ2hCNUIsUUFBUWpPLElBL0xOLEVBQ0QsRUFnTURtUCxFQUFVLENBQ1pILFdBQ0FELGVBQ0FLLFFBQVNRLEdBQWdCM0IsUUFBUWpPLEVBQVFvUCxTQUN6Q0MsS0FBTU8sR0FBZ0IzQixRQUFRak8sRUFBUXFQLE1BQ3RDSixLQUFNLE1BSVYsSUFBSUgsRUFBT0gsRUFBVXBRLElBQUlzUSxHQUN6QixRQUFhaUIsSUFBVGhCLEVBRUEsWUFEQUgsRUFBVXpQLElBQUkyUCxFQUFXTSxHQUs3QixJQUFJRCxFQUFPLEtBQ1gsS0FBZSxNQUFSSixHQUFjLENBQ2pCLEdBQ0lBLEVBQUtFLFdBQWFBLEdBQ2xCRixFQUFLQyxlQUFpQkEsRUFHdEIsT0FFSkcsRUFBT0osRUFDUEEsRUFBT0EsRUFBS0csS0FJaEJDLEVBQUtELEtBQU9FLEdBVWhCLG9CQUFvQk4sRUFBV0csRUFBVWhQLEdBQ3JDLEdBQWdCLE1BQVpnUCxFQUNBLE9BR0osTUFBTUwsRUFBWUQsRUFBYTlRLE1BSXpCbVIsR0FIVVAsRUFBU3hPLEdBQ25CaU8sUUFBUWpPLEVBQVE2UCxTQUNoQjVCLFFBQVFqTyxJQWpQTixFQUNELEVBbVBQLElBQUlrUCxFQUFPLEtBQ1BKLEVBQU9ILEVBQVVwUSxJQUFJc1EsR0FDekIsS0FBZSxNQUFSQyxHQUFjLENBQ2pCLEdBQ0lBLEVBQUtFLFdBQWFBLEdBQ2xCRixFQUFLQyxlQUFpQkEsRUFTdEIsWUFQYSxPQUFURyxFQUNBQSxFQUFLRCxLQUFPSCxFQUFLRyxLQUNJLE9BQWRILEVBQUtHLEtBQ1pOLEVBQVV6UCxJQUFJMlAsRUFBV0MsRUFBS0csTUFFOUJOLEVBQVV2TCxPQUFPeUwsSUFLekJLLEVBQU9KLEVBQ1BBLEVBQU9BLEVBQUtHLE9BU3BCLGNBQWNwRCxHQUNWLEdBQWEsTUFBVEEsR0FBdUMsaUJBQWZBLEVBQU1wTSxLQUM5QixNQUFNLElBQUlyQixVQUFVLG9DQUl4QixNQUFNdVEsRUFBWUQsRUFBYTlRLE1BQ3pCaVIsRUFBWWhELEVBQU1wTSxLQUN4QixJQUFJcVAsRUFBT0gsRUFBVXBRLElBQUlzUSxHQUN6QixHQUFZLE1BQVJDLEVBQ0EsT0FBTyxFQUlYLE1BQU1pQixFQTlWZCxTQUFtQnhELEVBQWFWLEdBRTVCLE9BQU8sSUFEU3FCLEVBQVdyUCxPQUFPK1AsZUFBZS9CLElBQzFDLENBQVlVLEVBQWFWLEdBNFZQbUUsQ0FBVXBTLEtBQU1pTyxHQUlyQyxJQUFJcUQsRUFBTyxLQUNYLEtBQWUsTUFBUkosR0FBYyxDQW1CakIsR0FqQklBLEVBQUtPLEtBQ1EsT0FBVEgsRUFDQUEsRUFBS0QsS0FBT0gsRUFBS0csS0FDSSxPQUFkSCxFQUFLRyxLQUNaTixFQUFVelAsSUFBSTJQLEVBQVdDLEVBQUtHLE1BRTlCTixFQUFVdkwsT0FBT3lMLEdBR3JCSyxFQUFPSixFQUlYaEIsRUFDSWlDLEVBQ0FqQixFQUFLTSxRQUFVTixFQUFLRSxTQUFXLE1BRU4sbUJBQWxCRixFQUFLRSxTQUNaLElBQ0lGLEVBQUtFLFNBQVMzUCxLQUFLekIsS0FBTW1TLEdBQzNCLE1BQU9FLEdBRWtCLG9CQUFabEUsU0FDa0IsbUJBQWxCQSxRQUFRNUgsT0FFZjRILFFBQVE1SCxNQUFNOEwsUUEzVHBCLElBK1RGbkIsRUFBS0MsY0FDZ0MsbUJBQTlCRCxFQUFLRSxTQUFTa0IsYUFFckJwQixFQUFLRSxTQUFTa0IsWUFBWUgsR0FJOUIsR0FBSWxDLEVBQVVrQyxHQUNWLE1BR0pqQixFQUFPQSxFQUFLRyxLQU1oQixPQUpBbkIsRUFBbUJpQyxFQUFjLE1Belh6QyxTQUF1QmxFLEVBQU9XLEdBQzFCWixFQUFHQyxHQUFPVyxXQXlYc0IsRUFBNUIyRCxDQUFjSixHQS9XdEIsU0FBMEJsRSxFQUFPWSxHQUM3QmIsRUFBR0MsR0FBT1ksY0ErV3lCLEtBQS9CMkQsQ0FBaUJMLElBRVRBLEVBQWFNLG1CQUs3QnhTLE9BQU9DLGVBQWVJLEVBQVlPLFVBQVcsY0FBZSxDQUN4RFYsTUFBT0csRUFDUGEsY0FBYyxFQUNkeU8sVUFBVSxJQUtRLG9CQUFYYSxhQUN1QixJQUF2QkEsT0FBT25RLGFBRWRMLE9BQU95USxlQUFlcFEsRUFBWU8sVUFBVzRQLE9BQU9uUSxZQUFZTyxXQUdwRWpCLEVBQVFnQixxQkFBdUJBLEVBQy9CaEIsRUFBUVUsWUFBY0EsRUFDdEJWLEVBQVFvQyxRQUFVMUIsRUFFbEJULEVBQU9ELFFBQVVVLEVBQ2pCVCxFQUFPRCxRQUFRVSxZQUFjVCxFQUFPRCxRQUFQLFFBQTRCVSxFQUN6RFQsRUFBT0QsUUFBUWdCLHFCQUF1QkEsRyxhQ3IyQnRDLE1BQU0sU0FBQzhSLEdBQVksRUFBUSxLQUtyQkMsRUFBSyxJQUFJN1IsUUFZZixNQUFNOFIsRUFTTCxZQUFZQyxFQUFZLEdBQUl6USxFQUFVLENBQUNQLEtBQU0sS0FDNUMsSUFBSWlSLEVBQU8sRUFFWCxNQUFNQyxFQUFRRixFQUFVbE8sS0FBSXFPLElBQzNCLElBQUluRixFQWNKLE9BWkNBLEVBREdtRixhQUFtQmhKLE9BQ2JnSixFQUNDQyxZQUFZQyxPQUFPRixHQUNwQmhKLE9BQU9nRCxLQUFLZ0csRUFBUW5GLE9BQVFtRixFQUFRRyxXQUFZSCxFQUFRSSxZQUN2REosYUFBbUJDLFlBQ3BCakosT0FBT2dELEtBQUtnRyxHQUNYQSxhQUFtQkosRUFDcEJJLEVBRUFoSixPQUFPZ0QsS0FBd0IsaUJBQVpnRyxFQUF1QkEsRUFBVUssT0FBT0wsSUFHckVGLEdBQVFqRixFQUFPMUUsUUFBVTBFLEVBQU9pRixNQUFRLEVBQ2pDakYsS0FHRmhNLE9BQXdCcVEsSUFBakI5UCxFQUFRUCxLQUFxQixHQUFLd1IsT0FBT2pSLEVBQVFQLE1BQU15UixjQUVwRVgsRUFBR3JSLElBQUl0QixLQUFNLENBQ1o2QixLQUFNLG1CQUFtQmlMLEtBQUtqTCxHQUFRLEdBQUtBLEVBQzNDaVIsT0FDQUMsVUFRRixXQUNDLE9BQU9KLEVBQUdoUyxJQUFJWCxNQUFNOFMsS0FNckIsV0FDQyxPQUFPSCxFQUFHaFMsSUFBSVgsTUFBTTZCLEtBVXJCLGFBQ0MsT0FBT21JLE9BQU9nRCxXQUFXaE4sS0FBS3VULGVBQWVySixXQVU5QyxvQkFDQyxNQUFNMUcsRUFBTyxJQUFJZ1EsV0FBV3hULEtBQUs4UyxNQUNqQyxJQUFJVyxFQUFTLEVBQ2IsVUFBVyxNQUFNM0osS0FBUzlKLEtBQUt3SixTQUM5QmhHLEVBQUtsQyxJQUFJd0ksRUFBTzJKLEdBQ2hCQSxHQUFVM0osRUFBTVgsT0FHakIsT0FBTzNGLEVBQUtxSyxPQVNiLFNBQ0MsT0FBTzZFLEVBQVMxRixLQXBHbEIwRyxnQkFBc0JYLEdBQ3JCLElBQUssTUFBTVksS0FBUVosRUFDZCxXQUFZWSxRQUNQQSxFQUFLbkssZUFFUG1LLEVBK0ZjQyxDQUFLakIsRUFBR2hTLElBQUlYLE1BQU0rUyxRQVl4QyxNQUFNbkksRUFBUSxFQUFHRSxFQUFNOUssS0FBSzhTLEtBQU1qUixFQUFPLElBQ3hDLE1BQU0sS0FBQ2lSLEdBQVE5UyxLQUVmLElBQUk2VCxFQUFnQmpKLEVBQVEsRUFBSWtKLEtBQUtDLElBQUlqQixFQUFPbEksRUFBTyxHQUFLa0osS0FBS0UsSUFBSXBKLEVBQU9rSSxHQUN4RW1CLEVBQWNuSixFQUFNLEVBQUlnSixLQUFLQyxJQUFJakIsRUFBT2hJLEVBQUssR0FBS2dKLEtBQUtFLElBQUlsSixFQUFLZ0ksR0FFcEUsTUFBTW9CLEVBQU9KLEtBQUtDLElBQUlFLEVBQWNKLEVBQWUsR0FDN0NkLEVBQVFKLEVBQUdoUyxJQUFJWCxNQUFNK1MsTUFBTW9CLFNBQzNCdEIsRUFBWSxHQUNsQixJQUFJdUIsRUFBUSxFQUVaLElBQUssTUFBTVQsS0FBUVosRUFBTyxDQUN6QixNQUFNRCxFQUFPRyxZQUFZQyxPQUFPUyxHQUFRQSxFQUFLUCxXQUFhTyxFQUFLYixLQUMvRCxHQUFJZSxHQUFpQmYsR0FBUWUsRUFHNUJBLEdBQWlCZixFQUNqQm1CLEdBQWVuQixNQUNULENBQ04sTUFBTWhKLEVBQVE2SixFQUFLVSxNQUFNUixFQUFlQyxLQUFLRSxJQUFJbEIsRUFBTW1CLElBTXZELEdBTEFwQixFQUFVOUksS0FBS0QsR0FDZnNLLEdBQVNuQixZQUFZQyxPQUFPcEosR0FBU0EsRUFBTXNKLFdBQWF0SixFQUFNZ0osS0FDOURlLEVBQWdCLEVBR1pPLEdBQVNGLEVBQ1osT0FLSCxNQUFNSSxFQUFPLElBQUkxQixFQUFLLEdBQUksQ0FBQy9RLFNBRzNCLE9BRkE1QixPQUFPc1UsT0FBTzVCLEVBQUdoUyxJQUFJMlQsR0FBTyxDQUFDeEIsS0FBTW9CLEVBQU1uQixNQUFPRixJQUV6Q3lCLEVBR1IzVCxJQUFLTSxPQUFPQyxlQUNYLE1BQU8sT0FHUixPQUFRRCxPQUFPdVQsYUFBYUMsR0FDM0IsTUFDbUIsaUJBQVhBLEdBQ2tCLG1CQUFsQkEsRUFBT2pMLFFBQ1csSUFBekJpTCxFQUFPakwsT0FBT0wsUUFDZ0IsbUJBQXZCc0wsRUFBTzlFLGFBQ2QsZ0JBQWdCN0MsS0FBSzJILEVBQU94VCxPQUFPQyxlQUt0Q2pCLE9BQU9jLGlCQUFpQjZSLEVBQUsvUixVQUFXLENBQ3ZDaVMsS0FBTSxDQUFDOVIsWUFBWSxHQUNuQmEsS0FBTSxDQUFDYixZQUFZLEdBQ25CcVQsTUFBTyxDQUFDclQsWUFBWSxLQUdyQm5CLEVBQU9ELFFBQVVnVCxHLDJCQ2hMakIsTUFBTThCLEVBQVEsRUFBUSxLQUNoQnRULEVBQWtCLEVBQVEsS0F3QmhDLEdBcEJLdVQsT0FBT0QsUUFDWEMsT0FBT0QsTUFBUSxDQUFDcFMsRUFBS0YsSUFBWXNTLEVBQU1wUyxFQUFLLENBQUNzUyxjQUh4QixPQUd5RHhTLEtBRzFFdVMsT0FBT0UsVUFDWEYsT0FBT0UsUUFBVUgsRUFBTUcsU0FHbkJGLE9BQU9oSyxVQUNYZ0ssT0FBT2hLLFFBQVUrSixFQUFNL0osU0FHbkJnSyxPQUFPRyxXQUNYSCxPQUFPRyxTQUFXSixFQUFNSSxVQUdwQkgsT0FBT3ZULGtCQUNYdVQsT0FBT3ZULGdCQUFrQkEsSUFHckJ1VCxPQUFPSSxlQUNYLElBQ0NKLE9BQU9JLGVBQWlCLEVBQVEsS0FDL0IsTUFBT0MsSUFHVm5WLEVBQU9ELFFBQVUsRUFBakIsTSxnQkNoQ0MsSUFBa0JELElBSVgsV0FBZSxhQUl0QixNQUFNc1YsRUFBVSxHQUVWQyxFQUFZQyxHQUVHLG9CQUFUQyxNQUF3QkEsTUFBUUQsS0FBWUMsS0FDL0NBLEtBSWMsb0JBQVgzRSxRQUEwQkEsUUFBVTBFLEtBQVkxRSxPQUNuREEsT0FHYyxvQkFBWGtFLFFBQTBCQSxRQUFVUSxLQUFZUixPQUNuREEsT0FJa0Isb0JBQWZVLFlBQThCQSxXQUNqQ0EsZ0JBRFIsRUFLS0MsRUFBbUIsQ0FDeEIsVUFDQSxVQUNBLFdBQ0EsaUJBQ0EsUUFDQSxrQkFDQSxZQUdELElBQUssTUFBTUgsS0FBWUcsRUFDdEJyVixPQUFPQyxlQUFlK1UsRUFBU0UsRUFBVSxDQUN4QyxNQUNDLE1BQU1JLEVBQWVMLEVBQVVDLEdBQ3pCaFYsRUFBUW9WLEdBQWdCQSxFQUFhSixHQUMzQyxNQUF3QixtQkFBVmhWLEVBQXVCQSxFQUFNcVYsS0FBS0QsR0FBZ0JwVixLQUtuRSxNQUFNeVEsRUFBV3pRLEdBQW1CLE9BQVZBLEdBQW1DLGlCQUFWQSxFQUM3Q3NWLEVBQTZELG1CQUE1QlIsRUFBUTdULGdCQUN6Q3NVLEVBQW9ELG1CQUEzQlQsRUFBUUYsZUFDakNZLEVBQStDLG1CQUFyQlYsRUFBUWhULFNBRWxDMlQsRUFBZSxDQUFDQyxFQUFTQyxLQUM5QixNQUFNQyxFQUFTLElBQUlkLEVBQVFKLFFBQVFnQixHQUFXLElBQ3hDRyxFQUFvQkYsYUFBbUJiLEVBQVFKLFFBQy9Db0IsRUFBUyxJQUFJaEIsRUFBUUosUUFBUWlCLEdBQVcsSUFFOUMsSUFBSyxNQUFPclQsRUFBS3RDLEtBQVU4VixFQUNyQkQsR0FBK0IsY0FBVjdWLFFBQW9DK1IsSUFBVi9SLEVBQ25ENFYsRUFBT3ZRLE9BQU8vQyxHQUVkc1QsRUFBT3pVLElBQUltQixFQUFLdEMsR0FJbEIsT0FBTzRWLEdBR0ZHLEVBQVksSUFBSUMsS0FDckIsSUFBSUMsRUFBYyxHQUNkdk4sRUFBVSxHQUVkLElBQUssTUFBTW9OLEtBQVVFLEVBQVMsQ0FDN0IsR0FBSWpPLE1BQU1DLFFBQVE4TixHQUNYL04sTUFBTUMsUUFBUWlPLEtBQ25CQSxFQUFjLElBR2ZBLEVBQWMsSUFBSUEsS0FBZ0JILFFBQzVCLEdBQUlyRixFQUFTcUYsR0FBUyxDQUM1QixJQUFLLElBQUt4VCxFQUFLdEMsS0FBVUYsT0FBT2lILFFBQVErTyxHQUNuQ3JGLEVBQVN6USxJQUFXc0MsS0FBTzJULElBQzlCalcsRUFBUStWLEVBQVVFLEVBQVkzVCxHQUFNdEMsSUFHckNpVyxFQUFjLElBQUlBLEVBQWEsQ0FBQzNULEdBQU10QyxHQUduQ3lRLEVBQVNxRixFQUFPcE4sV0FDbkJBLEVBQVUrTSxFQUFhL00sRUFBU29OLEVBQU9wTixVQUl6Q3VOLEVBQVl2TixRQUFVQSxFQUd2QixPQUFPdU4sR0FHRkMsRUFBaUIsQ0FDdEIsTUFDQSxPQUNBLE1BQ0EsUUFDQSxPQUNBLFVBR0tDLEVBQWdCLENBQ3JCbk0sS0FBTSxtQkFDTm9NLEtBQU0sU0FDTnJVLFNBQVUsc0JBQ1ZxUixZQUFhLE1BQ2JlLEtBQU0sT0FzQkRrQyxFQUF3QixDQUM3QixJQUNBLElBQ0EsS0FHS0MsRUFBT3hWLE9BQU8sUUFFcEIsTUFBTXlWLFVBQWtCbFUsTUFDdkIsWUFBWStCLEdBR1hoRSxNQUNDZ0UsRUFBUzZCLFlBQ1RpTixPQUNzQixJQUFwQjlPLEVBQVM0QixRQUFnQjVCLEVBQVM0QixPQUNsQzVCLEVBQVM0QixPQUFTLDJCQUdyQm5HLEtBQUsyRCxLQUFPLFlBQ1ozRCxLQUFLdUUsU0FBV0EsR0FJbEIsTUFBTW9TLFVBQXFCblUsTUFDMUIsWUFBWUUsR0FDWG5DLE1BQU0scUJBQ05QLEtBQUsyRCxLQUFPLGVBQ1ozRCxLQUFLMEMsUUFBVUEsR0FJakIsTUFBTWtVLEVBQVFDLEdBQU0sSUFBSW5OLFNBQVFDLEdBQVdtTixXQUFXbk4sRUFBU2tOLEtBdUJ6REUsRUFBeUJDLEdBQVNYLEVBQWVZLFNBQVNELEdBQVNBLEVBQU1FLGNBQWdCRixFQUV6RkcsRUFBc0IsQ0FDM0JDLE1BQU8sRUFDUEMsUUE5RW9CLENBQ3BCLE1BQ0EsTUFDQSxPQUNBLFNBQ0EsVUFDQSxTQXlFQUMsWUF0RXdCLENBQ3hCLElBQ0EsSUFDQSxJQUNBLElBQ0EsSUFDQSxJQUNBLEtBZ0VBQyxpQkFBa0JmLEdBR2JnQixFQUF3QixDQUFDQyxFQUFRLE1BQ3RDLEdBQXFCLGlCQUFWQSxFQUNWLE1BQU8sSUFDSE4sRUFDSEMsTUFBT0ssR0FJVCxHQUFJQSxFQUFNSixVQUFZblAsTUFBTUMsUUFBUXNQLEVBQU1KLFNBQ3pDLE1BQU0sSUFBSTdVLE1BQU0sa0NBR2pCLEdBQUlpVixFQUFNSCxjQUFnQnBQLE1BQU1DLFFBQVFzUCxFQUFNSCxhQUM3QyxNQUFNLElBQUk5VSxNQUFNLHNDQUdqQixNQUFPLElBQ0gyVSxLQUNBTSxFQUNIRixpQkFBa0JmLElBS2RrQixFQUFpQixXQUV2QixNQUFNQyxFQUNMLFlBQVlYLEVBQU81VSxFQUFVLElBcUI1QixHQXBCQXBDLEtBQUs0WCxZQUFjLEVBQ25CNVgsS0FBSzZYLE9BQVNiLEVBQ2RoWCxLQUFLOFgsU0FBVyxDQUVmQyxZQUFhL1gsS0FBSzZYLE9BQU9FLGFBQWUsaUJBQ3JDM1YsRUFDSHlHLFFBQVMrTSxFQUFhNVYsS0FBSzZYLE9BQU9oUCxRQUFTekcsRUFBUXlHLFNBQ25EbVAsTUFBTzlCLEVBQVUsQ0FDaEIrQixjQUFlLEdBQ2ZDLFlBQWEsR0FDYkMsY0FBZSxJQUNiL1YsRUFBUTRWLE9BQ1hsUCxPQUFRaU8sRUFBdUIzVSxFQUFRMEcsUUFBVTlJLEtBQUs2WCxPQUFPL08sUUFDN0RzUCxVQUFXL0UsT0FBT2pSLEVBQVFnVyxXQUFhLElBQ3ZDWCxNQUFPRCxFQUFzQnBWLEVBQVFxVixPQUNyQ25PLGlCQUE2QyxJQUE1QmxILEVBQVFrSCxnQkFDekIrTyxhQUFvQyxJQUFwQmpXLEVBQVFpVyxRQUEwQixJQUFRalcsRUFBUWlXLFFBQ2xFM0QsTUFBT3RTLEVBQVFzUyxPQUFTTyxFQUFRUCxPQUdOLGlCQUFoQjFVLEtBQUs2WCxVQUF5QjdYLEtBQUs2WCxrQkFBa0JTLEtBQU90WSxLQUFLNlgsa0JBQWtCNUMsRUFBUXRLLFNBQ3JHLE1BQU0sSUFBSW5LLFVBQVUsNkNBR3JCLEdBQUlSLEtBQUs4WCxTQUFTTSxXQUFvQyxpQkFBaEJwWSxLQUFLNlgsT0FBcUIsQ0FDL0QsR0FBSTdYLEtBQUs2WCxPQUFPVSxXQUFXLEtBQzFCLE1BQU0sSUFBSS9WLE1BQU0sOERBR1p4QyxLQUFLOFgsU0FBU00sVUFBVUksU0FBUyxPQUNyQ3hZLEtBQUs4WCxTQUFTTSxXQUFhLEtBRzVCcFksS0FBSzZYLE9BQVM3WCxLQUFLOFgsU0FBU00sVUFBWXBZLEtBQUs2WCxPQWdCOUMsR0FiSXBDLElBQ0h6VixLQUFLeVksZ0JBQWtCLElBQUl4RCxFQUFRN1QsZ0JBQy9CcEIsS0FBSzhYLFNBQVN2VyxRQUNqQnZCLEtBQUs4WCxTQUFTdlcsT0FBT21YLGlCQUFpQixTQUFTLEtBQzlDMVksS0FBS3lZLGdCQUFnQjFXLFdBSXZCL0IsS0FBSzhYLFNBQVN2VyxPQUFTdkIsS0FBS3lZLGdCQUFnQmxYLFFBRzdDdkIsS0FBSzBDLFFBQVUsSUFBSXVTLEVBQVF0SyxRQUFRM0ssS0FBSzZYLE9BQVE3WCxLQUFLOFgsVUFFakQ5WCxLQUFLOFgsU0FBUzFPLGFBQWMsQ0FDL0IsTUFBTUEsRUFBZSxJQUFNLElBQUl1UCxnQkFBZ0IzWSxLQUFLOFgsU0FBUzFPLGNBQWNjLFdBQ3JFNUgsRUFBTXRDLEtBQUswQyxRQUFRSixJQUFJNkssUUFBUSxvQkFBcUIvRCxLQUdwRHVNLEdBQW9CM1YsS0FBSzhYLFNBQVN0VCxnQkFBZ0J5USxFQUFRaFQsVUFBYWpDLEtBQUs4WCxTQUFTdFQsZ0JBQWdCbVUsa0JBQXNCM1ksS0FBSzhYLFNBQVNqUCxTQUFXN0ksS0FBSzhYLFNBQVNqUCxRQUFRLGlCQUMvSzdJLEtBQUswQyxRQUFRbUcsUUFBUXJELE9BQU8sZ0JBRzdCeEYsS0FBSzBDLFFBQVUsSUFBSXVTLEVBQVF0SyxRQUFRLElBQUlzSyxFQUFRdEssUUFBUXJJLEVBQUt0QyxLQUFLMEMsU0FBVTFDLEtBQUs4WCxlQUd0RDVGLElBQXZCbFMsS0FBSzhYLFNBQVMzTixPQUNqQm5LLEtBQUs4WCxTQUFTdFQsS0FBT29VLEtBQUtDLFVBQVU3WSxLQUFLOFgsU0FBUzNOLE1BQ2xEbkssS0FBSzBDLFFBQVFtRyxRQUFRdkgsSUFBSSxlQUFnQixvQkFDekN0QixLQUFLMEMsUUFBVSxJQUFJdVMsRUFBUXRLLFFBQVEzSyxLQUFLMEMsUUFBUyxDQUFDOEIsS0FBTXhFLEtBQUs4WCxTQUFTdFQsUUFHdkUsTUFBTXNVLEVBQUtwRixVQUNWLEdBQUkxVCxLQUFLOFgsU0FBU08sUUFBVVgsRUFDM0IsTUFBTSxJQUFJcUIsV0FBVyxnRUFHaEJuQyxFQUFNLEdBQ1osSUFBSXJTLFFBQWlCdkUsS0FBS2daLFNBRTFCLElBQUssTUFBTUMsS0FBUWpaLEtBQUs4WCxTQUFTRSxNQUFNRyxjQUFlLENBRXJELE1BQU1lLFFBQXlCRCxFQUM5QmpaLEtBQUswQyxRQUNMMUMsS0FBSzhYLFNBQ0w5WCxLQUFLbVosa0JBQWtCNVUsRUFBUzZVLFVBRzdCRixhQUE0QmpFLEVBQVFILFdBQ3ZDdlEsRUFBVzJVLEdBTWIsR0FGQWxaLEtBQUttWixrQkFBa0I1VSxJQUVsQkEsRUFBU2dGLElBQU12SixLQUFLOFgsU0FBU3hPLGdCQUNqQyxNQUFNLElBQUlvTixFQUFVblMsR0FLckIsR0FBSXZFLEtBQUs4WCxTQUFTdUIsbUJBQW9CLENBQ3JDLEdBQWdELG1CQUFyQ3JaLEtBQUs4WCxTQUFTdUIsbUJBQ3hCLE1BQU0sSUFBSTdZLFVBQVUsc0RBR3JCLElBQUtrVixFQUNKLE1BQU0sSUFBSWxULE1BQU0sK0VBR2pCLE9BQU94QyxLQUFLc1osUUFBUS9VLEVBQVM2VSxRQUFTcFosS0FBSzhYLFNBQVN1QixvQkFHckQsT0FBTzlVLEdBSUZ3UixFQURvQi9WLEtBQUs4WCxTQUFTTCxNQUFNSixRQUFRSixTQUFTalgsS0FBSzBDLFFBQVFvRyxPQUFPd0ssZUFDaER0VCxLQUFLdVosT0FBT1QsR0FBTUEsSUFFckQsSUFBSyxNQUFPalgsRUFBTTJYLEtBQWF2WixPQUFPaUgsUUFBUW9QLEdBQzdDUCxFQUFPbFUsR0FBUTZSLFVBQ2QxVCxLQUFLMEMsUUFBUW1HLFFBQVF2SCxJQUFJLFNBQVV0QixLQUFLMEMsUUFBUW1HLFFBQVFsSSxJQUFJLFdBQWE2WSxHQUV6RSxNQUFNalYsU0FBa0J3UixHQUFRcUQsUUFFaEMsR0FBYSxTQUFUdlgsRUFBaUIsQ0FDcEIsR0FBd0IsTUFBcEIwQyxFQUFTNEIsT0FDWixNQUFPLEdBR1IsR0FBSS9ELEVBQVFxWCxVQUNYLE9BQU9yWCxFQUFRcVgsZ0JBQWdCbFYsRUFBU2dTLFFBSTFDLE9BQU9oUyxFQUFTMUMsTUFJbEIsT0FBT2tVLEVBR1IscUJBQXFCeFAsR0FHcEIsR0FGQXZHLEtBQUs0WCxjQUVENVgsS0FBSzRYLFlBQWM1WCxLQUFLOFgsU0FBU0wsTUFBTUwsU0FBVzdRLGFBQWlCb1EsR0FBZSxDQUNyRixHQUFJcFEsYUFBaUJtUSxFQUFXLENBQy9CLElBQUsxVyxLQUFLOFgsU0FBU0wsTUFBTUgsWUFBWUwsU0FBUzFRLEVBQU1oQyxTQUFTNEIsUUFDNUQsT0FBTyxFQUdSLE1BQU11VCxFQUFhblQsRUFBTWhDLFNBQVNzRSxRQUFRbEksSUFBSSxlQUM5QyxHQUFJK1ksR0FBYzFaLEtBQUs4WCxTQUFTTCxNQUFNRixpQkFBaUJOLFNBQVMxUSxFQUFNaEMsU0FBUzRCLFFBQVMsQ0FDdkYsSUFBSXdULEVBQVFDLE9BQU9GLEdBT25CLE9BTklFLE9BQU9DLE1BQU1GLEdBQ2hCQSxFQUFROU8sS0FBS3RILE1BQU1tVyxHQUFjN08sS0FBS29FLE1BRXRDMEssR0FBUyxTQUd1QyxJQUF0QzNaLEtBQUs4WCxTQUFTTCxNQUFNcUMsZUFBaUNILEVBQVEzWixLQUFLOFgsU0FBU0wsTUFBTXFDLGNBQ3BGLEVBR0RILEVBR1IsR0FBOEIsTUFBMUJwVCxFQUFNaEMsU0FBUzRCLE9BQ2xCLE9BQU8sRUFLVCxNQUR1QixHQUNFLElBQU1uRyxLQUFLNFgsWUFBYyxHQUFNLElBR3pELE9BQU8sRUFHUixrQkFBa0JyVCxHQU9qQixPQU5JdkUsS0FBSzhYLFNBQVMyQixZQUNqQmxWLEVBQVM0RixLQUFPdUosU0FDUjFULEtBQUs4WCxTQUFTMkIsZ0JBQWdCbFYsRUFBU2dTLFNBSXpDaFMsRUFHUixhQUFhdVUsR0FDWixJQUNDLGFBQWFBLElBQ1osTUFBT3ZTLEdBQ1IsTUFBTXNRLEVBQUsvQyxLQUFLRSxJQUFJaFUsS0FBSytaLHFCQUFxQnhULEdBQVFtUixHQUN0RCxHQUFXLElBQVBiLEdBQVk3VyxLQUFLNFgsWUFBYyxFQUFHLE9BQy9CaEIsRUFBTUMsR0FFWixJQUFLLE1BQU1vQyxLQUFRalosS0FBSzhYLFNBQVNFLE1BQU1FLFlBVXRDLFNBUnlCZSxFQUFLLENBQzdCdlcsUUFBUzFDLEtBQUswQyxRQUNkTixRQUFTcEMsS0FBSzhYLFNBQ2R2UixRQUNBeVQsV0FBWWhhLEtBQUs0WCxnQkFJQ25CLEVBQ2xCLE9BSUYsT0FBT3pXLEtBQUt1WixPQUFPVCxHQUdwQixHQUFJOVksS0FBSzhYLFNBQVN4TyxnQkFDakIsTUFBTS9DLEdBS1QsZUFDQyxJQUFLLE1BQU0wUyxLQUFRalosS0FBSzhYLFNBQVNFLE1BQU1DLGNBQWUsQ0FFckQsTUFBTWxDLFFBQWVrRCxFQUFLalosS0FBSzBDLFFBQVMxQyxLQUFLOFgsVUFFN0MsR0FBSS9CLGFBQWtCcEwsUUFBUyxDQUM5QjNLLEtBQUswQyxRQUFVcVQsRUFDZixNQUdELEdBQUlBLGFBQWtCakIsU0FDckIsT0FBT2lCLEVBSVQsT0FBOEIsSUFBMUIvVixLQUFLOFgsU0FBU08sUUFDVnJZLEtBQUs4WCxTQUFTcEQsTUFBTTFVLEtBQUswQyxRQUFRMFcsVUFqUzFCMVcsRUFvU0ExQyxLQUFLMEMsUUFBUTBXLFFBcFNKWCxFQW9TYXpZLEtBQUt5WSxnQkFwU0RyVyxFQW9Ta0JwQyxLQUFLOFgsU0FuU2pFLElBQUlwTyxTQUFRLENBQUNDLEVBQVNDLEtBQ3JCLE1BQU1xUSxFQUFZbkQsWUFBVyxLQUN4QjJCLEdBQ0hBLEVBQWdCMVcsUUFHakI2SCxFQUFPLElBQUkrTSxFQUFhalUsTUFDdEJOLEVBQVFpVyxTQUdYalcsRUFBUXNTLE1BQU1oUyxHQUNaMkMsS0FBS3NFLEdBQ0x1USxNQUFNdFEsR0FDTnZFLE1BQUssS0FDTDhVLGFBQWFGLFVBZkQsSUFBQ3ZYLEVBQVMrVixFQUFpQnJXLEVBd1MxQyxRQUFRbUMsRUFBVThVLEdBQ2pCLE1BQU1lLEVBQWFSLE9BQU9yVixFQUFTc0UsUUFBUWxJLElBQUksb0JBQXNCLEVBQ3JFLElBQUkwWixFQUFtQixFQUV2QixPQUFPLElBQUlwRixFQUFRSCxTQUNsQixJQUFJRyxFQUFRRixlQUFlLENBQzFCLE1BQU1qVCxHQUNMLE1BQU13WSxFQUFTL1YsRUFBU0MsS0FBSytWLFlBRXpCbEIsR0FDSEEsRUFBbUIsQ0FBQ21CLFFBQVMsRUFBR0gsaUJBQWtCLEVBQUdELGNBQWEsSUFBSTVHLFlBR3ZFRSxlQUFlRSxJQUNkLE1BQU0sS0FBQzZHLEVBQUksTUFBRXRhLFNBQWVtYSxFQUFPMUcsT0FDL0I2RyxFQUNIM1ksRUFBVzRZLFNBSVJyQixJQUNIZ0IsR0FBb0JsYSxFQUFNaVQsV0FFMUJpRyxFQUFtQixDQUFDbUIsUUFEVyxJQUFmSixFQUFtQixFQUFJQyxFQUFtQkQsRUFDN0JDLG1CQUFrQkQsY0FBYWphLElBRzdEMkIsRUFBVzZZLFFBQVF4YSxHQUNuQnlULEtBR0RBLFFBT0wsTUFBTWdILEVBQW1CLElBQUl6RSxLQUM1QixJQUFLLE1BQU1GLEtBQVVFLEVBQ3BCLEtBQU12RixFQUFTcUYsSUFBVy9OLE1BQU1DLFFBQVE4TixVQUE4QixJQUFYQSxFQUMxRCxNQUFNLElBQUl6VixVQUFVLDRDQUl0QixPQUFPMFYsRUFBVSxNQUFPQyxJQUduQjBFLEVBQWlCQyxJQUN0QixNQUFNQyxFQUFLLENBQUMvRCxFQUFPNVUsSUFBWSxJQUFJdVYsRUFBR1gsRUFBTzRELEVBQWlCRSxFQUFVMVksSUFFeEUsSUFBSyxNQUFNMEcsS0FBVXVOLEVBQ3BCMEUsRUFBR2pTLEdBQVUsQ0FBQ2tPLEVBQU81VSxJQUFZLElBQUl1VixFQUFHWCxFQUFPNEQsRUFBaUJFLEVBQVUxWSxFQUFTLENBQUMwRyxZQVNyRixPQU5BaVMsRUFBR3JFLFVBQVlBLEVBQ2ZxRSxFQUFHcEUsYUFBZUEsRUFDbEJvRSxFQUFHdlosT0FBU3daLEdBQWVILEVBQWVELEVBQWlCSSxJQUMzREQsRUFBR0UsT0FBU0QsR0FBZUgsRUFBZUQsRUFBaUJFLEVBQVVFLElBQ3JFRCxFQUFHdEUsS0FBT0EsRUFFSHNFLEdBS1IsT0FGWUYsS0FwaEJtRGhiLEVBQU9ELFFBQVVELEssMkJDQ2pGQyxFQUFVQyxFQUFPRCxRQUFVOFUsRUFFM0IsTUFBTXdHLEVBQU8sRUFBUSxLQUNmQyxFQUFRLEVBQVEsS0FDaEJDLEVBQU8sRUFBUSxLQUNmQyxFQUFTLEVBQVEsS0FDakJDLEVBQWtCLEVBQVEsS0FDMUJDLEVBQU8sRUFBUSxLQUNmM0ksRUFBTyxFQUFRLElBQ2Y0SSxFQUFTLEVBQVEsS0FDakJsWixFQUFNLEVBQVEsS0FFcEIsTUFBTW1aLFVBQXVCalosTUFDNUIsWUFBWTZELEVBQVN4RSxHQUNwQnRCLE1BQU04RixHQUVON0QsTUFBTWtaLGtCQUFrQjFiLEtBQU1BLEtBQUsyUCxhQUVuQzNQLEtBQUs2QixLQUFPQSxFQUdiLFdBQ0MsT0FBTzdCLEtBQUsyUCxZQUFZaE0sS0FHekJoRCxJQUFLTSxPQUFPQyxlQUNYLE9BQU9sQixLQUFLMlAsWUFBWWhNLE1BVzFCLE1BQU1nWSxVQUFtQkYsRUFNeEIsWUFBWXBWLEVBQVN4RSxFQUFNK1osR0FDMUJyYixNQUFNOEYsRUFBU3hFLEdBRVgrWixJQUVINWIsS0FBS3dMLEtBQU94TCxLQUFLNmIsTUFBUUQsRUFBWXBRLEtBQ3JDeEwsS0FBSzhiLGVBQWlCRixFQUFZRyxVQVdyQyxNQUFNQyxFQUFPL2EsT0FBT0MsWUFTZCthLEVBQXdCeEgsR0FFVixpQkFBWEEsR0FDa0IsbUJBQWxCQSxFQUFPaEssUUFDVyxtQkFBbEJnSyxFQUFPalAsUUFDUSxtQkFBZmlQLEVBQU85VCxLQUNXLG1CQUFsQjhULEVBQU95SCxRQUNRLG1CQUFmekgsRUFBTzBILEtBQ1EsbUJBQWYxSCxFQUFPblQsS0FDUyxtQkFBaEJtVCxFQUFPMkgsTUFDRyxvQkFBakIzSCxFQUFPdUgsR0FVSEssRUFBUzVILEdBRUssaUJBQVhBLEdBQ3VCLG1CQUF2QkEsRUFBT2xCLGFBQ1MsaUJBQWhCa0IsRUFBTzVTLE1BQ1csbUJBQWxCNFMsRUFBT2pMLFFBQ2dCLG1CQUF2QmlMLEVBQU85RSxhQUNkLGdCQUFnQjdDLEtBQUsySCxFQUFPdUgsSUFVOUIsU0FBU00sRUFBVzdILEdBQ25CLE1BQ21CLGlCQUFYQSxHQUNrQixtQkFBbEJBLEVBQU9oSyxRQUNRLG1CQUFmZ0ssRUFBT25ULEtBQ1EsbUJBQWZtVCxFQUFPOVQsS0FDVyxtQkFBbEI4VCxFQUFPeUgsUUFDVyxtQkFBbEJ6SCxFQUFPalAsUUFDUyxtQkFBaEJpUCxFQUFPbkssTUFDVyxtQkFBbEJtSyxFQUFPTixRQUNZLG1CQUFuQk0sRUFBT3ZOLFNBQ2dCLG1CQUF2QnVOLEVBQU85RSxhQUNHLGFBQWpCOEUsRUFBT3VILEdBVVQsTUFPTU8sRUFBVyxPQUNYQyxFQUFTLElBQUlDLE9BQU8sR0FDcEJDLEVBQWlCMVMsT0FBT29KLFdBQVdtSixHQUtuQ0ksRUFBWUMsR0FBWSxHQUFHSixJQUFTSSxJQUFXSixJQUFTRCxFQUFTRSxPQUFPLEtBUzlFLFNBQVNJLEVBQVVELEVBQVVqWixFQUFNbVosR0FDbEMsSUFBSUMsRUFBUyxHQVViLE9BUkFBLEdBQVUsR0FBR1AsSUFBU0ksUUFDdEJHLEdBQVUseUNBQXlDcFosS0FFL0MwWSxFQUFPUyxLQUNWQyxHQUFVLGVBQWVELEVBQU1uWixZQUMvQm9aLEdBQVUsaUJBQWlCRCxFQUFNamIsTUFBUSw4QkFHbkMsR0FBR2tiLElBQVNSLEVBQVNFLE9BQU8sS0FvRHBDLE1BQU1PLEVBQVkvYixPQUFPLGtCQVd6QixNQUFNZ2MsRUFDTCxZQUFZelksR0FBTSxLQUNqQnNPLEVBQU8sR0FDSixJQUNILElBQUk4SixFQUFXLEtBRUYsT0FBVHBZLEVBRUhBLEVBQU8sS0FDR3lYLEVBQXNCelgsR0FFaENBLEVBQU93RixPQUFPZ0QsS0FBS3hJLEVBQUswRixZQUNkbVMsRUFBTzdYLElBQWtCd0YsT0FBT2tULFNBQVMxWSxLQUFrQitXLEVBQUt6SixNQUFNcUwsaUJBQWlCM1ksR0FFakdBLEVBQU93RixPQUFPZ0QsS0FBS3hJLEdBQ1R5TyxZQUFZQyxPQUFPMU8sR0FFN0JBLEVBQU93RixPQUFPZ0QsS0FBS3hJLEVBQUtxSixPQUFRckosRUFBSzJPLFdBQVkzTyxFQUFLNE8sWUFDNUM1TyxhQUFnQjZXLElBQW1CaUIsRUFBVzlYLElBRXhEb1ksRUFBVyw0QkE3RVlwQixFQUFPNEIsWUFBWSxHQUFHbFQsU0FBUyxTQThFdEQxRixFQUFPNlcsRUFBTzNJLFNBQVMxRixLQXhFMUIwRyxnQkFBa0MySixFQUFNVCxHQUN2QyxJQUFLLE1BQU9qWixFQUFNeEQsS0FBVWtkLFFBQ3JCUixFQUFVRCxFQUFValosRUFBTXhELEdBRTVCa2MsRUFBT2xjLFNBQ0ZBLEVBQU1xSixlQUVSckosUUFHRG9jLFFBR0RJLEVBQVVDLEdBMkRjVSxDQUFpQjlZLEVBQU1vWSxLQUluRHBZLEVBQU93RixPQUFPZ0QsS0FBS3FHLE9BQU83TyxNQUczQnhFLEtBQUtnZCxHQUFhLENBQ2pCeFksT0FDQW9ZLFdBQ0FXLFdBQVcsRUFDWGhYLE1BQU8sTUFFUnZHLEtBQUs4UyxLQUFPQSxFQUVSdE8sYUFBZ0I2VyxHQUNuQjdXLEVBQUtxRixHQUFHLFNBQVN3SSxJQUNoQixNQUFNOUwsRUFBUThMLGFBQWVvSixFQUM1QnBKLEVBQ0EsSUFBSXNKLEVBQVcsK0NBQStDM2IsS0FBS3NDLFFBQVErUCxFQUFJaE0sVUFBVyxTQUFVZ00sR0FDckdyUyxLQUFLZ2QsR0FBV3pXLE1BQVFBLEtBSzNCLFdBQ0MsT0FBT3ZHLEtBQUtnZCxHQUFXeFksS0FHeEIsZUFDQyxPQUFPeEUsS0FBS2dkLEdBQVdPLFVBUXhCLG9CQUNDLE1BQU0sT0FBQzFQLEVBQU0sV0FBRXNGLEVBQVUsV0FBRUMsU0FBb0JvSyxFQUFZeGQsTUFDM0QsT0FBTzZOLEVBQU93RyxNQUFNbEIsRUFBWUEsRUFBYUMsR0FROUMsYUFDQyxNQUFNcUssRUFBTXpkLEtBQUs2SSxTQUFXN0ksS0FBSzZJLFFBQVFsSSxJQUFJLGlCQUFxQlgsS0FBS2dkLEdBQVd4WSxNQUFReEUsS0FBS2dkLEdBQVd4WSxLQUFLM0MsTUFBUyxHQUNsSDZiLFFBQVkxZCxLQUFLNk4sU0FFdkIsT0FBTyxJQUFJK0UsRUFBSyxDQUFDOEssR0FBTSxDQUN0QjdiLEtBQU00YixJQVNSLGFBQ0MsTUFBTTVQLFFBQWUyUCxFQUFZeGQsTUFDakMsT0FBTzRZLEtBQUtyVixNQUFNc0ssRUFBTzNELFlBUTFCLGFBRUMsYUFEcUJzVCxFQUFZeGQsT0FDbkJrSyxXQVFmLFNBQ0MsT0FBT3NULEVBQVl4ZCxPQXFCckIwVCxlQUFlOEosRUFBWWhhLEdBQzFCLEdBQUlBLEVBQUt3WixHQUFXTyxVQUNuQixNQUFNLElBQUkvYyxVQUFVLDBCQUEwQmdELEVBQUtsQixPQUtwRCxHQUZBa0IsRUFBS3daLEdBQVdPLFdBQVksRUFFeEIvWixFQUFLd1osR0FBV3pXLE1BQ25CLE1BQU0vQyxFQUFLd1osR0FBV3pXLE1BR3ZCLElBQUksS0FBQy9CLEdBQVFoQixFQUdiLEdBQWEsT0FBVGdCLEVBQ0gsT0FBT3dGLE9BQU8yVCxNQUFNLEdBU3JCLEdBTEl0QixFQUFPN1gsS0FDVkEsRUFBT0EsRUFBS2dGLFVBSVRRLE9BQU9rVCxTQUFTMVksR0FDbkIsT0FBT0EsRUFJUixLQUFNQSxhQUFnQjZXLEdBQ3JCLE9BQU9yUixPQUFPMlQsTUFBTSxHQUtyQixNQUFNQyxFQUFRLEdBQ2QsSUFBSUMsRUFBYSxFQUVqQixJQUNDLFVBQVcsTUFBTS9ULEtBQVN0RixFQUFNLENBQy9CLEdBQUloQixFQUFLc1AsS0FBTyxHQUFLK0ssRUFBYS9ULEVBQU1YLE9BQVMzRixFQUFLc1AsS0FBTSxDQUMzRCxNQUFNVCxFQUFNLElBQUlzSixFQUFXLG1CQUFtQm5ZLEVBQUtsQixtQkFBbUJrQixFQUFLc1AsT0FBUSxZQUVuRixNQURBdE8sRUFBS2UsUUFBUThNLEdBQ1BBLEVBR1B3TCxHQUFjL1QsRUFBTVgsT0FDcEJ5VSxFQUFNN1QsS0FBS0QsSUFFWCxNQUFPdkQsR0FDUixNQUFJQSxhQUFpQmtWLEVBQ2RsVixFQUdBLElBQUlvVixFQUFXLCtDQUErQ25ZLEVBQUtsQixRQUFRaUUsRUFBTUYsVUFBVyxTQUFVRSxHQUk5RyxJQUEyQixJQUF2Qi9CLEVBQUtzWixnQkFBd0QsSUFBOUJ0WixFQUFLdVosZUFBZUMsTUFXdEQsTUFBTSxJQUFJckMsRUFBVyw0REFBNERuWSxFQUFLbEIsT0FWdEYsSUFDQyxPQUFJc2IsRUFBTUssT0FBTUMsR0FBa0IsaUJBQU5BLElBQ3BCbFUsT0FBT2dELEtBQUs0USxFQUFNeFYsS0FBSyxLQUd4QjRCLE9BQU9DLE9BQU8yVCxFQUFPQyxHQUMzQixNQUFPdFgsR0FDUixNQUFNLElBQUlvVixFQUFXLGtEQUFrRG5ZLEVBQUtsQixRQUFRaUUsRUFBTUYsVUFBVyxTQUFVRSxJQWxGbEh0RyxPQUFPYyxpQkFBaUJrYyxFQUFLcGMsVUFBVyxDQUN2QzJELEtBQU0sQ0FBQ3hELFlBQVksR0FDbkJtZCxTQUFVLENBQUNuZCxZQUFZLEdBQ3ZCdVMsWUFBYSxDQUFDdlMsWUFBWSxHQUMxQnNULEtBQU0sQ0FBQ3RULFlBQVksR0FDbkJtSixLQUFNLENBQUNuSixZQUFZLEdBQ25CdVYsS0FBTSxDQUFDdlYsWUFBWSxLQTBGcEIsTUFBTW9ZLEVBQVEsQ0FBQ2dGLEVBQVV4SixLQUN4QixJQUFJeUosRUFDQUMsR0FDQSxLQUFDOVosR0FBUTRaLEVBR2IsR0FBSUEsRUFBU0QsU0FDWixNQUFNLElBQUkzYixNQUFNLHNDQWdCakIsT0FYS2dDLGFBQWdCNlcsR0FBd0MsbUJBQXJCN1csRUFBSytaLGNBRTVDRixFQUFLLElBQUloRCxFQUFPbUQsWUFBWSxDQUFDNUosa0JBQzdCMEosRUFBSyxJQUFJakQsRUFBT21ELFlBQVksQ0FBQzVKLGtCQUM3QnBRLEVBQUtnRSxLQUFLNlYsR0FDVjdaLEVBQUtnRSxLQUFLOFYsR0FFVkYsRUFBU3BCLEdBQVd4WSxLQUFPNlosRUFDM0I3WixFQUFPOFosR0FHRDlaLEdBYUZpYSxFQUFxQixDQUFDamEsRUFBTTlCLElBRXBCLE9BQVQ4QixFQUNJLEtBSVksaUJBQVRBLEVBQ0gsMkJBSUp5WCxFQUFzQnpYLEdBQ2xCLGtEQUlKNlgsRUFBTzdYLEdBQ0hBLEVBQUszQyxNQUFRLEtBSWpCbUksT0FBT2tULFNBQVMxWSxJQUFTK1csRUFBS3pKLE1BQU1xTCxpQkFBaUIzWSxJQUFTeU8sWUFBWUMsT0FBTzFPLEdBQzdFLEtBSUpBLEdBQW9DLG1CQUFyQkEsRUFBSytaLFlBQ2hCLGdDQUFnQy9aLEVBQUsrWixnQkFHekNqQyxFQUFXOVgsR0FDUCxpQ0FBaUM5QixFQUFRc2EsR0FBV0osV0FJeERwWSxhQUFnQjZXLEVBQ1osS0FJRCwyQkEwRUZxRCxFQUF3RCxtQkFBNUJ4RCxFQUFLd0QsbUJBQ3RDeEQsRUFBS3dELG1CQUNML2EsSUFDQyxJQUFLLDBCQUEwQm1KLEtBQUtuSixHQUFPLENBQzFDLE1BQU0wTyxFQUFNLElBQUk3UixVQUFVLDJDQUEyQ21ELE1BRXJFLE1BREExRCxPQUFPQyxlQUFlbVMsRUFBSyxPQUFRLENBQUNsUyxNQUFPLDJCQUNyQ2tTLElBSUhzTSxFQUEwRCxtQkFBN0J6RCxFQUFLeUQsb0JBQ3ZDekQsRUFBS3lELG9CQUNMLENBQUNoYixFQUFNeEQsS0FDTixHQUFJLGtDQUFrQzJNLEtBQUszTSxHQUFRLENBQ2xELE1BQU1rUyxFQUFNLElBQUk3UixVQUFVLHlDQUF5Q21ELE9BRW5FLE1BREExRCxPQUFPQyxlQUFlbVMsRUFBSyxPQUFRLENBQUNsUyxNQUFPLHFCQUNyQ2tTLElBZ0JULE1BQU13QyxVQUFnQjhELGdCQU9yQixZQUFZaUcsR0FHWCxJQUFJN0ksRUFBUyxHQUNiLEdBQUk2SSxhQUFnQi9KLEVBQVMsQ0FDNUIsTUFBTWdLLEVBQU1ELEVBQUtDLE1BQ2pCLElBQUssTUFBT2xiLEVBQU13USxLQUFXbFUsT0FBT2lILFFBQVEyWCxHQUMzQzlJLEVBQU9oTSxRQUFRb0ssRUFBT3hQLEtBQUl4RSxHQUFTLENBQUN3RCxFQUFNeEQsV0FFckMsR0FBWSxNQUFSeWUsT0FBcUIsSUFBb0IsaUJBQVRBLEdBQXNCckQsRUFBS3pKLE1BQU1nTixpQkFBaUJGLEdBK0I1RixNQUFNLElBQUlwZSxVQUFVLHdJQS9CK0UsQ0FDbkcsTUFBTXNJLEVBQVM4VixFQUFLM2QsT0FBTzhkLFVBRTNCLEdBQWMsTUFBVmpXLEVBRUhpTixFQUFPaE0sUUFBUTlKLE9BQU9pSCxRQUFRMFgsUUFDeEIsQ0FDTixHQUFzQixtQkFBWDlWLEVBQ1YsTUFBTSxJQUFJdEksVUFBVSxpQ0FLckJ1VixFQUFTLElBQUk2SSxHQUNYamEsS0FBSXFhLElBQ0osR0FDaUIsaUJBQVRBLEdBQXFCekQsRUFBS3pKLE1BQU1nTixpQkFBaUJFLEdBRXhELE1BQU0sSUFBSXhlLFVBQVUsK0NBR3JCLE1BQU8sSUFBSXdlLE1BQ1RyYSxLQUFJcWEsSUFDTixHQUFvQixJQUFoQkEsRUFBSzdWLE9BQ1IsTUFBTSxJQUFJM0ksVUFBVSwrQ0FHckIsTUFBTyxJQUFJd2UsUUFxQmYsT0FiQWpKLEVBQ0NBLEVBQU81TSxPQUFTLEVBQ2Y0TSxFQUFPcFIsS0FBSSxFQUFFaEIsRUFBTXhELE1BQ2xCdWUsRUFBbUIvYSxHQUNuQmdiLEVBQW9CaGIsRUFBTTBQLE9BQU9sVCxJQUMxQixDQUFDa1QsT0FBTzFQLEdBQU0yUCxjQUFlRCxPQUFPbFQsWUFFNUMrUixFQUVGM1IsTUFBTXdWLEdBSUMsSUFBSWtKLE1BQU1qZixLQUFNLENBQ3RCLElBQUlrZixFQUFRQyxFQUFHQyxHQUNkLE9BQVFELEdBQ1AsSUFBSyxTQUNMLElBQUssTUFDSixNQUFPLENBQUN4YixFQUFNeEQsS0FDYnVlLEVBQW1CL2EsR0FDbkJnYixFQUFvQmhiLEVBQU0wUCxPQUFPbFQsSUFDMUJ3WSxnQkFBZ0I5WCxVQUFVc2UsR0FBRzFkLEtBQ25DMmQsRUFDQS9MLE9BQU8xUCxHQUFNMlAsY0FDYkQsT0FBT2xULEtBSVYsSUFBSyxTQUNMLElBQUssTUFDTCxJQUFLLFNBQ0osT0FBT3dELElBQ04rYSxFQUFtQi9hLEdBQ1pnVixnQkFBZ0I5WCxVQUFVc2UsR0FBRzFkLEtBQ25DMmQsRUFDQS9MLE9BQU8xUCxHQUFNMlAsZ0JBSWhCLElBQUssT0FDSixNQUFPLEtBQ040TCxFQUFPOUMsT0FDQSxJQUFJaUQsSUFBSTFHLGdCQUFnQjlYLFVBQVV5SixLQUFLN0ksS0FBS3lkLElBQVM1VSxRQUc5RCxRQUNDLE9BQU9nVixRQUFRM2UsSUFBSXVlLEVBQVFDLEVBQUdDLE9BT25DemUsSUFBS00sT0FBT0MsZUFDWCxPQUFPbEIsS0FBSzJQLFlBQVloTSxLQUd6QixXQUNDLE9BQU8xRCxPQUFPWSxVQUFVcUosU0FBU3pJLEtBQUt6QixNQUd2QyxJQUFJMkQsR0FDSCxNQUFNd1EsRUFBU25VLEtBQUtrYyxPQUFPdlksR0FDM0IsR0FBc0IsSUFBbEJ3USxFQUFPaEwsT0FDVixPQUFPLEtBR1IsSUFBSWhKLEVBQVFnVSxFQUFPL0wsS0FBSyxNQUt4QixNQUpJLHNCQUFzQjBFLEtBQUtuSixLQUM5QnhELEVBQVFBLEVBQU1tVCxlQUdSblQsRUFHUixRQUFRb2YsR0FDUCxJQUFLLE1BQU01YixLQUFRM0QsS0FBS3NLLE9BQ3ZCaVYsRUFBU3ZmLEtBQUtXLElBQUlnRCxHQUFPQSxHQUkzQixVQUNDLElBQUssTUFBTUEsS0FBUTNELEtBQUtzSyxhQUNqQnRLLEtBQUtXLElBQUlnRCxHQU9qQixXQUNDLElBQUssTUFBTUEsS0FBUTNELEtBQUtzSyxZQUNqQixDQUFDM0csRUFBTTNELEtBQUtXLElBQUlnRCxJQUl4QixDQUFDMUMsT0FBTzhkLFlBQ1AsT0FBTy9lLEtBQUtrSCxVQVFiLE1BQ0MsTUFBTyxJQUFJbEgsS0FBS3NLLFFBQVFsRCxRQUFPLENBQUMyTyxFQUFRdFQsS0FDdkNzVCxFQUFPdFQsR0FBT3pDLEtBQUtrYyxPQUFPelosR0FDbkJzVCxJQUNMLElBTUosQ0FBQzlVLE9BQU91ZSxJQUFJLGlDQUNYLE1BQU8sSUFBSXhmLEtBQUtzSyxRQUFRbEQsUUFBTyxDQUFDMk8sRUFBUXRULEtBQ3ZDLE1BQU0wUixFQUFTblUsS0FBS2tjLE9BQU96WixHQVMzQixPQUxDc1QsRUFBT3RULEdBREksU0FBUkEsRUFDVzBSLEVBQU8sR0FFUEEsRUFBT2hMLE9BQVMsRUFBSWdMLEVBQVNBLEVBQU8sR0FHNUM0QixJQUNMLEtBUUw5VixPQUFPYyxpQkFDTjhULEVBQVFoVSxVQUNSLENBQUMsTUFBTyxVQUFXLFVBQVcsVUFBVXVHLFFBQU8sQ0FBQzJPLEVBQVFaLEtBQ3ZEWSxFQUFPWixHQUFZLENBQUNuVSxZQUFZLEdBQ3pCK1UsSUFDTCxLQWdDSixNQUFNMEosRUFBaUIsSUFBSUosSUFBSSxDQUFDLElBQUssSUFBSyxJQUFLLElBQUssTUFROUNLLEVBQWFsVSxHQUNYaVUsRUFBZXRELElBQUkzUSxHQVNyQm1VLEVBQWMxZSxPQUFPLHNCQVMzQixNQUFNNlQsVUFBaUJtSSxFQUN0QixZQUFZelksRUFBTyxLQUFNcEMsRUFBVSxJQUNsQzdCLE1BQU1pRSxFQUFNcEMsR0FFWixNQUFNK0QsRUFBUy9ELEVBQVErRCxRQUFVLElBQzNCMEMsRUFBVSxJQUFJZ00sRUFBUXpTLEVBQVF5RyxTQUVwQyxHQUFhLE9BQVRyRSxJQUFrQnFFLEVBQVFzVCxJQUFJLGdCQUFpQixDQUNsRCxNQUFNeFQsRUFBYzhWLEVBQW1CamEsR0FDbkNtRSxHQUNIRSxFQUFRNEIsT0FBTyxlQUFnQjlCLEdBSWpDM0ksS0FBSzJmLEdBQWUsQ0FDbkJyZCxJQUFLRixFQUFRRSxJQUNiNkQsU0FDQUMsV0FBWWhFLEVBQVFnRSxZQUFjLEdBQ2xDeUMsVUFDQStXLFFBQVN4ZCxFQUFRd2QsUUFDakJoTCxjQUFleFMsRUFBUXdTLGVBSXpCLFVBQ0MsT0FBTzVVLEtBQUsyZixHQUFhcmQsS0FBTyxHQUdqQyxhQUNDLE9BQU90QyxLQUFLMmYsR0FBYXhaLE9BTTFCLFNBQ0MsT0FBT25HLEtBQUsyZixHQUFheFosUUFBVSxLQUFPbkcsS0FBSzJmLEdBQWF4WixPQUFTLElBR3RFLGlCQUNDLE9BQU9uRyxLQUFLMmYsR0FBYUMsUUFBVSxFQUdwQyxpQkFDQyxPQUFPNWYsS0FBSzJmLEdBQWF2WixXQUcxQixjQUNDLE9BQU9wRyxLQUFLMmYsR0FBYTlXLFFBRzFCLG9CQUNDLE9BQU83SSxLQUFLMmYsR0FBYS9LLGNBUTFCLFFBQ0MsT0FBTyxJQUFJRSxFQUFTc0UsRUFBTXBaLEtBQU1BLEtBQUs0VSxlQUFnQixDQUNwRHRTLElBQUt0QyxLQUFLc0MsSUFDVjZELE9BQVFuRyxLQUFLbUcsT0FDYkMsV0FBWXBHLEtBQUtvRyxXQUNqQnlDLFFBQVM3SSxLQUFLNkksUUFDZFUsR0FBSXZKLEtBQUt1SixHQUNUc1csV0FBWTdmLEtBQUs2ZixXQUNqQi9NLEtBQU05UyxLQUFLOFMsT0FTYixnQkFBZ0J4USxFQUFLNkQsRUFBUyxLQUM3QixJQUFLdVosRUFBV3ZaLEdBQ2YsTUFBTSxJQUFJNFMsV0FBVyxtRUFHdEIsT0FBTyxJQUFJakUsRUFBUyxLQUFNLENBQ3pCak0sUUFBUyxDQUNSaVgsU0FBVSxJQUFJeEgsSUFBSWhXLEdBQUs0SCxZQUV4Qi9ELFdBSUZ4RixJQUFLTSxPQUFPQyxlQUNYLE1BQU8sWUFJVGpCLE9BQU9jLGlCQUFpQitULEVBQVNqVSxVQUFXLENBQzNDeUIsSUFBSyxDQUFDdEIsWUFBWSxHQUNsQm1GLE9BQVEsQ0FBQ25GLFlBQVksR0FDckJ1SSxHQUFJLENBQUN2SSxZQUFZLEdBQ2pCNmUsV0FBWSxDQUFDN2UsWUFBWSxHQUN6Qm9GLFdBQVksQ0FBQ3BGLFlBQVksR0FDekI2SCxRQUFTLENBQUM3SCxZQUFZLEdBQ3RCb1ksTUFBTyxDQUFDcFksWUFBWSxLQUdyQixNQVVNK2UsRUFBYzllLE9BQU8scUJBUXJCK2UsRUFBWXZMLEdBRUUsaUJBQVhBLEdBQ3dCLGlCQUF4QkEsRUFBT3NMLEdBV2hCLE1BQU1wVixVQUFnQnNTLEVBQ3JCLFlBQVlqRyxFQUFPNEgsRUFBTyxJQUN6QixJQUFJcUIsRUFHQUQsRUFBVWhKLEdBQ2JpSixFQUFZLElBQUkzSCxJQUFJdEIsRUFBTTFVLE1BRTFCMmQsRUFBWSxJQUFJM0gsSUFBSXRCLEdBQ3BCQSxFQUFRLElBR1QsSUFBSWxPLEVBQVM4VixFQUFLOVYsUUFBVWtPLEVBQU1sTyxRQUFVLE1BSTVDLEdBSEFBLEVBQVNBLEVBQU9vTyxlQUdHLE1BQWIwSCxFQUFLcGEsTUFBZ0J3YixFQUFVaEosS0FBMEIsT0FBZkEsRUFBTXhTLE9BQ3pDLFFBQVhzRSxHQUErQixTQUFYQSxHQUNyQixNQUFNLElBQUl0SSxVQUFVLGlEQUdyQixNQUFNMGYsRUFBWXRCLEVBQUtwYSxLQUN0Qm9hLEVBQUtwYSxLQUNKd2IsRUFBVWhKLElBQXlCLE9BQWZBLEVBQU14UyxLQUMxQjRVLEVBQU1wQyxHQUNOLEtBRUZ6VyxNQUFNMmYsRUFBVyxDQUNoQnBOLEtBQU04TCxFQUFLOUwsTUFBUWtFLEVBQU1sRSxNQUFRLElBR2xDLE1BQU1qSyxFQUFVLElBQUlnTSxFQUFRK0osRUFBSy9WLFNBQVdtTyxFQUFNbk8sU0FBVyxJQUU3RCxHQUFrQixPQUFkcVgsSUFBdUJyWCxFQUFRc1QsSUFBSSxnQkFBaUIsQ0FDdkQsTUFBTXhULEVBQWM4VixFQUFtQnlCLEVBQVdsZ0IsTUFDOUMySSxHQUNIRSxFQUFRNEIsT0FBTyxlQUFnQjlCLEdBSWpDLElBQUlwSCxFQUFTeWUsRUFBVWhKLEdBQ3RCQSxFQUFNelYsT0FDTixLQUtELEdBSkksV0FBWXFkLElBQ2ZyZCxFQUFTcWQsRUFBS3JkLFFBR0EsT0FBWEEsSUFyNUJjLGlCQUZFa1QsRUF1NUJrQmxULElBcDVCckIsZ0JBQWpCa1QsRUFBT3VILElBcTVCTixNQUFNLElBQUl4YixVQUFVLG1EQXg1QkRpVSxNQTI1QnBCelUsS0FBSytmLEdBQWUsQ0FDbkJqWCxTQUNBcVgsU0FBVXZCLEVBQUt1QixVQUFZbkosRUFBTW1KLFVBQVksU0FDN0N0WCxVQUNBb1gsWUFDQTFlLFVBSUR2QixLQUFLb2dCLFlBQXlCbE8sSUFBaEIwTSxFQUFLd0IsWUFBeUNsTyxJQUFqQjhFLEVBQU1vSixPQUF1QixHQUFLcEosRUFBTW9KLE9BQVV4QixFQUFLd0IsT0FDbEdwZ0IsS0FBS3FnQixjQUE2Qm5PLElBQWxCME0sRUFBS3lCLGNBQTZDbk8sSUFBbkI4RSxFQUFNcUosVUFBZ0NySixFQUFNcUosU0FBWXpCLEVBQUt5QixTQUM1R3JnQixLQUFLNGYsUUFBVWhCLEVBQUtnQixTQUFXNUksRUFBTTRJLFNBQVcsRUFDaEQ1ZixLQUFLc2dCLE1BQVExQixFQUFLMEIsT0FBU3RKLEVBQU1zSixNQUNqQ3RnQixLQUFLNFUsY0FBZ0JnSyxFQUFLaEssZUFBaUJvQyxFQUFNcEMsZUFBaUIsTUFDbEU1VSxLQUFLdWdCLG1CQUFxQjNCLEVBQUsyQixvQkFBc0J2SixFQUFNdUoscUJBQXNCLEVBR2xGLGFBQ0MsT0FBT3ZnQixLQUFLK2YsR0FBYWpYLE9BRzFCLFVBQ0MsT0FBT3hHLEVBQUlrZSxPQUFPeGdCLEtBQUsrZixHQUFhRSxXQUdyQyxjQUNDLE9BQU9qZ0IsS0FBSytmLEdBQWFsWCxRQUcxQixlQUNDLE9BQU83SSxLQUFLK2YsR0FBYUksU0FHMUIsYUFDQyxPQUFPbmdCLEtBQUsrZixHQUFheGUsT0FRMUIsUUFDQyxPQUFPLElBQUlvSixFQUFRM0ssTUFHcEJXLElBQUtNLE9BQU9DLGVBQ1gsTUFBTyxXQUlUakIsT0FBT2MsaUJBQWlCNEosRUFBUTlKLFVBQVcsQ0FDMUNpSSxPQUFRLENBQUM5SCxZQUFZLEdBQ3JCc0IsSUFBSyxDQUFDdEIsWUFBWSxHQUNsQjZILFFBQVMsQ0FBQzdILFlBQVksR0FDdEJtZixTQUFVLENBQUNuZixZQUFZLEdBQ3ZCb1ksTUFBTyxDQUFDcFksWUFBWSxHQUNwQk8sT0FBUSxDQUFDUCxZQUFZLEtBbUZ0QixNQUFNeWYsVUFBbUJoRixFQUN4QixZQUFZcFYsRUFBU3hFLEVBQU8sV0FDM0J0QixNQUFNOEYsRUFBU3hFLElBWWpCLE1BQU02ZSxFQUFtQixJQUFJckIsSUFBSSxDQUFDLFFBQVMsUUFBUyxXQVNwRDNMLGVBQWVnQixFQUFNcFMsRUFBS3FlLEdBQ3pCLE9BQU8sSUFBSWpYLFNBQVEsQ0FBQ0MsRUFBU0MsS0FFNUIsTUFBTWxILEVBQVUsSUFBSWlJLEVBQVFySSxFQUFLcWUsR0FDM0J2ZSxFQXJHc0JNLEtBQzdCLE1BQU0sVUFBQ3VkLEdBQWF2ZCxFQUFRcWQsR0FDdEJsWCxFQUFVLElBQUlnTSxFQUFRblMsRUFBUXFkLEdBQWFsWCxTQUc1Q0EsRUFBUXNULElBQUksV0FDaEJ0VCxFQUFRdkgsSUFBSSxTQUFVLE9BSXZCLElBQUlzZixFQUFxQixLQUt6QixHQUpxQixPQUFqQmxlLEVBQVE4QixNQUFpQixnQkFBZ0JzSSxLQUFLcEssRUFBUW9HLFVBQ3pEOFgsRUFBcUIsS0FHRCxPQUFqQmxlLEVBQVE4QixLQUFlLENBQzFCLE1BQU00VixFQXRtQmMxWCxLQUNyQixNQUFNLEtBQUM4QixHQUFROUIsRUFHZixPQUFhLE9BQVQ4QixFQUNJLEVBSUo2WCxFQUFPN1gsR0FDSEEsRUFBS3NPLEtBSVQ5SSxPQUFPa1QsU0FBUzFZLEdBQ1pBLEVBQUsyRSxPQUlUM0UsR0FBc0MsbUJBQXZCQSxFQUFLcWMsY0FDaEJyYyxFQUFLc2MsZ0JBQWtCdGMsRUFBS3NjLGlCQUFtQnRjLEVBQUtxYyxnQkFBa0IsS0FJMUV2RSxFQUFXOVgsR0E3VmhCLFNBQTJCNlksRUFBTVQsR0FDaEMsSUFBSXpULEVBQVMsRUFFYixJQUFLLE1BQU94RixFQUFNeEQsS0FBVWtkLEVBQzNCbFUsR0FBVWEsT0FBT29KLFdBQVd5SixFQUFVRCxFQUFValosRUFBTXhELElBRWxEa2MsRUFBT2xjLEdBQ1ZnSixHQUFVaEosRUFBTTJTLEtBRWhCM0osR0FBVWEsT0FBT29KLFdBQVdDLE9BQU9sVCxJQUdwQ2dKLEdBQVV1VCxFQUtYLE9BRkF2VCxHQUFVYSxPQUFPb0osV0FBV3VKLEVBQVVDLElBRS9CelQsRUE2VUM0WCxDQUFrQnJlLEVBQVFzYSxHQUFXSixVQUl0QyxNQXlrQmFvRSxDQUFjdGUsR0FFUCxpQkFBZjBYLEdBQTRCUixPQUFPQyxNQUFNTyxLQUNuRHdHLEVBQXFCdk4sT0FBTytHLElBSTFCd0csR0FDSC9YLEVBQVF2SCxJQUFJLGlCQUFrQnNmLEdBSTFCL1gsRUFBUXNULElBQUksZUFDaEJ0VCxFQUFRdkgsSUFBSSxhQUFjLGNBSXZCb0IsRUFBUTJkLFdBQWF4WCxFQUFRc1QsSUFBSSxvQkFDcEN0VCxFQUFRdkgsSUFBSSxrQkFBbUIsbUJBR2hDLElBQUksTUFBQ2dmLEdBQVM1ZCxFQUNPLG1CQUFWNGQsSUFDVkEsRUFBUUEsRUFBTUwsSUFHVnBYLEVBQVFzVCxJQUFJLGVBQWtCbUUsR0FDbEN6WCxFQUFRdkgsSUFBSSxhQUFjLFNBTTNCLE1BQU0yZixFQXRNV2hCLEtBQ2pCLEdBQUlBLEVBQVVnQixPQUNiLE9BQU9oQixFQUFVZ0IsT0FHbEIsTUFBTUMsRUFBYWpCLEVBQVVrQixLQUFLaFksT0FBUyxFQUNyQ2lZLEVBQU9uQixFQUFVbUIsT0FBd0MsTUFBL0JuQixFQUFVa0IsS0FBS0QsR0FBc0IsSUFBTSxJQUMzRSxNQUFvRCxNQUE3Q2pCLEVBQVVrQixLQUFLRCxFQUFhRSxFQUFLalksUUFBa0IsSUFBTSxJQStMakRrWSxDQUFVcEIsR0FtQnpCLE1BaEJ1QixDQUN0QnFCLEtBQU1yQixFQUFVc0IsU0FBV04sRUFDM0JNLFNBQVV0QixFQUFVc0IsU0FDcEJDLFNBQVV2QixFQUFVdUIsU0FDcEJDLFNBQVV4QixFQUFVd0IsU0FDcEJDLEtBQU16QixFQUFVeUIsS0FDaEJOLEtBQU1uQixFQUFVbUIsS0FDaEJILE9BQVFoQixFQUFVZ0IsT0FDbEI3YixNQUFPNmEsRUFBVTdhLE1BQ2pCK2IsS0FBTWxCLEVBQVVrQixLQUNoQnJZLE9BQVFwRyxFQUFRb0csT0FDaEJELFFBQVNBLEVBQVE1SCxPQUFPdWUsSUFBSSxpQ0FDNUJlLG1CQUFvQjdkLEVBQVE2ZCxtQkFDNUJELFVBb0NnQnFCLENBQXNCamYsR0FDdEMsSUFBS2dlLEVBQWlCdkUsSUFBSS9aLEVBQVFxZixVQUNqQyxNQUFNLElBQUlqaEIsVUFBVSwwQkFBMEI4QixrQkFBb0JGLEVBQVFxZixTQUFTdFUsUUFBUSxLQUFNLDBCQUdsRyxHQUF5QixVQUFyQi9LLEVBQVFxZixTQUFzQixDQUNqQyxNQUFNamUsRUFBTzhYLEVBQWdCNVksRUFBUUosS0FDL0JpQyxFQUFXLElBQUl1USxFQUFTdFIsRUFBTSxDQUFDcUYsUUFBUyxDQUFDLGVBQWdCckYsRUFBS2lLLFlBRXBFLFlBREE5RCxFQUFRcEYsR0FLVCxNQUFNcWQsR0FBNkIsV0FBckJ4ZixFQUFRcWYsU0FBd0J0RyxFQUFRRCxHQUFNeFksU0FDdEQsT0FBQ25CLEdBQVVtQixFQUNqQixJQUFJNkIsRUFBVyxLQUVmLE1BQU14QyxFQUFRLEtBQ2IsTUFBTXdFLEVBQVEsSUFBSWthLEVBQVcsOEJBQzdCN1csRUFBT3JELEdBQ0g3RCxFQUFROEIsTUFBUTlCLEVBQVE4QixnQkFBZ0I2VyxFQUFPM0ksVUFDbERoUSxFQUFROEIsS0FBS2UsUUFBUWdCLEdBR2pCaEMsR0FBYUEsRUFBU0MsTUFJM0JELEVBQVNDLEtBQUtxZCxLQUFLLFFBQVN0YixJQUc3QixHQUFJaEYsR0FBVUEsRUFBT2QsUUFFcEIsWUFEQXNCLElBSUQsTUFBTStmLEVBQW1CLEtBQ3hCL2YsSUFDQWdnQixLQUlLQyxFQUFXSixFQUFLeGYsR0FFbEJiLEdBQ0hBLEVBQU9tWCxpQkFBaUIsUUFBU29KLEdBR2xDLE1BQU1DLEVBQVcsS0FDaEJDLEVBQVNqZ0IsUUFDTFIsR0FDSEEsRUFBTzBnQixvQkFBb0IsUUFBU0gsSUFJdENFLEVBQVNuWSxHQUFHLFNBQVN3SSxJQUNwQnpJLEVBQU8sSUFBSStSLEVBQVcsY0FBY2paLEVBQVFKLHVCQUF1QitQLEVBQUloTSxVQUFXLFNBQVVnTSxJQUM1RjBQLE9BR0RDLEVBQVNuWSxHQUFHLFlBQVlxWSxJQUN2QkYsRUFBU2xMLFdBQVcsR0FDcEIsTUFBTWpPLEVBcGRULFNBQXdCQSxFQUFVLElBQ2pDLE9BQU8sSUFBSWdNLEVBQ1ZoTSxFQUVFekIsUUFBTyxDQUFDMk8sRUFBUTVWLEVBQU9naUIsRUFBT0MsS0FDMUJELEVBQVEsR0FBTSxHQUNqQnBNLEVBQU9oTSxLQUFLcVksRUFBTS9OLE1BQU04TixFQUFPQSxFQUFRLElBR2pDcE0sSUFDTCxJQUNGeEwsUUFBTyxFQUFFNUcsRUFBTXhELE1BQ2YsSUFHQyxPQUZBdWUsRUFBbUIvYSxHQUNuQmdiLEVBQW9CaGIsRUFBTTBQLE9BQU9sVCxLQUMxQixFQUNOLE1BQ0QsT0FBTyxPQW1jT2tpQixDQUFlSCxFQUFVSSxZQUd6QyxHQUFJNUMsRUFBV3dDLEVBQVVLLFlBQWEsQ0FFckMsTUFBTXpDLEVBQVdqWCxFQUFRbEksSUFBSSxZQUd2QjZoQixFQUEyQixPQUFiMUMsRUFBb0IsS0FBTyxJQUFJeEgsSUFBSXdILEVBQVVwZCxFQUFRSixLQUd6RSxPQUFRSSxFQUFReWQsVUFDZixJQUFLLFFBR0osT0FGQXZXLEVBQU8sSUFBSStSLEVBQVcsMEVBQTBFalosRUFBUUosTUFBTyxxQkFDL0d5ZixJQUVELElBQUssU0FFSixHQUFvQixPQUFoQlMsRUFFSCxJQUNDM1osRUFBUXZILElBQUksV0FBWWtoQixHQUV2QixNQUFPamMsR0FDUnFELEVBQU9yRCxHQUlULE1BQ0QsSUFBSyxTQUFVLENBRWQsR0FBb0IsT0FBaEJpYyxFQUNILE1BSUQsR0FBSTlmLEVBQVFrZCxTQUFXbGQsRUFBUTBkLE9BRzlCLE9BRkF4VyxFQUFPLElBQUkrUixFQUFXLGdDQUFnQ2paLEVBQVFKLE1BQU8sc0JBQ3JFeWYsSUFNRCxNQUFNVSxFQUFpQixDQUN0QjVaLFFBQVMsSUFBSWdNLEVBQVFuUyxFQUFRbUcsU0FDN0J1WCxPQUFRMWQsRUFBUTBkLE9BQ2hCUixRQUFTbGQsRUFBUWtkLFFBQVUsRUFDM0JVLE1BQU81ZCxFQUFRNGQsTUFDZkQsU0FBVTNkLEVBQVEyZCxTQUNsQnZYLE9BQVFwRyxFQUFRb0csT0FDaEJ0RSxLQUFNOUIsRUFBUThCLEtBQ2RqRCxPQUFRbUIsRUFBUW5CLE9BQ2hCdVIsS0FBTXBRLEVBQVFvUSxNQUlmLE9BQTZCLE1BQXpCb1AsRUFBVUssWUFBc0I3ZixFQUFROEIsTUFBUW1jLEVBQVNuYyxnQkFBZ0I2VyxFQUFPM0ksVUFDbkY5SSxFQUFPLElBQUkrUixFQUFXLDJEQUE0RCw4QkFDbEZvRyxNQUs0QixNQUF6QkcsRUFBVUssYUFBaUQsTUFBekJMLEVBQVVLLFlBQStDLE1BQXpCTCxFQUFVSyxZQUEwQyxTQUFuQjdmLEVBQVFvRyxVQUM5RzJaLEVBQWUzWixPQUFTLE1BQ3hCMlosRUFBZWplLFVBQU8wTixFQUN0QnVRLEVBQWU1WixRQUFRckQsT0FBTyxtQkFJL0JtRSxFQUFRK0ssRUFBTSxJQUFJL0osRUFBUTZYLEVBQWFDLFVBQ3ZDVixPQVFIRyxFQUFVelEsS0FBSyxPQUFPLEtBQ2pCbFEsR0FDSEEsRUFBTzBnQixvQkFBb0IsUUFBU0gsTUFJdEMsSUFBSXRkLEVBQU82VyxFQUFPcUgsU0FBU1IsRUFBVyxJQUFJN0csRUFBT21ELGFBQWVqWSxJQUMvRHFELEVBQU9yRCxNQUdKb2MsUUFBUUMsUUFBVSxVQUNyQlYsRUFBVXJZLEdBQUcsVUFBV2lZLEdBR3pCLE1BQU1lLEVBQWtCLENBQ3ZCdmdCLElBQUtJLEVBQVFKLElBQ2I2RCxPQUFRK2IsRUFBVUssV0FDbEJuYyxXQUFZOGIsRUFBVVksY0FDdEJqYSxVQUNBaUssS0FBTXBRLEVBQVFvUSxLQUNkOE0sUUFBU2xkLEVBQVFrZCxRQUNqQmhMLGNBQWVsUyxFQUFRa1MsZUFJbEJtTyxFQUFVbGEsRUFBUWxJLElBQUksb0JBVTVCLElBQUsrQixFQUFRMmQsVUFBK0IsU0FBbkIzZCxFQUFRb0csUUFBaUMsT0FBWmlhLEdBQTZDLE1BQXpCYixFQUFVSyxZQUErQyxNQUF6QkwsRUFBVUssV0FHbkgsT0FGQWhlLEVBQVcsSUFBSXVRLEVBQVN0USxFQUFNcWUsUUFDOUJsWixFQUFRcEYsR0FTVCxNQUFNeWUsRUFBYyxDQUNuQkMsTUFBTzdILEVBQUs4SCxhQUNaQyxZQUFhL0gsRUFBSzhILGNBSW5CLEdBQWdCLFNBQVpILEdBQWtDLFdBQVpBLEVBTXpCLE9BTEF2ZSxFQUFPNlcsRUFBT3FILFNBQVNsZSxFQUFNNFcsRUFBS2dJLGFBQWFKLElBQWN6YyxJQUM1RHFELEVBQU9yRCxNQUVSaEMsRUFBVyxJQUFJdVEsRUFBU3RRLEVBQU1xZSxRQUM5QmxaLEVBQVFwRixHQUtULEdBQWdCLFlBQVp3ZSxHQUFxQyxjQUFaQSxFQUE3QixDQXlCQSxHQUFnQixPQUFaQSxFQU1ILE9BTEF2ZSxFQUFPNlcsRUFBT3FILFNBQVNsZSxFQUFNNFcsRUFBS2lJLDBCQUEwQjljLElBQzNEcUQsRUFBT3JELE1BRVJoQyxFQUFXLElBQUl1USxFQUFTdFEsRUFBTXFlLFFBQzlCbFosRUFBUXBGLEdBS1RBLEVBQVcsSUFBSXVRLEVBQVN0USxFQUFNcWUsR0FDOUJsWixFQUFRcEYsUUFqQ0s4VyxFQUFPcUgsU0FBU1IsRUFBVyxJQUFJN0csRUFBT21ELGFBQWVqWSxJQUNoRXFELEVBQU9yRCxNQUVKa0wsS0FBSyxRQUFRM0gsSUFHZnRGLEVBRHlCLElBQVYsR0FBWHNGLEVBQU0sSUFDSHVSLEVBQU9xSCxTQUFTbGUsRUFBTTRXLEVBQUtrSSxpQkFBaUIvYyxJQUNsRHFELEVBQU9yRCxNQUdEOFUsRUFBT3FILFNBQVNsZSxFQUFNNFcsRUFBS21JLG9CQUFvQmhkLElBQ3JEcUQsRUFBT3JELE1BSVRoQyxFQUFXLElBQUl1USxFQUFTdFEsRUFBTXFlLEdBQzlCbFosRUFBUXBGLFNBbjNCUyxFQUFDaWYsR0FBT2hmLFdBQ2hCLE9BQVRBLEVBRUhnZixFQUFLMVksTUFDS3VSLEVBQU83WCxHQUVqQkEsRUFBS2dGLFNBQVNoQixLQUFLZ2IsR0FDVHhaLE9BQU9rVCxTQUFTMVksSUFFMUJnZixFQUFLQyxNQUFNamYsR0FDWGdmLEVBQUsxWSxPQUdMdEcsRUFBS2dFLEtBQUtnYixJQTAzQlZFLENBQWMxQixFQUFVdGYsTUFJMUI5QyxFQUFRNmdCLFdBQWFBLEVBQ3JCN2dCLEVBQVErYixXQUFhQSxFQUNyQi9iLEVBQVFpVixRQUFVQSxFQUNsQmpWLEVBQVErSyxRQUFVQSxFQUNsQi9LLEVBQVFrVixTQUFXQSxFQUNuQmxWLEVBQVFvQyxRQUFVMFMsRUFDbEI5VSxFQUFROGYsV0FBYUEsRyxPQ2o4Q3JCLFNBQVNpRSxFQUFXNVcsR0FDbEIsT0FBT0EsRUFDRUksUUFBUSxTQUFVLEtBQ2xCQSxRQUFRLFFBQVMsS0FDakJBLFFBQVEsUUFBUyxLQUNqQkEsUUFBUSxRQUFTLE9BRzVCdE4sRUFBT0QsUUFBVSxXQUNmLElBQUlna0IsRUFBUyxHQUFHdlAsTUFBTTVTLEtBQUs0TixVQUFXLEdBQUdqSCxLQUFLLEtBQzlDLE9BQU91YixFQUFVQyxLLDJjQ05uQixNQUFNQyxFQUFtQyxtQkFBWDVpQixRQUFvRCxpQkFBcEJBLE9BQU84ZCxTQUNqRTlkLE9BQ0E2aUIsR0FBZSxVQUFVQSxLQUc3QixTQUFTQyxLQWVULE1BQU05TyxFQVhrQixvQkFBVEcsS0FDQUEsS0FFZ0Isb0JBQVgzRSxPQUNMQSxPQUVnQixvQkFBWGtFLE9BQ0xBLFlBRE4sRUFPVCxTQUFTcVAsRUFBYW5ULEdBQ2xCLE1BQXFCLGlCQUFOQSxHQUF3QixPQUFOQSxHQUE0QixtQkFBTkEsRUFFM0QsTUFBTW9ULEVBQWtDRixFQUVsQ0csRUFBa0J4YSxRQUNsQnlhLEVBQXNCemEsUUFBUTdJLFVBQVV3RSxLQUN4QytlLEVBQXlCMWEsUUFBUUMsUUFBUTZMLEtBQUswTyxHQUM5Q0csRUFBd0IzYSxRQUFRRSxPQUFPNEwsS0FBSzBPLEdBQ2xELFNBQVNJLEVBQVdDLEdBQ2hCLE9BQU8sSUFBSUwsRUFBZ0JLLEdBRS9CLFNBQVNDLEVBQW9CcmtCLEdBQ3pCLE9BQU9pa0IsRUFBdUJqa0IsR0FFbEMsU0FBU3NrQixFQUFvQkMsR0FDekIsT0FBT0wsRUFBc0JLLEdBRWpDLFNBQVNDLEVBQW1CQyxFQUFTQyxFQUFhQyxHQUc5QyxPQUFPWCxFQUFvQjFpQixLQUFLbWpCLEVBQVNDLEVBQWFDLEdBRTFELFNBQVNDLEVBQVlILEVBQVNDLEVBQWFDLEdBQ3ZDSCxFQUFtQkEsRUFBbUJDLEVBQVNDLEVBQWFDLFFBQWE1UyxFQUFXK1IsR0FFeEYsU0FBU2UsRUFBZ0JKLEVBQVNDLEdBQzlCRSxFQUFZSCxFQUFTQyxHQUV6QixTQUFTSSxFQUFjTCxFQUFTRSxHQUM1QkMsRUFBWUgsT0FBUzFTLEVBQVc0UyxHQUVwQyxTQUFTSSxFQUFxQk4sRUFBU08sRUFBb0JDLEdBQ3ZELE9BQU9ULEVBQW1CQyxFQUFTTyxFQUFvQkMsR0FFM0QsU0FBU0MsRUFBMEJULEdBQy9CRCxFQUFtQkMsT0FBUzFTLEVBQVcrUixHQUUzQyxNQUFNcUIsRUFBaUIsTUFDbkIsTUFBTUMsRUFBdUJ0USxHQUFXQSxFQUFRcVEsZUFDaEQsR0FBb0MsbUJBQXpCQyxFQUNQLE9BQU9BLEVBRVgsTUFBTUMsRUFBa0JoQixPQUFvQnRTLEdBQzVDLE9BQVE0RyxHQUFPNkwsRUFBbUJhLEVBQWlCMU0sSUFOaEMsR0FRdkIsU0FBUzJNLEVBQVlDLEVBQUdDLEVBQUdDLEdBQ3ZCLEdBQWlCLG1CQUFORixFQUNQLE1BQU0sSUFBSWxsQixVQUFVLDhCQUV4QixPQUFPcWxCLFNBQVNobEIsVUFBVXVPLE1BQU0zTixLQUFLaWtCLEVBQUdDLEVBQUdDLEdBRS9DLFNBQVNFLEVBQVlKLEVBQUdDLEVBQUdDLEdBQ3ZCLElBQ0ksT0FBT3BCLEVBQW9CaUIsRUFBWUMsRUFBR0MsRUFBR0MsSUFFakQsTUFBT3psQixHQUNILE9BQU9za0IsRUFBb0J0a0IsSUFhbkMsTUFBTTRsQixFQUNGLGNBQ0kvbEIsS0FBS2dtQixRQUFVLEVBQ2ZobUIsS0FBS2ltQixNQUFRLEVBRWJqbUIsS0FBS2ttQixPQUFTLENBQ1ZDLFVBQVcsR0FDWEMsV0FBT2xVLEdBRVhsUyxLQUFLcW1CLE1BQVFybUIsS0FBS2ttQixPQUlsQmxtQixLQUFLZ21CLFFBQVUsRUFFZmhtQixLQUFLaW1CLE1BQVEsRUFFakIsYUFDSSxPQUFPam1CLEtBQUtpbUIsTUFNaEIsS0FBS2pULEdBQ0QsTUFBTXNULEVBQVV0bUIsS0FBS3FtQixNQUNyQixJQUFJRSxFQUFVRCxFQUNtQkUsUUFBN0JGLEVBQVFILFVBQVVoZCxTQUNsQm9kLEVBQVUsQ0FDTkosVUFBVyxHQUNYQyxXQUFPbFUsSUFLZm9VLEVBQVFILFVBQVVwYyxLQUFLaUosR0FDbkJ1VCxJQUFZRCxJQUNadG1CLEtBQUtxbUIsTUFBUUUsRUFDYkQsRUFBUUYsTUFBUUcsS0FFbEJ2bUIsS0FBS2ltQixNQUlYLFFBQ0ksTUFBTVEsRUFBV3ptQixLQUFLa21CLE9BQ3RCLElBQUlRLEVBQVdELEVBQ2YsTUFBTUUsRUFBWTNtQixLQUFLZ21CLFFBQ3ZCLElBQUlZLEVBQVlELEVBQVksRUFDNUIsTUFBTUUsRUFBV0osRUFBU04sVUFDcEJuVCxFQUFVNlQsRUFBU0YsR0FhekIsT0F0RXFCLFFBMERqQkMsSUFDQUYsRUFBV0QsRUFBU0wsTUFDcEJRLEVBQVksS0FHZDVtQixLQUFLaW1CLE1BQ1BqbUIsS0FBS2dtQixRQUFVWSxFQUNYSCxJQUFhQyxJQUNiMW1CLEtBQUtrbUIsT0FBU1EsR0FHbEJHLEVBQVNGLFFBQWF6VSxFQUNmYyxFQVVYLFFBQVF1TSxHQUNKLElBQUk3UixFQUFJMU4sS0FBS2dtQixRQUNUOVUsRUFBT2xSLEtBQUtrbUIsT0FDWlcsRUFBVzNWLEVBQUtpVixVQUNwQixPQUFPelksSUFBTW1aLEVBQVMxZCxhQUF5QitJLElBQWZoQixFQUFLa1YsT0FDN0IxWSxJQUFNbVosRUFBUzFkLFNBQ2YrSCxFQUFPQSxFQUFLa1YsTUFDWlMsRUFBVzNWLEVBQUtpVixVQUNoQnpZLEVBQUksRUFDb0IsSUFBcEJtWixFQUFTMWQsVUFJakJvVyxFQUFTc0gsRUFBU25aLE1BQ2hCQSxFQUtWLE9BQ0ksTUFBTW9aLEVBQVE5bUIsS0FBS2ttQixPQUNiYSxFQUFTL21CLEtBQUtnbUIsUUFDcEIsT0FBT2MsRUFBTVgsVUFBVVksSUFJL0IsU0FBU0MsRUFBc0MxTSxFQUFROVEsR0FDbkQ4USxFQUFPMk0scUJBQXVCemQsRUFDOUJBLEVBQU8wZCxRQUFVNU0sRUFDSyxhQUFsQjlRLEVBQU8yZCxPQUNQQyxFQUFxQzlNLEdBRWQsV0FBbEI5USxFQUFPMmQsT0FzQ3BCLFNBQXdEN00sR0FDcEQ4TSxFQUFxQzlNLEdBQ3JDK00sRUFBa0MvTSxHQXZDOUJnTixDQUErQ2hOLEdBRy9DaU4sRUFBK0NqTixFQUFROVEsRUFBT2dlLGNBS3RFLFNBQVNDLEVBQWtDbk4sRUFBUW9LLEdBRS9DLE9BQU9nRCxHQURRcE4sRUFBTzJNLHFCQUNjdkMsR0FFeEMsU0FBU2lELEVBQW1Dck4sR0FDRyxhQUF2Q0EsRUFBTzJNLHFCQUFxQkUsT0FDNUJTLEVBQWlDdE4sRUFBUSxJQUFJOVosVUFBVSxxRkFvQy9ELFNBQW1EOFosRUFBUW9LLEdBQ3ZENkMsRUFBK0NqTixFQWxDTyxJQUFJOVosVUFBVSxxRkFBaEVxbkIsQ0FBMEN2TixHQUU5Q0EsRUFBTzJNLHFCQUFxQkMsYUFBVWhWLEVBQ3RDb0ksRUFBTzJNLDBCQUF1Qi9VLEVBR2xDLFNBQVM0VixFQUFvQm5rQixHQUN6QixPQUFPLElBQUluRCxVQUFVLFVBQVltRCxFQUFPLHFDQUc1QyxTQUFTeWpCLEVBQXFDOU0sR0FDMUNBLEVBQU95TixlQUFpQnpELEdBQVcsQ0FBQzNhLEVBQVNDLEtBQ3pDMFEsRUFBTzBOLHVCQUF5QnJlLEVBQ2hDMlEsRUFBTzJOLHNCQUF3QnJlLEtBR3ZDLFNBQVMyZCxFQUErQ2pOLEVBQVFvSyxHQUM1RDBDLEVBQXFDOU0sR0FDckNzTixFQUFpQ3ROLEVBQVFvSyxHQU03QyxTQUFTa0QsRUFBaUN0TixFQUFRb0ssUUFDVHhTLElBQWpDb0ksRUFBTzJOLHdCQUdYNUMsRUFBMEIvSyxFQUFPeU4sZ0JBQ2pDek4sRUFBTzJOLHNCQUFzQnZELEdBQzdCcEssRUFBTzBOLDRCQUF5QjlWLEVBQ2hDb0ksRUFBTzJOLDJCQUF3Qi9WLEdBS25DLFNBQVNtVixFQUFrQy9NLFFBQ0RwSSxJQUFsQ29JLEVBQU8wTix5QkFHWDFOLEVBQU8wTiw0QkFBdUI5VixHQUM5Qm9JLEVBQU8wTiw0QkFBeUI5VixFQUNoQ29JLEVBQU8yTiwyQkFBd0IvVixHQUduQyxNQUFNZ1csRUFBYXJFLEVBQWUsa0JBQzVCc0UsRUFBYXRFLEVBQWUsa0JBQzVCdUUsRUFBY3ZFLEVBQWUsbUJBQzdCd0UsRUFBWXhFLEVBQWUsaUJBSTNCeUUsRUFBaUIxTyxPQUFPMk8sVUFBWSxTQUFVMVgsR0FDaEQsTUFBb0IsaUJBQU5BLEdBQWtCMFgsU0FBUzFYLElBS3ZDMlgsRUFBWTFVLEtBQUsyVSxPQUFTLFNBQVVDLEdBQ3RDLE9BQU9BLEVBQUksRUFBSTVVLEtBQUs2VSxLQUFLRCxHQUFLNVUsS0FBSzhVLE1BQU1GLElBTzdDLFNBQVNHLEVBQWlCbmUsRUFBS29lLEdBQzNCLFFBQVk1VyxJQUFSeEgsR0FIZ0IsaUJBREZtRyxFQUlxQm5HLElBSE0sbUJBQU5tRyxFQUluQyxNQUFNLElBQUlyUSxVQUFVLEdBQUdzb0IsdUJBTC9CLElBQXNCalksRUFTdEIsU0FBU2tZLEVBQWVsWSxFQUFHaVksR0FDdkIsR0FBaUIsbUJBQU5qWSxFQUNQLE1BQU0sSUFBSXJRLFVBQVUsR0FBR3NvQix3QkFPL0IsU0FBU0UsRUFBYW5ZLEVBQUdpWSxHQUNyQixJQUpKLFNBQWtCalksR0FDZCxNQUFxQixpQkFBTkEsR0FBd0IsT0FBTkEsR0FBNEIsbUJBQU5BLEVBR2xERCxDQUFTQyxHQUNWLE1BQU0sSUFBSXJRLFVBQVUsR0FBR3NvQix1QkFHL0IsU0FBU0csRUFBdUJwWSxFQUFHcVksRUFBVUosR0FDekMsUUFBVTVXLElBQU5yQixFQUNBLE1BQU0sSUFBSXJRLFVBQVUsYUFBYTBvQixxQkFBNEJKLE9BR3JFLFNBQVNLLEVBQW9CdFksRUFBR2lNLEVBQU9nTSxHQUNuQyxRQUFVNVcsSUFBTnJCLEVBQ0EsTUFBTSxJQUFJclEsVUFBVSxHQUFHc2MscUJBQXlCZ00sT0FJeEQsU0FBU00sRUFBMEJqcEIsR0FDL0IsT0FBT3laLE9BQU96WixHQUVsQixTQUFTa3BCLEVBQW1CeFksR0FDeEIsT0FBYSxJQUFOQSxFQUFVLEVBQUlBLEVBTXpCLFNBQVN5WSxFQUF3Q25wQixFQUFPMm9CLEdBQ3BELE1BQ01TLEVBQWEzUCxPQUFPNFAsaUJBQzFCLElBQUkzWSxFQUFJK0ksT0FBT3paLEdBRWYsR0FEQTBRLEVBQUl3WSxFQUFtQnhZLElBQ2xCeVgsRUFBZXpYLEdBQ2hCLE1BQU0sSUFBSXJRLFVBQVUsR0FBR3NvQiw0QkFHM0IsR0FEQWpZLEVBWkosU0FBcUJBLEdBQ2pCLE9BQU93WSxFQUFtQmIsRUFBVTNYLElBV2hDNFksQ0FBWTVZLEdBQ1pBLEVBUmUsR0FRR0EsRUFBSTBZLEVBQ3RCLE1BQU0sSUFBSS9vQixVQUFVLEdBQUdzb0IsMkNBQTZEUyxnQkFFeEYsT0FBS2pCLEVBQWV6WCxJQUFZLElBQU5BLEVBT25CQSxFQU5JLEVBU2YsU0FBUzZZLEVBQXFCN1ksRUFBR2lZLEdBQzdCLElBQUthLEdBQWlCOVksR0FDbEIsTUFBTSxJQUFJclEsVUFBVSxHQUFHc29CLDhCQUsvQixTQUFTYyxFQUFtQ3BnQixHQUN4QyxPQUFPLElBQUlxZ0IsRUFBNEJyZ0IsR0FHM0MsU0FBU3NnQixFQUE2QnRnQixFQUFRdWdCLEdBQzFDdmdCLEVBQU8wZCxRQUFROEMsY0FBY2pnQixLQUFLZ2dCLEdBRXRDLFNBQVNFLEVBQWlDemdCLEVBQVFNLEVBQU8yUSxHQUNyRCxNQUNNc1AsRUFEU3ZnQixFQUFPMGQsUUFDSzhDLGNBQWNFLFFBQ3JDelAsRUFDQXNQLEVBQVlJLGNBR1pKLEVBQVlLLFlBQVl0Z0IsR0FHaEMsU0FBU3VnQixFQUFpQzdnQixHQUN0QyxPQUFPQSxFQUFPMGQsUUFBUThDLGNBQWM3Z0IsT0FFeEMsU0FBU21oQixFQUErQjlnQixHQUNwQyxNQUFNOFEsRUFBUzlRLEVBQU8wZCxRQUN0QixZQUFlaFYsSUFBWG9JLEtBR0NpUSxHQUE4QmpRLEdBVXZDLE1BQU11UCxFQUNGLFlBQVlyZ0IsR0FHUixHQUZBeWYsRUFBdUJ6ZixFQUFRLEVBQUcsK0JBQ2xDa2dCLEVBQXFCbGdCLEVBQVEsbUJBQ3pCZ2hCLEdBQXVCaGhCLEdBQ3ZCLE1BQU0sSUFBSWhKLFVBQVUsK0VBRXhCd21CLEVBQXNDaG5CLEtBQU13SixHQUM1Q3hKLEtBQUtncUIsY0FBZ0IsSUFBSWpFLEVBTTdCLGFBQ0ksT0FBS3dFLEdBQThCdnFCLE1BRzVCQSxLQUFLK25CLGVBRkR0RCxFQUFvQmdHLEdBQWlDLFdBT3BFLE9BQU8vRixHQUNILE9BQUs2RixHQUE4QnZxQixXQUdEa1MsSUFBOUJsUyxLQUFLaW5CLHFCQUNFeEMsRUFBb0JxRCxFQUFvQixXQUU1Q0wsRUFBa0N6bkIsS0FBTTBrQixHQUxwQ0QsRUFBb0JnRyxHQUFpQyxXQVlwRSxPQUNJLElBQUtGLEdBQThCdnFCLE1BQy9CLE9BQU95a0IsRUFBb0JnRyxHQUFpQyxTQUVoRSxRQUFrQ3ZZLElBQTlCbFMsS0FBS2luQixxQkFDTCxPQUFPeEMsRUFBb0JxRCxFQUFvQixjQUVuRCxJQUFJNEMsRUFDQUMsRUFDSixNQUFNL0YsRUFBVU4sR0FBVyxDQUFDM2EsRUFBU0MsS0FDakM4Z0IsRUFBaUIvZ0IsRUFDakJnaEIsRUFBZ0IvZ0IsS0FRcEIsT0FEQWdoQixHQUFnQzVxQixLQUxaLENBQ2hCb3FCLFlBQWF0Z0IsR0FBUzRnQixFQUFlLENBQUV2cUIsTUFBTzJKLEVBQU8yUSxNQUFNLElBQzNEMFAsWUFBYSxJQUFNTyxFQUFlLENBQUV2cUIsV0FBTytSLEVBQVd1SSxNQUFNLElBQzVEb1EsWUFBYUMsR0FBS0gsRUFBY0csS0FHN0JsRyxFQVdYLGNBQ0ksSUFBSzJGLEdBQThCdnFCLE1BQy9CLE1BQU15cUIsR0FBaUMsZUFFM0MsUUFBa0N2WSxJQUE5QmxTLEtBQUtpbkIscUJBQVQsQ0FHQSxHQUFJam5CLEtBQUtncUIsY0FBYzdnQixPQUFTLEVBQzVCLE1BQU0sSUFBSTNJLFVBQVUsdUZBRXhCbW5CLEVBQW1DM25CLFFBZ0IzQyxTQUFTdXFCLEdBQThCMVosR0FDbkMsUUFBS21ULEVBQWFuVCxNQUdiNVEsT0FBT1ksVUFBVWtxQixlQUFldHBCLEtBQUtvUCxFQUFHLGlCQUtqRCxTQUFTK1osR0FBZ0N0USxFQUFReVAsR0FDN0MsTUFBTXZnQixFQUFTOFEsRUFBTzJNLHFCQUN0QnpkLEVBQU93aEIsWUFBYSxFQUNFLFdBQWxCeGhCLEVBQU8yZCxPQUNQNEMsRUFBWUksY0FFVyxZQUFsQjNnQixFQUFPMmQsT0FDWjRDLEVBQVljLFlBQVlyaEIsRUFBT2dlLGNBRy9CaGUsRUFBT3loQiwwQkFBMEI1QyxHQUFXMEIsR0FJcEQsU0FBU1UsR0FBaUM5bUIsR0FDdEMsT0FBTyxJQUFJbkQsVUFBVSx5Q0FBeUNtRCx1REFyQ2xFMUQsT0FBT2MsaUJBQWlCOG9CLEVBQTRCaHBCLFVBQVcsQ0FDM0RxcUIsT0FBUSxDQUFFbHFCLFlBQVksR0FDdEI0UyxLQUFNLENBQUU1UyxZQUFZLEdBQ3BCbXFCLFlBQWEsQ0FBRW5xQixZQUFZLEdBQzNCb3FCLE9BQVEsQ0FBRXBxQixZQUFZLEtBRWdCLGlCQUEvQjZpQixFQUFlM2lCLGFBQ3RCakIsT0FBT0MsZUFBZTJwQixFQUE0QmhwQixVQUFXZ2pCLEVBQWUzaUIsWUFBYSxDQUNyRmYsTUFBTyw4QkFDUGdCLGNBQWMsSUFpQ3RCLE1BQU1rcUIsR0FBeUJwckIsT0FBTytQLGVBQWUvUCxPQUFPK1AsZ0JBQWUwRCxzQkFBd0I3UyxXQUduRyxNQUFNeXFCLEdBQ0YsWUFBWWhSLEVBQVFpUixHQUNoQnZyQixLQUFLd3JCLHFCQUFrQnRaLEVBQ3ZCbFMsS0FBS3lyQixhQUFjLEVBQ25CenJCLEtBQUtrbkIsUUFBVTVNLEVBQ2Z0YSxLQUFLMHJCLGVBQWlCSCxFQUUxQixPQUNJLE1BQU1JLEVBQVksSUFBTTNyQixLQUFLNHJCLGFBSTdCLE9BSEE1ckIsS0FBS3dyQixnQkFBa0J4ckIsS0FBS3dyQixnQkFDeEJ0RyxFQUFxQmxsQixLQUFLd3JCLGdCQUFpQkcsRUFBV0EsR0FDdERBLElBQ0czckIsS0FBS3dyQixnQkFFaEIsT0FBT3JyQixHQUNILE1BQU0wckIsRUFBYyxJQUFNN3JCLEtBQUs4ckIsYUFBYTNyQixHQUM1QyxPQUFPSCxLQUFLd3JCLGdCQUNSdEcsRUFBcUJsbEIsS0FBS3dyQixnQkFBaUJLLEVBQWFBLEdBQ3hEQSxJQUVSLGFBQ0ksR0FBSTdyQixLQUFLeXJCLFlBQ0wsT0FBTy9oQixRQUFRQyxRQUFRLENBQUV4SixXQUFPK1IsRUFBV3VJLE1BQU0sSUFFckQsTUFBTUgsRUFBU3RhLEtBQUtrbkIsUUFDcEIsUUFBb0NoVixJQUFoQ29JLEVBQU8yTSxxQkFDUCxPQUFPeEMsRUFBb0JxRCxFQUFvQixZQUVuRCxJQUFJNEMsRUFDQUMsRUFDSixNQUFNL0YsRUFBVU4sR0FBVyxDQUFDM2EsRUFBU0MsS0FDakM4Z0IsRUFBaUIvZ0IsRUFDakJnaEIsRUFBZ0IvZ0IsS0F1QnBCLE9BREFnaEIsR0FBZ0N0USxFQXBCWixDQUNoQjhQLFlBQWF0Z0IsSUFDVDlKLEtBQUt3ckIscUJBQWtCdFosRUFHdkJvVCxHQUFlLElBQU1vRixFQUFlLENBQUV2cUIsTUFBTzJKLEVBQU8yUSxNQUFNLE9BRTlEMFAsWUFBYSxLQUNUbnFCLEtBQUt3ckIscUJBQWtCdFosRUFDdkJsUyxLQUFLeXJCLGFBQWMsRUFDbkI5RCxFQUFtQ3JOLEdBQ25Db1EsRUFBZSxDQUFFdnFCLFdBQU8rUixFQUFXdUksTUFBTSxLQUU3Q29RLFlBQWFuRyxJQUNUMWtCLEtBQUt3ckIscUJBQWtCdFosRUFDdkJsUyxLQUFLeXJCLGFBQWMsRUFDbkI5RCxFQUFtQ3JOLEdBQ25DcVEsRUFBY2pHLE1BSWZFLEVBRVgsYUFBYXprQixHQUNULEdBQUlILEtBQUt5ckIsWUFDTCxPQUFPL2hCLFFBQVFDLFFBQVEsQ0FBRXhKLFFBQU9zYSxNQUFNLElBRTFDemEsS0FBS3lyQixhQUFjLEVBQ25CLE1BQU1uUixFQUFTdGEsS0FBS2tuQixRQUNwQixRQUFvQ2hWLElBQWhDb0ksRUFBTzJNLHFCQUNQLE9BQU94QyxFQUFvQnFELEVBQW9CLHFCQUVuRCxJQUFLOW5CLEtBQUswckIsZUFBZ0IsQ0FDdEIsTUFBTTNWLEVBQVMwUixFQUFrQ25OLEVBQVFuYSxHQUV6RCxPQURBd25CLEVBQW1Dck4sR0FDNUI0SyxFQUFxQm5QLEdBQVEsS0FBTSxDQUFHNVYsUUFBT3NhLE1BQU0sTUFHOUQsT0FEQWtOLEVBQW1Dck4sR0FDNUJrSyxFQUFvQixDQUFFcmtCLFFBQU9zYSxNQUFNLEtBR2xELE1BQU1zUixHQUF1QyxDQUN6QyxPQUNJLE9BQUtDLEdBQThCaHNCLE1BRzVCQSxLQUFLaXNCLG1CQUFtQjVhLE9BRnBCb1QsRUFBb0J5SCxHQUF1QyxVQUkxRSxPQUFPL3JCLEdBQ0gsT0FBSzZyQixHQUE4QmhzQixNQUc1QkEsS0FBS2lzQixtQkFBbUJFLE9BQU9oc0IsR0FGM0Jza0IsRUFBb0J5SCxHQUF1QyxhQWdCOUUsU0FBU0YsR0FBOEJuYixHQUNuQyxRQUFLbVQsRUFBYW5ULE1BR2I1USxPQUFPWSxVQUFVa3FCLGVBQWV0cEIsS0FBS29QLEVBQUcsc0JBTWpELFNBQVNxYixHQUF1Q3ZvQixHQUM1QyxPQUFPLElBQUluRCxVQUFVLCtCQUErQm1ELDJEQXRCekJ1TyxJQUEzQm1aLElBQ0FwckIsT0FBT3lRLGVBQWVxYixHQUFzQ1YsSUEwQmhFLE1BQU1lLEdBQWN4UyxPQUFPQyxPQUFTLFNBQVVoSixHQUUxQyxPQUFPQSxHQUFNQSxHQUdqQixTQUFTd2IsR0FBMEIzRCxHQUMvQixRQVFKLFNBQTZCQSxHQUN6QixNQUFpQixpQkFBTkEsS0FHUDBELEdBQVkxRCxNQUdaQSxFQUFJLElBZkg0RCxDQUFvQjVELElBR3JCQSxJQUFNNkQsSUFrQmQsU0FBU0MsR0FBYUMsR0FDbEIsTUFBTXpOLEVBQU95TixFQUFVQyxPQUFPeEMsUUFLOUIsT0FKQXVDLEVBQVVFLGlCQUFtQjNOLEVBQUtsTSxLQUM5QjJaLEVBQVVFLGdCQUFrQixJQUM1QkYsRUFBVUUsZ0JBQWtCLEdBRXpCM04sRUFBSzdlLE1BRWhCLFNBQVN5c0IsR0FBcUJILEVBQVd0c0IsRUFBTzJTLEdBRTVDLElBQUt1WixHQURMdlosRUFBTzhHLE9BQU85RyxJQUVWLE1BQU0sSUFBSWlHLFdBQVcsd0RBRXpCMFQsRUFBVUMsT0FBTzNpQixLQUFLLENBQUU1SixRQUFPMlMsU0FDL0IyWixFQUFVRSxpQkFBbUI3WixFQU1qQyxTQUFTK1osR0FBV0osR0FDaEJBLEVBQVVDLE9BQVMsSUFBSTNHLEVBQ3ZCMEcsRUFBVUUsZ0JBQWtCLEVBR2hDLFNBQVNHLEdBQW9CakcsR0FHekIsT0FBT0EsRUFBU3hTLFFBbUJwQixNQUFNMFksR0FDRixjQUNJLE1BQU0sSUFBSXZzQixVQUFVLHVCQUt4QixXQUNJLElBQUt3c0IsR0FBNEJodEIsTUFDN0IsTUFBTWl0QixHQUErQixRQUV6QyxPQUFPanRCLEtBQUtrdEIsTUFFaEIsUUFBUUMsR0FDSixJQUFLSCxHQUE0Qmh0QixNQUM3QixNQUFNaXRCLEdBQStCLFdBSXpDLEdBRkFoRSxFQUF1QmtFLEVBQWMsRUFBRyxXQUN4Q0EsRUFBZTdELEVBQXdDNkQsRUFBYyx3QkFDaEJqYixJQUFqRGxTLEtBQUtvdEIsd0NBQ0wsTUFBTSxJQUFJNXNCLFVBQVUsMENBRUhSLEtBQUtrdEIsTUFBTXJmLE9BdWZ4QyxTQUE2Qy9MLEVBQVlxckIsR0FFckQsSUFBS2QsR0FETGMsRUFBZXZULE9BQU91VCxJQUVsQixNQUFNLElBQUlwVSxXQUFXLGlDQUV6QnNVLEdBQTRDdnJCLEVBQVlxckIsR0EzZnBERyxDQUFvQ3R0QixLQUFLb3RCLHdDQUF5Q0QsR0FFdEYsbUJBQW1CSSxHQUNmLElBQUtQLEdBQTRCaHRCLE1BQzdCLE1BQU1pdEIsR0FBK0Isc0JBR3pDLEdBREFoRSxFQUF1QnNFLEVBQU0sRUFBRyx1QkFDM0J0YSxZQUFZQyxPQUFPcWEsR0FDcEIsTUFBTSxJQUFJL3NCLFVBQVUsZ0RBRXhCLEdBQXdCLElBQXBCK3NCLEVBQUtuYSxXQUNMLE1BQU0sSUFBSTVTLFVBQVUsdUNBRXhCLEdBQStCLElBQTNCK3NCLEVBQUsxZixPQUFPdUYsV0FDWixNQUFNLElBQUk1UyxVQUFVLGdEQUV4QixRQUFxRDBSLElBQWpEbFMsS0FBS290Qix3Q0FDTCxNQUFNLElBQUk1c0IsVUFBVSwyQ0E0ZWhDLFNBQXdEc0IsRUFBWXlyQixHQUNoRSxNQUFNQyxFQUFrQjFyQixFQUFXMnJCLGtCQUFrQkMsT0FDckQsR0FBSUYsRUFBZ0JyYSxXQUFhcWEsRUFBZ0JHLGNBQWdCSixFQUFLcGEsV0FDbEUsTUFBTSxJQUFJNEYsV0FBVywyREFFekIsR0FBSXlVLEVBQWdCcGEsYUFBZW1hLEVBQUtuYSxXQUNwQyxNQUFNLElBQUkyRixXQUFXLDhEQUV6QnlVLEVBQWdCM2YsT0FBUzBmLEVBQUsxZixPQUM5QndmLEdBQTRDdnJCLEVBQVl5ckIsRUFBS25hLFlBbmZ6RHdhLENBQStDNXRCLEtBQUtvdEIsd0NBQXlDRyxJQUdyR3R0QixPQUFPYyxpQkFBaUJnc0IsR0FBMEJsc0IsVUFBVyxDQUN6RGd0QixRQUFTLENBQUU3c0IsWUFBWSxHQUN2QjhzQixtQkFBb0IsQ0FBRTlzQixZQUFZLEdBQ2xDdXNCLEtBQU0sQ0FBRXZzQixZQUFZLEtBRWtCLGlCQUEvQjZpQixFQUFlM2lCLGFBQ3RCakIsT0FBT0MsZUFBZTZzQixHQUEwQmxzQixVQUFXZ2pCLEVBQWUzaUIsWUFBYSxDQUNuRmYsTUFBTyw0QkFDUGdCLGNBQWMsSUFRdEIsTUFBTTRzQixHQUNGLGNBQ0ksTUFBTSxJQUFJdnRCLFVBQVUsdUJBS3hCLGtCQUNJLElBQUt3dEIsR0FBK0JodUIsTUFDaEMsTUFBTWl1QixHQUF3QyxlQUVsRCxHQUEwQixPQUF0Qmp1QixLQUFLa3VCLGNBQXlCbHVCLEtBQUt5dEIsa0JBQWtCdGtCLE9BQVMsRUFBRyxDQUNqRSxNQUFNcWtCLEVBQWtCeHRCLEtBQUt5dEIsa0JBQWtCQyxPQUN6Q0gsRUFBTyxJQUFJL1osV0FBV2dhLEVBQWdCM2YsT0FBUTJmLEVBQWdCcmEsV0FBYXFhLEVBQWdCRyxZQUFhSCxFQUFnQnBhLFdBQWFvYSxFQUFnQkcsYUFDckpRLEVBQWNsdUIsT0FBT3VCLE9BQU91ckIsR0FBMEJsc0IsWUE2ZnhFLFNBQXdDNkIsRUFBU1osRUFBWXlyQixHQUN6RDdxQixFQUFRMHFCLHdDQUEwQ3RyQixFQUNsRFksRUFBUXdxQixNQUFRSyxFQTlmUmEsQ0FBK0JELEVBQWFudUIsS0FBTXV0QixHQUNsRHZ0QixLQUFLa3VCLGFBQWVDLEVBRXhCLE9BQU9udUIsS0FBS2t1QixhQU1oQixrQkFDSSxJQUFLRixHQUErQmh1QixNQUNoQyxNQUFNaXVCLEdBQXdDLGVBRWxELE9BQU9JLEdBQTJDcnVCLE1BTXRELFFBQ0ksSUFBS2d1QixHQUErQmh1QixNQUNoQyxNQUFNaXVCLEdBQXdDLFNBRWxELEdBQUlqdUIsS0FBS3N1QixnQkFDTCxNQUFNLElBQUk5dEIsVUFBVSw4REFFeEIsTUFBTXNELEVBQVE5RCxLQUFLdXVCLDhCQUE4QnBILE9BQ2pELEdBQWMsYUFBVnJqQixFQUNBLE1BQU0sSUFBSXRELFVBQVUsa0JBQWtCc0QsK0RBaVdsRCxTQUEyQ2hDLEdBQ3ZDLE1BQU0wSCxFQUFTMUgsRUFBV3lzQiw4QkFDMUIsSUFBSXpzQixFQUFXd3NCLGlCQUFxQyxhQUFsQjlrQixFQUFPMmQsT0FHekMsR0FBSXJsQixFQUFXNnFCLGdCQUFrQixFQUM3QjdxQixFQUFXd3NCLGlCQUFrQixNQURqQyxDQUlBLEdBQUl4c0IsRUFBVzJyQixrQkFBa0J0a0IsT0FBUyxHQUNUckgsRUFBVzJyQixrQkFBa0JDLE9BQ2pDQyxZQUFjLEVBQUcsQ0FDdEMsTUFBTTdDLEVBQUksSUFBSXRxQixVQUFVLDJEQUV4QixNQURBZ3VCLEdBQWtDMXNCLEVBQVlncEIsR0FDeENBLEVBR2QyRCxHQUE0QzNzQixHQUM1QzRzQixHQUFvQmxsQixJQWpYaEJtbEIsQ0FBa0MzdUIsTUFFdEMsUUFBUThKLEdBQ0osSUFBS2trQixHQUErQmh1QixNQUNoQyxNQUFNaXVCLEdBQXdDLFdBR2xELEdBREFoRixFQUF1Qm5mLEVBQU8sRUFBRyxZQUM1Qm1KLFlBQVlDLE9BQU9wSixHQUNwQixNQUFNLElBQUl0SixVQUFVLHNDQUV4QixHQUF5QixJQUFyQnNKLEVBQU1zSixXQUNOLE1BQU0sSUFBSTVTLFVBQVUsdUNBRXhCLEdBQWdDLElBQTVCc0osRUFBTStELE9BQU91RixXQUNiLE1BQU0sSUFBSTVTLFVBQVUsZ0RBRXhCLEdBQUlSLEtBQUtzdUIsZ0JBQ0wsTUFBTSxJQUFJOXRCLFVBQVUsZ0NBRXhCLE1BQU1zRCxFQUFROUQsS0FBS3V1Qiw4QkFBOEJwSCxPQUNqRCxHQUFjLGFBQVZyakIsRUFDQSxNQUFNLElBQUl0RCxVQUFVLGtCQUFrQnNELG9FQThWbEQsU0FBNkNoQyxFQUFZZ0ksR0FDckQsTUFBTU4sRUFBUzFILEVBQVd5c0IsOEJBQzFCLEdBQUl6c0IsRUFBV3dzQixpQkFBcUMsYUFBbEI5a0IsRUFBTzJkLE9BQ3JDLE9BRUosTUFBTXRaLEVBQVMvRCxFQUFNK0QsT0FDZnNGLEVBQWFySixFQUFNcUosV0FDbkJDLEVBQWF0SixFQUFNc0osV0FDbkJ3YixFQUF3Qy9nQixFQUMxQ3ljLEVBQStCOWdCLEdBQ2tCLElBQTdDNmdCLEVBQWlDN2dCLEdBQ2pDcWxCLEdBQWdEL3NCLEVBQVk4c0IsRUFBbUJ6YixFQUFZQyxHQUkzRjZXLEVBQWlDemdCLEVBRFQsSUFBSWdLLFdBQVdvYixFQUFtQnpiLEVBQVlDLElBQ1osR0FHekQwYixHQUE0QnRsQixJQUVqQ3FsQixHQUFnRC9zQixFQUFZOHNCLEVBQW1CemIsRUFBWUMsR0FDM0YyYixHQUFpRWp0QixJQUdqRStzQixHQUFnRC9zQixFQUFZOHNCLEVBQW1CemIsRUFBWUMsR0FFL0Y0YixHQUE2Q2x0QixHQXRYekNtdEIsQ0FBb0NqdkIsS0FBTThKLEdBSzlDLE1BQU1naEIsR0FDRixJQUFLa0QsR0FBK0JodUIsTUFDaEMsTUFBTWl1QixHQUF3QyxTQUVsRE8sR0FBa0N4dUIsS0FBTThxQixHQUc1QyxDQUFDMUMsR0FBYTFELEdBQ04xa0IsS0FBS3l0QixrQkFBa0J0a0IsT0FBUyxJQUNSbkosS0FBS3l0QixrQkFBa0JDLE9BQy9CQyxZQUFjLEdBRWxDZCxHQUFXN3NCLE1BQ1gsTUFBTStWLEVBQVMvVixLQUFLa3ZCLGlCQUFpQnhLLEdBRXJDLE9BREErSixHQUE0Q3p1QixNQUNyQytWLEVBR1gsQ0FBQ3NTLEdBQVcwQixHQUNSLE1BQU12Z0IsRUFBU3hKLEtBQUt1dUIsOEJBQ3BCLEdBQUl2dUIsS0FBSzJzQixnQkFBa0IsRUFBRyxDQUMxQixNQUFNd0MsRUFBUW52QixLQUFLMHNCLE9BQU94QyxRQUMxQmxxQixLQUFLMnNCLGlCQUFtQndDLEVBQU0vYixXQUM5QmdjLEdBQTZDcHZCLE1BQzdDLE1BQU11dEIsRUFBTyxJQUFJL1osV0FBVzJiLEVBQU10aEIsT0FBUXNoQixFQUFNaGMsV0FBWWdjLEVBQU0vYixZQUVsRSxZQURBMlcsRUFBWUssWUFBWW1ELEdBRzVCLE1BQU04QixFQUF3QnJ2QixLQUFLc3ZCLHVCQUNuQyxRQUE4QnBkLElBQTFCbWQsRUFBcUMsQ0FDckMsSUFBSXhoQixFQUNKLElBQ0lBLEVBQVMsSUFBSW9GLFlBQVlvYyxHQUU3QixNQUFPRSxHQUVILFlBREF4RixFQUFZYyxZQUFZMEUsR0FHNUIsTUFBTUMsRUFBcUIsQ0FDdkIzaEIsU0FDQXNGLFdBQVksRUFDWkMsV0FBWWljLEVBQ1oxQixZQUFhLEVBQ2I4QixZQUFhLEVBQ2JDLGdCQUFpQmxjLFdBQ2pCbWMsV0FBWSxXQUVoQjN2QixLQUFLeXRCLGtCQUFrQjFqQixLQUFLeWxCLEdBRWhDMUYsRUFBNkJ0Z0IsRUFBUXVnQixHQUNyQ2lGLEdBQTZDaHZCLE9BaUJyRCxTQUFTZ3VCLEdBQStCbmQsR0FDcEMsUUFBS21ULEVBQWFuVCxNQUdiNVEsT0FBT1ksVUFBVWtxQixlQUFldHBCLEtBQUtvUCxFQUFHLGlDQUtqRCxTQUFTbWMsR0FBNEJuYyxHQUNqQyxRQUFLbVQsRUFBYW5ULE1BR2I1USxPQUFPWSxVQUFVa3FCLGVBQWV0cEIsS0FBS29QLEVBQUcsMkNBS2pELFNBQVNtZSxHQUE2Q2x0QixJQWtOdEQsU0FBb0RBLEdBQ2hELE1BQU0wSCxFQUFTMUgsRUFBV3lzQiw4QkFDMUIsTUFBc0IsYUFBbEIva0IsRUFBTzJkLFVBR1BybEIsRUFBV3dzQixvQkFHVnhzQixFQUFXOHRCLGNBR1p0RixFQUErQjlnQixJQUFXNmdCLEVBQWlDN2dCLEdBQVUsUUFHckZzbEIsR0FBNEJ0bEIsSUFBV3FtQixHQUFxQ3JtQixHQUFVLElBR3RFNmtCLEdBQTJDdnNCLEdBQzdDLE9Bbk9DZ3VCLENBQTJDaHVCLEtBSTFEQSxFQUFXaXVCLFNBQ1hqdUIsRUFBV2t1QixZQUFhLEdBRzVCbHVCLEVBQVdpdUIsVUFBVyxFQUd0QmhMLEVBRG9CampCLEVBQVdtdUIsa0JBQ04sS0FDckJudUIsRUFBV2l1QixVQUFXLEVBQ2xCanVCLEVBQVdrdUIsYUFDWGx1QixFQUFXa3VCLFlBQWEsRUFDeEJoQixHQUE2Q2x0QixPQUVsRGdwQixJQUNDMEQsR0FBa0Mxc0IsRUFBWWdwQixRQU90RCxTQUFTb0YsR0FBcUQxbUIsRUFBUWdtQixHQUNsRSxJQUFJL1UsR0FBTyxFQUNXLFdBQWxCalIsRUFBTzJkLFNBQ1AxTSxHQUFPLEdBRVgsTUFBTTBWLEVBQWFDLEdBQXNEWixHQUNuQyxZQUFsQ0EsRUFBbUJHLFdBQ25CMUYsRUFBaUN6Z0IsRUFBUTJtQixFQUFZMVYsR0FpVzdELFNBQThDalIsRUFBUU0sRUFBTzJRLEdBQ3pELE1BQ000VixFQURTN21CLEVBQU8wZCxRQUNTb0osa0JBQWtCcEcsUUFDN0N6UCxFQUNBNFYsRUFBZ0JsRyxZQUFZcmdCLEdBRzVCdW1CLEVBQWdCakcsWUFBWXRnQixHQXJXNUJ5bUIsQ0FBcUMvbUIsRUFBUTJtQixFQUFZMVYsR0FHakUsU0FBUzJWLEdBQXNEWixHQUMzRCxNQUFNN0IsRUFBYzZCLEVBQW1CN0IsWUFDakM4QixFQUFjRCxFQUFtQkMsWUFDdkMsT0FBTyxJQUFJRCxFQUFtQkUsZ0JBQWdCRixFQUFtQjNoQixPQUFRMmhCLEVBQW1CcmMsV0FBWXdhLEVBQWM4QixHQUUxSCxTQUFTWixHQUFnRC9zQixFQUFZK0wsRUFBUXNGLEVBQVlDLEdBQ3JGdFIsRUFBVzRxQixPQUFPM2lCLEtBQUssQ0FBRThELFNBQVFzRixhQUFZQyxlQUM3Q3RSLEVBQVc2cUIsaUJBQW1CdlosRUFFbEMsU0FBU29kLEdBQTREMXVCLEVBQVkwdEIsR0FDN0UsTUFBTUMsRUFBY0QsRUFBbUJDLFlBQ2pDZ0IsRUFBc0JqQixFQUFtQjdCLFlBQWM2QixFQUFtQjdCLFlBQWM4QixFQUN4RmlCLEVBQWlCNWMsS0FBS0UsSUFBSWxTLEVBQVc2cUIsZ0JBQWlCNkMsRUFBbUJwYyxXQUFhb2MsRUFBbUI3QixhQUN6R2dELEVBQWlCbkIsRUFBbUI3QixZQUFjK0MsRUFDbERFLEVBQWtCRCxFQUFpQkEsRUFBaUJsQixFQUMxRCxJQUFJb0IsRUFBNEJILEVBQzVCSSxHQUFRLEVBQ1JGLEVBQWtCSCxJQUNsQkksRUFBNEJELEVBQWtCcEIsRUFBbUI3QixZQUNqRW1ELEdBQVEsR0FFWixNQUFNQyxFQUFRanZCLEVBQVc0cUIsT0FDekIsS0FBT21FLEVBQTRCLEdBQUcsQ0FDbEMsTUFBTUcsRUFBY0QsRUFBTXJELE9BQ3BCdUQsRUFBY25kLEtBQUtFLElBQUk2YyxFQUEyQkcsRUFBWTVkLFlBQzlEOGQsRUFBWTFCLEVBQW1CcmMsV0FBYXFjLEVBQW1CN0IsWUE1U2pEbkssRUE2U0RnTSxFQUFtQjNoQixPQTdTWnNqQixFQTZTb0JELEVBN1NSRSxFQTZTbUJKLEVBQVluakIsT0E3UzFCd2pCLEVBNlNrQ0wsRUFBWTdkLFdBN1NuQ21lLEVBNlMrQ0wsRUE1U3pHLElBQUl6ZCxXQUFXZ1EsR0FBTWxpQixJQUFJLElBQUlrUyxXQUFXNGQsRUFBS0MsRUFBV0MsR0FBSUgsR0E2U3BESCxFQUFZNWQsYUFBZTZkLEVBQzNCRixFQUFNN0csU0FHTjhHLEVBQVk3ZCxZQUFjOGQsRUFDMUJELEVBQVk1ZCxZQUFjNmQsR0FFOUJudkIsRUFBVzZxQixpQkFBbUJzRSxFQUM5Qk0sR0FBdUR6dkIsRUFBWW12QixFQUFhekIsR0FDaEZxQixHQUE2QkksRUF2VHJDLElBQTRCek4sRUFBTTJOLEVBQVlDLEVBQUtDLEVBQVdDLEVBeVQxRCxPQUFPUixFQUVYLFNBQVNTLEdBQXVEenZCLEVBQVlnUixFQUFNMGMsR0FDOUVnQyxHQUFrRDF2QixHQUNsRDB0QixFQUFtQjdCLGFBQWU3YSxFQUV0QyxTQUFTc2MsR0FBNkN0dEIsR0FDZixJQUEvQkEsRUFBVzZxQixpQkFBeUI3cUIsRUFBV3dzQixpQkFDL0NHLEdBQTRDM3NCLEdBQzVDNHNCLEdBQW9CNXNCLEVBQVd5c0IsZ0NBRy9CUyxHQUE2Q2x0QixHQUdyRCxTQUFTMHZCLEdBQWtEMXZCLEdBQ3ZCLE9BQTVCQSxFQUFXb3NCLGVBR2Zwc0IsRUFBV29zQixhQUFhZCw2Q0FBMENsYixFQUNsRXBRLEVBQVdvc0IsYUFBYWhCLE1BQVEsS0FDaENwckIsRUFBV29zQixhQUFlLE1BRTlCLFNBQVNhLEdBQWlFanRCLEdBQ3RFLEtBQU9BLEVBQVcyckIsa0JBQWtCdGtCLE9BQVMsR0FBRyxDQUM1QyxHQUFtQyxJQUEvQnJILEVBQVc2cUIsZ0JBQ1gsT0FFSixNQUFNNkMsRUFBcUIxdEIsRUFBVzJyQixrQkFBa0JDLE9BQ3BEOEMsR0FBNEQxdUIsRUFBWTB0QixLQUN4RWlDLEdBQWlEM3ZCLEdBQ2pEb3VCLEdBQXFEcHVCLEVBQVd5c0IsOEJBQStCaUIsS0FtRjNHLFNBQVNuQyxHQUE0Q3ZyQixFQUFZcXJCLEdBQzdELE1BQU1LLEVBQWtCMXJCLEVBQVcyckIsa0JBQWtCQyxPQUVyRCxHQUFjLFdBREE1ckIsRUFBV3lzQiw4QkFBOEJwSCxPQUMvQixDQUNwQixHQUFxQixJQUFqQmdHLEVBQ0EsTUFBTSxJQUFJM3NCLFVBQVUscUVBcENoQyxTQUEwRHNCLEVBQVkwckIsR0FDbEVBLEVBQWdCM2YsT0FBNkIyZixFQUFnQjNmLE9BQzdELE1BQU1yRSxFQUFTMUgsRUFBV3lzQiw4QkFDMUIsR0FBSU8sR0FBNEJ0bEIsR0FDNUIsS0FBT3FtQixHQUFxQ3JtQixHQUFVLEdBRWxEMG1CLEdBQXFEMW1CLEVBRDFCaW9CLEdBQWlEM3ZCLElBaUNoRjR2QixDQUFpRDV2QixFQUFZMHJCLFFBNUJyRSxTQUE0RDFyQixFQUFZcXJCLEVBQWNxQyxHQUNsRixHQUFJQSxFQUFtQjdCLFlBQWNSLEVBQWVxQyxFQUFtQnBjLFdBQ25FLE1BQU0sSUFBSTJGLFdBQVcsNkJBR3pCLEdBREF3WSxHQUF1RHp2QixFQUFZcXJCLEVBQWNxQyxHQUM3RUEsRUFBbUI3QixZQUFjNkIsRUFBbUJDLFlBRXBELE9BRUpnQyxHQUFpRDN2QixHQUNqRCxNQUFNNnZCLEVBQWdCbkMsRUFBbUI3QixZQUFjNkIsRUFBbUJDLFlBQzFFLEdBQUlrQyxFQUFnQixFQUFHLENBQ25CLE1BQU03bUIsRUFBTTBrQixFQUFtQnJjLFdBQWFxYyxFQUFtQjdCLFlBQ3pEaUUsRUFBWXBDLEVBQW1CM2hCLE9BQU93RyxNQUFNdkosRUFBTTZtQixFQUFlN21CLEdBQ3ZFK2pCLEdBQWdEL3NCLEVBQVk4dkIsRUFBVyxFQUFHQSxFQUFVeGUsWUFFeEZvYyxFQUFtQjNoQixPQUE2QjJoQixFQUFtQjNoQixPQUNuRTJoQixFQUFtQjdCLGFBQWVnRSxFQUNsQ3pCLEdBQXFEcHVCLEVBQVd5c0IsOEJBQStCaUIsR0FDL0ZULEdBQWlFanRCLEdBWTdEK3ZCLENBQW1EL3ZCLEVBQVlxckIsRUFBY0ssR0FFakZ3QixHQUE2Q2x0QixHQUVqRCxTQUFTMnZCLEdBQWlEM3ZCLEdBQ3RELE1BQU1nd0IsRUFBYWh3QixFQUFXMnJCLGtCQUFrQnZELFFBRWhELE9BREFzSCxHQUFrRDF2QixHQUMzQ2d3QixFQXlCWCxTQUFTckQsR0FBNEMzc0IsR0FDakRBLEVBQVdtdUIsb0JBQWlCL2QsRUFDNUJwUSxFQUFXb3RCLHNCQUFtQmhkLEVBbURsQyxTQUFTc2MsR0FBa0Mxc0IsRUFBWWdwQixHQUNuRCxNQUFNdGhCLEVBQVMxSCxFQUFXeXNCLDhCQUNKLGFBQWxCL2tCLEVBQU8yZCxTQTFRZixTQUEyRHJsQixHQUN2RDB2QixHQUFrRDF2QixHQUNsREEsRUFBVzJyQixrQkFBb0IsSUFBSTFILEVBMlFuQ2dNLENBQWtEandCLEdBQ2xEK3FCLEdBQVcvcUIsR0FDWDJzQixHQUE0QzNzQixHQUM1Q2t3QixHQUFvQnhvQixFQUFRc2hCLElBRWhDLFNBQVN1RCxHQUEyQ3ZzQixHQUNoRCxNQUFNZ0MsRUFBUWhDLEVBQVd5c0IsOEJBQThCcEgsT0FDdkQsTUFBYyxZQUFWcmpCLEVBQ08sS0FFRyxXQUFWQSxFQUNPLEVBRUpoQyxFQUFXbXdCLGFBQWVud0IsRUFBVzZxQixnQkFrRWhELFNBQVNNLEdBQStCdHBCLEdBQ3BDLE9BQU8sSUFBSW5ELFVBQVUsdUNBQXVDbUQscURBR2hFLFNBQVNzcUIsR0FBd0N0cUIsR0FDN0MsT0FBTyxJQUFJbkQsVUFBVSwwQ0FBMENtRCx3REFRbkUsU0FBU3V1QixHQUFpQzFvQixFQUFRNm1CLEdBQzlDN21CLEVBQU8wZCxRQUFRb0osa0JBQWtCdm1CLEtBQUtzbUIsR0FZMUMsU0FBU1IsR0FBcUNybUIsR0FDMUMsT0FBT0EsRUFBTzBkLFFBQVFvSixrQkFBa0JubkIsT0FFNUMsU0FBUzJsQixHQUE0QnRsQixHQUNqQyxNQUFNOFEsRUFBUzlRLEVBQU8wZCxRQUN0QixZQUFlaFYsSUFBWG9JLEtBR0M2WCxHQUEyQjdYLEdBcGJwQ3JhLE9BQU9jLGlCQUFpQmd0QixHQUE2Qmx0QixVQUFXLENBQzVENlosTUFBTyxDQUFFMVosWUFBWSxHQUNyQjJaLFFBQVMsQ0FBRTNaLFlBQVksR0FDdkJ1RixNQUFPLENBQUV2RixZQUFZLEdBQ3JCbXRCLFlBQWEsQ0FBRW50QixZQUFZLEdBQzNCb3hCLFlBQWEsQ0FBRXB4QixZQUFZLEtBRVcsaUJBQS9CNmlCLEVBQWUzaUIsYUFDdEJqQixPQUFPQyxlQUFlNnRCLEdBQTZCbHRCLFVBQVdnakIsRUFBZTNpQixZQUFhLENBQ3RGZixNQUFPLCtCQUNQZ0IsY0FBYyxJQW9idEIsTUFBTWt4QixHQUNGLFlBQVk3b0IsR0FHUixHQUZBeWYsRUFBdUJ6ZixFQUFRLEVBQUcsNEJBQ2xDa2dCLEVBQXFCbGdCLEVBQVEsbUJBQ3pCZ2hCLEdBQXVCaGhCLEdBQ3ZCLE1BQU0sSUFBSWhKLFVBQVUsK0VBRXhCLElBQUt3dEIsR0FBK0J4a0IsRUFBT3loQiwyQkFDdkMsTUFBTSxJQUFJenFCLFVBQVUsK0ZBR3hCd21CLEVBQXNDaG5CLEtBQU13SixHQUM1Q3hKLEtBQUtzd0Isa0JBQW9CLElBQUl2SyxFQU1qQyxhQUNJLE9BQUtvTSxHQUEyQm55QixNQUd6QkEsS0FBSytuQixlQUZEdEQsRUFBb0I2TixHQUE4QixXQU9qRSxPQUFPNU4sR0FDSCxPQUFLeU4sR0FBMkJueUIsV0FHRWtTLElBQTlCbFMsS0FBS2luQixxQkFDRXhDLEVBQW9CcUQsRUFBb0IsV0FFNUNMLEVBQWtDem5CLEtBQU0wa0IsR0FMcENELEVBQW9CNk4sR0FBOEIsV0FZakUsS0FBSy9FLEdBQ0QsSUFBSzRFLEdBQTJCbnlCLE1BQzVCLE9BQU95a0IsRUFBb0I2TixHQUE4QixTQUU3RCxJQUFLcmYsWUFBWUMsT0FBT3FhLEdBQ3BCLE9BQU85SSxFQUFvQixJQUFJamtCLFVBQVUsc0NBRTdDLEdBQXdCLElBQXBCK3NCLEVBQUtuYSxXQUNMLE9BQU9xUixFQUFvQixJQUFJamtCLFVBQVUsdUNBRTdDLEdBQStCLElBQTNCK3NCLEVBQUsxZixPQUFPdUYsV0FDWixPQUFPcVIsRUFBb0IsSUFBSWprQixVQUFVLGdEQUU3QyxRQUFrQzBSLElBQTlCbFMsS0FBS2luQixxQkFDTCxPQUFPeEMsRUFBb0JxRCxFQUFvQixjQUVuRCxJQUFJNEMsRUFDQUMsRUFDSixNQUFNL0YsRUFBVU4sR0FBVyxDQUFDM2EsRUFBU0MsS0FDakM4Z0IsRUFBaUIvZ0IsRUFDakJnaEIsRUFBZ0IvZ0IsS0FRcEIsT0E4Q1IsU0FBc0MwUSxFQUFRaVQsRUFBTThDLEdBQ2hELE1BQU03bUIsRUFBUzhRLEVBQU8yTSxxQkFDdEJ6ZCxFQUFPd2hCLFlBQWEsRUFDRSxZQUFsQnhoQixFQUFPMmQsT0FDUGtKLEVBQWdCeEYsWUFBWXJoQixFQUFPZ2UsY0FyYTNDLFNBQThDMWxCLEVBQVl5ckIsRUFBTThDLEdBQzVELE1BQU03bUIsRUFBUzFILEVBQVd5c0IsOEJBQzFCLElBQUlrQixFQUFjLEVBQ2RsQyxFQUFLNWQsY0FBZ0I0aUIsV0FDckI5QyxFQUFjbEMsRUFBSzVkLFlBQVk2aUIsbUJBRW5DLE1BQU1DLEVBQU9sRixFQUFLNWQsWUFFWjZmLEVBQXFCLENBQ3ZCM2hCLE9BRitCMGYsRUFBSzFmLE9BR3BDc0YsV0FBWW9hLEVBQUtwYSxXQUNqQkMsV0FBWW1hLEVBQUtuYSxXQUNqQnVhLFlBQWEsRUFDYjhCLGNBQ0FDLGdCQUFpQitDLEVBQ2pCOUMsV0FBWSxRQUVoQixHQUFJN3RCLEVBQVcyckIsa0JBQWtCdGtCLE9BQVMsRUFNdEMsT0FMQXJILEVBQVcyckIsa0JBQWtCMWpCLEtBQUt5bEIsUUFJbEMwQyxHQUFpQzFvQixFQUFRNm1CLEdBRzdDLEdBQXNCLFdBQWxCN21CLEVBQU8yZCxPQUFYLENBS0EsR0FBSXJsQixFQUFXNnFCLGdCQUFrQixFQUFHLENBQ2hDLEdBQUk2RCxHQUE0RDF1QixFQUFZMHRCLEdBQXFCLENBQzdGLE1BQU1XLEVBQWFDLEdBQXNEWixHQUd6RSxPQUZBSixHQUE2Q3R0QixRQUM3Q3V1QixFQUFnQmpHLFlBQVkrRixHQUdoQyxHQUFJcnVCLEVBQVd3c0IsZ0JBQWlCLENBQzVCLE1BQU14RCxFQUFJLElBQUl0cUIsVUFBVSwyREFHeEIsT0FGQWd1QixHQUFrQzFzQixFQUFZZ3BCLFFBQzlDdUYsRUFBZ0J4RixZQUFZQyxJQUlwQ2hwQixFQUFXMnJCLGtCQUFrQjFqQixLQUFLeWxCLEdBQ2xDMEMsR0FBaUMxb0IsRUFBUTZtQixHQUN6Q3JCLEdBQTZDbHRCLE9BckI3QyxDQUNJLE1BQU00d0IsRUFBWSxJQUFJRCxFQUFLakQsRUFBbUIzaEIsT0FBUTJoQixFQUFtQnJjLFdBQVksR0FDckZrZCxFQUFnQmxHLFlBQVl1SSxJQTZZNUJDLENBQXFDbnBCLEVBQU95aEIsMEJBQTJCc0MsRUFBTThDLEdBdEQ3RXVDLENBQTZCNXlCLEtBQU11dEIsRUFMWCxDQUNwQm5ELFlBQWF0Z0IsR0FBUzRnQixFQUFlLENBQUV2cUIsTUFBTzJKLEVBQU8yUSxNQUFNLElBQzNEMFAsWUFBYXJnQixHQUFTNGdCLEVBQWUsQ0FBRXZxQixNQUFPMkosRUFBTzJRLE1BQU0sSUFDM0RvUSxZQUFhQyxHQUFLSCxFQUFjRyxLQUc3QmxHLEVBV1gsY0FDSSxJQUFLdU4sR0FBMkJueUIsTUFDNUIsTUFBTXN5QixHQUE4QixlQUV4QyxRQUFrQ3BnQixJQUE5QmxTLEtBQUtpbkIscUJBQVQsQ0FHQSxHQUFJam5CLEtBQUtzd0Isa0JBQWtCbm5CLE9BQVMsRUFDaEMsTUFBTSxJQUFJM0ksVUFBVSx1RkFFeEJtbkIsRUFBbUMzbkIsUUFnQjNDLFNBQVNteUIsR0FBMkJ0aEIsR0FDaEMsUUFBS21ULEVBQWFuVCxNQUdiNVEsT0FBT1ksVUFBVWtxQixlQUFldHBCLEtBQUtvUCxFQUFHLHFCQWdCakQsU0FBU3loQixHQUE4QjN1QixHQUNuQyxPQUFPLElBQUluRCxVQUFVLHNDQUFzQ21ELG9EQUcvRCxTQUFTa3ZCLEdBQXFCQyxFQUFVQyxHQUNwQyxNQUFNLGNBQUVuZSxHQUFrQmtlLEVBQzFCLFFBQXNCNWdCLElBQWxCMEMsRUFDQSxPQUFPbWUsRUFFWCxHQUFJM0csR0FBWXhYLElBQWtCQSxFQUFnQixFQUM5QyxNQUFNLElBQUltRSxXQUFXLHlCQUV6QixPQUFPbkUsRUFFWCxTQUFTb2UsR0FBcUJGLEdBQzFCLE1BQU0sS0FBRWhnQixHQUFTZ2dCLEVBQ2pCLE9BQUtoZ0IsR0FDTSxLQUFNLEdBS3JCLFNBQVNtZ0IsR0FBdUJyVSxFQUFNa0ssR0FDbENELEVBQWlCakssRUFBTWtLLEdBQ3ZCLE1BQU1sVSxFQUFnQmdLLGFBQW1DLEVBQVNBLEVBQUtoSyxjQUNqRTlCLEVBQU84TCxhQUFtQyxFQUFTQSxFQUFLOUwsS0FDOUQsTUFBTyxDQUNIOEIsbUJBQWlDMUMsSUFBbEIwQyxPQUE4QjFDLEVBQVlrWCxFQUEwQnhVLEdBQ25GOUIsVUFBZVosSUFBVFksT0FBcUJaLEVBQVlnaEIsR0FBMkJwZ0IsRUFBTSxHQUFHZ1csNkJBR25GLFNBQVNvSyxHQUEyQnBhLEVBQUlnUSxHQUVwQyxPQURBQyxFQUFlalEsRUFBSWdRLEdBQ1poZixHQUFTc2YsRUFBMEJ0USxFQUFHaFAsSUEwQmpELFNBQVNxcEIsR0FBbUNyYSxFQUFJc2EsRUFBVXRLLEdBRXRELE9BREFDLEVBQWVqUSxFQUFJZ1EsR0FDWHBFLEdBQVdvQixFQUFZaE4sRUFBSXNhLEVBQVUsQ0FBQzFPLElBRWxELFNBQVMyTyxHQUFtQ3ZhLEVBQUlzYSxFQUFVdEssR0FFdEQsT0FEQUMsRUFBZWpRLEVBQUlnUSxHQUNaLElBQU1oRCxFQUFZaE4sRUFBSXNhLEVBQVUsSUFFM0MsU0FBU0UsR0FBbUN4YSxFQUFJc2EsRUFBVXRLLEdBRXRELE9BREFDLEVBQWVqUSxFQUFJZ1EsR0FDWGhuQixHQUFlMmpCLEVBQVkzTSxFQUFJc2EsRUFBVSxDQUFDdHhCLElBRXRELFNBQVN5eEIsR0FBbUN6YSxFQUFJc2EsRUFBVXRLLEdBRXRELE9BREFDLEVBQWVqUSxFQUFJZ1EsR0FDWixDQUFDaGYsRUFBT2hJLElBQWVna0IsRUFBWWhOLEVBQUlzYSxFQUFVLENBQUN0cEIsRUFBT2hJLElBR3BFLFNBQVMweEIsR0FBcUIzaUIsRUFBR2lZLEdBQzdCLElBQUsySyxHQUFpQjVpQixHQUNsQixNQUFNLElBQUlyUSxVQUFVLEdBQUdzb0IsOEJBL0cvQjdvQixPQUFPYyxpQkFBaUJzeEIsR0FBeUJ4eEIsVUFBVyxDQUN4RHFxQixPQUFRLENBQUVscUIsWUFBWSxHQUN0QjRTLEtBQU0sQ0FBRTVTLFlBQVksR0FDcEJtcUIsWUFBYSxDQUFFbnFCLFlBQVksR0FDM0JvcUIsT0FBUSxDQUFFcHFCLFlBQVksS0FFZ0IsaUJBQS9CNmlCLEVBQWUzaUIsYUFDdEJqQixPQUFPQyxlQUFlbXlCLEdBQXlCeHhCLFVBQVdnakIsRUFBZTNpQixZQUFhLENBQ2xGZixNQUFPLDJCQUNQZ0IsY0FBYyxJQStHdEIsTUFBTXV5QixHQUNGLFlBQVlDLEVBQW9CLEdBQUlDLEVBQWMsU0FDcEIxaEIsSUFBdEJ5aEIsRUFDQUEsRUFBb0IsS0FHcEIzSyxFQUFhMkssRUFBbUIsbUJBRXBDLE1BQU1iLEVBQVdHLEdBQXVCVyxFQUFhLG9CQUMvQ0MsRUE1RGQsU0FBK0JULEVBQVV0SyxHQUNyQ0QsRUFBaUJ1SyxFQUFVdEssR0FDM0IsTUFBTS9tQixFQUFRcXhCLGFBQTJDLEVBQVNBLEVBQVNyeEIsTUFDckUyWSxFQUFRMFksYUFBMkMsRUFBU0EsRUFBUzFZLE1BQ3JFOVAsRUFBUXdvQixhQUEyQyxFQUFTQSxFQUFTeG9CLE1BQ3JFL0ksRUFBT3V4QixhQUEyQyxFQUFTQSxFQUFTdnhCLEtBQ3BFNGhCLEVBQVEyUCxhQUEyQyxFQUFTQSxFQUFTM1AsTUFDM0UsTUFBTyxDQUNIMWhCLFdBQWlCbVEsSUFBVm5RLE9BQ0htUSxFQUNBaWhCLEdBQW1DcHhCLEVBQU9xeEIsRUFBVSxHQUFHdEssNkJBQzNEcE8sV0FBaUJ4SSxJQUFWd0ksT0FDSHhJLEVBQ0FtaEIsR0FBbUMzWSxFQUFPMFksRUFBVSxHQUFHdEssNkJBQzNEbGUsV0FBaUJzSCxJQUFWdEgsT0FDSHNILEVBQ0FvaEIsR0FBbUMxb0IsRUFBT3dvQixFQUFVLEdBQUd0Syw2QkFDM0RyRixXQUFpQnZSLElBQVZ1UixPQUNIdlIsRUFDQXFoQixHQUFtQzlQLEVBQU8yUCxFQUFVLEdBQUd0Syw2QkFDM0RqbkIsUUF3Q3VCaXlCLENBQXNCSCxFQUFtQixtQkFHaEUsR0FGQUksR0FBeUIvekIsV0FFWmtTLElBREEyaEIsRUFBZWh5QixLQUV4QixNQUFNLElBQUlrWCxXQUFXLDZCQUV6QixNQUFNaWIsRUFBZ0JoQixHQUFxQkYsSUFpb0JuRCxTQUFnRXRwQixFQUFRcXFCLEVBQWdCamYsRUFBZW9mLEdBQ25HLE1BQU1seUIsRUFBYTdCLE9BQU91QixPQUFPeXlCLEdBQWdDcHpCLFdBQ2pFLElBQUlxekIsRUFBaUIsT0FDakJDLEVBQWlCLElBQU0zUCxPQUFvQnRTLEdBQzNDa2lCLEVBQWlCLElBQU01UCxPQUFvQnRTLEdBQzNDbWlCLEVBQWlCLElBQU03UCxPQUFvQnRTLFFBQ2xCQSxJQUF6QjJoQixFQUFlanBCLFFBQ2ZzcEIsRUFBaUIsSUFBTUwsRUFBZWpwQixNQUFNOUksU0FFbkJvUSxJQUF6QjJoQixFQUFlcFEsUUFDZjBRLEVBQWlCcnFCLEdBQVMrcEIsRUFBZXBRLE1BQU0zWixFQUFPaEksU0FFN0JvUSxJQUF6QjJoQixFQUFlblosUUFDZjBaLEVBQWlCLElBQU1QLEVBQWVuWixjQUVieEksSUFBekIyaEIsRUFBZTl4QixRQUNmc3lCLEVBQWlCM1AsR0FBVW1QLEVBQWU5eEIsTUFBTTJpQixJQUVwRDRQLEdBQXFDOXFCLEVBQVExSCxFQUFZb3lCLEVBQWdCQyxFQUFnQkMsRUFBZ0JDLEVBQWdCemYsRUFBZW9mLEdBanBCcElPLENBQXVEdjBCLEtBQU02ekIsRUFEdkNoQixHQUFxQkMsRUFBVSxHQUN1Q2tCLEdBS2hHLGFBQ0ksSUFBS1AsR0FBaUJ6ekIsTUFDbEIsTUFBTXcwQixHQUEwQixVQUVwQyxPQUFPQyxHQUF1QnowQixNQVdsQyxNQUFNMGtCLEdBQ0YsT0FBSytPLEdBQWlCenpCLE1BR2xCeTBCLEdBQXVCejBCLE1BQ2hCeWtCLEVBQW9CLElBQUlqa0IsVUFBVSxvREFFdENrMEIsR0FBb0IxMEIsS0FBTTBrQixHQUx0QkQsRUFBb0IrUCxHQUEwQixVQWU3RCxRQUNJLE9BQUtmLEdBQWlCenpCLE1BR2xCeTBCLEdBQXVCejBCLE1BQ2hCeWtCLEVBQW9CLElBQUlqa0IsVUFBVSxvREFFekNtMEIsR0FBb0MzMEIsTUFDN0J5a0IsRUFBb0IsSUFBSWprQixVQUFVLDJDQUV0Q28wQixHQUFvQjUwQixNQVJoQnlrQixFQUFvQitQLEdBQTBCLFVBa0I3RCxZQUNJLElBQUtmLEdBQWlCenpCLE1BQ2xCLE1BQU13MEIsR0FBMEIsYUFFcEMsT0FBT0ssR0FBbUM3MEIsT0FnQmxELFNBQVM2MEIsR0FBbUNyckIsR0FDeEMsT0FBTyxJQUFJc3JCLEdBQTRCdHJCLEdBVTNDLFNBQVN1cUIsR0FBeUJ2cUIsR0FDOUJBLEVBQU8yZCxPQUFTLFdBR2hCM2QsRUFBT2dlLGtCQUFldFYsRUFDdEIxSSxFQUFPdXJCLGFBQVU3aUIsRUFHakIxSSxFQUFPd3JCLCtCQUE0QjlpQixFQUduQzFJLEVBQU95ckIsZUFBaUIsSUFBSWxQLEVBRzVCdmMsRUFBTzByQiwyQkFBd0JoakIsRUFHL0IxSSxFQUFPMnJCLG1CQUFnQmpqQixFQUd2QjFJLEVBQU80ckIsMkJBQXdCbGpCLEVBRS9CMUksRUFBTzZyQiwwQkFBdUJuakIsRUFFOUIxSSxFQUFPOHJCLGVBQWdCLEVBRTNCLFNBQVM3QixHQUFpQjVpQixHQUN0QixRQUFLbVQsRUFBYW5ULE1BR2I1USxPQUFPWSxVQUFVa3FCLGVBQWV0cEIsS0FBS29QLEVBQUcsNkJBS2pELFNBQVM0akIsR0FBdUJqckIsR0FDNUIsWUFBdUIwSSxJQUFuQjFJLEVBQU91ckIsUUFLZixTQUFTTCxHQUFvQmxyQixFQUFRa2IsR0FDakMsTUFBTTVnQixFQUFRMEYsRUFBTzJkLE9BQ3JCLEdBQWMsV0FBVnJqQixHQUFnQyxZQUFWQSxFQUN0QixPQUFPMGdCLE9BQW9CdFMsR0FFL0IsUUFBb0NBLElBQWhDMUksRUFBTzZyQixxQkFDUCxPQUFPN3JCLEVBQU82ckIscUJBQXFCRSxTQUV2QyxJQUFJQyxHQUFxQixFQUNYLGFBQVYxeEIsSUFDQTB4QixHQUFxQixFQUVyQjlRLE9BQVN4UyxHQUViLE1BQU0wUyxFQUFVTixHQUFXLENBQUMzYSxFQUFTQyxLQUNqQ0osRUFBTzZyQixxQkFBdUIsQ0FDMUJFLGNBQVVyakIsRUFDVnVqQixTQUFVOXJCLEVBQ1YrckIsUUFBUzlyQixFQUNUK3JCLFFBQVNqUixFQUNUa1Isb0JBQXFCSixNQU83QixPQUpBaHNCLEVBQU82ckIscUJBQXFCRSxTQUFXM1EsRUFDbEM0USxHQUNESyxHQUE0QnJzQixFQUFRa2IsR0FFakNFLEVBRVgsU0FBU2dRLEdBQW9CcHJCLEdBQ3pCLE1BQU0xRixFQUFRMEYsRUFBTzJkLE9BQ3JCLEdBQWMsV0FBVnJqQixHQUFnQyxZQUFWQSxFQUN0QixPQUFPMmdCLEVBQW9CLElBQUlqa0IsVUFBVSxrQkFBa0JzRCwrREFFL0QsTUFBTThnQixFQUFVTixHQUFXLENBQUMzYSxFQUFTQyxLQUNqQyxNQUFNa3NCLEVBQWUsQ0FDakJMLFNBQVU5ckIsRUFDVityQixRQUFTOXJCLEdBRWJKLEVBQU8yckIsY0FBZ0JXLEtBRXJCQyxFQUFTdnNCLEVBQU91ckIsUUFnZjFCLElBQThDanpCLEVBM2UxQyxZQUplb1EsSUFBWDZqQixHQUF3QnZzQixFQUFPOHJCLGVBQTJCLGFBQVZ4eEIsR0FDaERreUIsR0FBaUNELEdBK2VyQ25KLEdBRDBDOXFCLEVBNWVMMEgsRUFBT3dyQiwwQkE2ZVhpQixHQUFlLEdBQ2hEQyxHQUFvRHAwQixHQTdlN0M4aUIsRUFhWCxTQUFTdVIsR0FBZ0Mzc0IsRUFBUWpELEdBRS9CLGFBREFpRCxFQUFPMmQsT0FLckJpUCxHQUE2QjVzQixHQUh6QnFzQixHQUE0QnJzQixFQUFRakQsR0FLNUMsU0FBU3N2QixHQUE0QnJzQixFQUFRa2IsR0FDekMsTUFBTTVpQixFQUFhMEgsRUFBT3dyQiwwQkFDMUJ4ckIsRUFBTzJkLE9BQVMsV0FDaEIzZCxFQUFPZ2UsYUFBZTlDLEVBQ3RCLE1BQU1xUixFQUFTdnNCLEVBQU91ckIsYUFDUDdpQixJQUFYNmpCLEdBQ0FNLEdBQXNETixFQUFRclIsSUE4RXRFLFNBQWtEbGIsR0FDOUMsWUFBcUMwSSxJQUFqQzFJLEVBQU8wckIsNEJBQXdFaGpCLElBQWpDMUksRUFBTzRyQixzQkE3RXBEa0IsQ0FBeUM5c0IsSUFBVzFILEVBQVc4dEIsVUFDaEV3RyxHQUE2QjVzQixHQUdyQyxTQUFTNHNCLEdBQTZCNXNCLEdBQ2xDQSxFQUFPMmQsT0FBUyxVQUNoQjNkLEVBQU93ckIsMEJBQTBCN00sS0FDakMsTUFBTW9PLEVBQWMvc0IsRUFBT2dlLGFBSzNCLEdBSkFoZSxFQUFPeXJCLGVBQWV6cUIsU0FBUWdzQixJQUMxQkEsRUFBYWQsUUFBUWEsTUFFekIvc0IsRUFBT3lyQixlQUFpQixJQUFJbFAsT0FDUTdULElBQWhDMUksRUFBTzZyQixxQkFFUCxZQURBb0IsR0FBa0RqdEIsR0FHdEQsTUFBTWt0QixFQUFlbHRCLEVBQU82ckIscUJBRTVCLEdBREE3ckIsRUFBTzZyQiwwQkFBdUJuakIsRUFDMUJ3a0IsRUFBYWQsb0JBR2IsT0FGQWMsRUFBYWhCLFFBQVFhLFFBQ3JCRSxHQUFrRGp0QixHQUl0RHViLEVBRGdCdmIsRUFBT3dyQiwwQkFBMEI5TSxHQUFZd08sRUFBYWYsVUFDckQsS0FDakJlLEVBQWFqQixXQUNiZ0IsR0FBa0RqdEIsTUFDbERrYixJQUNBZ1MsRUFBYWhCLFFBQVFoUixHQUNyQitSLEdBQWtEanRCLE1BeUMxRCxTQUFTbXJCLEdBQW9DbnJCLEdBQ3pDLFlBQTZCMEksSUFBekIxSSxFQUFPMnJCLG9CQUFnRWpqQixJQUFqQzFJLEVBQU80ckIsc0JBa0JyRCxTQUFTcUIsR0FBa0RqdEIsUUFDMUIwSSxJQUF6QjFJLEVBQU8yckIsZ0JBQ1AzckIsRUFBTzJyQixjQUFjTyxRQUFRbHNCLEVBQU9nZSxjQUNwQ2hlLEVBQU8yckIsbUJBQWdCampCLEdBRTNCLE1BQU02akIsRUFBU3ZzQixFQUFPdXJCLGFBQ1A3aUIsSUFBWDZqQixHQUNBWSxHQUFpQ1osRUFBUXZzQixFQUFPZ2UsY0FHeEQsU0FBU29QLEdBQWlDcHRCLEVBQVFxdEIsR0FDOUMsTUFBTWQsRUFBU3ZzQixFQUFPdXJCLGFBQ1A3aUIsSUFBWDZqQixHQUF3QmMsSUFBaUJydEIsRUFBTzhyQixnQkFDNUN1QixFQXdoQlosU0FBd0NkLEdBQ3BDZSxHQUFvQ2YsR0F4aEI1QmdCLENBQStCaEIsR0FHL0JDLEdBQWlDRCxJQUd6Q3ZzQixFQUFPOHJCLGNBQWdCdUIsRUF6UDNCNTJCLE9BQU9jLGlCQUFpQjJ5QixHQUFlN3lCLFVBQVcsQ0FDOUNrQixNQUFPLENBQUVmLFlBQVksR0FDckIwWixNQUFPLENBQUUxWixZQUFZLEdBQ3JCZzJCLFVBQVcsQ0FBRWgyQixZQUFZLEdBQ3pCaTJCLE9BQVEsQ0FBRWoyQixZQUFZLEtBRWdCLGlCQUEvQjZpQixFQUFlM2lCLGFBQ3RCakIsT0FBT0MsZUFBZXd6QixHQUFlN3lCLFVBQVdnakIsRUFBZTNpQixZQUFhLENBQ3hFZixNQUFPLGlCQUNQZ0IsY0FBYyxJQXVQdEIsTUFBTTJ6QixHQUNGLFlBQVl0ckIsR0FHUixHQUZBeWYsRUFBdUJ6ZixFQUFRLEVBQUcsK0JBQ2xDZ3FCLEdBQXFCaHFCLEVBQVEsbUJBQ3pCaXJCLEdBQXVCanJCLEdBQ3ZCLE1BQU0sSUFBSWhKLFVBQVUsK0VBRXhCUixLQUFLazNCLHFCQUF1QjF0QixFQUM1QkEsRUFBT3VyQixRQUFVLzBCLEtBQ2pCLE1BQU04RCxFQUFRMEYsRUFBTzJkLE9BQ3JCLEdBQWMsYUFBVnJqQixHQUNLNndCLEdBQW9DbnJCLElBQVdBLEVBQU84ckIsY0FDdkR3QixHQUFvQzkyQixNQUdwQ20zQixHQUE4Q24zQixNQUVsRG8zQixHQUFxQ3AzQixXQUVwQyxHQUFjLGFBQVY4RCxFQUNMdXpCLEdBQThDcjNCLEtBQU13SixFQUFPZ2UsY0FDM0Q0UCxHQUFxQ3AzQixXQUVwQyxHQUFjLFdBQVY4RCxFQUNMcXpCLEdBQThDbjNCLE1BZ2N0RG8zQixHQS9idURwM0IsTUFnY3ZEczNCLEdBaGN1RHQzQixVQUU5QyxDQUNELE1BQU11MkIsRUFBYy9zQixFQUFPZ2UsYUFDM0I2UCxHQUE4Q3IzQixLQUFNdTJCLEdBQ3BEZ0IsR0FBK0N2M0IsS0FBTXUyQixJQU83RCxhQUNJLE9BQUtpQixHQUE4QngzQixNQUc1QkEsS0FBSytuQixlQUZEdEQsRUFBb0JnVCxHQUFpQyxXQVlwRSxrQkFDSSxJQUFLRCxHQUE4QngzQixNQUMvQixNQUFNeTNCLEdBQWlDLGVBRTNDLFFBQWtDdmxCLElBQTlCbFMsS0FBS2szQixxQkFDTCxNQUFNUSxHQUEyQixlQUVyQyxPQXVJUixTQUFtRDNCLEdBQy9DLE1BQU12c0IsRUFBU3VzQixFQUFPbUIscUJBQ2hCcHpCLEVBQVEwRixFQUFPMmQsT0FDckIsTUFBYyxZQUFWcmpCLEdBQWlDLGFBQVZBLEVBQ2hCLEtBRUcsV0FBVkEsRUFDTyxFQUVKNnpCLEdBQThDbnVCLEVBQU93ckIsMkJBaEpqRDRDLENBQTBDNTNCLE1BVXJELFlBQ0ksT0FBS3czQixHQUE4QngzQixNQUc1QkEsS0FBSzYzQixjQUZEcFQsRUFBb0JnVCxHQUFpQyxVQU9wRSxNQUFNL1MsR0FDRixPQUFLOFMsR0FBOEJ4M0IsV0FHRGtTLElBQTlCbFMsS0FBS2szQixxQkFDRXpTLEVBQW9CaVQsR0FBMkIsVUE0RWxFLFNBQTBDM0IsRUFBUXJSLEdBRTlDLE9BQU9nUSxHQURRcUIsRUFBT21CLHFCQUNheFMsR0E1RXhCb1QsQ0FBaUM5M0IsS0FBTTBrQixHQUxuQ0QsRUFBb0JnVCxHQUFpQyxVQVVwRSxRQUNJLElBQUtELEdBQThCeDNCLE1BQy9CLE9BQU95a0IsRUFBb0JnVCxHQUFpQyxVQUVoRSxNQUFNanVCLEVBQVN4SixLQUFLazNCLHFCQUNwQixZQUFlaGxCLElBQVgxSSxFQUNPaWIsRUFBb0JpVCxHQUEyQixVQUV0RC9DLEdBQW9DbnJCLEdBQzdCaWIsRUFBb0IsSUFBSWprQixVQUFVLDJDQUV0Q3UzQixHQUFpQy8zQixNQVk1QyxjQUNJLElBQUt3M0IsR0FBOEJ4M0IsTUFDL0IsTUFBTXkzQixHQUFpQyxvQkFHNUJ2bEIsSUFEQWxTLEtBQUtrM0Isc0JBSXBCYyxHQUFtQ2g0QixNQUV2QyxNQUFNOEosR0FDRixPQUFLMHRCLEdBQThCeDNCLFdBR0RrUyxJQUE5QmxTLEtBQUtrM0IscUJBQ0V6UyxFQUFvQmlULEdBQTJCLGFBRW5ETyxHQUFpQ2o0QixLQUFNOEosR0FMbkMyYSxFQUFvQmdULEdBQWlDLFdBd0J4RSxTQUFTRCxHQUE4QjNtQixHQUNuQyxRQUFLbVQsRUFBYW5ULE1BR2I1USxPQUFPWSxVQUFVa3FCLGVBQWV0cEIsS0FBS29QLEVBQUcsd0JBVWpELFNBQVNrbkIsR0FBaUNoQyxHQUV0QyxPQUFPbkIsR0FEUW1CLEVBQU9tQixzQkFzQjFCLFNBQVNiLEdBQXNETixFQUFReHZCLEdBQ2pDLFlBQTlCd3ZCLEVBQU9tQyxtQkFDUEMsR0FBZ0NwQyxFQUFReHZCLEdBa1ZoRCxTQUFrRHd2QixFQUFRclIsR0FDdEQyUyxHQUE4Q3RCLEVBQVFyUixHQWhWbEQwVCxDQUF5Q3JDLEVBQVF4dkIsR0FjekQsU0FBU3l4QixHQUFtQ2pDLEdBQ3hDLE1BQU12c0IsRUFBU3VzQixFQUFPbUIscUJBQ2hCbUIsRUFBZ0IsSUFBSTczQixVQUFVLG9GQUNwQzYxQixHQUFzRE4sRUFBUXNDLEdBOUJsRSxTQUFnRXRDLEVBQVF4dkIsR0FDakMsWUFBL0J3dkIsRUFBT3VDLG9CQUNQM0IsR0FBaUNaLEVBQVF4dkIsR0FrVGpELFNBQW1Ed3ZCLEVBQVFyUixHQUN2RDZTLEdBQStDeEIsRUFBUXJSLEdBaFRuRDZULENBQTBDeEMsRUFBUXh2QixHQTRCdERpeUIsQ0FBdUR6QyxFQUFRc0MsR0FDL0Q3dUIsRUFBT3VyQixhQUFVN2lCLEVBQ2pCNmpCLEVBQU9tQiwwQkFBdUJobEIsRUFFbEMsU0FBUytsQixHQUFpQ2xDLEVBQVFqc0IsR0FDOUMsTUFBTU4sRUFBU3VzQixFQUFPbUIscUJBQ2hCcDFCLEVBQWEwSCxFQUFPd3JCLDBCQUNwQnlELEVBcUlWLFNBQXFEMzJCLEVBQVlnSSxHQUM3RCxJQUNJLE9BQU9oSSxFQUFXNDJCLHVCQUF1QjV1QixHQUU3QyxNQUFPNnVCLEdBRUgsT0FEQUMsR0FBNkM5MkIsRUFBWTYyQixHQUNsRCxHQTNJT0UsQ0FBNEMvMkIsRUFBWWdJLEdBQzFFLEdBQUlOLElBQVd1c0IsRUFBT21CLHFCQUNsQixPQUFPelMsRUFBb0JpVCxHQUEyQixhQUUxRCxNQUFNNXpCLEVBQVEwRixFQUFPMmQsT0FDckIsR0FBYyxZQUFWcmpCLEVBQ0EsT0FBTzJnQixFQUFvQmpiLEVBQU9nZSxjQUV0QyxHQUFJbU4sR0FBb0NuckIsSUFBcUIsV0FBVjFGLEVBQy9DLE9BQU8yZ0IsRUFBb0IsSUFBSWprQixVQUFVLDZEQUU3QyxHQUFjLGFBQVZzRCxFQUNBLE9BQU8yZ0IsRUFBb0JqYixFQUFPZ2UsY0FFdEMsTUFBTTVDLEVBclhWLFNBQXVDcGIsR0FRbkMsT0FQZ0I4YSxHQUFXLENBQUMzYSxFQUFTQyxLQUNqQyxNQUFNNHNCLEVBQWUsQ0FDakJmLFNBQVU5ckIsRUFDVityQixRQUFTOXJCLEdBRWJKLEVBQU95ckIsZUFBZWxyQixLQUFLeXNCLE1BK1dmc0MsQ0FBOEJ0dkIsR0FFOUMsT0FpSUosU0FBOEMxSCxFQUFZZ0ksRUFBTzJ1QixHQUM3RCxJQUNJN0wsR0FBcUI5cUIsRUFBWWdJLEVBQU8ydUIsR0FFNUMsTUFBT00sR0FFSCxZQURBSCxHQUE2QzkyQixFQUFZaTNCLEdBRzdELE1BQU12dkIsRUFBUzFILEVBQVdrM0IsMEJBQ3JCckUsR0FBb0NuckIsSUFBNkIsYUFBbEJBLEVBQU8yZCxRQUV2RHlQLEdBQWlDcHRCLEVBRFp5dkIsR0FBK0NuM0IsSUFHeEVvMEIsR0FBb0RwMEIsR0EvSXBEbzNCLENBQXFDcDNCLEVBQVlnSSxFQUFPMnVCLEdBQ2pEN1QsRUFyR1gza0IsT0FBT2MsaUJBQWlCK3pCLEdBQTRCajBCLFVBQVcsQ0FDM0RrQixNQUFPLENBQUVmLFlBQVksR0FDckIwWixNQUFPLENBQUUxWixZQUFZLEdBQ3JCbXFCLFlBQWEsQ0FBRW5xQixZQUFZLEdBQzNCeWlCLE1BQU8sQ0FBRXppQixZQUFZLEdBQ3JCb3FCLE9BQVEsQ0FBRXBxQixZQUFZLEdBQ3RCb3hCLFlBQWEsQ0FBRXB4QixZQUFZLEdBQzNCOHZCLE1BQU8sQ0FBRTl2QixZQUFZLEtBRWlCLGlCQUEvQjZpQixFQUFlM2lCLGFBQ3RCakIsT0FBT0MsZUFBZTQwQixHQUE0QmowQixVQUFXZ2pCLEVBQWUzaUIsWUFBYSxDQUNyRmYsTUFBTyw4QkFDUGdCLGNBQWMsSUEyRnRCLE1BQU04MEIsR0FBZ0IsR0FNdEIsTUFBTWhDLEdBQ0YsY0FDSSxNQUFNLElBQUl6ekIsVUFBVSx1QkFTeEIsTUFBTXNxQixHQUNGLElBaUNDOUcsRUFEa0NuVCxFQWhDSTdRLFFBb0N0Q0MsT0FBT1ksVUFBVWtxQixlQUFldHBCLEtBQUtvUCxFQUFHLDZCQW5DckMsTUFBTSxJQUFJclEsVUFBVSx5R0ErQmhDLElBQTJDcVEsRUE1QnJCLGFBREE3USxLQUFLZzVCLDBCQUEwQjdSLFFBTTdDZ1MsR0FBcUNuNUIsS0FBTThxQixHQUcvQyxDQUFDNUMsR0FBWXhELEdBQ1QsTUFBTTNPLEVBQVMvVixLQUFLbzVCLGdCQUFnQjFVLEdBRXBDLE9BREEyVSxHQUErQ3I1QixNQUN4QytWLEVBR1gsQ0FBQ29TLEtBQ0cwRSxHQUFXN3NCLE9Bc0JuQixTQUFTczBCLEdBQXFDOXFCLEVBQVExSCxFQUFZb3lCLEVBQWdCQyxFQUFnQkMsRUFBZ0JDLEVBQWdCemYsRUFBZW9mLEdBQzdJbHlCLEVBQVdrM0IsMEJBQTRCeHZCLEVBQ3ZDQSxFQUFPd3JCLDBCQUE0Qmx6QixFQUVuQ0EsRUFBVzRxQixZQUFTeGEsRUFDcEJwUSxFQUFXNnFCLHFCQUFrQnphLEVBQzdCMmEsR0FBVy9xQixHQUNYQSxFQUFXOHRCLFVBQVcsRUFDdEI5dEIsRUFBVzQyQix1QkFBeUIxRSxFQUNwQ2x5QixFQUFXbXdCLGFBQWVyZCxFQUMxQjlTLEVBQVd3M0IsZ0JBQWtCbkYsRUFDN0JyeUIsRUFBV3kzQixnQkFBa0JuRixFQUM3QnR5QixFQUFXczNCLGdCQUFrQi9FLEVBQzdCLE1BQU13QyxFQUFlb0MsR0FBK0NuM0IsR0FDcEU4MEIsR0FBaUNwdEIsRUFBUXF0QixHQUd6QzlSLEVBRHFCUCxFQUREMFAsTUFFTSxLQUN0QnB5QixFQUFXOHRCLFVBQVcsRUFDdEJzRyxHQUFvRHAwQixNQUNyRDAzQixJQUNDMTNCLEVBQVc4dEIsVUFBVyxFQUN0QnVHLEdBQWdDM3NCLEVBQVFnd0IsTUF3QmhELFNBQVNILEdBQStDdjNCLEdBQ3BEQSxFQUFXdzNCLHFCQUFrQnBuQixFQUM3QnBRLEVBQVd5M0IscUJBQWtCcm5CLEVBQzdCcFEsRUFBV3MzQixxQkFBa0JsbkIsRUFDN0JwUSxFQUFXNDJCLDRCQUF5QnhtQixFQWV4QyxTQUFTeWxCLEdBQThDNzFCLEdBQ25ELE9BQU9BLEVBQVdtd0IsYUFBZW53QixFQUFXNnFCLGdCQWtCaEQsU0FBU3VKLEdBQW9EcDBCLEdBQ3pELE1BQU0wSCxFQUFTMUgsRUFBV2szQiwwQkFDMUIsSUFBS2wzQixFQUFXOHRCLFNBQ1osT0FFSixRQUFxQzFkLElBQWpDMUksRUFBTzByQixzQkFDUCxPQUdKLEdBQWMsYUFEQTFyQixFQUFPMmQsT0FHakIsWUFEQWlQLEdBQTZCNXNCLEdBR2pDLEdBQWlDLElBQTdCMUgsRUFBVzRxQixPQUFPdmpCLE9BQ2xCLE9BRUosTUFBTWhKLEVBQXVCMkIsRUF2a0RONHFCLE9BQU9nQixPQUNsQnZ0QixNQXVrRFJBLElBQVU4MUIsR0FZbEIsU0FBcURuMEIsR0FDakQsTUFBTTBILEVBQVMxSCxFQUFXazNCLDJCQTFiOUIsU0FBZ0R4dkIsR0FDNUNBLEVBQU80ckIsc0JBQXdCNXJCLEVBQU8yckIsY0FDdEMzckIsRUFBTzJyQixtQkFBZ0JqakIsR0F5YnZCdW5CLENBQXVDandCLEdBQ3ZDZ2pCLEdBQWExcUIsR0FDYixNQUFNNDNCLEVBQW1CNTNCLEVBQVd5M0Isa0JBQ3BDRixHQUErQ3YzQixHQUMvQ2lqQixFQUFZMlUsR0FBa0IsTUF4ZWxDLFNBQTJDbHdCLEdBQ3ZDQSxFQUFPNHJCLHNCQUFzQkssY0FBU3ZqQixHQUN0QzFJLEVBQU80ckIsMkJBQXdCbGpCLEVBRWpCLGFBREExSSxFQUFPMmQsU0FHakIzZCxFQUFPZ2Usa0JBQWV0VixPQUNjQSxJQUFoQzFJLEVBQU82ckIsdUJBQ1A3ckIsRUFBTzZyQixxQkFBcUJJLFdBQzVCanNCLEVBQU82ckIsMEJBQXVCbmpCLElBR3RDMUksRUFBTzJkLE9BQVMsU0FDaEIsTUFBTTRPLEVBQVN2c0IsRUFBT3VyQixhQUNQN2lCLElBQVg2akIsR0FDQXVCLEdBQWtDdkIsR0EwZGxDNEQsQ0FBa0Nud0IsTUFDbkNrYixLQXhkUCxTQUFvRGxiLEVBQVFqRCxHQUN4RGlELEVBQU80ckIsc0JBQXNCTSxRQUFRbnZCLEdBQ3JDaUQsRUFBTzRyQiwyQkFBd0JsakIsT0FFS0EsSUFBaEMxSSxFQUFPNnJCLHVCQUNQN3JCLEVBQU82ckIscUJBQXFCSyxRQUFRbnZCLEdBQ3BDaUQsRUFBTzZyQiwwQkFBdUJuakIsR0FFbENpa0IsR0FBZ0Mzc0IsRUFBUWpELEdBaWRwQ3F6QixDQUEyQ3B3QixFQUFRa2IsTUFwQm5EbVYsQ0FBNEMvM0IsR0F1QnBELFNBQXFEQSxFQUFZZ0ksR0FDN0QsTUFBTU4sRUFBUzFILEVBQVdrM0IsMkJBbGM5QixTQUFxRHh2QixHQUNqREEsRUFBTzByQixzQkFBd0IxckIsRUFBT3lyQixlQUFlL0ssUUFrY3JENFAsQ0FBNEN0d0IsR0FFNUN1YixFQUR5QmpqQixFQUFXdzNCLGdCQUFnQnh2QixJQUN0QixNQTNmbEMsU0FBMkNOLEdBQ3ZDQSxFQUFPMHJCLHNCQUFzQk8sY0FBU3ZqQixHQUN0QzFJLEVBQU8wckIsMkJBQXdCaGpCLEVBMGYzQjZuQixDQUFrQ3Z3QixHQUNsQyxNQUFNMUYsRUFBUTBGLEVBQU8yZCxPQUVyQixHQURBcUYsR0FBYTFxQixJQUNSNnlCLEdBQW9DbnJCLElBQXFCLGFBQVYxRixFQUFzQixDQUN0RSxNQUFNK3lCLEVBQWVvQyxHQUErQ24zQixHQUNwRTgwQixHQUFpQ3B0QixFQUFRcXRCLEdBRTdDWCxHQUFvRHAwQixNQUNyRDRpQixJQUN1QixhQUFsQmxiLEVBQU8yZCxRQUNQa1MsR0FBK0N2M0IsR0FsZ0IzRCxTQUFvRDBILEVBQVFqRCxHQUN4RGlELEVBQU8wckIsc0JBQXNCUSxRQUFRbnZCLEdBQ3JDaUQsRUFBTzByQiwyQkFBd0JoakIsRUFDL0Jpa0IsR0FBZ0Mzc0IsRUFBUWpELEdBaWdCcEN5ekIsQ0FBMkN4d0IsRUFBUWtiLE1BckNuRHVWLENBQTRDbjRCLEVBQVkzQixHQUdoRSxTQUFTeTRCLEdBQTZDOTJCLEVBQVl5RSxHQUNWLGFBQWhEekUsRUFBV2szQiwwQkFBMEI3UixRQUNyQ2dTLEdBQXFDcjNCLEVBQVl5RSxHQW1DekQsU0FBUzB5QixHQUErQ24zQixHQUVwRCxPQURvQjYxQixHQUE4QzcxQixJQUM1QyxFQUcxQixTQUFTcTNCLEdBQXFDcjNCLEVBQVl5RSxHQUN0RCxNQUFNaUQsRUFBUzFILEVBQVdrM0IsMEJBQzFCSyxHQUErQ3YzQixHQUMvQyt6QixHQUE0QnJzQixFQUFRakQsR0FHeEMsU0FBU2l1QixHQUEwQjd3QixHQUMvQixPQUFPLElBQUluRCxVQUFVLDRCQUE0Qm1ELDBDQUdyRCxTQUFTOHpCLEdBQWlDOXpCLEdBQ3RDLE9BQU8sSUFBSW5ELFVBQVUseUNBQXlDbUQsdURBRWxFLFNBQVMrekIsR0FBMkIvekIsR0FDaEMsT0FBTyxJQUFJbkQsVUFBVSxVQUFZbUQsRUFBTyxxQ0FFNUMsU0FBU3l6QixHQUFxQ3JCLEdBQzFDQSxFQUFPaE8sZUFBaUJ6RCxHQUFXLENBQUMzYSxFQUFTQyxLQUN6Q21zQixFQUFPL04sdUJBQXlCcmUsRUFDaENvc0IsRUFBTzlOLHNCQUF3QnJlLEVBQy9CbXNCLEVBQU91QyxvQkFBc0IsYUFHckMsU0FBU2YsR0FBK0N4QixFQUFRclIsR0FDNUQwUyxHQUFxQ3JCLEdBQ3JDWSxHQUFpQ1osRUFBUXJSLEdBTTdDLFNBQVNpUyxHQUFpQ1osRUFBUXJSLFFBQ1R4UyxJQUFqQzZqQixFQUFPOU4sd0JBR1g1QyxFQUEwQjBRLEVBQU9oTyxnQkFDakNnTyxFQUFPOU4sc0JBQXNCdkQsR0FDN0JxUixFQUFPL04sNEJBQXlCOVYsRUFDaEM2akIsRUFBTzlOLDJCQUF3Qi9WLEVBQy9CNmpCLEVBQU91QyxvQkFBc0IsWUFLakMsU0FBU2hCLEdBQWtDdkIsUUFDRDdqQixJQUFsQzZqQixFQUFPL04seUJBR1grTixFQUFPL04sNEJBQXVCOVYsR0FDOUI2akIsRUFBTy9OLDRCQUF5QjlWLEVBQ2hDNmpCLEVBQU85TiwyQkFBd0IvVixFQUMvQjZqQixFQUFPdUMsb0JBQXNCLFlBRWpDLFNBQVN4QixHQUFvQ2YsR0FDekNBLEVBQU84QixjQUFnQnZULEdBQVcsQ0FBQzNhLEVBQVNDLEtBQ3hDbXNCLEVBQU9tRSxzQkFBd0J2d0IsRUFDL0Jvc0IsRUFBT29FLHFCQUF1QnZ3QixLQUVsQ21zQixFQUFPbUMsbUJBQXFCLFVBRWhDLFNBQVNiLEdBQThDdEIsRUFBUXJSLEdBQzNEb1MsR0FBb0NmLEdBQ3BDb0MsR0FBZ0NwQyxFQUFRclIsR0FFNUMsU0FBU3lTLEdBQThDcEIsR0FDbkRlLEdBQW9DZixHQUNwQ0MsR0FBaUNELEdBRXJDLFNBQVNvQyxHQUFnQ3BDLEVBQVFyUixRQUNUeFMsSUFBaEM2akIsRUFBT29FLHVCQUdYOVUsRUFBMEIwUSxFQUFPOEIsZUFDakM5QixFQUFPb0UscUJBQXFCelYsR0FDNUJxUixFQUFPbUUsMkJBQXdCaG9CLEVBQy9CNmpCLEVBQU9vRSwwQkFBdUJqb0IsRUFDOUI2akIsRUFBT21DLG1CQUFxQixZQVFoQyxTQUFTbEMsR0FBaUNELFFBQ0Q3akIsSUFBakM2akIsRUFBT21FLHdCQUdYbkUsRUFBT21FLDJCQUFzQmhvQixHQUM3QjZqQixFQUFPbUUsMkJBQXdCaG9CLEVBQy9CNmpCLEVBQU9vRSwwQkFBdUJqb0IsRUFDOUI2akIsRUFBT21DLG1CQUFxQixhQXBRaENqNEIsT0FBT2MsaUJBQWlCa3pCLEdBQWdDcHpCLFVBQVcsQ0FDL0QwRixNQUFPLENBQUV2RixZQUFZLEtBRWlCLGlCQUEvQjZpQixFQUFlM2lCLGFBQ3RCakIsT0FBT0MsZUFBZSt6QixHQUFnQ3B6QixVQUFXZ2pCLEVBQWUzaUIsWUFBYSxDQUN6RmYsTUFBTyxrQ0FDUGdCLGNBQWMsSUErUXRCLE1BQU1pNUIsR0FBNkMsb0JBQWpCQyxhQUErQkEsa0JBQWVub0IsRUEyQjFFb29CLEdBeEJOLFNBQW1DN0gsR0FDL0IsR0FBc0IsbUJBQVRBLEdBQXVDLGlCQUFUQSxFQUN2QyxPQUFPLEVBRVgsSUFFSSxPQURBLElBQUlBLEdBQ0csRUFFWCxNQUFPOEgsR0FDSCxPQUFPLEdBZVFDLENBQTBCSixJQUFzQkEsR0FadkUsV0FDSSxNQUFNM0gsRUFBTyxTQUFzQnBzQixFQUFTMUMsR0FDeEMzRCxLQUFLcUcsUUFBVUEsR0FBVyxHQUMxQnJHLEtBQUsyRCxLQUFPQSxHQUFRLFFBQ2hCbkIsTUFBTWtaLG1CQUNObFosTUFBTWtaLGtCQUFrQjFiLEtBQU1BLEtBQUsyUCxjQUszQyxPQUZBOGlCLEVBQUs1eEIsVUFBWVosT0FBT3VCLE9BQU9nQixNQUFNM0IsV0FDckNaLE9BQU9DLGVBQWV1eUIsRUFBSzV4QixVQUFXLGNBQWUsQ0FBRVYsTUFBT3N5QixFQUFNN2lCLFVBQVUsRUFBTXpPLGNBQWMsSUFDM0ZzeEIsRUFFaUZnSSxHQUU1RixTQUFTQyxHQUFxQnprQixFQUFRdU4sRUFBTW1YLEVBQWNDLEVBQWNyUCxFQUFlaHFCLEdBQ25GLE1BQU0rWSxFQUFTc1AsRUFBbUMzVCxHQUM1QzhmLEVBQVNsQixHQUFtQ3JSLEdBQ2xEdk4sRUFBTytVLFlBQWEsRUFDcEIsSUFBSTZQLEdBQWUsRUFFZkMsRUFBZXRXLE9BQW9CdFMsR0FDdkMsT0FBT29TLEdBQVcsQ0FBQzNhLEVBQVNDLEtBQ3hCLElBQUl5cUIsRUFDSixRQUFlbmlCLElBQVgzUSxFQUFzQixDQXNCdEIsR0FyQkE4eUIsRUFBaUIsS0FDYixNQUFNOXRCLEVBQVEsSUFBSSt6QixHQUFlLFVBQVcsY0FDdENTLEVBQVUsR0FDWEgsR0FDREcsRUFBUWh4QixNQUFLLElBQ1csYUFBaEJ5WixFQUFLMkQsT0FDRXVOLEdBQW9CbFIsRUFBTWpkLEdBRTlCaWUsT0FBb0J0UyxLQUc5QnFaLEdBQ0R3UCxFQUFRaHhCLE1BQUssSUFDYSxhQUFsQmtNLEVBQU9rUixPQUNBTyxHQUFxQnpSLEVBQVExUCxHQUVqQ2llLE9BQW9CdFMsS0FHbkM4b0IsR0FBbUIsSUFBTXR4QixRQUFRdXhCLElBQUlGLEVBQVFwMkIsS0FBSXUyQixHQUFVQSxTQUFZLEVBQU0zMEIsSUFFN0VoRixFQUFPZCxRQUVQLFlBREE0ekIsSUFHSjl5QixFQUFPbVgsaUJBQWlCLFFBQVMyYixHQXlGckMsSUFBMkI3cUIsRUFBUW9iLEVBQVNzVyxFQXhCNUMsR0EzQkFDLEVBQW1CbGxCLEVBQVFxRSxFQUFPeU4sZ0JBQWdCd08sSUFDekNxRSxFQUlEUSxHQUFTLEVBQU03RSxHQUhmeUUsR0FBbUIsSUFBTXRHLEdBQW9CbFIsRUFBTStTLEtBQWMsRUFBTUEsTUFPL0U0RSxFQUFtQjNYLEVBQU11UyxFQUFPaE8sZ0JBQWdCd08sSUFDdkNoTCxFQUlENlAsR0FBUyxFQUFNN0UsR0FIZnlFLEdBQW1CLElBQU10VCxHQUFxQnpSLEVBQVFzZ0IsS0FBYyxFQUFNQSxNQXdDdkQvc0IsRUFqQ1R5TSxFQWlDaUIyTyxFQWpDVHRLLEVBQU95TixlQWlDV21ULEVBakNLLEtBQ3hDUCxFQUlEUyxJQUhBSixHQUFtQixJQTVmbkMsU0FBOERqRixHQUMxRCxNQUFNdnNCLEVBQVN1c0IsRUFBT21CLHFCQUNoQnB6QixFQUFRMEYsRUFBTzJkLE9BQ3JCLE9BQUl3TixHQUFvQ25yQixJQUFxQixXQUFWMUYsRUFDeEMwZ0IsT0FBb0J0UyxHQUVqQixZQUFWcE8sRUFDTzJnQixFQUFvQmpiLEVBQU9nZSxjQUUvQnVRLEdBQWlDaEMsR0FtZkhzRixDQUFxRHRGLE1BZ0M1RCxXQUFsQnZzQixFQUFPMmQsT0FDUCtULElBR0FsVyxFQUFnQkosRUFBU3NXLEdBN0I3QnZHLEdBQW9DblIsSUFBeUIsV0FBaEJBLEVBQUsyRCxPQUFxQixDQUN2RSxNQUFNbVUsRUFBYSxJQUFJOTZCLFVBQVUsK0VBQzVCK3FCLEVBSUQ2UCxHQUFTLEVBQU1FLEdBSGZOLEdBQW1CLElBQU10VCxHQUFxQnpSLEVBQVFxbEIsS0FBYSxFQUFNQSxHQU9qRixTQUFTQyxJQUdMLE1BQU1DLEVBQWtCVixFQUN4QixPQUFPblcsRUFBbUJtVyxHQUFjLElBQU1VLElBQW9CVixFQUFlUyxTQUEwQnJwQixJQUUvRyxTQUFTaXBCLEVBQW1CM3hCLEVBQVFvYixFQUFTc1csR0FDbkIsWUFBbEIxeEIsRUFBTzJkLE9BQ1ArVCxFQUFPMXhCLEVBQU9nZSxjQUdkdkMsRUFBY0wsRUFBU3NXLEdBVy9CLFNBQVNGLEVBQW1CRSxFQUFRTyxFQUFpQkMsR0FXakQsU0FBU0MsSUFDTDVXLEVBQVltVyxLQUFVLElBQU1uWixFQUFTMFosRUFBaUJDLEtBQWdCRSxHQUFZN1osR0FBUyxFQUFNNlosS0FYakdmLElBR0pBLEdBQWUsRUFDSyxhQUFoQnJYLEVBQUsyRCxRQUEwQndOLEdBQW9DblIsR0FJbkVtWSxJQUhBM1csRUFBZ0J1VyxJQUF5QkksSUFTakQsU0FBU1AsRUFBU1MsRUFBU3QxQixHQUNuQnMwQixJQUdKQSxHQUFlLEVBQ0ssYUFBaEJyWCxFQUFLMkQsUUFBMEJ3TixHQUFvQ25SLEdBSW5FekIsRUFBUzhaLEVBQVN0MUIsR0FIbEJ5ZSxFQUFnQnVXLEtBQXlCLElBQU14WixFQUFTOFosRUFBU3QxQixNQU16RSxTQUFTd2IsRUFBUzhaLEVBQVN0MUIsR0FDdkJ5eEIsR0FBbUNqQyxHQUNuQ3BPLEVBQW1Dck4sUUFDcEJwSSxJQUFYM1EsR0FDQUEsRUFBTzBnQixvQkFBb0IsUUFBU29TLEdBRXBDd0gsRUFDQWp5QixFQUFPckQsR0FHUG9ELE9BQVF1SSxHQTVEaEJtVCxFQXBFV2YsR0FBVyxDQUFDd1gsRUFBYUMsTUFDNUIsU0FBUzFxQixFQUFLb0osR0FDTkEsRUFDQXFoQixJQUtBblgsRUFPUmtXLEVBQ09yVyxHQUFvQixHQUV4QkcsRUFBbUJvUixFQUFPOEIsZUFBZSxJQUNyQ3ZULEdBQVcsQ0FBQzBYLEVBQWFDLEtBQzVCclIsR0FBZ0N0USxFQUFRLENBQ3BDOFAsWUFBYXRnQixJQUNUZ3hCLEVBQWVuVyxFQUFtQnNULEdBQWlDbEMsRUFBUWpzQixRQUFRb0ksRUFBVzZSLEdBQzlGaVksR0FBWSxJQUVoQjdSLFlBQWEsSUFBTTZSLEdBQVksR0FDL0JuUixZQUFhb1IsU0FsQmtCNXFCLEVBQU0wcUIsR0FHN0MxcUIsRUFBSyxVQWdJckIsTUFBTTZxQixHQUNGLGNBQ0ksTUFBTSxJQUFJMTdCLFVBQVUsdUJBTXhCLGtCQUNJLElBQUsyN0IsR0FBa0NuOEIsTUFDbkMsTUFBTW84QixHQUFxQyxlQUUvQyxPQUFPQyxHQUE4Q3I4QixNQU16RCxRQUNJLElBQUttOEIsR0FBa0NuOEIsTUFDbkMsTUFBTW84QixHQUFxQyxTQUUvQyxJQUFLRSxHQUFpRHQ4QixNQUNsRCxNQUFNLElBQUlRLFVBQVUsbURBRXhCKzdCLEdBQXFDdjhCLE1BRXpDLFFBQVE4SixHQUNKLElBQUtxeUIsR0FBa0NuOEIsTUFDbkMsTUFBTW84QixHQUFxQyxXQUUvQyxJQUFLRSxHQUFpRHQ4QixNQUNsRCxNQUFNLElBQUlRLFVBQVUscURBRXhCLE9BQU9nOEIsR0FBdUN4OEIsS0FBTThKLEdBS3hELE1BQU1naEIsR0FDRixJQUFLcVIsR0FBa0NuOEIsTUFDbkMsTUFBTW84QixHQUFxQyxTQUUvQ0ssR0FBcUN6OEIsS0FBTThxQixHQUcvQyxDQUFDMUMsR0FBYTFELEdBQ1ZtSSxHQUFXN3NCLE1BQ1gsTUFBTStWLEVBQVMvVixLQUFLa3ZCLGlCQUFpQnhLLEdBRXJDLE9BREFnWSxHQUErQzE4QixNQUN4QytWLEVBR1gsQ0FBQ3NTLEdBQVcwQixHQUNSLE1BQU12Z0IsRUFBU3hKLEtBQUsyOEIsMEJBQ3BCLEdBQUkzOEIsS0FBSzBzQixPQUFPdmpCLE9BQVMsRUFBRyxDQUN4QixNQUFNVyxFQUFRMGlCLEdBQWF4c0IsTUFDdkJBLEtBQUtzdUIsaUJBQTBDLElBQXZCdHVCLEtBQUswc0IsT0FBT3ZqQixRQUNwQ3V6QixHQUErQzE4QixNQUMvQzB1QixHQUFvQmxsQixJQUdwQm96QixHQUFnRDU4QixNQUVwRCtwQixFQUFZSyxZQUFZdGdCLFFBR3hCZ2dCLEVBQTZCdGdCLEVBQVF1Z0IsR0FDckM2UyxHQUFnRDU4QixPQWlCNUQsU0FBU204QixHQUFrQ3RyQixHQUN2QyxRQUFLbVQsRUFBYW5ULE1BR2I1USxPQUFPWSxVQUFVa3FCLGVBQWV0cEIsS0FBS29QLEVBQUcsNkJBS2pELFNBQVMrckIsR0FBZ0Q5NkIsR0FDbEMrNkIsR0FBOEMvNkIsS0FJN0RBLEVBQVdpdUIsU0FDWGp1QixFQUFXa3VCLFlBQWEsR0FHNUJsdUIsRUFBV2l1QixVQUFXLEVBRXRCaEwsRUFEb0JqakIsRUFBV211QixrQkFDTixLQUNyQm51QixFQUFXaXVCLFVBQVcsRUFDbEJqdUIsRUFBV2t1QixhQUNYbHVCLEVBQVdrdUIsWUFBYSxFQUN4QjRNLEdBQWdEOTZCLE9BRXJEZ3BCLElBQ0MyUixHQUFxQzM2QixFQUFZZ3BCLFFBR3pELFNBQVMrUixHQUE4Qy82QixHQUNuRCxNQUFNMEgsRUFBUzFILEVBQVc2NkIsMEJBQzFCLFFBQUtMLEdBQWlEeDZCLE9BR2pEQSxFQUFXOHRCLGNBR1pwRixHQUF1QmhoQixJQUFXNmdCLEVBQWlDN2dCLEdBQVUsSUFHN0Q2eUIsR0FBOEN2NkIsR0FDaEQsSUFLdEIsU0FBUzQ2QixHQUErQzU2QixHQUNwREEsRUFBV211QixvQkFBaUIvZCxFQUM1QnBRLEVBQVdvdEIsc0JBQW1CaGQsRUFDOUJwUSxFQUFXNDJCLDRCQUF5QnhtQixFQUd4QyxTQUFTcXFCLEdBQXFDejZCLEdBQzFDLElBQUt3NkIsR0FBaUR4NkIsR0FDbEQsT0FFSixNQUFNMEgsRUFBUzFILEVBQVc2NkIsMEJBQzFCNzZCLEVBQVd3c0IsaUJBQWtCLEVBQ0ksSUFBN0J4c0IsRUFBVzRxQixPQUFPdmpCLFNBQ2xCdXpCLEdBQStDNTZCLEdBQy9DNHNCLEdBQW9CbGxCLElBRzVCLFNBQVNnekIsR0FBdUMxNkIsRUFBWWdJLEdBQ3hELElBQUt3eUIsR0FBaUR4NkIsR0FDbEQsT0FFSixNQUFNMEgsRUFBUzFILEVBQVc2NkIsMEJBQzFCLEdBQUluUyxHQUF1QmhoQixJQUFXNmdCLEVBQWlDN2dCLEdBQVUsRUFDN0V5Z0IsRUFBaUN6Z0IsRUFBUU0sR0FBTyxPQUUvQyxDQUNELElBQUkydUIsRUFDSixJQUNJQSxFQUFZMzJCLEVBQVc0MkIsdUJBQXVCNXVCLEdBRWxELE1BQU82dUIsR0FFSCxNQURBOEQsR0FBcUMzNkIsRUFBWTYyQixHQUMzQ0EsRUFFVixJQUNJL0wsR0FBcUI5cUIsRUFBWWdJLEVBQU8ydUIsR0FFNUMsTUFBT00sR0FFSCxNQURBMEQsR0FBcUMzNkIsRUFBWWkzQixHQUMzQ0EsR0FHZDZELEdBQWdEOTZCLEdBRXBELFNBQVMyNkIsR0FBcUMzNkIsRUFBWWdwQixHQUN0RCxNQUFNdGhCLEVBQVMxSCxFQUFXNjZCLDBCQUNKLGFBQWxCbnpCLEVBQU8yZCxTQUdYMEYsR0FBVy9xQixHQUNYNDZCLEdBQStDNTZCLEdBQy9Da3dCLEdBQW9CeG9CLEVBQVFzaEIsSUFFaEMsU0FBU3VSLEdBQThDdjZCLEdBQ25ELE1BQU1nQyxFQUFRaEMsRUFBVzY2QiwwQkFBMEJ4VixPQUNuRCxNQUFjLFlBQVZyakIsRUFDTyxLQUVHLFdBQVZBLEVBQ08sRUFFSmhDLEVBQVdtd0IsYUFBZW53QixFQUFXNnFCLGdCQVNoRCxTQUFTMlAsR0FBaUR4NkIsR0FDdEQsTUFBTWdDLEVBQVFoQyxFQUFXNjZCLDBCQUEwQnhWLE9BQ25ELE9BQUtybEIsRUFBV3dzQixpQkFBNkIsYUFBVnhxQixFQUt2QyxTQUFTZzVCLEdBQXFDdHpCLEVBQVExSCxFQUFZb3lCLEVBQWdCNkksRUFBZUMsRUFBaUJwb0IsRUFBZW9mLEdBQzdIbHlCLEVBQVc2NkIsMEJBQTRCbnpCLEVBQ3ZDMUgsRUFBVzRxQixZQUFTeGEsRUFDcEJwUSxFQUFXNnFCLHFCQUFrQnphLEVBQzdCMmEsR0FBVy9xQixHQUNYQSxFQUFXOHRCLFVBQVcsRUFDdEI5dEIsRUFBV3dzQixpQkFBa0IsRUFDN0J4c0IsRUFBV2t1QixZQUFhLEVBQ3hCbHVCLEVBQVdpdUIsVUFBVyxFQUN0Qmp1QixFQUFXNDJCLHVCQUF5QjFFLEVBQ3BDbHlCLEVBQVdtd0IsYUFBZXJkLEVBQzFCOVMsRUFBV211QixlQUFpQjhNLEVBQzVCajdCLEVBQVdvdEIsaUJBQW1COE4sRUFDOUJ4ekIsRUFBT3loQiwwQkFBNEJucEIsRUFFbkNpakIsRUFBWVAsRUFEUTBQLE1BQzBCLEtBQzFDcHlCLEVBQVc4dEIsVUFBVyxFQUN0QmdOLEdBQWdEOTZCLE1BQ2pEMDNCLElBQ0NpRCxHQUFxQzM2QixFQUFZMDNCLE1Bb0J6RCxTQUFTNEMsR0FBcUN6NEIsR0FDMUMsT0FBTyxJQUFJbkQsVUFBVSw2Q0FBNkNtRCwyREFxSHRFLFNBQVNzNUIsR0FBc0Nua0IsRUFBSXNhLEVBQVV0SyxHQUV6RCxPQURBQyxFQUFlalEsRUFBSWdRLEdBQ1hwRSxHQUFXb0IsRUFBWWhOLEVBQUlzYSxFQUFVLENBQUMxTyxJQUVsRCxTQUFTd1ksR0FBb0Nwa0IsRUFBSXNhLEVBQVV0SyxHQUV2RCxPQURBQyxFQUFlalEsRUFBSWdRLEdBQ1hobkIsR0FBZWdrQixFQUFZaE4sRUFBSXNhLEVBQVUsQ0FBQ3R4QixJQUV0RCxTQUFTcTdCLEdBQXFDcmtCLEVBQUlzYSxFQUFVdEssR0FFeEQsT0FEQUMsRUFBZWpRLEVBQUlnUSxHQUNYaG5CLEdBQWUyakIsRUFBWTNNLEVBQUlzYSxFQUFVLENBQUN0eEIsSUFFdEQsU0FBU3M3QixHQUEwQnY3QixFQUFNaW5CLEdBRXJDLEdBQWEsVUFEYmpuQixFQUFPLEdBQUdBLEtBRU4sTUFBTSxJQUFJckIsVUFBVSxHQUFHc29CLE1BQVlqbkIsOERBRXZDLE9BQU9BLEVBVVgsU0FBU3c3QixHQUFnQ0MsRUFBTXhVLEdBRTNDLEdBQWEsU0FEYndVLEVBQU8sR0FBR0EsS0FFTixNQUFNLElBQUk5OEIsVUFBVSxHQUFHc29CLE1BQVl3VSxvRUFFdkMsT0FBT0EsRUFTWCxTQUFTQyxHQUFtQm43QixFQUFTMG1CLEdBQ2pDRCxFQUFpQnptQixFQUFTMG1CLEdBQzFCLE1BQU04UixFQUFleDRCLGFBQXlDLEVBQVNBLEVBQVF3NEIsYUFDekVyUCxFQUFnQm5wQixhQUF5QyxFQUFTQSxFQUFRbXBCLGNBQzFFb1AsRUFBZXY0QixhQUF5QyxFQUFTQSxFQUFRdTRCLGFBQ3pFcDVCLEVBQVNhLGFBQXlDLEVBQVNBLEVBQVFiLE9BSXpFLFlBSGUyUSxJQUFYM1EsR0FVUixTQUEyQkEsRUFBUXVuQixHQUMvQixJQXZvQkosU0FBdUIzb0IsR0FDbkIsR0FBcUIsaUJBQVZBLEdBQWdDLE9BQVZBLEVBQzdCLE9BQU8sRUFFWCxJQUNJLE1BQWdDLGtCQUFsQkEsRUFBTU0sUUFFeEIsTUFBTzg1QixHQUVILE9BQU8sR0E4bkJOaUQsQ0FBY2o4QixHQUNmLE1BQU0sSUFBSWYsVUFBVSxHQUFHc29CLDRCQVh2QjJVLENBQWtCbDhCLEVBQVEsR0FBR3VuQiw4QkFFMUIsQ0FDSDhSLGFBQWN2cUIsUUFBUXVxQixHQUN0QnJQLGNBQWVsYixRQUFRa2IsR0FDdkJvUCxhQUFjdHFCLFFBQVFzcUIsR0FDdEJwNUIsVUE1VlJ0QixPQUFPYyxpQkFBaUJtN0IsR0FBZ0NyN0IsVUFBVyxDQUMvRDZaLE1BQU8sQ0FBRTFaLFlBQVksR0FDckIyWixRQUFTLENBQUUzWixZQUFZLEdBQ3ZCdUYsTUFBTyxDQUFFdkYsWUFBWSxHQUNyQm94QixZQUFhLENBQUVweEIsWUFBWSxLQUVXLGlCQUEvQjZpQixFQUFlM2lCLGFBQ3RCakIsT0FBT0MsZUFBZWc4QixHQUFnQ3I3QixVQUFXZ2pCLEVBQWUzaUIsWUFBYSxDQUN6RmYsTUFBTyxrQ0FDUGdCLGNBQWMsSUE0V3RCLE1BQU00VCxHQUNGLFlBQVkyb0IsRUFBc0IsR0FBSTlKLEVBQWMsU0FDcEIxaEIsSUFBeEJ3ckIsRUFDQUEsRUFBc0IsS0FHdEIxVSxFQUFhMFUsRUFBcUIsbUJBRXRDLE1BQU01SyxFQUFXRyxHQUF1QlcsRUFBYSxvQkFDL0MrSixFQWhIZCxTQUE4QzFuQixFQUFRNlMsR0FDbERELEVBQWlCNVMsRUFBUTZTLEdBQ3pCLE1BQU1zSyxFQUFXbmQsRUFDWG9aLEVBQXdCK0QsYUFBMkMsRUFBU0EsRUFBUy9ELHNCQUNyRm5FLEVBQVNrSSxhQUEyQyxFQUFTQSxFQUFTbEksT0FDdEUwUyxFQUFPeEssYUFBMkMsRUFBU0EsRUFBU3dLLEtBQ3BFaHpCLEVBQVF3b0IsYUFBMkMsRUFBU0EsRUFBU3hvQixNQUNyRS9JLEVBQU91eEIsYUFBMkMsRUFBU0EsRUFBU3Z4QixLQUMxRSxNQUFPLENBQ0h3dEIsMkJBQWlEbmQsSUFBMUJtZCxPQUNuQm5kLEVBQ0FvWCxFQUF3QytGLEVBQXVCLEdBQUd2Ryw2Q0FDdEVvQyxZQUFtQmhaLElBQVhnWixPQUNKaFosRUFDQStxQixHQUFzQy9SLEVBQVFrSSxFQUFVLEdBQUd0Syw4QkFDL0Q4VSxVQUFlMXJCLElBQVQwckIsT0FDRjFyQixFQUNBZ3JCLEdBQW9DVSxFQUFNeEssRUFBVSxHQUFHdEssNEJBQzNEbGUsV0FBaUJzSCxJQUFWdEgsT0FDSHNILEVBQ0FpckIsR0FBcUN2eUIsRUFBT3dvQixFQUFVLEdBQUd0Syw2QkFDN0RqbkIsVUFBZXFRLElBQVRyUSxPQUFxQnFRLEVBQVlrckIsR0FBMEJ2N0IsRUFBTSxHQUFHaW5CLDZCQTJGakQrVSxDQUFxQ0gsRUFBcUIsbUJBRW5GLEdBREFJLEdBQXlCOTlCLE1BQ0ssVUFBMUIyOUIsRUFBaUI5N0IsS0FBa0IsQ0FDbkMsUUFBc0JxUSxJQUFsQjRnQixFQUFTaGdCLEtBQ1QsTUFBTSxJQUFJaUcsV0FBVywrREEzeURyQyxTQUErRHZQLEVBQVF1MEIsRUFBc0JucEIsR0FDekYsTUFBTTlTLEVBQWE3QixPQUFPdUIsT0FBT3VzQixHQUE2Qmx0QixXQUM5RCxJQUFJcXpCLEVBQWlCLE9BQ2pCNkksRUFBZ0IsSUFBTXZZLE9BQW9CdFMsR0FDMUM4cUIsRUFBa0IsSUFBTXhZLE9BQW9CdFMsUUFDYkEsSUFBL0I2ckIsRUFBcUJuekIsUUFDckJzcEIsRUFBaUIsSUFBTTZKLEVBQXFCbnpCLE1BQU05SSxTQUVwQm9RLElBQTlCNnJCLEVBQXFCSCxPQUNyQmIsRUFBZ0IsSUFBTWdCLEVBQXFCSCxLQUFLOTdCLFNBRWhCb1EsSUFBaEM2ckIsRUFBcUI3UyxTQUNyQjhSLEVBQWtCdFksR0FBVXFaLEVBQXFCN1MsT0FBT3hHLElBRTVELE1BQU0ySyxFQUF3QjBPLEVBQXFCMU8sdUJBdEN2RCxTQUEyQzdsQixFQUFRMUgsRUFBWW95QixFQUFnQjZJLEVBQWVDLEVBQWlCcG9CLEVBQWV5YSxHQUMxSHZ0QixFQUFXeXNCLDhCQUFnQy9rQixFQUMzQzFILEVBQVdrdUIsWUFBYSxFQUN4Qmx1QixFQUFXaXVCLFVBQVcsRUFDdEJqdUIsRUFBV29zQixhQUFlLEtBRTFCcHNCLEVBQVc0cUIsT0FBUzVxQixFQUFXNnFCLHFCQUFrQnphLEVBQ2pEMmEsR0FBVy9xQixHQUNYQSxFQUFXd3NCLGlCQUFrQixFQUM3QnhzQixFQUFXOHRCLFVBQVcsRUFDdEI5dEIsRUFBV213QixhQUFlcmQsRUFDMUI5UyxFQUFXbXVCLGVBQWlCOE0sRUFDNUJqN0IsRUFBV290QixpQkFBbUI4TixFQUM5Qmw3QixFQUFXd3RCLHVCQUF5QkQsRUFDcEN2dEIsRUFBVzJyQixrQkFBb0IsSUFBSTFILEVBQ25DdmMsRUFBT3loQiwwQkFBNEJucEIsRUFFbkNpakIsRUFBWVAsRUFEUTBQLE1BQzBCLEtBQzFDcHlCLEVBQVc4dEIsVUFBVyxFQUN0QlosR0FBNkNsdEIsTUFDOUMwM0IsSUFDQ2hMLEdBQWtDMXNCLEVBQVkwM0IsTUFrQmxEd0UsQ0FBa0N4MEIsRUFBUTFILEVBQVlveUIsRUFBZ0I2SSxFQUFlQyxFQUFpQnBvQixFQUFleWEsR0EreEQ3RzRPLENBQXNEaitCLEtBQU0yOUIsRUFEdEM5SyxHQUFxQkMsRUFBVSxRQUdwRCxDQUNELE1BQU1rQixFQUFnQmhCLEdBQXFCRixJQXpPdkQsU0FBa0V0cEIsRUFBUW0wQixFQUFrQi9vQixFQUFlb2YsR0FDdkcsTUFBTWx5QixFQUFhN0IsT0FBT3VCLE9BQU8wNkIsR0FBZ0NyN0IsV0FDakUsSUFBSXF6QixFQUFpQixPQUNqQjZJLEVBQWdCLElBQU12WSxPQUFvQnRTLEdBQzFDOHFCLEVBQWtCLElBQU14WSxPQUFvQnRTLFFBQ2pCQSxJQUEzQnlyQixFQUFpQi95QixRQUNqQnNwQixFQUFpQixJQUFNeUosRUFBaUIveUIsTUFBTTlJLFNBRXBCb1EsSUFBMUJ5ckIsRUFBaUJDLE9BQ2pCYixFQUFnQixJQUFNWSxFQUFpQkMsS0FBSzk3QixTQUVoQm9RLElBQTVCeXJCLEVBQWlCelMsU0FDakI4UixFQUFrQnRZLEdBQVVpWixFQUFpQnpTLE9BQU94RyxJQUV4RG9ZLEdBQXFDdHpCLEVBQVExSCxFQUFZb3lCLEVBQWdCNkksRUFBZUMsRUFBaUJwb0IsRUFBZW9mLEdBNk5oSGtLLENBQXlEbCtCLEtBQU0yOUIsRUFEekM5SyxHQUFxQkMsRUFBVSxHQUMyQ2tCLElBTXhHLGFBQ0ksSUFBS3JLLEdBQWlCM3BCLE1BQ2xCLE1BQU1tK0IsR0FBNEIsVUFFdEMsT0FBTzNULEdBQXVCeHFCLE1BUWxDLE9BQU8wa0IsR0FDSCxPQUFLaUYsR0FBaUIzcEIsTUFHbEJ3cUIsR0FBdUJ4cUIsTUFDaEJ5a0IsRUFBb0IsSUFBSWprQixVQUFVLHFEQUV0Q2tuQixHQUFxQjFuQixLQUFNMGtCLEdBTHZCRCxFQUFvQjBaLEdBQTRCLFdBTy9ELFVBQVVDLEdBQ04sSUFBS3pVLEdBQWlCM3BCLE1BQ2xCLE1BQU1tK0IsR0FBNEIsYUFHdEMsWUFBcUJqc0IsSUFoSDdCLFNBQThCOVAsRUFBUzBtQixHQUNuQ0QsRUFBaUJ6bUIsRUFBUzBtQixHQUMxQixNQUFNd1UsRUFBT2w3QixhQUF5QyxFQUFTQSxFQUFRazdCLEtBQ3ZFLE1BQU8sQ0FDSEEsVUFBZXByQixJQUFUb3JCLE9BQXFCcHJCLEVBQVltckIsR0FBZ0NDLEVBQU0sR0FBR3hVLDZCQTJHaEV1VixDQUFxQkQsRUFBWSxtQkFDckNkLEtBQ0QxVCxFQUFtQzVwQixNQXB6RDNDLElBQUlxeUIsR0FzekRnQ3J5QixNQUUzQyxZQUFZcytCLEVBQWNGLEVBQWEsSUFDbkMsSUFBS3pVLEdBQWlCM3BCLE1BQ2xCLE1BQU1tK0IsR0FBNEIsZUFFdENsVixFQUF1QnFWLEVBQWMsRUFBRyxlQUN4QyxNQUFNQyxFQS9FZCxTQUFxQ3ZmLEVBQU04SixHQUN2Q0QsRUFBaUI3SixFQUFNOEosR0FDdkIsTUFBTTBWLEVBQVd4ZixhQUFtQyxFQUFTQSxFQUFLd2YsU0FDbEVyVixFQUFvQnFWLEVBQVUsV0FBWSx3QkFDMUM5VSxFQUFxQjhVLEVBQVUsR0FBRzFWLGdDQUNsQyxNQUFNbFosRUFBV29QLGFBQW1DLEVBQVNBLEVBQUtwUCxTQUdsRSxPQUZBdVosRUFBb0J2WixFQUFVLFdBQVksd0JBQzFDNGpCLEdBQXFCNWpCLEVBQVUsR0FBR2taLGdDQUMzQixDQUFFMFYsV0FBVTV1QixZQXVFRzZ1QixDQUE0QkgsRUFBYyxtQkFDdERsOEIsRUFBVW03QixHQUFtQmEsRUFBWSxvQkFDL0MsR0FBSTVULEdBQXVCeHFCLE1BQ3ZCLE1BQU0sSUFBSVEsVUFBVSxrRkFFeEIsR0FBSWkwQixHQUF1QjhKLEVBQVUzdUIsVUFDakMsTUFBTSxJQUFJcFAsVUFBVSxrRkFJeEIsT0FEQTZrQixFQURnQnFWLEdBQXFCMTZCLEtBQU11K0IsRUFBVTN1QixTQUFVeE4sRUFBUXU0QixhQUFjdjRCLEVBQVF3NEIsYUFBY3g0QixFQUFRbXBCLGNBQWVucEIsRUFBUWIsU0FFbklnOUIsRUFBVUMsU0FFckIsT0FBT0UsRUFBYU4sRUFBYSxJQUM3QixJQUFLelUsR0FBaUIzcEIsTUFDbEIsT0FBT3lrQixFQUFvQjBaLEdBQTRCLFdBRTNELFFBQW9CanNCLElBQWhCd3NCLEVBQ0EsT0FBT2phLEVBQW9CLHdDQUUvQixJQUFLZ1AsR0FBaUJpTCxHQUNsQixPQUFPamEsRUFBb0IsSUFBSWprQixVQUFVLDhFQUU3QyxJQUFJNEIsRUFDSixJQUNJQSxFQUFVbTdCLEdBQW1CYSxFQUFZLG9CQUU3QyxNQUFPdFQsR0FDSCxPQUFPckcsRUFBb0JxRyxHQUUvQixPQUFJTixHQUF1QnhxQixNQUNoQnlrQixFQUFvQixJQUFJamtCLFVBQVUsOEVBRXpDaTBCLEdBQXVCaUssR0FDaEJqYSxFQUFvQixJQUFJamtCLFVBQVUsOEVBRXRDazZCLEdBQXFCMTZCLEtBQU0wK0IsRUFBYXQ4QixFQUFRdTRCLGFBQWN2NEIsRUFBUXc0QixhQUFjeDRCLEVBQVFtcEIsY0FBZW5wQixFQUFRYixRQWE5SCxNQUNJLElBQUtvb0IsR0FBaUIzcEIsTUFDbEIsTUFBTW0rQixHQUE0QixPQUV0QyxNQUFNUSxFQXBUZCxTQUEyQm4xQixFQUFRbzFCLEdBQy9CLE1BQU10a0IsRUFBU3NQLEVBQW1DcGdCLEdBQ2xELElBR0lxMUIsRUFDQUMsRUFDQUMsRUFDQUMsRUFDQUMsRUFQQUMsR0FBVSxFQUNWQyxHQUFZLEVBQ1pDLEdBQVksRUFNaEIsTUFBTUMsRUFBZ0IvYSxHQUFXM2EsSUFDN0JzMUIsRUFBdUJ0MUIsS0FFM0IsU0FBU296QixJQUNMLE9BQUltQyxJQUdKQSxHQUFVLEVBcUNWdFUsR0FBZ0N0USxFQXBDWixDQUNoQjhQLFlBQWFqcUIsSUFJVG1sQixHQUFlLEtBQ1g0WixHQUFVLEVBQ1YsTUFBTUksRUFBU24vQixFQUNUby9CLEVBQVNwL0IsRUFNVmcvQixHQUNEM0MsR0FBdUN1QyxFQUFROVQsMEJBQTJCcVUsR0FFekVGLEdBQ0Q1QyxHQUF1Q3dDLEVBQVEvVCwwQkFBMkJzVSxHQUU5RU4sT0FBcUIvc0IsT0FHN0JpWSxZQUFhLEtBQ1QrVSxHQUFVLEVBQ0xDLEdBQ0Q1QyxHQUFxQ3dDLEVBQVE5VCwyQkFFNUNtVSxHQUNEN0MsR0FBcUN5QyxFQUFRL1QsNEJBR3JESixZQUFhLEtBQ1RxVSxHQUFVLE1BcENQMWEsT0FBb0J0UyxHQThEbkMsU0FBU2dpQixLQVVULE9BUEE2SyxFQUFVUyxHQUFxQnRMLEVBQWdCNkksR0F2Qi9DLFNBQTBCclksR0FHdEIsR0FGQXlhLEdBQVksRUFDWk4sRUFBVW5hLEVBQ04wYSxFQUFXLENBQ1gsTUFBTUssRUFBa0IzUyxHQUFvQixDQUFDK1IsRUFBU0MsSUFDaERZLEVBQWVoWSxHQUFxQmxlLEVBQVFpMkIsR0FDbERSLEVBQXFCUyxHQUV6QixPQUFPTCxLQWdCWEwsRUFBVVEsR0FBcUJ0TCxFQUFnQjZJLEdBZC9DLFNBQTBCclksR0FHdEIsR0FGQTBhLEdBQVksRUFDWk4sRUFBVXBhLEVBQ055YSxFQUFXLENBQ1gsTUFBTU0sRUFBa0IzUyxHQUFvQixDQUFDK1IsRUFBU0MsSUFDaERZLEVBQWVoWSxHQUFxQmxlLEVBQVFpMkIsR0FDbERSLEVBQXFCUyxHQUV6QixPQUFPTCxLQU9YcGEsRUFBYzNLLEVBQU95TixnQkFBaUJ5UixJQUNsQ2lELEdBQXFDc0MsRUFBUTlULDBCQUEyQnVPLEdBQ3hFaUQsR0FBcUN1QyxFQUFRL1QsMEJBQTJCdU8sR0FDeEV5RixPQUFxQi9zQixNQUVsQixDQUFDNnNCLEVBQVNDLEdBNk5JVyxDQUFrQjMvQixNQUNuQyxPQUFPOHNCLEdBQW9CNlIsR0FFL0IsT0FBT1AsR0FDSCxJQUFLelUsR0FBaUIzcEIsTUFDbEIsTUFBTW0rQixHQUE0QixVQUd0QyxPQWpqRlIsU0FBNEMzMEIsRUFBUStoQixHQUNoRCxNQUFNalIsRUFBU3NQLEVBQW1DcGdCLEdBQzVDbzJCLEVBQU8sSUFBSXRVLEdBQWdDaFIsRUFBUWlSLEdBQ25EeE0sRUFBVzllLE9BQU91QixPQUFPdXFCLElBRS9CLE9BREFoTixFQUFTa04sbUJBQXFCMlQsRUFDdkI3Z0IsRUE0aUZJOGdCLENBQW1DNy9CLEtBdktsRCxTQUFnQ29DLEVBQVMwbUIsR0FDckNELEVBQWlCem1CLEVBcUtzQyxtQkFwS3ZELE1BQU1tcEIsRUFBZ0JucEIsYUFBeUMsRUFBU0EsRUFBUW1wQixjQUNoRixNQUFPLENBQUVBLGNBQWVsYixRQUFRa2IsSUFtS1p1VSxDQUF1QjFCLEdBQ2lCN1MsZ0JBMkJoRSxTQUFTaVUsR0FBcUJ0TCxFQUFnQjZJLEVBQWVDLEVBQWlCcG9CLEVBQWdCLEVBQUdvZixFQUFnQixLQUFNLElBQ25ILE1BQU14cUIsRUFBU3ZKLE9BQU91QixPQUFPdVQsR0FBZWxVLFdBSTVDLE9BSEFpOUIsR0FBeUJ0MEIsR0FFekJzekIsR0FBcUN0ekIsRUFEbEJ2SixPQUFPdUIsT0FBTzA2QixHQUFnQ3I3QixXQUNScXpCLEVBQWdCNkksRUFBZUMsRUFBaUJwb0IsRUFBZW9mLEdBQ2pIeHFCLEVBRVgsU0FBU3MwQixHQUF5QnQwQixHQUM5QkEsRUFBTzJkLE9BQVMsV0FDaEIzZCxFQUFPMGQsYUFBVWhWLEVBQ2pCMUksRUFBT2dlLGtCQUFldFYsRUFDdEIxSSxFQUFPd2hCLFlBQWEsRUFFeEIsU0FBU3JCLEdBQWlCOVksR0FDdEIsUUFBS21ULEVBQWFuVCxNQUdiNVEsT0FBT1ksVUFBVWtxQixlQUFldHBCLEtBQUtvUCxFQUFHLDZCQUtqRCxTQUFTMlosR0FBdUJoaEIsR0FDNUIsWUFBdUIwSSxJQUFuQjFJLEVBQU8wZCxRQU1mLFNBQVNRLEdBQXFCbGUsRUFBUWtiLEdBRWxDLE9BREFsYixFQUFPd2hCLFlBQWEsRUFDRSxXQUFsQnhoQixFQUFPMmQsT0FDQTNDLE9BQW9CdFMsR0FFVCxZQUFsQjFJLEVBQU8yZCxPQUNBMUMsRUFBb0JqYixFQUFPZ2UsZUFFdENrSCxHQUFvQmxsQixHQUViMGIsRUFEcUIxYixFQUFPeWhCLDBCQUEwQjdDLEdBQWExRCxHQUN6QlgsSUFFckQsU0FBUzJLLEdBQW9CbGxCLEdBQ3pCQSxFQUFPMmQsT0FBUyxTQUNoQixNQUFNN00sRUFBUzlRLEVBQU8wZCxhQUNQaFYsSUFBWG9JLElBR0FpUSxHQUE4QmpRLEtBQzlCQSxFQUFPMFAsY0FBY3hmLFNBQVF1ZixJQUN6QkEsRUFBWUksaUJBRWhCN1AsRUFBTzBQLGNBQWdCLElBQUlqRSxHQUUvQnNCLEVBQWtDL00sSUFFdEMsU0FBUzBYLEdBQW9CeG9CLEVBQVFzaEIsR0FDakN0aEIsRUFBTzJkLE9BQVMsVUFDaEIzZCxFQUFPZ2UsYUFBZXNELEVBQ3RCLE1BQU14USxFQUFTOVEsRUFBTzBkLGFBQ1BoVixJQUFYb0ksSUFHQWlRLEdBQThCalEsSUFDOUJBLEVBQU8wUCxjQUFjeGYsU0FBUXVmLElBQ3pCQSxFQUFZYyxZQUFZQyxNQUU1QnhRLEVBQU8wUCxjQUFnQixJQUFJakUsSUFHM0J6TCxFQUFPZ1csa0JBQWtCOWxCLFNBQVE2bEIsSUFDN0JBLEVBQWdCeEYsWUFBWUMsTUFFaEN4USxFQUFPZ1csa0JBQW9CLElBQUl2SyxHQUVuQzZCLEVBQWlDdE4sRUFBUXdRLElBRzdDLFNBQVNxVCxHQUE0Qng2QixHQUNqQyxPQUFPLElBQUluRCxVQUFVLDRCQUE0Qm1ELDBDQUdyRCxTQUFTbzhCLEdBQTJCbmhCLEVBQU1rSyxHQUN0Q0QsRUFBaUJqSyxFQUFNa0ssR0FDdkIsTUFBTWxVLEVBQWdCZ0ssYUFBbUMsRUFBU0EsRUFBS2hLLGNBRXZFLE9BREF1VSxFQUFvQnZVLEVBQWUsZ0JBQWlCLHVCQUM3QyxDQUNIQSxjQUFld1UsRUFBMEJ4VSxJQTlHakQzVSxPQUFPYyxpQkFBaUJnVSxHQUFlbFUsVUFBVyxDQUM5Q3FxQixPQUFRLENBQUVscUIsWUFBWSxHQUN0QnVaLFVBQVcsQ0FBRXZaLFlBQVksR0FDekJnL0IsWUFBYSxDQUFFaC9CLFlBQVksR0FDM0JpL0IsT0FBUSxDQUFFai9CLFlBQVksR0FDdEJrL0IsSUFBSyxDQUFFbC9CLFlBQVksR0FDbkJtVCxPQUFRLENBQUVuVCxZQUFZLEdBQ3RCaTJCLE9BQVEsQ0FBRWoyQixZQUFZLEtBRWdCLGlCQUEvQjZpQixFQUFlM2lCLGFBQ3RCakIsT0FBT0MsZUFBZTZVLEdBQWVsVSxVQUFXZ2pCLEVBQWUzaUIsWUFBYSxDQUN4RWYsTUFBTyxpQkFDUGdCLGNBQWMsSUFHc0IsaUJBQWpDMGlCLEVBQWVzYyxlQUN0QmxnQyxPQUFPQyxlQUFlNlUsR0FBZWxVLFVBQVdnakIsRUFBZXNjLGNBQWUsQ0FDMUVoZ0MsTUFBTzRVLEdBQWVsVSxVQUFVc1QsT0FDaEN2RSxVQUFVLEVBQ1Z6TyxjQUFjLElBK0Z0QixNQUFNaS9CLEdBQXlCLFNBQWN0MkIsR0FDekMsT0FBT0EsRUFBTXNKLFlBT2pCLE1BQU1pdEIsR0FDRixZQUFZaitCLEdBQ1I2bUIsRUFBdUI3bUIsRUFBUyxFQUFHLDZCQUNuQ0EsRUFBVTI5QixHQUEyQjM5QixFQUFTLG1CQUM5Q3BDLEtBQUtzZ0Msd0NBQTBDbCtCLEVBQVF3UyxjQUszRCxvQkFDSSxJQUFLMnJCLEdBQTRCdmdDLE1BQzdCLE1BQU13Z0MsR0FBOEIsaUJBRXhDLE9BQU94Z0MsS0FBS3NnQyx3Q0FLaEIsV0FDSSxJQUFLQyxHQUE0QnZnQyxNQUM3QixNQUFNd2dDLEdBQThCLFFBRXhDLE9BQU9KLElBY2YsU0FBU0ksR0FBOEI3OEIsR0FDbkMsT0FBTyxJQUFJbkQsVUFBVSx1Q0FBdUNtRCxxREFFaEUsU0FBUzQ4QixHQUE0QjF2QixHQUNqQyxRQUFLbVQsRUFBYW5ULE1BR2I1USxPQUFPWSxVQUFVa3FCLGVBQWV0cEIsS0FBS29QLEVBQUcsMkNBbEJqRDVRLE9BQU9jLGlCQUFpQnMvQixHQUEwQngvQixVQUFXLENBQ3pEK1QsY0FBZSxDQUFFNVQsWUFBWSxHQUM3QjhSLEtBQU0sQ0FBRTlSLFlBQVksS0FFa0IsaUJBQS9CNmlCLEVBQWUzaUIsYUFDdEJqQixPQUFPQyxlQUFlbWdDLEdBQTBCeC9CLFVBQVdnakIsRUFBZTNpQixZQUFhLENBQ25GZixNQUFPLDRCQUNQZ0IsY0FBYyxJQWlCdEIsTUFBTXMvQixHQUFvQixXQUN0QixPQUFPLEdBT1gsTUFBTUMsR0FDRixZQUFZdCtCLEdBQ1I2bUIsRUFBdUI3bUIsRUFBUyxFQUFHLHdCQUNuQ0EsRUFBVTI5QixHQUEyQjM5QixFQUFTLG1CQUM5Q3BDLEtBQUsyZ0MsbUNBQXFDditCLEVBQVF3UyxjQUt0RCxvQkFDSSxJQUFLZ3NCLEdBQXVCNWdDLE1BQ3hCLE1BQU02Z0MsR0FBeUIsaUJBRW5DLE9BQU83Z0MsS0FBSzJnQyxtQ0FNaEIsV0FDSSxJQUFLQyxHQUF1QjVnQyxNQUN4QixNQUFNNmdDLEdBQXlCLFFBRW5DLE9BQU9KLElBY2YsU0FBU0ksR0FBeUJsOUIsR0FDOUIsT0FBTyxJQUFJbkQsVUFBVSxrQ0FBa0NtRCxnREFFM0QsU0FBU2k5QixHQUF1Qi92QixHQUM1QixRQUFLbVQsRUFBYW5ULE1BR2I1USxPQUFPWSxVQUFVa3FCLGVBQWV0cEIsS0FBS29QLEVBQUcsc0NBMkJqRCxTQUFTaXdCLEdBQWdDaG9CLEVBQUlzYSxFQUFVdEssR0FFbkQsT0FEQUMsRUFBZWpRLEVBQUlnUSxHQUNYaG5CLEdBQWVna0IsRUFBWWhOLEVBQUlzYSxFQUFVLENBQUN0eEIsSUFFdEQsU0FBU2kvQixHQUFnQ2pvQixFQUFJc2EsRUFBVXRLLEdBRW5ELE9BREFDLEVBQWVqUSxFQUFJZ1EsR0FDWGhuQixHQUFlMmpCLEVBQVkzTSxFQUFJc2EsRUFBVSxDQUFDdHhCLElBRXRELFNBQVNrL0IsR0FBb0Nsb0IsRUFBSXNhLEVBQVV0SyxHQUV2RCxPQURBQyxFQUFlalEsRUFBSWdRLEdBQ1osQ0FBQ2hmLEVBQU9oSSxJQUFlZ2tCLEVBQVloTixFQUFJc2EsRUFBVSxDQUFDdHBCLEVBQU9oSSxJQXZEcEU3QixPQUFPYyxpQkFBaUIyL0IsR0FBcUI3L0IsVUFBVyxDQUNwRCtULGNBQWUsQ0FBRTVULFlBQVksR0FDN0I4UixLQUFNLENBQUU5UixZQUFZLEtBRWtCLGlCQUEvQjZpQixFQUFlM2lCLGFBQ3RCakIsT0FBT0MsZUFBZXdnQyxHQUFxQjcvQixVQUFXZ2pCLEVBQWUzaUIsWUFBYSxDQUM5RWYsTUFBTyx1QkFDUGdCLGNBQWMsSUE0RHRCLE1BQU04L0IsR0FDRixZQUFZQyxFQUFpQixHQUFJQyxFQUFzQixHQUFJQyxFQUFzQixTQUN0RGx2QixJQUFuQmd2QixJQUNBQSxFQUFpQixNQUVyQixNQUFNRyxFQUFtQnBPLEdBQXVCa08sRUFBcUIsb0JBQy9ERyxFQUFtQnJPLEdBQXVCbU8sRUFBcUIsbUJBQy9ERyxFQWxEZCxTQUE0Qm5PLEVBQVV0SyxHQUNsQ0QsRUFBaUJ1SyxFQUFVdEssR0FDM0IsTUFBTTdGLEVBQVFtUSxhQUEyQyxFQUFTQSxFQUFTblEsTUFDckV1ZSxFQUFlcE8sYUFBMkMsRUFBU0EsRUFBU29PLGFBQzVFNTJCLEVBQVF3b0IsYUFBMkMsRUFBU0EsRUFBU3hvQixNQUNyRTJ6QixFQUFZbkwsYUFBMkMsRUFBU0EsRUFBU21MLFVBQ3pFa0QsRUFBZXJPLGFBQTJDLEVBQVNBLEVBQVNxTyxhQUNsRixNQUFPLENBQ0h4ZSxXQUFpQi9RLElBQVYrUSxPQUNIL1EsRUFDQTR1QixHQUFnQzdkLEVBQU9tUSxFQUFVLEdBQUd0Syw2QkFDeEQwWSxlQUNBNTJCLFdBQWlCc0gsSUFBVnRILE9BQ0hzSCxFQUNBNnVCLEdBQWdDbjJCLEVBQU93b0IsRUFBVSxHQUFHdEssNkJBQ3hEeVYsZUFBeUJyc0IsSUFBZHFzQixPQUNQcnNCLEVBQ0E4dUIsR0FBb0N6QyxFQUFXbkwsRUFBVSxHQUFHdEssaUNBQ2hFMlksZ0JBZ0NvQkMsQ0FBbUJSLEVBQWdCLG1CQUN2RCxRQUFpQ2h2QixJQUE3QnF2QixFQUFZQyxhQUNaLE1BQU0sSUFBSXpvQixXQUFXLGtDQUV6QixRQUFpQzdHLElBQTdCcXZCLEVBQVlFLGFBQ1osTUFBTSxJQUFJMW9CLFdBQVcsa0NBRXpCLE1BQU00b0IsRUFBd0I5TyxHQUFxQnlPLEVBQWtCLEdBQy9ETSxFQUF3QjVPLEdBQXFCc08sR0FDN0NPLEVBQXdCaFAsR0FBcUJ3TyxFQUFrQixHQUMvRFMsRUFBd0I5TyxHQUFxQnFPLEdBQ25ELElBQUlVLEdBMENaLFNBQW1DdjRCLEVBQVF3NEIsRUFBY0gsRUFBdUJDLEVBQXVCSCxFQUF1QkMsR0FDMUgsU0FBUzFOLElBQ0wsT0FBTzhOLEVBV1h4NEIsRUFBT3k0QixVQTUzRFgsU0FBOEIvTixFQUFnQkMsRUFBZ0JDLEVBQWdCQyxFQUFnQnpmLEVBQWdCLEVBQUdvZixFQUFnQixLQUFNLElBQ25JLE1BQU14cUIsRUFBU3ZKLE9BQU91QixPQUFPa3lCLEdBQWU3eUIsV0FJNUMsT0FIQWt6QixHQUF5QnZxQixHQUV6QjhxQixHQUFxQzlxQixFQURsQnZKLE9BQU91QixPQUFPeXlCLEdBQWdDcHpCLFdBQ1JxekIsRUFBZ0JDLEVBQWdCQyxFQUFnQkMsRUFBZ0J6ZixFQUFlb2YsR0FDakl4cUIsRUF1M0RZMDRCLENBQXFCaE8sR0FUeEMsU0FBd0JwcUIsR0FDcEIsT0FvTVIsU0FBa0ROLEVBQVFNLEdBQ3RELE1BQU1oSSxFQUFhMEgsRUFBTzI0QiwyQkFDMUIsT0FBSTM0QixFQUFPOHJCLGNBRUFwUSxFQUQyQjFiLEVBQU80NEIsNEJBQ2MsS0FDbkQsTUFBTXh5QixFQUFXcEcsRUFBT3k0QixVQUV4QixHQUFjLGFBREFyeUIsRUFBU3VYLE9BRW5CLE1BQU12WCxFQUFTNFgsYUFFbkIsT0FBTzZhLEdBQWlEdmdDLEVBQVlnSSxNQUdyRXU0QixHQUFpRHZnQyxFQUFZZ0ksR0FqTnpEdzRCLENBQXlDOTRCLEVBQVFNLE1BSzVELFdBQ0ksT0FtTlIsU0FBa0ROLEdBRTlDLE1BQU1nMUIsRUFBV2gxQixFQUFPKzRCLFVBQ2xCemdDLEVBQWEwSCxFQUFPMjRCLDJCQUNwQkssRUFBZTFnQyxFQUFXMmdDLGtCQUdoQyxPQUZBQyxHQUFnRDVnQyxHQUV6Q29qQixFQUFxQnNkLEdBQWMsS0FDdEMsR0FBd0IsWUFBcEJoRSxFQUFTclgsT0FDVCxNQUFNcVgsRUFBU2hYLGFBRW5CK1UsR0FBcUNpQyxFQUFTdlQsOEJBQy9DdU8sSUFFQyxNQURBbUosR0FBcUJuNUIsRUFBUWd3QixHQUN2QmdGLEVBQVNoWCxnQkFqT1JvYixDQUF5Q3A1QixNQUpwRCxTQUF3QmtiLEdBQ3BCLE9BZ05SLFNBQWtEbGIsRUFBUWtiLEdBSXRELE9BREFpZSxHQUFxQm41QixFQUFRa2IsR0FDdEJGLE9BQW9CdFMsR0FwTmhCMndCLENBQXlDcjVCLEVBQVFrYixLQUs0Q21kLEVBQXVCQyxHQVEvSHQ0QixFQUFPKzRCLFVBQVkvQyxHQUFxQnRMLEdBUHhDLFdBQ0ksT0FpT1IsU0FBbUQxcUIsR0FJL0MsT0FGQXM1QixHQUErQnQ1QixHQUFRLEdBRWhDQSxFQUFPNDRCLDJCQXJPSFcsQ0FBMEN2NUIsTUFFckQsU0FBeUJrYixHQUVyQixPQURBc2UsR0FBNEN4NUIsRUFBUWtiLEdBQzdDRixPQUFvQnRTLEtBRXlEeXZCLEVBQXVCQyxHQUUvR3A0QixFQUFPOHJCLG1CQUFnQnBqQixFQUN2QjFJLEVBQU80NEIsZ0NBQTZCbHdCLEVBQ3BDMUksRUFBT3k1Qix3Q0FBcUMvd0IsRUFDNUM0d0IsR0FBK0J0NUIsR0FBUSxHQUN2Q0EsRUFBTzI0QixnQ0FBNkJqd0IsRUFqRWhDZ3hCLENBQTBCbGpDLEtBSExza0IsR0FBVzNhLElBQzVCbzRCLEVBQXVCcDRCLEtBRW1CazRCLEVBQXVCQyxFQUF1QkgsRUFBdUJDLEdBZ0wzSCxTQUE4RHA0QixFQUFRKzNCLEdBQ2xFLE1BQU16L0IsRUFBYTdCLE9BQU91QixPQUFPMmhDLEdBQWlDdGlDLFdBQ2xFLElBQUl1aUMsRUFBc0J0NUIsSUFDdEIsSUFFSSxPQURBdTVCLEdBQXdDdmhDLEVBQVlnSSxHQUM3QzBhLE9BQW9CdFMsR0FFL0IsTUFBT294QixHQUNILE9BQU83ZSxFQUFvQjZlLEtBRy9CQyxFQUFpQixJQUFNL2UsT0FBb0J0UyxRQUNqQkEsSUFBMUJxdkIsRUFBWWhELFlBQ1o2RSxFQUFxQnQ1QixHQUFTeTNCLEVBQVloRCxVQUFVejBCLEVBQU9oSSxTQUVyQ29RLElBQXRCcXZCLEVBQVl0ZSxRQUNac2dCLEVBQWlCLElBQU1oQyxFQUFZdGUsTUFBTW5oQixJQXRCakQsU0FBK0MwSCxFQUFRMUgsRUFBWXNoQyxFQUFvQkcsR0FDbkZ6aEMsRUFBVzBoQywyQkFBNkJoNkIsRUFDeENBLEVBQU8yNEIsMkJBQTZCcmdDLEVBQ3BDQSxFQUFXMmhDLG9CQUFzQkwsRUFDakN0aEMsRUFBVzJnQyxnQkFBa0JjLEVBb0I3QkcsQ0FBc0NsNkIsRUFBUTFILEVBQVlzaEMsRUFBb0JHLEdBak0xRUksQ0FBcUQzakMsS0FBTXVoQyxRQUNqQ3J2QixJQUF0QnF2QixFQUFZMzJCLE1BQ1ptM0IsRUFBcUJSLEVBQVkzMkIsTUFBTTVLLEtBQUttaUMsNkJBRzVDSixPQUFxQjd2QixHQU03QixlQUNJLElBQUsweEIsR0FBa0I1akMsTUFDbkIsTUFBTTZqQyxHQUE0QixZQUV0QyxPQUFPN2pDLEtBQUt1aUMsVUFLaEIsZUFDSSxJQUFLcUIsR0FBa0I1akMsTUFDbkIsTUFBTTZqQyxHQUE0QixZQUV0QyxPQUFPN2pDLEtBQUtpaUMsV0EwQ3BCLFNBQVMyQixHQUFrQi95QixHQUN2QixRQUFLbVQsRUFBYW5ULE1BR2I1USxPQUFPWSxVQUFVa3FCLGVBQWV0cEIsS0FBS29QLEVBQUcsOEJBTWpELFNBQVM4eEIsR0FBcUJuNUIsRUFBUXNoQixHQUNsQzJSLEdBQXFDanpCLEVBQU8rNEIsVUFBVXRYLDBCQUEyQkgsR0FDakZrWSxHQUE0Q3g1QixFQUFRc2hCLEdBRXhELFNBQVNrWSxHQUE0Q3g1QixFQUFRc2hCLEdBQ3pENFgsR0FBZ0RsNUIsRUFBTzI0Qiw0QkFDdkR2SixHQUE2Q3B2QixFQUFPeTRCLFVBQVVqTiwwQkFBMkJsSyxHQUNyRnRoQixFQUFPOHJCLGVBSVB3TixHQUErQnQ1QixHQUFRLEdBRy9DLFNBQVNzNUIsR0FBK0J0NUIsRUFBUXF0QixRQUVGM2tCLElBQXRDMUksRUFBTzQ0Qiw0QkFDUDU0QixFQUFPeTVCLHFDQUVYejVCLEVBQU80NEIsMkJBQTZCOWQsR0FBVzNhLElBQzNDSCxFQUFPeTVCLG1DQUFxQ3Q1QixLQUVoREgsRUFBTzhyQixjQUFnQnVCLEVBdkUzQjUyQixPQUFPYyxpQkFBaUJrZ0MsR0FBZ0JwZ0MsVUFBVyxDQUMvQzI5QixTQUFVLENBQUV4OUIsWUFBWSxHQUN4QjRPLFNBQVUsQ0FBRTVPLFlBQVksS0FFYyxpQkFBL0I2aUIsRUFBZTNpQixhQUN0QmpCLE9BQU9DLGVBQWUrZ0MsR0FBZ0JwZ0MsVUFBV2dqQixFQUFlM2lCLFlBQWEsQ0FDekVmLE1BQU8sa0JBQ1BnQixjQUFjLElBd0V0QixNQUFNZ2lDLEdBQ0YsY0FDSSxNQUFNLElBQUkzaUMsVUFBVSx1QkFLeEIsa0JBQ0ksSUFBS3NqQyxHQUFtQzlqQyxNQUNwQyxNQUFNK2pDLEdBQXVDLGVBR2pELE9BQU8xSCxHQURvQnI4QixLQUFLd2pDLDJCQUEyQmpCLFVBQVV0WCwyQkFHekUsUUFBUW5oQixHQUNKLElBQUtnNkIsR0FBbUM5akMsTUFDcEMsTUFBTStqQyxHQUF1QyxXQUVqRFYsR0FBd0NyakMsS0FBTThKLEdBTWxELE1BQU00YSxHQUNGLElBQUtvZixHQUFtQzlqQyxNQUNwQyxNQUFNK2pDLEdBQXVDLFNBd0Z6RCxJQUEyRGpaLElBdEZQcEcsRUF1RmhEaWUsR0F2RjBDM2lDLEtBdUZWd2pDLDJCQUE0QjFZLEdBakY1RCxZQUNJLElBQUtnWixHQUFtQzlqQyxNQUNwQyxNQUFNK2pDLEdBQXVDLGNBd0Z6RCxTQUFtRGppQyxHQUMvQyxNQUFNMEgsRUFBUzFILEVBQVcwaEMsMkJBRTFCakgsR0FEMkIveUIsRUFBTys0QixVQUFVdFgsMkJBRzVDK1gsR0FBNEN4NUIsRUFEOUIsSUFBSWhKLFVBQVUsK0JBMUZ4QndqQyxDQUEwQ2hrQyxPQWdCbEQsU0FBUzhqQyxHQUFtQ2p6QixHQUN4QyxRQUFLbVQsRUFBYW5ULE1BR2I1USxPQUFPWSxVQUFVa3FCLGVBQWV0cEIsS0FBS29QLEVBQUcsOEJBK0JqRCxTQUFTNnhCLEdBQWdENWdDLEdBQ3JEQSxFQUFXMmhDLHlCQUFzQnZ4QixFQUNqQ3BRLEVBQVcyZ0MscUJBQWtCdndCLEVBRWpDLFNBQVNteEIsR0FBd0N2aEMsRUFBWWdJLEdBQ3pELE1BQU1OLEVBQVMxSCxFQUFXMGhDLDJCQUNwQlMsRUFBcUJ6NkIsRUFBTys0QixVQUFVdFgsMEJBQzVDLElBQUtxUixHQUFpRDJILEdBQ2xELE1BQU0sSUFBSXpqQyxVQUFVLHdEQUl4QixJQUNJZzhCLEdBQXVDeUgsRUFBb0JuNkIsR0FFL0QsTUFBT2doQixHQUdILE1BREFrWSxHQUE0Q3g1QixFQUFRc2hCLEdBQzlDdGhCLEVBQU8rNEIsVUFBVS9hLGNBbjNCL0IsU0FBd0QxbEIsR0FDcEQsT0FBSSs2QixHQUE4Qy82QixJQW8zQjdCb2lDLENBQStDRCxLQUMvQ3o2QixFQUFPOHJCLGVBQ3hCd04sR0FBK0J0NUIsR0FBUSxHQU0vQyxTQUFTNjRCLEdBQWlEdmdDLEVBQVlnSSxHQUVsRSxPQUFPb2IsRUFEa0JwakIsRUFBVzJoQyxvQkFBb0IzNUIsUUFDVm9JLEdBQVdzbkIsSUFFckQsTUFEQW1KLEdBQXFCN2dDLEVBQVcwaEMsMkJBQTRCaEssR0FDdERBLEtBeURkLFNBQVN1SyxHQUF1Q3BnQyxHQUM1QyxPQUFPLElBQUluRCxVQUFVLDhDQUE4Q21ELDREQUd2RSxTQUFTa2dDLEdBQTRCbGdDLEdBQ2pDLE9BQU8sSUFBSW5ELFVBQVUsNkJBQTZCbUQsMkNBOUl0RDFELE9BQU9jLGlCQUFpQm9pQyxHQUFpQ3RpQyxVQUFXLENBQ2hFOFosUUFBUyxDQUFFM1osWUFBWSxHQUN2QnVGLE1BQU8sQ0FBRXZGLFlBQVksR0FDckJtakMsVUFBVyxDQUFFbmpDLFlBQVksR0FDekJveEIsWUFBYSxDQUFFcHhCLFlBQVksS0FFVyxpQkFBL0I2aUIsRUFBZTNpQixhQUN0QmpCLE9BQU9DLGVBQWVpakMsR0FBaUN0aUMsVUFBV2dqQixFQUFlM2lCLFlBQWEsQ0FDMUZmLE1BQU8sbUNBQ1BnQixjQUFjLEsscUJDdmxIdEJ0QixFQUFPRCxRQUFVd2tDLFFBQVEsVyxxQkNBekJ2a0MsRUFBT0QsUUFBVXdrQyxRQUFRLFMscUJDQXpCdmtDLEVBQU9ELFFBQVV3a0MsUUFBUSxVLHFCQ0F6QnZrQyxFQUFPRCxRQUFVd2tDLFFBQVEsVyxxQkNBekJ2a0MsRUFBT0QsUUFBVXdrQyxRQUFRLFEscUJDQXpCdmtDLEVBQU9ELFFBQVV3a0MsUUFBUSxTLHFCQ0F6QnZrQyxFQUFPRCxRQUFVd2tDLFFBQVEsVUNDckJDLEVBQTJCLEdBRy9CLFNBQVNDLEVBQW9CQyxHQUU1QixHQUFHRixFQUF5QkUsR0FDM0IsT0FBT0YsRUFBeUJFLEdBQVUza0MsUUFHM0MsSUFBSUMsRUFBU3drQyxFQUF5QkUsR0FBWSxDQUdqRDNrQyxRQUFTLElBT1YsT0FIQTRrQyxFQUFvQkQsR0FBVTlpQyxLQUFLNUIsRUFBT0QsUUFBU0MsRUFBUUEsRUFBT0QsUUFBUzBrQyxHQUdwRXprQyxFQUFPRCxRQ2pCZixPQ0ZBMGtDLEVBQW9CbDRCLEVBQUksQ0FBQ3hNLEVBQVM2a0MsS0FDakMsSUFBSSxJQUFJaGlDLEtBQU9naUMsRUFDWEgsRUFBb0JJLEVBQUVELEVBQVloaUMsS0FBUzZoQyxFQUFvQkksRUFBRTlrQyxFQUFTNkMsSUFDNUV4QyxPQUFPQyxlQUFlTixFQUFTNkMsRUFBSyxDQUFFekIsWUFBWSxFQUFNTCxJQUFLOGpDLEVBQVdoaUMsTUNKM0U2aEMsRUFBb0JJLEVBQUksQ0FBQ2g2QixFQUFLaTZCLElBQVMxa0MsT0FBT1ksVUFBVWtxQixlQUFldHBCLEtBQUtpSixFQUFLaTZCLEdDQ2pGTCxFQUFvQjlLLEVBQUs1NUIsSUFDSCxvQkFBWHFCLFFBQTBCQSxPQUFPQyxhQUMxQ2pCLE9BQU9DLGVBQWVOLEVBQVNxQixPQUFPQyxZQUFhLENBQUVmLE1BQU8sV0FFN0RGLE9BQU9DLGVBQWVOLEVBQVMsYUFBYyxDQUFFTyxPQUFPLEtIRmhEbWtDLEVBQW9CLE0iLCJmaWxlIjoibWFpbGd1bi5qcyIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiB3ZWJwYWNrVW5pdmVyc2FsTW9kdWxlRGVmaW5pdGlvbihyb290LCBmYWN0b3J5KSB7XG5cdGlmKHR5cGVvZiBleHBvcnRzID09PSAnb2JqZWN0JyAmJiB0eXBlb2YgbW9kdWxlID09PSAnb2JqZWN0Jylcblx0XHRtb2R1bGUuZXhwb3J0cyA9IGZhY3RvcnkoKTtcblx0ZWxzZSBpZih0eXBlb2YgZGVmaW5lID09PSAnZnVuY3Rpb24nICYmIGRlZmluZS5hbWQpXG5cdFx0ZGVmaW5lKFtdLCBmYWN0b3J5KTtcblx0ZWxzZSBpZih0eXBlb2YgZXhwb3J0cyA9PT0gJ29iamVjdCcpXG5cdFx0ZXhwb3J0c1tcIm1haWxndW5cIl0gPSBmYWN0b3J5KCk7XG5cdGVsc2Vcblx0XHRyb290W1wibWFpbGd1blwiXSA9IGZhY3RvcnkoKTtcbn0pKHRoaXMsIGZ1bmN0aW9uKCkge1xucmV0dXJuICIsIi8qKlxuICogQGF1dGhvciBUb3J1IE5hZ2FzaGltYSA8aHR0cHM6Ly9naXRodWIuY29tL215c3RpY2F0ZWE+XG4gKiBTZWUgTElDRU5TRSBmaWxlIGluIHJvb3QgZGlyZWN0b3J5IGZvciBmdWxsIGxpY2Vuc2UuXG4gKi9cbid1c2Ugc3RyaWN0JztcblxuT2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcblxudmFyIGV2ZW50VGFyZ2V0U2hpbSA9IHJlcXVpcmUoJ2V2ZW50LXRhcmdldC1zaGltJyk7XG5cbi8qKlxuICogVGhlIHNpZ25hbCBjbGFzcy5cbiAqIEBzZWUgaHR0cHM6Ly9kb20uc3BlYy53aGF0d2cub3JnLyNhYm9ydHNpZ25hbFxuICovXG5jbGFzcyBBYm9ydFNpZ25hbCBleHRlbmRzIGV2ZW50VGFyZ2V0U2hpbS5FdmVudFRhcmdldCB7XG4gICAgLyoqXG4gICAgICogQWJvcnRTaWduYWwgY2Fubm90IGJlIGNvbnN0cnVjdGVkIGRpcmVjdGx5LlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICBzdXBlcigpO1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiQWJvcnRTaWduYWwgY2Fubm90IGJlIGNvbnN0cnVjdGVkIGRpcmVjdGx5XCIpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIGB0cnVlYCBpZiB0aGlzIGBBYm9ydFNpZ25hbGAncyBgQWJvcnRDb250cm9sbGVyYCBoYXMgc2lnbmFsZWQgdG8gYWJvcnQsIGFuZCBgZmFsc2VgIG90aGVyd2lzZS5cbiAgICAgKi9cbiAgICBnZXQgYWJvcnRlZCgpIHtcbiAgICAgICAgY29uc3QgYWJvcnRlZCA9IGFib3J0ZWRGbGFncy5nZXQodGhpcyk7XG4gICAgICAgIGlmICh0eXBlb2YgYWJvcnRlZCAhPT0gXCJib29sZWFuXCIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoYEV4cGVjdGVkICd0aGlzJyB0byBiZSBhbiAnQWJvcnRTaWduYWwnIG9iamVjdCwgYnV0IGdvdCAke3RoaXMgPT09IG51bGwgPyBcIm51bGxcIiA6IHR5cGVvZiB0aGlzfWApO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBhYm9ydGVkO1xuICAgIH1cbn1cbmV2ZW50VGFyZ2V0U2hpbS5kZWZpbmVFdmVudEF0dHJpYnV0ZShBYm9ydFNpZ25hbC5wcm90b3R5cGUsIFwiYWJvcnRcIik7XG4vKipcbiAqIENyZWF0ZSBhbiBBYm9ydFNpZ25hbCBvYmplY3QuXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZUFib3J0U2lnbmFsKCkge1xuICAgIGNvbnN0IHNpZ25hbCA9IE9iamVjdC5jcmVhdGUoQWJvcnRTaWduYWwucHJvdG90eXBlKTtcbiAgICBldmVudFRhcmdldFNoaW0uRXZlbnRUYXJnZXQuY2FsbChzaWduYWwpO1xuICAgIGFib3J0ZWRGbGFncy5zZXQoc2lnbmFsLCBmYWxzZSk7XG4gICAgcmV0dXJuIHNpZ25hbDtcbn1cbi8qKlxuICogQWJvcnQgYSBnaXZlbiBzaWduYWwuXG4gKi9cbmZ1bmN0aW9uIGFib3J0U2lnbmFsKHNpZ25hbCkge1xuICAgIGlmIChhYm9ydGVkRmxhZ3MuZ2V0KHNpZ25hbCkgIT09IGZhbHNlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgYWJvcnRlZEZsYWdzLnNldChzaWduYWwsIHRydWUpO1xuICAgIHNpZ25hbC5kaXNwYXRjaEV2ZW50KHsgdHlwZTogXCJhYm9ydFwiIH0pO1xufVxuLyoqXG4gKiBBYm9ydGVkIGZsYWcgZm9yIGVhY2ggaW5zdGFuY2VzLlxuICovXG5jb25zdCBhYm9ydGVkRmxhZ3MgPSBuZXcgV2Vha01hcCgpO1xuLy8gUHJvcGVydGllcyBzaG91bGQgYmUgZW51bWVyYWJsZS5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKEFib3J0U2lnbmFsLnByb3RvdHlwZSwge1xuICAgIGFib3J0ZWQ6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxufSk7XG4vLyBgdG9TdHJpbmcoKWAgc2hvdWxkIHJldHVybiBgXCJbb2JqZWN0IEFib3J0U2lnbmFsXVwiYFxuaWYgKHR5cGVvZiBTeW1ib2wgPT09IFwiZnVuY3Rpb25cIiAmJiB0eXBlb2YgU3ltYm9sLnRvU3RyaW5nVGFnID09PSBcInN5bWJvbFwiKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KEFib3J0U2lnbmFsLnByb3RvdHlwZSwgU3ltYm9sLnRvU3RyaW5nVGFnLCB7XG4gICAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICAgICAgdmFsdWU6IFwiQWJvcnRTaWduYWxcIixcbiAgICB9KTtcbn1cblxuLyoqXG4gKiBUaGUgQWJvcnRDb250cm9sbGVyLlxuICogQHNlZSBodHRwczovL2RvbS5zcGVjLndoYXR3Zy5vcmcvI2Fib3J0Y29udHJvbGxlclxuICovXG5jbGFzcyBBYm9ydENvbnRyb2xsZXIge1xuICAgIC8qKlxuICAgICAqIEluaXRpYWxpemUgdGhpcyBjb250cm9sbGVyLlxuICAgICAqL1xuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICBzaWduYWxzLnNldCh0aGlzLCBjcmVhdGVBYm9ydFNpZ25hbCgpKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgYEFib3J0U2lnbmFsYCBvYmplY3QgYXNzb2NpYXRlZCB3aXRoIHRoaXMgb2JqZWN0LlxuICAgICAqL1xuICAgIGdldCBzaWduYWwoKSB7XG4gICAgICAgIHJldHVybiBnZXRTaWduYWwodGhpcyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIEFib3J0IGFuZCBzaWduYWwgdG8gYW55IG9ic2VydmVycyB0aGF0IHRoZSBhc3NvY2lhdGVkIGFjdGl2aXR5IGlzIHRvIGJlIGFib3J0ZWQuXG4gICAgICovXG4gICAgYWJvcnQoKSB7XG4gICAgICAgIGFib3J0U2lnbmFsKGdldFNpZ25hbCh0aGlzKSk7XG4gICAgfVxufVxuLyoqXG4gKiBBc3NvY2lhdGVkIHNpZ25hbHMuXG4gKi9cbmNvbnN0IHNpZ25hbHMgPSBuZXcgV2Vha01hcCgpO1xuLyoqXG4gKiBHZXQgdGhlIGFzc29jaWF0ZWQgc2lnbmFsIG9mIGEgZ2l2ZW4gY29udHJvbGxlci5cbiAqL1xuZnVuY3Rpb24gZ2V0U2lnbmFsKGNvbnRyb2xsZXIpIHtcbiAgICBjb25zdCBzaWduYWwgPSBzaWduYWxzLmdldChjb250cm9sbGVyKTtcbiAgICBpZiAoc2lnbmFsID09IG51bGwpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihgRXhwZWN0ZWQgJ3RoaXMnIHRvIGJlIGFuICdBYm9ydENvbnRyb2xsZXInIG9iamVjdCwgYnV0IGdvdCAke2NvbnRyb2xsZXIgPT09IG51bGwgPyBcIm51bGxcIiA6IHR5cGVvZiBjb250cm9sbGVyfWApO1xuICAgIH1cbiAgICByZXR1cm4gc2lnbmFsO1xufVxuLy8gUHJvcGVydGllcyBzaG91bGQgYmUgZW51bWVyYWJsZS5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKEFib3J0Q29udHJvbGxlci5wcm90b3R5cGUsIHtcbiAgICBzaWduYWw6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIGFib3J0OiB7IGVudW1lcmFibGU6IHRydWUgfSxcbn0pO1xuaWYgKHR5cGVvZiBTeW1ib2wgPT09IFwiZnVuY3Rpb25cIiAmJiB0eXBlb2YgU3ltYm9sLnRvU3RyaW5nVGFnID09PSBcInN5bWJvbFwiKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KEFib3J0Q29udHJvbGxlci5wcm90b3R5cGUsIFN5bWJvbC50b1N0cmluZ1RhZywge1xuICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgIHZhbHVlOiBcIkFib3J0Q29udHJvbGxlclwiLFxuICAgIH0pO1xufVxuXG5leHBvcnRzLkFib3J0Q29udHJvbGxlciA9IEFib3J0Q29udHJvbGxlcjtcbmV4cG9ydHMuQWJvcnRTaWduYWwgPSBBYm9ydFNpZ25hbDtcbmV4cG9ydHMuZGVmYXVsdCA9IEFib3J0Q29udHJvbGxlcjtcblxubW9kdWxlLmV4cG9ydHMgPSBBYm9ydENvbnRyb2xsZXJcbm1vZHVsZS5leHBvcnRzLkFib3J0Q29udHJvbGxlciA9IG1vZHVsZS5leHBvcnRzW1wiZGVmYXVsdFwiXSA9IEFib3J0Q29udHJvbGxlclxubW9kdWxlLmV4cG9ydHMuQWJvcnRTaWduYWwgPSBBYm9ydFNpZ25hbFxuLy8jIHNvdXJjZU1hcHBpbmdVUkw9YWJvcnQtY29udHJvbGxlci5qcy5tYXBcbiIsImltcG9ydCBDbGllbnQgZnJvbSAnLi9saWIvY2xpZW50J1xuaW1wb3J0IE9wdGlvbnMgZnJvbSAnLi9saWIvaW50ZXJmYWNlcy9PcHRpb25zJztcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgTWFpbGd1biB7XG4gIHByaXZhdGUgZm9ybURhdGE6IG5ldyAoKSA9PiBGb3JtRGF0YVxuXG4gIGNvbnN0cnVjdG9yKEZvcm1EYXRhOiBuZXcgKCkgPT4gRm9ybURhdGEpIHtcbiAgICB0aGlzLmZvcm1EYXRhID0gRm9ybURhdGE7XG4gIH1cblxuICBjbGllbnQob3B0aW9uczogT3B0aW9ucykge1xuICAgIHJldHVybiBuZXcgQ2xpZW50KG9wdGlvbnMsIHRoaXMuZm9ybURhdGEpXG4gIH1cbn07IiwiaW1wb3J0IFJlcXVlc3QgZnJvbSAnLi9yZXF1ZXN0JztcbmltcG9ydCBPcHRpb25zIGZyb20gJy4vaW50ZXJmYWNlcy9PcHRpb25zJztcbmltcG9ydCBSZXF1ZXN0T3B0aW9ucyBmcm9tICcuL2ludGVyZmFjZXMvUmVxdWVzdE9wdGlvbnMnO1xuXG5pbXBvcnQgRG9tYWluQ2xpZW50IGZyb20gJy4vZG9tYWlucyc7XG5pbXBvcnQgRXZlbnRDbGllbnQgZnJvbSAnLi9ldmVudHMnO1xuaW1wb3J0IFN0YXRzQ2xpZW50IGZyb20gJy4vc3RhdHMnO1xuaW1wb3J0IFN1cHByZXNzaW9uQ2xpZW50IGZyb20gJy4vc3VwcHJlc3Npb25zJztcbmltcG9ydCBXZWJob29rQ2xpZW50IGZyb20gJy4vd2ViaG9va3MnO1xuaW1wb3J0IE1lc3NhZ2VzQ2xpZW50IGZyb20gJy4vbWVzc2FnZXMnO1xuaW1wb3J0IFJvdXRlc0NsaWVudCBmcm9tICcuL3JvdXRlcyc7XG5pbXBvcnQgVmFsaWRhdGVDbGllbnQgZnJvbSAnLi92YWxpZGF0ZSc7XG5pbXBvcnQgUGFyc2VDbGllbnQgZnJvbSAnLi9wYXJzZSc7XG5pbXBvcnQgSXBzQ2xpZW50IGZyb20gJy4vaXBzJztcbmltcG9ydCBJcFBvb2xzQ2xpZW50IGZyb20gJy4vaXAtcG9vbHMnO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBDbGllbnQge1xuICBwcml2YXRlIHJlcXVlc3Q7XG5cbiAgcHVibGljIGRvbWFpbnM7XG4gIHB1YmxpYyB3ZWJob29rcztcbiAgcHVibGljIGV2ZW50cztcbiAgcHVibGljIHN0YXRzO1xuICBwdWJsaWMgc3VwcHJlc3Npb25zO1xuICBwdWJsaWMgbWVzc2FnZXM7XG4gIHB1YmxpYyByb3V0ZXM7XG4gIHB1YmxpYyBwdWJsaWNfcmVxdWVzdDtcbiAgcHVibGljIHZhbGlkYXRlO1xuICBwdWJsaWMgcGFyc2U7XG4gIHB1YmxpYyBpcHM7XG4gIHB1YmxpYyBpcF9wb29scztcblxuICBjb25zdHJ1Y3RvcihvcHRpb25zOiBPcHRpb25zLCBmb3JtRGF0YTogbmV3ICgpID0+IEZvcm1EYXRhKSB7XG4gICAgY29uc3QgY29uZmlnOiBSZXF1ZXN0T3B0aW9ucyA9IHsgLi4ub3B0aW9ucyB9IGFzIFJlcXVlc3RPcHRpb25zO1xuXG4gICAgaWYgKCFjb25maWcudXJsKSB7XG4gICAgICBjb25maWcudXJsID0gJ2h0dHBzOi8vYXBpLm1haWxndW4ubmV0J1xuICAgIH1cblxuICAgIGlmICghY29uZmlnLnVzZXJuYW1lKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhcmFtZXRlciBcInVzZXJuYW1lXCIgaXMgcmVxdWlyZWQnKTtcbiAgICB9XG5cbiAgICBpZiAoIWNvbmZpZy5rZXkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignUGFyYW1ldGVyIFwia2V5XCIgaXMgcmVxdWlyZWQnKTtcbiAgICB9XG5cbiAgICAvKiogQGludGVybmFsICovXG4gICAgdGhpcy5yZXF1ZXN0ID0gbmV3IFJlcXVlc3QoY29uZmlnLCBmb3JtRGF0YSk7XG5cbiAgICB0aGlzLmRvbWFpbnMgPSBuZXcgRG9tYWluQ2xpZW50KHRoaXMucmVxdWVzdCk7XG4gICAgdGhpcy53ZWJob29rcyA9IG5ldyBXZWJob29rQ2xpZW50KHRoaXMucmVxdWVzdCk7XG4gICAgdGhpcy5ldmVudHMgPSBuZXcgRXZlbnRDbGllbnQodGhpcy5yZXF1ZXN0KTtcbiAgICB0aGlzLnN0YXRzID0gbmV3IFN0YXRzQ2xpZW50KHRoaXMucmVxdWVzdCk7XG4gICAgdGhpcy5zdXBwcmVzc2lvbnMgPSBuZXcgU3VwcHJlc3Npb25DbGllbnQodGhpcy5yZXF1ZXN0KTtcbiAgICB0aGlzLm1lc3NhZ2VzID0gbmV3IE1lc3NhZ2VzQ2xpZW50KHRoaXMucmVxdWVzdCk7XG4gICAgdGhpcy5yb3V0ZXMgPSBuZXcgUm91dGVzQ2xpZW50KHRoaXMucmVxdWVzdCk7XG4gICAgdGhpcy5pcHMgPSBuZXcgSXBzQ2xpZW50KHRoaXMucmVxdWVzdCk7XG4gICAgdGhpcy5pcF9wb29scyA9IG5ldyBJcFBvb2xzQ2xpZW50KHRoaXMucmVxdWVzdCk7XG5cbiAgICBpZiAoY29uZmlnLnB1YmxpY19rZXkpIHtcbiAgICAgIGNvbmZpZy5rZXkgPSBjb25maWcucHVibGljX2tleTtcblxuICAgICAgdGhpcy5wdWJsaWNfcmVxdWVzdCA9IG5ldyBSZXF1ZXN0KGNvbmZpZywgZm9ybURhdGEpO1xuICAgICAgdGhpcy52YWxpZGF0ZSA9IG5ldyBWYWxpZGF0ZUNsaWVudCh0aGlzLnB1YmxpY19yZXF1ZXN0KTtcbiAgICAgIHRoaXMucGFyc2UgPSBuZXcgUGFyc2VDbGllbnQodGhpcy5wdWJsaWNfcmVxdWVzdCk7XG4gICAgfVxuICB9XG59XG4iLCJpbXBvcnQgdXJsam9pbiBmcm9tICd1cmwtam9pbic7XG5pbXBvcnQgUmVxdWVzdCBmcm9tICcuL3JlcXVlc3QnO1xuXG5pbnRlcmZhY2UgRG9tYWluRGF0YSB7XG4gIG5hbWU6IHN0cmluZztcbiAgcmVxdWlyZV90bHM6IGFueTtcbiAgc2tpcF92ZXJpZmljYXRpb246IGFueTtcbiAgc3RhdGU6IGFueTtcbiAgd2lsZGNhcmQ6IGFueTtcbiAgc3BhbV9hY3Rpb246IGFueTtcbiAgY3JlYXRlZF9hdDogc3RyaW5nIHwgRGF0ZTtcbiAgc210cF9wYXNzd29yZDogc3RyaW5nO1xuICBzbXRwX2xvZ2luOiBzdHJpbmc7XG4gIHR5cGU6IHN0cmluZztcbn1cblxuY2xhc3MgRG9tYWluIHtcbiAgbmFtZTogYW55O1xuICByZXF1aXJlX3RsczogYW55O1xuICBza2lwX3ZlcmlmaWNhdGlvbjogYW55O1xuICBzdGF0ZTogYW55O1xuICB3aWxkY2FyZDogYW55O1xuICBzcGFtX2FjdGlvbjogYW55O1xuICBjcmVhdGVkX2F0OiBhbnk7XG4gIHNtdHBfcGFzc3dvcmQ6IGFueTtcbiAgc210cF9sb2dpbjogYW55O1xuICB0eXBlOiBhbnk7XG4gIHJlY2VpdmluZ19kbnNfcmVjb3JkczogYW55O1xuICBzZW5kaW5nX2Ruc19yZWNvcmRzOiBhbnk7XG5cbiAgY29uc3RydWN0b3IoZGF0YTogRG9tYWluRGF0YSwgcmVjZWl2aW5nPzogYW55LCBzZW5kaW5nPzogYW55KSB7XG4gICAgdGhpcy5uYW1lID0gZGF0YS5uYW1lO1xuICAgIHRoaXMucmVxdWlyZV90bHMgPSBkYXRhLnJlcXVpcmVfdGxzO1xuICAgIHRoaXMuc2tpcF92ZXJpZmljYXRpb24gPSBkYXRhLnNraXBfdmVyaWZpY2F0aW9uO1xuICAgIHRoaXMuc3RhdGUgPSBkYXRhLnN0YXRlO1xuICAgIHRoaXMud2lsZGNhcmQgPSBkYXRhLndpbGRjYXJkO1xuICAgIHRoaXMuc3BhbV9hY3Rpb24gPSBkYXRhLnNwYW1fYWN0aW9uO1xuICAgIHRoaXMuY3JlYXRlZF9hdCA9IGRhdGEuY3JlYXRlZF9hdDtcbiAgICB0aGlzLnNtdHBfcGFzc3dvcmQgPSBkYXRhLnNtdHBfcGFzc3dvcmQ7XG4gICAgdGhpcy5zbXRwX2xvZ2luID0gZGF0YS5zbXRwX2xvZ2luO1xuICAgIHRoaXMudHlwZSA9IGRhdGEudHlwZTtcblxuICAgIHRoaXMucmVjZWl2aW5nX2Ruc19yZWNvcmRzID0gcmVjZWl2aW5nIHx8IG51bGw7XG4gICAgdGhpcy5zZW5kaW5nX2Ruc19yZWNvcmRzID0gc2VuZGluZyB8fCBudWxsO1xuICB9XG59XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIERvbWFpbkNsaWVudCB7XG4gIHJlcXVlc3Q6IFJlcXVlc3Q7XG5cbiAgY29uc3RydWN0b3IocmVxdWVzdDogUmVxdWVzdCkge1xuICAgIHRoaXMucmVxdWVzdCA9IHJlcXVlc3Q7XG4gIH1cblxuICBfcGFyc2VNZXNzYWdlKHJlc3BvbnNlOiB7IGJvZHk6IGFueSB9KSB7XG4gICAgcmV0dXJuIHJlc3BvbnNlLmJvZHk7XG4gIH1cblxuICBfcGFyc2VEb21haW5MaXN0KHJlc3BvbnNlOiB7IGJvZHk6IHsgaXRlbXM6IERvbWFpbkRhdGFbXSB9IH0pIHtcbiAgICByZXR1cm4gcmVzcG9uc2UuYm9keS5pdGVtcy5tYXAoZnVuY3Rpb24gKGl0ZW0pIHtcbiAgICAgIHJldHVybiBuZXcgRG9tYWluKGl0ZW0pO1xuICAgIH0pO1xuICB9XG5cbiAgX3BhcnNlRG9tYWluKHJlc3BvbnNlOiB7XG4gICAgYm9keToge1xuICAgICAgZG9tYWluOiBhbnksXG4gICAgICByZWNlaXZpbmdfZG5zX3JlY29yZHM6IGFueSxcbiAgICAgIHNlbmRpbmdfZG5zX3JlY29yZHM6IGFueVxuICAgIH1cbiAgfSkge1xuICAgIHJldHVybiBuZXcgRG9tYWluKFxuICAgICAgcmVzcG9uc2UuYm9keS5kb21haW4sXG4gICAgICByZXNwb25zZS5ib2R5LnJlY2VpdmluZ19kbnNfcmVjb3JkcyxcbiAgICAgIHJlc3BvbnNlLmJvZHkuc2VuZGluZ19kbnNfcmVjb3Jkc1xuICAgICk7XG4gIH1cblxuICBfcGFyc2VUcmFja2luZ1NldHRpbmdzKHJlc3BvbnNlOiB7IGJvZHk6IHsgdHJhY2tpbmc6IGFueSB9IH0pIHtcbiAgICByZXR1cm4gcmVzcG9uc2UuYm9keS50cmFja2luZztcbiAgfVxuXG4gIF9wYXJzZVRyYWNraW5nVXBkYXRlKHJlc3BvbnNlOiB7IGJvZHk6IGFueSB9KSB7XG4gICAgcmV0dXJuIHJlc3BvbnNlLmJvZHk7XG4gIH1cblxuICBsaXN0KHF1ZXJ5OiBhbnkpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LmdldCgnL3YyL2RvbWFpbnMnLCBxdWVyeSlcbiAgICAgIC50aGVuKHRoaXMuX3BhcnNlRG9tYWluTGlzdCk7XG4gIH1cblxuICBnZXQoZG9tYWluOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LmdldChgL3YyL2RvbWFpbnMvJHtkb21haW59YClcbiAgICAgIC50aGVuKHRoaXMuX3BhcnNlRG9tYWluKTtcbiAgfVxuXG4gIGNyZWF0ZShkYXRhOiBhbnkpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LnBvc3QoJy92Mi9kb21haW5zJywgZGF0YSlcbiAgICAgIC50aGVuKHRoaXMuX3BhcnNlRG9tYWluKTtcbiAgfVxuXG4gIGRlc3Ryb3koZG9tYWluOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LmRlbGV0ZShgL3YyL2RvbWFpbnMvJHtkb21haW59YClcbiAgICAgIC50aGVuKHRoaXMuX3BhcnNlTWVzc2FnZSk7XG4gIH1cblxuICAvLyBUcmFja2luZ1xuXG4gIGdldFRyYWNraW5nKGRvbWFpbjogc3RyaW5nKSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5nZXQodXJsam9pbignL3YyL2RvbWFpbnMnLCBkb21haW4sICd0cmFja2luZycpKVxuICAgICAgLnRoZW4odGhpcy5fcGFyc2VUcmFja2luZ1NldHRpbmdzKTtcbiAgfVxuXG4gIHVwZGF0ZVRyYWNraW5nKGRvbWFpbjogc3RyaW5nLCB0eXBlOiBzdHJpbmcsIGRhdGE6IGFueSkge1xuICAgIHJldHVybiB0aGlzLnJlcXVlc3QucHV0KHVybGpvaW4oJy92Mi9kb21haW5zJywgZG9tYWluLCAndHJhY2tpbmcnLCB0eXBlKSwgZGF0YSlcbiAgICAgIC50aGVuKHRoaXMuX3BhcnNlVHJhY2tpbmdVcGRhdGUpO1xuICB9XG5cbiAgLy8gSVBzXG5cbiAgZ2V0SXBzKGRvbWFpbjogc3RyaW5nKSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5nZXQodXJsam9pbignL3YyL2RvbWFpbnMnLCBkb21haW4sICdpcHMnKSlcbiAgICAgIC50aGVuKChyZXNwb25zZTogeyBib2R5OiB7IGl0ZW1zOiBzdHJpbmdbXSB9IH0pID0+IHJlc3BvbnNlPy5ib2R5Py5pdGVtcyk7XG4gIH1cblxuICBhc3NpZ25JcChkb21haW46IHN0cmluZywgaXA6IHN0cmluZykge1xuICAgIHJldHVybiB0aGlzLnJlcXVlc3QucG9zdCh1cmxqb2luKCcvdjIvZG9tYWlucycsIGRvbWFpbiwgJ2lwcycpLCB7IGlwIH0pO1xuICB9XG5cbiAgZGVsZXRlSXAoZG9tYWluOiBzdHJpbmcsIGlwOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LmRlbGV0ZSh1cmxqb2luKCcvdjIvZG9tYWlucycsIGRvbWFpbiwgJ2lwcycsIGlwKSk7XG4gIH1cblxuICBsaW5rSXBQb29sKGRvbWFpbjogc3RyaW5nLCBwb29sX2lkOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LnBvc3QodXJsam9pbignL3YyL2RvbWFpbnMnLCBkb21haW4sICdpcHMnKSwgeyBwb29sX2lkIH0pO1xuICB9XG5cbiAgdW5saW5rSXBQb2xsKGRvbWFpbjogc3RyaW5nLCBwb29sX2lkOiBzdHJpbmcsIGlwOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LmRlbGV0ZSh1cmxqb2luKCcvdjIvZG9tYWlucycsIGRvbWFpbiwgJ2lwcycsICdpcF9wb29sJyksIHsgcG9vbF9pZCwgaXAgfSk7XG4gIH1cbn1cbiIsImltcG9ydCBBUElFcnJvck9wdGlvbnMgZnJvbSAnLi9pbnRlcmZhY2VzL0FQSUVycm9yT3B0aW9ucyc7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEFQSUVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICBzdGF0dXM6IG51bWJlciB8IHN0cmluZztcbiAgc3RhY2s6IHN0cmluZztcbiAgZGV0YWlsczogc3RyaW5nO1xuXG4gIGNvbnN0cnVjdG9yKHtcbiAgICBzdGF0dXMsXG4gICAgc3RhdHVzVGV4dCxcbiAgICBtZXNzYWdlLFxuICAgIGJvZHkgPSB7fVxuICB9OiBBUElFcnJvck9wdGlvbnMpIHtcbiAgICBjb25zdCB7IG1lc3NhZ2U6IGJvZHlNZXNzYWdlLCBlcnJvciB9ID0gYm9keTtcbiAgICBzdXBlcigpO1xuXG4gICAgdGhpcy5zdGFjayA9IG51bGw7XG4gICAgdGhpcy5zdGF0dXMgPSBzdGF0dXM7XG4gICAgdGhpcy5tZXNzYWdlID0gbWVzc2FnZSB8fCBlcnJvciB8fCBzdGF0dXNUZXh0O1xuICAgIHRoaXMuZGV0YWlscyA9IGJvZHlNZXNzYWdlO1xuICB9XG59XG4iLCJjb25zdCB1cmxqb2luID0gcmVxdWlyZSgndXJsLWpvaW4nKTtcblxuY29uc3QgTWdSZXF1ZXN0ID0gcmVxdWlyZSgnLi9yZXF1ZXN0Jyk7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIEV2ZW50Q2xpZW50IHtcbiAgcmVxdWVzdDogdHlwZW9mIE1nUmVxdWVzdDtcblxuICBjb25zdHJ1Y3RvcihyZXF1ZXN0OiB0eXBlb2YgTWdSZXF1ZXN0KSB7XG4gICAgdGhpcy5yZXF1ZXN0ID0gcmVxdWVzdDtcbiAgfVxuXG4gIF9wYXJzZVBhZ2VOdW1iZXIodXJsOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gdXJsLnNwbGl0KCcvJykucG9wKCk7XG4gIH1cblxuICBfcGFyc2VQYWdlKGlkOiBzdHJpbmcsIHVybDogc3RyaW5nKSB7XG4gICAgcmV0dXJuIHsgaWQsIG51bWJlcjogdGhpcy5fcGFyc2VQYWdlTnVtYmVyKHVybCksIHVybCB9O1xuICB9XG5cbiAgX3BhcnNlUGFnZUxpbmtzKHJlc3BvbnNlOiB7IGJvZHk6IHsgcGFnaW5nOiBhbnkgfSB9KSB7XG4gICAgY29uc3QgcGFnZXMgPSBPYmplY3QuZW50cmllcyhyZXNwb25zZS5ib2R5LnBhZ2luZyk7XG4gICAgcmV0dXJuIHBhZ2VzLnJlZHVjZShcbiAgICAgIChhY2M6IGFueSwgW2lkLCB1cmxdOiBbdXJsOiBzdHJpbmcsIGlkOiBzdHJpbmddKSA9PiB7XG4gICAgICAgIGFjY1tpZF0gPSB0aGlzLl9wYXJzZVBhZ2UoaWQsIHVybClcbiAgICAgICAgcmV0dXJuIGFjY1xuICAgICAgfSwge30pO1xuICB9XG5cbiAgX3BhcnNlRXZlbnRMaXN0KHJlc3BvbnNlOiB7IGJvZHk6IHsgaXRlbXM6IGFueSwgcGFnaW5nOiBhbnkgfSAgfSkge1xuICAgIHJldHVybiB7XG4gICAgICBpdGVtczogcmVzcG9uc2UuYm9keS5pdGVtcyxcbiAgICAgIHBhZ2VzOiB0aGlzLl9wYXJzZVBhZ2VMaW5rcyhyZXNwb25zZSlcbiAgICB9O1xuICB9XG5cbiAgZ2V0KGRvbWFpbjogc3RyaW5nLCBxdWVyeTogeyBwYWdlOiBhbnkgfSkge1xuICAgIGxldCB1cmw7XG5cbiAgICBpZiAocXVlcnkgJiYgcXVlcnkucGFnZSkge1xuICAgICAgdXJsID0gdXJsam9pbignL3YyJywgZG9tYWluLCAnZXZlbnRzJywgcXVlcnkucGFnZSk7XG4gICAgICBkZWxldGUgcXVlcnkucGFnZTtcbiAgICB9IGVsc2Uge1xuICAgICAgdXJsID0gdXJsam9pbignL3YyJywgZG9tYWluLCAnZXZlbnRzJyk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5nZXQodXJsLCBxdWVyeSlcbiAgICAgIC50aGVuKChyZXNwb25zZTogeyBib2R5OiB7IGl0ZW1zOiBhbnksIHBhZ2luZzogYW55IH0gfSkgPT4gdGhpcy5fcGFyc2VFdmVudExpc3QocmVzcG9uc2UpKTtcbiAgfVxufVxuIiwiY29uc3QgTWdSZXF1ZXN0ID0gcmVxdWlyZSgnLi9yZXF1ZXN0Jyk7XG5cbmltcG9ydCB7IElwUG9vbCB9IGZyb20gXCIuL2ludGVyZmFjZXMvSXBQb29sc1wiO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBJcFBvb2xzQ2xpZW50IHtcbiAgcmVxdWVzdDogdHlwZW9mIE1nUmVxdWVzdDtcblxuICBjb25zdHJ1Y3RvcihyZXF1ZXN0OiB0eXBlb2YgTWdSZXF1ZXN0KSB7XG4gICAgdGhpcy5yZXF1ZXN0ID0gcmVxdWVzdDtcbiAgfVxuXG4gIGxpc3QocXVlcnk6IGFueSk6IElwUG9vbFtdIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LmdldCgnL3YxL2lwX3Bvb2xzJywgcXVlcnkpXG4gICAgICAudGhlbigocmVzcG9uc2U6IHsgYm9keTogeyBpcF9wb29sczogSXBQb29sLCBtZXNzYWdlOiBzdHJpbmcgfSB9KSA9PiB0aGlzLnBhcnNlSXBQb29sc1Jlc3BvbnNlKHJlc3BvbnNlKSk7XG4gIH1cblxuICBjcmVhdGUoZGF0YTogeyBuYW1lOiBzdHJpbmcsIGRlc2NyaXB0aW9uPzogc3RyaW5nLCBpcHM/OiBzdHJpbmdbXSB9KSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5wb3N0KCcvdjEvaXBfcG9vbHMnLCBkYXRhKVxuICAgICAgLnRoZW4oKHJlc3BvbnNlOiB7IGJvZHk6IHsgbWVzc2FnZTogc3RyaW5nLCBwb29sX2lkOiBzdHJpbmcgfSB9KSA9PiByZXNwb25zZT8uYm9keSk7XG4gIH1cblxuICB1cGRhdGUocG9vbElkOiBzdHJpbmcsIGRhdGE6IHsgbmFtZTogc3RyaW5nLCBkZXNjcmlwdGlvbjogc3RyaW5nLCBhZGRfaXA6IHN0cmluZywgcmVtb3ZlX2lwOiBzdHJpbmcgfSkge1xuICAgIHJldHVybiB0aGlzLnJlcXVlc3QucGF0Y2goYC92MS9pcF9wb29scy8ke3Bvb2xJZH1gLCBkYXRhKVxuICAgICAgLnRoZW4oKHJlc3BvbnNlOiB7IGJvZHk6IGFueSB9KSA9PiByZXNwb25zZT8uYm9keSk7XG4gIH1cblxuICBkZWxldGUocG9vbElkOiBzdHJpbmcsIGRhdGE6IHsgaWQ6IHN0cmluZywgcG9vbF9pZDogc3RyaW5nIH0pIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LmRlbGV0ZShgL3YxL2lwX3Bvb2xzLyR7cG9vbElkfWAsIGRhdGEpXG4gICAgICAudGhlbigocmVzcG9uc2U6IHsgYm9keTogYW55IH0pID0+IHJlc3BvbnNlPy5ib2R5KTtcbiAgfVxuXG4gIHByaXZhdGUgcGFyc2VJcFBvb2xzUmVzcG9uc2UocmVzcG9uc2U6IHsgYm9keTogYW55IHwgYW55IH0pIHtcbiAgICByZXR1cm4gcmVzcG9uc2UuYm9keS5pcF9wb29scztcbiAgfVxufVxuIiwiY29uc3QgTWdSZXF1ZXN0ID0gcmVxdWlyZSgnLi9yZXF1ZXN0Jyk7XG5pbXBvcnQgeyBJcERhdGEsIElwc0xpc3RSZXNwb25zZUJvZHkgfSBmcm9tICcuL2ludGVyZmFjZXMvSXBzJztcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgSXBzQ2xpZW50IHtcbiAgcmVxdWVzdDogdHlwZW9mIE1nUmVxdWVzdDtcblxuICBjb25zdHJ1Y3RvcihyZXF1ZXN0OiB0eXBlb2YgTWdSZXF1ZXN0KSB7XG4gICAgdGhpcy5yZXF1ZXN0ID0gcmVxdWVzdDtcbiAgfVxuXG4gIGxpc3QocXVlcnk6IGFueSkge1xuICAgIHJldHVybiB0aGlzLnJlcXVlc3QuZ2V0KCcvdjMvaXBzJywgcXVlcnkpXG4gICAgICAudGhlbigocmVzcG9uc2U6IHsgYm9keTogSXBzTGlzdFJlc3BvbnNlQm9keSB9KSA9PiB0aGlzLnBhcnNlSXBzUmVzcG9uc2UocmVzcG9uc2UpKTtcbiAgfVxuXG4gIGdldChpcDogc3RyaW5nKSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5nZXQoYC92My9pcHMvJHtpcH1gKVxuICAgICAgLnRoZW4oKHJlc3BvbnNlOiB7IGJvZHk6IElwRGF0YSB9KSA9PiB0aGlzLnBhcnNlSXBzUmVzcG9uc2UocmVzcG9uc2UpKTtcbiAgfVxuXG4gIHByaXZhdGUgcGFyc2VJcHNSZXNwb25zZShyZXNwb25zZTogeyBib2R5OiBJcHNMaXN0UmVzcG9uc2VCb2R5IHwgSXBEYXRhIH0pIHtcbiAgICByZXR1cm4gcmVzcG9uc2UuYm9keTtcbiAgfVxufVxuIiwiaW1wb3J0IFJlcXVlc3QgZnJvbSBcIi4vcmVxdWVzdFwiO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBNZXNzYWdlc0NsaWVudCB7XG4gIHJlcXVlc3Q6IFJlcXVlc3Q7XG5cbiAgY29uc3RydWN0b3IocmVxdWVzdDogUmVxdWVzdCkge1xuICAgIHRoaXMucmVxdWVzdCA9IHJlcXVlc3Q7XG4gIH1cblxuICBfcGFyc2VSZXNwb25zZShyZXNwb25zZTogeyBib2R5OiBhbnkgfSkge1xuICAgIGlmIChyZXNwb25zZS5ib2R5KSB7XG4gICAgICByZXR1cm4gcmVzcG9uc2UuYm9keTtcbiAgICB9XG5cbiAgICByZXR1cm4gcmVzcG9uc2U7XG4gIH1cblxuICBjcmVhdGUoZG9tYWluOiBzdHJpbmcsIGRhdGE6IGFueSkge1xuICAgIGlmIChkYXRhLm1lc3NhZ2UpIHtcbiAgICAgIHJldHVybiB0aGlzLnJlcXVlc3QucG9zdE11bHRpKGAvdjMvJHtkb21haW59L21lc3NhZ2VzLm1pbWVgLCBkYXRhKVxuICAgICAgLnRoZW4odGhpcy5fcGFyc2VSZXNwb25zZSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5wb3N0TXVsdGkoYC92My8ke2RvbWFpbn0vbWVzc2FnZXNgLCBkYXRhKVxuICAgICAgLnRoZW4odGhpcy5fcGFyc2VSZXNwb25zZSk7XG4gIH1cbn1cbiIsImltcG9ydCBSZXF1ZXN0IGZyb20gJy4vcmVxdWVzdCc7XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFBhcnNlQ2xpZW50IHtcbiAgcmVxdWVzdDogUmVxdWVzdDtcblxuICBjb25zdHJ1Y3RvcihyZXF1ZXN0OiBSZXF1ZXN0KSB7XG4gICAgdGhpcy5yZXF1ZXN0ID0gcmVxdWVzdDtcbiAgfVxuXG4gIGdldChhZGRyZXNzZXM6IHN0cmluZ1tdIHwgc3RyaW5nLCBlbmFibGVEbnNFc3BDaGVja3M6IGJvb2xlYW4pIHtcbiAgICBjb25zdCBxdWVyeSA9IHt9IGFzIHsgYWRkcmVzc2VzOiBzdHJpbmcsIHN5bnRheF9vbmx5OiBib29sZWFuIH07XG5cbiAgICBpZiAoQXJyYXkuaXNBcnJheShhZGRyZXNzZXMpKSB7XG4gICAgICBhZGRyZXNzZXMgPSBhZGRyZXNzZXMuam9pbignLCcpO1xuICAgIH1cblxuICAgIHF1ZXJ5LmFkZHJlc3NlcyA9IGFkZHJlc3NlcztcblxuICAgIGlmIChlbmFibGVEbnNFc3BDaGVja3MpIHtcbiAgICAgIHF1ZXJ5LnN5bnRheF9vbmx5ID0gZmFsc2U7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5nZXQoJy92My9hZGRyZXNzL3BhcnNlJywgcXVlcnkpXG4gICAgICAudGhlbigocmVzcG9uc2UpID0+IHJlc3BvbnNlLmJvZHkpO1xuICB9XG59XG4iLCJcbmltcG9ydCBCdG9hIGZyb20gJ2J0b2EnO1xuaW1wb3J0IHVybGpvaW4gZnJvbSAndXJsLWpvaW4nO1xuaW1wb3J0IGt5IGZyb20gJ2t5LXVuaXZlcnNhbCc7XG5cbmltcG9ydCBBUElFcnJvciBmcm9tICcuL2Vycm9yJztcbmltcG9ydCBSZXF1ZXN0T3B0aW9ucyBmcm9tICcuL2ludGVyZmFjZXMvUmVxdWVzdE9wdGlvbnMnO1xuaW1wb3J0IEFQSUVycm9yT3B0aW9ucyBmcm9tICcuL2ludGVyZmFjZXMvQVBJRXJyb3JPcHRpb25zJztcblxuY29uc3QgaXNTdHJlYW0gPSAoYXR0YWNobWVudDogYW55KSA9PiB0eXBlb2YgYXR0YWNobWVudCA9PT0gJ29iamVjdCcgJiYgdHlwZW9mIGF0dGFjaG1lbnQucGlwZSA9PT0gJ2Z1bmN0aW9uJztcblxuY29uc3QgZ2V0QXR0YWNobWVudE9wdGlvbnMgPSAoaXRlbTogYW55KTogeyBmaWxlbmFtZT86IHN0cmluZywgY29udGVudFR5cGU/OiBzdHJpbmcsIGtub3duTGVuZ3RoPzogbnVtYmVyIH0gPT4ge1xuICBpZiAodHlwZW9mIGl0ZW0gIT09ICdvYmplY3QnIHx8IGlzU3RyZWFtKGl0ZW0pKSByZXR1cm4ge307XG5cbiAgY29uc3Qge1xuICAgIGZpbGVuYW1lLFxuICAgIGNvbnRlbnRUeXBlLFxuICAgIGtub3duTGVuZ3RoXG4gIH0gPSBpdGVtO1xuXG4gIHJldHVybiB7XG4gICAgLi4uKGZpbGVuYW1lID8geyBmaWxlbmFtZSB9IDogeyBmaWxlbmFtZTogJ2ZpbGUnIH0pLFxuICAgIC4uLihjb250ZW50VHlwZSAmJiB7IGNvbnRlbnRUeXBlIH0pLFxuICAgIC4uLihrbm93bkxlbmd0aCAmJiB7IGtub3duTGVuZ3RoIH0pXG4gIH07XG59XG5cbmNvbnN0IHN0cmVhbVRvU3RyaW5nID0gKHN0cmVhbTogYW55KSA9PiB7XG4gIGNvbnN0IGNodW5rczogYW55ID0gW11cbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICBzdHJlYW0ub24oJ2RhdGEnLCAoY2h1bms6IGFueSkgPT4gY2h1bmtzLnB1c2goY2h1bmspKVxuICAgIHN0cmVhbS5vbignZXJyb3InLCByZWplY3QpXG4gICAgc3RyZWFtLm9uKCdlbmQnLCAoKSA9PiByZXNvbHZlKEJ1ZmZlci5jb25jYXQoY2h1bmtzKS50b1N0cmluZygndXRmOCcpKSlcbiAgfSlcbn1cblxuY2xhc3MgUmVxdWVzdCB7XG4gIHByaXZhdGUgdXNlcm5hbWU7XG4gIHByaXZhdGUga2V5O1xuICBwcml2YXRlIHVybDtcbiAgcHJpdmF0ZSBoZWFkZXJzOiBhbnk7XG4gIHByaXZhdGUgZm9ybURhdGE6IG5ldyAoKSA9PiBGb3JtRGF0YTtcblxuICBjb25zdHJ1Y3RvcihvcHRpb25zOiBSZXF1ZXN0T3B0aW9ucywgZm9ybURhdGE6IG5ldyAoKSA9PiBGb3JtRGF0YSkge1xuICAgIHRoaXMudXNlcm5hbWUgPSBvcHRpb25zLnVzZXJuYW1lO1xuICAgIHRoaXMua2V5ID0gb3B0aW9ucy5rZXk7XG4gICAgdGhpcy51cmwgPSBvcHRpb25zLnVybDtcbiAgICB0aGlzLmhlYWRlcnMgPSBvcHRpb25zLmhlYWRlcnMgfHwge307XG4gICAgdGhpcy5mb3JtRGF0YSA9IGZvcm1EYXRhO1xuICB9XG5cbiAgYXN5bmMgcmVxdWVzdChtZXRob2Q6IHN0cmluZywgdXJsOiBzdHJpbmcsIG9wdGlvbnM/OiBhbnkpIHtcbiAgICBjb25zdCBiYXNpYyA9IEJ0b2EoYCR7dGhpcy51c2VybmFtZX06JHt0aGlzLmtleX1gKTtcbiAgICBjb25zdCBoZWFkZXJzID0ge1xuICAgICAgQXV0aG9yaXphdGlvbjogYEJhc2ljICR7YmFzaWN9YCxcbiAgICAgIC4uLnRoaXMuaGVhZGVycyxcbiAgICAgIC4uLm9wdGlvbnM/LmhlYWRlcnNcbiAgICB9O1xuXG4gICAgZGVsZXRlIG9wdGlvbnM/LmhlYWRlcnM7XG5cbiAgICBpZiAoIWhlYWRlcnNbJ0NvbnRlbnQtVHlwZSddKSB7XG4gICAgICAvLyBmb3IgZm9ybS1kYXRhIGl0IHdpbGwgYmUgTnVsbCBzbyB3ZSBuZWVkIHRvIHJlbW92ZSBpdFxuICAgICAgZGVsZXRlIGhlYWRlcnNbJ0NvbnRlbnQtVHlwZSddO1xuICAgIH1cblxuICAgIGNvbnN0IHBhcmFtcyA9IHsgLi4ub3B0aW9ucyB9O1xuXG4gICAgaWYgKG9wdGlvbnM/LnF1ZXJ5ICYmIE9iamVjdC5nZXRPd25Qcm9wZXJ0eU5hbWVzKG9wdGlvbnM/LnF1ZXJ5KS5sZW5ndGggPiAwKSB7XG4gICAgICBwYXJhbXMuc2VhcmNoUGFyYW1zID0gb3B0aW9ucy5xdWVyeTtcbiAgICAgIGRlbGV0ZSBwYXJhbXMucXVlcnlcbiAgICB9XG5cbiAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGt5KFxuICAgICAgdXJsam9pbih0aGlzLnVybCwgdXJsKSxcbiAgICAgIHtcbiAgICAgICAgbWV0aG9kOiBtZXRob2QudG9Mb2NhbGVVcHBlckNhc2UoKSxcbiAgICAgICAgaGVhZGVycyxcbiAgICAgICAgdGhyb3dIdHRwRXJyb3JzOiBmYWxzZSxcbiAgICAgICAgLi4ucGFyYW1zXG4gICAgICB9XG4gICAgKTtcblxuICAgIGlmICghcmVzcG9uc2U/Lm9rKSB7XG4gICAgICBjb25zdCBtZXNzYWdlID0gcmVzcG9uc2U/LmJvZHkgJiYgaXNTdHJlYW0ocmVzcG9uc2UuYm9keSlcbiAgICAgICAgPyBhd2FpdCBzdHJlYW1Ub1N0cmluZyhyZXNwb25zZS5ib2R5KVxuICAgICAgICA6IGF3YWl0IHJlc3BvbnNlPy5qc29uKCk7XG5cbiAgICAgIHRocm93IG5ldyBBUElFcnJvcih7XG4gICAgICAgIHN0YXR1czogcmVzcG9uc2U/LnN0YXR1cyxcbiAgICAgICAgc3RhdHVzVGV4dDogcmVzcG9uc2U/LnN0YXR1c1RleHQsXG4gICAgICAgIGJvZHk6IHsgbWVzc2FnZSB9XG4gICAgICB9IGFzIEFQSUVycm9yT3B0aW9ucyk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIGJvZHk6IGF3YWl0IHJlc3BvbnNlPy5qc29uKCksXG4gICAgICBzdGF0dXM6IHJlc3BvbnNlPy5zdGF0dXNcbiAgICB9O1xuICB9XG5cbiAgcXVlcnkobWV0aG9kOiBzdHJpbmcsIHVybDogc3RyaW5nLCBxdWVyeTogYW55LCBvcHRpb25zPzogYW55KSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdChtZXRob2QsIHVybCwgeyBxdWVyeSwgLi4ub3B0aW9ucyB9KTtcbiAgfVxuXG4gIGNvbW1hbmQobWV0aG9kOiBzdHJpbmcsIHVybDogc3RyaW5nLCBkYXRhOiBhbnksIG9wdGlvbnM/OiBhbnkpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0KG1ldGhvZCwgdXJsLCB7XG4gICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJyB9LFxuICAgICAgYm9keTogZGF0YSxcbiAgICAgIC4uLm9wdGlvbnNcbiAgICB9KTtcbiAgfVxuXG4gIGdldCh1cmw6IHN0cmluZywgcXVlcnk/OiBhbnksIG9wdGlvbnM/OiBhbnkpIHtcbiAgICByZXR1cm4gdGhpcy5xdWVyeSgnZ2V0JywgdXJsLCBxdWVyeSwgb3B0aW9ucyk7XG4gIH1cblxuICBoZWFkKHVybDogc3RyaW5nLCBxdWVyeTogYW55LCBvcHRpb25zOiBhbnkpIHtcbiAgICByZXR1cm4gdGhpcy5xdWVyeSgnaGVhZCcsIHVybCwgcXVlcnksIG9wdGlvbnMpO1xuICB9XG5cbiAgb3B0aW9ucyh1cmw6IHN0cmluZywgcXVlcnk6IGFueSwgb3B0aW9uczogYW55KSB7XG4gICAgcmV0dXJuIHRoaXMucXVlcnkoJ29wdGlvbnMnLCB1cmwsIHF1ZXJ5LCBvcHRpb25zKTtcbiAgfVxuXG4gIHBvc3QodXJsOiBzdHJpbmcsIGRhdGE6IGFueSwgb3B0aW9ucz86IGFueSkge1xuICAgIHJldHVybiB0aGlzLmNvbW1hbmQoJ3Bvc3QnLCB1cmwsIGRhdGEsIG9wdGlvbnMpO1xuICB9XG5cbiAgcG9zdE11bHRpKHVybDogc3RyaW5nLCBkYXRhOiBhbnkpIHtcblxuICAgIGNvbnN0IGZvcm1EYXRhOiBGb3JtRGF0YSA9IG5ldyB0aGlzLmZvcm1EYXRhKCk7XG4gICAgY29uc3QgcGFyYW1zOiBhbnkgPSB7XG4gICAgICBoZWFkZXJzOiB7ICdDb250ZW50LVR5cGUnOiBudWxsIH1cbiAgICB9O1xuXG4gICAgT2JqZWN0LmtleXMoZGF0YSlcbiAgICAgIC5maWx0ZXIoZnVuY3Rpb24gKGtleSkgeyByZXR1cm4gZGF0YVtrZXldOyB9KVxuICAgICAgLmZvckVhY2goZnVuY3Rpb24gKGtleSkge1xuICAgICAgICBpZiAoa2V5ID09PSAnYXR0YWNobWVudCcpIHtcbiAgICAgICAgICBjb25zdCBvYmogPSBkYXRhLmF0dGFjaG1lbnQ7XG5cbiAgICAgICAgICBpZiAoQXJyYXkuaXNBcnJheShvYmopKSB7XG4gICAgICAgICAgICBvYmouZm9yRWFjaChmdW5jdGlvbiAoaXRlbSkge1xuICAgICAgICAgICAgICBjb25zdCBkYXRhID0gaXRlbS5kYXRhID8gaXRlbS5kYXRhIDogaXRlbTtcbiAgICAgICAgICAgICAgY29uc3Qgb3B0aW9ucyA9IGdldEF0dGFjaG1lbnRPcHRpb25zKGl0ZW0pO1xuICAgICAgICAgICAgICAoZm9ybURhdGEgYXMgYW55KS5hcHBlbmQoa2V5LCBkYXRhLCBvcHRpb25zKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBjb25zdCBkYXRhID0gaXNTdHJlYW0ob2JqKSA/IG9iaiA6IG9iai5kYXRhO1xuICAgICAgICAgICAgY29uc3Qgb3B0aW9ucyA9IGdldEF0dGFjaG1lbnRPcHRpb25zKG9iaik7XG4gICAgICAgICAgICAoZm9ybURhdGEgYXMgYW55KS5hcHBlbmQoa2V5LCBkYXRhLCBvcHRpb25zKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoQXJyYXkuaXNBcnJheShkYXRhW2tleV0pKSB7XG4gICAgICAgICAgZGF0YVtrZXldLmZvckVhY2goZnVuY3Rpb24gKGl0ZW06IGFueSkge1xuICAgICAgICAgICAgZm9ybURhdGEuYXBwZW5kKGtleSwgaXRlbSk7XG4gICAgICAgICAgfSk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgZm9ybURhdGEuYXBwZW5kKGtleSwgZGF0YVtrZXldKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG5cbiAgICByZXR1cm4gdGhpcy5jb21tYW5kKCdwb3N0JywgdXJsLCBmb3JtRGF0YSwgcGFyYW1zKTtcbiAgfVxuXG4gIHB1dCh1cmw6IHN0cmluZywgZGF0YTogYW55LCBvcHRpb25zPzogYW55KSB7XG4gICAgcmV0dXJuIHRoaXMuY29tbWFuZCgncHV0JywgdXJsLCBkYXRhLCBvcHRpb25zKTtcbiAgfVxuXG4gIHBhdGNoKHVybDogc3RyaW5nLCBkYXRhOiBhbnksIG9wdGlvbnM/OiBhbnkpIHtcbiAgICByZXR1cm4gdGhpcy5jb21tYW5kKCdwYXRjaCcsIHVybCwgZGF0YSwgb3B0aW9ucyk7XG4gIH1cblxuICBkZWxldGUodXJsOiBzdHJpbmcsIGRhdGE/OiBhbnksIG9wdGlvbnM/OiBhbnkpIHtcbiAgICByZXR1cm4gdGhpcy5jb21tYW5kKCdkZWxldGUnLCB1cmwsIGRhdGEsIG9wdGlvbnMpO1xuICB9XG59XG5cbmV4cG9ydCBkZWZhdWx0IFJlcXVlc3QiLCJpbXBvcnQgUmVxdWVzdCBmcm9tICcuL3JlcXVlc3QnO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBSb3V0ZXNDbGllbnQge1xuICByZXF1ZXN0OiBSZXF1ZXN0O1xuXG4gIGNvbnN0cnVjdG9yKHJlcXVlc3Q6IFJlcXVlc3QpIHtcbiAgICB0aGlzLnJlcXVlc3QgPSByZXF1ZXN0O1xuICB9XG5cbiAgbGlzdChxdWVyeTogYW55KSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5nZXQoJy92My9yb3V0ZXMnLCBxdWVyeSlcbiAgICAgIC50aGVuKChyZXNwb25zZSkgPT4gcmVzcG9uc2UuYm9keS5pdGVtcyk7XG4gIH1cblxuICBnZXQoaWQ6IHN0cmluZykge1xuICAgIHJldHVybiB0aGlzLnJlcXVlc3QuZ2V0KGAvdjMvcm91dGVzLyR7aWR9YClcbiAgICAgIC50aGVuKChyZXNwb25zZSkgPT4gcmVzcG9uc2UuYm9keS5yb3V0ZSk7XG4gIH1cblxuICBjcmVhdGUoZGF0YTogYW55KSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5wb3N0KCcvdjMvcm91dGVzJywgZGF0YSlcbiAgICAgIC50aGVuKChyZXNwb25zZSkgPT4gcmVzcG9uc2UuYm9keS5yb3V0ZSk7XG4gIH1cblxuICB1cGRhdGUoaWQ6IHN0cmluZywgZGF0YTogYW55KSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5wdXQoYC92My9yb3V0ZXMvJHtpZH1gLCBkYXRhKVxuICAgICAgLnRoZW4oKHJlc3BvbnNlKSA9PiByZXNwb25zZS5ib2R5KTtcbiAgfVxuXG4gIGRlc3Ryb3koaWQ6IHN0cmluZykge1xuICAgIHJldHVybiB0aGlzLnJlcXVlc3QuZGVsZXRlKGAvdjMvcm91dGVzLyR7aWR9YClcbiAgICAgIC50aGVuKChyZXNwb25zZSkgPT4gcmVzcG9uc2UuYm9keSk7XG4gIH1cbn1cbiIsImltcG9ydCB1cmxqb2luIGZyb20gJ3VybC1qb2luJztcbmltcG9ydCBSZXF1ZXN0IGZyb20gJy4vcmVxdWVzdCc7XG5pbXBvcnQgU3RhdHNPcHRpb25zIGZyb20gJy4vaW50ZXJmYWNlcy9TdGF0c09wdGlvbnMnO1xuXG5jbGFzcyBTdGF0cyB7XG4gIHN0YXJ0OiBEYXRlO1xuICBlbmQ6IERhdGU7XG4gIHJlc29sdXRpb246IHN0cmluZztcbiAgc3RhdHM6IGFueTtcblxuICBjb25zdHJ1Y3RvcihkYXRhOiBTdGF0c09wdGlvbnMpIHtcbiAgICB0aGlzLnN0YXJ0ID0gbmV3IERhdGUoZGF0YS5zdGFydCk7XG4gICAgdGhpcy5lbmQgPSBuZXcgRGF0ZShkYXRhLmVuZCk7XG4gICAgdGhpcy5yZXNvbHV0aW9uID0gZGF0YS5yZXNvbHV0aW9uO1xuICAgIHRoaXMuc3RhdHMgPSBkYXRhLnN0YXRzLm1hcChmdW5jdGlvbiAoc3RhdDogeyB0aW1lOiBzdHJpbmcgfCBEYXRlIH0pIHtcbiAgICAgIHN0YXQudGltZSA9IG5ldyBEYXRlKHN0YXQudGltZSk7XG4gICAgICByZXR1cm4gc3RhdDtcbiAgICB9KTtcbiAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBTdGF0c0NsaWVudCB7XG4gIHJlcXVlc3Q6IFJlcXVlc3Q7XG5cbiAgY29uc3RydWN0b3IocmVxdWVzdDogUmVxdWVzdCkge1xuICAgIHRoaXMucmVxdWVzdCA9IHJlcXVlc3Q7XG4gIH1cblxuICBfcGFyc2VTdGF0cyhyZXNwb25zZTogeyBib2R5OiBTdGF0c09wdGlvbnMgfSkge1xuICAgIHJldHVybiBuZXcgU3RhdHMocmVzcG9uc2UuYm9keSk7XG4gIH1cblxuICBnZXREb21haW4oZG9tYWluOiBzdHJpbmcsIHF1ZXJ5OiBhbnkpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LmdldCh1cmxqb2luKCcvdjMnLCBkb21haW4sICdzdGF0cy90b3RhbCcpLCBxdWVyeSlcbiAgICAgIC50aGVuKHRoaXMuX3BhcnNlU3RhdHMpO1xuICB9XG5cbiAgZ2V0QWNjb3VudChxdWVyeTogYW55KSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5nZXQoJy92My9zdGF0cy90b3RhbCcsIHF1ZXJ5KVxuICAgICAgLnRoZW4odGhpcy5fcGFyc2VTdGF0cyk7XG4gIH1cbn1cbiIsImltcG9ydCB1cmwgZnJvbSAndXJsJztcbmltcG9ydCB1cmxqb2luIGZyb20gJ3VybC1qb2luJztcblxuaW1wb3J0IFJlcXVlc3QgZnJvbSAnLi9yZXF1ZXN0JztcbmltcG9ydCB7IEJvdW5jZURhdGEsIENvbXBsYWludERhdGEsIFVuc3Vic2NyaWJlRGF0YSB9IGZyb20gJy4vaW50ZXJmYWNlcy9TdXByZXNzaW9ucyc7XG5cbnR5cGUgVE1vZGVsID0gdHlwZW9mIEJvdW5jZSB8IHR5cGVvZiBDb21wbGFpbnQgfCB0eXBlb2YgVW5zdWJzY3JpYmU7XG5cbmNvbnN0IGNyZWF0ZU9wdGlvbnMgPSB7XG4gIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyB9XG59O1xuXG5jbGFzcyBCb3VuY2Uge1xuICB0eXBlOiBzdHJpbmc7XG4gIGFkZHJlc3M6IHN0cmluZztcbiAgY29kZTogbnVtYmVyO1xuICBlcnJvcjogc3RyaW5nO1xuICBjcmVhdGVkX2F0OiBEYXRlO1xuXG4gIGNvbnN0cnVjdG9yKGRhdGE6IEJvdW5jZURhdGEpIHtcbiAgICB0aGlzLnR5cGUgPSAnYm91bmNlcyc7XG4gICAgdGhpcy5hZGRyZXNzID0gZGF0YS5hZGRyZXNzO1xuICAgIHRoaXMuY29kZSA9ICtkYXRhLmNvZGU7XG4gICAgdGhpcy5lcnJvciA9IGRhdGEuZXJyb3I7XG4gICAgdGhpcy5jcmVhdGVkX2F0ID0gbmV3IERhdGUoZGF0YS5jcmVhdGVkX2F0KTtcbiAgfVxufVxuXG5jbGFzcyBDb21wbGFpbnQge1xuICB0eXBlOiBzdHJpbmc7XG4gIGFkZHJlc3M6IGFueTtcbiAgY3JlYXRlZF9hdDogRGF0ZTtcblxuICBjb25zdHJ1Y3RvcihkYXRhOiBDb21wbGFpbnREYXRhKSB7XG4gICAgdGhpcy50eXBlID0gJ2NvbXBsYWludHMnO1xuICAgIHRoaXMuYWRkcmVzcyA9IGRhdGEuYWRkcmVzcztcbiAgICB0aGlzLmNyZWF0ZWRfYXQgPSBuZXcgRGF0ZShkYXRhLmNyZWF0ZWRfYXQpO1xuICB9XG59XG5cbmNsYXNzIFVuc3Vic2NyaWJlIHtcbiAgdHlwZTogc3RyaW5nO1xuICBhZGRyZXNzOiBzdHJpbmc7XG4gIHRhZ3M6IGFueTtcbiAgY3JlYXRlZF9hdDogRGF0ZTtcblxuICBjb25zdHJ1Y3RvcihkYXRhOiBVbnN1YnNjcmliZURhdGEpIHtcbiAgICB0aGlzLnR5cGUgPSAndW5zdWJzY3JpYmVzJztcbiAgICB0aGlzLmFkZHJlc3MgPSBkYXRhLmFkZHJlc3M7XG4gICAgdGhpcy50YWdzID0gZGF0YS50YWdzO1xuICAgIHRoaXMuY3JlYXRlZF9hdCA9IG5ldyBEYXRlKGRhdGEuY3JlYXRlZF9hdCk7XG4gIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgU3VwcHJlc3Npb25DbGllbnQge1xuICByZXF1ZXN0OiBhbnk7XG4gIG1vZGVsczoge1xuICAgIGJvdW5jZXM6IHR5cGVvZiBCb3VuY2U7XG4gICAgY29tcGxhaW50czogdHlwZW9mIENvbXBsYWludDtcbiAgICB1bnN1YnNjcmliZXM6IHR5cGVvZiBVbnN1YnNjcmliZTtcbiAgfTtcblxuICBjb25zdHJ1Y3RvcihyZXF1ZXN0OiBSZXF1ZXN0KSB7XG4gICAgdGhpcy5yZXF1ZXN0ID0gcmVxdWVzdDtcbiAgICB0aGlzLm1vZGVscyA9IHtcbiAgICAgIGJvdW5jZXM6IEJvdW5jZSxcbiAgICAgIGNvbXBsYWludHM6IENvbXBsYWludCxcbiAgICAgIHVuc3Vic2NyaWJlczogVW5zdWJzY3JpYmVcbiAgICB9O1xuICB9XG5cbiAgX3BhcnNlUGFnZShpZDogc3RyaW5nLCBwYWdlVXJsOiBzdHJpbmcpIHtcbiAgICBjb25zdCBwYXJzZWRVcmwgPSB1cmwucGFyc2UocGFnZVVybCwgdHJ1ZSk7XG4gICAgY29uc3QgeyBxdWVyeSB9ID0gcGFyc2VkVXJsO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIGlkLFxuICAgICAgcGFnZTogcXVlcnkucGFnZSxcbiAgICAgIGFkZHJlc3M6IHF1ZXJ5LmFkZHJlc3MsXG4gICAgICB1cmw6IHBhZ2VVcmxcbiAgICB9O1xuICB9XG5cbiAgX3BhcnNlUGFnZUxpbmtzKHJlc3BvbnNlOiB7IGJvZHk6IHsgcGFnaW5nOiBhbnkgfSB9KSB7XG4gICAgY29uc3QgcGFnZXMgPSBPYmplY3QuZW50cmllcyhyZXNwb25zZS5ib2R5LnBhZ2luZyk7XG4gICAgcmV0dXJuIHBhZ2VzLnJlZHVjZShcbiAgICAgIChhY2M6IGFueSwgW2lkLCB1cmxdOiBbdXJsOiBzdHJpbmcsIGlkOiBzdHJpbmddKSA9PiB7XG4gICAgICAgIGFjY1tpZF0gPSB0aGlzLl9wYXJzZVBhZ2UoaWQsIHVybClcbiAgICAgICAgcmV0dXJuIGFjY1xuICAgICAgfSwge30pO1xuICB9XG5cbiAgX3BhcnNlTGlzdChyZXNwb25zZTogeyBib2R5OiB7IGl0ZW1zOiBhbnksIHBhZ2luZzogYW55IH0gfSwgTW9kZWw6IFRNb2RlbCkge1xuICAgIGNvbnN0IGRhdGEgPSB7fSBhcyBhbnk7XG5cbiAgICBkYXRhLml0ZW1zID0gcmVzcG9uc2UuYm9keS5pdGVtcy5tYXAoKGQ6IGFueSkgPT4gbmV3IE1vZGVsKGQpKTtcblxuICAgIGRhdGEucGFnZXMgPSB0aGlzLl9wYXJzZVBhZ2VMaW5rcyhyZXNwb25zZSk7XG5cbiAgICByZXR1cm4gZGF0YTtcbiAgfVxuXG4gIF9wYXJzZUl0ZW0ocmVzcG9uc2U6IHsgYm9keTogYW55IH0sIE1vZGVsOiBUTW9kZWwpIHtcbiAgICByZXR1cm4gbmV3IE1vZGVsKHJlc3BvbnNlLmJvZHkpO1xuICB9XG5cbiAgbGlzdChkb21haW46IHN0cmluZywgdHlwZTogc3RyaW5nLCBxdWVyeTogYW55KSB7XG4gICAgY29uc3QgbW9kZWwgPSAodGhpcy5tb2RlbHMgYXMgYW55KVt0eXBlXTtcblxuICAgIHJldHVybiB0aGlzLnJlcXVlc3RcbiAgICAgIC5nZXQodXJsam9pbigndjMnLCBkb21haW4sIHR5cGUpLCBxdWVyeSlcbiAgICAgIC50aGVuKChyZXNwb25zZTogeyBib2R5OiB7IGl0ZW1zOiBhbnksIHBhZ2luZzogYW55IH0gfSkgPT4gdGhpcy5fcGFyc2VMaXN0KHJlc3BvbnNlLCBtb2RlbCkpO1xuICB9XG5cbiAgZ2V0KGRvbWFpbjogc3RyaW5nLCB0eXBlOiBzdHJpbmcsIGFkZHJlc3M6IHN0cmluZykge1xuICAgIGNvbnN0IG1vZGVsID0gKHRoaXMubW9kZWxzIGFzIGFueSlbdHlwZV07XG5cbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0XG4gICAgICAuZ2V0KHVybGpvaW4oJ3YzJywgZG9tYWluLCB0eXBlLCBlbmNvZGVVUklDb21wb25lbnQoYWRkcmVzcykpKVxuICAgICAgLnRoZW4oKHJlc3BvbnNlOiB7IGJvZHk6IGFueSB9KSA9PiB0aGlzLl9wYXJzZUl0ZW0ocmVzcG9uc2UsIG1vZGVsKSk7XG4gIH1cblxuICBjcmVhdGUoZG9tYWluOiBzdHJpbmcsIHR5cGU6IHN0cmluZywgZGF0YTogYW55KSB7XG4gICAgLy8gc3VwcG9ydHMgYWRkaW5nIG11bHRpcGxlIHN1cHByZXNzaW9ucyBieSBkZWZhdWx0XG4gICAgaWYgKCFBcnJheS5pc0FycmF5KGRhdGEpKSB7XG4gICAgICBkYXRhID0gW2RhdGFdO1xuICAgIH1cblxuICAgIHJldHVybiB0aGlzLnJlcXVlc3RcbiAgICAucG9zdCh1cmxqb2luKCd2MycsIGRvbWFpbiwgdHlwZSksIGRhdGEsIGNyZWF0ZU9wdGlvbnMpXG4gICAgLnRoZW4oKHJlc3BvbnNlOiB7IGJvZHk6IGFueSB9KSA9PiByZXNwb25zZS5ib2R5KTtcbiAgfVxuXG4gIGRlc3Ryb3koZG9tYWluOiBzdHJpbmcsIHR5cGU6IHN0cmluZywgYWRkcmVzczogc3RyaW5nKSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdFxuICAgIC5kZWxldGUodXJsam9pbigndjMnLCBkb21haW4sIHR5cGUsIGVuY29kZVVSSUNvbXBvbmVudChhZGRyZXNzKSkpXG4gICAgLnRoZW4oKHJlc3BvbnNlOiB7IGJvZHk6IGFueSB9KSA9PiByZXNwb25zZS5ib2R5KTtcbiAgfVxufVxuXG5tb2R1bGUuZXhwb3J0cyA9IFN1cHByZXNzaW9uQ2xpZW50O1xuIiwiXG5pbXBvcnQgUmVxdWVzdCBmcm9tICcuL3JlcXVlc3QnO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBWYWxpZGF0ZUNsaWVudCB7XG4gIHJlcXVlc3Q6IFJlcXVlc3Q7XG5cbiAgY29uc3RydWN0b3IocmVxdWVzdDogUmVxdWVzdCkge1xuICAgIHRoaXMucmVxdWVzdCA9IHJlcXVlc3Q7XG4gIH1cblxuICBnZXQoYWRkcmVzczogc3RyaW5nKSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5nZXQoJy92My9hZGRyZXNzL3ZhbGlkYXRlJywgeyBhZGRyZXNzIH0pXG4gICAgICAudGhlbigocmVzcG9uc2UpID0+IHJlc3BvbnNlLmJvZHkpO1xuICB9XG59XG4iLCJpbXBvcnQgdXJsam9pbiBmcm9tICd1cmwtam9pbic7XG5pbXBvcnQgUmVxdWVzdCBmcm9tICcuL3JlcXVlc3QnO1xuXG5jbGFzcyBXZWJob29rIHtcbiAgaWQ6IHN0cmluZztcbiAgdXJsOiBzdHJpbmc7XG5cbiAgY29uc3RydWN0b3IoaWQ6IHN0cmluZywgZGF0YTogeyB1cmw6IHN0cmluZyB9KSB7XG4gICAgdGhpcy5pZCA9IGlkO1xuICAgIHRoaXMudXJsID0gZGF0YS51cmw7XG4gIH1cbn1cblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgV2ViaG9va0NsaWVudCB7XG4gIHJlcXVlc3Q6IGFueTtcblxuICBjb25zdHJ1Y3RvcihyZXF1ZXN0OiBSZXF1ZXN0KSB7XG4gICAgdGhpcy5yZXF1ZXN0ID0gcmVxdWVzdDtcbiAgfVxuXG4gIF9wYXJzZVdlYmhvb2tMaXN0KHJlc3BvbnNlOiB7IGJvZHk6IHsgd2ViaG9va3M6IGFueSB9IH0pIHtcbiAgICByZXR1cm4gcmVzcG9uc2UuYm9keS53ZWJob29rcztcbiAgfVxuXG4gIF9wYXJzZVdlYmhvb2tXaXRoSUQoaWQ6IHN0cmluZykge1xuICAgIHJldHVybiBmdW5jdGlvbiAocmVzcG9uc2U6IHsgYm9keTogeyB3ZWJob29rOiBhbnkgfSB9KSB7XG4gICAgICByZXR1cm4gbmV3IFdlYmhvb2soaWQsIHJlc3BvbnNlLmJvZHkud2ViaG9vayk7XG4gICAgfTtcbiAgfVxuXG4gIF9wYXJzZVdlYmhvb2tUZXN0KHJlc3BvbnNlOiB7IGJvZHk6IHsgY29kZTogbnVtYmVyLCBtZXNzYWdlOiBzdHJpbmcgfSB9KSB7XG4gICAgcmV0dXJuIHsgY29kZTogcmVzcG9uc2UuYm9keS5jb2RlLCBtZXNzYWdlOiByZXNwb25zZS5ib2R5Lm1lc3NhZ2UgfTtcbiAgfVxuXG4gIGxpc3QoZG9tYWluOiBzdHJpbmcsIHF1ZXJ5OiBhbnkpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LmdldCh1cmxqb2luKCcvdjIvZG9tYWlucycsIGRvbWFpbiwgJ3dlYmhvb2tzJyksIHF1ZXJ5KVxuICAgICAgLnRoZW4odGhpcy5fcGFyc2VXZWJob29rTGlzdCk7XG4gIH1cblxuICBnZXQoZG9tYWluOiBzdHJpbmcsIGlkOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gdGhpcy5yZXF1ZXN0LmdldCh1cmxqb2luKCcvdjIvZG9tYWlucycsIGRvbWFpbiwgJ3dlYmhvb2tzJywgaWQpKVxuICAgICAgLnRoZW4odGhpcy5fcGFyc2VXZWJob29rV2l0aElEKGlkKSk7XG4gIH1cblxuICBjcmVhdGUoZG9tYWluOiBzdHJpbmcsIGlkOiBzdHJpbmcsIHVybDogc3RyaW5nLCB0ZXN0OiBib29sZWFuKSB7XG4gICAgaWYgKHRlc3QpIHtcbiAgICAgIHJldHVybiB0aGlzLnJlcXVlc3QucHV0KHVybGpvaW4oJy92Mi9kb21haW5zJywgZG9tYWluLCAnd2ViaG9va3MnLCBpZCwgJ3Rlc3QnKSwgeyB1cmwgfSlcbiAgICAgICAgLnRoZW4odGhpcy5fcGFyc2VXZWJob29rVGVzdCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5wb3N0KHVybGpvaW4oJy92Mi9kb21haW5zJywgZG9tYWluLCAnd2ViaG9va3MnKSwgeyBpZCwgdXJsIH0pXG4gICAgICAudGhlbih0aGlzLl9wYXJzZVdlYmhvb2tXaXRoSUQoaWQpKTtcbiAgfVxuXG4gIHVwZGF0ZShkb21haW46IHN0cmluZywgaWQ6IHN0cmluZywgdXJsOiBzdHJpbmcsKSB7XG4gICAgcmV0dXJuIHRoaXMucmVxdWVzdC5wdXQodXJsam9pbignL3YyL2RvbWFpbnMnLCBkb21haW4sICd3ZWJob29rcycsIGlkKSwgeyB1cmwgfSlcbiAgICAgIC50aGVuKHRoaXMuX3BhcnNlV2ViaG9va1dpdGhJRChpZCkpO1xuICB9XG5cbiAgZGVzdHJveShkb21haW46IHN0cmluZywgaWQ6IHN0cmluZykge1xuICAgIHJldHVybiB0aGlzLnJlcXVlc3QuZGVsZXRlKHVybGpvaW4oJy92Mi9kb21haW5zJywgZG9tYWluLCAnd2ViaG9va3MnLCBpZCkpXG4gICAgICAudGhlbih0aGlzLl9wYXJzZVdlYmhvb2tXaXRoSUQoaWQpKTtcbiAgfVxufVxuIiwiKGZ1bmN0aW9uICgpIHtcbiAgXCJ1c2Ugc3RyaWN0XCI7XG5cbiAgZnVuY3Rpb24gYnRvYShzdHIpIHtcbiAgICB2YXIgYnVmZmVyO1xuXG4gICAgaWYgKHN0ciBpbnN0YW5jZW9mIEJ1ZmZlcikge1xuICAgICAgYnVmZmVyID0gc3RyO1xuICAgIH0gZWxzZSB7XG4gICAgICBidWZmZXIgPSBCdWZmZXIuZnJvbShzdHIudG9TdHJpbmcoKSwgJ2JpbmFyeScpO1xuICAgIH1cblxuICAgIHJldHVybiBidWZmZXIudG9TdHJpbmcoJ2Jhc2U2NCcpO1xuICB9XG5cbiAgbW9kdWxlLmV4cG9ydHMgPSBidG9hO1xufSgpKTtcbiIsIlwidXNlIHN0cmljdFwiO1xuLyoqXG4gKiBSZXR1cm5zIGEgYEJ1ZmZlcmAgaW5zdGFuY2UgZnJvbSB0aGUgZ2l2ZW4gZGF0YSBVUkkgYHVyaWAuXG4gKlxuICogQHBhcmFtIHtTdHJpbmd9IHVyaSBEYXRhIFVSSSB0byB0dXJuIGludG8gYSBCdWZmZXIgaW5zdGFuY2VcbiAqIEByZXR1cm4ge0J1ZmZlcn0gQnVmZmVyIGluc3RhbmNlIGZyb20gRGF0YSBVUklcbiAqIEBhcGkgcHVibGljXG4gKi9cbmZ1bmN0aW9uIGRhdGFVcmlUb0J1ZmZlcih1cmkpIHtcbiAgICBpZiAoIS9eZGF0YTovaS50ZXN0KHVyaSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignYHVyaWAgZG9lcyBub3QgYXBwZWFyIHRvIGJlIGEgRGF0YSBVUkkgKG11c3QgYmVnaW4gd2l0aCBcImRhdGE6XCIpJyk7XG4gICAgfVxuICAgIC8vIHN0cmlwIG5ld2xpbmVzXG4gICAgdXJpID0gdXJpLnJlcGxhY2UoL1xccj9cXG4vZywgJycpO1xuICAgIC8vIHNwbGl0IHRoZSBVUkkgdXAgaW50byB0aGUgXCJtZXRhZGF0YVwiIGFuZCB0aGUgXCJkYXRhXCIgcG9ydGlvbnNcbiAgICBjb25zdCBmaXJzdENvbW1hID0gdXJpLmluZGV4T2YoJywnKTtcbiAgICBpZiAoZmlyc3RDb21tYSA9PT0gLTEgfHwgZmlyc3RDb21tYSA8PSA0KSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ21hbGZvcm1lZCBkYXRhOiBVUkknKTtcbiAgICB9XG4gICAgLy8gcmVtb3ZlIHRoZSBcImRhdGE6XCIgc2NoZW1lIGFuZCBwYXJzZSB0aGUgbWV0YWRhdGFcbiAgICBjb25zdCBtZXRhID0gdXJpLnN1YnN0cmluZyg1LCBmaXJzdENvbW1hKS5zcGxpdCgnOycpO1xuICAgIGxldCBjaGFyc2V0ID0gJyc7XG4gICAgbGV0IGJhc2U2NCA9IGZhbHNlO1xuICAgIGNvbnN0IHR5cGUgPSBtZXRhWzBdIHx8ICd0ZXh0L3BsYWluJztcbiAgICBsZXQgdHlwZUZ1bGwgPSB0eXBlO1xuICAgIGZvciAobGV0IGkgPSAxOyBpIDwgbWV0YS5sZW5ndGg7IGkrKykge1xuICAgICAgICBpZiAobWV0YVtpXSA9PT0gJ2Jhc2U2NCcpIHtcbiAgICAgICAgICAgIGJhc2U2NCA9IHRydWU7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0eXBlRnVsbCArPSBgOyR7bWV0YVtpXX1gO1xuICAgICAgICAgICAgaWYgKG1ldGFbaV0uaW5kZXhPZignY2hhcnNldD0nKSA9PT0gMCkge1xuICAgICAgICAgICAgICAgIGNoYXJzZXQgPSBtZXRhW2ldLnN1YnN0cmluZyg4KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbiAgICAvLyBkZWZhdWx0cyB0byBVUy1BU0NJSSBvbmx5IGlmIHR5cGUgaXMgbm90IHByb3ZpZGVkXG4gICAgaWYgKCFtZXRhWzBdICYmICFjaGFyc2V0Lmxlbmd0aCkge1xuICAgICAgICB0eXBlRnVsbCArPSAnO2NoYXJzZXQ9VVMtQVNDSUknO1xuICAgICAgICBjaGFyc2V0ID0gJ1VTLUFTQ0lJJztcbiAgICB9XG4gICAgLy8gZ2V0IHRoZSBlbmNvZGVkIGRhdGEgcG9ydGlvbiBhbmQgZGVjb2RlIFVSSS1lbmNvZGVkIGNoYXJzXG4gICAgY29uc3QgZW5jb2RpbmcgPSBiYXNlNjQgPyAnYmFzZTY0JyA6ICdhc2NpaSc7XG4gICAgY29uc3QgZGF0YSA9IHVuZXNjYXBlKHVyaS5zdWJzdHJpbmcoZmlyc3RDb21tYSArIDEpKTtcbiAgICBjb25zdCBidWZmZXIgPSBCdWZmZXIuZnJvbShkYXRhLCBlbmNvZGluZyk7XG4gICAgLy8gc2V0IGAudHlwZWAgYW5kIGAudHlwZUZ1bGxgIHByb3BlcnRpZXMgdG8gTUlNRSB0eXBlXG4gICAgYnVmZmVyLnR5cGUgPSB0eXBlO1xuICAgIGJ1ZmZlci50eXBlRnVsbCA9IHR5cGVGdWxsO1xuICAgIC8vIHNldCB0aGUgYC5jaGFyc2V0YCBwcm9wZXJ0eVxuICAgIGJ1ZmZlci5jaGFyc2V0ID0gY2hhcnNldDtcbiAgICByZXR1cm4gYnVmZmVyO1xufVxubW9kdWxlLmV4cG9ydHMgPSBkYXRhVXJpVG9CdWZmZXI7XG4vLyMgc291cmNlTWFwcGluZ1VSTD1pbmRleC5qcy5tYXAiLCIvKipcbiAqIEBhdXRob3IgVG9ydSBOYWdhc2hpbWEgPGh0dHBzOi8vZ2l0aHViLmNvbS9teXN0aWNhdGVhPlxuICogQGNvcHlyaWdodCAyMDE1IFRvcnUgTmFnYXNoaW1hLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogU2VlIExJQ0VOU0UgZmlsZSBpbiByb290IGRpcmVjdG9yeSBmb3IgZnVsbCBsaWNlbnNlLlxuICovXG4ndXNlIHN0cmljdCc7XG5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHsgdmFsdWU6IHRydWUgfSk7XG5cbi8qKlxuICogQHR5cGVkZWYge29iamVjdH0gUHJpdmF0ZURhdGFcbiAqIEBwcm9wZXJ0eSB7RXZlbnRUYXJnZXR9IGV2ZW50VGFyZ2V0IFRoZSBldmVudCB0YXJnZXQuXG4gKiBAcHJvcGVydHkge3t0eXBlOnN0cmluZ319IGV2ZW50IFRoZSBvcmlnaW5hbCBldmVudCBvYmplY3QuXG4gKiBAcHJvcGVydHkge251bWJlcn0gZXZlbnRQaGFzZSBUaGUgY3VycmVudCBldmVudCBwaGFzZS5cbiAqIEBwcm9wZXJ0eSB7RXZlbnRUYXJnZXR8bnVsbH0gY3VycmVudFRhcmdldCBUaGUgY3VycmVudCBldmVudCB0YXJnZXQuXG4gKiBAcHJvcGVydHkge2Jvb2xlYW59IGNhbmNlbGVkIFRoZSBmbGFnIHRvIHByZXZlbnQgZGVmYXVsdC5cbiAqIEBwcm9wZXJ0eSB7Ym9vbGVhbn0gc3RvcHBlZCBUaGUgZmxhZyB0byBzdG9wIHByb3BhZ2F0aW9uLlxuICogQHByb3BlcnR5IHtib29sZWFufSBpbW1lZGlhdGVTdG9wcGVkIFRoZSBmbGFnIHRvIHN0b3AgcHJvcGFnYXRpb24gaW1tZWRpYXRlbHkuXG4gKiBAcHJvcGVydHkge0Z1bmN0aW9ufG51bGx9IHBhc3NpdmVMaXN0ZW5lciBUaGUgbGlzdGVuZXIgaWYgdGhlIGN1cnJlbnQgbGlzdGVuZXIgaXMgcGFzc2l2ZS4gT3RoZXJ3aXNlIHRoaXMgaXMgbnVsbC5cbiAqIEBwcm9wZXJ0eSB7bnVtYmVyfSB0aW1lU3RhbXAgVGhlIHVuaXggdGltZS5cbiAqIEBwcml2YXRlXG4gKi9cblxuLyoqXG4gKiBQcml2YXRlIGRhdGEgZm9yIGV2ZW50IHdyYXBwZXJzLlxuICogQHR5cGUge1dlYWtNYXA8RXZlbnQsIFByaXZhdGVEYXRhPn1cbiAqIEBwcml2YXRlXG4gKi9cbmNvbnN0IHByaXZhdGVEYXRhID0gbmV3IFdlYWtNYXAoKTtcblxuLyoqXG4gKiBDYWNoZSBmb3Igd3JhcHBlciBjbGFzc2VzLlxuICogQHR5cGUge1dlYWtNYXA8T2JqZWN0LCBGdW5jdGlvbj59XG4gKiBAcHJpdmF0ZVxuICovXG5jb25zdCB3cmFwcGVycyA9IG5ldyBXZWFrTWFwKCk7XG5cbi8qKlxuICogR2V0IHByaXZhdGUgZGF0YS5cbiAqIEBwYXJhbSB7RXZlbnR9IGV2ZW50IFRoZSBldmVudCBvYmplY3QgdG8gZ2V0IHByaXZhdGUgZGF0YS5cbiAqIEByZXR1cm5zIHtQcml2YXRlRGF0YX0gVGhlIHByaXZhdGUgZGF0YSBvZiB0aGUgZXZlbnQuXG4gKiBAcHJpdmF0ZVxuICovXG5mdW5jdGlvbiBwZChldmVudCkge1xuICAgIGNvbnN0IHJldHYgPSBwcml2YXRlRGF0YS5nZXQoZXZlbnQpO1xuICAgIGNvbnNvbGUuYXNzZXJ0KFxuICAgICAgICByZXR2ICE9IG51bGwsXG4gICAgICAgIFwiJ3RoaXMnIGlzIGV4cGVjdGVkIGFuIEV2ZW50IG9iamVjdCwgYnV0IGdvdFwiLFxuICAgICAgICBldmVudFxuICAgICk7XG4gICAgcmV0dXJuIHJldHZcbn1cblxuLyoqXG4gKiBodHRwczovL2RvbS5zcGVjLndoYXR3Zy5vcmcvI3NldC10aGUtY2FuY2VsZWQtZmxhZ1xuICogQHBhcmFtIGRhdGEge1ByaXZhdGVEYXRhfSBwcml2YXRlIGRhdGEuXG4gKi9cbmZ1bmN0aW9uIHNldENhbmNlbEZsYWcoZGF0YSkge1xuICAgIGlmIChkYXRhLnBhc3NpdmVMaXN0ZW5lciAhPSBudWxsKSB7XG4gICAgICAgIGlmIChcbiAgICAgICAgICAgIHR5cGVvZiBjb25zb2xlICE9PSBcInVuZGVmaW5lZFwiICYmXG4gICAgICAgICAgICB0eXBlb2YgY29uc29sZS5lcnJvciA9PT0gXCJmdW5jdGlvblwiXG4gICAgICAgICkge1xuICAgICAgICAgICAgY29uc29sZS5lcnJvcihcbiAgICAgICAgICAgICAgICBcIlVuYWJsZSB0byBwcmV2ZW50RGVmYXVsdCBpbnNpZGUgcGFzc2l2ZSBldmVudCBsaXN0ZW5lciBpbnZvY2F0aW9uLlwiLFxuICAgICAgICAgICAgICAgIGRhdGEucGFzc2l2ZUxpc3RlbmVyXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIHJldHVyblxuICAgIH1cbiAgICBpZiAoIWRhdGEuZXZlbnQuY2FuY2VsYWJsZSkge1xuICAgICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBkYXRhLmNhbmNlbGVkID0gdHJ1ZTtcbiAgICBpZiAodHlwZW9mIGRhdGEuZXZlbnQucHJldmVudERlZmF1bHQgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICBkYXRhLmV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgfVxufVxuXG4vKipcbiAqIEBzZWUgaHR0cHM6Ly9kb20uc3BlYy53aGF0d2cub3JnLyNpbnRlcmZhY2UtZXZlbnRcbiAqIEBwcml2YXRlXG4gKi9cbi8qKlxuICogVGhlIGV2ZW50IHdyYXBwZXIuXG4gKiBAY29uc3RydWN0b3JcbiAqIEBwYXJhbSB7RXZlbnRUYXJnZXR9IGV2ZW50VGFyZ2V0IFRoZSBldmVudCB0YXJnZXQgb2YgdGhpcyBkaXNwYXRjaGluZy5cbiAqIEBwYXJhbSB7RXZlbnR8e3R5cGU6c3RyaW5nfX0gZXZlbnQgVGhlIG9yaWdpbmFsIGV2ZW50IHRvIHdyYXAuXG4gKi9cbmZ1bmN0aW9uIEV2ZW50KGV2ZW50VGFyZ2V0LCBldmVudCkge1xuICAgIHByaXZhdGVEYXRhLnNldCh0aGlzLCB7XG4gICAgICAgIGV2ZW50VGFyZ2V0LFxuICAgICAgICBldmVudCxcbiAgICAgICAgZXZlbnRQaGFzZTogMixcbiAgICAgICAgY3VycmVudFRhcmdldDogZXZlbnRUYXJnZXQsXG4gICAgICAgIGNhbmNlbGVkOiBmYWxzZSxcbiAgICAgICAgc3RvcHBlZDogZmFsc2UsXG4gICAgICAgIGltbWVkaWF0ZVN0b3BwZWQ6IGZhbHNlLFxuICAgICAgICBwYXNzaXZlTGlzdGVuZXI6IG51bGwsXG4gICAgICAgIHRpbWVTdGFtcDogZXZlbnQudGltZVN0YW1wIHx8IERhdGUubm93KCksXG4gICAgfSk7XG5cbiAgICAvLyBodHRwczovL2hleWNhbS5naXRodWIuaW8vd2ViaWRsLyNVbmZvcmdlYWJsZVxuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCBcImlzVHJ1c3RlZFwiLCB7IHZhbHVlOiBmYWxzZSwgZW51bWVyYWJsZTogdHJ1ZSB9KTtcblxuICAgIC8vIERlZmluZSBhY2Nlc3NvcnNcbiAgICBjb25zdCBrZXlzID0gT2JqZWN0LmtleXMoZXZlbnQpO1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwga2V5cy5sZW5ndGg7ICsraSkge1xuICAgICAgICBjb25zdCBrZXkgPSBrZXlzW2ldO1xuICAgICAgICBpZiAoIShrZXkgaW4gdGhpcykpIHtcbiAgICAgICAgICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eSh0aGlzLCBrZXksIGRlZmluZVJlZGlyZWN0RGVzY3JpcHRvcihrZXkpKTtcbiAgICAgICAgfVxuICAgIH1cbn1cblxuLy8gU2hvdWxkIGJlIGVudW1lcmFibGUsIGJ1dCBjbGFzcyBtZXRob2RzIGFyZSBub3QgZW51bWVyYWJsZS5cbkV2ZW50LnByb3RvdHlwZSA9IHtcbiAgICAvKipcbiAgICAgKiBUaGUgdHlwZSBvZiB0aGlzIGV2ZW50LlxuICAgICAqIEB0eXBlIHtzdHJpbmd9XG4gICAgICovXG4gICAgZ2V0IHR5cGUoKSB7XG4gICAgICAgIHJldHVybiBwZCh0aGlzKS5ldmVudC50eXBlXG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFRoZSB0YXJnZXQgb2YgdGhpcyBldmVudC5cbiAgICAgKiBAdHlwZSB7RXZlbnRUYXJnZXR9XG4gICAgICovXG4gICAgZ2V0IHRhcmdldCgpIHtcbiAgICAgICAgcmV0dXJuIHBkKHRoaXMpLmV2ZW50VGFyZ2V0XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFRoZSB0YXJnZXQgb2YgdGhpcyBldmVudC5cbiAgICAgKiBAdHlwZSB7RXZlbnRUYXJnZXR9XG4gICAgICovXG4gICAgZ2V0IGN1cnJlbnRUYXJnZXQoKSB7XG4gICAgICAgIHJldHVybiBwZCh0aGlzKS5jdXJyZW50VGFyZ2V0XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIEByZXR1cm5zIHtFdmVudFRhcmdldFtdfSBUaGUgY29tcG9zZWQgcGF0aCBvZiB0aGlzIGV2ZW50LlxuICAgICAqL1xuICAgIGNvbXBvc2VkUGF0aCgpIHtcbiAgICAgICAgY29uc3QgY3VycmVudFRhcmdldCA9IHBkKHRoaXMpLmN1cnJlbnRUYXJnZXQ7XG4gICAgICAgIGlmIChjdXJyZW50VGFyZ2V0ID09IG51bGwpIHtcbiAgICAgICAgICAgIHJldHVybiBbXVxuICAgICAgICB9XG4gICAgICAgIHJldHVybiBbY3VycmVudFRhcmdldF1cbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogQ29uc3RhbnQgb2YgTk9ORS5cbiAgICAgKiBAdHlwZSB7bnVtYmVyfVxuICAgICAqL1xuICAgIGdldCBOT05FKCkge1xuICAgICAgICByZXR1cm4gMFxuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBDb25zdGFudCBvZiBDQVBUVVJJTkdfUEhBU0UuXG4gICAgICogQHR5cGUge251bWJlcn1cbiAgICAgKi9cbiAgICBnZXQgQ0FQVFVSSU5HX1BIQVNFKCkge1xuICAgICAgICByZXR1cm4gMVxuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBDb25zdGFudCBvZiBBVF9UQVJHRVQuXG4gICAgICogQHR5cGUge251bWJlcn1cbiAgICAgKi9cbiAgICBnZXQgQVRfVEFSR0VUKCkge1xuICAgICAgICByZXR1cm4gMlxuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBDb25zdGFudCBvZiBCVUJCTElOR19QSEFTRS5cbiAgICAgKiBAdHlwZSB7bnVtYmVyfVxuICAgICAqL1xuICAgIGdldCBCVUJCTElOR19QSEFTRSgpIHtcbiAgICAgICAgcmV0dXJuIDNcbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogVGhlIHRhcmdldCBvZiB0aGlzIGV2ZW50LlxuICAgICAqIEB0eXBlIHtudW1iZXJ9XG4gICAgICovXG4gICAgZ2V0IGV2ZW50UGhhc2UoKSB7XG4gICAgICAgIHJldHVybiBwZCh0aGlzKS5ldmVudFBoYXNlXG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFN0b3AgZXZlbnQgYnViYmxpbmcuXG4gICAgICogQHJldHVybnMge3ZvaWR9XG4gICAgICovXG4gICAgc3RvcFByb3BhZ2F0aW9uKCkge1xuICAgICAgICBjb25zdCBkYXRhID0gcGQodGhpcyk7XG5cbiAgICAgICAgZGF0YS5zdG9wcGVkID0gdHJ1ZTtcbiAgICAgICAgaWYgKHR5cGVvZiBkYXRhLmV2ZW50LnN0b3BQcm9wYWdhdGlvbiA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICBkYXRhLmV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFN0b3AgZXZlbnQgYnViYmxpbmcuXG4gICAgICogQHJldHVybnMge3ZvaWR9XG4gICAgICovXG4gICAgc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uKCkge1xuICAgICAgICBjb25zdCBkYXRhID0gcGQodGhpcyk7XG5cbiAgICAgICAgZGF0YS5zdG9wcGVkID0gdHJ1ZTtcbiAgICAgICAgZGF0YS5pbW1lZGlhdGVTdG9wcGVkID0gdHJ1ZTtcbiAgICAgICAgaWYgKHR5cGVvZiBkYXRhLmV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbiA9PT0gXCJmdW5jdGlvblwiKSB7XG4gICAgICAgICAgICBkYXRhLmV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbigpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFRoZSBmbGFnIHRvIGJlIGJ1YmJsaW5nLlxuICAgICAqIEB0eXBlIHtib29sZWFufVxuICAgICAqL1xuICAgIGdldCBidWJibGVzKCkge1xuICAgICAgICByZXR1cm4gQm9vbGVhbihwZCh0aGlzKS5ldmVudC5idWJibGVzKVxuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBUaGUgZmxhZyB0byBiZSBjYW5jZWxhYmxlLlxuICAgICAqIEB0eXBlIHtib29sZWFufVxuICAgICAqL1xuICAgIGdldCBjYW5jZWxhYmxlKCkge1xuICAgICAgICByZXR1cm4gQm9vbGVhbihwZCh0aGlzKS5ldmVudC5jYW5jZWxhYmxlKVxuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBDYW5jZWwgdGhpcyBldmVudC5cbiAgICAgKiBAcmV0dXJucyB7dm9pZH1cbiAgICAgKi9cbiAgICBwcmV2ZW50RGVmYXVsdCgpIHtcbiAgICAgICAgc2V0Q2FuY2VsRmxhZyhwZCh0aGlzKSk7XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFRoZSBmbGFnIHRvIGluZGljYXRlIGNhbmNlbGxhdGlvbiBzdGF0ZS5cbiAgICAgKiBAdHlwZSB7Ym9vbGVhbn1cbiAgICAgKi9cbiAgICBnZXQgZGVmYXVsdFByZXZlbnRlZCgpIHtcbiAgICAgICAgcmV0dXJuIHBkKHRoaXMpLmNhbmNlbGVkXG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFRoZSBmbGFnIHRvIGJlIGNvbXBvc2VkLlxuICAgICAqIEB0eXBlIHtib29sZWFufVxuICAgICAqL1xuICAgIGdldCBjb21wb3NlZCgpIHtcbiAgICAgICAgcmV0dXJuIEJvb2xlYW4ocGQodGhpcykuZXZlbnQuY29tcG9zZWQpXG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFRoZSB1bml4IHRpbWUgb2YgdGhpcyBldmVudC5cbiAgICAgKiBAdHlwZSB7bnVtYmVyfVxuICAgICAqL1xuICAgIGdldCB0aW1lU3RhbXAoKSB7XG4gICAgICAgIHJldHVybiBwZCh0aGlzKS50aW1lU3RhbXBcbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogVGhlIHRhcmdldCBvZiB0aGlzIGV2ZW50LlxuICAgICAqIEB0eXBlIHtFdmVudFRhcmdldH1cbiAgICAgKiBAZGVwcmVjYXRlZFxuICAgICAqL1xuICAgIGdldCBzcmNFbGVtZW50KCkge1xuICAgICAgICByZXR1cm4gcGQodGhpcykuZXZlbnRUYXJnZXRcbiAgICB9LFxuXG4gICAgLyoqXG4gICAgICogVGhlIGZsYWcgdG8gc3RvcCBldmVudCBidWJibGluZy5cbiAgICAgKiBAdHlwZSB7Ym9vbGVhbn1cbiAgICAgKiBAZGVwcmVjYXRlZFxuICAgICAqL1xuICAgIGdldCBjYW5jZWxCdWJibGUoKSB7XG4gICAgICAgIHJldHVybiBwZCh0aGlzKS5zdG9wcGVkXG4gICAgfSxcbiAgICBzZXQgY2FuY2VsQnViYmxlKHZhbHVlKSB7XG4gICAgICAgIGlmICghdmFsdWUpIHtcbiAgICAgICAgICAgIHJldHVyblxuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGRhdGEgPSBwZCh0aGlzKTtcblxuICAgICAgICBkYXRhLnN0b3BwZWQgPSB0cnVlO1xuICAgICAgICBpZiAodHlwZW9mIGRhdGEuZXZlbnQuY2FuY2VsQnViYmxlID09PSBcImJvb2xlYW5cIikge1xuICAgICAgICAgICAgZGF0YS5ldmVudC5jYW5jZWxCdWJibGUgPSB0cnVlO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFRoZSBmbGFnIHRvIGluZGljYXRlIGNhbmNlbGxhdGlvbiBzdGF0ZS5cbiAgICAgKiBAdHlwZSB7Ym9vbGVhbn1cbiAgICAgKiBAZGVwcmVjYXRlZFxuICAgICAqL1xuICAgIGdldCByZXR1cm5WYWx1ZSgpIHtcbiAgICAgICAgcmV0dXJuICFwZCh0aGlzKS5jYW5jZWxlZFxuICAgIH0sXG4gICAgc2V0IHJldHVyblZhbHVlKHZhbHVlKSB7XG4gICAgICAgIGlmICghdmFsdWUpIHtcbiAgICAgICAgICAgIHNldENhbmNlbEZsYWcocGQodGhpcykpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIEluaXRpYWxpemUgdGhpcyBldmVudCBvYmplY3QuIEJ1dCBkbyBub3RoaW5nIHVuZGVyIGV2ZW50IGRpc3BhdGNoaW5nLlxuICAgICAqIEBwYXJhbSB7c3RyaW5nfSB0eXBlIFRoZSBldmVudCB0eXBlLlxuICAgICAqIEBwYXJhbSB7Ym9vbGVhbn0gW2J1YmJsZXM9ZmFsc2VdIFRoZSBmbGFnIHRvIGJlIHBvc3NpYmxlIHRvIGJ1YmJsZSB1cC5cbiAgICAgKiBAcGFyYW0ge2Jvb2xlYW59IFtjYW5jZWxhYmxlPWZhbHNlXSBUaGUgZmxhZyB0byBiZSBwb3NzaWJsZSB0byBjYW5jZWwuXG4gICAgICogQGRlcHJlY2F0ZWRcbiAgICAgKi9cbiAgICBpbml0RXZlbnQoKSB7XG4gICAgICAgIC8vIERvIG5vdGhpbmcuXG4gICAgfSxcbn07XG5cbi8vIGBjb25zdHJ1Y3RvcmAgaXMgbm90IGVudW1lcmFibGUuXG5PYmplY3QuZGVmaW5lUHJvcGVydHkoRXZlbnQucHJvdG90eXBlLCBcImNvbnN0cnVjdG9yXCIsIHtcbiAgICB2YWx1ZTogRXZlbnQsXG4gICAgY29uZmlndXJhYmxlOiB0cnVlLFxuICAgIHdyaXRhYmxlOiB0cnVlLFxufSk7XG5cbi8vIEVuc3VyZSBgZXZlbnQgaW5zdGFuY2VvZiB3aW5kb3cuRXZlbnRgIGlzIGB0cnVlYC5cbmlmICh0eXBlb2Ygd2luZG93ICE9PSBcInVuZGVmaW5lZFwiICYmIHR5cGVvZiB3aW5kb3cuRXZlbnQgIT09IFwidW5kZWZpbmVkXCIpIHtcbiAgICBPYmplY3Quc2V0UHJvdG90eXBlT2YoRXZlbnQucHJvdG90eXBlLCB3aW5kb3cuRXZlbnQucHJvdG90eXBlKTtcblxuICAgIC8vIE1ha2UgYXNzb2NpYXRpb24gZm9yIHdyYXBwZXJzLlxuICAgIHdyYXBwZXJzLnNldCh3aW5kb3cuRXZlbnQucHJvdG90eXBlLCBFdmVudCk7XG59XG5cbi8qKlxuICogR2V0IHRoZSBwcm9wZXJ0eSBkZXNjcmlwdG9yIHRvIHJlZGlyZWN0IGEgZ2l2ZW4gcHJvcGVydHkuXG4gKiBAcGFyYW0ge3N0cmluZ30ga2V5IFByb3BlcnR5IG5hbWUgdG8gZGVmaW5lIHByb3BlcnR5IGRlc2NyaXB0b3IuXG4gKiBAcmV0dXJucyB7UHJvcGVydHlEZXNjcmlwdG9yfSBUaGUgcHJvcGVydHkgZGVzY3JpcHRvciB0byByZWRpcmVjdCB0aGUgcHJvcGVydHkuXG4gKiBAcHJpdmF0ZVxuICovXG5mdW5jdGlvbiBkZWZpbmVSZWRpcmVjdERlc2NyaXB0b3Ioa2V5KSB7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgZ2V0KCkge1xuICAgICAgICAgICAgcmV0dXJuIHBkKHRoaXMpLmV2ZW50W2tleV1cbiAgICAgICAgfSxcbiAgICAgICAgc2V0KHZhbHVlKSB7XG4gICAgICAgICAgICBwZCh0aGlzKS5ldmVudFtrZXldID0gdmFsdWU7XG4gICAgICAgIH0sXG4gICAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZSxcbiAgICAgICAgZW51bWVyYWJsZTogdHJ1ZSxcbiAgICB9XG59XG5cbi8qKlxuICogR2V0IHRoZSBwcm9wZXJ0eSBkZXNjcmlwdG9yIHRvIGNhbGwgYSBnaXZlbiBtZXRob2QgcHJvcGVydHkuXG4gKiBAcGFyYW0ge3N0cmluZ30ga2V5IFByb3BlcnR5IG5hbWUgdG8gZGVmaW5lIHByb3BlcnR5IGRlc2NyaXB0b3IuXG4gKiBAcmV0dXJucyB7UHJvcGVydHlEZXNjcmlwdG9yfSBUaGUgcHJvcGVydHkgZGVzY3JpcHRvciB0byBjYWxsIHRoZSBtZXRob2QgcHJvcGVydHkuXG4gKiBAcHJpdmF0ZVxuICovXG5mdW5jdGlvbiBkZWZpbmVDYWxsRGVzY3JpcHRvcihrZXkpIHtcbiAgICByZXR1cm4ge1xuICAgICAgICB2YWx1ZSgpIHtcbiAgICAgICAgICAgIGNvbnN0IGV2ZW50ID0gcGQodGhpcykuZXZlbnQ7XG4gICAgICAgICAgICByZXR1cm4gZXZlbnRba2V5XS5hcHBseShldmVudCwgYXJndW1lbnRzKVxuICAgICAgICB9LFxuICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgfVxufVxuXG4vKipcbiAqIERlZmluZSBuZXcgd3JhcHBlciBjbGFzcy5cbiAqIEBwYXJhbSB7RnVuY3Rpb259IEJhc2VFdmVudCBUaGUgYmFzZSB3cmFwcGVyIGNsYXNzLlxuICogQHBhcmFtIHtPYmplY3R9IHByb3RvIFRoZSBwcm90b3R5cGUgb2YgdGhlIG9yaWdpbmFsIGV2ZW50LlxuICogQHJldHVybnMge0Z1bmN0aW9ufSBUaGUgZGVmaW5lZCB3cmFwcGVyIGNsYXNzLlxuICogQHByaXZhdGVcbiAqL1xuZnVuY3Rpb24gZGVmaW5lV3JhcHBlcihCYXNlRXZlbnQsIHByb3RvKSB7XG4gICAgY29uc3Qga2V5cyA9IE9iamVjdC5rZXlzKHByb3RvKTtcbiAgICBpZiAoa2V5cy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgcmV0dXJuIEJhc2VFdmVudFxuICAgIH1cblxuICAgIC8qKiBDdXN0b21FdmVudCAqL1xuICAgIGZ1bmN0aW9uIEN1c3RvbUV2ZW50KGV2ZW50VGFyZ2V0LCBldmVudCkge1xuICAgICAgICBCYXNlRXZlbnQuY2FsbCh0aGlzLCBldmVudFRhcmdldCwgZXZlbnQpO1xuICAgIH1cblxuICAgIEN1c3RvbUV2ZW50LnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoQmFzZUV2ZW50LnByb3RvdHlwZSwge1xuICAgICAgICBjb25zdHJ1Y3RvcjogeyB2YWx1ZTogQ3VzdG9tRXZlbnQsIGNvbmZpZ3VyYWJsZTogdHJ1ZSwgd3JpdGFibGU6IHRydWUgfSxcbiAgICB9KTtcblxuICAgIC8vIERlZmluZSBhY2Nlc3NvcnMuXG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBrZXlzLmxlbmd0aDsgKytpKSB7XG4gICAgICAgIGNvbnN0IGtleSA9IGtleXNbaV07XG4gICAgICAgIGlmICghKGtleSBpbiBCYXNlRXZlbnQucHJvdG90eXBlKSkge1xuICAgICAgICAgICAgY29uc3QgZGVzY3JpcHRvciA9IE9iamVjdC5nZXRPd25Qcm9wZXJ0eURlc2NyaXB0b3IocHJvdG8sIGtleSk7XG4gICAgICAgICAgICBjb25zdCBpc0Z1bmMgPSB0eXBlb2YgZGVzY3JpcHRvci52YWx1ZSA9PT0gXCJmdW5jdGlvblwiO1xuICAgICAgICAgICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KFxuICAgICAgICAgICAgICAgIEN1c3RvbUV2ZW50LnByb3RvdHlwZSxcbiAgICAgICAgICAgICAgICBrZXksXG4gICAgICAgICAgICAgICAgaXNGdW5jXG4gICAgICAgICAgICAgICAgICAgID8gZGVmaW5lQ2FsbERlc2NyaXB0b3Ioa2V5KVxuICAgICAgICAgICAgICAgICAgICA6IGRlZmluZVJlZGlyZWN0RGVzY3JpcHRvcihrZXkpXG4gICAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIEN1c3RvbUV2ZW50XG59XG5cbi8qKlxuICogR2V0IHRoZSB3cmFwcGVyIGNsYXNzIG9mIGEgZ2l2ZW4gcHJvdG90eXBlLlxuICogQHBhcmFtIHtPYmplY3R9IHByb3RvIFRoZSBwcm90b3R5cGUgb2YgdGhlIG9yaWdpbmFsIGV2ZW50IHRvIGdldCBpdHMgd3JhcHBlci5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gVGhlIHdyYXBwZXIgY2xhc3MuXG4gKiBAcHJpdmF0ZVxuICovXG5mdW5jdGlvbiBnZXRXcmFwcGVyKHByb3RvKSB7XG4gICAgaWYgKHByb3RvID09IG51bGwgfHwgcHJvdG8gPT09IE9iamVjdC5wcm90b3R5cGUpIHtcbiAgICAgICAgcmV0dXJuIEV2ZW50XG4gICAgfVxuXG4gICAgbGV0IHdyYXBwZXIgPSB3cmFwcGVycy5nZXQocHJvdG8pO1xuICAgIGlmICh3cmFwcGVyID09IG51bGwpIHtcbiAgICAgICAgd3JhcHBlciA9IGRlZmluZVdyYXBwZXIoZ2V0V3JhcHBlcihPYmplY3QuZ2V0UHJvdG90eXBlT2YocHJvdG8pKSwgcHJvdG8pO1xuICAgICAgICB3cmFwcGVycy5zZXQocHJvdG8sIHdyYXBwZXIpO1xuICAgIH1cbiAgICByZXR1cm4gd3JhcHBlclxufVxuXG4vKipcbiAqIFdyYXAgYSBnaXZlbiBldmVudCB0byBtYW5hZ2VtZW50IGEgZGlzcGF0Y2hpbmcuXG4gKiBAcGFyYW0ge0V2ZW50VGFyZ2V0fSBldmVudFRhcmdldCBUaGUgZXZlbnQgdGFyZ2V0IG9mIHRoaXMgZGlzcGF0Y2hpbmcuXG4gKiBAcGFyYW0ge09iamVjdH0gZXZlbnQgVGhlIGV2ZW50IHRvIHdyYXAuXG4gKiBAcmV0dXJucyB7RXZlbnR9IFRoZSB3cmFwcGVyIGluc3RhbmNlLlxuICogQHByaXZhdGVcbiAqL1xuZnVuY3Rpb24gd3JhcEV2ZW50KGV2ZW50VGFyZ2V0LCBldmVudCkge1xuICAgIGNvbnN0IFdyYXBwZXIgPSBnZXRXcmFwcGVyKE9iamVjdC5nZXRQcm90b3R5cGVPZihldmVudCkpO1xuICAgIHJldHVybiBuZXcgV3JhcHBlcihldmVudFRhcmdldCwgZXZlbnQpXG59XG5cbi8qKlxuICogR2V0IHRoZSBpbW1lZGlhdGVTdG9wcGVkIGZsYWcgb2YgYSBnaXZlbiBldmVudC5cbiAqIEBwYXJhbSB7RXZlbnR9IGV2ZW50IFRoZSBldmVudCB0byBnZXQuXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gVGhlIGZsYWcgdG8gc3RvcCBwcm9wYWdhdGlvbiBpbW1lZGlhdGVseS5cbiAqIEBwcml2YXRlXG4gKi9cbmZ1bmN0aW9uIGlzU3RvcHBlZChldmVudCkge1xuICAgIHJldHVybiBwZChldmVudCkuaW1tZWRpYXRlU3RvcHBlZFxufVxuXG4vKipcbiAqIFNldCB0aGUgY3VycmVudCBldmVudCBwaGFzZSBvZiBhIGdpdmVuIGV2ZW50LlxuICogQHBhcmFtIHtFdmVudH0gZXZlbnQgVGhlIGV2ZW50IHRvIHNldCBjdXJyZW50IHRhcmdldC5cbiAqIEBwYXJhbSB7bnVtYmVyfSBldmVudFBoYXNlIE5ldyBldmVudCBwaGFzZS5cbiAqIEByZXR1cm5zIHt2b2lkfVxuICogQHByaXZhdGVcbiAqL1xuZnVuY3Rpb24gc2V0RXZlbnRQaGFzZShldmVudCwgZXZlbnRQaGFzZSkge1xuICAgIHBkKGV2ZW50KS5ldmVudFBoYXNlID0gZXZlbnRQaGFzZTtcbn1cblxuLyoqXG4gKiBTZXQgdGhlIGN1cnJlbnQgdGFyZ2V0IG9mIGEgZ2l2ZW4gZXZlbnQuXG4gKiBAcGFyYW0ge0V2ZW50fSBldmVudCBUaGUgZXZlbnQgdG8gc2V0IGN1cnJlbnQgdGFyZ2V0LlxuICogQHBhcmFtIHtFdmVudFRhcmdldHxudWxsfSBjdXJyZW50VGFyZ2V0IE5ldyBjdXJyZW50IHRhcmdldC5cbiAqIEByZXR1cm5zIHt2b2lkfVxuICogQHByaXZhdGVcbiAqL1xuZnVuY3Rpb24gc2V0Q3VycmVudFRhcmdldChldmVudCwgY3VycmVudFRhcmdldCkge1xuICAgIHBkKGV2ZW50KS5jdXJyZW50VGFyZ2V0ID0gY3VycmVudFRhcmdldDtcbn1cblxuLyoqXG4gKiBTZXQgYSBwYXNzaXZlIGxpc3RlbmVyIG9mIGEgZ2l2ZW4gZXZlbnQuXG4gKiBAcGFyYW0ge0V2ZW50fSBldmVudCBUaGUgZXZlbnQgdG8gc2V0IGN1cnJlbnQgdGFyZ2V0LlxuICogQHBhcmFtIHtGdW5jdGlvbnxudWxsfSBwYXNzaXZlTGlzdGVuZXIgTmV3IHBhc3NpdmUgbGlzdGVuZXIuXG4gKiBAcmV0dXJucyB7dm9pZH1cbiAqIEBwcml2YXRlXG4gKi9cbmZ1bmN0aW9uIHNldFBhc3NpdmVMaXN0ZW5lcihldmVudCwgcGFzc2l2ZUxpc3RlbmVyKSB7XG4gICAgcGQoZXZlbnQpLnBhc3NpdmVMaXN0ZW5lciA9IHBhc3NpdmVMaXN0ZW5lcjtcbn1cblxuLyoqXG4gKiBAdHlwZWRlZiB7b2JqZWN0fSBMaXN0ZW5lck5vZGVcbiAqIEBwcm9wZXJ0eSB7RnVuY3Rpb259IGxpc3RlbmVyXG4gKiBAcHJvcGVydHkgezF8MnwzfSBsaXN0ZW5lclR5cGVcbiAqIEBwcm9wZXJ0eSB7Ym9vbGVhbn0gcGFzc2l2ZVxuICogQHByb3BlcnR5IHtib29sZWFufSBvbmNlXG4gKiBAcHJvcGVydHkge0xpc3RlbmVyTm9kZXxudWxsfSBuZXh0XG4gKiBAcHJpdmF0ZVxuICovXG5cbi8qKlxuICogQHR5cGUge1dlYWtNYXA8b2JqZWN0LCBNYXA8c3RyaW5nLCBMaXN0ZW5lck5vZGU+Pn1cbiAqIEBwcml2YXRlXG4gKi9cbmNvbnN0IGxpc3RlbmVyc01hcCA9IG5ldyBXZWFrTWFwKCk7XG5cbi8vIExpc3RlbmVyIHR5cGVzXG5jb25zdCBDQVBUVVJFID0gMTtcbmNvbnN0IEJVQkJMRSA9IDI7XG5jb25zdCBBVFRSSUJVVEUgPSAzO1xuXG4vKipcbiAqIENoZWNrIHdoZXRoZXIgYSBnaXZlbiB2YWx1ZSBpcyBhbiBvYmplY3Qgb3Igbm90LlxuICogQHBhcmFtIHthbnl9IHggVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IGB0cnVlYCBpZiB0aGUgdmFsdWUgaXMgYW4gb2JqZWN0LlxuICovXG5mdW5jdGlvbiBpc09iamVjdCh4KSB7XG4gICAgcmV0dXJuIHggIT09IG51bGwgJiYgdHlwZW9mIHggPT09IFwib2JqZWN0XCIgLy9lc2xpbnQtZGlzYWJsZS1saW5lIG5vLXJlc3RyaWN0ZWQtc3ludGF4XG59XG5cbi8qKlxuICogR2V0IGxpc3RlbmVycy5cbiAqIEBwYXJhbSB7RXZlbnRUYXJnZXR9IGV2ZW50VGFyZ2V0IFRoZSBldmVudCB0YXJnZXQgdG8gZ2V0LlxuICogQHJldHVybnMge01hcDxzdHJpbmcsIExpc3RlbmVyTm9kZT59IFRoZSBsaXN0ZW5lcnMuXG4gKiBAcHJpdmF0ZVxuICovXG5mdW5jdGlvbiBnZXRMaXN0ZW5lcnMoZXZlbnRUYXJnZXQpIHtcbiAgICBjb25zdCBsaXN0ZW5lcnMgPSBsaXN0ZW5lcnNNYXAuZ2V0KGV2ZW50VGFyZ2V0KTtcbiAgICBpZiAobGlzdGVuZXJzID09IG51bGwpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcbiAgICAgICAgICAgIFwiJ3RoaXMnIGlzIGV4cGVjdGVkIGFuIEV2ZW50VGFyZ2V0IG9iamVjdCwgYnV0IGdvdCBhbm90aGVyIHZhbHVlLlwiXG4gICAgICAgIClcbiAgICB9XG4gICAgcmV0dXJuIGxpc3RlbmVyc1xufVxuXG4vKipcbiAqIEdldCB0aGUgcHJvcGVydHkgZGVzY3JpcHRvciBmb3IgdGhlIGV2ZW50IGF0dHJpYnV0ZSBvZiBhIGdpdmVuIGV2ZW50LlxuICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZSBUaGUgZXZlbnQgbmFtZSB0byBnZXQgcHJvcGVydHkgZGVzY3JpcHRvci5cbiAqIEByZXR1cm5zIHtQcm9wZXJ0eURlc2NyaXB0b3J9IFRoZSBwcm9wZXJ0eSBkZXNjcmlwdG9yLlxuICogQHByaXZhdGVcbiAqL1xuZnVuY3Rpb24gZGVmaW5lRXZlbnRBdHRyaWJ1dGVEZXNjcmlwdG9yKGV2ZW50TmFtZSkge1xuICAgIHJldHVybiB7XG4gICAgICAgIGdldCgpIHtcbiAgICAgICAgICAgIGNvbnN0IGxpc3RlbmVycyA9IGdldExpc3RlbmVycyh0aGlzKTtcbiAgICAgICAgICAgIGxldCBub2RlID0gbGlzdGVuZXJzLmdldChldmVudE5hbWUpO1xuICAgICAgICAgICAgd2hpbGUgKG5vZGUgIT0gbnVsbCkge1xuICAgICAgICAgICAgICAgIGlmIChub2RlLmxpc3RlbmVyVHlwZSA9PT0gQVRUUklCVVRFKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBub2RlLmxpc3RlbmVyXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIG5vZGUgPSBub2RlLm5leHQ7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gbnVsbFxuICAgICAgICB9LFxuXG4gICAgICAgIHNldChsaXN0ZW5lcikge1xuICAgICAgICAgICAgaWYgKHR5cGVvZiBsaXN0ZW5lciAhPT0gXCJmdW5jdGlvblwiICYmICFpc09iamVjdChsaXN0ZW5lcikpIHtcbiAgICAgICAgICAgICAgICBsaXN0ZW5lciA9IG51bGw7IC8vIGVzbGludC1kaXNhYmxlLWxpbmUgbm8tcGFyYW0tcmVhc3NpZ25cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IGxpc3RlbmVycyA9IGdldExpc3RlbmVycyh0aGlzKTtcblxuICAgICAgICAgICAgLy8gVHJhdmVyc2UgdG8gdGhlIHRhaWwgd2hpbGUgcmVtb3Zpbmcgb2xkIHZhbHVlLlxuICAgICAgICAgICAgbGV0IHByZXYgPSBudWxsO1xuICAgICAgICAgICAgbGV0IG5vZGUgPSBsaXN0ZW5lcnMuZ2V0KGV2ZW50TmFtZSk7XG4gICAgICAgICAgICB3aGlsZSAobm9kZSAhPSBudWxsKSB7XG4gICAgICAgICAgICAgICAgaWYgKG5vZGUubGlzdGVuZXJUeXBlID09PSBBVFRSSUJVVEUpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gUmVtb3ZlIG9sZCB2YWx1ZS5cbiAgICAgICAgICAgICAgICAgICAgaWYgKHByZXYgIT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHByZXYubmV4dCA9IG5vZGUubmV4dDtcbiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIGlmIChub2RlLm5leHQgIT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy5zZXQoZXZlbnROYW1lLCBub2RlLm5leHQpO1xuICAgICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLmRlbGV0ZShldmVudE5hbWUpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgcHJldiA9IG5vZGU7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgbm9kZSA9IG5vZGUubmV4dDtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gQWRkIG5ldyB2YWx1ZS5cbiAgICAgICAgICAgIGlmIChsaXN0ZW5lciAhPT0gbnVsbCkge1xuICAgICAgICAgICAgICAgIGNvbnN0IG5ld05vZGUgPSB7XG4gICAgICAgICAgICAgICAgICAgIGxpc3RlbmVyLFxuICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lclR5cGU6IEFUVFJJQlVURSxcbiAgICAgICAgICAgICAgICAgICAgcGFzc2l2ZTogZmFsc2UsXG4gICAgICAgICAgICAgICAgICAgIG9uY2U6IGZhbHNlLFxuICAgICAgICAgICAgICAgICAgICBuZXh0OiBudWxsLFxuICAgICAgICAgICAgICAgIH07XG4gICAgICAgICAgICAgICAgaWYgKHByZXYgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnNldChldmVudE5hbWUsIG5ld05vZGUpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIHByZXYubmV4dCA9IG5ld05vZGU7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgIGVudW1lcmFibGU6IHRydWUsXG4gICAgfVxufVxuXG4vKipcbiAqIERlZmluZSBhbiBldmVudCBhdHRyaWJ1dGUgKGUuZy4gYGV2ZW50VGFyZ2V0Lm9uY2xpY2tgKS5cbiAqIEBwYXJhbSB7T2JqZWN0fSBldmVudFRhcmdldFByb3RvdHlwZSBUaGUgZXZlbnQgdGFyZ2V0IHByb3RvdHlwZSB0byBkZWZpbmUgYW4gZXZlbnQgYXR0cmJpdGUuXG4gKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lIFRoZSBldmVudCBuYW1lIHRvIGRlZmluZS5cbiAqIEByZXR1cm5zIHt2b2lkfVxuICovXG5mdW5jdGlvbiBkZWZpbmVFdmVudEF0dHJpYnV0ZShldmVudFRhcmdldFByb3RvdHlwZSwgZXZlbnROYW1lKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KFxuICAgICAgICBldmVudFRhcmdldFByb3RvdHlwZSxcbiAgICAgICAgYG9uJHtldmVudE5hbWV9YCxcbiAgICAgICAgZGVmaW5lRXZlbnRBdHRyaWJ1dGVEZXNjcmlwdG9yKGV2ZW50TmFtZSlcbiAgICApO1xufVxuXG4vKipcbiAqIERlZmluZSBhIGN1c3RvbSBFdmVudFRhcmdldCB3aXRoIGV2ZW50IGF0dHJpYnV0ZXMuXG4gKiBAcGFyYW0ge3N0cmluZ1tdfSBldmVudE5hbWVzIEV2ZW50IG5hbWVzIGZvciBldmVudCBhdHRyaWJ1dGVzLlxuICogQHJldHVybnMge0V2ZW50VGFyZ2V0fSBUaGUgY3VzdG9tIEV2ZW50VGFyZ2V0LlxuICogQHByaXZhdGVcbiAqL1xuZnVuY3Rpb24gZGVmaW5lQ3VzdG9tRXZlbnRUYXJnZXQoZXZlbnROYW1lcykge1xuICAgIC8qKiBDdXN0b21FdmVudFRhcmdldCAqL1xuICAgIGZ1bmN0aW9uIEN1c3RvbUV2ZW50VGFyZ2V0KCkge1xuICAgICAgICBFdmVudFRhcmdldC5jYWxsKHRoaXMpO1xuICAgIH1cblxuICAgIEN1c3RvbUV2ZW50VGFyZ2V0LnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoRXZlbnRUYXJnZXQucHJvdG90eXBlLCB7XG4gICAgICAgIGNvbnN0cnVjdG9yOiB7XG4gICAgICAgICAgICB2YWx1ZTogQ3VzdG9tRXZlbnRUYXJnZXQsXG4gICAgICAgICAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgICAgICAgICB3cml0YWJsZTogdHJ1ZSxcbiAgICAgICAgfSxcbiAgICB9KTtcblxuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgZXZlbnROYW1lcy5sZW5ndGg7ICsraSkge1xuICAgICAgICBkZWZpbmVFdmVudEF0dHJpYnV0ZShDdXN0b21FdmVudFRhcmdldC5wcm90b3R5cGUsIGV2ZW50TmFtZXNbaV0pO1xuICAgIH1cblxuICAgIHJldHVybiBDdXN0b21FdmVudFRhcmdldFxufVxuXG4vKipcbiAqIEV2ZW50VGFyZ2V0LlxuICpcbiAqIC0gVGhpcyBpcyBjb25zdHJ1Y3RvciBpZiBubyBhcmd1bWVudHMuXG4gKiAtIFRoaXMgaXMgYSBmdW5jdGlvbiB3aGljaCByZXR1cm5zIGEgQ3VzdG9tRXZlbnRUYXJnZXQgY29uc3RydWN0b3IgaWYgdGhlcmUgYXJlIGFyZ3VtZW50cy5cbiAqXG4gKiBGb3IgZXhhbXBsZTpcbiAqXG4gKiAgICAgY2xhc3MgQSBleHRlbmRzIEV2ZW50VGFyZ2V0IHt9XG4gKiAgICAgY2xhc3MgQiBleHRlbmRzIEV2ZW50VGFyZ2V0KFwibWVzc2FnZVwiKSB7fVxuICogICAgIGNsYXNzIEMgZXh0ZW5kcyBFdmVudFRhcmdldChcIm1lc3NhZ2VcIiwgXCJlcnJvclwiKSB7fVxuICogICAgIGNsYXNzIEQgZXh0ZW5kcyBFdmVudFRhcmdldChbXCJtZXNzYWdlXCIsIFwiZXJyb3JcIl0pIHt9XG4gKi9cbmZ1bmN0aW9uIEV2ZW50VGFyZ2V0KCkge1xuICAgIC8qZXNsaW50LWRpc2FibGUgY29uc2lzdGVudC1yZXR1cm4gKi9cbiAgICBpZiAodGhpcyBpbnN0YW5jZW9mIEV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIGxpc3RlbmVyc01hcC5zZXQodGhpcywgbmV3IE1hcCgpKTtcbiAgICAgICAgcmV0dXJuXG4gICAgfVxuICAgIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAxICYmIEFycmF5LmlzQXJyYXkoYXJndW1lbnRzWzBdKSkge1xuICAgICAgICByZXR1cm4gZGVmaW5lQ3VzdG9tRXZlbnRUYXJnZXQoYXJndW1lbnRzWzBdKVxuICAgIH1cbiAgICBpZiAoYXJndW1lbnRzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgY29uc3QgdHlwZXMgPSBuZXcgQXJyYXkoYXJndW1lbnRzLmxlbmd0aCk7XG4gICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgYXJndW1lbnRzLmxlbmd0aDsgKytpKSB7XG4gICAgICAgICAgICB0eXBlc1tpXSA9IGFyZ3VtZW50c1tpXTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gZGVmaW5lQ3VzdG9tRXZlbnRUYXJnZXQodHlwZXMpXG4gICAgfVxuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb25cIilcbiAgICAvKmVzbGludC1lbmFibGUgY29uc2lzdGVudC1yZXR1cm4gKi9cbn1cblxuLy8gU2hvdWxkIGJlIGVudW1lcmFibGUsIGJ1dCBjbGFzcyBtZXRob2RzIGFyZSBub3QgZW51bWVyYWJsZS5cbkV2ZW50VGFyZ2V0LnByb3RvdHlwZSA9IHtcbiAgICAvKipcbiAgICAgKiBBZGQgYSBnaXZlbiBsaXN0ZW5lciB0byB0aGlzIGV2ZW50IHRhcmdldC5cbiAgICAgKiBAcGFyYW0ge3N0cmluZ30gZXZlbnROYW1lIFRoZSBldmVudCBuYW1lIHRvIGFkZC5cbiAgICAgKiBAcGFyYW0ge0Z1bmN0aW9ufSBsaXN0ZW5lciBUaGUgbGlzdGVuZXIgdG8gYWRkLlxuICAgICAqIEBwYXJhbSB7Ym9vbGVhbnx7Y2FwdHVyZT86Ym9vbGVhbixwYXNzaXZlPzpib29sZWFuLG9uY2U/OmJvb2xlYW59fSBbb3B0aW9uc10gVGhlIG9wdGlvbnMgZm9yIHRoaXMgbGlzdGVuZXIuXG4gICAgICogQHJldHVybnMge3ZvaWR9XG4gICAgICovXG4gICAgYWRkRXZlbnRMaXN0ZW5lcihldmVudE5hbWUsIGxpc3RlbmVyLCBvcHRpb25zKSB7XG4gICAgICAgIGlmIChsaXN0ZW5lciA9PSBudWxsKSB7XG4gICAgICAgICAgICByZXR1cm5cbiAgICAgICAgfVxuICAgICAgICBpZiAodHlwZW9mIGxpc3RlbmVyICE9PSBcImZ1bmN0aW9uXCIgJiYgIWlzT2JqZWN0KGxpc3RlbmVyKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIidsaXN0ZW5lcicgc2hvdWxkIGJlIGEgZnVuY3Rpb24gb3IgYW4gb2JqZWN0LlwiKVxuICAgICAgICB9XG5cbiAgICAgICAgY29uc3QgbGlzdGVuZXJzID0gZ2V0TGlzdGVuZXJzKHRoaXMpO1xuICAgICAgICBjb25zdCBvcHRpb25zSXNPYmogPSBpc09iamVjdChvcHRpb25zKTtcbiAgICAgICAgY29uc3QgY2FwdHVyZSA9IG9wdGlvbnNJc09ialxuICAgICAgICAgICAgPyBCb29sZWFuKG9wdGlvbnMuY2FwdHVyZSlcbiAgICAgICAgICAgIDogQm9vbGVhbihvcHRpb25zKTtcbiAgICAgICAgY29uc3QgbGlzdGVuZXJUeXBlID0gY2FwdHVyZSA/IENBUFRVUkUgOiBCVUJCTEU7XG4gICAgICAgIGNvbnN0IG5ld05vZGUgPSB7XG4gICAgICAgICAgICBsaXN0ZW5lcixcbiAgICAgICAgICAgIGxpc3RlbmVyVHlwZSxcbiAgICAgICAgICAgIHBhc3NpdmU6IG9wdGlvbnNJc09iaiAmJiBCb29sZWFuKG9wdGlvbnMucGFzc2l2ZSksXG4gICAgICAgICAgICBvbmNlOiBvcHRpb25zSXNPYmogJiYgQm9vbGVhbihvcHRpb25zLm9uY2UpLFxuICAgICAgICAgICAgbmV4dDogbnVsbCxcbiAgICAgICAgfTtcblxuICAgICAgICAvLyBTZXQgaXQgYXMgdGhlIGZpcnN0IG5vZGUgaWYgdGhlIGZpcnN0IG5vZGUgaXMgbnVsbC5cbiAgICAgICAgbGV0IG5vZGUgPSBsaXN0ZW5lcnMuZ2V0KGV2ZW50TmFtZSk7XG4gICAgICAgIGlmIChub2RlID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIGxpc3RlbmVycy5zZXQoZXZlbnROYW1lLCBuZXdOb2RlKTtcbiAgICAgICAgICAgIHJldHVyblxuICAgICAgICB9XG5cbiAgICAgICAgLy8gVHJhdmVyc2UgdG8gdGhlIHRhaWwgd2hpbGUgY2hlY2tpbmcgZHVwbGljYXRpb24uLlxuICAgICAgICBsZXQgcHJldiA9IG51bGw7XG4gICAgICAgIHdoaWxlIChub2RlICE9IG51bGwpIHtcbiAgICAgICAgICAgIGlmIChcbiAgICAgICAgICAgICAgICBub2RlLmxpc3RlbmVyID09PSBsaXN0ZW5lciAmJlxuICAgICAgICAgICAgICAgIG5vZGUubGlzdGVuZXJUeXBlID09PSBsaXN0ZW5lclR5cGVcbiAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAgIC8vIFNob3VsZCBpZ25vcmUgZHVwbGljYXRpb24uXG4gICAgICAgICAgICAgICAgcmV0dXJuXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBwcmV2ID0gbm9kZTtcbiAgICAgICAgICAgIG5vZGUgPSBub2RlLm5leHQ7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBBZGQgaXQuXG4gICAgICAgIHByZXYubmV4dCA9IG5ld05vZGU7XG4gICAgfSxcblxuICAgIC8qKlxuICAgICAqIFJlbW92ZSBhIGdpdmVuIGxpc3RlbmVyIGZyb20gdGhpcyBldmVudCB0YXJnZXQuXG4gICAgICogQHBhcmFtIHtzdHJpbmd9IGV2ZW50TmFtZSBUaGUgZXZlbnQgbmFtZSB0byByZW1vdmUuXG4gICAgICogQHBhcmFtIHtGdW5jdGlvbn0gbGlzdGVuZXIgVGhlIGxpc3RlbmVyIHRvIHJlbW92ZS5cbiAgICAgKiBAcGFyYW0ge2Jvb2xlYW58e2NhcHR1cmU/OmJvb2xlYW4scGFzc2l2ZT86Ym9vbGVhbixvbmNlPzpib29sZWFufX0gW29wdGlvbnNdIFRoZSBvcHRpb25zIGZvciB0aGlzIGxpc3RlbmVyLlxuICAgICAqIEByZXR1cm5zIHt2b2lkfVxuICAgICAqL1xuICAgIHJlbW92ZUV2ZW50TGlzdGVuZXIoZXZlbnROYW1lLCBsaXN0ZW5lciwgb3B0aW9ucykge1xuICAgICAgICBpZiAobGlzdGVuZXIgPT0gbnVsbCkge1xuICAgICAgICAgICAgcmV0dXJuXG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBsaXN0ZW5lcnMgPSBnZXRMaXN0ZW5lcnModGhpcyk7XG4gICAgICAgIGNvbnN0IGNhcHR1cmUgPSBpc09iamVjdChvcHRpb25zKVxuICAgICAgICAgICAgPyBCb29sZWFuKG9wdGlvbnMuY2FwdHVyZSlcbiAgICAgICAgICAgIDogQm9vbGVhbihvcHRpb25zKTtcbiAgICAgICAgY29uc3QgbGlzdGVuZXJUeXBlID0gY2FwdHVyZSA/IENBUFRVUkUgOiBCVUJCTEU7XG5cbiAgICAgICAgbGV0IHByZXYgPSBudWxsO1xuICAgICAgICBsZXQgbm9kZSA9IGxpc3RlbmVycy5nZXQoZXZlbnROYW1lKTtcbiAgICAgICAgd2hpbGUgKG5vZGUgIT0gbnVsbCkge1xuICAgICAgICAgICAgaWYgKFxuICAgICAgICAgICAgICAgIG5vZGUubGlzdGVuZXIgPT09IGxpc3RlbmVyICYmXG4gICAgICAgICAgICAgICAgbm9kZS5saXN0ZW5lclR5cGUgPT09IGxpc3RlbmVyVHlwZVxuICAgICAgICAgICAgKSB7XG4gICAgICAgICAgICAgICAgaWYgKHByZXYgIT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgcHJldi5uZXh0ID0gbm9kZS5uZXh0O1xuICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAobm9kZS5uZXh0ICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy5zZXQoZXZlbnROYW1lLCBub2RlLm5leHQpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy5kZWxldGUoZXZlbnROYW1lKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuXG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHByZXYgPSBub2RlO1xuICAgICAgICAgICAgbm9kZSA9IG5vZGUubmV4dDtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICAvKipcbiAgICAgKiBEaXNwYXRjaCBhIGdpdmVuIGV2ZW50LlxuICAgICAqIEBwYXJhbSB7RXZlbnR8e3R5cGU6c3RyaW5nfX0gZXZlbnQgVGhlIGV2ZW50IHRvIGRpc3BhdGNoLlxuICAgICAqIEByZXR1cm5zIHtib29sZWFufSBgZmFsc2VgIGlmIGNhbmNlbGVkLlxuICAgICAqL1xuICAgIGRpc3BhdGNoRXZlbnQoZXZlbnQpIHtcbiAgICAgICAgaWYgKGV2ZW50ID09IG51bGwgfHwgdHlwZW9mIGV2ZW50LnR5cGUgIT09IFwic3RyaW5nXCIpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ1wiZXZlbnQudHlwZVwiIHNob3VsZCBiZSBhIHN0cmluZy4nKVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gSWYgbGlzdGVuZXJzIGFyZW4ndCByZWdpc3RlcmVkLCB0ZXJtaW5hdGUuXG4gICAgICAgIGNvbnN0IGxpc3RlbmVycyA9IGdldExpc3RlbmVycyh0aGlzKTtcbiAgICAgICAgY29uc3QgZXZlbnROYW1lID0gZXZlbnQudHlwZTtcbiAgICAgICAgbGV0IG5vZGUgPSBsaXN0ZW5lcnMuZ2V0KGV2ZW50TmFtZSk7XG4gICAgICAgIGlmIChub2RlID09IG51bGwpIHtcbiAgICAgICAgICAgIHJldHVybiB0cnVlXG4gICAgICAgIH1cblxuICAgICAgICAvLyBTaW5jZSB3ZSBjYW5ub3QgcmV3cml0ZSBzZXZlcmFsIHByb3BlcnRpZXMsIHNvIHdyYXAgb2JqZWN0LlxuICAgICAgICBjb25zdCB3cmFwcGVkRXZlbnQgPSB3cmFwRXZlbnQodGhpcywgZXZlbnQpO1xuXG4gICAgICAgIC8vIFRoaXMgZG9lc24ndCBwcm9jZXNzIGNhcHR1cmluZyBwaGFzZSBhbmQgYnViYmxpbmcgcGhhc2UuXG4gICAgICAgIC8vIFRoaXMgaXNuJ3QgcGFydGljaXBhdGluZyBpbiBhIHRyZWUuXG4gICAgICAgIGxldCBwcmV2ID0gbnVsbDtcbiAgICAgICAgd2hpbGUgKG5vZGUgIT0gbnVsbCkge1xuICAgICAgICAgICAgLy8gUmVtb3ZlIHRoaXMgbGlzdGVuZXIgaWYgaXQncyBvbmNlXG4gICAgICAgICAgICBpZiAobm9kZS5vbmNlKSB7XG4gICAgICAgICAgICAgICAgaWYgKHByZXYgIT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICAgICAgcHJldi5uZXh0ID0gbm9kZS5uZXh0O1xuICAgICAgICAgICAgICAgIH0gZWxzZSBpZiAobm9kZS5uZXh0ICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy5zZXQoZXZlbnROYW1lLCBub2RlLm5leHQpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy5kZWxldGUoZXZlbnROYW1lKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgIHByZXYgPSBub2RlO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBDYWxsIHRoaXMgbGlzdGVuZXJcbiAgICAgICAgICAgIHNldFBhc3NpdmVMaXN0ZW5lcihcbiAgICAgICAgICAgICAgICB3cmFwcGVkRXZlbnQsXG4gICAgICAgICAgICAgICAgbm9kZS5wYXNzaXZlID8gbm9kZS5saXN0ZW5lciA6IG51bGxcbiAgICAgICAgICAgICk7XG4gICAgICAgICAgICBpZiAodHlwZW9mIG5vZGUubGlzdGVuZXIgPT09IFwiZnVuY3Rpb25cIikge1xuICAgICAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgICAgICAgIG5vZGUubGlzdGVuZXIuY2FsbCh0aGlzLCB3cmFwcGVkRXZlbnQpO1xuICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgICAgICAgICAgICB0eXBlb2YgY29uc29sZSAhPT0gXCJ1bmRlZmluZWRcIiAmJlxuICAgICAgICAgICAgICAgICAgICAgICAgdHlwZW9mIGNvbnNvbGUuZXJyb3IgPT09IFwiZnVuY3Rpb25cIlxuICAgICAgICAgICAgICAgICAgICApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZXJyKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0gZWxzZSBpZiAoXG4gICAgICAgICAgICAgICAgbm9kZS5saXN0ZW5lclR5cGUgIT09IEFUVFJJQlVURSAmJlxuICAgICAgICAgICAgICAgIHR5cGVvZiBub2RlLmxpc3RlbmVyLmhhbmRsZUV2ZW50ID09PSBcImZ1bmN0aW9uXCJcbiAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAgIG5vZGUubGlzdGVuZXIuaGFuZGxlRXZlbnQod3JhcHBlZEV2ZW50KTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gQnJlYWsgaWYgYGV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbmAgd2FzIGNhbGxlZC5cbiAgICAgICAgICAgIGlmIChpc1N0b3BwZWQod3JhcHBlZEV2ZW50KSkge1xuICAgICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIG5vZGUgPSBub2RlLm5leHQ7XG4gICAgICAgIH1cbiAgICAgICAgc2V0UGFzc2l2ZUxpc3RlbmVyKHdyYXBwZWRFdmVudCwgbnVsbCk7XG4gICAgICAgIHNldEV2ZW50UGhhc2Uod3JhcHBlZEV2ZW50LCAwKTtcbiAgICAgICAgc2V0Q3VycmVudFRhcmdldCh3cmFwcGVkRXZlbnQsIG51bGwpO1xuXG4gICAgICAgIHJldHVybiAhd3JhcHBlZEV2ZW50LmRlZmF1bHRQcmV2ZW50ZWRcbiAgICB9LFxufTtcblxuLy8gYGNvbnN0cnVjdG9yYCBpcyBub3QgZW51bWVyYWJsZS5cbk9iamVjdC5kZWZpbmVQcm9wZXJ0eShFdmVudFRhcmdldC5wcm90b3R5cGUsIFwiY29uc3RydWN0b3JcIiwge1xuICAgIHZhbHVlOiBFdmVudFRhcmdldCxcbiAgICBjb25maWd1cmFibGU6IHRydWUsXG4gICAgd3JpdGFibGU6IHRydWUsXG59KTtcblxuLy8gRW5zdXJlIGBldmVudFRhcmdldCBpbnN0YW5jZW9mIHdpbmRvdy5FdmVudFRhcmdldGAgaXMgYHRydWVgLlxuaWYgKFxuICAgIHR5cGVvZiB3aW5kb3cgIT09IFwidW5kZWZpbmVkXCIgJiZcbiAgICB0eXBlb2Ygd2luZG93LkV2ZW50VGFyZ2V0ICE9PSBcInVuZGVmaW5lZFwiXG4pIHtcbiAgICBPYmplY3Quc2V0UHJvdG90eXBlT2YoRXZlbnRUYXJnZXQucHJvdG90eXBlLCB3aW5kb3cuRXZlbnRUYXJnZXQucHJvdG90eXBlKTtcbn1cblxuZXhwb3J0cy5kZWZpbmVFdmVudEF0dHJpYnV0ZSA9IGRlZmluZUV2ZW50QXR0cmlidXRlO1xuZXhwb3J0cy5FdmVudFRhcmdldCA9IEV2ZW50VGFyZ2V0O1xuZXhwb3J0cy5kZWZhdWx0ID0gRXZlbnRUYXJnZXQ7XG5cbm1vZHVsZS5leHBvcnRzID0gRXZlbnRUYXJnZXRcbm1vZHVsZS5leHBvcnRzLkV2ZW50VGFyZ2V0ID0gbW9kdWxlLmV4cG9ydHNbXCJkZWZhdWx0XCJdID0gRXZlbnRUYXJnZXRcbm1vZHVsZS5leHBvcnRzLmRlZmluZUV2ZW50QXR0cmlidXRlID0gZGVmaW5lRXZlbnRBdHRyaWJ1dGVcbi8vIyBzb3VyY2VNYXBwaW5nVVJMPWV2ZW50LXRhcmdldC1zaGltLmpzLm1hcFxuIiwiY29uc3Qge1JlYWRhYmxlfSA9IHJlcXVpcmUoJ3N0cmVhbScpO1xuXG4vKipcbiAqIEB0eXBlIHtXZWFrTWFwPEJsb2IsIHt0eXBlOiBzdHJpbmcsIHNpemU6IG51bWJlciwgcGFydHM6IChCbG9iIHwgQnVmZmVyKVtdIH0+fVxuICovXG5jb25zdCB3bSA9IG5ldyBXZWFrTWFwKCk7XG5cbmFzeW5jIGZ1bmN0aW9uICogcmVhZChwYXJ0cykge1xuXHRmb3IgKGNvbnN0IHBhcnQgb2YgcGFydHMpIHtcblx0XHRpZiAoJ3N0cmVhbScgaW4gcGFydCkge1xuXHRcdFx0eWllbGQgKiBwYXJ0LnN0cmVhbSgpO1xuXHRcdH0gZWxzZSB7XG5cdFx0XHR5aWVsZCBwYXJ0O1xuXHRcdH1cblx0fVxufVxuXG5jbGFzcyBCbG9iIHtcblx0LyoqXG5cdCAqIFRoZSBCbG9iKCkgY29uc3RydWN0b3IgcmV0dXJucyBhIG5ldyBCbG9iIG9iamVjdC4gVGhlIGNvbnRlbnRcblx0ICogb2YgdGhlIGJsb2IgY29uc2lzdHMgb2YgdGhlIGNvbmNhdGVuYXRpb24gb2YgdGhlIHZhbHVlcyBnaXZlblxuXHQgKiBpbiB0aGUgcGFyYW1ldGVyIGFycmF5LlxuXHQgKlxuXHQgKiBAcGFyYW0geyhBcnJheUJ1ZmZlckxpa2UgfCBBcnJheUJ1ZmZlclZpZXcgfCBCbG9iIHwgQnVmZmVyIHwgc3RyaW5nKVtdfSBibG9iUGFydHNcblx0ICogQHBhcmFtIHt7IHR5cGU/OiBzdHJpbmcgfX0gW29wdGlvbnNdXG5cdCAqL1xuXHRjb25zdHJ1Y3RvcihibG9iUGFydHMgPSBbXSwgb3B0aW9ucyA9IHt0eXBlOiAnJ30pIHtcblx0XHRsZXQgc2l6ZSA9IDA7XG5cblx0XHRjb25zdCBwYXJ0cyA9IGJsb2JQYXJ0cy5tYXAoZWxlbWVudCA9PiB7XG5cdFx0XHRsZXQgYnVmZmVyO1xuXHRcdFx0aWYgKGVsZW1lbnQgaW5zdGFuY2VvZiBCdWZmZXIpIHtcblx0XHRcdFx0YnVmZmVyID0gZWxlbWVudDtcblx0XHRcdH0gZWxzZSBpZiAoQXJyYXlCdWZmZXIuaXNWaWV3KGVsZW1lbnQpKSB7XG5cdFx0XHRcdGJ1ZmZlciA9IEJ1ZmZlci5mcm9tKGVsZW1lbnQuYnVmZmVyLCBlbGVtZW50LmJ5dGVPZmZzZXQsIGVsZW1lbnQuYnl0ZUxlbmd0aCk7XG5cdFx0XHR9IGVsc2UgaWYgKGVsZW1lbnQgaW5zdGFuY2VvZiBBcnJheUJ1ZmZlcikge1xuXHRcdFx0XHRidWZmZXIgPSBCdWZmZXIuZnJvbShlbGVtZW50KTtcblx0XHRcdH0gZWxzZSBpZiAoZWxlbWVudCBpbnN0YW5jZW9mIEJsb2IpIHtcblx0XHRcdFx0YnVmZmVyID0gZWxlbWVudDtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdGJ1ZmZlciA9IEJ1ZmZlci5mcm9tKHR5cGVvZiBlbGVtZW50ID09PSAnc3RyaW5nJyA/IGVsZW1lbnQgOiBTdHJpbmcoZWxlbWVudCkpO1xuXHRcdFx0fVxuXG5cdFx0XHRzaXplICs9IGJ1ZmZlci5sZW5ndGggfHwgYnVmZmVyLnNpemUgfHwgMDtcblx0XHRcdHJldHVybiBidWZmZXI7XG5cdFx0fSk7XG5cblx0XHRjb25zdCB0eXBlID0gb3B0aW9ucy50eXBlID09PSB1bmRlZmluZWQgPyAnJyA6IFN0cmluZyhvcHRpb25zLnR5cGUpLnRvTG93ZXJDYXNlKCk7XG5cblx0XHR3bS5zZXQodGhpcywge1xuXHRcdFx0dHlwZTogL1teXFx1MDAyMC1cXHUwMDdFXS8udGVzdCh0eXBlKSA/ICcnIDogdHlwZSxcblx0XHRcdHNpemUsXG5cdFx0XHRwYXJ0c1xuXHRcdH0pO1xuXHR9XG5cblx0LyoqXG5cdCAqIFRoZSBCbG9iIGludGVyZmFjZSdzIHNpemUgcHJvcGVydHkgcmV0dXJucyB0aGVcblx0ICogc2l6ZSBvZiB0aGUgQmxvYiBpbiBieXRlcy5cblx0ICovXG5cdGdldCBzaXplKCkge1xuXHRcdHJldHVybiB3bS5nZXQodGhpcykuc2l6ZTtcblx0fVxuXG5cdC8qKlxuXHQgKiBUaGUgdHlwZSBwcm9wZXJ0eSBvZiBhIEJsb2Igb2JqZWN0IHJldHVybnMgdGhlIE1JTUUgdHlwZSBvZiB0aGUgZmlsZS5cblx0ICovXG5cdGdldCB0eXBlKCkge1xuXHRcdHJldHVybiB3bS5nZXQodGhpcykudHlwZTtcblx0fVxuXG5cdC8qKlxuXHQgKiBUaGUgdGV4dCgpIG1ldGhvZCBpbiB0aGUgQmxvYiBpbnRlcmZhY2UgcmV0dXJucyBhIFByb21pc2Vcblx0ICogdGhhdCByZXNvbHZlcyB3aXRoIGEgc3RyaW5nIGNvbnRhaW5pbmcgdGhlIGNvbnRlbnRzIG9mXG5cdCAqIHRoZSBibG9iLCBpbnRlcnByZXRlZCBhcyBVVEYtOC5cblx0ICpcblx0ICogQHJldHVybiB7UHJvbWlzZTxzdHJpbmc+fVxuXHQgKi9cblx0YXN5bmMgdGV4dCgpIHtcblx0XHRyZXR1cm4gQnVmZmVyLmZyb20oYXdhaXQgdGhpcy5hcnJheUJ1ZmZlcigpKS50b1N0cmluZygpO1xuXHR9XG5cblx0LyoqXG5cdCAqIFRoZSBhcnJheUJ1ZmZlcigpIG1ldGhvZCBpbiB0aGUgQmxvYiBpbnRlcmZhY2UgcmV0dXJucyBhXG5cdCAqIFByb21pc2UgdGhhdCByZXNvbHZlcyB3aXRoIHRoZSBjb250ZW50cyBvZiB0aGUgYmxvYiBhc1xuXHQgKiBiaW5hcnkgZGF0YSBjb250YWluZWQgaW4gYW4gQXJyYXlCdWZmZXIuXG5cdCAqXG5cdCAqIEByZXR1cm4ge1Byb21pc2U8QXJyYXlCdWZmZXI+fVxuXHQgKi9cblx0YXN5bmMgYXJyYXlCdWZmZXIoKSB7XG5cdFx0Y29uc3QgZGF0YSA9IG5ldyBVaW50OEFycmF5KHRoaXMuc2l6ZSk7XG5cdFx0bGV0IG9mZnNldCA9IDA7XG5cdFx0Zm9yIGF3YWl0IChjb25zdCBjaHVuayBvZiB0aGlzLnN0cmVhbSgpKSB7XG5cdFx0XHRkYXRhLnNldChjaHVuaywgb2Zmc2V0KTtcblx0XHRcdG9mZnNldCArPSBjaHVuay5sZW5ndGg7XG5cdFx0fVxuXG5cdFx0cmV0dXJuIGRhdGEuYnVmZmVyO1xuXHR9XG5cblx0LyoqXG5cdCAqIFRoZSBCbG9iIGludGVyZmFjZSdzIHN0cmVhbSgpIG1ldGhvZCBpcyBkaWZmZXJlbmNlIGZyb20gbmF0aXZlXG5cdCAqIGFuZCB1c2VzIG5vZGUgc3RyZWFtcyBpbnN0ZWFkIG9mIHdoYXR3ZyBzdHJlYW1zLlxuXHQgKlxuXHQgKiBAcmV0dXJucyB7UmVhZGFibGV9IE5vZGUgcmVhZGFibGUgc3RyZWFtXG5cdCAqL1xuXHRzdHJlYW0oKSB7XG5cdFx0cmV0dXJuIFJlYWRhYmxlLmZyb20ocmVhZCh3bS5nZXQodGhpcykucGFydHMpKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBUaGUgQmxvYiBpbnRlcmZhY2UncyBzbGljZSgpIG1ldGhvZCBjcmVhdGVzIGFuZCByZXR1cm5zIGFcblx0ICogbmV3IEJsb2Igb2JqZWN0IHdoaWNoIGNvbnRhaW5zIGRhdGEgZnJvbSBhIHN1YnNldCBvZiB0aGVcblx0ICogYmxvYiBvbiB3aGljaCBpdCdzIGNhbGxlZC5cblx0ICpcblx0ICogQHBhcmFtIHtudW1iZXJ9IFtzdGFydF1cblx0ICogQHBhcmFtIHtudW1iZXJ9IFtlbmRdXG5cdCAqIEBwYXJhbSB7c3RyaW5nfSBbdHlwZV1cblx0ICovXG5cdHNsaWNlKHN0YXJ0ID0gMCwgZW5kID0gdGhpcy5zaXplLCB0eXBlID0gJycpIHtcblx0XHRjb25zdCB7c2l6ZX0gPSB0aGlzO1xuXG5cdFx0bGV0IHJlbGF0aXZlU3RhcnQgPSBzdGFydCA8IDAgPyBNYXRoLm1heChzaXplICsgc3RhcnQsIDApIDogTWF0aC5taW4oc3RhcnQsIHNpemUpO1xuXHRcdGxldCByZWxhdGl2ZUVuZCA9IGVuZCA8IDAgPyBNYXRoLm1heChzaXplICsgZW5kLCAwKSA6IE1hdGgubWluKGVuZCwgc2l6ZSk7XG5cblx0XHRjb25zdCBzcGFuID0gTWF0aC5tYXgocmVsYXRpdmVFbmQgLSByZWxhdGl2ZVN0YXJ0LCAwKTtcblx0XHRjb25zdCBwYXJ0cyA9IHdtLmdldCh0aGlzKS5wYXJ0cy52YWx1ZXMoKTtcblx0XHRjb25zdCBibG9iUGFydHMgPSBbXTtcblx0XHRsZXQgYWRkZWQgPSAwO1xuXG5cdFx0Zm9yIChjb25zdCBwYXJ0IG9mIHBhcnRzKSB7XG5cdFx0XHRjb25zdCBzaXplID0gQXJyYXlCdWZmZXIuaXNWaWV3KHBhcnQpID8gcGFydC5ieXRlTGVuZ3RoIDogcGFydC5zaXplO1xuXHRcdFx0aWYgKHJlbGF0aXZlU3RhcnQgJiYgc2l6ZSA8PSByZWxhdGl2ZVN0YXJ0KSB7XG5cdFx0XHRcdC8vIFNraXAgdGhlIGJlZ2lubmluZyBhbmQgY2hhbmdlIHRoZSByZWxhdGl2ZVxuXHRcdFx0XHQvLyBzdGFydCAmIGVuZCBwb3NpdGlvbiBhcyB3ZSBza2lwIHRoZSB1bndhbnRlZCBwYXJ0c1xuXHRcdFx0XHRyZWxhdGl2ZVN0YXJ0IC09IHNpemU7XG5cdFx0XHRcdHJlbGF0aXZlRW5kIC09IHNpemU7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRjb25zdCBjaHVuayA9IHBhcnQuc2xpY2UocmVsYXRpdmVTdGFydCwgTWF0aC5taW4oc2l6ZSwgcmVsYXRpdmVFbmQpKTtcblx0XHRcdFx0YmxvYlBhcnRzLnB1c2goY2h1bmspO1xuXHRcdFx0XHRhZGRlZCArPSBBcnJheUJ1ZmZlci5pc1ZpZXcoY2h1bmspID8gY2h1bmsuYnl0ZUxlbmd0aCA6IGNodW5rLnNpemU7XG5cdFx0XHRcdHJlbGF0aXZlU3RhcnQgPSAwOyAvLyBBbGwgbmV4dCBzZXF1ZW50YWwgcGFydHMgc2hvdWxkIHN0YXJ0IGF0IDBcblxuXHRcdFx0XHQvLyBkb24ndCBhZGQgdGhlIG92ZXJmbG93IHRvIG5ldyBibG9iUGFydHNcblx0XHRcdFx0aWYgKGFkZGVkID49IHNwYW4pIHtcblx0XHRcdFx0XHRicmVhaztcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH1cblxuXHRcdGNvbnN0IGJsb2IgPSBuZXcgQmxvYihbXSwge3R5cGV9KTtcblx0XHRPYmplY3QuYXNzaWduKHdtLmdldChibG9iKSwge3NpemU6IHNwYW4sIHBhcnRzOiBibG9iUGFydHN9KTtcblxuXHRcdHJldHVybiBibG9iO1xuXHR9XG5cblx0Z2V0IFtTeW1ib2wudG9TdHJpbmdUYWddKCkge1xuXHRcdHJldHVybiAnQmxvYic7XG5cdH1cblxuXHRzdGF0aWMgW1N5bWJvbC5oYXNJbnN0YW5jZV0ob2JqZWN0KSB7XG5cdFx0cmV0dXJuIChcblx0XHRcdHR5cGVvZiBvYmplY3QgPT09ICdvYmplY3QnICYmXG5cdFx0XHR0eXBlb2Ygb2JqZWN0LnN0cmVhbSA9PT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdFx0b2JqZWN0LnN0cmVhbS5sZW5ndGggPT09IDAgJiZcblx0XHRcdHR5cGVvZiBvYmplY3QuY29uc3RydWN0b3IgPT09ICdmdW5jdGlvbicgJiZcblx0XHRcdC9eKEJsb2J8RmlsZSkkLy50ZXN0KG9iamVjdFtTeW1ib2wudG9TdHJpbmdUYWddKVxuXHRcdCk7XG5cdH1cbn1cblxuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoQmxvYi5wcm90b3R5cGUsIHtcblx0c2l6ZToge2VudW1lcmFibGU6IHRydWV9LFxuXHR0eXBlOiB7ZW51bWVyYWJsZTogdHJ1ZX0sXG5cdHNsaWNlOiB7ZW51bWVyYWJsZTogdHJ1ZX1cbn0pO1xuXG5tb2R1bGUuZXhwb3J0cyA9IEJsb2I7XG4iLCIndXNlIHN0cmljdCc7XG5jb25zdCBmZXRjaCA9IHJlcXVpcmUoJ25vZGUtZmV0Y2gnKTtcbmNvbnN0IEFib3J0Q29udHJvbGxlciA9IHJlcXVpcmUoJ2Fib3J0LWNvbnRyb2xsZXInKTtcblxuY29uc3QgVEVOX01FR0FCWVRFUyA9IDEwMDAgKiAxMDAwICogMTA7XG5cbmlmICghZ2xvYmFsLmZldGNoKSB7XG5cdGdsb2JhbC5mZXRjaCA9ICh1cmwsIG9wdGlvbnMpID0+IGZldGNoKHVybCwge2hpZ2hXYXRlck1hcms6IFRFTl9NRUdBQllURVMsIC4uLm9wdGlvbnN9KTtcbn1cblxuaWYgKCFnbG9iYWwuSGVhZGVycykge1xuXHRnbG9iYWwuSGVhZGVycyA9IGZldGNoLkhlYWRlcnM7XG59XG5cbmlmICghZ2xvYmFsLlJlcXVlc3QpIHtcblx0Z2xvYmFsLlJlcXVlc3QgPSBmZXRjaC5SZXF1ZXN0O1xufVxuXG5pZiAoIWdsb2JhbC5SZXNwb25zZSkge1xuXHRnbG9iYWwuUmVzcG9uc2UgPSBmZXRjaC5SZXNwb25zZTtcbn1cblxuaWYgKCFnbG9iYWwuQWJvcnRDb250cm9sbGVyKSB7XG5cdGdsb2JhbC5BYm9ydENvbnRyb2xsZXIgPSBBYm9ydENvbnRyb2xsZXI7XG59XG5cbmlmICghZ2xvYmFsLlJlYWRhYmxlU3RyZWFtKSB7XG5cdHRyeSB7XG5cdFx0Z2xvYmFsLlJlYWRhYmxlU3RyZWFtID0gcmVxdWlyZSgnd2ViLXN0cmVhbXMtcG9seWZpbGwvcG9ueWZpbGwvZXMyMDE4Jyk7XG5cdH0gY2F0Y2ggKF8pIHt9XG59XG5cbm1vZHVsZS5leHBvcnRzID0gcmVxdWlyZSgna3kvdW1kJyk7XG4iLCIoZnVuY3Rpb24gKGdsb2JhbCwgZmFjdG9yeSkge1xuXHR0eXBlb2YgZXhwb3J0cyA9PT0gJ29iamVjdCcgJiYgdHlwZW9mIG1vZHVsZSAhPT0gJ3VuZGVmaW5lZCcgPyBtb2R1bGUuZXhwb3J0cyA9IGZhY3RvcnkoKSA6XG5cdHR5cGVvZiBkZWZpbmUgPT09ICdmdW5jdGlvbicgJiYgZGVmaW5lLmFtZCA/IGRlZmluZShmYWN0b3J5KSA6XG5cdChnbG9iYWwgPSB0eXBlb2YgZ2xvYmFsVGhpcyAhPT0gJ3VuZGVmaW5lZCcgPyBnbG9iYWxUaGlzIDogZ2xvYmFsIHx8IHNlbGYsIGdsb2JhbC5reSA9IGZhY3RvcnkoKSk7XG59KHRoaXMsIChmdW5jdGlvbiAoKSB7ICd1c2Ugc3RyaWN0JztcblxuXHQvKiEgTUlUIExpY2Vuc2UgwqkgU2luZHJlIFNvcmh1cyAqL1xuXG5cdGNvbnN0IGdsb2JhbHMgPSB7fTtcblxuXHRjb25zdCBnZXRHbG9iYWwgPSBwcm9wZXJ0eSA9PiB7XG5cdFx0LyogaXN0YW5idWwgaWdub3JlIG5leHQgKi9cblx0XHRpZiAodHlwZW9mIHNlbGYgIT09ICd1bmRlZmluZWQnICYmIHNlbGYgJiYgcHJvcGVydHkgaW4gc2VsZikge1xuXHRcdFx0cmV0dXJuIHNlbGY7XG5cdFx0fVxuXG5cdFx0LyogaXN0YW5idWwgaWdub3JlIG5leHQgKi9cblx0XHRpZiAodHlwZW9mIHdpbmRvdyAhPT0gJ3VuZGVmaW5lZCcgJiYgd2luZG93ICYmIHByb3BlcnR5IGluIHdpbmRvdykge1xuXHRcdFx0cmV0dXJuIHdpbmRvdztcblx0XHR9XG5cblx0XHRpZiAodHlwZW9mIGdsb2JhbCAhPT0gJ3VuZGVmaW5lZCcgJiYgZ2xvYmFsICYmIHByb3BlcnR5IGluIGdsb2JhbCkge1xuXHRcdFx0cmV0dXJuIGdsb2JhbDtcblx0XHR9XG5cblx0XHQvKiBpc3RhbmJ1bCBpZ25vcmUgbmV4dCAqL1xuXHRcdGlmICh0eXBlb2YgZ2xvYmFsVGhpcyAhPT0gJ3VuZGVmaW5lZCcgJiYgZ2xvYmFsVGhpcykge1xuXHRcdFx0cmV0dXJuIGdsb2JhbFRoaXM7XG5cdFx0fVxuXHR9O1xuXG5cdGNvbnN0IGdsb2JhbFByb3BlcnRpZXMgPSBbXG5cdFx0J0hlYWRlcnMnLFxuXHRcdCdSZXF1ZXN0Jyxcblx0XHQnUmVzcG9uc2UnLFxuXHRcdCdSZWFkYWJsZVN0cmVhbScsXG5cdFx0J2ZldGNoJyxcblx0XHQnQWJvcnRDb250cm9sbGVyJyxcblx0XHQnRm9ybURhdGEnXG5cdF07XG5cblx0Zm9yIChjb25zdCBwcm9wZXJ0eSBvZiBnbG9iYWxQcm9wZXJ0aWVzKSB7XG5cdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGdsb2JhbHMsIHByb3BlcnR5LCB7XG5cdFx0XHRnZXQoKSB7XG5cdFx0XHRcdGNvbnN0IGdsb2JhbE9iamVjdCA9IGdldEdsb2JhbChwcm9wZXJ0eSk7XG5cdFx0XHRcdGNvbnN0IHZhbHVlID0gZ2xvYmFsT2JqZWN0ICYmIGdsb2JhbE9iamVjdFtwcm9wZXJ0eV07XG5cdFx0XHRcdHJldHVybiB0eXBlb2YgdmFsdWUgPT09ICdmdW5jdGlvbicgPyB2YWx1ZS5iaW5kKGdsb2JhbE9iamVjdCkgOiB2YWx1ZTtcblx0XHRcdH1cblx0XHR9KTtcblx0fVxuXG5cdGNvbnN0IGlzT2JqZWN0ID0gdmFsdWUgPT4gdmFsdWUgIT09IG51bGwgJiYgdHlwZW9mIHZhbHVlID09PSAnb2JqZWN0Jztcblx0Y29uc3Qgc3VwcG9ydHNBYm9ydENvbnRyb2xsZXIgPSB0eXBlb2YgZ2xvYmFscy5BYm9ydENvbnRyb2xsZXIgPT09ICdmdW5jdGlvbic7XG5cdGNvbnN0IHN1cHBvcnRzU3RyZWFtcyA9IHR5cGVvZiBnbG9iYWxzLlJlYWRhYmxlU3RyZWFtID09PSAnZnVuY3Rpb24nO1xuXHRjb25zdCBzdXBwb3J0c0Zvcm1EYXRhID0gdHlwZW9mIGdsb2JhbHMuRm9ybURhdGEgPT09ICdmdW5jdGlvbic7XG5cblx0Y29uc3QgbWVyZ2VIZWFkZXJzID0gKHNvdXJjZTEsIHNvdXJjZTIpID0+IHtcblx0XHRjb25zdCByZXN1bHQgPSBuZXcgZ2xvYmFscy5IZWFkZXJzKHNvdXJjZTEgfHwge30pO1xuXHRcdGNvbnN0IGlzSGVhZGVyc0luc3RhbmNlID0gc291cmNlMiBpbnN0YW5jZW9mIGdsb2JhbHMuSGVhZGVycztcblx0XHRjb25zdCBzb3VyY2UgPSBuZXcgZ2xvYmFscy5IZWFkZXJzKHNvdXJjZTIgfHwge30pO1xuXG5cdFx0Zm9yIChjb25zdCBba2V5LCB2YWx1ZV0gb2Ygc291cmNlKSB7XG5cdFx0XHRpZiAoKGlzSGVhZGVyc0luc3RhbmNlICYmIHZhbHVlID09PSAndW5kZWZpbmVkJykgfHwgdmFsdWUgPT09IHVuZGVmaW5lZCkge1xuXHRcdFx0XHRyZXN1bHQuZGVsZXRlKGtleSk7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRyZXN1bHQuc2V0KGtleSwgdmFsdWUpO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdHJldHVybiByZXN1bHQ7XG5cdH07XG5cblx0Y29uc3QgZGVlcE1lcmdlID0gKC4uLnNvdXJjZXMpID0+IHtcblx0XHRsZXQgcmV0dXJuVmFsdWUgPSB7fTtcblx0XHRsZXQgaGVhZGVycyA9IHt9O1xuXG5cdFx0Zm9yIChjb25zdCBzb3VyY2Ugb2Ygc291cmNlcykge1xuXHRcdFx0aWYgKEFycmF5LmlzQXJyYXkoc291cmNlKSkge1xuXHRcdFx0XHRpZiAoIShBcnJheS5pc0FycmF5KHJldHVyblZhbHVlKSkpIHtcblx0XHRcdFx0XHRyZXR1cm5WYWx1ZSA9IFtdO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0cmV0dXJuVmFsdWUgPSBbLi4ucmV0dXJuVmFsdWUsIC4uLnNvdXJjZV07XG5cdFx0XHR9IGVsc2UgaWYgKGlzT2JqZWN0KHNvdXJjZSkpIHtcblx0XHRcdFx0Zm9yIChsZXQgW2tleSwgdmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKHNvdXJjZSkpIHtcblx0XHRcdFx0XHRpZiAoaXNPYmplY3QodmFsdWUpICYmIChrZXkgaW4gcmV0dXJuVmFsdWUpKSB7XG5cdFx0XHRcdFx0XHR2YWx1ZSA9IGRlZXBNZXJnZShyZXR1cm5WYWx1ZVtrZXldLCB2YWx1ZSk7XG5cdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0cmV0dXJuVmFsdWUgPSB7Li4ucmV0dXJuVmFsdWUsIFtrZXldOiB2YWx1ZX07XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRpZiAoaXNPYmplY3Qoc291cmNlLmhlYWRlcnMpKSB7XG5cdFx0XHRcdFx0aGVhZGVycyA9IG1lcmdlSGVhZGVycyhoZWFkZXJzLCBzb3VyY2UuaGVhZGVycyk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0cmV0dXJuVmFsdWUuaGVhZGVycyA9IGhlYWRlcnM7XG5cdFx0fVxuXG5cdFx0cmV0dXJuIHJldHVyblZhbHVlO1xuXHR9O1xuXG5cdGNvbnN0IHJlcXVlc3RNZXRob2RzID0gW1xuXHRcdCdnZXQnLFxuXHRcdCdwb3N0Jyxcblx0XHQncHV0Jyxcblx0XHQncGF0Y2gnLFxuXHRcdCdoZWFkJyxcblx0XHQnZGVsZXRlJ1xuXHRdO1xuXG5cdGNvbnN0IHJlc3BvbnNlVHlwZXMgPSB7XG5cdFx0anNvbjogJ2FwcGxpY2F0aW9uL2pzb24nLFxuXHRcdHRleHQ6ICd0ZXh0LyonLFxuXHRcdGZvcm1EYXRhOiAnbXVsdGlwYXJ0L2Zvcm0tZGF0YScsXG5cdFx0YXJyYXlCdWZmZXI6ICcqLyonLFxuXHRcdGJsb2I6ICcqLyonXG5cdH07XG5cblx0Y29uc3QgcmV0cnlNZXRob2RzID0gW1xuXHRcdCdnZXQnLFxuXHRcdCdwdXQnLFxuXHRcdCdoZWFkJyxcblx0XHQnZGVsZXRlJyxcblx0XHQnb3B0aW9ucycsXG5cdFx0J3RyYWNlJ1xuXHRdO1xuXG5cdGNvbnN0IHJldHJ5U3RhdHVzQ29kZXMgPSBbXG5cdFx0NDA4LFxuXHRcdDQxMyxcblx0XHQ0MjksXG5cdFx0NTAwLFxuXHRcdDUwMixcblx0XHQ1MDMsXG5cdFx0NTA0XG5cdF07XG5cblx0Y29uc3QgcmV0cnlBZnRlclN0YXR1c0NvZGVzID0gW1xuXHRcdDQxMyxcblx0XHQ0MjksXG5cdFx0NTAzXG5cdF07XG5cblx0Y29uc3Qgc3RvcCA9IFN5bWJvbCgnc3RvcCcpO1xuXG5cdGNsYXNzIEhUVFBFcnJvciBleHRlbmRzIEVycm9yIHtcblx0XHRjb25zdHJ1Y3RvcihyZXNwb25zZSkge1xuXHRcdFx0Ly8gU2V0IHRoZSBtZXNzYWdlIHRvIHRoZSBzdGF0dXMgdGV4dCwgc3VjaCBhcyBVbmF1dGhvcml6ZWQsXG5cdFx0XHQvLyB3aXRoIHNvbWUgZmFsbGJhY2tzLiBUaGlzIG1lc3NhZ2Ugc2hvdWxkIG5ldmVyIGJlIHVuZGVmaW5lZC5cblx0XHRcdHN1cGVyKFxuXHRcdFx0XHRyZXNwb25zZS5zdGF0dXNUZXh0IHx8XG5cdFx0XHRcdFN0cmluZyhcblx0XHRcdFx0XHQocmVzcG9uc2Uuc3RhdHVzID09PSAwIHx8IHJlc3BvbnNlLnN0YXR1cykgP1xuXHRcdFx0XHRcdFx0cmVzcG9uc2Uuc3RhdHVzIDogJ1Vua25vd24gcmVzcG9uc2UgZXJyb3InXG5cdFx0XHRcdClcblx0XHRcdCk7XG5cdFx0XHR0aGlzLm5hbWUgPSAnSFRUUEVycm9yJztcblx0XHRcdHRoaXMucmVzcG9uc2UgPSByZXNwb25zZTtcblx0XHR9XG5cdH1cblxuXHRjbGFzcyBUaW1lb3V0RXJyb3IgZXh0ZW5kcyBFcnJvciB7XG5cdFx0Y29uc3RydWN0b3IocmVxdWVzdCkge1xuXHRcdFx0c3VwZXIoJ1JlcXVlc3QgdGltZWQgb3V0Jyk7XG5cdFx0XHR0aGlzLm5hbWUgPSAnVGltZW91dEVycm9yJztcblx0XHRcdHRoaXMucmVxdWVzdCA9IHJlcXVlc3Q7XG5cdFx0fVxuXHR9XG5cblx0Y29uc3QgZGVsYXkgPSBtcyA9PiBuZXcgUHJvbWlzZShyZXNvbHZlID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgbXMpKTtcblxuXHQvLyBgUHJvbWlzZS5yYWNlKClgIHdvcmthcm91bmQgKCM5MSlcblx0Y29uc3QgdGltZW91dCA9IChyZXF1ZXN0LCBhYm9ydENvbnRyb2xsZXIsIG9wdGlvbnMpID0+XG5cdFx0bmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuXHRcdFx0Y29uc3QgdGltZW91dElEID0gc2V0VGltZW91dCgoKSA9PiB7XG5cdFx0XHRcdGlmIChhYm9ydENvbnRyb2xsZXIpIHtcblx0XHRcdFx0XHRhYm9ydENvbnRyb2xsZXIuYWJvcnQoKTtcblx0XHRcdFx0fVxuXG5cdFx0XHRcdHJlamVjdChuZXcgVGltZW91dEVycm9yKHJlcXVlc3QpKTtcblx0XHRcdH0sIG9wdGlvbnMudGltZW91dCk7XG5cblx0XHRcdC8qIGVzbGludC1kaXNhYmxlIHByb21pc2UvcHJlZmVyLWF3YWl0LXRvLXRoZW4gKi9cblx0XHRcdG9wdGlvbnMuZmV0Y2gocmVxdWVzdClcblx0XHRcdFx0LnRoZW4ocmVzb2x2ZSlcblx0XHRcdFx0LmNhdGNoKHJlamVjdClcblx0XHRcdFx0LnRoZW4oKCkgPT4ge1xuXHRcdFx0XHRcdGNsZWFyVGltZW91dCh0aW1lb3V0SUQpO1xuXHRcdFx0XHR9KTtcblx0XHRcdC8qIGVzbGludC1lbmFibGUgcHJvbWlzZS9wcmVmZXItYXdhaXQtdG8tdGhlbiAqL1xuXHRcdH0pO1xuXG5cdGNvbnN0IG5vcm1hbGl6ZVJlcXVlc3RNZXRob2QgPSBpbnB1dCA9PiByZXF1ZXN0TWV0aG9kcy5pbmNsdWRlcyhpbnB1dCkgPyBpbnB1dC50b1VwcGVyQ2FzZSgpIDogaW5wdXQ7XG5cblx0Y29uc3QgZGVmYXVsdFJldHJ5T3B0aW9ucyA9IHtcblx0XHRsaW1pdDogMixcblx0XHRtZXRob2RzOiByZXRyeU1ldGhvZHMsXG5cdFx0c3RhdHVzQ29kZXM6IHJldHJ5U3RhdHVzQ29kZXMsXG5cdFx0YWZ0ZXJTdGF0dXNDb2RlczogcmV0cnlBZnRlclN0YXR1c0NvZGVzXG5cdH07XG5cblx0Y29uc3Qgbm9ybWFsaXplUmV0cnlPcHRpb25zID0gKHJldHJ5ID0ge30pID0+IHtcblx0XHRpZiAodHlwZW9mIHJldHJ5ID09PSAnbnVtYmVyJykge1xuXHRcdFx0cmV0dXJuIHtcblx0XHRcdFx0Li4uZGVmYXVsdFJldHJ5T3B0aW9ucyxcblx0XHRcdFx0bGltaXQ6IHJldHJ5XG5cdFx0XHR9O1xuXHRcdH1cblxuXHRcdGlmIChyZXRyeS5tZXRob2RzICYmICFBcnJheS5pc0FycmF5KHJldHJ5Lm1ldGhvZHMpKSB7XG5cdFx0XHR0aHJvdyBuZXcgRXJyb3IoJ3JldHJ5Lm1ldGhvZHMgbXVzdCBiZSBhbiBhcnJheScpO1xuXHRcdH1cblxuXHRcdGlmIChyZXRyeS5zdGF0dXNDb2RlcyAmJiAhQXJyYXkuaXNBcnJheShyZXRyeS5zdGF0dXNDb2RlcykpIHtcblx0XHRcdHRocm93IG5ldyBFcnJvcigncmV0cnkuc3RhdHVzQ29kZXMgbXVzdCBiZSBhbiBhcnJheScpO1xuXHRcdH1cblxuXHRcdHJldHVybiB7XG5cdFx0XHQuLi5kZWZhdWx0UmV0cnlPcHRpb25zLFxuXHRcdFx0Li4ucmV0cnksXG5cdFx0XHRhZnRlclN0YXR1c0NvZGVzOiByZXRyeUFmdGVyU3RhdHVzQ29kZXNcblx0XHR9O1xuXHR9O1xuXG5cdC8vIFRoZSBtYXhpbXVtIHZhbHVlIG9mIGEgMzJiaXQgaW50IChzZWUgaXNzdWUgIzExNylcblx0Y29uc3QgbWF4U2FmZVRpbWVvdXQgPSAyMTQ3NDgzNjQ3O1xuXG5cdGNsYXNzIEt5IHtcblx0XHRjb25zdHJ1Y3RvcihpbnB1dCwgb3B0aW9ucyA9IHt9KSB7XG5cdFx0XHR0aGlzLl9yZXRyeUNvdW50ID0gMDtcblx0XHRcdHRoaXMuX2lucHV0ID0gaW5wdXQ7XG5cdFx0XHR0aGlzLl9vcHRpb25zID0ge1xuXHRcdFx0XHQvLyBUT0RPOiBjcmVkZW50aWFscyBjYW4gYmUgcmVtb3ZlZCB3aGVuIHRoZSBzcGVjIGNoYW5nZSBpcyBpbXBsZW1lbnRlZCBpbiBhbGwgYnJvd3NlcnMuIENvbnRleHQ6IGh0dHBzOi8vd3d3LmNocm9tZXN0YXR1cy5jb20vZmVhdHVyZS80NTM5NDczMzEyMzUwMjA4XG5cdFx0XHRcdGNyZWRlbnRpYWxzOiB0aGlzLl9pbnB1dC5jcmVkZW50aWFscyB8fCAnc2FtZS1vcmlnaW4nLFxuXHRcdFx0XHQuLi5vcHRpb25zLFxuXHRcdFx0XHRoZWFkZXJzOiBtZXJnZUhlYWRlcnModGhpcy5faW5wdXQuaGVhZGVycywgb3B0aW9ucy5oZWFkZXJzKSxcblx0XHRcdFx0aG9va3M6IGRlZXBNZXJnZSh7XG5cdFx0XHRcdFx0YmVmb3JlUmVxdWVzdDogW10sXG5cdFx0XHRcdFx0YmVmb3JlUmV0cnk6IFtdLFxuXHRcdFx0XHRcdGFmdGVyUmVzcG9uc2U6IFtdXG5cdFx0XHRcdH0sIG9wdGlvbnMuaG9va3MpLFxuXHRcdFx0XHRtZXRob2Q6IG5vcm1hbGl6ZVJlcXVlc3RNZXRob2Qob3B0aW9ucy5tZXRob2QgfHwgdGhpcy5faW5wdXQubWV0aG9kKSxcblx0XHRcdFx0cHJlZml4VXJsOiBTdHJpbmcob3B0aW9ucy5wcmVmaXhVcmwgfHwgJycpLFxuXHRcdFx0XHRyZXRyeTogbm9ybWFsaXplUmV0cnlPcHRpb25zKG9wdGlvbnMucmV0cnkpLFxuXHRcdFx0XHR0aHJvd0h0dHBFcnJvcnM6IG9wdGlvbnMudGhyb3dIdHRwRXJyb3JzICE9PSBmYWxzZSxcblx0XHRcdFx0dGltZW91dDogdHlwZW9mIG9wdGlvbnMudGltZW91dCA9PT0gJ3VuZGVmaW5lZCcgPyAxMDAwMCA6IG9wdGlvbnMudGltZW91dCxcblx0XHRcdFx0ZmV0Y2g6IG9wdGlvbnMuZmV0Y2ggfHwgZ2xvYmFscy5mZXRjaFxuXHRcdFx0fTtcblxuXHRcdFx0aWYgKHR5cGVvZiB0aGlzLl9pbnB1dCAhPT0gJ3N0cmluZycgJiYgISh0aGlzLl9pbnB1dCBpbnN0YW5jZW9mIFVSTCB8fCB0aGlzLl9pbnB1dCBpbnN0YW5jZW9mIGdsb2JhbHMuUmVxdWVzdCkpIHtcblx0XHRcdFx0dGhyb3cgbmV3IFR5cGVFcnJvcignYGlucHV0YCBtdXN0IGJlIGEgc3RyaW5nLCBVUkwsIG9yIFJlcXVlc3QnKTtcblx0XHRcdH1cblxuXHRcdFx0aWYgKHRoaXMuX29wdGlvbnMucHJlZml4VXJsICYmIHR5cGVvZiB0aGlzLl9pbnB1dCA9PT0gJ3N0cmluZycpIHtcblx0XHRcdFx0aWYgKHRoaXMuX2lucHV0LnN0YXJ0c1dpdGgoJy8nKSkge1xuXHRcdFx0XHRcdHRocm93IG5ldyBFcnJvcignYGlucHV0YCBtdXN0IG5vdCBiZWdpbiB3aXRoIGEgc2xhc2ggd2hlbiB1c2luZyBgcHJlZml4VXJsYCcpO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0aWYgKCF0aGlzLl9vcHRpb25zLnByZWZpeFVybC5lbmRzV2l0aCgnLycpKSB7XG5cdFx0XHRcdFx0dGhpcy5fb3B0aW9ucy5wcmVmaXhVcmwgKz0gJy8nO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0dGhpcy5faW5wdXQgPSB0aGlzLl9vcHRpb25zLnByZWZpeFVybCArIHRoaXMuX2lucHV0O1xuXHRcdFx0fVxuXG5cdFx0XHRpZiAoc3VwcG9ydHNBYm9ydENvbnRyb2xsZXIpIHtcblx0XHRcdFx0dGhpcy5hYm9ydENvbnRyb2xsZXIgPSBuZXcgZ2xvYmFscy5BYm9ydENvbnRyb2xsZXIoKTtcblx0XHRcdFx0aWYgKHRoaXMuX29wdGlvbnMuc2lnbmFsKSB7XG5cdFx0XHRcdFx0dGhpcy5fb3B0aW9ucy5zaWduYWwuYWRkRXZlbnRMaXN0ZW5lcignYWJvcnQnLCAoKSA9PiB7XG5cdFx0XHRcdFx0XHR0aGlzLmFib3J0Q29udHJvbGxlci5hYm9ydCgpO1xuXHRcdFx0XHRcdH0pO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0dGhpcy5fb3B0aW9ucy5zaWduYWwgPSB0aGlzLmFib3J0Q29udHJvbGxlci5zaWduYWw7XG5cdFx0XHR9XG5cblx0XHRcdHRoaXMucmVxdWVzdCA9IG5ldyBnbG9iYWxzLlJlcXVlc3QodGhpcy5faW5wdXQsIHRoaXMuX29wdGlvbnMpO1xuXG5cdFx0XHRpZiAodGhpcy5fb3B0aW9ucy5zZWFyY2hQYXJhbXMpIHtcblx0XHRcdFx0Y29uc3Qgc2VhcmNoUGFyYW1zID0gJz8nICsgbmV3IFVSTFNlYXJjaFBhcmFtcyh0aGlzLl9vcHRpb25zLnNlYXJjaFBhcmFtcykudG9TdHJpbmcoKTtcblx0XHRcdFx0Y29uc3QgdXJsID0gdGhpcy5yZXF1ZXN0LnVybC5yZXBsYWNlKC8oPzpcXD8uKj8pPyg/PSN8JCkvLCBzZWFyY2hQYXJhbXMpO1xuXG5cdFx0XHRcdC8vIFRvIHByb3ZpZGUgY29ycmVjdCBmb3JtIGJvdW5kYXJ5LCBDb250ZW50LVR5cGUgaGVhZGVyIHNob3VsZCBiZSBkZWxldGVkIGVhY2ggdGltZSB3aGVuIG5ldyBSZXF1ZXN0IGluc3RhbnRpYXRlZCBmcm9tIGFub3RoZXIgb25lXG5cdFx0XHRcdGlmICgoKHN1cHBvcnRzRm9ybURhdGEgJiYgdGhpcy5fb3B0aW9ucy5ib2R5IGluc3RhbmNlb2YgZ2xvYmFscy5Gb3JtRGF0YSkgfHwgdGhpcy5fb3B0aW9ucy5ib2R5IGluc3RhbmNlb2YgVVJMU2VhcmNoUGFyYW1zKSAmJiAhKHRoaXMuX29wdGlvbnMuaGVhZGVycyAmJiB0aGlzLl9vcHRpb25zLmhlYWRlcnNbJ2NvbnRlbnQtdHlwZSddKSkge1xuXHRcdFx0XHRcdHRoaXMucmVxdWVzdC5oZWFkZXJzLmRlbGV0ZSgnY29udGVudC10eXBlJyk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHR0aGlzLnJlcXVlc3QgPSBuZXcgZ2xvYmFscy5SZXF1ZXN0KG5ldyBnbG9iYWxzLlJlcXVlc3QodXJsLCB0aGlzLnJlcXVlc3QpLCB0aGlzLl9vcHRpb25zKTtcblx0XHRcdH1cblxuXHRcdFx0aWYgKHRoaXMuX29wdGlvbnMuanNvbiAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0XHRcdHRoaXMuX29wdGlvbnMuYm9keSA9IEpTT04uc3RyaW5naWZ5KHRoaXMuX29wdGlvbnMuanNvbik7XG5cdFx0XHRcdHRoaXMucmVxdWVzdC5oZWFkZXJzLnNldCgnY29udGVudC10eXBlJywgJ2FwcGxpY2F0aW9uL2pzb24nKTtcblx0XHRcdFx0dGhpcy5yZXF1ZXN0ID0gbmV3IGdsb2JhbHMuUmVxdWVzdCh0aGlzLnJlcXVlc3QsIHtib2R5OiB0aGlzLl9vcHRpb25zLmJvZHl9KTtcblx0XHRcdH1cblxuXHRcdFx0Y29uc3QgZm4gPSBhc3luYyAoKSA9PiB7XG5cdFx0XHRcdGlmICh0aGlzLl9vcHRpb25zLnRpbWVvdXQgPiBtYXhTYWZlVGltZW91dCkge1xuXHRcdFx0XHRcdHRocm93IG5ldyBSYW5nZUVycm9yKGBUaGUgXFxgdGltZW91dFxcYCBvcHRpb24gY2Fubm90IGJlIGdyZWF0ZXIgdGhhbiAke21heFNhZmVUaW1lb3V0fWApO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0YXdhaXQgZGVsYXkoMSk7XG5cdFx0XHRcdGxldCByZXNwb25zZSA9IGF3YWl0IHRoaXMuX2ZldGNoKCk7XG5cblx0XHRcdFx0Zm9yIChjb25zdCBob29rIG9mIHRoaXMuX29wdGlvbnMuaG9va3MuYWZ0ZXJSZXNwb25zZSkge1xuXHRcdFx0XHRcdC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBuby1hd2FpdC1pbi1sb29wXG5cdFx0XHRcdFx0Y29uc3QgbW9kaWZpZWRSZXNwb25zZSA9IGF3YWl0IGhvb2soXG5cdFx0XHRcdFx0XHR0aGlzLnJlcXVlc3QsXG5cdFx0XHRcdFx0XHR0aGlzLl9vcHRpb25zLFxuXHRcdFx0XHRcdFx0dGhpcy5fZGVjb3JhdGVSZXNwb25zZShyZXNwb25zZS5jbG9uZSgpKVxuXHRcdFx0XHRcdCk7XG5cblx0XHRcdFx0XHRpZiAobW9kaWZpZWRSZXNwb25zZSBpbnN0YW5jZW9mIGdsb2JhbHMuUmVzcG9uc2UpIHtcblx0XHRcdFx0XHRcdHJlc3BvbnNlID0gbW9kaWZpZWRSZXNwb25zZTtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblxuXHRcdFx0XHR0aGlzLl9kZWNvcmF0ZVJlc3BvbnNlKHJlc3BvbnNlKTtcblxuXHRcdFx0XHRpZiAoIXJlc3BvbnNlLm9rICYmIHRoaXMuX29wdGlvbnMudGhyb3dIdHRwRXJyb3JzKSB7XG5cdFx0XHRcdFx0dGhyb3cgbmV3IEhUVFBFcnJvcihyZXNwb25zZSk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHQvLyBJZiBgb25Eb3dubG9hZFByb2dyZXNzYCBpcyBwYXNzZWQsIGl0IHVzZXMgdGhlIHN0cmVhbSBBUEkgaW50ZXJuYWxseVxuXHRcdFx0XHQvKiBpc3RhbmJ1bCBpZ25vcmUgbmV4dCAqL1xuXHRcdFx0XHRpZiAodGhpcy5fb3B0aW9ucy5vbkRvd25sb2FkUHJvZ3Jlc3MpIHtcblx0XHRcdFx0XHRpZiAodHlwZW9mIHRoaXMuX29wdGlvbnMub25Eb3dubG9hZFByb2dyZXNzICE9PSAnZnVuY3Rpb24nKSB7XG5cdFx0XHRcdFx0XHR0aHJvdyBuZXcgVHlwZUVycm9yKCdUaGUgYG9uRG93bmxvYWRQcm9ncmVzc2Agb3B0aW9uIG11c3QgYmUgYSBmdW5jdGlvbicpO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdGlmICghc3VwcG9ydHNTdHJlYW1zKSB7XG5cdFx0XHRcdFx0XHR0aHJvdyBuZXcgRXJyb3IoJ1N0cmVhbXMgYXJlIG5vdCBzdXBwb3J0ZWQgaW4geW91ciBlbnZpcm9ubWVudC4gYFJlYWRhYmxlU3RyZWFtYCBpcyBtaXNzaW5nLicpO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdHJldHVybiB0aGlzLl9zdHJlYW0ocmVzcG9uc2UuY2xvbmUoKSwgdGhpcy5fb3B0aW9ucy5vbkRvd25sb2FkUHJvZ3Jlc3MpO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0cmV0dXJuIHJlc3BvbnNlO1xuXHRcdFx0fTtcblxuXHRcdFx0Y29uc3QgaXNSZXRyaWFibGVNZXRob2QgPSB0aGlzLl9vcHRpb25zLnJldHJ5Lm1ldGhvZHMuaW5jbHVkZXModGhpcy5yZXF1ZXN0Lm1ldGhvZC50b0xvd2VyQ2FzZSgpKTtcblx0XHRcdGNvbnN0IHJlc3VsdCA9IGlzUmV0cmlhYmxlTWV0aG9kID8gdGhpcy5fcmV0cnkoZm4pIDogZm4oKTtcblxuXHRcdFx0Zm9yIChjb25zdCBbdHlwZSwgbWltZVR5cGVdIG9mIE9iamVjdC5lbnRyaWVzKHJlc3BvbnNlVHlwZXMpKSB7XG5cdFx0XHRcdHJlc3VsdFt0eXBlXSA9IGFzeW5jICgpID0+IHtcblx0XHRcdFx0XHR0aGlzLnJlcXVlc3QuaGVhZGVycy5zZXQoJ2FjY2VwdCcsIHRoaXMucmVxdWVzdC5oZWFkZXJzLmdldCgnYWNjZXB0JykgfHwgbWltZVR5cGUpO1xuXG5cdFx0XHRcdFx0Y29uc3QgcmVzcG9uc2UgPSAoYXdhaXQgcmVzdWx0KS5jbG9uZSgpO1xuXG5cdFx0XHRcdFx0aWYgKHR5cGUgPT09ICdqc29uJykge1xuXHRcdFx0XHRcdFx0aWYgKHJlc3BvbnNlLnN0YXR1cyA9PT0gMjA0KSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiAnJztcblx0XHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdFx0aWYgKG9wdGlvbnMucGFyc2VKc29uKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiBvcHRpb25zLnBhcnNlSnNvbihhd2FpdCByZXNwb25zZS50ZXh0KCkpO1xuXHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdHJldHVybiByZXNwb25zZVt0eXBlXSgpO1xuXHRcdFx0XHR9O1xuXHRcdFx0fVxuXG5cdFx0XHRyZXR1cm4gcmVzdWx0O1xuXHRcdH1cblxuXHRcdF9jYWxjdWxhdGVSZXRyeURlbGF5KGVycm9yKSB7XG5cdFx0XHR0aGlzLl9yZXRyeUNvdW50Kys7XG5cblx0XHRcdGlmICh0aGlzLl9yZXRyeUNvdW50IDwgdGhpcy5fb3B0aW9ucy5yZXRyeS5saW1pdCAmJiAhKGVycm9yIGluc3RhbmNlb2YgVGltZW91dEVycm9yKSkge1xuXHRcdFx0XHRpZiAoZXJyb3IgaW5zdGFuY2VvZiBIVFRQRXJyb3IpIHtcblx0XHRcdFx0XHRpZiAoIXRoaXMuX29wdGlvbnMucmV0cnkuc3RhdHVzQ29kZXMuaW5jbHVkZXMoZXJyb3IucmVzcG9uc2Uuc3RhdHVzKSkge1xuXHRcdFx0XHRcdFx0cmV0dXJuIDA7XG5cdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0Y29uc3QgcmV0cnlBZnRlciA9IGVycm9yLnJlc3BvbnNlLmhlYWRlcnMuZ2V0KCdSZXRyeS1BZnRlcicpO1xuXHRcdFx0XHRcdGlmIChyZXRyeUFmdGVyICYmIHRoaXMuX29wdGlvbnMucmV0cnkuYWZ0ZXJTdGF0dXNDb2Rlcy5pbmNsdWRlcyhlcnJvci5yZXNwb25zZS5zdGF0dXMpKSB7XG5cdFx0XHRcdFx0XHRsZXQgYWZ0ZXIgPSBOdW1iZXIocmV0cnlBZnRlcik7XG5cdFx0XHRcdFx0XHRpZiAoTnVtYmVyLmlzTmFOKGFmdGVyKSkge1xuXHRcdFx0XHRcdFx0XHRhZnRlciA9IERhdGUucGFyc2UocmV0cnlBZnRlcikgLSBEYXRlLm5vdygpO1xuXHRcdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdFx0YWZ0ZXIgKj0gMTAwMDtcblx0XHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdFx0aWYgKHR5cGVvZiB0aGlzLl9vcHRpb25zLnJldHJ5Lm1heFJldHJ5QWZ0ZXIgIT09ICd1bmRlZmluZWQnICYmIGFmdGVyID4gdGhpcy5fb3B0aW9ucy5yZXRyeS5tYXhSZXRyeUFmdGVyKSB7XG5cdFx0XHRcdFx0XHRcdHJldHVybiAwO1xuXHRcdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0XHRyZXR1cm4gYWZ0ZXI7XG5cdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0aWYgKGVycm9yLnJlc3BvbnNlLnN0YXR1cyA9PT0gNDEzKSB7XG5cdFx0XHRcdFx0XHRyZXR1cm4gMDtcblx0XHRcdFx0XHR9XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRjb25zdCBCQUNLT0ZGX0ZBQ1RPUiA9IDAuMztcblx0XHRcdFx0cmV0dXJuIEJBQ0tPRkZfRkFDVE9SICogKDIgKiogKHRoaXMuX3JldHJ5Q291bnQgLSAxKSkgKiAxMDAwO1xuXHRcdFx0fVxuXG5cdFx0XHRyZXR1cm4gMDtcblx0XHR9XG5cblx0XHRfZGVjb3JhdGVSZXNwb25zZShyZXNwb25zZSkge1xuXHRcdFx0aWYgKHRoaXMuX29wdGlvbnMucGFyc2VKc29uKSB7XG5cdFx0XHRcdHJlc3BvbnNlLmpzb24gPSBhc3luYyAoKSA9PiB7XG5cdFx0XHRcdFx0cmV0dXJuIHRoaXMuX29wdGlvbnMucGFyc2VKc29uKGF3YWl0IHJlc3BvbnNlLnRleHQoKSk7XG5cdFx0XHRcdH07XG5cdFx0XHR9XG5cblx0XHRcdHJldHVybiByZXNwb25zZTtcblx0XHR9XG5cblx0XHRhc3luYyBfcmV0cnkoZm4pIHtcblx0XHRcdHRyeSB7XG5cdFx0XHRcdHJldHVybiBhd2FpdCBmbigpO1xuXHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdFx0Y29uc3QgbXMgPSBNYXRoLm1pbih0aGlzLl9jYWxjdWxhdGVSZXRyeURlbGF5KGVycm9yKSwgbWF4U2FmZVRpbWVvdXQpO1xuXHRcdFx0XHRpZiAobXMgIT09IDAgJiYgdGhpcy5fcmV0cnlDb3VudCA+IDApIHtcblx0XHRcdFx0XHRhd2FpdCBkZWxheShtcyk7XG5cblx0XHRcdFx0XHRmb3IgKGNvbnN0IGhvb2sgb2YgdGhpcy5fb3B0aW9ucy5ob29rcy5iZWZvcmVSZXRyeSkge1xuXHRcdFx0XHRcdFx0Ly8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWF3YWl0LWluLWxvb3Bcblx0XHRcdFx0XHRcdGNvbnN0IGhvb2tSZXN1bHQgPSBhd2FpdCBob29rKHtcblx0XHRcdFx0XHRcdFx0cmVxdWVzdDogdGhpcy5yZXF1ZXN0LFxuXHRcdFx0XHRcdFx0XHRvcHRpb25zOiB0aGlzLl9vcHRpb25zLFxuXHRcdFx0XHRcdFx0XHRlcnJvcixcblx0XHRcdFx0XHRcdFx0cmV0cnlDb3VudDogdGhpcy5fcmV0cnlDb3VudFxuXHRcdFx0XHRcdFx0fSk7XG5cblx0XHRcdFx0XHRcdC8vIElmIGBzdG9wYCBpcyByZXR1cm5lZCBmcm9tIHRoZSBob29rLCB0aGUgcmV0cnkgcHJvY2VzcyBpcyBzdG9wcGVkXG5cdFx0XHRcdFx0XHRpZiAoaG9va1Jlc3VsdCA9PT0gc3RvcCkge1xuXHRcdFx0XHRcdFx0XHRyZXR1cm47XG5cdFx0XHRcdFx0XHR9XG5cdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0cmV0dXJuIHRoaXMuX3JldHJ5KGZuKTtcblx0XHRcdFx0fVxuXG5cdFx0XHRcdGlmICh0aGlzLl9vcHRpb25zLnRocm93SHR0cEVycm9ycykge1xuXHRcdFx0XHRcdHRocm93IGVycm9yO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0YXN5bmMgX2ZldGNoKCkge1xuXHRcdFx0Zm9yIChjb25zdCBob29rIG9mIHRoaXMuX29wdGlvbnMuaG9va3MuYmVmb3JlUmVxdWVzdCkge1xuXHRcdFx0XHQvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tYXdhaXQtaW4tbG9vcFxuXHRcdFx0XHRjb25zdCByZXN1bHQgPSBhd2FpdCBob29rKHRoaXMucmVxdWVzdCwgdGhpcy5fb3B0aW9ucyk7XG5cblx0XHRcdFx0aWYgKHJlc3VsdCBpbnN0YW5jZW9mIFJlcXVlc3QpIHtcblx0XHRcdFx0XHR0aGlzLnJlcXVlc3QgPSByZXN1bHQ7XG5cdFx0XHRcdFx0YnJlYWs7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRpZiAocmVzdWx0IGluc3RhbmNlb2YgUmVzcG9uc2UpIHtcblx0XHRcdFx0XHRyZXR1cm4gcmVzdWx0O1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cblx0XHRcdGlmICh0aGlzLl9vcHRpb25zLnRpbWVvdXQgPT09IGZhbHNlKSB7XG5cdFx0XHRcdHJldHVybiB0aGlzLl9vcHRpb25zLmZldGNoKHRoaXMucmVxdWVzdC5jbG9uZSgpKTtcblx0XHRcdH1cblxuXHRcdFx0cmV0dXJuIHRpbWVvdXQodGhpcy5yZXF1ZXN0LmNsb25lKCksIHRoaXMuYWJvcnRDb250cm9sbGVyLCB0aGlzLl9vcHRpb25zKTtcblx0XHR9XG5cblx0XHQvKiBpc3RhbmJ1bCBpZ25vcmUgbmV4dCAqL1xuXHRcdF9zdHJlYW0ocmVzcG9uc2UsIG9uRG93bmxvYWRQcm9ncmVzcykge1xuXHRcdFx0Y29uc3QgdG90YWxCeXRlcyA9IE51bWJlcihyZXNwb25zZS5oZWFkZXJzLmdldCgnY29udGVudC1sZW5ndGgnKSkgfHwgMDtcblx0XHRcdGxldCB0cmFuc2ZlcnJlZEJ5dGVzID0gMDtcblxuXHRcdFx0cmV0dXJuIG5ldyBnbG9iYWxzLlJlc3BvbnNlKFxuXHRcdFx0XHRuZXcgZ2xvYmFscy5SZWFkYWJsZVN0cmVhbSh7XG5cdFx0XHRcdFx0c3RhcnQoY29udHJvbGxlcikge1xuXHRcdFx0XHRcdFx0Y29uc3QgcmVhZGVyID0gcmVzcG9uc2UuYm9keS5nZXRSZWFkZXIoKTtcblxuXHRcdFx0XHRcdFx0aWYgKG9uRG93bmxvYWRQcm9ncmVzcykge1xuXHRcdFx0XHRcdFx0XHRvbkRvd25sb2FkUHJvZ3Jlc3Moe3BlcmNlbnQ6IDAsIHRyYW5zZmVycmVkQnl0ZXM6IDAsIHRvdGFsQnl0ZXN9LCBuZXcgVWludDhBcnJheSgpKTtcblx0XHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdFx0YXN5bmMgZnVuY3Rpb24gcmVhZCgpIHtcblx0XHRcdFx0XHRcdFx0Y29uc3Qge2RvbmUsIHZhbHVlfSA9IGF3YWl0IHJlYWRlci5yZWFkKCk7XG5cdFx0XHRcdFx0XHRcdGlmIChkb25lKSB7XG5cdFx0XHRcdFx0XHRcdFx0Y29udHJvbGxlci5jbG9zZSgpO1xuXHRcdFx0XHRcdFx0XHRcdHJldHVybjtcblx0XHRcdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0XHRcdGlmIChvbkRvd25sb2FkUHJvZ3Jlc3MpIHtcblx0XHRcdFx0XHRcdFx0XHR0cmFuc2ZlcnJlZEJ5dGVzICs9IHZhbHVlLmJ5dGVMZW5ndGg7XG5cdFx0XHRcdFx0XHRcdFx0Y29uc3QgcGVyY2VudCA9IHRvdGFsQnl0ZXMgPT09IDAgPyAwIDogdHJhbnNmZXJyZWRCeXRlcyAvIHRvdGFsQnl0ZXM7XG5cdFx0XHRcdFx0XHRcdFx0b25Eb3dubG9hZFByb2dyZXNzKHtwZXJjZW50LCB0cmFuc2ZlcnJlZEJ5dGVzLCB0b3RhbEJ5dGVzfSwgdmFsdWUpO1xuXHRcdFx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHRcdFx0Y29udHJvbGxlci5lbnF1ZXVlKHZhbHVlKTtcblx0XHRcdFx0XHRcdFx0cmVhZCgpO1xuXHRcdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0XHRyZWFkKCk7XG5cdFx0XHRcdFx0fVxuXHRcdFx0XHR9KVxuXHRcdFx0KTtcblx0XHR9XG5cdH1cblxuXHRjb25zdCB2YWxpZGF0ZUFuZE1lcmdlID0gKC4uLnNvdXJjZXMpID0+IHtcblx0XHRmb3IgKGNvbnN0IHNvdXJjZSBvZiBzb3VyY2VzKSB7XG5cdFx0XHRpZiAoKCFpc09iamVjdChzb3VyY2UpIHx8IEFycmF5LmlzQXJyYXkoc291cmNlKSkgJiYgdHlwZW9mIHNvdXJjZSAhPT0gJ3VuZGVmaW5lZCcpIHtcblx0XHRcdFx0dGhyb3cgbmV3IFR5cGVFcnJvcignVGhlIGBvcHRpb25zYCBhcmd1bWVudCBtdXN0IGJlIGFuIG9iamVjdCcpO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdHJldHVybiBkZWVwTWVyZ2Uoe30sIC4uLnNvdXJjZXMpO1xuXHR9O1xuXG5cdGNvbnN0IGNyZWF0ZUluc3RhbmNlID0gZGVmYXVsdHMgPT4ge1xuXHRcdGNvbnN0IGt5ID0gKGlucHV0LCBvcHRpb25zKSA9PiBuZXcgS3koaW5wdXQsIHZhbGlkYXRlQW5kTWVyZ2UoZGVmYXVsdHMsIG9wdGlvbnMpKTtcblxuXHRcdGZvciAoY29uc3QgbWV0aG9kIG9mIHJlcXVlc3RNZXRob2RzKSB7XG5cdFx0XHRreVttZXRob2RdID0gKGlucHV0LCBvcHRpb25zKSA9PiBuZXcgS3koaW5wdXQsIHZhbGlkYXRlQW5kTWVyZ2UoZGVmYXVsdHMsIG9wdGlvbnMsIHttZXRob2R9KSk7XG5cdFx0fVxuXG5cdFx0a3kuSFRUUEVycm9yID0gSFRUUEVycm9yO1xuXHRcdGt5LlRpbWVvdXRFcnJvciA9IFRpbWVvdXRFcnJvcjtcblx0XHRreS5jcmVhdGUgPSBuZXdEZWZhdWx0cyA9PiBjcmVhdGVJbnN0YW5jZSh2YWxpZGF0ZUFuZE1lcmdlKG5ld0RlZmF1bHRzKSk7XG5cdFx0a3kuZXh0ZW5kID0gbmV3RGVmYXVsdHMgPT4gY3JlYXRlSW5zdGFuY2UodmFsaWRhdGVBbmRNZXJnZShkZWZhdWx0cywgbmV3RGVmYXVsdHMpKTtcblx0XHRreS5zdG9wID0gc3RvcDtcblxuXHRcdHJldHVybiBreTtcblx0fTtcblxuXHR2YXIgaW5kZXggPSBjcmVhdGVJbnN0YW5jZSgpO1xuXG5cdHJldHVybiBpbmRleDtcblxufSkpKTtcbiIsIid1c2Ugc3RyaWN0JztcblxuZXhwb3J0cyA9IG1vZHVsZS5leHBvcnRzID0gZmV0Y2g7XG5cbmNvbnN0IGh0dHAgPSByZXF1aXJlKCdodHRwJyk7XG5jb25zdCBodHRwcyA9IHJlcXVpcmUoJ2h0dHBzJyk7XG5jb25zdCB6bGliID0gcmVxdWlyZSgnemxpYicpO1xuY29uc3QgU3RyZWFtID0gcmVxdWlyZSgnc3RyZWFtJyk7XG5jb25zdCBkYXRhVXJpVG9CdWZmZXIgPSByZXF1aXJlKCdkYXRhLXVyaS10by1idWZmZXInKTtcbmNvbnN0IHV0aWwgPSByZXF1aXJlKCd1dGlsJyk7XG5jb25zdCBCbG9iID0gcmVxdWlyZSgnZmV0Y2gtYmxvYicpO1xuY29uc3QgY3J5cHRvID0gcmVxdWlyZSgnY3J5cHRvJyk7XG5jb25zdCB1cmwgPSByZXF1aXJlKCd1cmwnKTtcblxuY2xhc3MgRmV0Y2hCYXNlRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG5cdGNvbnN0cnVjdG9yKG1lc3NhZ2UsIHR5cGUpIHtcblx0XHRzdXBlcihtZXNzYWdlKTtcblx0XHQvLyBIaWRlIGN1c3RvbSBlcnJvciBpbXBsZW1lbnRhdGlvbiBkZXRhaWxzIGZyb20gZW5kLXVzZXJzXG5cdFx0RXJyb3IuY2FwdHVyZVN0YWNrVHJhY2UodGhpcywgdGhpcy5jb25zdHJ1Y3Rvcik7XG5cblx0XHR0aGlzLnR5cGUgPSB0eXBlO1xuXHR9XG5cblx0Z2V0IG5hbWUoKSB7XG5cdFx0cmV0dXJuIHRoaXMuY29uc3RydWN0b3IubmFtZTtcblx0fVxuXG5cdGdldCBbU3ltYm9sLnRvU3RyaW5nVGFnXSgpIHtcblx0XHRyZXR1cm4gdGhpcy5jb25zdHJ1Y3Rvci5uYW1lO1xuXHR9XG59XG5cbi8qKlxuICogQHR5cGVkZWYge3sgYWRkcmVzcz86IHN0cmluZywgY29kZTogc3RyaW5nLCBkZXN0Pzogc3RyaW5nLCBlcnJubzogbnVtYmVyLCBpbmZvPzogb2JqZWN0LCBtZXNzYWdlOiBzdHJpbmcsIHBhdGg/OiBzdHJpbmcsIHBvcnQ/OiBudW1iZXIsIHN5c2NhbGw6IHN0cmluZ319IFN5c3RlbUVycm9yXG4qL1xuXG4vKipcbiAqIEZldGNoRXJyb3IgaW50ZXJmYWNlIGZvciBvcGVyYXRpb25hbCBlcnJvcnNcbiAqL1xuY2xhc3MgRmV0Y2hFcnJvciBleHRlbmRzIEZldGNoQmFzZUVycm9yIHtcblx0LyoqXG5cdCAqIEBwYXJhbSAge3N0cmluZ30gbWVzc2FnZSAtICAgICAgRXJyb3IgbWVzc2FnZSBmb3IgaHVtYW5cblx0ICogQHBhcmFtICB7c3RyaW5nfSBbdHlwZV0gLSAgICAgICAgRXJyb3IgdHlwZSBmb3IgbWFjaGluZVxuXHQgKiBAcGFyYW0gIHtTeXN0ZW1FcnJvcn0gW3N5c3RlbUVycm9yXSAtIEZvciBOb2RlLmpzIHN5c3RlbSBlcnJvclxuXHQgKi9cblx0Y29uc3RydWN0b3IobWVzc2FnZSwgdHlwZSwgc3lzdGVtRXJyb3IpIHtcblx0XHRzdXBlcihtZXNzYWdlLCB0eXBlKTtcblx0XHQvLyBXaGVuIGVyci50eXBlIGlzIGBzeXN0ZW1gLCBlcnIuZXJyb3JlZFN5c0NhbGwgY29udGFpbnMgc3lzdGVtIGVycm9yIGFuZCBlcnIuY29kZSBjb250YWlucyBzeXN0ZW0gZXJyb3IgY29kZVxuXHRcdGlmIChzeXN0ZW1FcnJvcikge1xuXHRcdFx0Ly8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLW11bHRpLWFzc2lnblxuXHRcdFx0dGhpcy5jb2RlID0gdGhpcy5lcnJubyA9IHN5c3RlbUVycm9yLmNvZGU7XG5cdFx0XHR0aGlzLmVycm9yZWRTeXNDYWxsID0gc3lzdGVtRXJyb3Iuc3lzY2FsbDtcblx0XHR9XG5cdH1cbn1cblxuLyoqXG4gKiBJcy5qc1xuICpcbiAqIE9iamVjdCB0eXBlIGNoZWNrcy5cbiAqL1xuXG5jb25zdCBOQU1FID0gU3ltYm9sLnRvU3RyaW5nVGFnO1xuXG4vKipcbiAqIENoZWNrIGlmIGBvYmpgIGlzIGEgVVJMU2VhcmNoUGFyYW1zIG9iamVjdFxuICogcmVmOiBodHRwczovL2dpdGh1Yi5jb20vbm9kZS1mZXRjaC9ub2RlLWZldGNoL2lzc3Vlcy8yOTYjaXNzdWVjb21tZW50LTMwNzU5ODE0M1xuICpcbiAqIEBwYXJhbSAgeyp9IG9ialxuICogQHJldHVybiB7Ym9vbGVhbn1cbiAqL1xuY29uc3QgaXNVUkxTZWFyY2hQYXJhbWV0ZXJzID0gb2JqZWN0ID0+IHtcblx0cmV0dXJuIChcblx0XHR0eXBlb2Ygb2JqZWN0ID09PSAnb2JqZWN0JyAmJlxuXHRcdHR5cGVvZiBvYmplY3QuYXBwZW5kID09PSAnZnVuY3Rpb24nICYmXG5cdFx0dHlwZW9mIG9iamVjdC5kZWxldGUgPT09ICdmdW5jdGlvbicgJiZcblx0XHR0eXBlb2Ygb2JqZWN0LmdldCA9PT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdHR5cGVvZiBvYmplY3QuZ2V0QWxsID09PSAnZnVuY3Rpb24nICYmXG5cdFx0dHlwZW9mIG9iamVjdC5oYXMgPT09ICdmdW5jdGlvbicgJiZcblx0XHR0eXBlb2Ygb2JqZWN0LnNldCA9PT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdHR5cGVvZiBvYmplY3Quc29ydCA9PT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdG9iamVjdFtOQU1FXSA9PT0gJ1VSTFNlYXJjaFBhcmFtcydcblx0KTtcbn07XG5cbi8qKlxuICogQ2hlY2sgaWYgYG9iamVjdGAgaXMgYSBXM0MgYEJsb2JgIG9iamVjdCAod2hpY2ggYEZpbGVgIGluaGVyaXRzIGZyb20pXG4gKlxuICogQHBhcmFtICB7Kn0gb2JqXG4gKiBAcmV0dXJuIHtib29sZWFufVxuICovXG5jb25zdCBpc0Jsb2IgPSBvYmplY3QgPT4ge1xuXHRyZXR1cm4gKFxuXHRcdHR5cGVvZiBvYmplY3QgPT09ICdvYmplY3QnICYmXG5cdFx0dHlwZW9mIG9iamVjdC5hcnJheUJ1ZmZlciA9PT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdHR5cGVvZiBvYmplY3QudHlwZSA9PT0gJ3N0cmluZycgJiZcblx0XHR0eXBlb2Ygb2JqZWN0LnN0cmVhbSA9PT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdHR5cGVvZiBvYmplY3QuY29uc3RydWN0b3IgPT09ICdmdW5jdGlvbicgJiZcblx0XHQvXihCbG9ifEZpbGUpJC8udGVzdChvYmplY3RbTkFNRV0pXG5cdCk7XG59O1xuXG4vKipcbiAqIENoZWNrIGlmIGBvYmpgIGlzIGEgc3BlYy1jb21wbGlhbnQgYEZvcm1EYXRhYCBvYmplY3RcbiAqXG4gKiBAcGFyYW0geyp9IG9iamVjdFxuICogQHJldHVybiB7Ym9vbGVhbn1cbiAqL1xuZnVuY3Rpb24gaXNGb3JtRGF0YShvYmplY3QpIHtcblx0cmV0dXJuIChcblx0XHR0eXBlb2Ygb2JqZWN0ID09PSAnb2JqZWN0JyAmJlxuXHRcdHR5cGVvZiBvYmplY3QuYXBwZW5kID09PSAnZnVuY3Rpb24nICYmXG5cdFx0dHlwZW9mIG9iamVjdC5zZXQgPT09ICdmdW5jdGlvbicgJiZcblx0XHR0eXBlb2Ygb2JqZWN0LmdldCA9PT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdHR5cGVvZiBvYmplY3QuZ2V0QWxsID09PSAnZnVuY3Rpb24nICYmXG5cdFx0dHlwZW9mIG9iamVjdC5kZWxldGUgPT09ICdmdW5jdGlvbicgJiZcblx0XHR0eXBlb2Ygb2JqZWN0LmtleXMgPT09ICdmdW5jdGlvbicgJiZcblx0XHR0eXBlb2Ygb2JqZWN0LnZhbHVlcyA9PT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdHR5cGVvZiBvYmplY3QuZW50cmllcyA9PT0gJ2Z1bmN0aW9uJyAmJlxuXHRcdHR5cGVvZiBvYmplY3QuY29uc3RydWN0b3IgPT09ICdmdW5jdGlvbicgJiZcblx0XHRvYmplY3RbTkFNRV0gPT09ICdGb3JtRGF0YSdcblx0KTtcbn1cblxuLyoqXG4gKiBDaGVjayBpZiBgb2JqYCBpcyBhbiBpbnN0YW5jZSBvZiBBYm9ydFNpZ25hbC5cbiAqXG4gKiBAcGFyYW0gIHsqfSBvYmpcbiAqIEByZXR1cm4ge2Jvb2xlYW59XG4gKi9cbmNvbnN0IGlzQWJvcnRTaWduYWwgPSBvYmplY3QgPT4ge1xuXHRyZXR1cm4gKFxuXHRcdHR5cGVvZiBvYmplY3QgPT09ICdvYmplY3QnICYmXG5cdFx0b2JqZWN0W05BTUVdID09PSAnQWJvcnRTaWduYWwnXG5cdCk7XG59O1xuXG5jb25zdCBjYXJyaWFnZSA9ICdcXHJcXG4nO1xuY29uc3QgZGFzaGVzID0gJy0nLnJlcGVhdCgyKTtcbmNvbnN0IGNhcnJpYWdlTGVuZ3RoID0gQnVmZmVyLmJ5dGVMZW5ndGgoY2FycmlhZ2UpO1xuXG4vKipcbiAqIEBwYXJhbSB7c3RyaW5nfSBib3VuZGFyeVxuICovXG5jb25zdCBnZXRGb290ZXIgPSBib3VuZGFyeSA9PiBgJHtkYXNoZXN9JHtib3VuZGFyeX0ke2Rhc2hlc30ke2NhcnJpYWdlLnJlcGVhdCgyKX1gO1xuXG4vKipcbiAqIEBwYXJhbSB7c3RyaW5nfSBib3VuZGFyeVxuICogQHBhcmFtIHtzdHJpbmd9IG5hbWVcbiAqIEBwYXJhbSB7Kn0gZmllbGRcbiAqXG4gKiBAcmV0dXJuIHtzdHJpbmd9XG4gKi9cbmZ1bmN0aW9uIGdldEhlYWRlcihib3VuZGFyeSwgbmFtZSwgZmllbGQpIHtcblx0bGV0IGhlYWRlciA9ICcnO1xuXG5cdGhlYWRlciArPSBgJHtkYXNoZXN9JHtib3VuZGFyeX0ke2NhcnJpYWdlfWA7XG5cdGhlYWRlciArPSBgQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPVwiJHtuYW1lfVwiYDtcblxuXHRpZiAoaXNCbG9iKGZpZWxkKSkge1xuXHRcdGhlYWRlciArPSBgOyBmaWxlbmFtZT1cIiR7ZmllbGQubmFtZX1cIiR7Y2FycmlhZ2V9YDtcblx0XHRoZWFkZXIgKz0gYENvbnRlbnQtVHlwZTogJHtmaWVsZC50eXBlIHx8ICdhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0nfWA7XG5cdH1cblxuXHRyZXR1cm4gYCR7aGVhZGVyfSR7Y2FycmlhZ2UucmVwZWF0KDIpfWA7XG59XG5cbi8qKlxuICogQHJldHVybiB7c3RyaW5nfVxuICovXG5jb25zdCBnZXRCb3VuZGFyeSA9ICgpID0+IGNyeXB0by5yYW5kb21CeXRlcyg4KS50b1N0cmluZygnaGV4Jyk7XG5cbi8qKlxuICogQHBhcmFtIHtGb3JtRGF0YX0gZm9ybVxuICogQHBhcmFtIHtzdHJpbmd9IGJvdW5kYXJ5XG4gKi9cbmFzeW5jIGZ1bmN0aW9uICogZm9ybURhdGFJdGVyYXRvcihmb3JtLCBib3VuZGFyeSkge1xuXHRmb3IgKGNvbnN0IFtuYW1lLCB2YWx1ZV0gb2YgZm9ybSkge1xuXHRcdHlpZWxkIGdldEhlYWRlcihib3VuZGFyeSwgbmFtZSwgdmFsdWUpO1xuXG5cdFx0aWYgKGlzQmxvYih2YWx1ZSkpIHtcblx0XHRcdHlpZWxkICogdmFsdWUuc3RyZWFtKCk7XG5cdFx0fSBlbHNlIHtcblx0XHRcdHlpZWxkIHZhbHVlO1xuXHRcdH1cblxuXHRcdHlpZWxkIGNhcnJpYWdlO1xuXHR9XG5cblx0eWllbGQgZ2V0Rm9vdGVyKGJvdW5kYXJ5KTtcbn1cblxuLyoqXG4gKiBAcGFyYW0ge0Zvcm1EYXRhfSBmb3JtXG4gKiBAcGFyYW0ge3N0cmluZ30gYm91bmRhcnlcbiAqL1xuZnVuY3Rpb24gZ2V0Rm9ybURhdGFMZW5ndGgoZm9ybSwgYm91bmRhcnkpIHtcblx0bGV0IGxlbmd0aCA9IDA7XG5cblx0Zm9yIChjb25zdCBbbmFtZSwgdmFsdWVdIG9mIGZvcm0pIHtcblx0XHRsZW5ndGggKz0gQnVmZmVyLmJ5dGVMZW5ndGgoZ2V0SGVhZGVyKGJvdW5kYXJ5LCBuYW1lLCB2YWx1ZSkpO1xuXG5cdFx0aWYgKGlzQmxvYih2YWx1ZSkpIHtcblx0XHRcdGxlbmd0aCArPSB2YWx1ZS5zaXplO1xuXHRcdH0gZWxzZSB7XG5cdFx0XHRsZW5ndGggKz0gQnVmZmVyLmJ5dGVMZW5ndGgoU3RyaW5nKHZhbHVlKSk7XG5cdFx0fVxuXG5cdFx0bGVuZ3RoICs9IGNhcnJpYWdlTGVuZ3RoO1xuXHR9XG5cblx0bGVuZ3RoICs9IEJ1ZmZlci5ieXRlTGVuZ3RoKGdldEZvb3Rlcihib3VuZGFyeSkpO1xuXG5cdHJldHVybiBsZW5ndGg7XG59XG5cbmNvbnN0IElOVEVSTkFMUyA9IFN5bWJvbCgnQm9keSBpbnRlcm5hbHMnKTtcblxuLyoqXG4gKiBCb2R5IG1peGluXG4gKlxuICogUmVmOiBodHRwczovL2ZldGNoLnNwZWMud2hhdHdnLm9yZy8jYm9keVxuICpcbiAqIEBwYXJhbSAgIFN0cmVhbSAgYm9keSAgUmVhZGFibGUgc3RyZWFtXG4gKiBAcGFyYW0gICBPYmplY3QgIG9wdHMgIFJlc3BvbnNlIG9wdGlvbnNcbiAqIEByZXR1cm4gIFZvaWRcbiAqL1xuY2xhc3MgQm9keSB7XG5cdGNvbnN0cnVjdG9yKGJvZHksIHtcblx0XHRzaXplID0gMFxuXHR9ID0ge30pIHtcblx0XHRsZXQgYm91bmRhcnkgPSBudWxsO1xuXG5cdFx0aWYgKGJvZHkgPT09IG51bGwpIHtcblx0XHRcdC8vIEJvZHkgaXMgdW5kZWZpbmVkIG9yIG51bGxcblx0XHRcdGJvZHkgPSBudWxsO1xuXHRcdH0gZWxzZSBpZiAoaXNVUkxTZWFyY2hQYXJhbWV0ZXJzKGJvZHkpKSB7XG5cdFx0Ly8gQm9keSBpcyBhIFVSTFNlYXJjaFBhcmFtc1xuXHRcdFx0Ym9keSA9IEJ1ZmZlci5mcm9tKGJvZHkudG9TdHJpbmcoKSk7XG5cdFx0fSBlbHNlIGlmIChpc0Jsb2IoYm9keSkpIDsgZWxzZSBpZiAoQnVmZmVyLmlzQnVmZmVyKGJvZHkpKSA7IGVsc2UgaWYgKHV0aWwudHlwZXMuaXNBbnlBcnJheUJ1ZmZlcihib2R5KSkge1xuXHRcdFx0Ly8gQm9keSBpcyBBcnJheUJ1ZmZlclxuXHRcdFx0Ym9keSA9IEJ1ZmZlci5mcm9tKGJvZHkpO1xuXHRcdH0gZWxzZSBpZiAoQXJyYXlCdWZmZXIuaXNWaWV3KGJvZHkpKSB7XG5cdFx0XHQvLyBCb2R5IGlzIEFycmF5QnVmZmVyVmlld1xuXHRcdFx0Ym9keSA9IEJ1ZmZlci5mcm9tKGJvZHkuYnVmZmVyLCBib2R5LmJ5dGVPZmZzZXQsIGJvZHkuYnl0ZUxlbmd0aCk7XG5cdFx0fSBlbHNlIGlmIChib2R5IGluc3RhbmNlb2YgU3RyZWFtKSA7IGVsc2UgaWYgKGlzRm9ybURhdGEoYm9keSkpIHtcblx0XHRcdC8vIEJvZHkgaXMgYW4gaW5zdGFuY2Ugb2YgZm9ybWRhdGEtbm9kZVxuXHRcdFx0Ym91bmRhcnkgPSBgTm9kZUZldGNoRm9ybURhdGFCb3VuZGFyeSR7Z2V0Qm91bmRhcnkoKX1gO1xuXHRcdFx0Ym9keSA9IFN0cmVhbS5SZWFkYWJsZS5mcm9tKGZvcm1EYXRhSXRlcmF0b3IoYm9keSwgYm91bmRhcnkpKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0Ly8gTm9uZSBvZiB0aGUgYWJvdmVcblx0XHRcdC8vIGNvZXJjZSB0byBzdHJpbmcgdGhlbiBidWZmZXJcblx0XHRcdGJvZHkgPSBCdWZmZXIuZnJvbShTdHJpbmcoYm9keSkpO1xuXHRcdH1cblxuXHRcdHRoaXNbSU5URVJOQUxTXSA9IHtcblx0XHRcdGJvZHksXG5cdFx0XHRib3VuZGFyeSxcblx0XHRcdGRpc3R1cmJlZDogZmFsc2UsXG5cdFx0XHRlcnJvcjogbnVsbFxuXHRcdH07XG5cdFx0dGhpcy5zaXplID0gc2l6ZTtcblxuXHRcdGlmIChib2R5IGluc3RhbmNlb2YgU3RyZWFtKSB7XG5cdFx0XHRib2R5Lm9uKCdlcnJvcicsIGVyciA9PiB7XG5cdFx0XHRcdGNvbnN0IGVycm9yID0gZXJyIGluc3RhbmNlb2YgRmV0Y2hCYXNlRXJyb3IgP1xuXHRcdFx0XHRcdGVyciA6XG5cdFx0XHRcdFx0bmV3IEZldGNoRXJyb3IoYEludmFsaWQgcmVzcG9uc2UgYm9keSB3aGlsZSB0cnlpbmcgdG8gZmV0Y2ggJHt0aGlzLnVybH06ICR7ZXJyLm1lc3NhZ2V9YCwgJ3N5c3RlbScsIGVycik7XG5cdFx0XHRcdHRoaXNbSU5URVJOQUxTXS5lcnJvciA9IGVycm9yO1xuXHRcdFx0fSk7XG5cdFx0fVxuXHR9XG5cblx0Z2V0IGJvZHkoKSB7XG5cdFx0cmV0dXJuIHRoaXNbSU5URVJOQUxTXS5ib2R5O1xuXHR9XG5cblx0Z2V0IGJvZHlVc2VkKCkge1xuXHRcdHJldHVybiB0aGlzW0lOVEVSTkFMU10uZGlzdHVyYmVkO1xuXHR9XG5cblx0LyoqXG5cdCAqIERlY29kZSByZXNwb25zZSBhcyBBcnJheUJ1ZmZlclxuXHQgKlxuXHQgKiBAcmV0dXJuICBQcm9taXNlXG5cdCAqL1xuXHRhc3luYyBhcnJheUJ1ZmZlcigpIHtcblx0XHRjb25zdCB7YnVmZmVyLCBieXRlT2Zmc2V0LCBieXRlTGVuZ3RofSA9IGF3YWl0IGNvbnN1bWVCb2R5KHRoaXMpO1xuXHRcdHJldHVybiBidWZmZXIuc2xpY2UoYnl0ZU9mZnNldCwgYnl0ZU9mZnNldCArIGJ5dGVMZW5ndGgpO1xuXHR9XG5cblx0LyoqXG5cdCAqIFJldHVybiByYXcgcmVzcG9uc2UgYXMgQmxvYlxuXHQgKlxuXHQgKiBAcmV0dXJuIFByb21pc2Vcblx0ICovXG5cdGFzeW5jIGJsb2IoKSB7XG5cdFx0Y29uc3QgY3QgPSAodGhpcy5oZWFkZXJzICYmIHRoaXMuaGVhZGVycy5nZXQoJ2NvbnRlbnQtdHlwZScpKSB8fCAodGhpc1tJTlRFUk5BTFNdLmJvZHkgJiYgdGhpc1tJTlRFUk5BTFNdLmJvZHkudHlwZSkgfHwgJyc7XG5cdFx0Y29uc3QgYnVmID0gYXdhaXQgdGhpcy5idWZmZXIoKTtcblxuXHRcdHJldHVybiBuZXcgQmxvYihbYnVmXSwge1xuXHRcdFx0dHlwZTogY3Rcblx0XHR9KTtcblx0fVxuXG5cdC8qKlxuXHQgKiBEZWNvZGUgcmVzcG9uc2UgYXMganNvblxuXHQgKlxuXHQgKiBAcmV0dXJuICBQcm9taXNlXG5cdCAqL1xuXHRhc3luYyBqc29uKCkge1xuXHRcdGNvbnN0IGJ1ZmZlciA9IGF3YWl0IGNvbnN1bWVCb2R5KHRoaXMpO1xuXHRcdHJldHVybiBKU09OLnBhcnNlKGJ1ZmZlci50b1N0cmluZygpKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBEZWNvZGUgcmVzcG9uc2UgYXMgdGV4dFxuXHQgKlxuXHQgKiBAcmV0dXJuICBQcm9taXNlXG5cdCAqL1xuXHRhc3luYyB0ZXh0KCkge1xuXHRcdGNvbnN0IGJ1ZmZlciA9IGF3YWl0IGNvbnN1bWVCb2R5KHRoaXMpO1xuXHRcdHJldHVybiBidWZmZXIudG9TdHJpbmcoKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBEZWNvZGUgcmVzcG9uc2UgYXMgYnVmZmVyIChub24tc3BlYyBhcGkpXG5cdCAqXG5cdCAqIEByZXR1cm4gIFByb21pc2Vcblx0ICovXG5cdGJ1ZmZlcigpIHtcblx0XHRyZXR1cm4gY29uc3VtZUJvZHkodGhpcyk7XG5cdH1cbn1cblxuLy8gSW4gYnJvd3NlcnMsIGFsbCBwcm9wZXJ0aWVzIGFyZSBlbnVtZXJhYmxlLlxuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoQm9keS5wcm90b3R5cGUsIHtcblx0Ym9keToge2VudW1lcmFibGU6IHRydWV9LFxuXHRib2R5VXNlZDoge2VudW1lcmFibGU6IHRydWV9LFxuXHRhcnJheUJ1ZmZlcjoge2VudW1lcmFibGU6IHRydWV9LFxuXHRibG9iOiB7ZW51bWVyYWJsZTogdHJ1ZX0sXG5cdGpzb246IHtlbnVtZXJhYmxlOiB0cnVlfSxcblx0dGV4dDoge2VudW1lcmFibGU6IHRydWV9XG59KTtcblxuLyoqXG4gKiBDb25zdW1lIGFuZCBjb252ZXJ0IGFuIGVudGlyZSBCb2R5IHRvIGEgQnVmZmVyLlxuICpcbiAqIFJlZjogaHR0cHM6Ly9mZXRjaC5zcGVjLndoYXR3Zy5vcmcvI2NvbmNlcHQtYm9keS1jb25zdW1lLWJvZHlcbiAqXG4gKiBAcmV0dXJuIFByb21pc2VcbiAqL1xuYXN5bmMgZnVuY3Rpb24gY29uc3VtZUJvZHkoZGF0YSkge1xuXHRpZiAoZGF0YVtJTlRFUk5BTFNdLmRpc3R1cmJlZCkge1xuXHRcdHRocm93IG5ldyBUeXBlRXJyb3IoYGJvZHkgdXNlZCBhbHJlYWR5IGZvcjogJHtkYXRhLnVybH1gKTtcblx0fVxuXG5cdGRhdGFbSU5URVJOQUxTXS5kaXN0dXJiZWQgPSB0cnVlO1xuXG5cdGlmIChkYXRhW0lOVEVSTkFMU10uZXJyb3IpIHtcblx0XHR0aHJvdyBkYXRhW0lOVEVSTkFMU10uZXJyb3I7XG5cdH1cblxuXHRsZXQge2JvZHl9ID0gZGF0YTtcblxuXHQvLyBCb2R5IGlzIG51bGxcblx0aWYgKGJvZHkgPT09IG51bGwpIHtcblx0XHRyZXR1cm4gQnVmZmVyLmFsbG9jKDApO1xuXHR9XG5cblx0Ly8gQm9keSBpcyBibG9iXG5cdGlmIChpc0Jsb2IoYm9keSkpIHtcblx0XHRib2R5ID0gYm9keS5zdHJlYW0oKTtcblx0fVxuXG5cdC8vIEJvZHkgaXMgYnVmZmVyXG5cdGlmIChCdWZmZXIuaXNCdWZmZXIoYm9keSkpIHtcblx0XHRyZXR1cm4gYm9keTtcblx0fVxuXG5cdC8qIGM4IGlnbm9yZSBuZXh0IDMgKi9cblx0aWYgKCEoYm9keSBpbnN0YW5jZW9mIFN0cmVhbSkpIHtcblx0XHRyZXR1cm4gQnVmZmVyLmFsbG9jKDApO1xuXHR9XG5cblx0Ly8gQm9keSBpcyBzdHJlYW1cblx0Ly8gZ2V0IHJlYWR5IHRvIGFjdHVhbGx5IGNvbnN1bWUgdGhlIGJvZHlcblx0Y29uc3QgYWNjdW0gPSBbXTtcblx0bGV0IGFjY3VtQnl0ZXMgPSAwO1xuXG5cdHRyeSB7XG5cdFx0Zm9yIGF3YWl0IChjb25zdCBjaHVuayBvZiBib2R5KSB7XG5cdFx0XHRpZiAoZGF0YS5zaXplID4gMCAmJiBhY2N1bUJ5dGVzICsgY2h1bmsubGVuZ3RoID4gZGF0YS5zaXplKSB7XG5cdFx0XHRcdGNvbnN0IGVyciA9IG5ldyBGZXRjaEVycm9yKGBjb250ZW50IHNpemUgYXQgJHtkYXRhLnVybH0gb3ZlciBsaW1pdDogJHtkYXRhLnNpemV9YCwgJ21heC1zaXplJyk7XG5cdFx0XHRcdGJvZHkuZGVzdHJveShlcnIpO1xuXHRcdFx0XHR0aHJvdyBlcnI7XG5cdFx0XHR9XG5cblx0XHRcdGFjY3VtQnl0ZXMgKz0gY2h1bmsubGVuZ3RoO1xuXHRcdFx0YWNjdW0ucHVzaChjaHVuayk7XG5cdFx0fVxuXHR9IGNhdGNoIChlcnJvcikge1xuXHRcdGlmIChlcnJvciBpbnN0YW5jZW9mIEZldGNoQmFzZUVycm9yKSB7XG5cdFx0XHR0aHJvdyBlcnJvcjtcblx0XHR9IGVsc2Uge1xuXHRcdFx0Ly8gT3RoZXIgZXJyb3JzLCBzdWNoIGFzIGluY29ycmVjdCBjb250ZW50LWVuY29kaW5nXG5cdFx0XHR0aHJvdyBuZXcgRmV0Y2hFcnJvcihgSW52YWxpZCByZXNwb25zZSBib2R5IHdoaWxlIHRyeWluZyB0byBmZXRjaCAke2RhdGEudXJsfTogJHtlcnJvci5tZXNzYWdlfWAsICdzeXN0ZW0nLCBlcnJvcik7XG5cdFx0fVxuXHR9XG5cblx0aWYgKGJvZHkucmVhZGFibGVFbmRlZCA9PT0gdHJ1ZSB8fCBib2R5Ll9yZWFkYWJsZVN0YXRlLmVuZGVkID09PSB0cnVlKSB7XG5cdFx0dHJ5IHtcblx0XHRcdGlmIChhY2N1bS5ldmVyeShjID0+IHR5cGVvZiBjID09PSAnc3RyaW5nJykpIHtcblx0XHRcdFx0cmV0dXJuIEJ1ZmZlci5mcm9tKGFjY3VtLmpvaW4oJycpKTtcblx0XHRcdH1cblxuXHRcdFx0cmV0dXJuIEJ1ZmZlci5jb25jYXQoYWNjdW0sIGFjY3VtQnl0ZXMpO1xuXHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHR0aHJvdyBuZXcgRmV0Y2hFcnJvcihgQ291bGQgbm90IGNyZWF0ZSBCdWZmZXIgZnJvbSByZXNwb25zZSBib2R5IGZvciAke2RhdGEudXJsfTogJHtlcnJvci5tZXNzYWdlfWAsICdzeXN0ZW0nLCBlcnJvcik7XG5cdFx0fVxuXHR9IGVsc2Uge1xuXHRcdHRocm93IG5ldyBGZXRjaEVycm9yKGBQcmVtYXR1cmUgY2xvc2Ugb2Ygc2VydmVyIHJlc3BvbnNlIHdoaWxlIHRyeWluZyB0byBmZXRjaCAke2RhdGEudXJsfWApO1xuXHR9XG59XG5cbi8qKlxuICogQ2xvbmUgYm9keSBnaXZlbiBSZXMvUmVxIGluc3RhbmNlXG4gKlxuICogQHBhcmFtICAgTWl4ZWQgICBpbnN0YW5jZSAgICAgICBSZXNwb25zZSBvciBSZXF1ZXN0IGluc3RhbmNlXG4gKiBAcGFyYW0gICBTdHJpbmcgIGhpZ2hXYXRlck1hcmsgIGhpZ2hXYXRlck1hcmsgZm9yIGJvdGggUGFzc1Rocm91Z2ggYm9keSBzdHJlYW1zXG4gKiBAcmV0dXJuICBNaXhlZFxuICovXG5jb25zdCBjbG9uZSA9IChpbnN0YW5jZSwgaGlnaFdhdGVyTWFyaykgPT4ge1xuXHRsZXQgcDE7XG5cdGxldCBwMjtcblx0bGV0IHtib2R5fSA9IGluc3RhbmNlO1xuXG5cdC8vIERvbid0IGFsbG93IGNsb25pbmcgYSB1c2VkIGJvZHlcblx0aWYgKGluc3RhbmNlLmJvZHlVc2VkKSB7XG5cdFx0dGhyb3cgbmV3IEVycm9yKCdjYW5ub3QgY2xvbmUgYm9keSBhZnRlciBpdCBpcyB1c2VkJyk7XG5cdH1cblxuXHQvLyBDaGVjayB0aGF0IGJvZHkgaXMgYSBzdHJlYW0gYW5kIG5vdCBmb3JtLWRhdGEgb2JqZWN0XG5cdC8vIG5vdGU6IHdlIGNhbid0IGNsb25lIHRoZSBmb3JtLWRhdGEgb2JqZWN0IHdpdGhvdXQgaGF2aW5nIGl0IGFzIGEgZGVwZW5kZW5jeVxuXHRpZiAoKGJvZHkgaW5zdGFuY2VvZiBTdHJlYW0pICYmICh0eXBlb2YgYm9keS5nZXRCb3VuZGFyeSAhPT0gJ2Z1bmN0aW9uJykpIHtcblx0XHQvLyBUZWUgaW5zdGFuY2UgYm9keVxuXHRcdHAxID0gbmV3IFN0cmVhbS5QYXNzVGhyb3VnaCh7aGlnaFdhdGVyTWFya30pO1xuXHRcdHAyID0gbmV3IFN0cmVhbS5QYXNzVGhyb3VnaCh7aGlnaFdhdGVyTWFya30pO1xuXHRcdGJvZHkucGlwZShwMSk7XG5cdFx0Ym9keS5waXBlKHAyKTtcblx0XHQvLyBTZXQgaW5zdGFuY2UgYm9keSB0byB0ZWVkIGJvZHkgYW5kIHJldHVybiB0aGUgb3RoZXIgdGVlZCBib2R5XG5cdFx0aW5zdGFuY2VbSU5URVJOQUxTXS5ib2R5ID0gcDE7XG5cdFx0Ym9keSA9IHAyO1xuXHR9XG5cblx0cmV0dXJuIGJvZHk7XG59O1xuXG4vKipcbiAqIFBlcmZvcm1zIHRoZSBvcGVyYXRpb24gXCJleHRyYWN0IGEgYENvbnRlbnQtVHlwZWAgdmFsdWUgZnJvbSB8b2JqZWN0fFwiIGFzXG4gKiBzcGVjaWZpZWQgaW4gdGhlIHNwZWNpZmljYXRpb246XG4gKiBodHRwczovL2ZldGNoLnNwZWMud2hhdHdnLm9yZy8jY29uY2VwdC1ib2R5aW5pdC1leHRyYWN0XG4gKlxuICogVGhpcyBmdW5jdGlvbiBhc3N1bWVzIHRoYXQgaW5zdGFuY2UuYm9keSBpcyBwcmVzZW50LlxuICpcbiAqIEBwYXJhbSB7YW55fSBib2R5IEFueSBvcHRpb25zLmJvZHkgaW5wdXRcbiAqIEByZXR1cm5zIHtzdHJpbmcgfCBudWxsfVxuICovXG5jb25zdCBleHRyYWN0Q29udGVudFR5cGUgPSAoYm9keSwgcmVxdWVzdCkgPT4ge1xuXHQvLyBCb2R5IGlzIG51bGwgb3IgdW5kZWZpbmVkXG5cdGlmIChib2R5ID09PSBudWxsKSB7XG5cdFx0cmV0dXJuIG51bGw7XG5cdH1cblxuXHQvLyBCb2R5IGlzIHN0cmluZ1xuXHRpZiAodHlwZW9mIGJvZHkgPT09ICdzdHJpbmcnKSB7XG5cdFx0cmV0dXJuICd0ZXh0L3BsYWluO2NoYXJzZXQ9VVRGLTgnO1xuXHR9XG5cblx0Ly8gQm9keSBpcyBhIFVSTFNlYXJjaFBhcmFtc1xuXHRpZiAoaXNVUkxTZWFyY2hQYXJhbWV0ZXJzKGJvZHkpKSB7XG5cdFx0cmV0dXJuICdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQ7Y2hhcnNldD1VVEYtOCc7XG5cdH1cblxuXHQvLyBCb2R5IGlzIGJsb2Jcblx0aWYgKGlzQmxvYihib2R5KSkge1xuXHRcdHJldHVybiBib2R5LnR5cGUgfHwgbnVsbDtcblx0fVxuXG5cdC8vIEJvZHkgaXMgYSBCdWZmZXIgKEJ1ZmZlciwgQXJyYXlCdWZmZXIgb3IgQXJyYXlCdWZmZXJWaWV3KVxuXHRpZiAoQnVmZmVyLmlzQnVmZmVyKGJvZHkpIHx8IHV0aWwudHlwZXMuaXNBbnlBcnJheUJ1ZmZlcihib2R5KSB8fCBBcnJheUJ1ZmZlci5pc1ZpZXcoYm9keSkpIHtcblx0XHRyZXR1cm4gbnVsbDtcblx0fVxuXG5cdC8vIERldGVjdCBmb3JtIGRhdGEgaW5wdXQgZnJvbSBmb3JtLWRhdGEgbW9kdWxlXG5cdGlmIChib2R5ICYmIHR5cGVvZiBib2R5LmdldEJvdW5kYXJ5ID09PSAnZnVuY3Rpb24nKSB7XG5cdFx0cmV0dXJuIGBtdWx0aXBhcnQvZm9ybS1kYXRhO2JvdW5kYXJ5PSR7Ym9keS5nZXRCb3VuZGFyeSgpfWA7XG5cdH1cblxuXHRpZiAoaXNGb3JtRGF0YShib2R5KSkge1xuXHRcdHJldHVybiBgbXVsdGlwYXJ0L2Zvcm0tZGF0YTsgYm91bmRhcnk9JHtyZXF1ZXN0W0lOVEVSTkFMU10uYm91bmRhcnl9YDtcblx0fVxuXG5cdC8vIEJvZHkgaXMgc3RyZWFtIC0gY2FuJ3QgcmVhbGx5IGRvIG11Y2ggYWJvdXQgdGhpc1xuXHRpZiAoYm9keSBpbnN0YW5jZW9mIFN0cmVhbSkge1xuXHRcdHJldHVybiBudWxsO1xuXHR9XG5cblx0Ly8gQm9keSBjb25zdHJ1Y3RvciBkZWZhdWx0cyBvdGhlciB0aGluZ3MgdG8gc3RyaW5nXG5cdHJldHVybiAndGV4dC9wbGFpbjtjaGFyc2V0PVVURi04Jztcbn07XG5cbi8qKlxuICogVGhlIEZldGNoIFN0YW5kYXJkIHRyZWF0cyB0aGlzIGFzIGlmIFwidG90YWwgYnl0ZXNcIiBpcyBhIHByb3BlcnR5IG9uIHRoZSBib2R5LlxuICogRm9yIHVzLCB3ZSBoYXZlIHRvIGV4cGxpY2l0bHkgZ2V0IGl0IHdpdGggYSBmdW5jdGlvbi5cbiAqXG4gKiByZWY6IGh0dHBzOi8vZmV0Y2guc3BlYy53aGF0d2cub3JnLyNjb25jZXB0LWJvZHktdG90YWwtYnl0ZXNcbiAqXG4gKiBAcGFyYW0ge2FueX0gb2JqLmJvZHkgQm9keSBvYmplY3QgZnJvbSB0aGUgQm9keSBpbnN0YW5jZS5cbiAqIEByZXR1cm5zIHtudW1iZXIgfCBudWxsfVxuICovXG5jb25zdCBnZXRUb3RhbEJ5dGVzID0gcmVxdWVzdCA9PiB7XG5cdGNvbnN0IHtib2R5fSA9IHJlcXVlc3Q7XG5cblx0Ly8gQm9keSBpcyBudWxsIG9yIHVuZGVmaW5lZFxuXHRpZiAoYm9keSA9PT0gbnVsbCkge1xuXHRcdHJldHVybiAwO1xuXHR9XG5cblx0Ly8gQm9keSBpcyBCbG9iXG5cdGlmIChpc0Jsb2IoYm9keSkpIHtcblx0XHRyZXR1cm4gYm9keS5zaXplO1xuXHR9XG5cblx0Ly8gQm9keSBpcyBCdWZmZXJcblx0aWYgKEJ1ZmZlci5pc0J1ZmZlcihib2R5KSkge1xuXHRcdHJldHVybiBib2R5Lmxlbmd0aDtcblx0fVxuXG5cdC8vIERldGVjdCBmb3JtIGRhdGEgaW5wdXQgZnJvbSBmb3JtLWRhdGEgbW9kdWxlXG5cdGlmIChib2R5ICYmIHR5cGVvZiBib2R5LmdldExlbmd0aFN5bmMgPT09ICdmdW5jdGlvbicpIHtcblx0XHRyZXR1cm4gYm9keS5oYXNLbm93bkxlbmd0aCAmJiBib2R5Lmhhc0tub3duTGVuZ3RoKCkgPyBib2R5LmdldExlbmd0aFN5bmMoKSA6IG51bGw7XG5cdH1cblxuXHQvLyBCb2R5IGlzIGEgc3BlYy1jb21wbGlhbnQgZm9ybS1kYXRhXG5cdGlmIChpc0Zvcm1EYXRhKGJvZHkpKSB7XG5cdFx0cmV0dXJuIGdldEZvcm1EYXRhTGVuZ3RoKHJlcXVlc3RbSU5URVJOQUxTXS5ib3VuZGFyeSk7XG5cdH1cblxuXHQvLyBCb2R5IGlzIHN0cmVhbVxuXHRyZXR1cm4gbnVsbDtcbn07XG5cbi8qKlxuICogV3JpdGUgYSBCb2R5IHRvIGEgTm9kZS5qcyBXcml0YWJsZVN0cmVhbSAoZS5nLiBodHRwLlJlcXVlc3QpIG9iamVjdC5cbiAqXG4gKiBAcGFyYW0ge1N0cmVhbS5Xcml0YWJsZX0gZGVzdCBUaGUgc3RyZWFtIHRvIHdyaXRlIHRvLlxuICogQHBhcmFtIG9iai5ib2R5IEJvZHkgb2JqZWN0IGZyb20gdGhlIEJvZHkgaW5zdGFuY2UuXG4gKiBAcmV0dXJucyB7dm9pZH1cbiAqL1xuY29uc3Qgd3JpdGVUb1N0cmVhbSA9IChkZXN0LCB7Ym9keX0pID0+IHtcblx0aWYgKGJvZHkgPT09IG51bGwpIHtcblx0XHQvLyBCb2R5IGlzIG51bGxcblx0XHRkZXN0LmVuZCgpO1xuXHR9IGVsc2UgaWYgKGlzQmxvYihib2R5KSkge1xuXHRcdC8vIEJvZHkgaXMgQmxvYlxuXHRcdGJvZHkuc3RyZWFtKCkucGlwZShkZXN0KTtcblx0fSBlbHNlIGlmIChCdWZmZXIuaXNCdWZmZXIoYm9keSkpIHtcblx0XHQvLyBCb2R5IGlzIGJ1ZmZlclxuXHRcdGRlc3Qud3JpdGUoYm9keSk7XG5cdFx0ZGVzdC5lbmQoKTtcblx0fSBlbHNlIHtcblx0XHQvLyBCb2R5IGlzIHN0cmVhbVxuXHRcdGJvZHkucGlwZShkZXN0KTtcblx0fVxufTtcblxuLyoqXG4gKiBIZWFkZXJzLmpzXG4gKlxuICogSGVhZGVycyBjbGFzcyBvZmZlcnMgY29udmVuaWVudCBoZWxwZXJzXG4gKi9cblxuY29uc3QgdmFsaWRhdGVIZWFkZXJOYW1lID0gdHlwZW9mIGh0dHAudmFsaWRhdGVIZWFkZXJOYW1lID09PSAnZnVuY3Rpb24nID9cblx0aHR0cC52YWxpZGF0ZUhlYWRlck5hbWUgOlxuXHRuYW1lID0+IHtcblx0XHRpZiAoIS9eW1xcXmBcXC1cXHchIyQlJicqKy58fl0rJC8udGVzdChuYW1lKSkge1xuXHRcdFx0Y29uc3QgZXJyID0gbmV3IFR5cGVFcnJvcihgSGVhZGVyIG5hbWUgbXVzdCBiZSBhIHZhbGlkIEhUVFAgdG9rZW4gWyR7bmFtZX1dYCk7XG5cdFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXJyLCAnY29kZScsIHt2YWx1ZTogJ0VSUl9JTlZBTElEX0hUVFBfVE9LRU4nfSk7XG5cdFx0XHR0aHJvdyBlcnI7XG5cdFx0fVxuXHR9O1xuXG5jb25zdCB2YWxpZGF0ZUhlYWRlclZhbHVlID0gdHlwZW9mIGh0dHAudmFsaWRhdGVIZWFkZXJWYWx1ZSA9PT0gJ2Z1bmN0aW9uJyA/XG5cdGh0dHAudmFsaWRhdGVIZWFkZXJWYWx1ZSA6XG5cdChuYW1lLCB2YWx1ZSkgPT4ge1xuXHRcdGlmICgvW15cXHRcXHUwMDIwLVxcdTAwN0VcXHUwMDgwLVxcdTAwRkZdLy50ZXN0KHZhbHVlKSkge1xuXHRcdFx0Y29uc3QgZXJyID0gbmV3IFR5cGVFcnJvcihgSW52YWxpZCBjaGFyYWN0ZXIgaW4gaGVhZGVyIGNvbnRlbnQgW1wiJHtuYW1lfVwiXWApO1xuXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGVyciwgJ2NvZGUnLCB7dmFsdWU6ICdFUlJfSU5WQUxJRF9DSEFSJ30pO1xuXHRcdFx0dGhyb3cgZXJyO1xuXHRcdH1cblx0fTtcblxuLyoqXG4gKiBAdHlwZWRlZiB7SGVhZGVycyB8IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gfCBJdGVyYWJsZTxyZWFkb25seSBbc3RyaW5nLCBzdHJpbmddPiB8IEl0ZXJhYmxlPEl0ZXJhYmxlPHN0cmluZz4+fSBIZWFkZXJzSW5pdFxuICovXG5cbi8qKlxuICogVGhpcyBGZXRjaCBBUEkgaW50ZXJmYWNlIGFsbG93cyB5b3UgdG8gcGVyZm9ybSB2YXJpb3VzIGFjdGlvbnMgb24gSFRUUCByZXF1ZXN0IGFuZCByZXNwb25zZSBoZWFkZXJzLlxuICogVGhlc2UgYWN0aW9ucyBpbmNsdWRlIHJldHJpZXZpbmcsIHNldHRpbmcsIGFkZGluZyB0bywgYW5kIHJlbW92aW5nLlxuICogQSBIZWFkZXJzIG9iamVjdCBoYXMgYW4gYXNzb2NpYXRlZCBoZWFkZXIgbGlzdCwgd2hpY2ggaXMgaW5pdGlhbGx5IGVtcHR5IGFuZCBjb25zaXN0cyBvZiB6ZXJvIG9yIG1vcmUgbmFtZSBhbmQgdmFsdWUgcGFpcnMuXG4gKiBZb3UgY2FuIGFkZCB0byB0aGlzIHVzaW5nIG1ldGhvZHMgbGlrZSBhcHBlbmQoKSAoc2VlIEV4YW1wbGVzLilcbiAqIEluIGFsbCBtZXRob2RzIG9mIHRoaXMgaW50ZXJmYWNlLCBoZWFkZXIgbmFtZXMgYXJlIG1hdGNoZWQgYnkgY2FzZS1pbnNlbnNpdGl2ZSBieXRlIHNlcXVlbmNlLlxuICpcbiAqL1xuY2xhc3MgSGVhZGVycyBleHRlbmRzIFVSTFNlYXJjaFBhcmFtcyB7XG5cdC8qKlxuXHQgKiBIZWFkZXJzIGNsYXNzXG5cdCAqXG5cdCAqIEBjb25zdHJ1Y3RvclxuXHQgKiBAcGFyYW0ge0hlYWRlcnNJbml0fSBbaW5pdF0gLSBSZXNwb25zZSBoZWFkZXJzXG5cdCAqL1xuXHRjb25zdHJ1Y3Rvcihpbml0KSB7XG5cdFx0Ly8gVmFsaWRhdGUgYW5kIG5vcm1hbGl6ZSBpbml0IG9iamVjdCBpbiBbbmFtZSwgdmFsdWUocyldW11cblx0XHQvKiogQHR5cGUge3N0cmluZ1tdW119ICovXG5cdFx0bGV0IHJlc3VsdCA9IFtdO1xuXHRcdGlmIChpbml0IGluc3RhbmNlb2YgSGVhZGVycykge1xuXHRcdFx0Y29uc3QgcmF3ID0gaW5pdC5yYXcoKTtcblx0XHRcdGZvciAoY29uc3QgW25hbWUsIHZhbHVlc10gb2YgT2JqZWN0LmVudHJpZXMocmF3KSkge1xuXHRcdFx0XHRyZXN1bHQucHVzaCguLi52YWx1ZXMubWFwKHZhbHVlID0+IFtuYW1lLCB2YWx1ZV0pKTtcblx0XHRcdH1cblx0XHR9IGVsc2UgaWYgKGluaXQgPT0gbnVsbCkgOyBlbHNlIGlmICh0eXBlb2YgaW5pdCA9PT0gJ29iamVjdCcgJiYgIXV0aWwudHlwZXMuaXNCb3hlZFByaW1pdGl2ZShpbml0KSkge1xuXHRcdFx0Y29uc3QgbWV0aG9kID0gaW5pdFtTeW1ib2wuaXRlcmF0b3JdO1xuXHRcdFx0Ly8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIG5vLWVxLW51bGwsIGVxZXFlcVxuXHRcdFx0aWYgKG1ldGhvZCA9PSBudWxsKSB7XG5cdFx0XHRcdC8vIFJlY29yZDxCeXRlU3RyaW5nLCBCeXRlU3RyaW5nPlxuXHRcdFx0XHRyZXN1bHQucHVzaCguLi5PYmplY3QuZW50cmllcyhpbml0KSk7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRpZiAodHlwZW9mIG1ldGhvZCAhPT0gJ2Z1bmN0aW9uJykge1xuXHRcdFx0XHRcdHRocm93IG5ldyBUeXBlRXJyb3IoJ0hlYWRlciBwYWlycyBtdXN0IGJlIGl0ZXJhYmxlJyk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHQvLyBTZXF1ZW5jZTxzZXF1ZW5jZTxCeXRlU3RyaW5nPj5cblx0XHRcdFx0Ly8gTm90ZTogcGVyIHNwZWMgd2UgaGF2ZSB0byBmaXJzdCBleGhhdXN0IHRoZSBsaXN0cyB0aGVuIHByb2Nlc3MgdGhlbVxuXHRcdFx0XHRyZXN1bHQgPSBbLi4uaW5pdF1cblx0XHRcdFx0XHQubWFwKHBhaXIgPT4ge1xuXHRcdFx0XHRcdFx0aWYgKFxuXHRcdFx0XHRcdFx0XHR0eXBlb2YgcGFpciAhPT0gJ29iamVjdCcgfHwgdXRpbC50eXBlcy5pc0JveGVkUHJpbWl0aXZlKHBhaXIpXG5cdFx0XHRcdFx0XHQpIHtcblx0XHRcdFx0XHRcdFx0dGhyb3cgbmV3IFR5cGVFcnJvcignRWFjaCBoZWFkZXIgcGFpciBtdXN0IGJlIGFuIGl0ZXJhYmxlIG9iamVjdCcpO1xuXHRcdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0XHRyZXR1cm4gWy4uLnBhaXJdO1xuXHRcdFx0XHRcdH0pLm1hcChwYWlyID0+IHtcblx0XHRcdFx0XHRcdGlmIChwYWlyLmxlbmd0aCAhPT0gMikge1xuXHRcdFx0XHRcdFx0XHR0aHJvdyBuZXcgVHlwZUVycm9yKCdFYWNoIGhlYWRlciBwYWlyIG11c3QgYmUgYSBuYW1lL3ZhbHVlIHR1cGxlJyk7XG5cdFx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHRcdHJldHVybiBbLi4ucGFpcl07XG5cdFx0XHRcdFx0fSk7XG5cdFx0XHR9XG5cdFx0fSBlbHNlIHtcblx0XHRcdHRocm93IG5ldyBUeXBlRXJyb3IoJ0ZhaWxlZCB0byBjb25zdHJ1Y3QgXFwnSGVhZGVyc1xcJzogVGhlIHByb3ZpZGVkIHZhbHVlIGlzIG5vdCBvZiB0eXBlIFxcJyhzZXF1ZW5jZTxzZXF1ZW5jZTxCeXRlU3RyaW5nPj4gb3IgcmVjb3JkPEJ5dGVTdHJpbmcsIEJ5dGVTdHJpbmc+KScpO1xuXHRcdH1cblxuXHRcdC8vIFZhbGlkYXRlIGFuZCBsb3dlcmNhc2Vcblx0XHRyZXN1bHQgPVxuXHRcdFx0cmVzdWx0Lmxlbmd0aCA+IDAgP1xuXHRcdFx0XHRyZXN1bHQubWFwKChbbmFtZSwgdmFsdWVdKSA9PiB7XG5cdFx0XHRcdFx0dmFsaWRhdGVIZWFkZXJOYW1lKG5hbWUpO1xuXHRcdFx0XHRcdHZhbGlkYXRlSGVhZGVyVmFsdWUobmFtZSwgU3RyaW5nKHZhbHVlKSk7XG5cdFx0XHRcdFx0cmV0dXJuIFtTdHJpbmcobmFtZSkudG9Mb3dlckNhc2UoKSwgU3RyaW5nKHZhbHVlKV07XG5cdFx0XHRcdH0pIDpcblx0XHRcdFx0dW5kZWZpbmVkO1xuXG5cdFx0c3VwZXIocmVzdWx0KTtcblxuXHRcdC8vIFJldHVybmluZyBhIFByb3h5IHRoYXQgd2lsbCBsb3dlcmNhc2Uga2V5IG5hbWVzLCB2YWxpZGF0ZSBwYXJhbWV0ZXJzIGFuZCBzb3J0IGtleXNcblx0XHQvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc3RydWN0b3ItcmV0dXJuXG5cdFx0cmV0dXJuIG5ldyBQcm94eSh0aGlzLCB7XG5cdFx0XHRnZXQodGFyZ2V0LCBwLCByZWNlaXZlcikge1xuXHRcdFx0XHRzd2l0Y2ggKHApIHtcblx0XHRcdFx0XHRjYXNlICdhcHBlbmQnOlxuXHRcdFx0XHRcdGNhc2UgJ3NldCc6XG5cdFx0XHRcdFx0XHRyZXR1cm4gKG5hbWUsIHZhbHVlKSA9PiB7XG5cdFx0XHRcdFx0XHRcdHZhbGlkYXRlSGVhZGVyTmFtZShuYW1lKTtcblx0XHRcdFx0XHRcdFx0dmFsaWRhdGVIZWFkZXJWYWx1ZShuYW1lLCBTdHJpbmcodmFsdWUpKTtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIFVSTFNlYXJjaFBhcmFtcy5wcm90b3R5cGVbcF0uY2FsbChcblx0XHRcdFx0XHRcdFx0XHRyZWNlaXZlcixcblx0XHRcdFx0XHRcdFx0XHRTdHJpbmcobmFtZSkudG9Mb3dlckNhc2UoKSxcblx0XHRcdFx0XHRcdFx0XHRTdHJpbmcodmFsdWUpXG5cdFx0XHRcdFx0XHRcdCk7XG5cdFx0XHRcdFx0XHR9O1xuXG5cdFx0XHRcdFx0Y2FzZSAnZGVsZXRlJzpcblx0XHRcdFx0XHRjYXNlICdoYXMnOlxuXHRcdFx0XHRcdGNhc2UgJ2dldEFsbCc6XG5cdFx0XHRcdFx0XHRyZXR1cm4gbmFtZSA9PiB7XG5cdFx0XHRcdFx0XHRcdHZhbGlkYXRlSGVhZGVyTmFtZShuYW1lKTtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIFVSTFNlYXJjaFBhcmFtcy5wcm90b3R5cGVbcF0uY2FsbChcblx0XHRcdFx0XHRcdFx0XHRyZWNlaXZlcixcblx0XHRcdFx0XHRcdFx0XHRTdHJpbmcobmFtZSkudG9Mb3dlckNhc2UoKVxuXHRcdFx0XHRcdFx0XHQpO1xuXHRcdFx0XHRcdFx0fTtcblxuXHRcdFx0XHRcdGNhc2UgJ2tleXMnOlxuXHRcdFx0XHRcdFx0cmV0dXJuICgpID0+IHtcblx0XHRcdFx0XHRcdFx0dGFyZ2V0LnNvcnQoKTtcblx0XHRcdFx0XHRcdFx0cmV0dXJuIG5ldyBTZXQoVVJMU2VhcmNoUGFyYW1zLnByb3RvdHlwZS5rZXlzLmNhbGwodGFyZ2V0KSkua2V5cygpO1xuXHRcdFx0XHRcdFx0fTtcblxuXHRcdFx0XHRcdGRlZmF1bHQ6XG5cdFx0XHRcdFx0XHRyZXR1cm4gUmVmbGVjdC5nZXQodGFyZ2V0LCBwLCByZWNlaXZlcik7XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHRcdC8qIGM4IGlnbm9yZSBuZXh0ICovXG5cdFx0fSk7XG5cdH1cblxuXHRnZXQgW1N5bWJvbC50b1N0cmluZ1RhZ10oKSB7XG5cdFx0cmV0dXJuIHRoaXMuY29uc3RydWN0b3IubmFtZTtcblx0fVxuXG5cdHRvU3RyaW5nKCkge1xuXHRcdHJldHVybiBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nLmNhbGwodGhpcyk7XG5cdH1cblxuXHRnZXQobmFtZSkge1xuXHRcdGNvbnN0IHZhbHVlcyA9IHRoaXMuZ2V0QWxsKG5hbWUpO1xuXHRcdGlmICh2YWx1ZXMubGVuZ3RoID09PSAwKSB7XG5cdFx0XHRyZXR1cm4gbnVsbDtcblx0XHR9XG5cblx0XHRsZXQgdmFsdWUgPSB2YWx1ZXMuam9pbignLCAnKTtcblx0XHRpZiAoL15jb250ZW50LWVuY29kaW5nJC9pLnRlc3QobmFtZSkpIHtcblx0XHRcdHZhbHVlID0gdmFsdWUudG9Mb3dlckNhc2UoKTtcblx0XHR9XG5cblx0XHRyZXR1cm4gdmFsdWU7XG5cdH1cblxuXHRmb3JFYWNoKGNhbGxiYWNrKSB7XG5cdFx0Zm9yIChjb25zdCBuYW1lIG9mIHRoaXMua2V5cygpKSB7XG5cdFx0XHRjYWxsYmFjayh0aGlzLmdldChuYW1lKSwgbmFtZSk7XG5cdFx0fVxuXHR9XG5cblx0KiB2YWx1ZXMoKSB7XG5cdFx0Zm9yIChjb25zdCBuYW1lIG9mIHRoaXMua2V5cygpKSB7XG5cdFx0XHR5aWVsZCB0aGlzLmdldChuYW1lKTtcblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogQHR5cGUgeygpID0+IEl0ZXJhYmxlSXRlcmF0b3I8W3N0cmluZywgc3RyaW5nXT59XG5cdCAqL1xuXHQqIGVudHJpZXMoKSB7XG5cdFx0Zm9yIChjb25zdCBuYW1lIG9mIHRoaXMua2V5cygpKSB7XG5cdFx0XHR5aWVsZCBbbmFtZSwgdGhpcy5nZXQobmFtZSldO1xuXHRcdH1cblx0fVxuXG5cdFtTeW1ib2wuaXRlcmF0b3JdKCkge1xuXHRcdHJldHVybiB0aGlzLmVudHJpZXMoKTtcblx0fVxuXG5cdC8qKlxuXHQgKiBOb2RlLWZldGNoIG5vbi1zcGVjIG1ldGhvZFxuXHQgKiByZXR1cm5pbmcgYWxsIGhlYWRlcnMgYW5kIHRoZWlyIHZhbHVlcyBhcyBhcnJheVxuXHQgKiBAcmV0dXJucyB7UmVjb3JkPHN0cmluZywgc3RyaW5nW10+fVxuXHQgKi9cblx0cmF3KCkge1xuXHRcdHJldHVybiBbLi4udGhpcy5rZXlzKCldLnJlZHVjZSgocmVzdWx0LCBrZXkpID0+IHtcblx0XHRcdHJlc3VsdFtrZXldID0gdGhpcy5nZXRBbGwoa2V5KTtcblx0XHRcdHJldHVybiByZXN1bHQ7XG5cdFx0fSwge30pO1xuXHR9XG5cblx0LyoqXG5cdCAqIEZvciBiZXR0ZXIgY29uc29sZS5sb2coaGVhZGVycykgYW5kIGFsc28gdG8gY29udmVydCBIZWFkZXJzIGludG8gTm9kZS5qcyBSZXF1ZXN0IGNvbXBhdGlibGUgZm9ybWF0XG5cdCAqL1xuXHRbU3ltYm9sLmZvcignbm9kZWpzLnV0aWwuaW5zcGVjdC5jdXN0b20nKV0oKSB7XG5cdFx0cmV0dXJuIFsuLi50aGlzLmtleXMoKV0ucmVkdWNlKChyZXN1bHQsIGtleSkgPT4ge1xuXHRcdFx0Y29uc3QgdmFsdWVzID0gdGhpcy5nZXRBbGwoa2V5KTtcblx0XHRcdC8vIEh0dHAucmVxdWVzdCgpIG9ubHkgc3VwcG9ydHMgc3RyaW5nIGFzIEhvc3QgaGVhZGVyLlxuXHRcdFx0Ly8gVGhpcyBoYWNrIG1ha2VzIHNwZWNpZnlpbmcgY3VzdG9tIEhvc3QgaGVhZGVyIHBvc3NpYmxlLlxuXHRcdFx0aWYgKGtleSA9PT0gJ2hvc3QnKSB7XG5cdFx0XHRcdHJlc3VsdFtrZXldID0gdmFsdWVzWzBdO1xuXHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0cmVzdWx0W2tleV0gPSB2YWx1ZXMubGVuZ3RoID4gMSA/IHZhbHVlcyA6IHZhbHVlc1swXTtcblx0XHRcdH1cblxuXHRcdFx0cmV0dXJuIHJlc3VsdDtcblx0XHR9LCB7fSk7XG5cdH1cbn1cblxuLyoqXG4gKiBSZS1zaGFwaW5nIG9iamVjdCBmb3IgV2ViIElETCB0ZXN0c1xuICogT25seSBuZWVkIHRvIGRvIGl0IGZvciBvdmVycmlkZGVuIG1ldGhvZHNcbiAqL1xuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoXG5cdEhlYWRlcnMucHJvdG90eXBlLFxuXHRbJ2dldCcsICdlbnRyaWVzJywgJ2ZvckVhY2gnLCAndmFsdWVzJ10ucmVkdWNlKChyZXN1bHQsIHByb3BlcnR5KSA9PiB7XG5cdFx0cmVzdWx0W3Byb3BlcnR5XSA9IHtlbnVtZXJhYmxlOiB0cnVlfTtcblx0XHRyZXR1cm4gcmVzdWx0O1xuXHR9LCB7fSlcbik7XG5cbi8qKlxuICogQ3JlYXRlIGEgSGVhZGVycyBvYmplY3QgZnJvbSBhbiBodHRwLkluY29taW5nTWVzc2FnZS5yYXdIZWFkZXJzLCBpZ25vcmluZyB0aG9zZSB0aGF0IGRvXG4gKiBub3QgY29uZm9ybSB0byBIVFRQIGdyYW1tYXIgcHJvZHVjdGlvbnMuXG4gKiBAcGFyYW0ge2ltcG9ydCgnaHR0cCcpLkluY29taW5nTWVzc2FnZVsncmF3SGVhZGVycyddfSBoZWFkZXJzXG4gKi9cbmZ1bmN0aW9uIGZyb21SYXdIZWFkZXJzKGhlYWRlcnMgPSBbXSkge1xuXHRyZXR1cm4gbmV3IEhlYWRlcnMoXG5cdFx0aGVhZGVyc1xuXHRcdFx0Ly8gU3BsaXQgaW50byBwYWlyc1xuXHRcdFx0LnJlZHVjZSgocmVzdWx0LCB2YWx1ZSwgaW5kZXgsIGFycmF5KSA9PiB7XG5cdFx0XHRcdGlmIChpbmRleCAlIDIgPT09IDApIHtcblx0XHRcdFx0XHRyZXN1bHQucHVzaChhcnJheS5zbGljZShpbmRleCwgaW5kZXggKyAyKSk7XG5cdFx0XHRcdH1cblxuXHRcdFx0XHRyZXR1cm4gcmVzdWx0O1xuXHRcdFx0fSwgW10pXG5cdFx0XHQuZmlsdGVyKChbbmFtZSwgdmFsdWVdKSA9PiB7XG5cdFx0XHRcdHRyeSB7XG5cdFx0XHRcdFx0dmFsaWRhdGVIZWFkZXJOYW1lKG5hbWUpO1xuXHRcdFx0XHRcdHZhbGlkYXRlSGVhZGVyVmFsdWUobmFtZSwgU3RyaW5nKHZhbHVlKSk7XG5cdFx0XHRcdFx0cmV0dXJuIHRydWU7XG5cdFx0XHRcdH0gY2F0Y2gge1xuXHRcdFx0XHRcdHJldHVybiBmYWxzZTtcblx0XHRcdFx0fVxuXHRcdFx0fSlcblxuXHQpO1xufVxuXG5jb25zdCByZWRpcmVjdFN0YXR1cyA9IG5ldyBTZXQoWzMwMSwgMzAyLCAzMDMsIDMwNywgMzA4XSk7XG5cbi8qKlxuICogUmVkaXJlY3QgY29kZSBtYXRjaGluZ1xuICpcbiAqIEBwYXJhbSB7bnVtYmVyfSBjb2RlIC0gU3RhdHVzIGNvZGVcbiAqIEByZXR1cm4ge2Jvb2xlYW59XG4gKi9cbmNvbnN0IGlzUmVkaXJlY3QgPSBjb2RlID0+IHtcblx0cmV0dXJuIHJlZGlyZWN0U3RhdHVzLmhhcyhjb2RlKTtcbn07XG5cbi8qKlxuICogUmVzcG9uc2UuanNcbiAqXG4gKiBSZXNwb25zZSBjbGFzcyBwcm92aWRlcyBjb250ZW50IGRlY29kaW5nXG4gKi9cblxuY29uc3QgSU5URVJOQUxTJDEgPSBTeW1ib2woJ1Jlc3BvbnNlIGludGVybmFscycpO1xuXG4vKipcbiAqIFJlc3BvbnNlIGNsYXNzXG4gKlxuICogQHBhcmFtICAgU3RyZWFtICBib2R5ICBSZWFkYWJsZSBzdHJlYW1cbiAqIEBwYXJhbSAgIE9iamVjdCAgb3B0cyAgUmVzcG9uc2Ugb3B0aW9uc1xuICogQHJldHVybiAgVm9pZFxuICovXG5jbGFzcyBSZXNwb25zZSBleHRlbmRzIEJvZHkge1xuXHRjb25zdHJ1Y3Rvcihib2R5ID0gbnVsbCwgb3B0aW9ucyA9IHt9KSB7XG5cdFx0c3VwZXIoYm9keSwgb3B0aW9ucyk7XG5cblx0XHRjb25zdCBzdGF0dXMgPSBvcHRpb25zLnN0YXR1cyB8fCAyMDA7XG5cdFx0Y29uc3QgaGVhZGVycyA9IG5ldyBIZWFkZXJzKG9wdGlvbnMuaGVhZGVycyk7XG5cblx0XHRpZiAoYm9keSAhPT0gbnVsbCAmJiAhaGVhZGVycy5oYXMoJ0NvbnRlbnQtVHlwZScpKSB7XG5cdFx0XHRjb25zdCBjb250ZW50VHlwZSA9IGV4dHJhY3RDb250ZW50VHlwZShib2R5KTtcblx0XHRcdGlmIChjb250ZW50VHlwZSkge1xuXHRcdFx0XHRoZWFkZXJzLmFwcGVuZCgnQ29udGVudC1UeXBlJywgY29udGVudFR5cGUpO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdHRoaXNbSU5URVJOQUxTJDFdID0ge1xuXHRcdFx0dXJsOiBvcHRpb25zLnVybCxcblx0XHRcdHN0YXR1cyxcblx0XHRcdHN0YXR1c1RleHQ6IG9wdGlvbnMuc3RhdHVzVGV4dCB8fCAnJyxcblx0XHRcdGhlYWRlcnMsXG5cdFx0XHRjb3VudGVyOiBvcHRpb25zLmNvdW50ZXIsXG5cdFx0XHRoaWdoV2F0ZXJNYXJrOiBvcHRpb25zLmhpZ2hXYXRlck1hcmtcblx0XHR9O1xuXHR9XG5cblx0Z2V0IHVybCgpIHtcblx0XHRyZXR1cm4gdGhpc1tJTlRFUk5BTFMkMV0udXJsIHx8ICcnO1xuXHR9XG5cblx0Z2V0IHN0YXR1cygpIHtcblx0XHRyZXR1cm4gdGhpc1tJTlRFUk5BTFMkMV0uc3RhdHVzO1xuXHR9XG5cblx0LyoqXG5cdCAqIENvbnZlbmllbmNlIHByb3BlcnR5IHJlcHJlc2VudGluZyBpZiB0aGUgcmVxdWVzdCBlbmRlZCBub3JtYWxseVxuXHQgKi9cblx0Z2V0IG9rKCkge1xuXHRcdHJldHVybiB0aGlzW0lOVEVSTkFMUyQxXS5zdGF0dXMgPj0gMjAwICYmIHRoaXNbSU5URVJOQUxTJDFdLnN0YXR1cyA8IDMwMDtcblx0fVxuXG5cdGdldCByZWRpcmVjdGVkKCkge1xuXHRcdHJldHVybiB0aGlzW0lOVEVSTkFMUyQxXS5jb3VudGVyID4gMDtcblx0fVxuXG5cdGdldCBzdGF0dXNUZXh0KCkge1xuXHRcdHJldHVybiB0aGlzW0lOVEVSTkFMUyQxXS5zdGF0dXNUZXh0O1xuXHR9XG5cblx0Z2V0IGhlYWRlcnMoKSB7XG5cdFx0cmV0dXJuIHRoaXNbSU5URVJOQUxTJDFdLmhlYWRlcnM7XG5cdH1cblxuXHRnZXQgaGlnaFdhdGVyTWFyaygpIHtcblx0XHRyZXR1cm4gdGhpc1tJTlRFUk5BTFMkMV0uaGlnaFdhdGVyTWFyaztcblx0fVxuXG5cdC8qKlxuXHQgKiBDbG9uZSB0aGlzIHJlc3BvbnNlXG5cdCAqXG5cdCAqIEByZXR1cm4gIFJlc3BvbnNlXG5cdCAqL1xuXHRjbG9uZSgpIHtcblx0XHRyZXR1cm4gbmV3IFJlc3BvbnNlKGNsb25lKHRoaXMsIHRoaXMuaGlnaFdhdGVyTWFyayksIHtcblx0XHRcdHVybDogdGhpcy51cmwsXG5cdFx0XHRzdGF0dXM6IHRoaXMuc3RhdHVzLFxuXHRcdFx0c3RhdHVzVGV4dDogdGhpcy5zdGF0dXNUZXh0LFxuXHRcdFx0aGVhZGVyczogdGhpcy5oZWFkZXJzLFxuXHRcdFx0b2s6IHRoaXMub2ssXG5cdFx0XHRyZWRpcmVjdGVkOiB0aGlzLnJlZGlyZWN0ZWQsXG5cdFx0XHRzaXplOiB0aGlzLnNpemVcblx0XHR9KTtcblx0fVxuXG5cdC8qKlxuXHQgKiBAcGFyYW0ge3N0cmluZ30gdXJsICAgIFRoZSBVUkwgdGhhdCB0aGUgbmV3IHJlc3BvbnNlIGlzIHRvIG9yaWdpbmF0ZSBmcm9tLlxuXHQgKiBAcGFyYW0ge251bWJlcn0gc3RhdHVzIEFuIG9wdGlvbmFsIHN0YXR1cyBjb2RlIGZvciB0aGUgcmVzcG9uc2UgKGUuZy4sIDMwMi4pXG5cdCAqIEByZXR1cm5zIHtSZXNwb25zZX0gICAgQSBSZXNwb25zZSBvYmplY3QuXG5cdCAqL1xuXHRzdGF0aWMgcmVkaXJlY3QodXJsLCBzdGF0dXMgPSAzMDIpIHtcblx0XHRpZiAoIWlzUmVkaXJlY3Qoc3RhdHVzKSkge1xuXHRcdFx0dGhyb3cgbmV3IFJhbmdlRXJyb3IoJ0ZhaWxlZCB0byBleGVjdXRlIFwicmVkaXJlY3RcIiBvbiBcInJlc3BvbnNlXCI6IEludmFsaWQgc3RhdHVzIGNvZGUnKTtcblx0XHR9XG5cblx0XHRyZXR1cm4gbmV3IFJlc3BvbnNlKG51bGwsIHtcblx0XHRcdGhlYWRlcnM6IHtcblx0XHRcdFx0bG9jYXRpb246IG5ldyBVUkwodXJsKS50b1N0cmluZygpXG5cdFx0XHR9LFxuXHRcdFx0c3RhdHVzXG5cdFx0fSk7XG5cdH1cblxuXHRnZXQgW1N5bWJvbC50b1N0cmluZ1RhZ10oKSB7XG5cdFx0cmV0dXJuICdSZXNwb25zZSc7XG5cdH1cbn1cblxuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoUmVzcG9uc2UucHJvdG90eXBlLCB7XG5cdHVybDoge2VudW1lcmFibGU6IHRydWV9LFxuXHRzdGF0dXM6IHtlbnVtZXJhYmxlOiB0cnVlfSxcblx0b2s6IHtlbnVtZXJhYmxlOiB0cnVlfSxcblx0cmVkaXJlY3RlZDoge2VudW1lcmFibGU6IHRydWV9LFxuXHRzdGF0dXNUZXh0OiB7ZW51bWVyYWJsZTogdHJ1ZX0sXG5cdGhlYWRlcnM6IHtlbnVtZXJhYmxlOiB0cnVlfSxcblx0Y2xvbmU6IHtlbnVtZXJhYmxlOiB0cnVlfVxufSk7XG5cbmNvbnN0IGdldFNlYXJjaCA9IHBhcnNlZFVSTCA9PiB7XG5cdGlmIChwYXJzZWRVUkwuc2VhcmNoKSB7XG5cdFx0cmV0dXJuIHBhcnNlZFVSTC5zZWFyY2g7XG5cdH1cblxuXHRjb25zdCBsYXN0T2Zmc2V0ID0gcGFyc2VkVVJMLmhyZWYubGVuZ3RoIC0gMTtcblx0Y29uc3QgaGFzaCA9IHBhcnNlZFVSTC5oYXNoIHx8IChwYXJzZWRVUkwuaHJlZltsYXN0T2Zmc2V0XSA9PT0gJyMnID8gJyMnIDogJycpO1xuXHRyZXR1cm4gcGFyc2VkVVJMLmhyZWZbbGFzdE9mZnNldCAtIGhhc2gubGVuZ3RoXSA9PT0gJz8nID8gJz8nIDogJyc7XG59O1xuXG5jb25zdCBJTlRFUk5BTFMkMiA9IFN5bWJvbCgnUmVxdWVzdCBpbnRlcm5hbHMnKTtcblxuLyoqXG4gKiBDaGVjayBpZiBgb2JqYCBpcyBhbiBpbnN0YW5jZSBvZiBSZXF1ZXN0LlxuICpcbiAqIEBwYXJhbSAgeyp9IG9ialxuICogQHJldHVybiB7Ym9vbGVhbn1cbiAqL1xuY29uc3QgaXNSZXF1ZXN0ID0gb2JqZWN0ID0+IHtcblx0cmV0dXJuIChcblx0XHR0eXBlb2Ygb2JqZWN0ID09PSAnb2JqZWN0JyAmJlxuXHRcdHR5cGVvZiBvYmplY3RbSU5URVJOQUxTJDJdID09PSAnb2JqZWN0J1xuXHQpO1xufTtcblxuLyoqXG4gKiBSZXF1ZXN0IGNsYXNzXG4gKlxuICogQHBhcmFtICAgTWl4ZWQgICBpbnB1dCAgVXJsIG9yIFJlcXVlc3QgaW5zdGFuY2VcbiAqIEBwYXJhbSAgIE9iamVjdCAgaW5pdCAgIEN1c3RvbSBvcHRpb25zXG4gKiBAcmV0dXJuICBWb2lkXG4gKi9cbmNsYXNzIFJlcXVlc3QgZXh0ZW5kcyBCb2R5IHtcblx0Y29uc3RydWN0b3IoaW5wdXQsIGluaXQgPSB7fSkge1xuXHRcdGxldCBwYXJzZWRVUkw7XG5cblx0XHQvLyBOb3JtYWxpemUgaW5wdXQgYW5kIGZvcmNlIFVSTCB0byBiZSBlbmNvZGVkIGFzIFVURi04IChodHRwczovL2dpdGh1Yi5jb20vbm9kZS1mZXRjaC9ub2RlLWZldGNoL2lzc3Vlcy8yNDUpXG5cdFx0aWYgKGlzUmVxdWVzdChpbnB1dCkpIHtcblx0XHRcdHBhcnNlZFVSTCA9IG5ldyBVUkwoaW5wdXQudXJsKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0cGFyc2VkVVJMID0gbmV3IFVSTChpbnB1dCk7XG5cdFx0XHRpbnB1dCA9IHt9O1xuXHRcdH1cblxuXHRcdGxldCBtZXRob2QgPSBpbml0Lm1ldGhvZCB8fCBpbnB1dC5tZXRob2QgfHwgJ0dFVCc7XG5cdFx0bWV0aG9kID0gbWV0aG9kLnRvVXBwZXJDYXNlKCk7XG5cblx0XHQvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tZXEtbnVsbCwgZXFlcWVxXG5cdFx0aWYgKCgoaW5pdC5ib2R5ICE9IG51bGwgfHwgaXNSZXF1ZXN0KGlucHV0KSkgJiYgaW5wdXQuYm9keSAhPT0gbnVsbCkgJiZcblx0XHRcdChtZXRob2QgPT09ICdHRVQnIHx8IG1ldGhvZCA9PT0gJ0hFQUQnKSkge1xuXHRcdFx0dGhyb3cgbmV3IFR5cGVFcnJvcignUmVxdWVzdCB3aXRoIEdFVC9IRUFEIG1ldGhvZCBjYW5ub3QgaGF2ZSBib2R5Jyk7XG5cdFx0fVxuXG5cdFx0Y29uc3QgaW5wdXRCb2R5ID0gaW5pdC5ib2R5ID9cblx0XHRcdGluaXQuYm9keSA6XG5cdFx0XHQoaXNSZXF1ZXN0KGlucHV0KSAmJiBpbnB1dC5ib2R5ICE9PSBudWxsID9cblx0XHRcdFx0Y2xvbmUoaW5wdXQpIDpcblx0XHRcdFx0bnVsbCk7XG5cblx0XHRzdXBlcihpbnB1dEJvZHksIHtcblx0XHRcdHNpemU6IGluaXQuc2l6ZSB8fCBpbnB1dC5zaXplIHx8IDBcblx0XHR9KTtcblxuXHRcdGNvbnN0IGhlYWRlcnMgPSBuZXcgSGVhZGVycyhpbml0LmhlYWRlcnMgfHwgaW5wdXQuaGVhZGVycyB8fCB7fSk7XG5cblx0XHRpZiAoaW5wdXRCb2R5ICE9PSBudWxsICYmICFoZWFkZXJzLmhhcygnQ29udGVudC1UeXBlJykpIHtcblx0XHRcdGNvbnN0IGNvbnRlbnRUeXBlID0gZXh0cmFjdENvbnRlbnRUeXBlKGlucHV0Qm9keSwgdGhpcyk7XG5cdFx0XHRpZiAoY29udGVudFR5cGUpIHtcblx0XHRcdFx0aGVhZGVycy5hcHBlbmQoJ0NvbnRlbnQtVHlwZScsIGNvbnRlbnRUeXBlKTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHRsZXQgc2lnbmFsID0gaXNSZXF1ZXN0KGlucHV0KSA/XG5cdFx0XHRpbnB1dC5zaWduYWwgOlxuXHRcdFx0bnVsbDtcblx0XHRpZiAoJ3NpZ25hbCcgaW4gaW5pdCkge1xuXHRcdFx0c2lnbmFsID0gaW5pdC5zaWduYWw7XG5cdFx0fVxuXG5cdFx0aWYgKHNpZ25hbCAhPT0gbnVsbCAmJiAhaXNBYm9ydFNpZ25hbChzaWduYWwpKSB7XG5cdFx0XHR0aHJvdyBuZXcgVHlwZUVycm9yKCdFeHBlY3RlZCBzaWduYWwgdG8gYmUgYW4gaW5zdGFuY2VvZiBBYm9ydFNpZ25hbCcpO1xuXHRcdH1cblxuXHRcdHRoaXNbSU5URVJOQUxTJDJdID0ge1xuXHRcdFx0bWV0aG9kLFxuXHRcdFx0cmVkaXJlY3Q6IGluaXQucmVkaXJlY3QgfHwgaW5wdXQucmVkaXJlY3QgfHwgJ2ZvbGxvdycsXG5cdFx0XHRoZWFkZXJzLFxuXHRcdFx0cGFyc2VkVVJMLFxuXHRcdFx0c2lnbmFsXG5cdFx0fTtcblxuXHRcdC8vIE5vZGUtZmV0Y2gtb25seSBvcHRpb25zXG5cdFx0dGhpcy5mb2xsb3cgPSBpbml0LmZvbGxvdyA9PT0gdW5kZWZpbmVkID8gKGlucHV0LmZvbGxvdyA9PT0gdW5kZWZpbmVkID8gMjAgOiBpbnB1dC5mb2xsb3cpIDogaW5pdC5mb2xsb3c7XG5cdFx0dGhpcy5jb21wcmVzcyA9IGluaXQuY29tcHJlc3MgPT09IHVuZGVmaW5lZCA/IChpbnB1dC5jb21wcmVzcyA9PT0gdW5kZWZpbmVkID8gdHJ1ZSA6IGlucHV0LmNvbXByZXNzKSA6IGluaXQuY29tcHJlc3M7XG5cdFx0dGhpcy5jb3VudGVyID0gaW5pdC5jb3VudGVyIHx8IGlucHV0LmNvdW50ZXIgfHwgMDtcblx0XHR0aGlzLmFnZW50ID0gaW5pdC5hZ2VudCB8fCBpbnB1dC5hZ2VudDtcblx0XHR0aGlzLmhpZ2hXYXRlck1hcmsgPSBpbml0LmhpZ2hXYXRlck1hcmsgfHwgaW5wdXQuaGlnaFdhdGVyTWFyayB8fCAxNjM4NDtcblx0XHR0aGlzLmluc2VjdXJlSFRUUFBhcnNlciA9IGluaXQuaW5zZWN1cmVIVFRQUGFyc2VyIHx8IGlucHV0Lmluc2VjdXJlSFRUUFBhcnNlciB8fCBmYWxzZTtcblx0fVxuXG5cdGdldCBtZXRob2QoKSB7XG5cdFx0cmV0dXJuIHRoaXNbSU5URVJOQUxTJDJdLm1ldGhvZDtcblx0fVxuXG5cdGdldCB1cmwoKSB7XG5cdFx0cmV0dXJuIHVybC5mb3JtYXQodGhpc1tJTlRFUk5BTFMkMl0ucGFyc2VkVVJMKTtcblx0fVxuXG5cdGdldCBoZWFkZXJzKCkge1xuXHRcdHJldHVybiB0aGlzW0lOVEVSTkFMUyQyXS5oZWFkZXJzO1xuXHR9XG5cblx0Z2V0IHJlZGlyZWN0KCkge1xuXHRcdHJldHVybiB0aGlzW0lOVEVSTkFMUyQyXS5yZWRpcmVjdDtcblx0fVxuXG5cdGdldCBzaWduYWwoKSB7XG5cdFx0cmV0dXJuIHRoaXNbSU5URVJOQUxTJDJdLnNpZ25hbDtcblx0fVxuXG5cdC8qKlxuXHQgKiBDbG9uZSB0aGlzIHJlcXVlc3Rcblx0ICpcblx0ICogQHJldHVybiAgUmVxdWVzdFxuXHQgKi9cblx0Y2xvbmUoKSB7XG5cdFx0cmV0dXJuIG5ldyBSZXF1ZXN0KHRoaXMpO1xuXHR9XG5cblx0Z2V0IFtTeW1ib2wudG9TdHJpbmdUYWddKCkge1xuXHRcdHJldHVybiAnUmVxdWVzdCc7XG5cdH1cbn1cblxuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoUmVxdWVzdC5wcm90b3R5cGUsIHtcblx0bWV0aG9kOiB7ZW51bWVyYWJsZTogdHJ1ZX0sXG5cdHVybDoge2VudW1lcmFibGU6IHRydWV9LFxuXHRoZWFkZXJzOiB7ZW51bWVyYWJsZTogdHJ1ZX0sXG5cdHJlZGlyZWN0OiB7ZW51bWVyYWJsZTogdHJ1ZX0sXG5cdGNsb25lOiB7ZW51bWVyYWJsZTogdHJ1ZX0sXG5cdHNpZ25hbDoge2VudW1lcmFibGU6IHRydWV9XG59KTtcblxuLyoqXG4gKiBDb252ZXJ0IGEgUmVxdWVzdCB0byBOb2RlLmpzIGh0dHAgcmVxdWVzdCBvcHRpb25zLlxuICpcbiAqIEBwYXJhbSAgIFJlcXVlc3QgIEEgUmVxdWVzdCBpbnN0YW5jZVxuICogQHJldHVybiAgT2JqZWN0ICAgVGhlIG9wdGlvbnMgb2JqZWN0IHRvIGJlIHBhc3NlZCB0byBodHRwLnJlcXVlc3RcbiAqL1xuY29uc3QgZ2V0Tm9kZVJlcXVlc3RPcHRpb25zID0gcmVxdWVzdCA9PiB7XG5cdGNvbnN0IHtwYXJzZWRVUkx9ID0gcmVxdWVzdFtJTlRFUk5BTFMkMl07XG5cdGNvbnN0IGhlYWRlcnMgPSBuZXcgSGVhZGVycyhyZXF1ZXN0W0lOVEVSTkFMUyQyXS5oZWFkZXJzKTtcblxuXHQvLyBGZXRjaCBzdGVwIDEuM1xuXHRpZiAoIWhlYWRlcnMuaGFzKCdBY2NlcHQnKSkge1xuXHRcdGhlYWRlcnMuc2V0KCdBY2NlcHQnLCAnKi8qJyk7XG5cdH1cblxuXHQvLyBIVFRQLW5ldHdvcmstb3ItY2FjaGUgZmV0Y2ggc3RlcHMgMi40LTIuN1xuXHRsZXQgY29udGVudExlbmd0aFZhbHVlID0gbnVsbDtcblx0aWYgKHJlcXVlc3QuYm9keSA9PT0gbnVsbCAmJiAvXihwb3N0fHB1dCkkL2kudGVzdChyZXF1ZXN0Lm1ldGhvZCkpIHtcblx0XHRjb250ZW50TGVuZ3RoVmFsdWUgPSAnMCc7XG5cdH1cblxuXHRpZiAocmVxdWVzdC5ib2R5ICE9PSBudWxsKSB7XG5cdFx0Y29uc3QgdG90YWxCeXRlcyA9IGdldFRvdGFsQnl0ZXMocmVxdWVzdCk7XG5cdFx0Ly8gU2V0IENvbnRlbnQtTGVuZ3RoIGlmIHRvdGFsQnl0ZXMgaXMgYSBudW1iZXIgKHRoYXQgaXMgbm90IE5hTilcblx0XHRpZiAodHlwZW9mIHRvdGFsQnl0ZXMgPT09ICdudW1iZXInICYmICFOdW1iZXIuaXNOYU4odG90YWxCeXRlcykpIHtcblx0XHRcdGNvbnRlbnRMZW5ndGhWYWx1ZSA9IFN0cmluZyh0b3RhbEJ5dGVzKTtcblx0XHR9XG5cdH1cblxuXHRpZiAoY29udGVudExlbmd0aFZhbHVlKSB7XG5cdFx0aGVhZGVycy5zZXQoJ0NvbnRlbnQtTGVuZ3RoJywgY29udGVudExlbmd0aFZhbHVlKTtcblx0fVxuXG5cdC8vIEhUVFAtbmV0d29yay1vci1jYWNoZSBmZXRjaCBzdGVwIDIuMTFcblx0aWYgKCFoZWFkZXJzLmhhcygnVXNlci1BZ2VudCcpKSB7XG5cdFx0aGVhZGVycy5zZXQoJ1VzZXItQWdlbnQnLCAnbm9kZS1mZXRjaCcpO1xuXHR9XG5cblx0Ly8gSFRUUC1uZXR3b3JrLW9yLWNhY2hlIGZldGNoIHN0ZXAgMi4xNVxuXHRpZiAocmVxdWVzdC5jb21wcmVzcyAmJiAhaGVhZGVycy5oYXMoJ0FjY2VwdC1FbmNvZGluZycpKSB7XG5cdFx0aGVhZGVycy5zZXQoJ0FjY2VwdC1FbmNvZGluZycsICdnemlwLGRlZmxhdGUsYnInKTtcblx0fVxuXG5cdGxldCB7YWdlbnR9ID0gcmVxdWVzdDtcblx0aWYgKHR5cGVvZiBhZ2VudCA9PT0gJ2Z1bmN0aW9uJykge1xuXHRcdGFnZW50ID0gYWdlbnQocGFyc2VkVVJMKTtcblx0fVxuXG5cdGlmICghaGVhZGVycy5oYXMoJ0Nvbm5lY3Rpb24nKSAmJiAhYWdlbnQpIHtcblx0XHRoZWFkZXJzLnNldCgnQ29ubmVjdGlvbicsICdjbG9zZScpO1xuXHR9XG5cblx0Ly8gSFRUUC1uZXR3b3JrIGZldGNoIHN0ZXAgNC4yXG5cdC8vIGNodW5rZWQgZW5jb2RpbmcgaXMgaGFuZGxlZCBieSBOb2RlLmpzXG5cblx0Y29uc3Qgc2VhcmNoID0gZ2V0U2VhcmNoKHBhcnNlZFVSTCk7XG5cblx0Ly8gTWFudWFsbHkgc3ByZWFkIHRoZSBVUkwgb2JqZWN0IGluc3RlYWQgb2Ygc3ByZWFkIHN5bnRheFxuXHRjb25zdCByZXF1ZXN0T3B0aW9ucyA9IHtcblx0XHRwYXRoOiBwYXJzZWRVUkwucGF0aG5hbWUgKyBzZWFyY2gsXG5cdFx0cGF0aG5hbWU6IHBhcnNlZFVSTC5wYXRobmFtZSxcblx0XHRob3N0bmFtZTogcGFyc2VkVVJMLmhvc3RuYW1lLFxuXHRcdHByb3RvY29sOiBwYXJzZWRVUkwucHJvdG9jb2wsXG5cdFx0cG9ydDogcGFyc2VkVVJMLnBvcnQsXG5cdFx0aGFzaDogcGFyc2VkVVJMLmhhc2gsXG5cdFx0c2VhcmNoOiBwYXJzZWRVUkwuc2VhcmNoLFxuXHRcdHF1ZXJ5OiBwYXJzZWRVUkwucXVlcnksXG5cdFx0aHJlZjogcGFyc2VkVVJMLmhyZWYsXG5cdFx0bWV0aG9kOiByZXF1ZXN0Lm1ldGhvZCxcblx0XHRoZWFkZXJzOiBoZWFkZXJzW1N5bWJvbC5mb3IoJ25vZGVqcy51dGlsLmluc3BlY3QuY3VzdG9tJyldKCksXG5cdFx0aW5zZWN1cmVIVFRQUGFyc2VyOiByZXF1ZXN0Lmluc2VjdXJlSFRUUFBhcnNlcixcblx0XHRhZ2VudFxuXHR9O1xuXG5cdHJldHVybiByZXF1ZXN0T3B0aW9ucztcbn07XG5cbi8qKlxuICogQWJvcnRFcnJvciBpbnRlcmZhY2UgZm9yIGNhbmNlbGxlZCByZXF1ZXN0c1xuICovXG5jbGFzcyBBYm9ydEVycm9yIGV4dGVuZHMgRmV0Y2hCYXNlRXJyb3Ige1xuXHRjb25zdHJ1Y3RvcihtZXNzYWdlLCB0eXBlID0gJ2Fib3J0ZWQnKSB7XG5cdFx0c3VwZXIobWVzc2FnZSwgdHlwZSk7XG5cdH1cbn1cblxuLyoqXG4gKiBJbmRleC5qc1xuICpcbiAqIGEgcmVxdWVzdCBBUEkgY29tcGF0aWJsZSB3aXRoIHdpbmRvdy5mZXRjaFxuICpcbiAqIEFsbCBzcGVjIGFsZ29yaXRobSBzdGVwIG51bWJlcnMgYXJlIGJhc2VkIG9uIGh0dHBzOi8vZmV0Y2guc3BlYy53aGF0d2cub3JnL2NvbW1pdC1zbmFwc2hvdHMvYWU3MTY4MjJjYjNhNjE4NDMyMjZjZDA5MGVlZmM2NTg5NDQ2YzFkMi8uXG4gKi9cblxuY29uc3Qgc3VwcG9ydGVkU2NoZW1hcyA9IG5ldyBTZXQoWydkYXRhOicsICdodHRwOicsICdodHRwczonXSk7XG5cbi8qKlxuICogRmV0Y2ggZnVuY3Rpb25cbiAqXG4gKiBAcGFyYW0gICB7c3RyaW5nIHwgVVJMIHwgaW1wb3J0KCcuL3JlcXVlc3QnKS5kZWZhdWx0fSB1cmwgLSBBYnNvbHV0ZSB1cmwgb3IgUmVxdWVzdCBpbnN0YW5jZVxuICogQHBhcmFtICAgeyp9IFtvcHRpb25zX10gLSBGZXRjaCBvcHRpb25zXG4gKiBAcmV0dXJuICB7UHJvbWlzZTxpbXBvcnQoJy4vcmVzcG9uc2UnKS5kZWZhdWx0Pn1cbiAqL1xuYXN5bmMgZnVuY3Rpb24gZmV0Y2godXJsLCBvcHRpb25zXykge1xuXHRyZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuXHRcdC8vIEJ1aWxkIHJlcXVlc3Qgb2JqZWN0XG5cdFx0Y29uc3QgcmVxdWVzdCA9IG5ldyBSZXF1ZXN0KHVybCwgb3B0aW9uc18pO1xuXHRcdGNvbnN0IG9wdGlvbnMgPSBnZXROb2RlUmVxdWVzdE9wdGlvbnMocmVxdWVzdCk7XG5cdFx0aWYgKCFzdXBwb3J0ZWRTY2hlbWFzLmhhcyhvcHRpb25zLnByb3RvY29sKSkge1xuXHRcdFx0dGhyb3cgbmV3IFR5cGVFcnJvcihgbm9kZS1mZXRjaCBjYW5ub3QgbG9hZCAke3VybH0uIFVSTCBzY2hlbWUgXCIke29wdGlvbnMucHJvdG9jb2wucmVwbGFjZSgvOiQvLCAnJyl9XCIgaXMgbm90IHN1cHBvcnRlZC5gKTtcblx0XHR9XG5cblx0XHRpZiAob3B0aW9ucy5wcm90b2NvbCA9PT0gJ2RhdGE6Jykge1xuXHRcdFx0Y29uc3QgZGF0YSA9IGRhdGFVcmlUb0J1ZmZlcihyZXF1ZXN0LnVybCk7XG5cdFx0XHRjb25zdCByZXNwb25zZSA9IG5ldyBSZXNwb25zZShkYXRhLCB7aGVhZGVyczogeydDb250ZW50LVR5cGUnOiBkYXRhLnR5cGVGdWxsfX0pO1xuXHRcdFx0cmVzb2x2ZShyZXNwb25zZSk7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cdFx0Ly8gV3JhcCBodHRwLnJlcXVlc3QgaW50byBmZXRjaFxuXHRcdGNvbnN0IHNlbmQgPSAob3B0aW9ucy5wcm90b2NvbCA9PT0gJ2h0dHBzOicgPyBodHRwcyA6IGh0dHApLnJlcXVlc3Q7XG5cdFx0Y29uc3Qge3NpZ25hbH0gPSByZXF1ZXN0O1xuXHRcdGxldCByZXNwb25zZSA9IG51bGw7XG5cblx0XHRjb25zdCBhYm9ydCA9ICgpID0+IHtcblx0XHRcdGNvbnN0IGVycm9yID0gbmV3IEFib3J0RXJyb3IoJ1RoZSBvcGVyYXRpb24gd2FzIGFib3J0ZWQuJyk7XG5cdFx0XHRyZWplY3QoZXJyb3IpO1xuXHRcdFx0aWYgKHJlcXVlc3QuYm9keSAmJiByZXF1ZXN0LmJvZHkgaW5zdGFuY2VvZiBTdHJlYW0uUmVhZGFibGUpIHtcblx0XHRcdFx0cmVxdWVzdC5ib2R5LmRlc3Ryb3koZXJyb3IpO1xuXHRcdFx0fVxuXG5cdFx0XHRpZiAoIXJlc3BvbnNlIHx8ICFyZXNwb25zZS5ib2R5KSB7XG5cdFx0XHRcdHJldHVybjtcblx0XHRcdH1cblxuXHRcdFx0cmVzcG9uc2UuYm9keS5lbWl0KCdlcnJvcicsIGVycm9yKTtcblx0XHR9O1xuXG5cdFx0aWYgKHNpZ25hbCAmJiBzaWduYWwuYWJvcnRlZCkge1xuXHRcdFx0YWJvcnQoKTtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cblx0XHRjb25zdCBhYm9ydEFuZEZpbmFsaXplID0gKCkgPT4ge1xuXHRcdFx0YWJvcnQoKTtcblx0XHRcdGZpbmFsaXplKCk7XG5cdFx0fTtcblxuXHRcdC8vIFNlbmQgcmVxdWVzdFxuXHRcdGNvbnN0IHJlcXVlc3RfID0gc2VuZChvcHRpb25zKTtcblxuXHRcdGlmIChzaWduYWwpIHtcblx0XHRcdHNpZ25hbC5hZGRFdmVudExpc3RlbmVyKCdhYm9ydCcsIGFib3J0QW5kRmluYWxpemUpO1xuXHRcdH1cblxuXHRcdGNvbnN0IGZpbmFsaXplID0gKCkgPT4ge1xuXHRcdFx0cmVxdWVzdF8uYWJvcnQoKTtcblx0XHRcdGlmIChzaWduYWwpIHtcblx0XHRcdFx0c2lnbmFsLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ2Fib3J0JywgYWJvcnRBbmRGaW5hbGl6ZSk7XG5cdFx0XHR9XG5cdFx0fTtcblxuXHRcdHJlcXVlc3RfLm9uKCdlcnJvcicsIGVyciA9PiB7XG5cdFx0XHRyZWplY3QobmV3IEZldGNoRXJyb3IoYHJlcXVlc3QgdG8gJHtyZXF1ZXN0LnVybH0gZmFpbGVkLCByZWFzb246ICR7ZXJyLm1lc3NhZ2V9YCwgJ3N5c3RlbScsIGVycikpO1xuXHRcdFx0ZmluYWxpemUoKTtcblx0XHR9KTtcblxuXHRcdHJlcXVlc3RfLm9uKCdyZXNwb25zZScsIHJlc3BvbnNlXyA9PiB7XG5cdFx0XHRyZXF1ZXN0Xy5zZXRUaW1lb3V0KDApO1xuXHRcdFx0Y29uc3QgaGVhZGVycyA9IGZyb21SYXdIZWFkZXJzKHJlc3BvbnNlXy5yYXdIZWFkZXJzKTtcblxuXHRcdFx0Ly8gSFRUUCBmZXRjaCBzdGVwIDVcblx0XHRcdGlmIChpc1JlZGlyZWN0KHJlc3BvbnNlXy5zdGF0dXNDb2RlKSkge1xuXHRcdFx0XHQvLyBIVFRQIGZldGNoIHN0ZXAgNS4yXG5cdFx0XHRcdGNvbnN0IGxvY2F0aW9uID0gaGVhZGVycy5nZXQoJ0xvY2F0aW9uJyk7XG5cblx0XHRcdFx0Ly8gSFRUUCBmZXRjaCBzdGVwIDUuM1xuXHRcdFx0XHRjb25zdCBsb2NhdGlvblVSTCA9IGxvY2F0aW9uID09PSBudWxsID8gbnVsbCA6IG5ldyBVUkwobG9jYXRpb24sIHJlcXVlc3QudXJsKTtcblxuXHRcdFx0XHQvLyBIVFRQIGZldGNoIHN0ZXAgNS41XG5cdFx0XHRcdHN3aXRjaCAocmVxdWVzdC5yZWRpcmVjdCkge1xuXHRcdFx0XHRcdGNhc2UgJ2Vycm9yJzpcblx0XHRcdFx0XHRcdHJlamVjdChuZXcgRmV0Y2hFcnJvcihgdXJpIHJlcXVlc3RlZCByZXNwb25kcyB3aXRoIGEgcmVkaXJlY3QsIHJlZGlyZWN0IG1vZGUgaXMgc2V0IHRvIGVycm9yOiAke3JlcXVlc3QudXJsfWAsICduby1yZWRpcmVjdCcpKTtcblx0XHRcdFx0XHRcdGZpbmFsaXplKCk7XG5cdFx0XHRcdFx0XHRyZXR1cm47XG5cdFx0XHRcdFx0Y2FzZSAnbWFudWFsJzpcblx0XHRcdFx0XHRcdC8vIE5vZGUtZmV0Y2gtc3BlY2lmaWMgc3RlcDogbWFrZSBtYW51YWwgcmVkaXJlY3QgYSBiaXQgZWFzaWVyIHRvIHVzZSBieSBzZXR0aW5nIHRoZSBMb2NhdGlvbiBoZWFkZXIgdmFsdWUgdG8gdGhlIHJlc29sdmVkIFVSTC5cblx0XHRcdFx0XHRcdGlmIChsb2NhdGlvblVSTCAhPT0gbnVsbCkge1xuXHRcdFx0XHRcdFx0XHQvLyBIYW5kbGUgY29ycnVwdGVkIGhlYWRlclxuXHRcdFx0XHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdFx0XHRcdGhlYWRlcnMuc2V0KCdMb2NhdGlvbicsIGxvY2F0aW9uVVJMKTtcblx0XHRcdFx0XHRcdFx0XHQvKiBjOCBpZ25vcmUgbmV4dCAzICovXG5cdFx0XHRcdFx0XHRcdH0gY2F0Y2ggKGVycm9yKSB7XG5cdFx0XHRcdFx0XHRcdFx0cmVqZWN0KGVycm9yKTtcblx0XHRcdFx0XHRcdFx0fVxuXHRcdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0XHRicmVhaztcblx0XHRcdFx0XHRjYXNlICdmb2xsb3cnOiB7XG5cdFx0XHRcdFx0XHQvLyBIVFRQLXJlZGlyZWN0IGZldGNoIHN0ZXAgMlxuXHRcdFx0XHRcdFx0aWYgKGxvY2F0aW9uVVJMID09PSBudWxsKSB7XG5cdFx0XHRcdFx0XHRcdGJyZWFrO1xuXHRcdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0XHQvLyBIVFRQLXJlZGlyZWN0IGZldGNoIHN0ZXAgNVxuXHRcdFx0XHRcdFx0aWYgKHJlcXVlc3QuY291bnRlciA+PSByZXF1ZXN0LmZvbGxvdykge1xuXHRcdFx0XHRcdFx0XHRyZWplY3QobmV3IEZldGNoRXJyb3IoYG1heGltdW0gcmVkaXJlY3QgcmVhY2hlZCBhdDogJHtyZXF1ZXN0LnVybH1gLCAnbWF4LXJlZGlyZWN0JykpO1xuXHRcdFx0XHRcdFx0XHRmaW5hbGl6ZSgpO1xuXHRcdFx0XHRcdFx0XHRyZXR1cm47XG5cdFx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHRcdC8vIEhUVFAtcmVkaXJlY3QgZmV0Y2ggc3RlcCA2IChjb3VudGVyIGluY3JlbWVudClcblx0XHRcdFx0XHRcdC8vIENyZWF0ZSBhIG5ldyBSZXF1ZXN0IG9iamVjdC5cblx0XHRcdFx0XHRcdGNvbnN0IHJlcXVlc3RPcHRpb25zID0ge1xuXHRcdFx0XHRcdFx0XHRoZWFkZXJzOiBuZXcgSGVhZGVycyhyZXF1ZXN0LmhlYWRlcnMpLFxuXHRcdFx0XHRcdFx0XHRmb2xsb3c6IHJlcXVlc3QuZm9sbG93LFxuXHRcdFx0XHRcdFx0XHRjb3VudGVyOiByZXF1ZXN0LmNvdW50ZXIgKyAxLFxuXHRcdFx0XHRcdFx0XHRhZ2VudDogcmVxdWVzdC5hZ2VudCxcblx0XHRcdFx0XHRcdFx0Y29tcHJlc3M6IHJlcXVlc3QuY29tcHJlc3MsXG5cdFx0XHRcdFx0XHRcdG1ldGhvZDogcmVxdWVzdC5tZXRob2QsXG5cdFx0XHRcdFx0XHRcdGJvZHk6IHJlcXVlc3QuYm9keSxcblx0XHRcdFx0XHRcdFx0c2lnbmFsOiByZXF1ZXN0LnNpZ25hbCxcblx0XHRcdFx0XHRcdFx0c2l6ZTogcmVxdWVzdC5zaXplXG5cdFx0XHRcdFx0XHR9O1xuXG5cdFx0XHRcdFx0XHQvLyBIVFRQLXJlZGlyZWN0IGZldGNoIHN0ZXAgOVxuXHRcdFx0XHRcdFx0aWYgKHJlc3BvbnNlXy5zdGF0dXNDb2RlICE9PSAzMDMgJiYgcmVxdWVzdC5ib2R5ICYmIG9wdGlvbnNfLmJvZHkgaW5zdGFuY2VvZiBTdHJlYW0uUmVhZGFibGUpIHtcblx0XHRcdFx0XHRcdFx0cmVqZWN0KG5ldyBGZXRjaEVycm9yKCdDYW5ub3QgZm9sbG93IHJlZGlyZWN0IHdpdGggYm9keSBiZWluZyBhIHJlYWRhYmxlIHN0cmVhbScsICd1bnN1cHBvcnRlZC1yZWRpcmVjdCcpKTtcblx0XHRcdFx0XHRcdFx0ZmluYWxpemUoKTtcblx0XHRcdFx0XHRcdFx0cmV0dXJuO1xuXHRcdFx0XHRcdFx0fVxuXG5cdFx0XHRcdFx0XHQvLyBIVFRQLXJlZGlyZWN0IGZldGNoIHN0ZXAgMTFcblx0XHRcdFx0XHRcdGlmIChyZXNwb25zZV8uc3RhdHVzQ29kZSA9PT0gMzAzIHx8ICgocmVzcG9uc2VfLnN0YXR1c0NvZGUgPT09IDMwMSB8fCByZXNwb25zZV8uc3RhdHVzQ29kZSA9PT0gMzAyKSAmJiByZXF1ZXN0Lm1ldGhvZCA9PT0gJ1BPU1QnKSkge1xuXHRcdFx0XHRcdFx0XHRyZXF1ZXN0T3B0aW9ucy5tZXRob2QgPSAnR0VUJztcblx0XHRcdFx0XHRcdFx0cmVxdWVzdE9wdGlvbnMuYm9keSA9IHVuZGVmaW5lZDtcblx0XHRcdFx0XHRcdFx0cmVxdWVzdE9wdGlvbnMuaGVhZGVycy5kZWxldGUoJ2NvbnRlbnQtbGVuZ3RoJyk7XG5cdFx0XHRcdFx0XHR9XG5cblx0XHRcdFx0XHRcdC8vIEhUVFAtcmVkaXJlY3QgZmV0Y2ggc3RlcCAxNVxuXHRcdFx0XHRcdFx0cmVzb2x2ZShmZXRjaChuZXcgUmVxdWVzdChsb2NhdGlvblVSTCwgcmVxdWVzdE9wdGlvbnMpKSk7XG5cdFx0XHRcdFx0XHRmaW5hbGl6ZSgpO1xuXHRcdFx0XHRcdFx0cmV0dXJuO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHQvLyBEbyBub3RoaW5nXG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0Ly8gUHJlcGFyZSByZXNwb25zZVxuXHRcdFx0cmVzcG9uc2VfLm9uY2UoJ2VuZCcsICgpID0+IHtcblx0XHRcdFx0aWYgKHNpZ25hbCkge1xuXHRcdFx0XHRcdHNpZ25hbC5yZW1vdmVFdmVudExpc3RlbmVyKCdhYm9ydCcsIGFib3J0QW5kRmluYWxpemUpO1xuXHRcdFx0XHR9XG5cdFx0XHR9KTtcblxuXHRcdFx0bGV0IGJvZHkgPSBTdHJlYW0ucGlwZWxpbmUocmVzcG9uc2VfLCBuZXcgU3RyZWFtLlBhc3NUaHJvdWdoKCksIGVycm9yID0+IHtcblx0XHRcdFx0cmVqZWN0KGVycm9yKTtcblx0XHRcdH0pO1xuXHRcdFx0Ly8gc2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9ub2RlanMvbm9kZS9wdWxsLzI5Mzc2XG5cdFx0XHRpZiAocHJvY2Vzcy52ZXJzaW9uIDwgJ3YxMi4xMCcpIHtcblx0XHRcdFx0cmVzcG9uc2VfLm9uKCdhYm9ydGVkJywgYWJvcnRBbmRGaW5hbGl6ZSk7XG5cdFx0XHR9XG5cblx0XHRcdGNvbnN0IHJlc3BvbnNlT3B0aW9ucyA9IHtcblx0XHRcdFx0dXJsOiByZXF1ZXN0LnVybCxcblx0XHRcdFx0c3RhdHVzOiByZXNwb25zZV8uc3RhdHVzQ29kZSxcblx0XHRcdFx0c3RhdHVzVGV4dDogcmVzcG9uc2VfLnN0YXR1c01lc3NhZ2UsXG5cdFx0XHRcdGhlYWRlcnMsXG5cdFx0XHRcdHNpemU6IHJlcXVlc3Quc2l6ZSxcblx0XHRcdFx0Y291bnRlcjogcmVxdWVzdC5jb3VudGVyLFxuXHRcdFx0XHRoaWdoV2F0ZXJNYXJrOiByZXF1ZXN0LmhpZ2hXYXRlck1hcmtcblx0XHRcdH07XG5cblx0XHRcdC8vIEhUVFAtbmV0d29yayBmZXRjaCBzdGVwIDEyLjEuMS4zXG5cdFx0XHRjb25zdCBjb2RpbmdzID0gaGVhZGVycy5nZXQoJ0NvbnRlbnQtRW5jb2RpbmcnKTtcblxuXHRcdFx0Ly8gSFRUUC1uZXR3b3JrIGZldGNoIHN0ZXAgMTIuMS4xLjQ6IGhhbmRsZSBjb250ZW50IGNvZGluZ3NcblxuXHRcdFx0Ly8gaW4gZm9sbG93aW5nIHNjZW5hcmlvcyB3ZSBpZ25vcmUgY29tcHJlc3Npb24gc3VwcG9ydFxuXHRcdFx0Ly8gMS4gY29tcHJlc3Npb24gc3VwcG9ydCBpcyBkaXNhYmxlZFxuXHRcdFx0Ly8gMi4gSEVBRCByZXF1ZXN0XG5cdFx0XHQvLyAzLiBubyBDb250ZW50LUVuY29kaW5nIGhlYWRlclxuXHRcdFx0Ly8gNC4gbm8gY29udGVudCByZXNwb25zZSAoMjA0KVxuXHRcdFx0Ly8gNS4gY29udGVudCBub3QgbW9kaWZpZWQgcmVzcG9uc2UgKDMwNClcblx0XHRcdGlmICghcmVxdWVzdC5jb21wcmVzcyB8fCByZXF1ZXN0Lm1ldGhvZCA9PT0gJ0hFQUQnIHx8IGNvZGluZ3MgPT09IG51bGwgfHwgcmVzcG9uc2VfLnN0YXR1c0NvZGUgPT09IDIwNCB8fCByZXNwb25zZV8uc3RhdHVzQ29kZSA9PT0gMzA0KSB7XG5cdFx0XHRcdHJlc3BvbnNlID0gbmV3IFJlc3BvbnNlKGJvZHksIHJlc3BvbnNlT3B0aW9ucyk7XG5cdFx0XHRcdHJlc29sdmUocmVzcG9uc2UpO1xuXHRcdFx0XHRyZXR1cm47XG5cdFx0XHR9XG5cblx0XHRcdC8vIEZvciBOb2RlIHY2K1xuXHRcdFx0Ly8gQmUgbGVzcyBzdHJpY3Qgd2hlbiBkZWNvZGluZyBjb21wcmVzc2VkIHJlc3BvbnNlcywgc2luY2Ugc29tZXRpbWVzXG5cdFx0XHQvLyBzZXJ2ZXJzIHNlbmQgc2xpZ2h0bHkgaW52YWxpZCByZXNwb25zZXMgdGhhdCBhcmUgc3RpbGwgYWNjZXB0ZWRcblx0XHRcdC8vIGJ5IGNvbW1vbiBicm93c2Vycy5cblx0XHRcdC8vIEFsd2F5cyB1c2luZyBaX1NZTkNfRkxVU0ggaXMgd2hhdCBjVVJMIGRvZXMuXG5cdFx0XHRjb25zdCB6bGliT3B0aW9ucyA9IHtcblx0XHRcdFx0Zmx1c2g6IHpsaWIuWl9TWU5DX0ZMVVNILFxuXHRcdFx0XHRmaW5pc2hGbHVzaDogemxpYi5aX1NZTkNfRkxVU0hcblx0XHRcdH07XG5cblx0XHRcdC8vIEZvciBnemlwXG5cdFx0XHRpZiAoY29kaW5ncyA9PT0gJ2d6aXAnIHx8IGNvZGluZ3MgPT09ICd4LWd6aXAnKSB7XG5cdFx0XHRcdGJvZHkgPSBTdHJlYW0ucGlwZWxpbmUoYm9keSwgemxpYi5jcmVhdGVHdW56aXAoemxpYk9wdGlvbnMpLCBlcnJvciA9PiB7XG5cdFx0XHRcdFx0cmVqZWN0KGVycm9yKTtcblx0XHRcdFx0fSk7XG5cdFx0XHRcdHJlc3BvbnNlID0gbmV3IFJlc3BvbnNlKGJvZHksIHJlc3BvbnNlT3B0aW9ucyk7XG5cdFx0XHRcdHJlc29sdmUocmVzcG9uc2UpO1xuXHRcdFx0XHRyZXR1cm47XG5cdFx0XHR9XG5cblx0XHRcdC8vIEZvciBkZWZsYXRlXG5cdFx0XHRpZiAoY29kaW5ncyA9PT0gJ2RlZmxhdGUnIHx8IGNvZGluZ3MgPT09ICd4LWRlZmxhdGUnKSB7XG5cdFx0XHRcdC8vIEhhbmRsZSB0aGUgaW5mYW1vdXMgcmF3IGRlZmxhdGUgcmVzcG9uc2UgZnJvbSBvbGQgc2VydmVyc1xuXHRcdFx0XHQvLyBhIGhhY2sgZm9yIG9sZCBJSVMgYW5kIEFwYWNoZSBzZXJ2ZXJzXG5cdFx0XHRcdGNvbnN0IHJhdyA9IFN0cmVhbS5waXBlbGluZShyZXNwb25zZV8sIG5ldyBTdHJlYW0uUGFzc1Rocm91Z2goKSwgZXJyb3IgPT4ge1xuXHRcdFx0XHRcdHJlamVjdChlcnJvcik7XG5cdFx0XHRcdH0pO1xuXHRcdFx0XHRyYXcub25jZSgnZGF0YScsIGNodW5rID0+IHtcblx0XHRcdFx0XHQvLyBTZWUgaHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8zNzUxOTgyOFxuXHRcdFx0XHRcdGlmICgoY2h1bmtbMF0gJiAweDBGKSA9PT0gMHgwOCkge1xuXHRcdFx0XHRcdFx0Ym9keSA9IFN0cmVhbS5waXBlbGluZShib2R5LCB6bGliLmNyZWF0ZUluZmxhdGUoKSwgZXJyb3IgPT4ge1xuXHRcdFx0XHRcdFx0XHRyZWplY3QoZXJyb3IpO1xuXHRcdFx0XHRcdFx0fSk7XG5cdFx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRcdGJvZHkgPSBTdHJlYW0ucGlwZWxpbmUoYm9keSwgemxpYi5jcmVhdGVJbmZsYXRlUmF3KCksIGVycm9yID0+IHtcblx0XHRcdFx0XHRcdFx0cmVqZWN0KGVycm9yKTtcblx0XHRcdFx0XHRcdH0pO1xuXHRcdFx0XHRcdH1cblxuXHRcdFx0XHRcdHJlc3BvbnNlID0gbmV3IFJlc3BvbnNlKGJvZHksIHJlc3BvbnNlT3B0aW9ucyk7XG5cdFx0XHRcdFx0cmVzb2x2ZShyZXNwb25zZSk7XG5cdFx0XHRcdH0pO1xuXHRcdFx0XHRyZXR1cm47XG5cdFx0XHR9XG5cblx0XHRcdC8vIEZvciBiclxuXHRcdFx0aWYgKGNvZGluZ3MgPT09ICdicicpIHtcblx0XHRcdFx0Ym9keSA9IFN0cmVhbS5waXBlbGluZShib2R5LCB6bGliLmNyZWF0ZUJyb3RsaURlY29tcHJlc3MoKSwgZXJyb3IgPT4ge1xuXHRcdFx0XHRcdHJlamVjdChlcnJvcik7XG5cdFx0XHRcdH0pO1xuXHRcdFx0XHRyZXNwb25zZSA9IG5ldyBSZXNwb25zZShib2R5LCByZXNwb25zZU9wdGlvbnMpO1xuXHRcdFx0XHRyZXNvbHZlKHJlc3BvbnNlKTtcblx0XHRcdFx0cmV0dXJuO1xuXHRcdFx0fVxuXG5cdFx0XHQvLyBPdGhlcndpc2UsIHVzZSByZXNwb25zZSBhcy1pc1xuXHRcdFx0cmVzcG9uc2UgPSBuZXcgUmVzcG9uc2UoYm9keSwgcmVzcG9uc2VPcHRpb25zKTtcblx0XHRcdHJlc29sdmUocmVzcG9uc2UpO1xuXHRcdH0pO1xuXG5cdFx0d3JpdGVUb1N0cmVhbShyZXF1ZXN0XywgcmVxdWVzdCk7XG5cdH0pO1xufVxuXG5leHBvcnRzLkFib3J0RXJyb3IgPSBBYm9ydEVycm9yO1xuZXhwb3J0cy5GZXRjaEVycm9yID0gRmV0Y2hFcnJvcjtcbmV4cG9ydHMuSGVhZGVycyA9IEhlYWRlcnM7XG5leHBvcnRzLlJlcXVlc3QgPSBSZXF1ZXN0O1xuZXhwb3J0cy5SZXNwb25zZSA9IFJlc3BvbnNlO1xuZXhwb3J0cy5kZWZhdWx0ID0gZmV0Y2g7XG5leHBvcnRzLmlzUmVkaXJlY3QgPSBpc1JlZGlyZWN0O1xuLy8jIHNvdXJjZU1hcHBpbmdVUkw9aW5kZXguY2pzLm1hcFxuIiwiZnVuY3Rpb24gbm9ybWFsaXplIChzdHIpIHtcbiAgcmV0dXJuIHN0clxuICAgICAgICAgIC5yZXBsYWNlKC9bXFwvXSsvZywgJy8nKVxuICAgICAgICAgIC5yZXBsYWNlKC9cXC9cXD8vZywgJz8nKVxuICAgICAgICAgIC5yZXBsYWNlKC9cXC9cXCMvZywgJyMnKVxuICAgICAgICAgIC5yZXBsYWNlKC9cXDpcXC8vZywgJzovLycpO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uICgpIHtcbiAgdmFyIGpvaW5lZCA9IFtdLnNsaWNlLmNhbGwoYXJndW1lbnRzLCAwKS5qb2luKCcvJyk7XG4gIHJldHVybiBub3JtYWxpemUoam9pbmVkKTtcbn07IiwiLyoqXG4gKiB3ZWItc3RyZWFtcy1wb2x5ZmlsbCB2My4wLjFcbiAqL1xuLy8vIDxyZWZlcmVuY2UgbGliPVwiZXMyMDE1LnN5bWJvbFwiIC8+XG5jb25zdCBTeW1ib2xQb2x5ZmlsbCA9IHR5cGVvZiBTeW1ib2wgPT09ICdmdW5jdGlvbicgJiYgdHlwZW9mIFN5bWJvbC5pdGVyYXRvciA9PT0gJ3N5bWJvbCcgP1xuICAgIFN5bWJvbCA6XG4gICAgZGVzY3JpcHRpb24gPT4gYFN5bWJvbCgke2Rlc2NyaXB0aW9ufSlgO1xuXG4vLy8gPHJlZmVyZW5jZSBsaWI9XCJkb21cIiAvPlxuZnVuY3Rpb24gbm9vcCgpIHtcbiAgICAvLyBkbyBub3RoaW5nXG59XG5mdW5jdGlvbiBnZXRHbG9iYWxzKCkge1xuICAgIGlmICh0eXBlb2Ygc2VsZiAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgICAgcmV0dXJuIHNlbGY7XG4gICAgfVxuICAgIGVsc2UgaWYgKHR5cGVvZiB3aW5kb3cgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgIHJldHVybiB3aW5kb3c7XG4gICAgfVxuICAgIGVsc2UgaWYgKHR5cGVvZiBnbG9iYWwgIT09ICd1bmRlZmluZWQnKSB7XG4gICAgICAgIHJldHVybiBnbG9iYWw7XG4gICAgfVxuICAgIHJldHVybiB1bmRlZmluZWQ7XG59XG5jb25zdCBnbG9iYWxzID0gZ2V0R2xvYmFscygpO1xuXG5mdW5jdGlvbiB0eXBlSXNPYmplY3QoeCkge1xuICAgIHJldHVybiAodHlwZW9mIHggPT09ICdvYmplY3QnICYmIHggIT09IG51bGwpIHx8IHR5cGVvZiB4ID09PSAnZnVuY3Rpb24nO1xufVxuY29uc3QgcmV0aHJvd0Fzc2VydGlvbkVycm9yUmVqZWN0aW9uID0gIG5vb3A7XG5cbmNvbnN0IG9yaWdpbmFsUHJvbWlzZSA9IFByb21pc2U7XG5jb25zdCBvcmlnaW5hbFByb21pc2VUaGVuID0gUHJvbWlzZS5wcm90b3R5cGUudGhlbjtcbmNvbnN0IG9yaWdpbmFsUHJvbWlzZVJlc29sdmUgPSBQcm9taXNlLnJlc29sdmUuYmluZChvcmlnaW5hbFByb21pc2UpO1xuY29uc3Qgb3JpZ2luYWxQcm9taXNlUmVqZWN0ID0gUHJvbWlzZS5yZWplY3QuYmluZChvcmlnaW5hbFByb21pc2UpO1xuZnVuY3Rpb24gbmV3UHJvbWlzZShleGVjdXRvcikge1xuICAgIHJldHVybiBuZXcgb3JpZ2luYWxQcm9taXNlKGV4ZWN1dG9yKTtcbn1cbmZ1bmN0aW9uIHByb21pc2VSZXNvbHZlZFdpdGgodmFsdWUpIHtcbiAgICByZXR1cm4gb3JpZ2luYWxQcm9taXNlUmVzb2x2ZSh2YWx1ZSk7XG59XG5mdW5jdGlvbiBwcm9taXNlUmVqZWN0ZWRXaXRoKHJlYXNvbikge1xuICAgIHJldHVybiBvcmlnaW5hbFByb21pc2VSZWplY3QocmVhc29uKTtcbn1cbmZ1bmN0aW9uIFBlcmZvcm1Qcm9taXNlVGhlbihwcm9taXNlLCBvbkZ1bGZpbGxlZCwgb25SZWplY3RlZCkge1xuICAgIC8vIFRoZXJlIGRvZXNuJ3QgYXBwZWFyIHRvIGJlIGFueSB3YXkgdG8gY29ycmVjdGx5IGVtdWxhdGUgdGhlIGJlaGF2aW91ciBmcm9tIEphdmFTY3JpcHQsIHNvIHRoaXMgaXMganVzdCBhblxuICAgIC8vIGFwcHJveGltYXRpb24uXG4gICAgcmV0dXJuIG9yaWdpbmFsUHJvbWlzZVRoZW4uY2FsbChwcm9taXNlLCBvbkZ1bGZpbGxlZCwgb25SZWplY3RlZCk7XG59XG5mdW5jdGlvbiB1cG9uUHJvbWlzZShwcm9taXNlLCBvbkZ1bGZpbGxlZCwgb25SZWplY3RlZCkge1xuICAgIFBlcmZvcm1Qcm9taXNlVGhlbihQZXJmb3JtUHJvbWlzZVRoZW4ocHJvbWlzZSwgb25GdWxmaWxsZWQsIG9uUmVqZWN0ZWQpLCB1bmRlZmluZWQsIHJldGhyb3dBc3NlcnRpb25FcnJvclJlamVjdGlvbik7XG59XG5mdW5jdGlvbiB1cG9uRnVsZmlsbG1lbnQocHJvbWlzZSwgb25GdWxmaWxsZWQpIHtcbiAgICB1cG9uUHJvbWlzZShwcm9taXNlLCBvbkZ1bGZpbGxlZCk7XG59XG5mdW5jdGlvbiB1cG9uUmVqZWN0aW9uKHByb21pc2UsIG9uUmVqZWN0ZWQpIHtcbiAgICB1cG9uUHJvbWlzZShwcm9taXNlLCB1bmRlZmluZWQsIG9uUmVqZWN0ZWQpO1xufVxuZnVuY3Rpb24gdHJhbnNmb3JtUHJvbWlzZVdpdGgocHJvbWlzZSwgZnVsZmlsbG1lbnRIYW5kbGVyLCByZWplY3Rpb25IYW5kbGVyKSB7XG4gICAgcmV0dXJuIFBlcmZvcm1Qcm9taXNlVGhlbihwcm9taXNlLCBmdWxmaWxsbWVudEhhbmRsZXIsIHJlamVjdGlvbkhhbmRsZXIpO1xufVxuZnVuY3Rpb24gc2V0UHJvbWlzZUlzSGFuZGxlZFRvVHJ1ZShwcm9taXNlKSB7XG4gICAgUGVyZm9ybVByb21pc2VUaGVuKHByb21pc2UsIHVuZGVmaW5lZCwgcmV0aHJvd0Fzc2VydGlvbkVycm9yUmVqZWN0aW9uKTtcbn1cbmNvbnN0IHF1ZXVlTWljcm90YXNrID0gKCgpID0+IHtcbiAgICBjb25zdCBnbG9iYWxRdWV1ZU1pY3JvdGFzayA9IGdsb2JhbHMgJiYgZ2xvYmFscy5xdWV1ZU1pY3JvdGFzaztcbiAgICBpZiAodHlwZW9mIGdsb2JhbFF1ZXVlTWljcm90YXNrID09PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIHJldHVybiBnbG9iYWxRdWV1ZU1pY3JvdGFzaztcbiAgICB9XG4gICAgY29uc3QgcmVzb2x2ZWRQcm9taXNlID0gcHJvbWlzZVJlc29sdmVkV2l0aCh1bmRlZmluZWQpO1xuICAgIHJldHVybiAoZm4pID0+IFBlcmZvcm1Qcm9taXNlVGhlbihyZXNvbHZlZFByb21pc2UsIGZuKTtcbn0pKCk7XG5mdW5jdGlvbiByZWZsZWN0Q2FsbChGLCBWLCBhcmdzKSB7XG4gICAgaWYgKHR5cGVvZiBGICE9PSAnZnVuY3Rpb24nKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0FyZ3VtZW50IGlzIG5vdCBhIGZ1bmN0aW9uJyk7XG4gICAgfVxuICAgIHJldHVybiBGdW5jdGlvbi5wcm90b3R5cGUuYXBwbHkuY2FsbChGLCBWLCBhcmdzKTtcbn1cbmZ1bmN0aW9uIHByb21pc2VDYWxsKEYsIFYsIGFyZ3MpIHtcbiAgICB0cnkge1xuICAgICAgICByZXR1cm4gcHJvbWlzZVJlc29sdmVkV2l0aChyZWZsZWN0Q2FsbChGLCBWLCBhcmdzKSk7XG4gICAgfVxuICAgIGNhdGNoICh2YWx1ZSkge1xuICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aCh2YWx1ZSk7XG4gICAgfVxufVxuXG4vLyBPcmlnaW5hbCBmcm9tIENocm9taXVtXG4vLyBodHRwczovL2Nocm9taXVtLmdvb2dsZXNvdXJjZS5jb20vY2hyb21pdW0vc3JjLysvMGFlZTQ0MzRhNGRiYTQyYTQyYWJhZWE5YmZiYzBjZDE5NmE2M2JjMS90aGlyZF9wYXJ0eS9ibGluay9yZW5kZXJlci9jb3JlL3N0cmVhbXMvU2ltcGxlUXVldWUuanNcbmNvbnN0IFFVRVVFX01BWF9BUlJBWV9TSVpFID0gMTYzODQ7XG4vKipcbiAqIFNpbXBsZSBxdWV1ZSBzdHJ1Y3R1cmUuXG4gKlxuICogQXZvaWRzIHNjYWxhYmlsaXR5IGlzc3VlcyB3aXRoIHVzaW5nIGEgcGFja2VkIGFycmF5IGRpcmVjdGx5IGJ5IHVzaW5nXG4gKiBtdWx0aXBsZSBhcnJheXMgaW4gYSBsaW5rZWQgbGlzdCBhbmQga2VlcGluZyB0aGUgYXJyYXkgc2l6ZSBib3VuZGVkLlxuICovXG5jbGFzcyBTaW1wbGVRdWV1ZSB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRoaXMuX2N1cnNvciA9IDA7XG4gICAgICAgIHRoaXMuX3NpemUgPSAwO1xuICAgICAgICAvLyBfZnJvbnQgYW5kIF9iYWNrIGFyZSBhbHdheXMgZGVmaW5lZC5cbiAgICAgICAgdGhpcy5fZnJvbnQgPSB7XG4gICAgICAgICAgICBfZWxlbWVudHM6IFtdLFxuICAgICAgICAgICAgX25leHQ6IHVuZGVmaW5lZFxuICAgICAgICB9O1xuICAgICAgICB0aGlzLl9iYWNrID0gdGhpcy5fZnJvbnQ7XG4gICAgICAgIC8vIFRoZSBjdXJzb3IgaXMgdXNlZCB0byBhdm9pZCBjYWxsaW5nIEFycmF5LnNoaWZ0KCkuXG4gICAgICAgIC8vIEl0IGNvbnRhaW5zIHRoZSBpbmRleCBvZiB0aGUgZnJvbnQgZWxlbWVudCBvZiB0aGUgYXJyYXkgaW5zaWRlIHRoZVxuICAgICAgICAvLyBmcm9udC1tb3N0IG5vZGUuIEl0IGlzIGFsd2F5cyBpbiB0aGUgcmFuZ2UgWzAsIFFVRVVFX01BWF9BUlJBWV9TSVpFKS5cbiAgICAgICAgdGhpcy5fY3Vyc29yID0gMDtcbiAgICAgICAgLy8gV2hlbiB0aGVyZSBpcyBvbmx5IG9uZSBub2RlLCBzaXplID09PSBlbGVtZW50cy5sZW5ndGggLSBjdXJzb3IuXG4gICAgICAgIHRoaXMuX3NpemUgPSAwO1xuICAgIH1cbiAgICBnZXQgbGVuZ3RoKCkge1xuICAgICAgICByZXR1cm4gdGhpcy5fc2l6ZTtcbiAgICB9XG4gICAgLy8gRm9yIGV4Y2VwdGlvbiBzYWZldHksIHRoaXMgbWV0aG9kIGlzIHN0cnVjdHVyZWQgaW4gb3JkZXI6XG4gICAgLy8gMS4gUmVhZCBzdGF0ZVxuICAgIC8vIDIuIENhbGN1bGF0ZSByZXF1aXJlZCBzdGF0ZSBtdXRhdGlvbnNcbiAgICAvLyAzLiBQZXJmb3JtIHN0YXRlIG11dGF0aW9uc1xuICAgIHB1c2goZWxlbWVudCkge1xuICAgICAgICBjb25zdCBvbGRCYWNrID0gdGhpcy5fYmFjaztcbiAgICAgICAgbGV0IG5ld0JhY2sgPSBvbGRCYWNrO1xuICAgICAgICBpZiAob2xkQmFjay5fZWxlbWVudHMubGVuZ3RoID09PSBRVUVVRV9NQVhfQVJSQVlfU0laRSAtIDEpIHtcbiAgICAgICAgICAgIG5ld0JhY2sgPSB7XG4gICAgICAgICAgICAgICAgX2VsZW1lbnRzOiBbXSxcbiAgICAgICAgICAgICAgICBfbmV4dDogdW5kZWZpbmVkXG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICAgIC8vIHB1c2goKSBpcyB0aGUgbXV0YXRpb24gbW9zdCBsaWtlbHkgdG8gdGhyb3cgYW4gZXhjZXB0aW9uLCBzbyBpdFxuICAgICAgICAvLyBnb2VzIGZpcnN0LlxuICAgICAgICBvbGRCYWNrLl9lbGVtZW50cy5wdXNoKGVsZW1lbnQpO1xuICAgICAgICBpZiAobmV3QmFjayAhPT0gb2xkQmFjaykge1xuICAgICAgICAgICAgdGhpcy5fYmFjayA9IG5ld0JhY2s7XG4gICAgICAgICAgICBvbGRCYWNrLl9uZXh0ID0gbmV3QmFjaztcbiAgICAgICAgfVxuICAgICAgICArK3RoaXMuX3NpemU7XG4gICAgfVxuICAgIC8vIExpa2UgcHVzaCgpLCBzaGlmdCgpIGZvbGxvd3MgdGhlIHJlYWQgLT4gY2FsY3VsYXRlIC0+IG11dGF0ZSBwYXR0ZXJuIGZvclxuICAgIC8vIGV4Y2VwdGlvbiBzYWZldHkuXG4gICAgc2hpZnQoKSB7IC8vIG11c3Qgbm90IGJlIGNhbGxlZCBvbiBhbiBlbXB0eSBxdWV1ZVxuICAgICAgICBjb25zdCBvbGRGcm9udCA9IHRoaXMuX2Zyb250O1xuICAgICAgICBsZXQgbmV3RnJvbnQgPSBvbGRGcm9udDtcbiAgICAgICAgY29uc3Qgb2xkQ3Vyc29yID0gdGhpcy5fY3Vyc29yO1xuICAgICAgICBsZXQgbmV3Q3Vyc29yID0gb2xkQ3Vyc29yICsgMTtcbiAgICAgICAgY29uc3QgZWxlbWVudHMgPSBvbGRGcm9udC5fZWxlbWVudHM7XG4gICAgICAgIGNvbnN0IGVsZW1lbnQgPSBlbGVtZW50c1tvbGRDdXJzb3JdO1xuICAgICAgICBpZiAobmV3Q3Vyc29yID09PSBRVUVVRV9NQVhfQVJSQVlfU0laRSkge1xuICAgICAgICAgICAgbmV3RnJvbnQgPSBvbGRGcm9udC5fbmV4dDtcbiAgICAgICAgICAgIG5ld0N1cnNvciA9IDA7XG4gICAgICAgIH1cbiAgICAgICAgLy8gTm8gbXV0YXRpb25zIGJlZm9yZSB0aGlzIHBvaW50LlxuICAgICAgICAtLXRoaXMuX3NpemU7XG4gICAgICAgIHRoaXMuX2N1cnNvciA9IG5ld0N1cnNvcjtcbiAgICAgICAgaWYgKG9sZEZyb250ICE9PSBuZXdGcm9udCkge1xuICAgICAgICAgICAgdGhpcy5fZnJvbnQgPSBuZXdGcm9udDtcbiAgICAgICAgfVxuICAgICAgICAvLyBQZXJtaXQgc2hpZnRlZCBlbGVtZW50IHRvIGJlIGdhcmJhZ2UgY29sbGVjdGVkLlxuICAgICAgICBlbGVtZW50c1tvbGRDdXJzb3JdID0gdW5kZWZpbmVkO1xuICAgICAgICByZXR1cm4gZWxlbWVudDtcbiAgICB9XG4gICAgLy8gVGhlIHRyaWNreSB0aGluZyBhYm91dCBmb3JFYWNoKCkgaXMgdGhhdCBpdCBjYW4gYmUgY2FsbGVkXG4gICAgLy8gcmUtZW50cmFudGx5LiBUaGUgcXVldWUgbWF5IGJlIG11dGF0ZWQgaW5zaWRlIHRoZSBjYWxsYmFjay4gSXQgaXMgZWFzeSB0b1xuICAgIC8vIHNlZSB0aGF0IHB1c2goKSB3aXRoaW4gdGhlIGNhbGxiYWNrIGhhcyBubyBuZWdhdGl2ZSBlZmZlY3RzIHNpbmNlIHRoZSBlbmRcbiAgICAvLyBvZiB0aGUgcXVldWUgaXMgY2hlY2tlZCBmb3Igb24gZXZlcnkgaXRlcmF0aW9uLiBJZiBzaGlmdCgpIGlzIGNhbGxlZFxuICAgIC8vIHJlcGVhdGVkbHkgd2l0aGluIHRoZSBjYWxsYmFjayB0aGVuIHRoZSBuZXh0IGl0ZXJhdGlvbiBtYXkgcmV0dXJuIGFuXG4gICAgLy8gZWxlbWVudCB0aGF0IGhhcyBiZWVuIHJlbW92ZWQuIEluIHRoaXMgY2FzZSB0aGUgY2FsbGJhY2sgd2lsbCBiZSBjYWxsZWRcbiAgICAvLyB3aXRoIHVuZGVmaW5lZCB2YWx1ZXMgdW50aWwgd2UgZWl0aGVyIFwiY2F0Y2ggdXBcIiB3aXRoIGVsZW1lbnRzIHRoYXQgc3RpbGxcbiAgICAvLyBleGlzdCBvciByZWFjaCB0aGUgYmFjayBvZiB0aGUgcXVldWUuXG4gICAgZm9yRWFjaChjYWxsYmFjaykge1xuICAgICAgICBsZXQgaSA9IHRoaXMuX2N1cnNvcjtcbiAgICAgICAgbGV0IG5vZGUgPSB0aGlzLl9mcm9udDtcbiAgICAgICAgbGV0IGVsZW1lbnRzID0gbm9kZS5fZWxlbWVudHM7XG4gICAgICAgIHdoaWxlIChpICE9PSBlbGVtZW50cy5sZW5ndGggfHwgbm9kZS5fbmV4dCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBpZiAoaSA9PT0gZWxlbWVudHMubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgbm9kZSA9IG5vZGUuX25leHQ7XG4gICAgICAgICAgICAgICAgZWxlbWVudHMgPSBub2RlLl9lbGVtZW50cztcbiAgICAgICAgICAgICAgICBpID0gMDtcbiAgICAgICAgICAgICAgICBpZiAoZWxlbWVudHMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNhbGxiYWNrKGVsZW1lbnRzW2ldKTtcbiAgICAgICAgICAgICsraTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvLyBSZXR1cm4gdGhlIGVsZW1lbnQgdGhhdCB3b3VsZCBiZSByZXR1cm5lZCBpZiBzaGlmdCgpIHdhcyBjYWxsZWQgbm93LFxuICAgIC8vIHdpdGhvdXQgbW9kaWZ5aW5nIHRoZSBxdWV1ZS5cbiAgICBwZWVrKCkgeyAvLyBtdXN0IG5vdCBiZSBjYWxsZWQgb24gYW4gZW1wdHkgcXVldWVcbiAgICAgICAgY29uc3QgZnJvbnQgPSB0aGlzLl9mcm9udDtcbiAgICAgICAgY29uc3QgY3Vyc29yID0gdGhpcy5fY3Vyc29yO1xuICAgICAgICByZXR1cm4gZnJvbnQuX2VsZW1lbnRzW2N1cnNvcl07XG4gICAgfVxufVxuXG5mdW5jdGlvbiBSZWFkYWJsZVN0cmVhbVJlYWRlckdlbmVyaWNJbml0aWFsaXplKHJlYWRlciwgc3RyZWFtKSB7XG4gICAgcmVhZGVyLl9vd25lclJlYWRhYmxlU3RyZWFtID0gc3RyZWFtO1xuICAgIHN0cmVhbS5fcmVhZGVyID0gcmVhZGVyO1xuICAgIGlmIChzdHJlYW0uX3N0YXRlID09PSAncmVhZGFibGUnKSB7XG4gICAgICAgIGRlZmF1bHRSZWFkZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZShyZWFkZXIpO1xuICAgIH1cbiAgICBlbHNlIGlmIChzdHJlYW0uX3N0YXRlID09PSAnY2xvc2VkJykge1xuICAgICAgICBkZWZhdWx0UmVhZGVyQ2xvc2VkUHJvbWlzZUluaXRpYWxpemVBc1Jlc29sdmVkKHJlYWRlcik7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBkZWZhdWx0UmVhZGVyQ2xvc2VkUHJvbWlzZUluaXRpYWxpemVBc1JlamVjdGVkKHJlYWRlciwgc3RyZWFtLl9zdG9yZWRFcnJvcik7XG4gICAgfVxufVxuLy8gQSBjbGllbnQgb2YgUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyIGFuZCBSZWFkYWJsZVN0cmVhbUJZT0JSZWFkZXIgbWF5IHVzZSB0aGVzZSBmdW5jdGlvbnMgZGlyZWN0bHkgdG8gYnlwYXNzIHN0YXRlXG4vLyBjaGVjay5cbmZ1bmN0aW9uIFJlYWRhYmxlU3RyZWFtUmVhZGVyR2VuZXJpY0NhbmNlbChyZWFkZXIsIHJlYXNvbikge1xuICAgIGNvbnN0IHN0cmVhbSA9IHJlYWRlci5fb3duZXJSZWFkYWJsZVN0cmVhbTtcbiAgICByZXR1cm4gUmVhZGFibGVTdHJlYW1DYW5jZWwoc3RyZWFtLCByZWFzb24pO1xufVxuZnVuY3Rpb24gUmVhZGFibGVTdHJlYW1SZWFkZXJHZW5lcmljUmVsZWFzZShyZWFkZXIpIHtcbiAgICBpZiAocmVhZGVyLl9vd25lclJlYWRhYmxlU3RyZWFtLl9zdGF0ZSA9PT0gJ3JlYWRhYmxlJykge1xuICAgICAgICBkZWZhdWx0UmVhZGVyQ2xvc2VkUHJvbWlzZVJlamVjdChyZWFkZXIsIG5ldyBUeXBlRXJyb3IoYFJlYWRlciB3YXMgcmVsZWFzZWQgYW5kIGNhbiBubyBsb25nZXIgYmUgdXNlZCB0byBtb25pdG9yIHRoZSBzdHJlYW0ncyBjbG9zZWRuZXNzYCkpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgZGVmYXVsdFJlYWRlckNsb3NlZFByb21pc2VSZXNldFRvUmVqZWN0ZWQocmVhZGVyLCBuZXcgVHlwZUVycm9yKGBSZWFkZXIgd2FzIHJlbGVhc2VkIGFuZCBjYW4gbm8gbG9uZ2VyIGJlIHVzZWQgdG8gbW9uaXRvciB0aGUgc3RyZWFtJ3MgY2xvc2VkbmVzc2ApKTtcbiAgICB9XG4gICAgcmVhZGVyLl9vd25lclJlYWRhYmxlU3RyZWFtLl9yZWFkZXIgPSB1bmRlZmluZWQ7XG4gICAgcmVhZGVyLl9vd25lclJlYWRhYmxlU3RyZWFtID0gdW5kZWZpbmVkO1xufVxuLy8gSGVscGVyIGZ1bmN0aW9ucyBmb3IgdGhlIHJlYWRlcnMuXG5mdW5jdGlvbiByZWFkZXJMb2NrRXhjZXB0aW9uKG5hbWUpIHtcbiAgICByZXR1cm4gbmV3IFR5cGVFcnJvcignQ2Fubm90ICcgKyBuYW1lICsgJyBhIHN0cmVhbSB1c2luZyBhIHJlbGVhc2VkIHJlYWRlcicpO1xufVxuLy8gSGVscGVyIGZ1bmN0aW9ucyBmb3IgdGhlIFJlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlci5cbmZ1bmN0aW9uIGRlZmF1bHRSZWFkZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZShyZWFkZXIpIHtcbiAgICByZWFkZXIuX2Nsb3NlZFByb21pc2UgPSBuZXdQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgcmVhZGVyLl9jbG9zZWRQcm9taXNlX3Jlc29sdmUgPSByZXNvbHZlO1xuICAgICAgICByZWFkZXIuX2Nsb3NlZFByb21pc2VfcmVqZWN0ID0gcmVqZWN0O1xuICAgIH0pO1xufVxuZnVuY3Rpb24gZGVmYXVsdFJlYWRlckNsb3NlZFByb21pc2VJbml0aWFsaXplQXNSZWplY3RlZChyZWFkZXIsIHJlYXNvbikge1xuICAgIGRlZmF1bHRSZWFkZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZShyZWFkZXIpO1xuICAgIGRlZmF1bHRSZWFkZXJDbG9zZWRQcm9taXNlUmVqZWN0KHJlYWRlciwgcmVhc29uKTtcbn1cbmZ1bmN0aW9uIGRlZmF1bHRSZWFkZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZUFzUmVzb2x2ZWQocmVhZGVyKSB7XG4gICAgZGVmYXVsdFJlYWRlckNsb3NlZFByb21pc2VJbml0aWFsaXplKHJlYWRlcik7XG4gICAgZGVmYXVsdFJlYWRlckNsb3NlZFByb21pc2VSZXNvbHZlKHJlYWRlcik7XG59XG5mdW5jdGlvbiBkZWZhdWx0UmVhZGVyQ2xvc2VkUHJvbWlzZVJlamVjdChyZWFkZXIsIHJlYXNvbikge1xuICAgIGlmIChyZWFkZXIuX2Nsb3NlZFByb21pc2VfcmVqZWN0ID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBzZXRQcm9taXNlSXNIYW5kbGVkVG9UcnVlKHJlYWRlci5fY2xvc2VkUHJvbWlzZSk7XG4gICAgcmVhZGVyLl9jbG9zZWRQcm9taXNlX3JlamVjdChyZWFzb24pO1xuICAgIHJlYWRlci5fY2xvc2VkUHJvbWlzZV9yZXNvbHZlID0gdW5kZWZpbmVkO1xuICAgIHJlYWRlci5fY2xvc2VkUHJvbWlzZV9yZWplY3QgPSB1bmRlZmluZWQ7XG59XG5mdW5jdGlvbiBkZWZhdWx0UmVhZGVyQ2xvc2VkUHJvbWlzZVJlc2V0VG9SZWplY3RlZChyZWFkZXIsIHJlYXNvbikge1xuICAgIGRlZmF1bHRSZWFkZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZUFzUmVqZWN0ZWQocmVhZGVyLCByZWFzb24pO1xufVxuZnVuY3Rpb24gZGVmYXVsdFJlYWRlckNsb3NlZFByb21pc2VSZXNvbHZlKHJlYWRlcikge1xuICAgIGlmIChyZWFkZXIuX2Nsb3NlZFByb21pc2VfcmVzb2x2ZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgcmVhZGVyLl9jbG9zZWRQcm9taXNlX3Jlc29sdmUodW5kZWZpbmVkKTtcbiAgICByZWFkZXIuX2Nsb3NlZFByb21pc2VfcmVzb2x2ZSA9IHVuZGVmaW5lZDtcbiAgICByZWFkZXIuX2Nsb3NlZFByb21pc2VfcmVqZWN0ID0gdW5kZWZpbmVkO1xufVxuXG5jb25zdCBBYm9ydFN0ZXBzID0gU3ltYm9sUG9seWZpbGwoJ1tbQWJvcnRTdGVwc11dJyk7XG5jb25zdCBFcnJvclN0ZXBzID0gU3ltYm9sUG9seWZpbGwoJ1tbRXJyb3JTdGVwc11dJyk7XG5jb25zdCBDYW5jZWxTdGVwcyA9IFN5bWJvbFBvbHlmaWxsKCdbW0NhbmNlbFN0ZXBzXV0nKTtcbmNvbnN0IFB1bGxTdGVwcyA9IFN5bWJvbFBvbHlmaWxsKCdbW1B1bGxTdGVwc11dJyk7XG5cbi8vLyA8cmVmZXJlbmNlIGxpYj1cImVzMjAxNS5jb3JlXCIgLz5cbi8vIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0phdmFTY3JpcHQvUmVmZXJlbmNlL0dsb2JhbF9PYmplY3RzL051bWJlci9pc0Zpbml0ZSNQb2x5ZmlsbFxuY29uc3QgTnVtYmVySXNGaW5pdGUgPSBOdW1iZXIuaXNGaW5pdGUgfHwgZnVuY3Rpb24gKHgpIHtcbiAgICByZXR1cm4gdHlwZW9mIHggPT09ICdudW1iZXInICYmIGlzRmluaXRlKHgpO1xufTtcblxuLy8vIDxyZWZlcmVuY2UgbGliPVwiZXMyMDE1LmNvcmVcIiAvPlxuLy8gaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9XZWIvSmF2YVNjcmlwdC9SZWZlcmVuY2UvR2xvYmFsX09iamVjdHMvTWF0aC90cnVuYyNQb2x5ZmlsbFxuY29uc3QgTWF0aFRydW5jID0gTWF0aC50cnVuYyB8fCBmdW5jdGlvbiAodikge1xuICAgIHJldHVybiB2IDwgMCA/IE1hdGguY2VpbCh2KSA6IE1hdGguZmxvb3Iodik7XG59O1xuXG4vLyBodHRwczovL2hleWNhbS5naXRodWIuaW8vd2ViaWRsLyNpZGwtZGljdGlvbmFyaWVzXG5mdW5jdGlvbiBpc0RpY3Rpb25hcnkoeCkge1xuICAgIHJldHVybiB0eXBlb2YgeCA9PT0gJ29iamVjdCcgfHwgdHlwZW9mIHggPT09ICdmdW5jdGlvbic7XG59XG5mdW5jdGlvbiBhc3NlcnREaWN0aW9uYXJ5KG9iaiwgY29udGV4dCkge1xuICAgIGlmIChvYmogIT09IHVuZGVmaW5lZCAmJiAhaXNEaWN0aW9uYXJ5KG9iaikpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihgJHtjb250ZXh0fSBpcyBub3QgYW4gb2JqZWN0LmApO1xuICAgIH1cbn1cbi8vIGh0dHBzOi8vaGV5Y2FtLmdpdGh1Yi5pby93ZWJpZGwvI2lkbC1jYWxsYmFjay1mdW5jdGlvbnNcbmZ1bmN0aW9uIGFzc2VydEZ1bmN0aW9uKHgsIGNvbnRleHQpIHtcbiAgICBpZiAodHlwZW9mIHggIT09ICdmdW5jdGlvbicpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihgJHtjb250ZXh0fSBpcyBub3QgYSBmdW5jdGlvbi5gKTtcbiAgICB9XG59XG4vLyBodHRwczovL2hleWNhbS5naXRodWIuaW8vd2ViaWRsLyNpZGwtb2JqZWN0XG5mdW5jdGlvbiBpc09iamVjdCh4KSB7XG4gICAgcmV0dXJuICh0eXBlb2YgeCA9PT0gJ29iamVjdCcgJiYgeCAhPT0gbnVsbCkgfHwgdHlwZW9mIHggPT09ICdmdW5jdGlvbic7XG59XG5mdW5jdGlvbiBhc3NlcnRPYmplY3QoeCwgY29udGV4dCkge1xuICAgIGlmICghaXNPYmplY3QoeCkpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihgJHtjb250ZXh0fSBpcyBub3QgYW4gb2JqZWN0LmApO1xuICAgIH1cbn1cbmZ1bmN0aW9uIGFzc2VydFJlcXVpcmVkQXJndW1lbnQoeCwgcG9zaXRpb24sIGNvbnRleHQpIHtcbiAgICBpZiAoeCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoYFBhcmFtZXRlciAke3Bvc2l0aW9ufSBpcyByZXF1aXJlZCBpbiAnJHtjb250ZXh0fScuYCk7XG4gICAgfVxufVxuZnVuY3Rpb24gYXNzZXJ0UmVxdWlyZWRGaWVsZCh4LCBmaWVsZCwgY29udGV4dCkge1xuICAgIGlmICh4ID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihgJHtmaWVsZH0gaXMgcmVxdWlyZWQgaW4gJyR7Y29udGV4dH0nLmApO1xuICAgIH1cbn1cbi8vIGh0dHBzOi8vaGV5Y2FtLmdpdGh1Yi5pby93ZWJpZGwvI2lkbC11bnJlc3RyaWN0ZWQtZG91YmxlXG5mdW5jdGlvbiBjb252ZXJ0VW5yZXN0cmljdGVkRG91YmxlKHZhbHVlKSB7XG4gICAgcmV0dXJuIE51bWJlcih2YWx1ZSk7XG59XG5mdW5jdGlvbiBjZW5zb3JOZWdhdGl2ZVplcm8oeCkge1xuICAgIHJldHVybiB4ID09PSAwID8gMCA6IHg7XG59XG5mdW5jdGlvbiBpbnRlZ2VyUGFydCh4KSB7XG4gICAgcmV0dXJuIGNlbnNvck5lZ2F0aXZlWmVybyhNYXRoVHJ1bmMoeCkpO1xufVxuLy8gaHR0cHM6Ly9oZXljYW0uZ2l0aHViLmlvL3dlYmlkbC8jaWRsLXVuc2lnbmVkLWxvbmctbG9uZ1xuZnVuY3Rpb24gY29udmVydFVuc2lnbmVkTG9uZ0xvbmdXaXRoRW5mb3JjZVJhbmdlKHZhbHVlLCBjb250ZXh0KSB7XG4gICAgY29uc3QgbG93ZXJCb3VuZCA9IDA7XG4gICAgY29uc3QgdXBwZXJCb3VuZCA9IE51bWJlci5NQVhfU0FGRV9JTlRFR0VSO1xuICAgIGxldCB4ID0gTnVtYmVyKHZhbHVlKTtcbiAgICB4ID0gY2Vuc29yTmVnYXRpdmVaZXJvKHgpO1xuICAgIGlmICghTnVtYmVySXNGaW5pdGUoeCkpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihgJHtjb250ZXh0fSBpcyBub3QgYSBmaW5pdGUgbnVtYmVyYCk7XG4gICAgfVxuICAgIHggPSBpbnRlZ2VyUGFydCh4KTtcbiAgICBpZiAoeCA8IGxvd2VyQm91bmQgfHwgeCA+IHVwcGVyQm91bmQpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihgJHtjb250ZXh0fSBpcyBvdXRzaWRlIHRoZSBhY2NlcHRlZCByYW5nZSBvZiAke2xvd2VyQm91bmR9IHRvICR7dXBwZXJCb3VuZH0sIGluY2x1c2l2ZWApO1xuICAgIH1cbiAgICBpZiAoIU51bWJlcklzRmluaXRlKHgpIHx8IHggPT09IDApIHtcbiAgICAgICAgcmV0dXJuIDA7XG4gICAgfVxuICAgIC8vIFRPRE8gVXNlIEJpZ0ludCBpZiBzdXBwb3J0ZWQ/XG4gICAgLy8gbGV0IHhCaWdJbnQgPSBCaWdJbnQoaW50ZWdlclBhcnQoeCkpO1xuICAgIC8vIHhCaWdJbnQgPSBCaWdJbnQuYXNVaW50Tig2NCwgeEJpZ0ludCk7XG4gICAgLy8gcmV0dXJuIE51bWJlcih4QmlnSW50KTtcbiAgICByZXR1cm4geDtcbn1cblxuZnVuY3Rpb24gYXNzZXJ0UmVhZGFibGVTdHJlYW0oeCwgY29udGV4dCkge1xuICAgIGlmICghSXNSZWFkYWJsZVN0cmVhbSh4KSkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKGAke2NvbnRleHR9IGlzIG5vdCBhIFJlYWRhYmxlU3RyZWFtLmApO1xuICAgIH1cbn1cblxuLy8gQWJzdHJhY3Qgb3BlcmF0aW9ucyBmb3IgdGhlIFJlYWRhYmxlU3RyZWFtLlxuZnVuY3Rpb24gQWNxdWlyZVJlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlcihzdHJlYW0pIHtcbiAgICByZXR1cm4gbmV3IFJlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlcihzdHJlYW0pO1xufVxuLy8gUmVhZGFibGVTdHJlYW0gQVBJIGV4cG9zZWQgZm9yIGNvbnRyb2xsZXJzLlxuZnVuY3Rpb24gUmVhZGFibGVTdHJlYW1BZGRSZWFkUmVxdWVzdChzdHJlYW0sIHJlYWRSZXF1ZXN0KSB7XG4gICAgc3RyZWFtLl9yZWFkZXIuX3JlYWRSZXF1ZXN0cy5wdXNoKHJlYWRSZXF1ZXN0KTtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlU3RyZWFtRnVsZmlsbFJlYWRSZXF1ZXN0KHN0cmVhbSwgY2h1bmssIGRvbmUpIHtcbiAgICBjb25zdCByZWFkZXIgPSBzdHJlYW0uX3JlYWRlcjtcbiAgICBjb25zdCByZWFkUmVxdWVzdCA9IHJlYWRlci5fcmVhZFJlcXVlc3RzLnNoaWZ0KCk7XG4gICAgaWYgKGRvbmUpIHtcbiAgICAgICAgcmVhZFJlcXVlc3QuX2Nsb3NlU3RlcHMoKTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIHJlYWRSZXF1ZXN0Ll9jaHVua1N0ZXBzKGNodW5rKTtcbiAgICB9XG59XG5mdW5jdGlvbiBSZWFkYWJsZVN0cmVhbUdldE51bVJlYWRSZXF1ZXN0cyhzdHJlYW0pIHtcbiAgICByZXR1cm4gc3RyZWFtLl9yZWFkZXIuX3JlYWRSZXF1ZXN0cy5sZW5ndGg7XG59XG5mdW5jdGlvbiBSZWFkYWJsZVN0cmVhbUhhc0RlZmF1bHRSZWFkZXIoc3RyZWFtKSB7XG4gICAgY29uc3QgcmVhZGVyID0gc3RyZWFtLl9yZWFkZXI7XG4gICAgaWYgKHJlYWRlciA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgaWYgKCFJc1JlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlcihyZWFkZXIpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG59XG4vKipcbiAqIEEgZGVmYXVsdCByZWFkZXIgdmVuZGVkIGJ5IGEge0BsaW5rIFJlYWRhYmxlU3RyZWFtfS5cbiAqXG4gKiBAcHVibGljXG4gKi9cbmNsYXNzIFJlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlciB7XG4gICAgY29uc3RydWN0b3Ioc3RyZWFtKSB7XG4gICAgICAgIGFzc2VydFJlcXVpcmVkQXJndW1lbnQoc3RyZWFtLCAxLCAnUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyJyk7XG4gICAgICAgIGFzc2VydFJlYWRhYmxlU3RyZWFtKHN0cmVhbSwgJ0ZpcnN0IHBhcmFtZXRlcicpO1xuICAgICAgICBpZiAoSXNSZWFkYWJsZVN0cmVhbUxvY2tlZChzdHJlYW0pKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdUaGlzIHN0cmVhbSBoYXMgYWxyZWFkeSBiZWVuIGxvY2tlZCBmb3IgZXhjbHVzaXZlIHJlYWRpbmcgYnkgYW5vdGhlciByZWFkZXInKTtcbiAgICAgICAgfVxuICAgICAgICBSZWFkYWJsZVN0cmVhbVJlYWRlckdlbmVyaWNJbml0aWFsaXplKHRoaXMsIHN0cmVhbSk7XG4gICAgICAgIHRoaXMuX3JlYWRSZXF1ZXN0cyA9IG5ldyBTaW1wbGVRdWV1ZSgpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIGEgcHJvbWlzZSB0aGF0IHdpbGwgYmUgZnVsZmlsbGVkIHdoZW4gdGhlIHN0cmVhbSBiZWNvbWVzIGNsb3NlZCxcbiAgICAgKiBvciByZWplY3RlZCBpZiB0aGUgc3RyZWFtIGV2ZXIgZXJyb3JzIG9yIHRoZSByZWFkZXIncyBsb2NrIGlzIHJlbGVhc2VkIGJlZm9yZSB0aGUgc3RyZWFtIGZpbmlzaGVzIGNsb3NpbmcuXG4gICAgICovXG4gICAgZ2V0IGNsb3NlZCgpIHtcbiAgICAgICAgaWYgKCFJc1JlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlcih0aGlzKSkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoZGVmYXVsdFJlYWRlckJyYW5kQ2hlY2tFeGNlcHRpb24oJ2Nsb3NlZCcpKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5fY2xvc2VkUHJvbWlzZTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogSWYgdGhlIHJlYWRlciBpcyBhY3RpdmUsIGJlaGF2ZXMgdGhlIHNhbWUgYXMge0BsaW5rIFJlYWRhYmxlU3RyZWFtLmNhbmNlbCB8IHN0cmVhbS5jYW5jZWwocmVhc29uKX0uXG4gICAgICovXG4gICAgY2FuY2VsKHJlYXNvbiA9IHVuZGVmaW5lZCkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyKHRoaXMpKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChkZWZhdWx0UmVhZGVyQnJhbmRDaGVja0V4Y2VwdGlvbignY2FuY2VsJykpO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0aGlzLl9vd25lclJlYWRhYmxlU3RyZWFtID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKHJlYWRlckxvY2tFeGNlcHRpb24oJ2NhbmNlbCcpKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gUmVhZGFibGVTdHJlYW1SZWFkZXJHZW5lcmljQ2FuY2VsKHRoaXMsIHJlYXNvbik7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBwcm9taXNlIHRoYXQgYWxsb3dzIGFjY2VzcyB0byB0aGUgbmV4dCBjaHVuayBmcm9tIHRoZSBzdHJlYW0ncyBpbnRlcm5hbCBxdWV1ZSwgaWYgYXZhaWxhYmxlLlxuICAgICAqXG4gICAgICogSWYgcmVhZGluZyBhIGNodW5rIGNhdXNlcyB0aGUgcXVldWUgdG8gYmVjb21lIGVtcHR5LCBtb3JlIGRhdGEgd2lsbCBiZSBwdWxsZWQgZnJvbSB0aGUgdW5kZXJseWluZyBzb3VyY2UuXG4gICAgICovXG4gICAgcmVhZCgpIHtcbiAgICAgICAgaWYgKCFJc1JlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlcih0aGlzKSkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoZGVmYXVsdFJlYWRlckJyYW5kQ2hlY2tFeGNlcHRpb24oJ3JlYWQnKSk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuX293bmVyUmVhZGFibGVTdHJlYW0gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgocmVhZGVyTG9ja0V4Y2VwdGlvbigncmVhZCBmcm9tJykpO1xuICAgICAgICB9XG4gICAgICAgIGxldCByZXNvbHZlUHJvbWlzZTtcbiAgICAgICAgbGV0IHJlamVjdFByb21pc2U7XG4gICAgICAgIGNvbnN0IHByb21pc2UgPSBuZXdQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgIHJlc29sdmVQcm9taXNlID0gcmVzb2x2ZTtcbiAgICAgICAgICAgIHJlamVjdFByb21pc2UgPSByZWplY3Q7XG4gICAgICAgIH0pO1xuICAgICAgICBjb25zdCByZWFkUmVxdWVzdCA9IHtcbiAgICAgICAgICAgIF9jaHVua1N0ZXBzOiBjaHVuayA9PiByZXNvbHZlUHJvbWlzZSh7IHZhbHVlOiBjaHVuaywgZG9uZTogZmFsc2UgfSksXG4gICAgICAgICAgICBfY2xvc2VTdGVwczogKCkgPT4gcmVzb2x2ZVByb21pc2UoeyB2YWx1ZTogdW5kZWZpbmVkLCBkb25lOiB0cnVlIH0pLFxuICAgICAgICAgICAgX2Vycm9yU3RlcHM6IGUgPT4gcmVqZWN0UHJvbWlzZShlKVxuICAgICAgICB9O1xuICAgICAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRSZWFkZXJSZWFkKHRoaXMsIHJlYWRSZXF1ZXN0KTtcbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlbGVhc2VzIHRoZSByZWFkZXIncyBsb2NrIG9uIHRoZSBjb3JyZXNwb25kaW5nIHN0cmVhbS4gQWZ0ZXIgdGhlIGxvY2sgaXMgcmVsZWFzZWQsIHRoZSByZWFkZXIgaXMgbm8gbG9uZ2VyIGFjdGl2ZS5cbiAgICAgKiBJZiB0aGUgYXNzb2NpYXRlZCBzdHJlYW0gaXMgZXJyb3JlZCB3aGVuIHRoZSBsb2NrIGlzIHJlbGVhc2VkLCB0aGUgcmVhZGVyIHdpbGwgYXBwZWFyIGVycm9yZWQgaW4gdGhlIHNhbWUgd2F5XG4gICAgICogZnJvbSBub3cgb247IG90aGVyd2lzZSwgdGhlIHJlYWRlciB3aWxsIGFwcGVhciBjbG9zZWQuXG4gICAgICpcbiAgICAgKiBBIHJlYWRlcidzIGxvY2sgY2Fubm90IGJlIHJlbGVhc2VkIHdoaWxlIGl0IHN0aWxsIGhhcyBhIHBlbmRpbmcgcmVhZCByZXF1ZXN0LCBpLmUuLCBpZiBhIHByb21pc2UgcmV0dXJuZWQgYnlcbiAgICAgKiB0aGUgcmVhZGVyJ3Mge0BsaW5rIFJlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlci5yZWFkIHwgcmVhZCgpfSBtZXRob2QgaGFzIG5vdCB5ZXQgYmVlbiBzZXR0bGVkLiBBdHRlbXB0aW5nIHRvXG4gICAgICogZG8gc28gd2lsbCB0aHJvdyBhIGBUeXBlRXJyb3JgIGFuZCBsZWF2ZSB0aGUgcmVhZGVyIGxvY2tlZCB0byB0aGUgc3RyZWFtLlxuICAgICAqL1xuICAgIHJlbGVhc2VMb2NrKCkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBkZWZhdWx0UmVhZGVyQnJhbmRDaGVja0V4Y2VwdGlvbigncmVsZWFzZUxvY2snKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAodGhpcy5fb3duZXJSZWFkYWJsZVN0cmVhbSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuX3JlYWRSZXF1ZXN0cy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdUcmllZCB0byByZWxlYXNlIGEgcmVhZGVyIGxvY2sgd2hlbiB0aGF0IHJlYWRlciBoYXMgcGVuZGluZyByZWFkKCkgY2FsbHMgdW4tc2V0dGxlZCcpO1xuICAgICAgICB9XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtUmVhZGVyR2VuZXJpY1JlbGVhc2UodGhpcyk7XG4gICAgfVxufVxuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyLnByb3RvdHlwZSwge1xuICAgIGNhbmNlbDogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgcmVhZDogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgcmVsZWFzZUxvY2s6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIGNsb3NlZDogeyBlbnVtZXJhYmxlOiB0cnVlIH1cbn0pO1xuaWYgKHR5cGVvZiBTeW1ib2xQb2x5ZmlsbC50b1N0cmluZ1RhZyA9PT0gJ3N5bWJvbCcpIHtcbiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyLnByb3RvdHlwZSwgU3ltYm9sUG9seWZpbGwudG9TdHJpbmdUYWcsIHtcbiAgICAgICAgdmFsdWU6ICdSZWFkYWJsZVN0cmVhbURlZmF1bHRSZWFkZXInLFxuICAgICAgICBjb25maWd1cmFibGU6IHRydWVcbiAgICB9KTtcbn1cbi8vIEFic3RyYWN0IG9wZXJhdGlvbnMgZm9yIHRoZSByZWFkZXJzLlxuZnVuY3Rpb24gSXNSZWFkYWJsZVN0cmVhbURlZmF1bHRSZWFkZXIoeCkge1xuICAgIGlmICghdHlwZUlzT2JqZWN0KHgpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgaWYgKCFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoeCwgJ19yZWFkUmVxdWVzdHMnKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xufVxuZnVuY3Rpb24gUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyUmVhZChyZWFkZXIsIHJlYWRSZXF1ZXN0KSB7XG4gICAgY29uc3Qgc3RyZWFtID0gcmVhZGVyLl9vd25lclJlYWRhYmxlU3RyZWFtO1xuICAgIHN0cmVhbS5fZGlzdHVyYmVkID0gdHJ1ZTtcbiAgICBpZiAoc3RyZWFtLl9zdGF0ZSA9PT0gJ2Nsb3NlZCcpIHtcbiAgICAgICAgcmVhZFJlcXVlc3QuX2Nsb3NlU3RlcHMoKTtcbiAgICB9XG4gICAgZWxzZSBpZiAoc3RyZWFtLl9zdGF0ZSA9PT0gJ2Vycm9yZWQnKSB7XG4gICAgICAgIHJlYWRSZXF1ZXN0Ll9lcnJvclN0ZXBzKHN0cmVhbS5fc3RvcmVkRXJyb3IpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgc3RyZWFtLl9yZWFkYWJsZVN0cmVhbUNvbnRyb2xsZXJbUHVsbFN0ZXBzXShyZWFkUmVxdWVzdCk7XG4gICAgfVxufVxuLy8gSGVscGVyIGZ1bmN0aW9ucyBmb3IgdGhlIFJlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlci5cbmZ1bmN0aW9uIGRlZmF1bHRSZWFkZXJCcmFuZENoZWNrRXhjZXB0aW9uKG5hbWUpIHtcbiAgICByZXR1cm4gbmV3IFR5cGVFcnJvcihgUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyLnByb3RvdHlwZS4ke25hbWV9IGNhbiBvbmx5IGJlIHVzZWQgb24gYSBSZWFkYWJsZVN0cmVhbURlZmF1bHRSZWFkZXJgKTtcbn1cblxuLy8vIDxyZWZlcmVuY2UgbGliPVwiZXMyMDE4LmFzeW5jaXRlcmFibGVcIiAvPlxuLyogZXNsaW50LWRpc2FibGUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWVtcHR5LWZ1bmN0aW9uICovXG5jb25zdCBBc3luY0l0ZXJhdG9yUHJvdG90eXBlID0gT2JqZWN0LmdldFByb3RvdHlwZU9mKE9iamVjdC5nZXRQcm90b3R5cGVPZihhc3luYyBmdW5jdGlvbiogKCkgeyB9KS5wcm90b3R5cGUpO1xuXG4vLy8gPHJlZmVyZW5jZSBsaWI9XCJlczIwMTguYXN5bmNpdGVyYWJsZVwiIC8+XG5jbGFzcyBSZWFkYWJsZVN0cmVhbUFzeW5jSXRlcmF0b3JJbXBsIHtcbiAgICBjb25zdHJ1Y3RvcihyZWFkZXIsIHByZXZlbnRDYW5jZWwpIHtcbiAgICAgICAgdGhpcy5fb25nb2luZ1Byb21pc2UgPSB1bmRlZmluZWQ7XG4gICAgICAgIHRoaXMuX2lzRmluaXNoZWQgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5fcmVhZGVyID0gcmVhZGVyO1xuICAgICAgICB0aGlzLl9wcmV2ZW50Q2FuY2VsID0gcHJldmVudENhbmNlbDtcbiAgICB9XG4gICAgbmV4dCgpIHtcbiAgICAgICAgY29uc3QgbmV4dFN0ZXBzID0gKCkgPT4gdGhpcy5fbmV4dFN0ZXBzKCk7XG4gICAgICAgIHRoaXMuX29uZ29pbmdQcm9taXNlID0gdGhpcy5fb25nb2luZ1Byb21pc2UgP1xuICAgICAgICAgICAgdHJhbnNmb3JtUHJvbWlzZVdpdGgodGhpcy5fb25nb2luZ1Byb21pc2UsIG5leHRTdGVwcywgbmV4dFN0ZXBzKSA6XG4gICAgICAgICAgICBuZXh0U3RlcHMoKTtcbiAgICAgICAgcmV0dXJuIHRoaXMuX29uZ29pbmdQcm9taXNlO1xuICAgIH1cbiAgICByZXR1cm4odmFsdWUpIHtcbiAgICAgICAgY29uc3QgcmV0dXJuU3RlcHMgPSAoKSA9PiB0aGlzLl9yZXR1cm5TdGVwcyh2YWx1ZSk7XG4gICAgICAgIHJldHVybiB0aGlzLl9vbmdvaW5nUHJvbWlzZSA/XG4gICAgICAgICAgICB0cmFuc2Zvcm1Qcm9taXNlV2l0aCh0aGlzLl9vbmdvaW5nUHJvbWlzZSwgcmV0dXJuU3RlcHMsIHJldHVyblN0ZXBzKSA6XG4gICAgICAgICAgICByZXR1cm5TdGVwcygpO1xuICAgIH1cbiAgICBfbmV4dFN0ZXBzKCkge1xuICAgICAgICBpZiAodGhpcy5faXNGaW5pc2hlZCkge1xuICAgICAgICAgICAgcmV0dXJuIFByb21pc2UucmVzb2x2ZSh7IHZhbHVlOiB1bmRlZmluZWQsIGRvbmU6IHRydWUgfSk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgcmVhZGVyID0gdGhpcy5fcmVhZGVyO1xuICAgICAgICBpZiAocmVhZGVyLl9vd25lclJlYWRhYmxlU3RyZWFtID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKHJlYWRlckxvY2tFeGNlcHRpb24oJ2l0ZXJhdGUnKSk7XG4gICAgICAgIH1cbiAgICAgICAgbGV0IHJlc29sdmVQcm9taXNlO1xuICAgICAgICBsZXQgcmVqZWN0UHJvbWlzZTtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IG5ld1Byb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgcmVzb2x2ZVByb21pc2UgPSByZXNvbHZlO1xuICAgICAgICAgICAgcmVqZWN0UHJvbWlzZSA9IHJlamVjdDtcbiAgICAgICAgfSk7XG4gICAgICAgIGNvbnN0IHJlYWRSZXF1ZXN0ID0ge1xuICAgICAgICAgICAgX2NodW5rU3RlcHM6IGNodW5rID0+IHtcbiAgICAgICAgICAgICAgICB0aGlzLl9vbmdvaW5nUHJvbWlzZSA9IHVuZGVmaW5lZDtcbiAgICAgICAgICAgICAgICAvLyBUaGlzIG5lZWRzIHRvIGJlIGRlbGF5ZWQgYnkgb25lIG1pY3JvdGFzaywgb3RoZXJ3aXNlIHdlIHN0b3AgcHVsbGluZyB0b28gZWFybHkgd2hpY2ggYnJlYWtzIGEgdGVzdC5cbiAgICAgICAgICAgICAgICAvLyBGSVhNRSBJcyB0aGlzIGEgYnVnIGluIHRoZSBzcGVjaWZpY2F0aW9uLCBvciBpbiB0aGUgdGVzdD9cbiAgICAgICAgICAgICAgICBxdWV1ZU1pY3JvdGFzaygoKSA9PiByZXNvbHZlUHJvbWlzZSh7IHZhbHVlOiBjaHVuaywgZG9uZTogZmFsc2UgfSkpO1xuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIF9jbG9zZVN0ZXBzOiAoKSA9PiB7XG4gICAgICAgICAgICAgICAgdGhpcy5fb25nb2luZ1Byb21pc2UgPSB1bmRlZmluZWQ7XG4gICAgICAgICAgICAgICAgdGhpcy5faXNGaW5pc2hlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgUmVhZGFibGVTdHJlYW1SZWFkZXJHZW5lcmljUmVsZWFzZShyZWFkZXIpO1xuICAgICAgICAgICAgICAgIHJlc29sdmVQcm9taXNlKHsgdmFsdWU6IHVuZGVmaW5lZCwgZG9uZTogdHJ1ZSB9KTtcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBfZXJyb3JTdGVwczogcmVhc29uID0+IHtcbiAgICAgICAgICAgICAgICB0aGlzLl9vbmdvaW5nUHJvbWlzZSA9IHVuZGVmaW5lZDtcbiAgICAgICAgICAgICAgICB0aGlzLl9pc0ZpbmlzaGVkID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICBSZWFkYWJsZVN0cmVhbVJlYWRlckdlbmVyaWNSZWxlYXNlKHJlYWRlcik7XG4gICAgICAgICAgICAgICAgcmVqZWN0UHJvbWlzZShyZWFzb24pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRSZWFkZXJSZWFkKHJlYWRlciwgcmVhZFJlcXVlc3QpO1xuICAgICAgICByZXR1cm4gcHJvbWlzZTtcbiAgICB9XG4gICAgX3JldHVyblN0ZXBzKHZhbHVlKSB7XG4gICAgICAgIGlmICh0aGlzLl9pc0ZpbmlzaGVkKSB7XG4gICAgICAgICAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKHsgdmFsdWUsIGRvbmU6IHRydWUgfSk7XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5faXNGaW5pc2hlZCA9IHRydWU7XG4gICAgICAgIGNvbnN0IHJlYWRlciA9IHRoaXMuX3JlYWRlcjtcbiAgICAgICAgaWYgKHJlYWRlci5fb3duZXJSZWFkYWJsZVN0cmVhbSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChyZWFkZXJMb2NrRXhjZXB0aW9uKCdmaW5pc2ggaXRlcmF0aW5nJykpO1xuICAgICAgICB9XG4gICAgICAgIGlmICghdGhpcy5fcHJldmVudENhbmNlbCkge1xuICAgICAgICAgICAgY29uc3QgcmVzdWx0ID0gUmVhZGFibGVTdHJlYW1SZWFkZXJHZW5lcmljQ2FuY2VsKHJlYWRlciwgdmFsdWUpO1xuICAgICAgICAgICAgUmVhZGFibGVTdHJlYW1SZWFkZXJHZW5lcmljUmVsZWFzZShyZWFkZXIpO1xuICAgICAgICAgICAgcmV0dXJuIHRyYW5zZm9ybVByb21pc2VXaXRoKHJlc3VsdCwgKCkgPT4gKHsgdmFsdWUsIGRvbmU6IHRydWUgfSkpO1xuICAgICAgICB9XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtUmVhZGVyR2VuZXJpY1JlbGVhc2UocmVhZGVyKTtcbiAgICAgICAgcmV0dXJuIHByb21pc2VSZXNvbHZlZFdpdGgoeyB2YWx1ZSwgZG9uZTogdHJ1ZSB9KTtcbiAgICB9XG59XG5jb25zdCBSZWFkYWJsZVN0cmVhbUFzeW5jSXRlcmF0b3JQcm90b3R5cGUgPSB7XG4gICAgbmV4dCgpIHtcbiAgICAgICAgaWYgKCFJc1JlYWRhYmxlU3RyZWFtQXN5bmNJdGVyYXRvcih0aGlzKSkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoc3RyZWFtQXN5bmNJdGVyYXRvckJyYW5kQ2hlY2tFeGNlcHRpb24oJ25leHQnKSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuX2FzeW5jSXRlcmF0b3JJbXBsLm5leHQoKTtcbiAgICB9LFxuICAgIHJldHVybih2YWx1ZSkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVTdHJlYW1Bc3luY0l0ZXJhdG9yKHRoaXMpKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChzdHJlYW1Bc3luY0l0ZXJhdG9yQnJhbmRDaGVja0V4Y2VwdGlvbigncmV0dXJuJykpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLl9hc3luY0l0ZXJhdG9ySW1wbC5yZXR1cm4odmFsdWUpO1xuICAgIH1cbn07XG5pZiAoQXN5bmNJdGVyYXRvclByb3RvdHlwZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgT2JqZWN0LnNldFByb3RvdHlwZU9mKFJlYWRhYmxlU3RyZWFtQXN5bmNJdGVyYXRvclByb3RvdHlwZSwgQXN5bmNJdGVyYXRvclByb3RvdHlwZSk7XG59XG4vLyBBYnN0cmFjdCBvcGVyYXRpb25zIGZvciB0aGUgUmVhZGFibGVTdHJlYW0uXG5mdW5jdGlvbiBBY3F1aXJlUmVhZGFibGVTdHJlYW1Bc3luY0l0ZXJhdG9yKHN0cmVhbSwgcHJldmVudENhbmNlbCkge1xuICAgIGNvbnN0IHJlYWRlciA9IEFjcXVpcmVSZWFkYWJsZVN0cmVhbURlZmF1bHRSZWFkZXIoc3RyZWFtKTtcbiAgICBjb25zdCBpbXBsID0gbmV3IFJlYWRhYmxlU3RyZWFtQXN5bmNJdGVyYXRvckltcGwocmVhZGVyLCBwcmV2ZW50Q2FuY2VsKTtcbiAgICBjb25zdCBpdGVyYXRvciA9IE9iamVjdC5jcmVhdGUoUmVhZGFibGVTdHJlYW1Bc3luY0l0ZXJhdG9yUHJvdG90eXBlKTtcbiAgICBpdGVyYXRvci5fYXN5bmNJdGVyYXRvckltcGwgPSBpbXBsO1xuICAgIHJldHVybiBpdGVyYXRvcjtcbn1cbmZ1bmN0aW9uIElzUmVhZGFibGVTdHJlYW1Bc3luY0l0ZXJhdG9yKHgpIHtcbiAgICBpZiAoIXR5cGVJc09iamVjdCh4KSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmICghT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHgsICdfYXN5bmNJdGVyYXRvckltcGwnKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xufVxuLy8gSGVscGVyIGZ1bmN0aW9ucyBmb3IgdGhlIFJlYWRhYmxlU3RyZWFtLlxuZnVuY3Rpb24gc3RyZWFtQXN5bmNJdGVyYXRvckJyYW5kQ2hlY2tFeGNlcHRpb24obmFtZSkge1xuICAgIHJldHVybiBuZXcgVHlwZUVycm9yKGBSZWFkYWJsZVN0cmVhbUFzeW5jSXRlcmF0b3IuJHtuYW1lfSBjYW4gb25seSBiZSB1c2VkIG9uIGEgUmVhZGFibGVTdGVhbUFzeW5jSXRlcmF0b3JgKTtcbn1cblxuLy8vIDxyZWZlcmVuY2UgbGliPVwiZXMyMDE1LmNvcmVcIiAvPlxuLy8gaHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9XZWIvSmF2YVNjcmlwdC9SZWZlcmVuY2UvR2xvYmFsX09iamVjdHMvTnVtYmVyL2lzTmFOI1BvbHlmaWxsXG5jb25zdCBOdW1iZXJJc05hTiA9IE51bWJlci5pc05hTiB8fCBmdW5jdGlvbiAoeCkge1xuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBuby1zZWxmLWNvbXBhcmVcbiAgICByZXR1cm4geCAhPT0geDtcbn07XG5cbmZ1bmN0aW9uIElzRmluaXRlTm9uTmVnYXRpdmVOdW1iZXIodikge1xuICAgIGlmICghSXNOb25OZWdhdGl2ZU51bWJlcih2KSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmICh2ID09PSBJbmZpbml0eSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xufVxuZnVuY3Rpb24gSXNOb25OZWdhdGl2ZU51bWJlcih2KSB7XG4gICAgaWYgKHR5cGVvZiB2ICE9PSAnbnVtYmVyJykge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmIChOdW1iZXJJc05hTih2KSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmICh2IDwgMCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xufVxuXG5mdW5jdGlvbiBEZXF1ZXVlVmFsdWUoY29udGFpbmVyKSB7XG4gICAgY29uc3QgcGFpciA9IGNvbnRhaW5lci5fcXVldWUuc2hpZnQoKTtcbiAgICBjb250YWluZXIuX3F1ZXVlVG90YWxTaXplIC09IHBhaXIuc2l6ZTtcbiAgICBpZiAoY29udGFpbmVyLl9xdWV1ZVRvdGFsU2l6ZSA8IDApIHtcbiAgICAgICAgY29udGFpbmVyLl9xdWV1ZVRvdGFsU2l6ZSA9IDA7XG4gICAgfVxuICAgIHJldHVybiBwYWlyLnZhbHVlO1xufVxuZnVuY3Rpb24gRW5xdWV1ZVZhbHVlV2l0aFNpemUoY29udGFpbmVyLCB2YWx1ZSwgc2l6ZSkge1xuICAgIHNpemUgPSBOdW1iZXIoc2l6ZSk7XG4gICAgaWYgKCFJc0Zpbml0ZU5vbk5lZ2F0aXZlTnVtYmVyKHNpemUpKSB7XG4gICAgICAgIHRocm93IG5ldyBSYW5nZUVycm9yKCdTaXplIG11c3QgYmUgYSBmaW5pdGUsIG5vbi1OYU4sIG5vbi1uZWdhdGl2ZSBudW1iZXIuJyk7XG4gICAgfVxuICAgIGNvbnRhaW5lci5fcXVldWUucHVzaCh7IHZhbHVlLCBzaXplIH0pO1xuICAgIGNvbnRhaW5lci5fcXVldWVUb3RhbFNpemUgKz0gc2l6ZTtcbn1cbmZ1bmN0aW9uIFBlZWtRdWV1ZVZhbHVlKGNvbnRhaW5lcikge1xuICAgIGNvbnN0IHBhaXIgPSBjb250YWluZXIuX3F1ZXVlLnBlZWsoKTtcbiAgICByZXR1cm4gcGFpci52YWx1ZTtcbn1cbmZ1bmN0aW9uIFJlc2V0UXVldWUoY29udGFpbmVyKSB7XG4gICAgY29udGFpbmVyLl9xdWV1ZSA9IG5ldyBTaW1wbGVRdWV1ZSgpO1xuICAgIGNvbnRhaW5lci5fcXVldWVUb3RhbFNpemUgPSAwO1xufVxuXG5mdW5jdGlvbiBDcmVhdGVBcnJheUZyb21MaXN0KGVsZW1lbnRzKSB7XG4gICAgLy8gV2UgdXNlIGFycmF5cyB0byByZXByZXNlbnQgbGlzdHMsIHNvIHRoaXMgaXMgYmFzaWNhbGx5IGEgbm8tb3AuXG4gICAgLy8gRG8gYSBzbGljZSB0aG91Z2gganVzdCBpbiBjYXNlIHdlIGhhcHBlbiB0byBkZXBlbmQgb24gdGhlIHVuaXF1ZS1uZXNzLlxuICAgIHJldHVybiBlbGVtZW50cy5zbGljZSgpO1xufVxuZnVuY3Rpb24gQ29weURhdGFCbG9ja0J5dGVzKGRlc3QsIGRlc3RPZmZzZXQsIHNyYywgc3JjT2Zmc2V0LCBuKSB7XG4gICAgbmV3IFVpbnQ4QXJyYXkoZGVzdCkuc2V0KG5ldyBVaW50OEFycmF5KHNyYywgc3JjT2Zmc2V0LCBuKSwgZGVzdE9mZnNldCk7XG59XG4vLyBOb3QgaW1wbGVtZW50ZWQgY29ycmVjdGx5XG5mdW5jdGlvbiBUcmFuc2ZlckFycmF5QnVmZmVyKE8pIHtcbiAgICByZXR1cm4gTztcbn1cbi8vIE5vdCBpbXBsZW1lbnRlZCBjb3JyZWN0bHlcbmZ1bmN0aW9uIElzRGV0YWNoZWRCdWZmZXIoTykge1xuICAgIHJldHVybiBmYWxzZTtcbn1cblxuLyoqXG4gKiBBIHB1bGwtaW50byByZXF1ZXN0IGluIGEge0BsaW5rIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJ9LlxuICpcbiAqIEBwdWJsaWNcbiAqL1xuY2xhc3MgUmVhZGFibGVTdHJlYW1CWU9CUmVxdWVzdCB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0lsbGVnYWwgY29uc3RydWN0b3InKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgdmlldyBmb3Igd3JpdGluZyBpbiB0bywgb3IgYG51bGxgIGlmIHRoZSBCWU9CIHJlcXVlc3QgaGFzIGFscmVhZHkgYmVlbiByZXNwb25kZWQgdG8uXG4gICAgICovXG4gICAgZ2V0IHZpZXcoKSB7XG4gICAgICAgIGlmICghSXNSZWFkYWJsZVN0cmVhbUJZT0JSZXF1ZXN0KHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBieW9iUmVxdWVzdEJyYW5kQ2hlY2tFeGNlcHRpb24oJ3ZpZXcnKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5fdmlldztcbiAgICB9XG4gICAgcmVzcG9uZChieXRlc1dyaXR0ZW4pIHtcbiAgICAgICAgaWYgKCFJc1JlYWRhYmxlU3RyZWFtQllPQlJlcXVlc3QodGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IGJ5b2JSZXF1ZXN0QnJhbmRDaGVja0V4Y2VwdGlvbigncmVzcG9uZCcpO1xuICAgICAgICB9XG4gICAgICAgIGFzc2VydFJlcXVpcmVkQXJndW1lbnQoYnl0ZXNXcml0dGVuLCAxLCAncmVzcG9uZCcpO1xuICAgICAgICBieXRlc1dyaXR0ZW4gPSBjb252ZXJ0VW5zaWduZWRMb25nTG9uZ1dpdGhFbmZvcmNlUmFuZ2UoYnl0ZXNXcml0dGVuLCAnRmlyc3QgcGFyYW1ldGVyJyk7XG4gICAgICAgIGlmICh0aGlzLl9hc3NvY2lhdGVkUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlciA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdUaGlzIEJZT0IgcmVxdWVzdCBoYXMgYmVlbiBpbnZhbGlkYXRlZCcpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChJc0RldGFjaGVkQnVmZmVyKHRoaXMuX3ZpZXcuYnVmZmVyKSkgO1xuICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyUmVzcG9uZCh0aGlzLl9hc3NvY2lhdGVkUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlciwgYnl0ZXNXcml0dGVuKTtcbiAgICB9XG4gICAgcmVzcG9uZFdpdGhOZXdWaWV3KHZpZXcpIHtcbiAgICAgICAgaWYgKCFJc1JlYWRhYmxlU3RyZWFtQllPQlJlcXVlc3QodGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IGJ5b2JSZXF1ZXN0QnJhbmRDaGVja0V4Y2VwdGlvbigncmVzcG9uZFdpdGhOZXdWaWV3Jyk7XG4gICAgICAgIH1cbiAgICAgICAgYXNzZXJ0UmVxdWlyZWRBcmd1bWVudCh2aWV3LCAxLCAncmVzcG9uZFdpdGhOZXdWaWV3Jyk7XG4gICAgICAgIGlmICghQXJyYXlCdWZmZXIuaXNWaWV3KHZpZXcpKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdZb3UgY2FuIG9ubHkgcmVzcG9uZCB3aXRoIGFycmF5IGJ1ZmZlciB2aWV3cycpO1xuICAgICAgICB9XG4gICAgICAgIGlmICh2aWV3LmJ5dGVMZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ2NodW5rIG11c3QgaGF2ZSBub24temVybyBieXRlTGVuZ3RoJyk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHZpZXcuYnVmZmVyLmJ5dGVMZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoYGNodW5rJ3MgYnVmZmVyIG11c3QgaGF2ZSBub24temVybyBieXRlTGVuZ3RoYCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuX2Fzc29jaWF0ZWRSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ1RoaXMgQllPQiByZXF1ZXN0IGhhcyBiZWVuIGludmFsaWRhdGVkJyk7XG4gICAgICAgIH1cbiAgICAgICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclJlc3BvbmRXaXRoTmV3Vmlldyh0aGlzLl9hc3NvY2lhdGVkUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlciwgdmlldyk7XG4gICAgfVxufVxuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoUmVhZGFibGVTdHJlYW1CWU9CUmVxdWVzdC5wcm90b3R5cGUsIHtcbiAgICByZXNwb25kOiB7IGVudW1lcmFibGU6IHRydWUgfSxcbiAgICByZXNwb25kV2l0aE5ld1ZpZXc6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIHZpZXc6IHsgZW51bWVyYWJsZTogdHJ1ZSB9XG59KTtcbmlmICh0eXBlb2YgU3ltYm9sUG9seWZpbGwudG9TdHJpbmdUYWcgPT09ICdzeW1ib2wnKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KFJlYWRhYmxlU3RyZWFtQllPQlJlcXVlc3QucHJvdG90eXBlLCBTeW1ib2xQb2x5ZmlsbC50b1N0cmluZ1RhZywge1xuICAgICAgICB2YWx1ZTogJ1JlYWRhYmxlU3RyZWFtQllPQlJlcXVlc3QnLFxuICAgICAgICBjb25maWd1cmFibGU6IHRydWVcbiAgICB9KTtcbn1cbi8qKlxuICogQWxsb3dzIGNvbnRyb2wgb2YgYSB7QGxpbmsgUmVhZGFibGVTdHJlYW0gfCByZWFkYWJsZSBieXRlIHN0cmVhbX0ncyBzdGF0ZSBhbmQgaW50ZXJuYWwgcXVldWUuXG4gKlxuICogQHB1YmxpY1xuICovXG5jbGFzcyBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyIHtcbiAgICBjb25zdHJ1Y3RvcigpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignSWxsZWdhbCBjb25zdHJ1Y3RvcicpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBjdXJyZW50IEJZT0IgcHVsbCByZXF1ZXN0LCBvciBgbnVsbGAgaWYgdGhlcmUgaXNuJ3Qgb25lLlxuICAgICAqL1xuICAgIGdldCBieW9iUmVxdWVzdCgpIHtcbiAgICAgICAgaWYgKCFJc1JlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXIodGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IGJ5dGVTdHJlYW1Db250cm9sbGVyQnJhbmRDaGVja0V4Y2VwdGlvbignYnlvYlJlcXVlc3QnKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAodGhpcy5fYnlvYlJlcXVlc3QgPT09IG51bGwgJiYgdGhpcy5fcGVuZGluZ1B1bGxJbnRvcy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBjb25zdCBmaXJzdERlc2NyaXB0b3IgPSB0aGlzLl9wZW5kaW5nUHVsbEludG9zLnBlZWsoKTtcbiAgICAgICAgICAgIGNvbnN0IHZpZXcgPSBuZXcgVWludDhBcnJheShmaXJzdERlc2NyaXB0b3IuYnVmZmVyLCBmaXJzdERlc2NyaXB0b3IuYnl0ZU9mZnNldCArIGZpcnN0RGVzY3JpcHRvci5ieXRlc0ZpbGxlZCwgZmlyc3REZXNjcmlwdG9yLmJ5dGVMZW5ndGggLSBmaXJzdERlc2NyaXB0b3IuYnl0ZXNGaWxsZWQpO1xuICAgICAgICAgICAgY29uc3QgYnlvYlJlcXVlc3QgPSBPYmplY3QuY3JlYXRlKFJlYWRhYmxlU3RyZWFtQllPQlJlcXVlc3QucHJvdG90eXBlKTtcbiAgICAgICAgICAgIFNldFVwUmVhZGFibGVTdHJlYW1CWU9CUmVxdWVzdChieW9iUmVxdWVzdCwgdGhpcywgdmlldyk7XG4gICAgICAgICAgICB0aGlzLl9ieW9iUmVxdWVzdCA9IGJ5b2JSZXF1ZXN0O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLl9ieW9iUmVxdWVzdDtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgZGVzaXJlZCBzaXplIHRvIGZpbGwgdGhlIGNvbnRyb2xsZWQgc3RyZWFtJ3MgaW50ZXJuYWwgcXVldWUuIEl0IGNhbiBiZSBuZWdhdGl2ZSwgaWYgdGhlIHF1ZXVlIGlzXG4gICAgICogb3Zlci1mdWxsLiBBbiB1bmRlcmx5aW5nIGJ5dGUgc291cmNlIG91Z2h0IHRvIHVzZSB0aGlzIGluZm9ybWF0aW9uIHRvIGRldGVybWluZSB3aGVuIGFuZCBob3cgdG8gYXBwbHkgYmFja3ByZXNzdXJlLlxuICAgICAqL1xuICAgIGdldCBkZXNpcmVkU2l6ZSgpIHtcbiAgICAgICAgaWYgKCFJc1JlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXIodGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IGJ5dGVTdHJlYW1Db250cm9sbGVyQnJhbmRDaGVja0V4Y2VwdGlvbignZGVzaXJlZFNpemUnKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckdldERlc2lyZWRTaXplKHRoaXMpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBDbG9zZXMgdGhlIGNvbnRyb2xsZWQgcmVhZGFibGUgc3RyZWFtLiBDb25zdW1lcnMgd2lsbCBzdGlsbCBiZSBhYmxlIHRvIHJlYWQgYW55IHByZXZpb3VzbHktZW5xdWV1ZWQgY2h1bmtzIGZyb21cbiAgICAgKiB0aGUgc3RyZWFtLCBidXQgb25jZSB0aG9zZSBhcmUgcmVhZCwgdGhlIHN0cmVhbSB3aWxsIGJlY29tZSBjbG9zZWQuXG4gICAgICovXG4gICAgY2xvc2UoKSB7XG4gICAgICAgIGlmICghSXNSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBieXRlU3RyZWFtQ29udHJvbGxlckJyYW5kQ2hlY2tFeGNlcHRpb24oJ2Nsb3NlJyk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuX2Nsb3NlUmVxdWVzdGVkKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdUaGUgc3RyZWFtIGhhcyBhbHJlYWR5IGJlZW4gY2xvc2VkOyBkbyBub3QgY2xvc2UgaXQgYWdhaW4hJyk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgc3RhdGUgPSB0aGlzLl9jb250cm9sbGVkUmVhZGFibGVCeXRlU3RyZWFtLl9zdGF0ZTtcbiAgICAgICAgaWYgKHN0YXRlICE9PSAncmVhZGFibGUnKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKGBUaGUgc3RyZWFtIChpbiAke3N0YXRlfSBzdGF0ZSkgaXMgbm90IGluIHRoZSByZWFkYWJsZSBzdGF0ZSBhbmQgY2Fubm90IGJlIGNsb3NlZGApO1xuICAgICAgICB9XG4gICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJDbG9zZSh0aGlzKTtcbiAgICB9XG4gICAgZW5xdWV1ZShjaHVuaykge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlcih0aGlzKSkge1xuICAgICAgICAgICAgdGhyb3cgYnl0ZVN0cmVhbUNvbnRyb2xsZXJCcmFuZENoZWNrRXhjZXB0aW9uKCdlbnF1ZXVlJyk7XG4gICAgICAgIH1cbiAgICAgICAgYXNzZXJ0UmVxdWlyZWRBcmd1bWVudChjaHVuaywgMSwgJ2VucXVldWUnKTtcbiAgICAgICAgaWYgKCFBcnJheUJ1ZmZlci5pc1ZpZXcoY2h1bmspKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdjaHVuayBtdXN0IGJlIGFuIGFycmF5IGJ1ZmZlciB2aWV3Jyk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGNodW5rLmJ5dGVMZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ2NodW5rIG11c3QgaGF2ZSBub24temVybyBieXRlTGVuZ3RoJyk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGNodW5rLmJ1ZmZlci5ieXRlTGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKGBjaHVuaydzIGJ1ZmZlciBtdXN0IGhhdmUgbm9uLXplcm8gYnl0ZUxlbmd0aGApO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0aGlzLl9jbG9zZVJlcXVlc3RlZCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignc3RyZWFtIGlzIGNsb3NlZCBvciBkcmFpbmluZycpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHN0YXRlID0gdGhpcy5fY29udHJvbGxlZFJlYWRhYmxlQnl0ZVN0cmVhbS5fc3RhdGU7XG4gICAgICAgIGlmIChzdGF0ZSAhPT0gJ3JlYWRhYmxlJykge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihgVGhlIHN0cmVhbSAoaW4gJHtzdGF0ZX0gc3RhdGUpIGlzIG5vdCBpbiB0aGUgcmVhZGFibGUgc3RhdGUgYW5kIGNhbm5vdCBiZSBlbnF1ZXVlZCB0b2ApO1xuICAgICAgICB9XG4gICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJFbnF1ZXVlKHRoaXMsIGNodW5rKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRXJyb3JzIHRoZSBjb250cm9sbGVkIHJlYWRhYmxlIHN0cmVhbSwgbWFraW5nIGFsbCBmdXR1cmUgaW50ZXJhY3Rpb25zIHdpdGggaXQgZmFpbCB3aXRoIHRoZSBnaXZlbiBlcnJvciBgZWAuXG4gICAgICovXG4gICAgZXJyb3IoZSA9IHVuZGVmaW5lZCkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlcih0aGlzKSkge1xuICAgICAgICAgICAgdGhyb3cgYnl0ZVN0cmVhbUNvbnRyb2xsZXJCcmFuZENoZWNrRXhjZXB0aW9uKCdlcnJvcicpO1xuICAgICAgICB9XG4gICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJFcnJvcih0aGlzLCBlKTtcbiAgICB9XG4gICAgLyoqIEBpbnRlcm5hbCAqL1xuICAgIFtDYW5jZWxTdGVwc10ocmVhc29uKSB7XG4gICAgICAgIGlmICh0aGlzLl9wZW5kaW5nUHVsbEludG9zLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIGNvbnN0IGZpcnN0RGVzY3JpcHRvciA9IHRoaXMuX3BlbmRpbmdQdWxsSW50b3MucGVlaygpO1xuICAgICAgICAgICAgZmlyc3REZXNjcmlwdG9yLmJ5dGVzRmlsbGVkID0gMDtcbiAgICAgICAgfVxuICAgICAgICBSZXNldFF1ZXVlKHRoaXMpO1xuICAgICAgICBjb25zdCByZXN1bHQgPSB0aGlzLl9jYW5jZWxBbGdvcml0aG0ocmVhc29uKTtcbiAgICAgICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNsZWFyQWxnb3JpdGhtcyh0aGlzKTtcbiAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9XG4gICAgLyoqIEBpbnRlcm5hbCAqL1xuICAgIFtQdWxsU3RlcHNdKHJlYWRSZXF1ZXN0KSB7XG4gICAgICAgIGNvbnN0IHN0cmVhbSA9IHRoaXMuX2NvbnRyb2xsZWRSZWFkYWJsZUJ5dGVTdHJlYW07XG4gICAgICAgIGlmICh0aGlzLl9xdWV1ZVRvdGFsU2l6ZSA+IDApIHtcbiAgICAgICAgICAgIGNvbnN0IGVudHJ5ID0gdGhpcy5fcXVldWUuc2hpZnQoKTtcbiAgICAgICAgICAgIHRoaXMuX3F1ZXVlVG90YWxTaXplIC09IGVudHJ5LmJ5dGVMZW5ndGg7XG4gICAgICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVySGFuZGxlUXVldWVEcmFpbih0aGlzKTtcbiAgICAgICAgICAgIGNvbnN0IHZpZXcgPSBuZXcgVWludDhBcnJheShlbnRyeS5idWZmZXIsIGVudHJ5LmJ5dGVPZmZzZXQsIGVudHJ5LmJ5dGVMZW5ndGgpO1xuICAgICAgICAgICAgcmVhZFJlcXVlc3QuX2NodW5rU3RlcHModmlldyk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgYXV0b0FsbG9jYXRlQ2h1bmtTaXplID0gdGhpcy5fYXV0b0FsbG9jYXRlQ2h1bmtTaXplO1xuICAgICAgICBpZiAoYXV0b0FsbG9jYXRlQ2h1bmtTaXplICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIGxldCBidWZmZXI7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgIGJ1ZmZlciA9IG5ldyBBcnJheUJ1ZmZlcihhdXRvQWxsb2NhdGVDaHVua1NpemUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY2F0Y2ggKGJ1ZmZlckUpIHtcbiAgICAgICAgICAgICAgICByZWFkUmVxdWVzdC5fZXJyb3JTdGVwcyhidWZmZXJFKTtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBjb25zdCBwdWxsSW50b0Rlc2NyaXB0b3IgPSB7XG4gICAgICAgICAgICAgICAgYnVmZmVyLFxuICAgICAgICAgICAgICAgIGJ5dGVPZmZzZXQ6IDAsXG4gICAgICAgICAgICAgICAgYnl0ZUxlbmd0aDogYXV0b0FsbG9jYXRlQ2h1bmtTaXplLFxuICAgICAgICAgICAgICAgIGJ5dGVzRmlsbGVkOiAwLFxuICAgICAgICAgICAgICAgIGVsZW1lbnRTaXplOiAxLFxuICAgICAgICAgICAgICAgIHZpZXdDb25zdHJ1Y3RvcjogVWludDhBcnJheSxcbiAgICAgICAgICAgICAgICByZWFkZXJUeXBlOiAnZGVmYXVsdCdcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICB0aGlzLl9wZW5kaW5nUHVsbEludG9zLnB1c2gocHVsbEludG9EZXNjcmlwdG9yKTtcbiAgICAgICAgfVxuICAgICAgICBSZWFkYWJsZVN0cmVhbUFkZFJlYWRSZXF1ZXN0KHN0cmVhbSwgcmVhZFJlcXVlc3QpO1xuICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyQ2FsbFB1bGxJZk5lZWRlZCh0aGlzKTtcbiAgICB9XG59XG5PYmplY3QuZGVmaW5lUHJvcGVydGllcyhSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyLnByb3RvdHlwZSwge1xuICAgIGNsb3NlOiB7IGVudW1lcmFibGU6IHRydWUgfSxcbiAgICBlbnF1ZXVlOiB7IGVudW1lcmFibGU6IHRydWUgfSxcbiAgICBlcnJvcjogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgYnlvYlJlcXVlc3Q6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIGRlc2lyZWRTaXplOiB7IGVudW1lcmFibGU6IHRydWUgfVxufSk7XG5pZiAodHlwZW9mIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnID09PSAnc3ltYm9sJykge1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyLnByb3RvdHlwZSwgU3ltYm9sUG9seWZpbGwudG9TdHJpbmdUYWcsIHtcbiAgICAgICAgdmFsdWU6ICdSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyJyxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlXG4gICAgfSk7XG59XG4vLyBBYnN0cmFjdCBvcGVyYXRpb25zIGZvciB0aGUgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlci5cbmZ1bmN0aW9uIElzUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlcih4KSB7XG4gICAgaWYgKCF0eXBlSXNPYmplY3QoeCkpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBpZiAoIU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbCh4LCAnX2NvbnRyb2xsZWRSZWFkYWJsZUJ5dGVTdHJlYW0nKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xufVxuZnVuY3Rpb24gSXNSZWFkYWJsZVN0cmVhbUJZT0JSZXF1ZXN0KHgpIHtcbiAgICBpZiAoIXR5cGVJc09iamVjdCh4KSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmICghT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHgsICdfYXNzb2NpYXRlZFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXInKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xufVxuZnVuY3Rpb24gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNhbGxQdWxsSWZOZWVkZWQoY29udHJvbGxlcikge1xuICAgIGNvbnN0IHNob3VsZFB1bGwgPSBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyU2hvdWxkQ2FsbFB1bGwoY29udHJvbGxlcik7XG4gICAgaWYgKCFzaG91bGRQdWxsKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGNvbnRyb2xsZXIuX3B1bGxpbmcpIHtcbiAgICAgICAgY29udHJvbGxlci5fcHVsbEFnYWluID0gdHJ1ZTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb250cm9sbGVyLl9wdWxsaW5nID0gdHJ1ZTtcbiAgICAvLyBUT0RPOiBUZXN0IGNvbnRyb2xsZXIgYXJndW1lbnRcbiAgICBjb25zdCBwdWxsUHJvbWlzZSA9IGNvbnRyb2xsZXIuX3B1bGxBbGdvcml0aG0oKTtcbiAgICB1cG9uUHJvbWlzZShwdWxsUHJvbWlzZSwgKCkgPT4ge1xuICAgICAgICBjb250cm9sbGVyLl9wdWxsaW5nID0gZmFsc2U7XG4gICAgICAgIGlmIChjb250cm9sbGVyLl9wdWxsQWdhaW4pIHtcbiAgICAgICAgICAgIGNvbnRyb2xsZXIuX3B1bGxBZ2FpbiA9IGZhbHNlO1xuICAgICAgICAgICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNhbGxQdWxsSWZOZWVkZWQoY29udHJvbGxlcik7XG4gICAgICAgIH1cbiAgICB9LCBlID0+IHtcbiAgICAgICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckVycm9yKGNvbnRyb2xsZXIsIGUpO1xuICAgIH0pO1xufVxuZnVuY3Rpb24gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNsZWFyUGVuZGluZ1B1bGxJbnRvcyhjb250cm9sbGVyKSB7XG4gICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckludmFsaWRhdGVCWU9CUmVxdWVzdChjb250cm9sbGVyKTtcbiAgICBjb250cm9sbGVyLl9wZW5kaW5nUHVsbEludG9zID0gbmV3IFNpbXBsZVF1ZXVlKCk7XG59XG5mdW5jdGlvbiBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyQ29tbWl0UHVsbEludG9EZXNjcmlwdG9yKHN0cmVhbSwgcHVsbEludG9EZXNjcmlwdG9yKSB7XG4gICAgbGV0IGRvbmUgPSBmYWxzZTtcbiAgICBpZiAoc3RyZWFtLl9zdGF0ZSA9PT0gJ2Nsb3NlZCcpIHtcbiAgICAgICAgZG9uZSA9IHRydWU7XG4gICAgfVxuICAgIGNvbnN0IGZpbGxlZFZpZXcgPSBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyQ29udmVydFB1bGxJbnRvRGVzY3JpcHRvcihwdWxsSW50b0Rlc2NyaXB0b3IpO1xuICAgIGlmIChwdWxsSW50b0Rlc2NyaXB0b3IucmVhZGVyVHlwZSA9PT0gJ2RlZmF1bHQnKSB7XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtRnVsZmlsbFJlYWRSZXF1ZXN0KHN0cmVhbSwgZmlsbGVkVmlldywgZG9uZSk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBSZWFkYWJsZVN0cmVhbUZ1bGZpbGxSZWFkSW50b1JlcXVlc3Qoc3RyZWFtLCBmaWxsZWRWaWV3LCBkb25lKTtcbiAgICB9XG59XG5mdW5jdGlvbiBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyQ29udmVydFB1bGxJbnRvRGVzY3JpcHRvcihwdWxsSW50b0Rlc2NyaXB0b3IpIHtcbiAgICBjb25zdCBieXRlc0ZpbGxlZCA9IHB1bGxJbnRvRGVzY3JpcHRvci5ieXRlc0ZpbGxlZDtcbiAgICBjb25zdCBlbGVtZW50U2l6ZSA9IHB1bGxJbnRvRGVzY3JpcHRvci5lbGVtZW50U2l6ZTtcbiAgICByZXR1cm4gbmV3IHB1bGxJbnRvRGVzY3JpcHRvci52aWV3Q29uc3RydWN0b3IocHVsbEludG9EZXNjcmlwdG9yLmJ1ZmZlciwgcHVsbEludG9EZXNjcmlwdG9yLmJ5dGVPZmZzZXQsIGJ5dGVzRmlsbGVkIC8gZWxlbWVudFNpemUpO1xufVxuZnVuY3Rpb24gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckVucXVldWVDaHVua1RvUXVldWUoY29udHJvbGxlciwgYnVmZmVyLCBieXRlT2Zmc2V0LCBieXRlTGVuZ3RoKSB7XG4gICAgY29udHJvbGxlci5fcXVldWUucHVzaCh7IGJ1ZmZlciwgYnl0ZU9mZnNldCwgYnl0ZUxlbmd0aCB9KTtcbiAgICBjb250cm9sbGVyLl9xdWV1ZVRvdGFsU2l6ZSArPSBieXRlTGVuZ3RoO1xufVxuZnVuY3Rpb24gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckZpbGxQdWxsSW50b0Rlc2NyaXB0b3JGcm9tUXVldWUoY29udHJvbGxlciwgcHVsbEludG9EZXNjcmlwdG9yKSB7XG4gICAgY29uc3QgZWxlbWVudFNpemUgPSBwdWxsSW50b0Rlc2NyaXB0b3IuZWxlbWVudFNpemU7XG4gICAgY29uc3QgY3VycmVudEFsaWduZWRCeXRlcyA9IHB1bGxJbnRvRGVzY3JpcHRvci5ieXRlc0ZpbGxlZCAtIHB1bGxJbnRvRGVzY3JpcHRvci5ieXRlc0ZpbGxlZCAlIGVsZW1lbnRTaXplO1xuICAgIGNvbnN0IG1heEJ5dGVzVG9Db3B5ID0gTWF0aC5taW4oY29udHJvbGxlci5fcXVldWVUb3RhbFNpemUsIHB1bGxJbnRvRGVzY3JpcHRvci5ieXRlTGVuZ3RoIC0gcHVsbEludG9EZXNjcmlwdG9yLmJ5dGVzRmlsbGVkKTtcbiAgICBjb25zdCBtYXhCeXRlc0ZpbGxlZCA9IHB1bGxJbnRvRGVzY3JpcHRvci5ieXRlc0ZpbGxlZCArIG1heEJ5dGVzVG9Db3B5O1xuICAgIGNvbnN0IG1heEFsaWduZWRCeXRlcyA9IG1heEJ5dGVzRmlsbGVkIC0gbWF4Qnl0ZXNGaWxsZWQgJSBlbGVtZW50U2l6ZTtcbiAgICBsZXQgdG90YWxCeXRlc1RvQ29weVJlbWFpbmluZyA9IG1heEJ5dGVzVG9Db3B5O1xuICAgIGxldCByZWFkeSA9IGZhbHNlO1xuICAgIGlmIChtYXhBbGlnbmVkQnl0ZXMgPiBjdXJyZW50QWxpZ25lZEJ5dGVzKSB7XG4gICAgICAgIHRvdGFsQnl0ZXNUb0NvcHlSZW1haW5pbmcgPSBtYXhBbGlnbmVkQnl0ZXMgLSBwdWxsSW50b0Rlc2NyaXB0b3IuYnl0ZXNGaWxsZWQ7XG4gICAgICAgIHJlYWR5ID0gdHJ1ZTtcbiAgICB9XG4gICAgY29uc3QgcXVldWUgPSBjb250cm9sbGVyLl9xdWV1ZTtcbiAgICB3aGlsZSAodG90YWxCeXRlc1RvQ29weVJlbWFpbmluZyA+IDApIHtcbiAgICAgICAgY29uc3QgaGVhZE9mUXVldWUgPSBxdWV1ZS5wZWVrKCk7XG4gICAgICAgIGNvbnN0IGJ5dGVzVG9Db3B5ID0gTWF0aC5taW4odG90YWxCeXRlc1RvQ29weVJlbWFpbmluZywgaGVhZE9mUXVldWUuYnl0ZUxlbmd0aCk7XG4gICAgICAgIGNvbnN0IGRlc3RTdGFydCA9IHB1bGxJbnRvRGVzY3JpcHRvci5ieXRlT2Zmc2V0ICsgcHVsbEludG9EZXNjcmlwdG9yLmJ5dGVzRmlsbGVkO1xuICAgICAgICBDb3B5RGF0YUJsb2NrQnl0ZXMocHVsbEludG9EZXNjcmlwdG9yLmJ1ZmZlciwgZGVzdFN0YXJ0LCBoZWFkT2ZRdWV1ZS5idWZmZXIsIGhlYWRPZlF1ZXVlLmJ5dGVPZmZzZXQsIGJ5dGVzVG9Db3B5KTtcbiAgICAgICAgaWYgKGhlYWRPZlF1ZXVlLmJ5dGVMZW5ndGggPT09IGJ5dGVzVG9Db3B5KSB7XG4gICAgICAgICAgICBxdWV1ZS5zaGlmdCgpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgaGVhZE9mUXVldWUuYnl0ZU9mZnNldCArPSBieXRlc1RvQ29weTtcbiAgICAgICAgICAgIGhlYWRPZlF1ZXVlLmJ5dGVMZW5ndGggLT0gYnl0ZXNUb0NvcHk7XG4gICAgICAgIH1cbiAgICAgICAgY29udHJvbGxlci5fcXVldWVUb3RhbFNpemUgLT0gYnl0ZXNUb0NvcHk7XG4gICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJGaWxsSGVhZFB1bGxJbnRvRGVzY3JpcHRvcihjb250cm9sbGVyLCBieXRlc1RvQ29weSwgcHVsbEludG9EZXNjcmlwdG9yKTtcbiAgICAgICAgdG90YWxCeXRlc1RvQ29weVJlbWFpbmluZyAtPSBieXRlc1RvQ29weTtcbiAgICB9XG4gICAgcmV0dXJuIHJlYWR5O1xufVxuZnVuY3Rpb24gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckZpbGxIZWFkUHVsbEludG9EZXNjcmlwdG9yKGNvbnRyb2xsZXIsIHNpemUsIHB1bGxJbnRvRGVzY3JpcHRvcikge1xuICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJJbnZhbGlkYXRlQllPQlJlcXVlc3QoY29udHJvbGxlcik7XG4gICAgcHVsbEludG9EZXNjcmlwdG9yLmJ5dGVzRmlsbGVkICs9IHNpemU7XG59XG5mdW5jdGlvbiBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVySGFuZGxlUXVldWVEcmFpbihjb250cm9sbGVyKSB7XG4gICAgaWYgKGNvbnRyb2xsZXIuX3F1ZXVlVG90YWxTaXplID09PSAwICYmIGNvbnRyb2xsZXIuX2Nsb3NlUmVxdWVzdGVkKSB7XG4gICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJDbGVhckFsZ29yaXRobXMoY29udHJvbGxlcik7XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtQ2xvc2UoY29udHJvbGxlci5fY29udHJvbGxlZFJlYWRhYmxlQnl0ZVN0cmVhbSk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyQ2FsbFB1bGxJZk5lZWRlZChjb250cm9sbGVyKTtcbiAgICB9XG59XG5mdW5jdGlvbiBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVySW52YWxpZGF0ZUJZT0JSZXF1ZXN0KGNvbnRyb2xsZXIpIHtcbiAgICBpZiAoY29udHJvbGxlci5fYnlvYlJlcXVlc3QgPT09IG51bGwpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb250cm9sbGVyLl9ieW9iUmVxdWVzdC5fYXNzb2NpYXRlZFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXIgPSB1bmRlZmluZWQ7XG4gICAgY29udHJvbGxlci5fYnlvYlJlcXVlc3QuX3ZpZXcgPSBudWxsO1xuICAgIGNvbnRyb2xsZXIuX2J5b2JSZXF1ZXN0ID0gbnVsbDtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJQcm9jZXNzUHVsbEludG9EZXNjcmlwdG9yc1VzaW5nUXVldWUoY29udHJvbGxlcikge1xuICAgIHdoaWxlIChjb250cm9sbGVyLl9wZW5kaW5nUHVsbEludG9zLmxlbmd0aCA+IDApIHtcbiAgICAgICAgaWYgKGNvbnRyb2xsZXIuX3F1ZXVlVG90YWxTaXplID09PSAwKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgcHVsbEludG9EZXNjcmlwdG9yID0gY29udHJvbGxlci5fcGVuZGluZ1B1bGxJbnRvcy5wZWVrKCk7XG4gICAgICAgIGlmIChSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyRmlsbFB1bGxJbnRvRGVzY3JpcHRvckZyb21RdWV1ZShjb250cm9sbGVyLCBwdWxsSW50b0Rlc2NyaXB0b3IpKSB7XG4gICAgICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyU2hpZnRQZW5kaW5nUHVsbEludG8oY29udHJvbGxlcik7XG4gICAgICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyQ29tbWl0UHVsbEludG9EZXNjcmlwdG9yKGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRSZWFkYWJsZUJ5dGVTdHJlYW0sIHB1bGxJbnRvRGVzY3JpcHRvcik7XG4gICAgICAgIH1cbiAgICB9XG59XG5mdW5jdGlvbiBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyUHVsbEludG8oY29udHJvbGxlciwgdmlldywgcmVhZEludG9SZXF1ZXN0KSB7XG4gICAgY29uc3Qgc3RyZWFtID0gY29udHJvbGxlci5fY29udHJvbGxlZFJlYWRhYmxlQnl0ZVN0cmVhbTtcbiAgICBsZXQgZWxlbWVudFNpemUgPSAxO1xuICAgIGlmICh2aWV3LmNvbnN0cnVjdG9yICE9PSBEYXRhVmlldykge1xuICAgICAgICBlbGVtZW50U2l6ZSA9IHZpZXcuY29uc3RydWN0b3IuQllURVNfUEVSX0VMRU1FTlQ7XG4gICAgfVxuICAgIGNvbnN0IGN0b3IgPSB2aWV3LmNvbnN0cnVjdG9yO1xuICAgIGNvbnN0IGJ1ZmZlciA9IFRyYW5zZmVyQXJyYXlCdWZmZXIodmlldy5idWZmZXIpO1xuICAgIGNvbnN0IHB1bGxJbnRvRGVzY3JpcHRvciA9IHtcbiAgICAgICAgYnVmZmVyLFxuICAgICAgICBieXRlT2Zmc2V0OiB2aWV3LmJ5dGVPZmZzZXQsXG4gICAgICAgIGJ5dGVMZW5ndGg6IHZpZXcuYnl0ZUxlbmd0aCxcbiAgICAgICAgYnl0ZXNGaWxsZWQ6IDAsXG4gICAgICAgIGVsZW1lbnRTaXplLFxuICAgICAgICB2aWV3Q29uc3RydWN0b3I6IGN0b3IsXG4gICAgICAgIHJlYWRlclR5cGU6ICdieW9iJ1xuICAgIH07XG4gICAgaWYgKGNvbnRyb2xsZXIuX3BlbmRpbmdQdWxsSW50b3MubGVuZ3RoID4gMCkge1xuICAgICAgICBjb250cm9sbGVyLl9wZW5kaW5nUHVsbEludG9zLnB1c2gocHVsbEludG9EZXNjcmlwdG9yKTtcbiAgICAgICAgLy8gTm8gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNhbGxQdWxsSWZOZWVkZWQoKSBjYWxsIHNpbmNlOlxuICAgICAgICAvLyAtIE5vIGNoYW5nZSBoYXBwZW5zIG9uIGRlc2lyZWRTaXplXG4gICAgICAgIC8vIC0gVGhlIHNvdXJjZSBoYXMgYWxyZWFkeSBiZWVuIG5vdGlmaWVkIG9mIHRoYXQgdGhlcmUncyBhdCBsZWFzdCAxIHBlbmRpbmcgcmVhZCh2aWV3KVxuICAgICAgICBSZWFkYWJsZVN0cmVhbUFkZFJlYWRJbnRvUmVxdWVzdChzdHJlYW0sIHJlYWRJbnRvUmVxdWVzdCk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKHN0cmVhbS5fc3RhdGUgPT09ICdjbG9zZWQnKSB7XG4gICAgICAgIGNvbnN0IGVtcHR5VmlldyA9IG5ldyBjdG9yKHB1bGxJbnRvRGVzY3JpcHRvci5idWZmZXIsIHB1bGxJbnRvRGVzY3JpcHRvci5ieXRlT2Zmc2V0LCAwKTtcbiAgICAgICAgcmVhZEludG9SZXF1ZXN0Ll9jbG9zZVN0ZXBzKGVtcHR5Vmlldyk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGNvbnRyb2xsZXIuX3F1ZXVlVG90YWxTaXplID4gMCkge1xuICAgICAgICBpZiAoUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckZpbGxQdWxsSW50b0Rlc2NyaXB0b3JGcm9tUXVldWUoY29udHJvbGxlciwgcHVsbEludG9EZXNjcmlwdG9yKSkge1xuICAgICAgICAgICAgY29uc3QgZmlsbGVkVmlldyA9IFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJDb252ZXJ0UHVsbEludG9EZXNjcmlwdG9yKHB1bGxJbnRvRGVzY3JpcHRvcik7XG4gICAgICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVySGFuZGxlUXVldWVEcmFpbihjb250cm9sbGVyKTtcbiAgICAgICAgICAgIHJlYWRJbnRvUmVxdWVzdC5fY2h1bmtTdGVwcyhmaWxsZWRWaWV3KTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBpZiAoY29udHJvbGxlci5fY2xvc2VSZXF1ZXN0ZWQpIHtcbiAgICAgICAgICAgIGNvbnN0IGUgPSBuZXcgVHlwZUVycm9yKCdJbnN1ZmZpY2llbnQgYnl0ZXMgdG8gZmlsbCBlbGVtZW50cyBpbiB0aGUgZ2l2ZW4gYnVmZmVyJyk7XG4gICAgICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyRXJyb3IoY29udHJvbGxlciwgZSk7XG4gICAgICAgICAgICByZWFkSW50b1JlcXVlc3QuX2Vycm9yU3RlcHMoZSk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICB9XG4gICAgY29udHJvbGxlci5fcGVuZGluZ1B1bGxJbnRvcy5wdXNoKHB1bGxJbnRvRGVzY3JpcHRvcik7XG4gICAgUmVhZGFibGVTdHJlYW1BZGRSZWFkSW50b1JlcXVlc3Qoc3RyZWFtLCByZWFkSW50b1JlcXVlc3QpO1xuICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJDYWxsUHVsbElmTmVlZGVkKGNvbnRyb2xsZXIpO1xufVxuZnVuY3Rpb24gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclJlc3BvbmRJbkNsb3NlZFN0YXRlKGNvbnRyb2xsZXIsIGZpcnN0RGVzY3JpcHRvcikge1xuICAgIGZpcnN0RGVzY3JpcHRvci5idWZmZXIgPSBUcmFuc2ZlckFycmF5QnVmZmVyKGZpcnN0RGVzY3JpcHRvci5idWZmZXIpO1xuICAgIGNvbnN0IHN0cmVhbSA9IGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRSZWFkYWJsZUJ5dGVTdHJlYW07XG4gICAgaWYgKFJlYWRhYmxlU3RyZWFtSGFzQllPQlJlYWRlcihzdHJlYW0pKSB7XG4gICAgICAgIHdoaWxlIChSZWFkYWJsZVN0cmVhbUdldE51bVJlYWRJbnRvUmVxdWVzdHMoc3RyZWFtKSA+IDApIHtcbiAgICAgICAgICAgIGNvbnN0IHB1bGxJbnRvRGVzY3JpcHRvciA9IFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJTaGlmdFBlbmRpbmdQdWxsSW50byhjb250cm9sbGVyKTtcbiAgICAgICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJDb21taXRQdWxsSW50b0Rlc2NyaXB0b3Ioc3RyZWFtLCBwdWxsSW50b0Rlc2NyaXB0b3IpO1xuICAgICAgICB9XG4gICAgfVxufVxuZnVuY3Rpb24gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclJlc3BvbmRJblJlYWRhYmxlU3RhdGUoY29udHJvbGxlciwgYnl0ZXNXcml0dGVuLCBwdWxsSW50b0Rlc2NyaXB0b3IpIHtcbiAgICBpZiAocHVsbEludG9EZXNjcmlwdG9yLmJ5dGVzRmlsbGVkICsgYnl0ZXNXcml0dGVuID4gcHVsbEludG9EZXNjcmlwdG9yLmJ5dGVMZW5ndGgpIHtcbiAgICAgICAgdGhyb3cgbmV3IFJhbmdlRXJyb3IoJ2J5dGVzV3JpdHRlbiBvdXQgb2YgcmFuZ2UnKTtcbiAgICB9XG4gICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckZpbGxIZWFkUHVsbEludG9EZXNjcmlwdG9yKGNvbnRyb2xsZXIsIGJ5dGVzV3JpdHRlbiwgcHVsbEludG9EZXNjcmlwdG9yKTtcbiAgICBpZiAocHVsbEludG9EZXNjcmlwdG9yLmJ5dGVzRmlsbGVkIDwgcHVsbEludG9EZXNjcmlwdG9yLmVsZW1lbnRTaXplKSB7XG4gICAgICAgIC8vIFRPRE86IEZpZ3VyZSBvdXQgd2hldGhlciB3ZSBzaG91bGQgZGV0YWNoIHRoZSBidWZmZXIgb3Igbm90IGhlcmUuXG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclNoaWZ0UGVuZGluZ1B1bGxJbnRvKGNvbnRyb2xsZXIpO1xuICAgIGNvbnN0IHJlbWFpbmRlclNpemUgPSBwdWxsSW50b0Rlc2NyaXB0b3IuYnl0ZXNGaWxsZWQgJSBwdWxsSW50b0Rlc2NyaXB0b3IuZWxlbWVudFNpemU7XG4gICAgaWYgKHJlbWFpbmRlclNpemUgPiAwKSB7XG4gICAgICAgIGNvbnN0IGVuZCA9IHB1bGxJbnRvRGVzY3JpcHRvci5ieXRlT2Zmc2V0ICsgcHVsbEludG9EZXNjcmlwdG9yLmJ5dGVzRmlsbGVkO1xuICAgICAgICBjb25zdCByZW1haW5kZXIgPSBwdWxsSW50b0Rlc2NyaXB0b3IuYnVmZmVyLnNsaWNlKGVuZCAtIHJlbWFpbmRlclNpemUsIGVuZCk7XG4gICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJFbnF1ZXVlQ2h1bmtUb1F1ZXVlKGNvbnRyb2xsZXIsIHJlbWFpbmRlciwgMCwgcmVtYWluZGVyLmJ5dGVMZW5ndGgpO1xuICAgIH1cbiAgICBwdWxsSW50b0Rlc2NyaXB0b3IuYnVmZmVyID0gVHJhbnNmZXJBcnJheUJ1ZmZlcihwdWxsSW50b0Rlc2NyaXB0b3IuYnVmZmVyKTtcbiAgICBwdWxsSW50b0Rlc2NyaXB0b3IuYnl0ZXNGaWxsZWQgLT0gcmVtYWluZGVyU2l6ZTtcbiAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyQ29tbWl0UHVsbEludG9EZXNjcmlwdG9yKGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRSZWFkYWJsZUJ5dGVTdHJlYW0sIHB1bGxJbnRvRGVzY3JpcHRvcik7XG4gICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclByb2Nlc3NQdWxsSW50b0Rlc2NyaXB0b3JzVXNpbmdRdWV1ZShjb250cm9sbGVyKTtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJSZXNwb25kSW50ZXJuYWwoY29udHJvbGxlciwgYnl0ZXNXcml0dGVuKSB7XG4gICAgY29uc3QgZmlyc3REZXNjcmlwdG9yID0gY29udHJvbGxlci5fcGVuZGluZ1B1bGxJbnRvcy5wZWVrKCk7XG4gICAgY29uc3Qgc3RhdGUgPSBjb250cm9sbGVyLl9jb250cm9sbGVkUmVhZGFibGVCeXRlU3RyZWFtLl9zdGF0ZTtcbiAgICBpZiAoc3RhdGUgPT09ICdjbG9zZWQnKSB7XG4gICAgICAgIGlmIChieXRlc1dyaXR0ZW4gIT09IDApIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ2J5dGVzV3JpdHRlbiBtdXN0IGJlIDAgd2hlbiBjYWxsaW5nIHJlc3BvbmQoKSBvbiBhIGNsb3NlZCBzdHJlYW0nKTtcbiAgICAgICAgfVxuICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyUmVzcG9uZEluQ2xvc2VkU3RhdGUoY29udHJvbGxlciwgZmlyc3REZXNjcmlwdG9yKTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJSZXNwb25kSW5SZWFkYWJsZVN0YXRlKGNvbnRyb2xsZXIsIGJ5dGVzV3JpdHRlbiwgZmlyc3REZXNjcmlwdG9yKTtcbiAgICB9XG4gICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNhbGxQdWxsSWZOZWVkZWQoY29udHJvbGxlcik7XG59XG5mdW5jdGlvbiBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyU2hpZnRQZW5kaW5nUHVsbEludG8oY29udHJvbGxlcikge1xuICAgIGNvbnN0IGRlc2NyaXB0b3IgPSBjb250cm9sbGVyLl9wZW5kaW5nUHVsbEludG9zLnNoaWZ0KCk7XG4gICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckludmFsaWRhdGVCWU9CUmVxdWVzdChjb250cm9sbGVyKTtcbiAgICByZXR1cm4gZGVzY3JpcHRvcjtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJTaG91bGRDYWxsUHVsbChjb250cm9sbGVyKSB7XG4gICAgY29uc3Qgc3RyZWFtID0gY29udHJvbGxlci5fY29udHJvbGxlZFJlYWRhYmxlQnl0ZVN0cmVhbTtcbiAgICBpZiAoc3RyZWFtLl9zdGF0ZSAhPT0gJ3JlYWRhYmxlJykge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmIChjb250cm9sbGVyLl9jbG9zZVJlcXVlc3RlZCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmICghY29udHJvbGxlci5fc3RhcnRlZCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmIChSZWFkYWJsZVN0cmVhbUhhc0RlZmF1bHRSZWFkZXIoc3RyZWFtKSAmJiBSZWFkYWJsZVN0cmVhbUdldE51bVJlYWRSZXF1ZXN0cyhzdHJlYW0pID4gMCkge1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgaWYgKFJlYWRhYmxlU3RyZWFtSGFzQllPQlJlYWRlcihzdHJlYW0pICYmIFJlYWRhYmxlU3RyZWFtR2V0TnVtUmVhZEludG9SZXF1ZXN0cyhzdHJlYW0pID4gMCkge1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgY29uc3QgZGVzaXJlZFNpemUgPSBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyR2V0RGVzaXJlZFNpemUoY29udHJvbGxlcik7XG4gICAgaWYgKGRlc2lyZWRTaXplID4gMCkge1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xufVxuZnVuY3Rpb24gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNsZWFyQWxnb3JpdGhtcyhjb250cm9sbGVyKSB7XG4gICAgY29udHJvbGxlci5fcHVsbEFsZ29yaXRobSA9IHVuZGVmaW5lZDtcbiAgICBjb250cm9sbGVyLl9jYW5jZWxBbGdvcml0aG0gPSB1bmRlZmluZWQ7XG59XG4vLyBBIGNsaWVudCBvZiBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyIG1heSB1c2UgdGhlc2UgZnVuY3Rpb25zIGRpcmVjdGx5IHRvIGJ5cGFzcyBzdGF0ZSBjaGVjay5cbmZ1bmN0aW9uIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJDbG9zZShjb250cm9sbGVyKSB7XG4gICAgY29uc3Qgc3RyZWFtID0gY29udHJvbGxlci5fY29udHJvbGxlZFJlYWRhYmxlQnl0ZVN0cmVhbTtcbiAgICBpZiAoY29udHJvbGxlci5fY2xvc2VSZXF1ZXN0ZWQgfHwgc3RyZWFtLl9zdGF0ZSAhPT0gJ3JlYWRhYmxlJykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChjb250cm9sbGVyLl9xdWV1ZVRvdGFsU2l6ZSA+IDApIHtcbiAgICAgICAgY29udHJvbGxlci5fY2xvc2VSZXF1ZXN0ZWQgPSB0cnVlO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChjb250cm9sbGVyLl9wZW5kaW5nUHVsbEludG9zLmxlbmd0aCA+IDApIHtcbiAgICAgICAgY29uc3QgZmlyc3RQZW5kaW5nUHVsbEludG8gPSBjb250cm9sbGVyLl9wZW5kaW5nUHVsbEludG9zLnBlZWsoKTtcbiAgICAgICAgaWYgKGZpcnN0UGVuZGluZ1B1bGxJbnRvLmJ5dGVzRmlsbGVkID4gMCkge1xuICAgICAgICAgICAgY29uc3QgZSA9IG5ldyBUeXBlRXJyb3IoJ0luc3VmZmljaWVudCBieXRlcyB0byBmaWxsIGVsZW1lbnRzIGluIHRoZSBnaXZlbiBidWZmZXInKTtcbiAgICAgICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJFcnJvcihjb250cm9sbGVyLCBlKTtcbiAgICAgICAgICAgIHRocm93IGU7XG4gICAgICAgIH1cbiAgICB9XG4gICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckNsZWFyQWxnb3JpdGhtcyhjb250cm9sbGVyKTtcbiAgICBSZWFkYWJsZVN0cmVhbUNsb3NlKHN0cmVhbSk7XG59XG5mdW5jdGlvbiBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyRW5xdWV1ZShjb250cm9sbGVyLCBjaHVuaykge1xuICAgIGNvbnN0IHN0cmVhbSA9IGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRSZWFkYWJsZUJ5dGVTdHJlYW07XG4gICAgaWYgKGNvbnRyb2xsZXIuX2Nsb3NlUmVxdWVzdGVkIHx8IHN0cmVhbS5fc3RhdGUgIT09ICdyZWFkYWJsZScpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBjb25zdCBidWZmZXIgPSBjaHVuay5idWZmZXI7XG4gICAgY29uc3QgYnl0ZU9mZnNldCA9IGNodW5rLmJ5dGVPZmZzZXQ7XG4gICAgY29uc3QgYnl0ZUxlbmd0aCA9IGNodW5rLmJ5dGVMZW5ndGg7XG4gICAgY29uc3QgdHJhbnNmZXJyZWRCdWZmZXIgPSBUcmFuc2ZlckFycmF5QnVmZmVyKGJ1ZmZlcik7XG4gICAgaWYgKFJlYWRhYmxlU3RyZWFtSGFzRGVmYXVsdFJlYWRlcihzdHJlYW0pKSB7XG4gICAgICAgIGlmIChSZWFkYWJsZVN0cmVhbUdldE51bVJlYWRSZXF1ZXN0cyhzdHJlYW0pID09PSAwKSB7XG4gICAgICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyRW5xdWV1ZUNodW5rVG9RdWV1ZShjb250cm9sbGVyLCB0cmFuc2ZlcnJlZEJ1ZmZlciwgYnl0ZU9mZnNldCwgYnl0ZUxlbmd0aCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBjb25zdCB0cmFuc2ZlcnJlZFZpZXcgPSBuZXcgVWludDhBcnJheSh0cmFuc2ZlcnJlZEJ1ZmZlciwgYnl0ZU9mZnNldCwgYnl0ZUxlbmd0aCk7XG4gICAgICAgICAgICBSZWFkYWJsZVN0cmVhbUZ1bGZpbGxSZWFkUmVxdWVzdChzdHJlYW0sIHRyYW5zZmVycmVkVmlldywgZmFsc2UpO1xuICAgICAgICB9XG4gICAgfVxuICAgIGVsc2UgaWYgKFJlYWRhYmxlU3RyZWFtSGFzQllPQlJlYWRlcihzdHJlYW0pKSB7XG4gICAgICAgIC8vIFRPRE86IElkZWFsbHkgaW4gdGhpcyBicmFuY2ggZGV0YWNoaW5nIHNob3VsZCBoYXBwZW4gb25seSBpZiB0aGUgYnVmZmVyIGlzIG5vdCBjb25zdW1lZCBmdWxseS5cbiAgICAgICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckVucXVldWVDaHVua1RvUXVldWUoY29udHJvbGxlciwgdHJhbnNmZXJyZWRCdWZmZXIsIGJ5dGVPZmZzZXQsIGJ5dGVMZW5ndGgpO1xuICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyUHJvY2Vzc1B1bGxJbnRvRGVzY3JpcHRvcnNVc2luZ1F1ZXVlKGNvbnRyb2xsZXIpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlckVucXVldWVDaHVua1RvUXVldWUoY29udHJvbGxlciwgdHJhbnNmZXJyZWRCdWZmZXIsIGJ5dGVPZmZzZXQsIGJ5dGVMZW5ndGgpO1xuICAgIH1cbiAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyQ2FsbFB1bGxJZk5lZWRlZChjb250cm9sbGVyKTtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJFcnJvcihjb250cm9sbGVyLCBlKSB7XG4gICAgY29uc3Qgc3RyZWFtID0gY29udHJvbGxlci5fY29udHJvbGxlZFJlYWRhYmxlQnl0ZVN0cmVhbTtcbiAgICBpZiAoc3RyZWFtLl9zdGF0ZSAhPT0gJ3JlYWRhYmxlJykge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJDbGVhclBlbmRpbmdQdWxsSW50b3MoY29udHJvbGxlcik7XG4gICAgUmVzZXRRdWV1ZShjb250cm9sbGVyKTtcbiAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyQ2xlYXJBbGdvcml0aG1zKGNvbnRyb2xsZXIpO1xuICAgIFJlYWRhYmxlU3RyZWFtRXJyb3Ioc3RyZWFtLCBlKTtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJHZXREZXNpcmVkU2l6ZShjb250cm9sbGVyKSB7XG4gICAgY29uc3Qgc3RhdGUgPSBjb250cm9sbGVyLl9jb250cm9sbGVkUmVhZGFibGVCeXRlU3RyZWFtLl9zdGF0ZTtcbiAgICBpZiAoc3RhdGUgPT09ICdlcnJvcmVkJykge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgaWYgKHN0YXRlID09PSAnY2xvc2VkJykge1xuICAgICAgICByZXR1cm4gMDtcbiAgICB9XG4gICAgcmV0dXJuIGNvbnRyb2xsZXIuX3N0cmF0ZWd5SFdNIC0gY29udHJvbGxlci5fcXVldWVUb3RhbFNpemU7XG59XG5mdW5jdGlvbiBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyUmVzcG9uZChjb250cm9sbGVyLCBieXRlc1dyaXR0ZW4pIHtcbiAgICBieXRlc1dyaXR0ZW4gPSBOdW1iZXIoYnl0ZXNXcml0dGVuKTtcbiAgICBpZiAoIUlzRmluaXRlTm9uTmVnYXRpdmVOdW1iZXIoYnl0ZXNXcml0dGVuKSkge1xuICAgICAgICB0aHJvdyBuZXcgUmFuZ2VFcnJvcignYnl0ZXNXcml0dGVuIG11c3QgYmUgYSBmaW5pdGUnKTtcbiAgICB9XG4gICAgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclJlc3BvbmRJbnRlcm5hbChjb250cm9sbGVyLCBieXRlc1dyaXR0ZW4pO1xufVxuZnVuY3Rpb24gUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlclJlc3BvbmRXaXRoTmV3Vmlldyhjb250cm9sbGVyLCB2aWV3KSB7XG4gICAgY29uc3QgZmlyc3REZXNjcmlwdG9yID0gY29udHJvbGxlci5fcGVuZGluZ1B1bGxJbnRvcy5wZWVrKCk7XG4gICAgaWYgKGZpcnN0RGVzY3JpcHRvci5ieXRlT2Zmc2V0ICsgZmlyc3REZXNjcmlwdG9yLmJ5dGVzRmlsbGVkICE9PSB2aWV3LmJ5dGVPZmZzZXQpIHtcbiAgICAgICAgdGhyb3cgbmV3IFJhbmdlRXJyb3IoJ1RoZSByZWdpb24gc3BlY2lmaWVkIGJ5IHZpZXcgZG9lcyBub3QgbWF0Y2ggYnlvYlJlcXVlc3QnKTtcbiAgICB9XG4gICAgaWYgKGZpcnN0RGVzY3JpcHRvci5ieXRlTGVuZ3RoICE9PSB2aWV3LmJ5dGVMZW5ndGgpIHtcbiAgICAgICAgdGhyb3cgbmV3IFJhbmdlRXJyb3IoJ1RoZSBidWZmZXIgb2YgdmlldyBoYXMgZGlmZmVyZW50IGNhcGFjaXR5IHRoYW4gYnlvYlJlcXVlc3QnKTtcbiAgICB9XG4gICAgZmlyc3REZXNjcmlwdG9yLmJ1ZmZlciA9IHZpZXcuYnVmZmVyO1xuICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJSZXNwb25kSW50ZXJuYWwoY29udHJvbGxlciwgdmlldy5ieXRlTGVuZ3RoKTtcbn1cbmZ1bmN0aW9uIFNldFVwUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlcihzdHJlYW0sIGNvbnRyb2xsZXIsIHN0YXJ0QWxnb3JpdGhtLCBwdWxsQWxnb3JpdGhtLCBjYW5jZWxBbGdvcml0aG0sIGhpZ2hXYXRlck1hcmssIGF1dG9BbGxvY2F0ZUNodW5rU2l6ZSkge1xuICAgIGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRSZWFkYWJsZUJ5dGVTdHJlYW0gPSBzdHJlYW07XG4gICAgY29udHJvbGxlci5fcHVsbEFnYWluID0gZmFsc2U7XG4gICAgY29udHJvbGxlci5fcHVsbGluZyA9IGZhbHNlO1xuICAgIGNvbnRyb2xsZXIuX2J5b2JSZXF1ZXN0ID0gbnVsbDtcbiAgICAvLyBOZWVkIHRvIHNldCB0aGUgc2xvdHMgc28gdGhhdCB0aGUgYXNzZXJ0IGRvZXNuJ3QgZmlyZS4gSW4gdGhlIHNwZWMgdGhlIHNsb3RzIGFscmVhZHkgZXhpc3QgaW1wbGljaXRseS5cbiAgICBjb250cm9sbGVyLl9xdWV1ZSA9IGNvbnRyb2xsZXIuX3F1ZXVlVG90YWxTaXplID0gdW5kZWZpbmVkO1xuICAgIFJlc2V0UXVldWUoY29udHJvbGxlcik7XG4gICAgY29udHJvbGxlci5fY2xvc2VSZXF1ZXN0ZWQgPSBmYWxzZTtcbiAgICBjb250cm9sbGVyLl9zdGFydGVkID0gZmFsc2U7XG4gICAgY29udHJvbGxlci5fc3RyYXRlZ3lIV00gPSBoaWdoV2F0ZXJNYXJrO1xuICAgIGNvbnRyb2xsZXIuX3B1bGxBbGdvcml0aG0gPSBwdWxsQWxnb3JpdGhtO1xuICAgIGNvbnRyb2xsZXIuX2NhbmNlbEFsZ29yaXRobSA9IGNhbmNlbEFsZ29yaXRobTtcbiAgICBjb250cm9sbGVyLl9hdXRvQWxsb2NhdGVDaHVua1NpemUgPSBhdXRvQWxsb2NhdGVDaHVua1NpemU7XG4gICAgY29udHJvbGxlci5fcGVuZGluZ1B1bGxJbnRvcyA9IG5ldyBTaW1wbGVRdWV1ZSgpO1xuICAgIHN0cmVhbS5fcmVhZGFibGVTdHJlYW1Db250cm9sbGVyID0gY29udHJvbGxlcjtcbiAgICBjb25zdCBzdGFydFJlc3VsdCA9IHN0YXJ0QWxnb3JpdGhtKCk7XG4gICAgdXBvblByb21pc2UocHJvbWlzZVJlc29sdmVkV2l0aChzdGFydFJlc3VsdCksICgpID0+IHtcbiAgICAgICAgY29udHJvbGxlci5fc3RhcnRlZCA9IHRydWU7XG4gICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJDYWxsUHVsbElmTmVlZGVkKGNvbnRyb2xsZXIpO1xuICAgIH0sIHIgPT4ge1xuICAgICAgICBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyRXJyb3IoY29udHJvbGxlciwgcik7XG4gICAgfSk7XG59XG5mdW5jdGlvbiBTZXRVcFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJGcm9tVW5kZXJseWluZ1NvdXJjZShzdHJlYW0sIHVuZGVybHlpbmdCeXRlU291cmNlLCBoaWdoV2F0ZXJNYXJrKSB7XG4gICAgY29uc3QgY29udHJvbGxlciA9IE9iamVjdC5jcmVhdGUoUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlci5wcm90b3R5cGUpO1xuICAgIGxldCBzdGFydEFsZ29yaXRobSA9ICgpID0+IHVuZGVmaW5lZDtcbiAgICBsZXQgcHVsbEFsZ29yaXRobSA9ICgpID0+IHByb21pc2VSZXNvbHZlZFdpdGgodW5kZWZpbmVkKTtcbiAgICBsZXQgY2FuY2VsQWxnb3JpdGhtID0gKCkgPT4gcHJvbWlzZVJlc29sdmVkV2l0aCh1bmRlZmluZWQpO1xuICAgIGlmICh1bmRlcmx5aW5nQnl0ZVNvdXJjZS5zdGFydCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHN0YXJ0QWxnb3JpdGhtID0gKCkgPT4gdW5kZXJseWluZ0J5dGVTb3VyY2Uuc3RhcnQoY29udHJvbGxlcik7XG4gICAgfVxuICAgIGlmICh1bmRlcmx5aW5nQnl0ZVNvdXJjZS5wdWxsICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcHVsbEFsZ29yaXRobSA9ICgpID0+IHVuZGVybHlpbmdCeXRlU291cmNlLnB1bGwoY29udHJvbGxlcik7XG4gICAgfVxuICAgIGlmICh1bmRlcmx5aW5nQnl0ZVNvdXJjZS5jYW5jZWwgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBjYW5jZWxBbGdvcml0aG0gPSByZWFzb24gPT4gdW5kZXJseWluZ0J5dGVTb3VyY2UuY2FuY2VsKHJlYXNvbik7XG4gICAgfVxuICAgIGNvbnN0IGF1dG9BbGxvY2F0ZUNodW5rU2l6ZSA9IHVuZGVybHlpbmdCeXRlU291cmNlLmF1dG9BbGxvY2F0ZUNodW5rU2l6ZTtcbiAgICBTZXRVcFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXIoc3RyZWFtLCBjb250cm9sbGVyLCBzdGFydEFsZ29yaXRobSwgcHVsbEFsZ29yaXRobSwgY2FuY2VsQWxnb3JpdGhtLCBoaWdoV2F0ZXJNYXJrLCBhdXRvQWxsb2NhdGVDaHVua1NpemUpO1xufVxuZnVuY3Rpb24gU2V0VXBSZWFkYWJsZVN0cmVhbUJZT0JSZXF1ZXN0KHJlcXVlc3QsIGNvbnRyb2xsZXIsIHZpZXcpIHtcbiAgICByZXF1ZXN0Ll9hc3NvY2lhdGVkUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlciA9IGNvbnRyb2xsZXI7XG4gICAgcmVxdWVzdC5fdmlldyA9IHZpZXc7XG59XG4vLyBIZWxwZXIgZnVuY3Rpb25zIGZvciB0aGUgUmVhZGFibGVTdHJlYW1CWU9CUmVxdWVzdC5cbmZ1bmN0aW9uIGJ5b2JSZXF1ZXN0QnJhbmRDaGVja0V4Y2VwdGlvbihuYW1lKSB7XG4gICAgcmV0dXJuIG5ldyBUeXBlRXJyb3IoYFJlYWRhYmxlU3RyZWFtQllPQlJlcXVlc3QucHJvdG90eXBlLiR7bmFtZX0gY2FuIG9ubHkgYmUgdXNlZCBvbiBhIFJlYWRhYmxlU3RyZWFtQllPQlJlcXVlc3RgKTtcbn1cbi8vIEhlbHBlciBmdW5jdGlvbnMgZm9yIHRoZSBSZWFkYWJsZUJ5dGVTdHJlYW1Db250cm9sbGVyLlxuZnVuY3Rpb24gYnl0ZVN0cmVhbUNvbnRyb2xsZXJCcmFuZENoZWNrRXhjZXB0aW9uKG5hbWUpIHtcbiAgICByZXR1cm4gbmV3IFR5cGVFcnJvcihgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlci5wcm90b3R5cGUuJHtuYW1lfSBjYW4gb25seSBiZSB1c2VkIG9uIGEgUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlcmApO1xufVxuXG4vLyBBYnN0cmFjdCBvcGVyYXRpb25zIGZvciB0aGUgUmVhZGFibGVTdHJlYW0uXG5mdW5jdGlvbiBBY3F1aXJlUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyKHN0cmVhbSkge1xuICAgIHJldHVybiBuZXcgUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyKHN0cmVhbSk7XG59XG4vLyBSZWFkYWJsZVN0cmVhbSBBUEkgZXhwb3NlZCBmb3IgY29udHJvbGxlcnMuXG5mdW5jdGlvbiBSZWFkYWJsZVN0cmVhbUFkZFJlYWRJbnRvUmVxdWVzdChzdHJlYW0sIHJlYWRJbnRvUmVxdWVzdCkge1xuICAgIHN0cmVhbS5fcmVhZGVyLl9yZWFkSW50b1JlcXVlc3RzLnB1c2gocmVhZEludG9SZXF1ZXN0KTtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlU3RyZWFtRnVsZmlsbFJlYWRJbnRvUmVxdWVzdChzdHJlYW0sIGNodW5rLCBkb25lKSB7XG4gICAgY29uc3QgcmVhZGVyID0gc3RyZWFtLl9yZWFkZXI7XG4gICAgY29uc3QgcmVhZEludG9SZXF1ZXN0ID0gcmVhZGVyLl9yZWFkSW50b1JlcXVlc3RzLnNoaWZ0KCk7XG4gICAgaWYgKGRvbmUpIHtcbiAgICAgICAgcmVhZEludG9SZXF1ZXN0Ll9jbG9zZVN0ZXBzKGNodW5rKTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIHJlYWRJbnRvUmVxdWVzdC5fY2h1bmtTdGVwcyhjaHVuayk7XG4gICAgfVxufVxuZnVuY3Rpb24gUmVhZGFibGVTdHJlYW1HZXROdW1SZWFkSW50b1JlcXVlc3RzKHN0cmVhbSkge1xuICAgIHJldHVybiBzdHJlYW0uX3JlYWRlci5fcmVhZEludG9SZXF1ZXN0cy5sZW5ndGg7XG59XG5mdW5jdGlvbiBSZWFkYWJsZVN0cmVhbUhhc0JZT0JSZWFkZXIoc3RyZWFtKSB7XG4gICAgY29uc3QgcmVhZGVyID0gc3RyZWFtLl9yZWFkZXI7XG4gICAgaWYgKHJlYWRlciA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgaWYgKCFJc1JlYWRhYmxlU3RyZWFtQllPQlJlYWRlcihyZWFkZXIpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG59XG4vKipcbiAqIEEgQllPQiByZWFkZXIgdmVuZGVkIGJ5IGEge0BsaW5rIFJlYWRhYmxlU3RyZWFtfS5cbiAqXG4gKiBAcHVibGljXG4gKi9cbmNsYXNzIFJlYWRhYmxlU3RyZWFtQllPQlJlYWRlciB7XG4gICAgY29uc3RydWN0b3Ioc3RyZWFtKSB7XG4gICAgICAgIGFzc2VydFJlcXVpcmVkQXJndW1lbnQoc3RyZWFtLCAxLCAnUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyJyk7XG4gICAgICAgIGFzc2VydFJlYWRhYmxlU3RyZWFtKHN0cmVhbSwgJ0ZpcnN0IHBhcmFtZXRlcicpO1xuICAgICAgICBpZiAoSXNSZWFkYWJsZVN0cmVhbUxvY2tlZChzdHJlYW0pKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdUaGlzIHN0cmVhbSBoYXMgYWxyZWFkeSBiZWVuIGxvY2tlZCBmb3IgZXhjbHVzaXZlIHJlYWRpbmcgYnkgYW5vdGhlciByZWFkZXInKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoIUlzUmVhZGFibGVCeXRlU3RyZWFtQ29udHJvbGxlcihzdHJlYW0uX3JlYWRhYmxlU3RyZWFtQ29udHJvbGxlcikpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0Nhbm5vdCBjb25zdHJ1Y3QgYSBSZWFkYWJsZVN0cmVhbUJZT0JSZWFkZXIgZm9yIGEgc3RyZWFtIG5vdCBjb25zdHJ1Y3RlZCB3aXRoIGEgYnl0ZSAnICtcbiAgICAgICAgICAgICAgICAnc291cmNlJyk7XG4gICAgICAgIH1cbiAgICAgICAgUmVhZGFibGVTdHJlYW1SZWFkZXJHZW5lcmljSW5pdGlhbGl6ZSh0aGlzLCBzdHJlYW0pO1xuICAgICAgICB0aGlzLl9yZWFkSW50b1JlcXVlc3RzID0gbmV3IFNpbXBsZVF1ZXVlKCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJldHVybnMgYSBwcm9taXNlIHRoYXQgd2lsbCBiZSBmdWxmaWxsZWQgd2hlbiB0aGUgc3RyZWFtIGJlY29tZXMgY2xvc2VkLCBvciByZWplY3RlZCBpZiB0aGUgc3RyZWFtIGV2ZXIgZXJyb3JzIG9yXG4gICAgICogdGhlIHJlYWRlcidzIGxvY2sgaXMgcmVsZWFzZWQgYmVmb3JlIHRoZSBzdHJlYW0gZmluaXNoZXMgY2xvc2luZy5cbiAgICAgKi9cbiAgICBnZXQgY2xvc2VkKCkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyKHRoaXMpKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChieW9iUmVhZGVyQnJhbmRDaGVja0V4Y2VwdGlvbignY2xvc2VkJykpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLl9jbG9zZWRQcm9taXNlO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBJZiB0aGUgcmVhZGVyIGlzIGFjdGl2ZSwgYmVoYXZlcyB0aGUgc2FtZSBhcyB7QGxpbmsgUmVhZGFibGVTdHJlYW0uY2FuY2VsIHwgc3RyZWFtLmNhbmNlbChyZWFzb24pfS5cbiAgICAgKi9cbiAgICBjYW5jZWwocmVhc29uID0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGlmICghSXNSZWFkYWJsZVN0cmVhbUJZT0JSZWFkZXIodGhpcykpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKGJ5b2JSZWFkZXJCcmFuZENoZWNrRXhjZXB0aW9uKCdjYW5jZWwnKSk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuX293bmVyUmVhZGFibGVTdHJlYW0gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgocmVhZGVyTG9ja0V4Y2VwdGlvbignY2FuY2VsJykpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBSZWFkYWJsZVN0cmVhbVJlYWRlckdlbmVyaWNDYW5jZWwodGhpcywgcmVhc29uKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQXR0ZW1wdHMgdG8gcmVhZHMgYnl0ZXMgaW50byB2aWV3LCBhbmQgcmV0dXJucyBhIHByb21pc2UgcmVzb2x2ZWQgd2l0aCB0aGUgcmVzdWx0LlxuICAgICAqXG4gICAgICogSWYgcmVhZGluZyBhIGNodW5rIGNhdXNlcyB0aGUgcXVldWUgdG8gYmVjb21lIGVtcHR5LCBtb3JlIGRhdGEgd2lsbCBiZSBwdWxsZWQgZnJvbSB0aGUgdW5kZXJseWluZyBzb3VyY2UuXG4gICAgICovXG4gICAgcmVhZCh2aWV3KSB7XG4gICAgICAgIGlmICghSXNSZWFkYWJsZVN0cmVhbUJZT0JSZWFkZXIodGhpcykpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKGJ5b2JSZWFkZXJCcmFuZENoZWNrRXhjZXB0aW9uKCdyZWFkJykpO1xuICAgICAgICB9XG4gICAgICAgIGlmICghQXJyYXlCdWZmZXIuaXNWaWV3KHZpZXcpKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChuZXcgVHlwZUVycm9yKCd2aWV3IG11c3QgYmUgYW4gYXJyYXkgYnVmZmVyIHZpZXcnKSk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHZpZXcuYnl0ZUxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgobmV3IFR5cGVFcnJvcigndmlldyBtdXN0IGhhdmUgbm9uLXplcm8gYnl0ZUxlbmd0aCcpKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAodmlldy5idWZmZXIuYnl0ZUxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgobmV3IFR5cGVFcnJvcihgdmlldydzIGJ1ZmZlciBtdXN0IGhhdmUgbm9uLXplcm8gYnl0ZUxlbmd0aGApKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAodGhpcy5fb3duZXJSZWFkYWJsZVN0cmVhbSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChyZWFkZXJMb2NrRXhjZXB0aW9uKCdyZWFkIGZyb20nKSk7XG4gICAgICAgIH1cbiAgICAgICAgbGV0IHJlc29sdmVQcm9taXNlO1xuICAgICAgICBsZXQgcmVqZWN0UHJvbWlzZTtcbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IG5ld1Byb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICAgICAgcmVzb2x2ZVByb21pc2UgPSByZXNvbHZlO1xuICAgICAgICAgICAgcmVqZWN0UHJvbWlzZSA9IHJlamVjdDtcbiAgICAgICAgfSk7XG4gICAgICAgIGNvbnN0IHJlYWRJbnRvUmVxdWVzdCA9IHtcbiAgICAgICAgICAgIF9jaHVua1N0ZXBzOiBjaHVuayA9PiByZXNvbHZlUHJvbWlzZSh7IHZhbHVlOiBjaHVuaywgZG9uZTogZmFsc2UgfSksXG4gICAgICAgICAgICBfY2xvc2VTdGVwczogY2h1bmsgPT4gcmVzb2x2ZVByb21pc2UoeyB2YWx1ZTogY2h1bmssIGRvbmU6IHRydWUgfSksXG4gICAgICAgICAgICBfZXJyb3JTdGVwczogZSA9PiByZWplY3RQcm9taXNlKGUpXG4gICAgICAgIH07XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtQllPQlJlYWRlclJlYWQodGhpcywgdmlldywgcmVhZEludG9SZXF1ZXN0KTtcbiAgICAgICAgcmV0dXJuIHByb21pc2U7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJlbGVhc2VzIHRoZSByZWFkZXIncyBsb2NrIG9uIHRoZSBjb3JyZXNwb25kaW5nIHN0cmVhbS4gQWZ0ZXIgdGhlIGxvY2sgaXMgcmVsZWFzZWQsIHRoZSByZWFkZXIgaXMgbm8gbG9uZ2VyIGFjdGl2ZS5cbiAgICAgKiBJZiB0aGUgYXNzb2NpYXRlZCBzdHJlYW0gaXMgZXJyb3JlZCB3aGVuIHRoZSBsb2NrIGlzIHJlbGVhc2VkLCB0aGUgcmVhZGVyIHdpbGwgYXBwZWFyIGVycm9yZWQgaW4gdGhlIHNhbWUgd2F5XG4gICAgICogZnJvbSBub3cgb247IG90aGVyd2lzZSwgdGhlIHJlYWRlciB3aWxsIGFwcGVhciBjbG9zZWQuXG4gICAgICpcbiAgICAgKiBBIHJlYWRlcidzIGxvY2sgY2Fubm90IGJlIHJlbGVhc2VkIHdoaWxlIGl0IHN0aWxsIGhhcyBhIHBlbmRpbmcgcmVhZCByZXF1ZXN0LCBpLmUuLCBpZiBhIHByb21pc2UgcmV0dXJuZWQgYnlcbiAgICAgKiB0aGUgcmVhZGVyJ3Mge0BsaW5rIFJlYWRhYmxlU3RyZWFtQllPQlJlYWRlci5yZWFkIHwgcmVhZCgpfSBtZXRob2QgaGFzIG5vdCB5ZXQgYmVlbiBzZXR0bGVkLiBBdHRlbXB0aW5nIHRvXG4gICAgICogZG8gc28gd2lsbCB0aHJvdyBhIGBUeXBlRXJyb3JgIGFuZCBsZWF2ZSB0aGUgcmVhZGVyIGxvY2tlZCB0byB0aGUgc3RyZWFtLlxuICAgICAqL1xuICAgIHJlbGVhc2VMb2NrKCkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBieW9iUmVhZGVyQnJhbmRDaGVja0V4Y2VwdGlvbigncmVsZWFzZUxvY2snKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAodGhpcy5fb3duZXJSZWFkYWJsZVN0cmVhbSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuX3JlYWRJbnRvUmVxdWVzdHMubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignVHJpZWQgdG8gcmVsZWFzZSBhIHJlYWRlciBsb2NrIHdoZW4gdGhhdCByZWFkZXIgaGFzIHBlbmRpbmcgcmVhZCgpIGNhbGxzIHVuLXNldHRsZWQnKTtcbiAgICAgICAgfVxuICAgICAgICBSZWFkYWJsZVN0cmVhbVJlYWRlckdlbmVyaWNSZWxlYXNlKHRoaXMpO1xuICAgIH1cbn1cbk9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKFJlYWRhYmxlU3RyZWFtQllPQlJlYWRlci5wcm90b3R5cGUsIHtcbiAgICBjYW5jZWw6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIHJlYWQ6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIHJlbGVhc2VMb2NrOiB7IGVudW1lcmFibGU6IHRydWUgfSxcbiAgICBjbG9zZWQ6IHsgZW51bWVyYWJsZTogdHJ1ZSB9XG59KTtcbmlmICh0eXBlb2YgU3ltYm9sUG9seWZpbGwudG9TdHJpbmdUYWcgPT09ICdzeW1ib2wnKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KFJlYWRhYmxlU3RyZWFtQllPQlJlYWRlci5wcm90b3R5cGUsIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnLCB7XG4gICAgICAgIHZhbHVlOiAnUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyJyxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlXG4gICAgfSk7XG59XG4vLyBBYnN0cmFjdCBvcGVyYXRpb25zIGZvciB0aGUgcmVhZGVycy5cbmZ1bmN0aW9uIElzUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyKHgpIHtcbiAgICBpZiAoIXR5cGVJc09iamVjdCh4KSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmICghT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHgsICdfcmVhZEludG9SZXF1ZXN0cycpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG59XG5mdW5jdGlvbiBSZWFkYWJsZVN0cmVhbUJZT0JSZWFkZXJSZWFkKHJlYWRlciwgdmlldywgcmVhZEludG9SZXF1ZXN0KSB7XG4gICAgY29uc3Qgc3RyZWFtID0gcmVhZGVyLl9vd25lclJlYWRhYmxlU3RyZWFtO1xuICAgIHN0cmVhbS5fZGlzdHVyYmVkID0gdHJ1ZTtcbiAgICBpZiAoc3RyZWFtLl9zdGF0ZSA9PT0gJ2Vycm9yZWQnKSB7XG4gICAgICAgIHJlYWRJbnRvUmVxdWVzdC5fZXJyb3JTdGVwcyhzdHJlYW0uX3N0b3JlZEVycm9yKTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJQdWxsSW50byhzdHJlYW0uX3JlYWRhYmxlU3RyZWFtQ29udHJvbGxlciwgdmlldywgcmVhZEludG9SZXF1ZXN0KTtcbiAgICB9XG59XG4vLyBIZWxwZXIgZnVuY3Rpb25zIGZvciB0aGUgUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyLlxuZnVuY3Rpb24gYnlvYlJlYWRlckJyYW5kQ2hlY2tFeGNlcHRpb24obmFtZSkge1xuICAgIHJldHVybiBuZXcgVHlwZUVycm9yKGBSZWFkYWJsZVN0cmVhbUJZT0JSZWFkZXIucHJvdG90eXBlLiR7bmFtZX0gY2FuIG9ubHkgYmUgdXNlZCBvbiBhIFJlYWRhYmxlU3RyZWFtQllPQlJlYWRlcmApO1xufVxuXG5mdW5jdGlvbiBFeHRyYWN0SGlnaFdhdGVyTWFyayhzdHJhdGVneSwgZGVmYXVsdEhXTSkge1xuICAgIGNvbnN0IHsgaGlnaFdhdGVyTWFyayB9ID0gc3RyYXRlZ3k7XG4gICAgaWYgKGhpZ2hXYXRlck1hcmsgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICByZXR1cm4gZGVmYXVsdEhXTTtcbiAgICB9XG4gICAgaWYgKE51bWJlcklzTmFOKGhpZ2hXYXRlck1hcmspIHx8IGhpZ2hXYXRlck1hcmsgPCAwKSB7XG4gICAgICAgIHRocm93IG5ldyBSYW5nZUVycm9yKCdJbnZhbGlkIGhpZ2hXYXRlck1hcmsnKTtcbiAgICB9XG4gICAgcmV0dXJuIGhpZ2hXYXRlck1hcms7XG59XG5mdW5jdGlvbiBFeHRyYWN0U2l6ZUFsZ29yaXRobShzdHJhdGVneSkge1xuICAgIGNvbnN0IHsgc2l6ZSB9ID0gc3RyYXRlZ3k7XG4gICAgaWYgKCFzaXplKSB7XG4gICAgICAgIHJldHVybiAoKSA9PiAxO1xuICAgIH1cbiAgICByZXR1cm4gc2l6ZTtcbn1cblxuZnVuY3Rpb24gY29udmVydFF1ZXVpbmdTdHJhdGVneShpbml0LCBjb250ZXh0KSB7XG4gICAgYXNzZXJ0RGljdGlvbmFyeShpbml0LCBjb250ZXh0KTtcbiAgICBjb25zdCBoaWdoV2F0ZXJNYXJrID0gaW5pdCA9PT0gbnVsbCB8fCBpbml0ID09PSB2b2lkIDAgPyB2b2lkIDAgOiBpbml0LmhpZ2hXYXRlck1hcms7XG4gICAgY29uc3Qgc2l6ZSA9IGluaXQgPT09IG51bGwgfHwgaW5pdCA9PT0gdm9pZCAwID8gdm9pZCAwIDogaW5pdC5zaXplO1xuICAgIHJldHVybiB7XG4gICAgICAgIGhpZ2hXYXRlck1hcms6IGhpZ2hXYXRlck1hcmsgPT09IHVuZGVmaW5lZCA/IHVuZGVmaW5lZCA6IGNvbnZlcnRVbnJlc3RyaWN0ZWREb3VibGUoaGlnaFdhdGVyTWFyayksXG4gICAgICAgIHNpemU6IHNpemUgPT09IHVuZGVmaW5lZCA/IHVuZGVmaW5lZCA6IGNvbnZlcnRRdWV1aW5nU3RyYXRlZ3lTaXplKHNpemUsIGAke2NvbnRleHR9IGhhcyBtZW1iZXIgJ3NpemUnIHRoYXRgKVxuICAgIH07XG59XG5mdW5jdGlvbiBjb252ZXJ0UXVldWluZ1N0cmF0ZWd5U2l6ZShmbiwgY29udGV4dCkge1xuICAgIGFzc2VydEZ1bmN0aW9uKGZuLCBjb250ZXh0KTtcbiAgICByZXR1cm4gY2h1bmsgPT4gY29udmVydFVucmVzdHJpY3RlZERvdWJsZShmbihjaHVuaykpO1xufVxuXG5mdW5jdGlvbiBjb252ZXJ0VW5kZXJseWluZ1Npbmsob3JpZ2luYWwsIGNvbnRleHQpIHtcbiAgICBhc3NlcnREaWN0aW9uYXJ5KG9yaWdpbmFsLCBjb250ZXh0KTtcbiAgICBjb25zdCBhYm9ydCA9IG9yaWdpbmFsID09PSBudWxsIHx8IG9yaWdpbmFsID09PSB2b2lkIDAgPyB2b2lkIDAgOiBvcmlnaW5hbC5hYm9ydDtcbiAgICBjb25zdCBjbG9zZSA9IG9yaWdpbmFsID09PSBudWxsIHx8IG9yaWdpbmFsID09PSB2b2lkIDAgPyB2b2lkIDAgOiBvcmlnaW5hbC5jbG9zZTtcbiAgICBjb25zdCBzdGFydCA9IG9yaWdpbmFsID09PSBudWxsIHx8IG9yaWdpbmFsID09PSB2b2lkIDAgPyB2b2lkIDAgOiBvcmlnaW5hbC5zdGFydDtcbiAgICBjb25zdCB0eXBlID0gb3JpZ2luYWwgPT09IG51bGwgfHwgb3JpZ2luYWwgPT09IHZvaWQgMCA/IHZvaWQgMCA6IG9yaWdpbmFsLnR5cGU7XG4gICAgY29uc3Qgd3JpdGUgPSBvcmlnaW5hbCA9PT0gbnVsbCB8fCBvcmlnaW5hbCA9PT0gdm9pZCAwID8gdm9pZCAwIDogb3JpZ2luYWwud3JpdGU7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgYWJvcnQ6IGFib3J0ID09PSB1bmRlZmluZWQgP1xuICAgICAgICAgICAgdW5kZWZpbmVkIDpcbiAgICAgICAgICAgIGNvbnZlcnRVbmRlcmx5aW5nU2lua0Fib3J0Q2FsbGJhY2soYWJvcnQsIG9yaWdpbmFsLCBgJHtjb250ZXh0fSBoYXMgbWVtYmVyICdhYm9ydCcgdGhhdGApLFxuICAgICAgICBjbG9zZTogY2xvc2UgPT09IHVuZGVmaW5lZCA/XG4gICAgICAgICAgICB1bmRlZmluZWQgOlxuICAgICAgICAgICAgY29udmVydFVuZGVybHlpbmdTaW5rQ2xvc2VDYWxsYmFjayhjbG9zZSwgb3JpZ2luYWwsIGAke2NvbnRleHR9IGhhcyBtZW1iZXIgJ2Nsb3NlJyB0aGF0YCksXG4gICAgICAgIHN0YXJ0OiBzdGFydCA9PT0gdW5kZWZpbmVkID9cbiAgICAgICAgICAgIHVuZGVmaW5lZCA6XG4gICAgICAgICAgICBjb252ZXJ0VW5kZXJseWluZ1NpbmtTdGFydENhbGxiYWNrKHN0YXJ0LCBvcmlnaW5hbCwgYCR7Y29udGV4dH0gaGFzIG1lbWJlciAnc3RhcnQnIHRoYXRgKSxcbiAgICAgICAgd3JpdGU6IHdyaXRlID09PSB1bmRlZmluZWQgP1xuICAgICAgICAgICAgdW5kZWZpbmVkIDpcbiAgICAgICAgICAgIGNvbnZlcnRVbmRlcmx5aW5nU2lua1dyaXRlQ2FsbGJhY2sod3JpdGUsIG9yaWdpbmFsLCBgJHtjb250ZXh0fSBoYXMgbWVtYmVyICd3cml0ZScgdGhhdGApLFxuICAgICAgICB0eXBlXG4gICAgfTtcbn1cbmZ1bmN0aW9uIGNvbnZlcnRVbmRlcmx5aW5nU2lua0Fib3J0Q2FsbGJhY2soZm4sIG9yaWdpbmFsLCBjb250ZXh0KSB7XG4gICAgYXNzZXJ0RnVuY3Rpb24oZm4sIGNvbnRleHQpO1xuICAgIHJldHVybiAocmVhc29uKSA9PiBwcm9taXNlQ2FsbChmbiwgb3JpZ2luYWwsIFtyZWFzb25dKTtcbn1cbmZ1bmN0aW9uIGNvbnZlcnRVbmRlcmx5aW5nU2lua0Nsb3NlQ2FsbGJhY2soZm4sIG9yaWdpbmFsLCBjb250ZXh0KSB7XG4gICAgYXNzZXJ0RnVuY3Rpb24oZm4sIGNvbnRleHQpO1xuICAgIHJldHVybiAoKSA9PiBwcm9taXNlQ2FsbChmbiwgb3JpZ2luYWwsIFtdKTtcbn1cbmZ1bmN0aW9uIGNvbnZlcnRVbmRlcmx5aW5nU2lua1N0YXJ0Q2FsbGJhY2soZm4sIG9yaWdpbmFsLCBjb250ZXh0KSB7XG4gICAgYXNzZXJ0RnVuY3Rpb24oZm4sIGNvbnRleHQpO1xuICAgIHJldHVybiAoY29udHJvbGxlcikgPT4gcmVmbGVjdENhbGwoZm4sIG9yaWdpbmFsLCBbY29udHJvbGxlcl0pO1xufVxuZnVuY3Rpb24gY29udmVydFVuZGVybHlpbmdTaW5rV3JpdGVDYWxsYmFjayhmbiwgb3JpZ2luYWwsIGNvbnRleHQpIHtcbiAgICBhc3NlcnRGdW5jdGlvbihmbiwgY29udGV4dCk7XG4gICAgcmV0dXJuIChjaHVuaywgY29udHJvbGxlcikgPT4gcHJvbWlzZUNhbGwoZm4sIG9yaWdpbmFsLCBbY2h1bmssIGNvbnRyb2xsZXJdKTtcbn1cblxuZnVuY3Rpb24gYXNzZXJ0V3JpdGFibGVTdHJlYW0oeCwgY29udGV4dCkge1xuICAgIGlmICghSXNXcml0YWJsZVN0cmVhbSh4KSkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKGAke2NvbnRleHR9IGlzIG5vdCBhIFdyaXRhYmxlU3RyZWFtLmApO1xuICAgIH1cbn1cblxuLyoqXG4gKiBBIHdyaXRhYmxlIHN0cmVhbSByZXByZXNlbnRzIGEgZGVzdGluYXRpb24gZm9yIGRhdGEsIGludG8gd2hpY2ggeW91IGNhbiB3cml0ZS5cbiAqXG4gKiBAcHVibGljXG4gKi9cbmNsYXNzIFdyaXRhYmxlU3RyZWFtIHtcbiAgICBjb25zdHJ1Y3RvcihyYXdVbmRlcmx5aW5nU2luayA9IHt9LCByYXdTdHJhdGVneSA9IHt9KSB7XG4gICAgICAgIGlmIChyYXdVbmRlcmx5aW5nU2luayA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICByYXdVbmRlcmx5aW5nU2luayA9IG51bGw7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBhc3NlcnRPYmplY3QocmF3VW5kZXJseWluZ1NpbmssICdGaXJzdCBwYXJhbWV0ZXInKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBzdHJhdGVneSA9IGNvbnZlcnRRdWV1aW5nU3RyYXRlZ3kocmF3U3RyYXRlZ3ksICdTZWNvbmQgcGFyYW1ldGVyJyk7XG4gICAgICAgIGNvbnN0IHVuZGVybHlpbmdTaW5rID0gY29udmVydFVuZGVybHlpbmdTaW5rKHJhd1VuZGVybHlpbmdTaW5rLCAnRmlyc3QgcGFyYW1ldGVyJyk7XG4gICAgICAgIEluaXRpYWxpemVXcml0YWJsZVN0cmVhbSh0aGlzKTtcbiAgICAgICAgY29uc3QgdHlwZSA9IHVuZGVybHlpbmdTaW5rLnR5cGU7XG4gICAgICAgIGlmICh0eXBlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBSYW5nZUVycm9yKCdJbnZhbGlkIHR5cGUgaXMgc3BlY2lmaWVkJyk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgc2l6ZUFsZ29yaXRobSA9IEV4dHJhY3RTaXplQWxnb3JpdGhtKHN0cmF0ZWd5KTtcbiAgICAgICAgY29uc3QgaGlnaFdhdGVyTWFyayA9IEV4dHJhY3RIaWdoV2F0ZXJNYXJrKHN0cmF0ZWd5LCAxKTtcbiAgICAgICAgU2V0VXBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRnJvbVVuZGVybHlpbmdTaW5rKHRoaXMsIHVuZGVybHlpbmdTaW5rLCBoaWdoV2F0ZXJNYXJrLCBzaXplQWxnb3JpdGhtKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmV0dXJucyB3aGV0aGVyIG9yIG5vdCB0aGUgd3JpdGFibGUgc3RyZWFtIGlzIGxvY2tlZCB0byBhIHdyaXRlci5cbiAgICAgKi9cbiAgICBnZXQgbG9ja2VkKCkge1xuICAgICAgICBpZiAoIUlzV3JpdGFibGVTdHJlYW0odGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IHN0cmVhbUJyYW5kQ2hlY2tFeGNlcHRpb24oJ2xvY2tlZCcpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBJc1dyaXRhYmxlU3RyZWFtTG9ja2VkKHRoaXMpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBBYm9ydHMgdGhlIHN0cmVhbSwgc2lnbmFsaW5nIHRoYXQgdGhlIHByb2R1Y2VyIGNhbiBubyBsb25nZXIgc3VjY2Vzc2Z1bGx5IHdyaXRlIHRvIHRoZSBzdHJlYW0gYW5kIGl0IGlzIHRvIGJlXG4gICAgICogaW1tZWRpYXRlbHkgbW92ZWQgdG8gYW4gZXJyb3JlZCBzdGF0ZSwgd2l0aCBhbnkgcXVldWVkLXVwIHdyaXRlcyBkaXNjYXJkZWQuIFRoaXMgd2lsbCBhbHNvIGV4ZWN1dGUgYW55IGFib3J0XG4gICAgICogbWVjaGFuaXNtIG9mIHRoZSB1bmRlcmx5aW5nIHNpbmsuXG4gICAgICpcbiAgICAgKiBUaGUgcmV0dXJuZWQgcHJvbWlzZSB3aWxsIGZ1bGZpbGwgaWYgdGhlIHN0cmVhbSBzaHV0cyBkb3duIHN1Y2Nlc3NmdWxseSwgb3IgcmVqZWN0IGlmIHRoZSB1bmRlcmx5aW5nIHNpbmsgc2lnbmFsZWRcbiAgICAgKiB0aGF0IHRoZXJlIHdhcyBhbiBlcnJvciBkb2luZyBzby4gQWRkaXRpb25hbGx5LCBpdCB3aWxsIHJlamVjdCB3aXRoIGEgYFR5cGVFcnJvcmAgKHdpdGhvdXQgYXR0ZW1wdGluZyB0byBjYW5jZWxcbiAgICAgKiB0aGUgc3RyZWFtKSBpZiB0aGUgc3RyZWFtIGlzIGN1cnJlbnRseSBsb2NrZWQuXG4gICAgICovXG4gICAgYWJvcnQocmVhc29uID0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGlmICghSXNXcml0YWJsZVN0cmVhbSh0aGlzKSkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoc3RyZWFtQnJhbmRDaGVja0V4Y2VwdGlvbignYWJvcnQnKSk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKElzV3JpdGFibGVTdHJlYW1Mb2NrZWQodGhpcykpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKG5ldyBUeXBlRXJyb3IoJ0Nhbm5vdCBhYm9ydCBhIHN0cmVhbSB0aGF0IGFscmVhZHkgaGFzIGEgd3JpdGVyJykpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBXcml0YWJsZVN0cmVhbUFib3J0KHRoaXMsIHJlYXNvbik7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENsb3NlcyB0aGUgc3RyZWFtLiBUaGUgdW5kZXJseWluZyBzaW5rIHdpbGwgZmluaXNoIHByb2Nlc3NpbmcgYW55IHByZXZpb3VzbHktd3JpdHRlbiBjaHVua3MsIGJlZm9yZSBpbnZva2luZyBpdHNcbiAgICAgKiBjbG9zZSBiZWhhdmlvci4gRHVyaW5nIHRoaXMgdGltZSBhbnkgZnVydGhlciBhdHRlbXB0cyB0byB3cml0ZSB3aWxsIGZhaWwgKHdpdGhvdXQgZXJyb3JpbmcgdGhlIHN0cmVhbSkuXG4gICAgICpcbiAgICAgKiBUaGUgbWV0aG9kIHJldHVybnMgYSBwcm9taXNlIHRoYXQgd2lsbCBmdWxmaWxsIGlmIGFsbCByZW1haW5pbmcgY2h1bmtzIGFyZSBzdWNjZXNzZnVsbHkgd3JpdHRlbiBhbmQgdGhlIHN0cmVhbVxuICAgICAqIHN1Y2Nlc3NmdWxseSBjbG9zZXMsIG9yIHJlamVjdHMgaWYgYW4gZXJyb3IgaXMgZW5jb3VudGVyZWQgZHVyaW5nIHRoaXMgcHJvY2Vzcy4gQWRkaXRpb25hbGx5LCBpdCB3aWxsIHJlamVjdCB3aXRoXG4gICAgICogYSBgVHlwZUVycm9yYCAod2l0aG91dCBhdHRlbXB0aW5nIHRvIGNhbmNlbCB0aGUgc3RyZWFtKSBpZiB0aGUgc3RyZWFtIGlzIGN1cnJlbnRseSBsb2NrZWQuXG4gICAgICovXG4gICAgY2xvc2UoKSB7XG4gICAgICAgIGlmICghSXNXcml0YWJsZVN0cmVhbSh0aGlzKSkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoc3RyZWFtQnJhbmRDaGVja0V4Y2VwdGlvbignY2xvc2UnKSk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKElzV3JpdGFibGVTdHJlYW1Mb2NrZWQodGhpcykpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKG5ldyBUeXBlRXJyb3IoJ0Nhbm5vdCBjbG9zZSBhIHN0cmVhbSB0aGF0IGFscmVhZHkgaGFzIGEgd3JpdGVyJykpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChXcml0YWJsZVN0cmVhbUNsb3NlUXVldWVkT3JJbkZsaWdodCh0aGlzKSkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgobmV3IFR5cGVFcnJvcignQ2Fubm90IGNsb3NlIGFuIGFscmVhZHktY2xvc2luZyBzdHJlYW0nKSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIFdyaXRhYmxlU3RyZWFtQ2xvc2UodGhpcyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENyZWF0ZXMgYSB7QGxpbmsgV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyIHwgd3JpdGVyfSBhbmQgbG9ja3MgdGhlIHN0cmVhbSB0byB0aGUgbmV3IHdyaXRlci4gV2hpbGUgdGhlIHN0cmVhbVxuICAgICAqIGlzIGxvY2tlZCwgbm8gb3RoZXIgd3JpdGVyIGNhbiBiZSBhY3F1aXJlZCB1bnRpbCB0aGlzIG9uZSBpcyByZWxlYXNlZC5cbiAgICAgKlxuICAgICAqIFRoaXMgZnVuY3Rpb25hbGl0eSBpcyBlc3BlY2lhbGx5IHVzZWZ1bCBmb3IgY3JlYXRpbmcgYWJzdHJhY3Rpb25zIHRoYXQgZGVzaXJlIHRoZSBhYmlsaXR5IHRvIHdyaXRlIHRvIGEgc3RyZWFtXG4gICAgICogd2l0aG91dCBpbnRlcnJ1cHRpb24gb3IgaW50ZXJsZWF2aW5nLiBCeSBnZXR0aW5nIGEgd3JpdGVyIGZvciB0aGUgc3RyZWFtLCB5b3UgY2FuIGVuc3VyZSBub2JvZHkgZWxzZSBjYW4gd3JpdGUgYXRcbiAgICAgKiB0aGUgc2FtZSB0aW1lLCB3aGljaCB3b3VsZCBjYXVzZSB0aGUgcmVzdWx0aW5nIHdyaXR0ZW4gZGF0YSB0byBiZSB1bnByZWRpY3RhYmxlIGFuZCBwcm9iYWJseSB1c2VsZXNzLlxuICAgICAqL1xuICAgIGdldFdyaXRlcigpIHtcbiAgICAgICAgaWYgKCFJc1dyaXRhYmxlU3RyZWFtKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBzdHJlYW1CcmFuZENoZWNrRXhjZXB0aW9uKCdnZXRXcml0ZXInKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gQWNxdWlyZVdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlcih0aGlzKTtcbiAgICB9XG59XG5PYmplY3QuZGVmaW5lUHJvcGVydGllcyhXcml0YWJsZVN0cmVhbS5wcm90b3R5cGUsIHtcbiAgICBhYm9ydDogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgY2xvc2U6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIGdldFdyaXRlcjogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgbG9ja2VkOiB7IGVudW1lcmFibGU6IHRydWUgfVxufSk7XG5pZiAodHlwZW9mIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnID09PSAnc3ltYm9sJykge1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShXcml0YWJsZVN0cmVhbS5wcm90b3R5cGUsIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnLCB7XG4gICAgICAgIHZhbHVlOiAnV3JpdGFibGVTdHJlYW0nLFxuICAgICAgICBjb25maWd1cmFibGU6IHRydWVcbiAgICB9KTtcbn1cbi8vIEFic3RyYWN0IG9wZXJhdGlvbnMgZm9yIHRoZSBXcml0YWJsZVN0cmVhbS5cbmZ1bmN0aW9uIEFjcXVpcmVXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXIoc3RyZWFtKSB7XG4gICAgcmV0dXJuIG5ldyBXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXIoc3RyZWFtKTtcbn1cbi8vIFRocm93cyBpZiBhbmQgb25seSBpZiBzdGFydEFsZ29yaXRobSB0aHJvd3MuXG5mdW5jdGlvbiBDcmVhdGVXcml0YWJsZVN0cmVhbShzdGFydEFsZ29yaXRobSwgd3JpdGVBbGdvcml0aG0sIGNsb3NlQWxnb3JpdGhtLCBhYm9ydEFsZ29yaXRobSwgaGlnaFdhdGVyTWFyayA9IDEsIHNpemVBbGdvcml0aG0gPSAoKSA9PiAxKSB7XG4gICAgY29uc3Qgc3RyZWFtID0gT2JqZWN0LmNyZWF0ZShXcml0YWJsZVN0cmVhbS5wcm90b3R5cGUpO1xuICAgIEluaXRpYWxpemVXcml0YWJsZVN0cmVhbShzdHJlYW0pO1xuICAgIGNvbnN0IGNvbnRyb2xsZXIgPSBPYmplY3QuY3JlYXRlKFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIucHJvdG90eXBlKTtcbiAgICBTZXRVcFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIoc3RyZWFtLCBjb250cm9sbGVyLCBzdGFydEFsZ29yaXRobSwgd3JpdGVBbGdvcml0aG0sIGNsb3NlQWxnb3JpdGhtLCBhYm9ydEFsZ29yaXRobSwgaGlnaFdhdGVyTWFyaywgc2l6ZUFsZ29yaXRobSk7XG4gICAgcmV0dXJuIHN0cmVhbTtcbn1cbmZ1bmN0aW9uIEluaXRpYWxpemVXcml0YWJsZVN0cmVhbShzdHJlYW0pIHtcbiAgICBzdHJlYW0uX3N0YXRlID0gJ3dyaXRhYmxlJztcbiAgICAvLyBUaGUgZXJyb3IgdGhhdCB3aWxsIGJlIHJlcG9ydGVkIGJ5IG5ldyBtZXRob2QgY2FsbHMgb25jZSB0aGUgc3RhdGUgYmVjb21lcyBlcnJvcmVkLiBPbmx5IHNldCB3aGVuIFtbc3RhdGVdXSBpc1xuICAgIC8vICdlcnJvcmluZycgb3IgJ2Vycm9yZWQnLiBNYXkgYmUgc2V0IHRvIGFuIHVuZGVmaW5lZCB2YWx1ZS5cbiAgICBzdHJlYW0uX3N0b3JlZEVycm9yID0gdW5kZWZpbmVkO1xuICAgIHN0cmVhbS5fd3JpdGVyID0gdW5kZWZpbmVkO1xuICAgIC8vIEluaXRpYWxpemUgdG8gdW5kZWZpbmVkIGZpcnN0IGJlY2F1c2UgdGhlIGNvbnN0cnVjdG9yIG9mIHRoZSBjb250cm9sbGVyIGNoZWNrcyB0aGlzXG4gICAgLy8gdmFyaWFibGUgdG8gdmFsaWRhdGUgdGhlIGNhbGxlci5cbiAgICBzdHJlYW0uX3dyaXRhYmxlU3RyZWFtQ29udHJvbGxlciA9IHVuZGVmaW5lZDtcbiAgICAvLyBUaGlzIHF1ZXVlIGlzIHBsYWNlZCBoZXJlIGluc3RlYWQgb2YgdGhlIHdyaXRlciBjbGFzcyBpbiBvcmRlciB0byBhbGxvdyBmb3IgcGFzc2luZyBhIHdyaXRlciB0byB0aGUgbmV4dCBkYXRhXG4gICAgLy8gcHJvZHVjZXIgd2l0aG91dCB3YWl0aW5nIGZvciB0aGUgcXVldWVkIHdyaXRlcyB0byBmaW5pc2guXG4gICAgc3RyZWFtLl93cml0ZVJlcXVlc3RzID0gbmV3IFNpbXBsZVF1ZXVlKCk7XG4gICAgLy8gV3JpdGUgcmVxdWVzdHMgYXJlIHJlbW92ZWQgZnJvbSBfd3JpdGVSZXF1ZXN0cyB3aGVuIHdyaXRlKCkgaXMgY2FsbGVkIG9uIHRoZSB1bmRlcmx5aW5nIHNpbmsuIFRoaXMgcHJldmVudHNcbiAgICAvLyB0aGVtIGZyb20gYmVpbmcgZXJyb25lb3VzbHkgcmVqZWN0ZWQgb24gZXJyb3IuIElmIGEgd3JpdGUoKSBjYWxsIGlzIGluLWZsaWdodCwgdGhlIHJlcXVlc3QgaXMgc3RvcmVkIGhlcmUuXG4gICAgc3RyZWFtLl9pbkZsaWdodFdyaXRlUmVxdWVzdCA9IHVuZGVmaW5lZDtcbiAgICAvLyBUaGUgcHJvbWlzZSB0aGF0IHdhcyByZXR1cm5lZCBmcm9tIHdyaXRlci5jbG9zZSgpLiBTdG9yZWQgaGVyZSBiZWNhdXNlIGl0IG1heSBiZSBmdWxmaWxsZWQgYWZ0ZXIgdGhlIHdyaXRlclxuICAgIC8vIGhhcyBiZWVuIGRldGFjaGVkLlxuICAgIHN0cmVhbS5fY2xvc2VSZXF1ZXN0ID0gdW5kZWZpbmVkO1xuICAgIC8vIENsb3NlIHJlcXVlc3QgaXMgcmVtb3ZlZCBmcm9tIF9jbG9zZVJlcXVlc3Qgd2hlbiBjbG9zZSgpIGlzIGNhbGxlZCBvbiB0aGUgdW5kZXJseWluZyBzaW5rLiBUaGlzIHByZXZlbnRzIGl0XG4gICAgLy8gZnJvbSBiZWluZyBlcnJvbmVvdXNseSByZWplY3RlZCBvbiBlcnJvci4gSWYgYSBjbG9zZSgpIGNhbGwgaXMgaW4tZmxpZ2h0LCB0aGUgcmVxdWVzdCBpcyBzdG9yZWQgaGVyZS5cbiAgICBzdHJlYW0uX2luRmxpZ2h0Q2xvc2VSZXF1ZXN0ID0gdW5kZWZpbmVkO1xuICAgIC8vIFRoZSBwcm9taXNlIHRoYXQgd2FzIHJldHVybmVkIGZyb20gd3JpdGVyLmFib3J0KCkuIFRoaXMgbWF5IGFsc28gYmUgZnVsZmlsbGVkIGFmdGVyIHRoZSB3cml0ZXIgaGFzIGRldGFjaGVkLlxuICAgIHN0cmVhbS5fcGVuZGluZ0Fib3J0UmVxdWVzdCA9IHVuZGVmaW5lZDtcbiAgICAvLyBUaGUgYmFja3ByZXNzdXJlIHNpZ25hbCBzZXQgYnkgdGhlIGNvbnRyb2xsZXIuXG4gICAgc3RyZWFtLl9iYWNrcHJlc3N1cmUgPSBmYWxzZTtcbn1cbmZ1bmN0aW9uIElzV3JpdGFibGVTdHJlYW0oeCkge1xuICAgIGlmICghdHlwZUlzT2JqZWN0KHgpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgaWYgKCFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoeCwgJ193cml0YWJsZVN0cmVhbUNvbnRyb2xsZXInKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xufVxuZnVuY3Rpb24gSXNXcml0YWJsZVN0cmVhbUxvY2tlZChzdHJlYW0pIHtcbiAgICBpZiAoc3RyZWFtLl93cml0ZXIgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1BYm9ydChzdHJlYW0sIHJlYXNvbikge1xuICAgIGNvbnN0IHN0YXRlID0gc3RyZWFtLl9zdGF0ZTtcbiAgICBpZiAoc3RhdGUgPT09ICdjbG9zZWQnIHx8IHN0YXRlID09PSAnZXJyb3JlZCcpIHtcbiAgICAgICAgcmV0dXJuIHByb21pc2VSZXNvbHZlZFdpdGgodW5kZWZpbmVkKTtcbiAgICB9XG4gICAgaWYgKHN0cmVhbS5fcGVuZGluZ0Fib3J0UmVxdWVzdCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBzdHJlYW0uX3BlbmRpbmdBYm9ydFJlcXVlc3QuX3Byb21pc2U7XG4gICAgfVxuICAgIGxldCB3YXNBbHJlYWR5RXJyb3JpbmcgPSBmYWxzZTtcbiAgICBpZiAoc3RhdGUgPT09ICdlcnJvcmluZycpIHtcbiAgICAgICAgd2FzQWxyZWFkeUVycm9yaW5nID0gdHJ1ZTtcbiAgICAgICAgLy8gcmVhc29uIHdpbGwgbm90IGJlIHVzZWQsIHNvIGRvbid0IGtlZXAgYSByZWZlcmVuY2UgdG8gaXQuXG4gICAgICAgIHJlYXNvbiA9IHVuZGVmaW5lZDtcbiAgICB9XG4gICAgY29uc3QgcHJvbWlzZSA9IG5ld1Byb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICBzdHJlYW0uX3BlbmRpbmdBYm9ydFJlcXVlc3QgPSB7XG4gICAgICAgICAgICBfcHJvbWlzZTogdW5kZWZpbmVkLFxuICAgICAgICAgICAgX3Jlc29sdmU6IHJlc29sdmUsXG4gICAgICAgICAgICBfcmVqZWN0OiByZWplY3QsXG4gICAgICAgICAgICBfcmVhc29uOiByZWFzb24sXG4gICAgICAgICAgICBfd2FzQWxyZWFkeUVycm9yaW5nOiB3YXNBbHJlYWR5RXJyb3JpbmdcbiAgICAgICAgfTtcbiAgICB9KTtcbiAgICBzdHJlYW0uX3BlbmRpbmdBYm9ydFJlcXVlc3QuX3Byb21pc2UgPSBwcm9taXNlO1xuICAgIGlmICghd2FzQWxyZWFkeUVycm9yaW5nKSB7XG4gICAgICAgIFdyaXRhYmxlU3RyZWFtU3RhcnRFcnJvcmluZyhzdHJlYW0sIHJlYXNvbik7XG4gICAgfVxuICAgIHJldHVybiBwcm9taXNlO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1DbG9zZShzdHJlYW0pIHtcbiAgICBjb25zdCBzdGF0ZSA9IHN0cmVhbS5fc3RhdGU7XG4gICAgaWYgKHN0YXRlID09PSAnY2xvc2VkJyB8fCBzdGF0ZSA9PT0gJ2Vycm9yZWQnKSB7XG4gICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKG5ldyBUeXBlRXJyb3IoYFRoZSBzdHJlYW0gKGluICR7c3RhdGV9IHN0YXRlKSBpcyBub3QgaW4gdGhlIHdyaXRhYmxlIHN0YXRlIGFuZCBjYW5ub3QgYmUgY2xvc2VkYCkpO1xuICAgIH1cbiAgICBjb25zdCBwcm9taXNlID0gbmV3UHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGNvbnN0IGNsb3NlUmVxdWVzdCA9IHtcbiAgICAgICAgICAgIF9yZXNvbHZlOiByZXNvbHZlLFxuICAgICAgICAgICAgX3JlamVjdDogcmVqZWN0XG4gICAgICAgIH07XG4gICAgICAgIHN0cmVhbS5fY2xvc2VSZXF1ZXN0ID0gY2xvc2VSZXF1ZXN0O1xuICAgIH0pO1xuICAgIGNvbnN0IHdyaXRlciA9IHN0cmVhbS5fd3JpdGVyO1xuICAgIGlmICh3cml0ZXIgIT09IHVuZGVmaW5lZCAmJiBzdHJlYW0uX2JhY2twcmVzc3VyZSAmJiBzdGF0ZSA9PT0gJ3dyaXRhYmxlJykge1xuICAgICAgICBkZWZhdWx0V3JpdGVyUmVhZHlQcm9taXNlUmVzb2x2ZSh3cml0ZXIpO1xuICAgIH1cbiAgICBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2xvc2Uoc3RyZWFtLl93cml0YWJsZVN0cmVhbUNvbnRyb2xsZXIpO1xuICAgIHJldHVybiBwcm9taXNlO1xufVxuLy8gV3JpdGFibGVTdHJlYW0gQVBJIGV4cG9zZWQgZm9yIGNvbnRyb2xsZXJzLlxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1BZGRXcml0ZVJlcXVlc3Qoc3RyZWFtKSB7XG4gICAgY29uc3QgcHJvbWlzZSA9IG5ld1Byb21pc2UoKHJlc29sdmUsIHJlamVjdCkgPT4ge1xuICAgICAgICBjb25zdCB3cml0ZVJlcXVlc3QgPSB7XG4gICAgICAgICAgICBfcmVzb2x2ZTogcmVzb2x2ZSxcbiAgICAgICAgICAgIF9yZWplY3Q6IHJlamVjdFxuICAgICAgICB9O1xuICAgICAgICBzdHJlYW0uX3dyaXRlUmVxdWVzdHMucHVzaCh3cml0ZVJlcXVlc3QpO1xuICAgIH0pO1xuICAgIHJldHVybiBwcm9taXNlO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1EZWFsV2l0aFJlamVjdGlvbihzdHJlYW0sIGVycm9yKSB7XG4gICAgY29uc3Qgc3RhdGUgPSBzdHJlYW0uX3N0YXRlO1xuICAgIGlmIChzdGF0ZSA9PT0gJ3dyaXRhYmxlJykge1xuICAgICAgICBXcml0YWJsZVN0cmVhbVN0YXJ0RXJyb3Jpbmcoc3RyZWFtLCBlcnJvcik7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgV3JpdGFibGVTdHJlYW1GaW5pc2hFcnJvcmluZyhzdHJlYW0pO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1TdGFydEVycm9yaW5nKHN0cmVhbSwgcmVhc29uKSB7XG4gICAgY29uc3QgY29udHJvbGxlciA9IHN0cmVhbS5fd3JpdGFibGVTdHJlYW1Db250cm9sbGVyO1xuICAgIHN0cmVhbS5fc3RhdGUgPSAnZXJyb3JpbmcnO1xuICAgIHN0cmVhbS5fc3RvcmVkRXJyb3IgPSByZWFzb247XG4gICAgY29uc3Qgd3JpdGVyID0gc3RyZWFtLl93cml0ZXI7XG4gICAgaWYgKHdyaXRlciAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlckVuc3VyZVJlYWR5UHJvbWlzZVJlamVjdGVkKHdyaXRlciwgcmVhc29uKTtcbiAgICB9XG4gICAgaWYgKCFXcml0YWJsZVN0cmVhbUhhc09wZXJhdGlvbk1hcmtlZEluRmxpZ2h0KHN0cmVhbSkgJiYgY29udHJvbGxlci5fc3RhcnRlZCkge1xuICAgICAgICBXcml0YWJsZVN0cmVhbUZpbmlzaEVycm9yaW5nKHN0cmVhbSk7XG4gICAgfVxufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1GaW5pc2hFcnJvcmluZyhzdHJlYW0pIHtcbiAgICBzdHJlYW0uX3N0YXRlID0gJ2Vycm9yZWQnO1xuICAgIHN0cmVhbS5fd3JpdGFibGVTdHJlYW1Db250cm9sbGVyW0Vycm9yU3RlcHNdKCk7XG4gICAgY29uc3Qgc3RvcmVkRXJyb3IgPSBzdHJlYW0uX3N0b3JlZEVycm9yO1xuICAgIHN0cmVhbS5fd3JpdGVSZXF1ZXN0cy5mb3JFYWNoKHdyaXRlUmVxdWVzdCA9PiB7XG4gICAgICAgIHdyaXRlUmVxdWVzdC5fcmVqZWN0KHN0b3JlZEVycm9yKTtcbiAgICB9KTtcbiAgICBzdHJlYW0uX3dyaXRlUmVxdWVzdHMgPSBuZXcgU2ltcGxlUXVldWUoKTtcbiAgICBpZiAoc3RyZWFtLl9wZW5kaW5nQWJvcnRSZXF1ZXN0ID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgV3JpdGFibGVTdHJlYW1SZWplY3RDbG9zZUFuZENsb3NlZFByb21pc2VJZk5lZWRlZChzdHJlYW0pO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGFib3J0UmVxdWVzdCA9IHN0cmVhbS5fcGVuZGluZ0Fib3J0UmVxdWVzdDtcbiAgICBzdHJlYW0uX3BlbmRpbmdBYm9ydFJlcXVlc3QgPSB1bmRlZmluZWQ7XG4gICAgaWYgKGFib3J0UmVxdWVzdC5fd2FzQWxyZWFkeUVycm9yaW5nKSB7XG4gICAgICAgIGFib3J0UmVxdWVzdC5fcmVqZWN0KHN0b3JlZEVycm9yKTtcbiAgICAgICAgV3JpdGFibGVTdHJlYW1SZWplY3RDbG9zZUFuZENsb3NlZFByb21pc2VJZk5lZWRlZChzdHJlYW0pO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHByb21pc2UgPSBzdHJlYW0uX3dyaXRhYmxlU3RyZWFtQ29udHJvbGxlcltBYm9ydFN0ZXBzXShhYm9ydFJlcXVlc3QuX3JlYXNvbik7XG4gICAgdXBvblByb21pc2UocHJvbWlzZSwgKCkgPT4ge1xuICAgICAgICBhYm9ydFJlcXVlc3QuX3Jlc29sdmUoKTtcbiAgICAgICAgV3JpdGFibGVTdHJlYW1SZWplY3RDbG9zZUFuZENsb3NlZFByb21pc2VJZk5lZWRlZChzdHJlYW0pO1xuICAgIH0sIChyZWFzb24pID0+IHtcbiAgICAgICAgYWJvcnRSZXF1ZXN0Ll9yZWplY3QocmVhc29uKTtcbiAgICAgICAgV3JpdGFibGVTdHJlYW1SZWplY3RDbG9zZUFuZENsb3NlZFByb21pc2VJZk5lZWRlZChzdHJlYW0pO1xuICAgIH0pO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1GaW5pc2hJbkZsaWdodFdyaXRlKHN0cmVhbSkge1xuICAgIHN0cmVhbS5faW5GbGlnaHRXcml0ZVJlcXVlc3QuX3Jlc29sdmUodW5kZWZpbmVkKTtcbiAgICBzdHJlYW0uX2luRmxpZ2h0V3JpdGVSZXF1ZXN0ID0gdW5kZWZpbmVkO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1GaW5pc2hJbkZsaWdodFdyaXRlV2l0aEVycm9yKHN0cmVhbSwgZXJyb3IpIHtcbiAgICBzdHJlYW0uX2luRmxpZ2h0V3JpdGVSZXF1ZXN0Ll9yZWplY3QoZXJyb3IpO1xuICAgIHN0cmVhbS5faW5GbGlnaHRXcml0ZVJlcXVlc3QgPSB1bmRlZmluZWQ7XG4gICAgV3JpdGFibGVTdHJlYW1EZWFsV2l0aFJlamVjdGlvbihzdHJlYW0sIGVycm9yKTtcbn1cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtRmluaXNoSW5GbGlnaHRDbG9zZShzdHJlYW0pIHtcbiAgICBzdHJlYW0uX2luRmxpZ2h0Q2xvc2VSZXF1ZXN0Ll9yZXNvbHZlKHVuZGVmaW5lZCk7XG4gICAgc3RyZWFtLl9pbkZsaWdodENsb3NlUmVxdWVzdCA9IHVuZGVmaW5lZDtcbiAgICBjb25zdCBzdGF0ZSA9IHN0cmVhbS5fc3RhdGU7XG4gICAgaWYgKHN0YXRlID09PSAnZXJyb3JpbmcnKSB7XG4gICAgICAgIC8vIFRoZSBlcnJvciB3YXMgdG9vIGxhdGUgdG8gZG8gYW55dGhpbmcsIHNvIGl0IGlzIGlnbm9yZWQuXG4gICAgICAgIHN0cmVhbS5fc3RvcmVkRXJyb3IgPSB1bmRlZmluZWQ7XG4gICAgICAgIGlmIChzdHJlYW0uX3BlbmRpbmdBYm9ydFJlcXVlc3QgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgc3RyZWFtLl9wZW5kaW5nQWJvcnRSZXF1ZXN0Ll9yZXNvbHZlKCk7XG4gICAgICAgICAgICBzdHJlYW0uX3BlbmRpbmdBYm9ydFJlcXVlc3QgPSB1bmRlZmluZWQ7XG4gICAgICAgIH1cbiAgICB9XG4gICAgc3RyZWFtLl9zdGF0ZSA9ICdjbG9zZWQnO1xuICAgIGNvbnN0IHdyaXRlciA9IHN0cmVhbS5fd3JpdGVyO1xuICAgIGlmICh3cml0ZXIgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBkZWZhdWx0V3JpdGVyQ2xvc2VkUHJvbWlzZVJlc29sdmUod3JpdGVyKTtcbiAgICB9XG59XG5mdW5jdGlvbiBXcml0YWJsZVN0cmVhbUZpbmlzaEluRmxpZ2h0Q2xvc2VXaXRoRXJyb3Ioc3RyZWFtLCBlcnJvcikge1xuICAgIHN0cmVhbS5faW5GbGlnaHRDbG9zZVJlcXVlc3QuX3JlamVjdChlcnJvcik7XG4gICAgc3RyZWFtLl9pbkZsaWdodENsb3NlUmVxdWVzdCA9IHVuZGVmaW5lZDtcbiAgICAvLyBOZXZlciBleGVjdXRlIHNpbmsgYWJvcnQoKSBhZnRlciBzaW5rIGNsb3NlKCkuXG4gICAgaWYgKHN0cmVhbS5fcGVuZGluZ0Fib3J0UmVxdWVzdCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHN0cmVhbS5fcGVuZGluZ0Fib3J0UmVxdWVzdC5fcmVqZWN0KGVycm9yKTtcbiAgICAgICAgc3RyZWFtLl9wZW5kaW5nQWJvcnRSZXF1ZXN0ID0gdW5kZWZpbmVkO1xuICAgIH1cbiAgICBXcml0YWJsZVN0cmVhbURlYWxXaXRoUmVqZWN0aW9uKHN0cmVhbSwgZXJyb3IpO1xufVxuLy8gVE9ETyhyaWNlYSk6IEZpeCBhbHBoYWJldGljYWwgb3JkZXIuXG5mdW5jdGlvbiBXcml0YWJsZVN0cmVhbUNsb3NlUXVldWVkT3JJbkZsaWdodChzdHJlYW0pIHtcbiAgICBpZiAoc3RyZWFtLl9jbG9zZVJlcXVlc3QgPT09IHVuZGVmaW5lZCAmJiBzdHJlYW0uX2luRmxpZ2h0Q2xvc2VSZXF1ZXN0ID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICByZXR1cm4gdHJ1ZTtcbn1cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtSGFzT3BlcmF0aW9uTWFya2VkSW5GbGlnaHQoc3RyZWFtKSB7XG4gICAgaWYgKHN0cmVhbS5faW5GbGlnaHRXcml0ZVJlcXVlc3QgPT09IHVuZGVmaW5lZCAmJiBzdHJlYW0uX2luRmxpZ2h0Q2xvc2VSZXF1ZXN0ID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICByZXR1cm4gdHJ1ZTtcbn1cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtTWFya0Nsb3NlUmVxdWVzdEluRmxpZ2h0KHN0cmVhbSkge1xuICAgIHN0cmVhbS5faW5GbGlnaHRDbG9zZVJlcXVlc3QgPSBzdHJlYW0uX2Nsb3NlUmVxdWVzdDtcbiAgICBzdHJlYW0uX2Nsb3NlUmVxdWVzdCA9IHVuZGVmaW5lZDtcbn1cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtTWFya0ZpcnN0V3JpdGVSZXF1ZXN0SW5GbGlnaHQoc3RyZWFtKSB7XG4gICAgc3RyZWFtLl9pbkZsaWdodFdyaXRlUmVxdWVzdCA9IHN0cmVhbS5fd3JpdGVSZXF1ZXN0cy5zaGlmdCgpO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1SZWplY3RDbG9zZUFuZENsb3NlZFByb21pc2VJZk5lZWRlZChzdHJlYW0pIHtcbiAgICBpZiAoc3RyZWFtLl9jbG9zZVJlcXVlc3QgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBzdHJlYW0uX2Nsb3NlUmVxdWVzdC5fcmVqZWN0KHN0cmVhbS5fc3RvcmVkRXJyb3IpO1xuICAgICAgICBzdHJlYW0uX2Nsb3NlUmVxdWVzdCA9IHVuZGVmaW5lZDtcbiAgICB9XG4gICAgY29uc3Qgd3JpdGVyID0gc3RyZWFtLl93cml0ZXI7XG4gICAgaWYgKHdyaXRlciAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlUmVqZWN0KHdyaXRlciwgc3RyZWFtLl9zdG9yZWRFcnJvcik7XG4gICAgfVxufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1VcGRhdGVCYWNrcHJlc3N1cmUoc3RyZWFtLCBiYWNrcHJlc3N1cmUpIHtcbiAgICBjb25zdCB3cml0ZXIgPSBzdHJlYW0uX3dyaXRlcjtcbiAgICBpZiAod3JpdGVyICE9PSB1bmRlZmluZWQgJiYgYmFja3ByZXNzdXJlICE9PSBzdHJlYW0uX2JhY2twcmVzc3VyZSkge1xuICAgICAgICBpZiAoYmFja3ByZXNzdXJlKSB7XG4gICAgICAgICAgICBkZWZhdWx0V3JpdGVyUmVhZHlQcm9taXNlUmVzZXQod3JpdGVyKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGRlZmF1bHRXcml0ZXJSZWFkeVByb21pc2VSZXNvbHZlKHdyaXRlcik7XG4gICAgICAgIH1cbiAgICB9XG4gICAgc3RyZWFtLl9iYWNrcHJlc3N1cmUgPSBiYWNrcHJlc3N1cmU7XG59XG4vKipcbiAqIEEgZGVmYXVsdCB3cml0ZXIgdmVuZGVkIGJ5IGEge0BsaW5rIFdyaXRhYmxlU3RyZWFtfS5cbiAqXG4gKiBAcHVibGljXG4gKi9cbmNsYXNzIFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlciB7XG4gICAgY29uc3RydWN0b3Ioc3RyZWFtKSB7XG4gICAgICAgIGFzc2VydFJlcXVpcmVkQXJndW1lbnQoc3RyZWFtLCAxLCAnV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyJyk7XG4gICAgICAgIGFzc2VydFdyaXRhYmxlU3RyZWFtKHN0cmVhbSwgJ0ZpcnN0IHBhcmFtZXRlcicpO1xuICAgICAgICBpZiAoSXNXcml0YWJsZVN0cmVhbUxvY2tlZChzdHJlYW0pKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdUaGlzIHN0cmVhbSBoYXMgYWxyZWFkeSBiZWVuIGxvY2tlZCBmb3IgZXhjbHVzaXZlIHdyaXRpbmcgYnkgYW5vdGhlciB3cml0ZXInKTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLl9vd25lcldyaXRhYmxlU3RyZWFtID0gc3RyZWFtO1xuICAgICAgICBzdHJlYW0uX3dyaXRlciA9IHRoaXM7XG4gICAgICAgIGNvbnN0IHN0YXRlID0gc3RyZWFtLl9zdGF0ZTtcbiAgICAgICAgaWYgKHN0YXRlID09PSAnd3JpdGFibGUnKSB7XG4gICAgICAgICAgICBpZiAoIVdyaXRhYmxlU3RyZWFtQ2xvc2VRdWV1ZWRPckluRmxpZ2h0KHN0cmVhbSkgJiYgc3RyZWFtLl9iYWNrcHJlc3N1cmUpIHtcbiAgICAgICAgICAgICAgICBkZWZhdWx0V3JpdGVyUmVhZHlQcm9taXNlSW5pdGlhbGl6ZSh0aGlzKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGRlZmF1bHRXcml0ZXJSZWFkeVByb21pc2VJbml0aWFsaXplQXNSZXNvbHZlZCh0aGlzKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZSh0aGlzKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChzdGF0ZSA9PT0gJ2Vycm9yaW5nJykge1xuICAgICAgICAgICAgZGVmYXVsdFdyaXRlclJlYWR5UHJvbWlzZUluaXRpYWxpemVBc1JlamVjdGVkKHRoaXMsIHN0cmVhbS5fc3RvcmVkRXJyb3IpO1xuICAgICAgICAgICAgZGVmYXVsdFdyaXRlckNsb3NlZFByb21pc2VJbml0aWFsaXplKHRoaXMpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHN0YXRlID09PSAnY2xvc2VkJykge1xuICAgICAgICAgICAgZGVmYXVsdFdyaXRlclJlYWR5UHJvbWlzZUluaXRpYWxpemVBc1Jlc29sdmVkKHRoaXMpO1xuICAgICAgICAgICAgZGVmYXVsdFdyaXRlckNsb3NlZFByb21pc2VJbml0aWFsaXplQXNSZXNvbHZlZCh0aGlzKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGNvbnN0IHN0b3JlZEVycm9yID0gc3RyZWFtLl9zdG9yZWRFcnJvcjtcbiAgICAgICAgICAgIGRlZmF1bHRXcml0ZXJSZWFkeVByb21pc2VJbml0aWFsaXplQXNSZWplY3RlZCh0aGlzLCBzdG9yZWRFcnJvcik7XG4gICAgICAgICAgICBkZWZhdWx0V3JpdGVyQ2xvc2VkUHJvbWlzZUluaXRpYWxpemVBc1JlamVjdGVkKHRoaXMsIHN0b3JlZEVycm9yKTtcbiAgICAgICAgfVxuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIGEgcHJvbWlzZSB0aGF0IHdpbGwgYmUgZnVsZmlsbGVkIHdoZW4gdGhlIHN0cmVhbSBiZWNvbWVzIGNsb3NlZCwgb3IgcmVqZWN0ZWQgaWYgdGhlIHN0cmVhbSBldmVyIGVycm9ycyBvclxuICAgICAqIHRoZSB3cml0ZXLigJlzIGxvY2sgaXMgcmVsZWFzZWQgYmVmb3JlIHRoZSBzdHJlYW0gZmluaXNoZXMgY2xvc2luZy5cbiAgICAgKi9cbiAgICBnZXQgY2xvc2VkKCkge1xuICAgICAgICBpZiAoIUlzV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyKHRoaXMpKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChkZWZhdWx0V3JpdGVyQnJhbmRDaGVja0V4Y2VwdGlvbignY2xvc2VkJykpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLl9jbG9zZWRQcm9taXNlO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBkZXNpcmVkIHNpemUgdG8gZmlsbCB0aGUgc3RyZWFt4oCZcyBpbnRlcm5hbCBxdWV1ZS4gSXQgY2FuIGJlIG5lZ2F0aXZlLCBpZiB0aGUgcXVldWUgaXMgb3Zlci1mdWxsLlxuICAgICAqIEEgcHJvZHVjZXIgY2FuIHVzZSB0aGlzIGluZm9ybWF0aW9uIHRvIGRldGVybWluZSB0aGUgcmlnaHQgYW1vdW50IG9mIGRhdGEgdG8gd3JpdGUuXG4gICAgICpcbiAgICAgKiBJdCB3aWxsIGJlIGBudWxsYCBpZiB0aGUgc3RyZWFtIGNhbm5vdCBiZSBzdWNjZXNzZnVsbHkgd3JpdHRlbiB0byAoZHVlIHRvIGVpdGhlciBiZWluZyBlcnJvcmVkLCBvciBoYXZpbmcgYW4gYWJvcnRcbiAgICAgKiBxdWV1ZWQgdXApLiBJdCB3aWxsIHJldHVybiB6ZXJvIGlmIHRoZSBzdHJlYW0gaXMgY2xvc2VkLiBBbmQgdGhlIGdldHRlciB3aWxsIHRocm93IGFuIGV4Y2VwdGlvbiBpZiBpbnZva2VkIHdoZW5cbiAgICAgKiB0aGUgd3JpdGVy4oCZcyBsb2NrIGlzIHJlbGVhc2VkLlxuICAgICAqL1xuICAgIGdldCBkZXNpcmVkU2l6ZSgpIHtcbiAgICAgICAgaWYgKCFJc1dyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlcih0aGlzKSkge1xuICAgICAgICAgICAgdGhyb3cgZGVmYXVsdFdyaXRlckJyYW5kQ2hlY2tFeGNlcHRpb24oJ2Rlc2lyZWRTaXplJyk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKHRoaXMuX293bmVyV3JpdGFibGVTdHJlYW0gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgdGhyb3cgZGVmYXVsdFdyaXRlckxvY2tFeGNlcHRpb24oJ2Rlc2lyZWRTaXplJyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlckdldERlc2lyZWRTaXplKHRoaXMpO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIGEgcHJvbWlzZSB0aGF0IHdpbGwgYmUgZnVsZmlsbGVkIHdoZW4gdGhlIGRlc2lyZWQgc2l6ZSB0byBmaWxsIHRoZSBzdHJlYW3igJlzIGludGVybmFsIHF1ZXVlIHRyYW5zaXRpb25zXG4gICAgICogZnJvbSBub24tcG9zaXRpdmUgdG8gcG9zaXRpdmUsIHNpZ25hbGluZyB0aGF0IGl0IGlzIG5vIGxvbmdlciBhcHBseWluZyBiYWNrcHJlc3N1cmUuIE9uY2UgdGhlIGRlc2lyZWQgc2l6ZSBkaXBzXG4gICAgICogYmFjayB0byB6ZXJvIG9yIGJlbG93LCB0aGUgZ2V0dGVyIHdpbGwgcmV0dXJuIGEgbmV3IHByb21pc2UgdGhhdCBzdGF5cyBwZW5kaW5nIHVudGlsIHRoZSBuZXh0IHRyYW5zaXRpb24uXG4gICAgICpcbiAgICAgKiBJZiB0aGUgc3RyZWFtIGJlY29tZXMgZXJyb3JlZCBvciBhYm9ydGVkLCBvciB0aGUgd3JpdGVy4oCZcyBsb2NrIGlzIHJlbGVhc2VkLCB0aGUgcmV0dXJuZWQgcHJvbWlzZSB3aWxsIGJlY29tZVxuICAgICAqIHJlamVjdGVkLlxuICAgICAqL1xuICAgIGdldCByZWFkeSgpIHtcbiAgICAgICAgaWYgKCFJc1dyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlcih0aGlzKSkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoZGVmYXVsdFdyaXRlckJyYW5kQ2hlY2tFeGNlcHRpb24oJ3JlYWR5JykpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLl9yZWFkeVByb21pc2U7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIElmIHRoZSByZWFkZXIgaXMgYWN0aXZlLCBiZWhhdmVzIHRoZSBzYW1lIGFzIHtAbGluayBXcml0YWJsZVN0cmVhbS5hYm9ydCB8IHN0cmVhbS5hYm9ydChyZWFzb24pfS5cbiAgICAgKi9cbiAgICBhYm9ydChyZWFzb24gPSB1bmRlZmluZWQpIHtcbiAgICAgICAgaWYgKCFJc1dyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlcih0aGlzKSkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoZGVmYXVsdFdyaXRlckJyYW5kQ2hlY2tFeGNlcHRpb24oJ2Fib3J0JykpO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0aGlzLl9vd25lcldyaXRhYmxlU3RyZWFtID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKGRlZmF1bHRXcml0ZXJMb2NrRXhjZXB0aW9uKCdhYm9ydCcpKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyQWJvcnQodGhpcywgcmVhc29uKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogSWYgdGhlIHJlYWRlciBpcyBhY3RpdmUsIGJlaGF2ZXMgdGhlIHNhbWUgYXMge0BsaW5rIFdyaXRhYmxlU3RyZWFtLmNsb3NlIHwgc3RyZWFtLmNsb3NlKCl9LlxuICAgICAqL1xuICAgIGNsb3NlKCkge1xuICAgICAgICBpZiAoIUlzV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyKHRoaXMpKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChkZWZhdWx0V3JpdGVyQnJhbmRDaGVja0V4Y2VwdGlvbignY2xvc2UnKSk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgc3RyZWFtID0gdGhpcy5fb3duZXJXcml0YWJsZVN0cmVhbTtcbiAgICAgICAgaWYgKHN0cmVhbSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChkZWZhdWx0V3JpdGVyTG9ja0V4Y2VwdGlvbignY2xvc2UnKSk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKFdyaXRhYmxlU3RyZWFtQ2xvc2VRdWV1ZWRPckluRmxpZ2h0KHN0cmVhbSkpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKG5ldyBUeXBlRXJyb3IoJ0Nhbm5vdCBjbG9zZSBhbiBhbHJlYWR5LWNsb3Npbmcgc3RyZWFtJykpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXJDbG9zZSh0aGlzKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmVsZWFzZXMgdGhlIHdyaXRlcuKAmXMgbG9jayBvbiB0aGUgY29ycmVzcG9uZGluZyBzdHJlYW0uIEFmdGVyIHRoZSBsb2NrIGlzIHJlbGVhc2VkLCB0aGUgd3JpdGVyIGlzIG5vIGxvbmdlciBhY3RpdmUuXG4gICAgICogSWYgdGhlIGFzc29jaWF0ZWQgc3RyZWFtIGlzIGVycm9yZWQgd2hlbiB0aGUgbG9jayBpcyByZWxlYXNlZCwgdGhlIHdyaXRlciB3aWxsIGFwcGVhciBlcnJvcmVkIGluIHRoZSBzYW1lIHdheSBmcm9tXG4gICAgICogbm93IG9uOyBvdGhlcndpc2UsIHRoZSB3cml0ZXIgd2lsbCBhcHBlYXIgY2xvc2VkLlxuICAgICAqXG4gICAgICogTm90ZSB0aGF0IHRoZSBsb2NrIGNhbiBzdGlsbCBiZSByZWxlYXNlZCBldmVuIGlmIHNvbWUgb25nb2luZyB3cml0ZXMgaGF2ZSBub3QgeWV0IGZpbmlzaGVkIChpLmUuIGV2ZW4gaWYgdGhlXG4gICAgICogcHJvbWlzZXMgcmV0dXJuZWQgZnJvbSBwcmV2aW91cyBjYWxscyB0byB7QGxpbmsgV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyLndyaXRlIHwgd3JpdGUoKX0gaGF2ZSBub3QgeWV0IHNldHRsZWQpLlxuICAgICAqIEl04oCZcyBub3QgbmVjZXNzYXJ5IHRvIGhvbGQgdGhlIGxvY2sgb24gdGhlIHdyaXRlciBmb3IgdGhlIGR1cmF0aW9uIG9mIHRoZSB3cml0ZTsgdGhlIGxvY2sgaW5zdGVhZCBzaW1wbHkgcHJldmVudHNcbiAgICAgKiBvdGhlciBwcm9kdWNlcnMgZnJvbSB3cml0aW5nIGluIGFuIGludGVybGVhdmVkIG1hbm5lci5cbiAgICAgKi9cbiAgICByZWxlYXNlTG9jaygpIHtcbiAgICAgICAgaWYgKCFJc1dyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlcih0aGlzKSkge1xuICAgICAgICAgICAgdGhyb3cgZGVmYXVsdFdyaXRlckJyYW5kQ2hlY2tFeGNlcHRpb24oJ3JlbGVhc2VMb2NrJyk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgc3RyZWFtID0gdGhpcy5fb3duZXJXcml0YWJsZVN0cmVhbTtcbiAgICAgICAgaWYgKHN0cmVhbSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cbiAgICAgICAgV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyUmVsZWFzZSh0aGlzKTtcbiAgICB9XG4gICAgd3JpdGUoY2h1bmsgPSB1bmRlZmluZWQpIHtcbiAgICAgICAgaWYgKCFJc1dyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlcih0aGlzKSkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoZGVmYXVsdFdyaXRlckJyYW5kQ2hlY2tFeGNlcHRpb24oJ3dyaXRlJykpO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0aGlzLl9vd25lcldyaXRhYmxlU3RyZWFtID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKGRlZmF1bHRXcml0ZXJMb2NrRXhjZXB0aW9uKCd3cml0ZSB0bycpKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyV3JpdGUodGhpcywgY2h1bmspO1xuICAgIH1cbn1cbk9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlci5wcm90b3R5cGUsIHtcbiAgICBhYm9ydDogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgY2xvc2U6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIHJlbGVhc2VMb2NrOiB7IGVudW1lcmFibGU6IHRydWUgfSxcbiAgICB3cml0ZTogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgY2xvc2VkOiB7IGVudW1lcmFibGU6IHRydWUgfSxcbiAgICBkZXNpcmVkU2l6ZTogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgcmVhZHk6IHsgZW51bWVyYWJsZTogdHJ1ZSB9XG59KTtcbmlmICh0eXBlb2YgU3ltYm9sUG9seWZpbGwudG9TdHJpbmdUYWcgPT09ICdzeW1ib2wnKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlci5wcm90b3R5cGUsIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnLCB7XG4gICAgICAgIHZhbHVlOiAnV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyJyxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlXG4gICAgfSk7XG59XG4vLyBBYnN0cmFjdCBvcGVyYXRpb25zIGZvciB0aGUgV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyLlxuZnVuY3Rpb24gSXNXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXIoeCkge1xuICAgIGlmICghdHlwZUlzT2JqZWN0KHgpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgaWYgKCFPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwoeCwgJ19vd25lcldyaXRhYmxlU3RyZWFtJykpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICByZXR1cm4gdHJ1ZTtcbn1cbi8vIEEgY2xpZW50IG9mIFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlciBtYXkgdXNlIHRoZXNlIGZ1bmN0aW9ucyBkaXJlY3RseSB0byBieXBhc3Mgc3RhdGUgY2hlY2suXG5mdW5jdGlvbiBXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXJBYm9ydCh3cml0ZXIsIHJlYXNvbikge1xuICAgIGNvbnN0IHN0cmVhbSA9IHdyaXRlci5fb3duZXJXcml0YWJsZVN0cmVhbTtcbiAgICByZXR1cm4gV3JpdGFibGVTdHJlYW1BYm9ydChzdHJlYW0sIHJlYXNvbik7XG59XG5mdW5jdGlvbiBXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXJDbG9zZSh3cml0ZXIpIHtcbiAgICBjb25zdCBzdHJlYW0gPSB3cml0ZXIuX293bmVyV3JpdGFibGVTdHJlYW07XG4gICAgcmV0dXJuIFdyaXRhYmxlU3RyZWFtQ2xvc2Uoc3RyZWFtKTtcbn1cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlckNsb3NlV2l0aEVycm9yUHJvcGFnYXRpb24od3JpdGVyKSB7XG4gICAgY29uc3Qgc3RyZWFtID0gd3JpdGVyLl9vd25lcldyaXRhYmxlU3RyZWFtO1xuICAgIGNvbnN0IHN0YXRlID0gc3RyZWFtLl9zdGF0ZTtcbiAgICBpZiAoV3JpdGFibGVTdHJlYW1DbG9zZVF1ZXVlZE9ySW5GbGlnaHQoc3RyZWFtKSB8fCBzdGF0ZSA9PT0gJ2Nsb3NlZCcpIHtcbiAgICAgICAgcmV0dXJuIHByb21pc2VSZXNvbHZlZFdpdGgodW5kZWZpbmVkKTtcbiAgICB9XG4gICAgaWYgKHN0YXRlID09PSAnZXJyb3JlZCcpIHtcbiAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoc3RyZWFtLl9zdG9yZWRFcnJvcik7XG4gICAgfVxuICAgIHJldHVybiBXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXJDbG9zZSh3cml0ZXIpO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyRW5zdXJlQ2xvc2VkUHJvbWlzZVJlamVjdGVkKHdyaXRlciwgZXJyb3IpIHtcbiAgICBpZiAod3JpdGVyLl9jbG9zZWRQcm9taXNlU3RhdGUgPT09ICdwZW5kaW5nJykge1xuICAgICAgICBkZWZhdWx0V3JpdGVyQ2xvc2VkUHJvbWlzZVJlamVjdCh3cml0ZXIsIGVycm9yKTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIGRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlUmVzZXRUb1JlamVjdGVkKHdyaXRlciwgZXJyb3IpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlckVuc3VyZVJlYWR5UHJvbWlzZVJlamVjdGVkKHdyaXRlciwgZXJyb3IpIHtcbiAgICBpZiAod3JpdGVyLl9yZWFkeVByb21pc2VTdGF0ZSA9PT0gJ3BlbmRpbmcnKSB7XG4gICAgICAgIGRlZmF1bHRXcml0ZXJSZWFkeVByb21pc2VSZWplY3Qod3JpdGVyLCBlcnJvcik7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBkZWZhdWx0V3JpdGVyUmVhZHlQcm9taXNlUmVzZXRUb1JlamVjdGVkKHdyaXRlciwgZXJyb3IpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlckdldERlc2lyZWRTaXplKHdyaXRlcikge1xuICAgIGNvbnN0IHN0cmVhbSA9IHdyaXRlci5fb3duZXJXcml0YWJsZVN0cmVhbTtcbiAgICBjb25zdCBzdGF0ZSA9IHN0cmVhbS5fc3RhdGU7XG4gICAgaWYgKHN0YXRlID09PSAnZXJyb3JlZCcgfHwgc3RhdGUgPT09ICdlcnJvcmluZycpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIGlmIChzdGF0ZSA9PT0gJ2Nsb3NlZCcpIHtcbiAgICAgICAgcmV0dXJuIDA7XG4gICAgfVxuICAgIHJldHVybiBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyR2V0RGVzaXJlZFNpemUoc3RyZWFtLl93cml0YWJsZVN0cmVhbUNvbnRyb2xsZXIpO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyUmVsZWFzZSh3cml0ZXIpIHtcbiAgICBjb25zdCBzdHJlYW0gPSB3cml0ZXIuX293bmVyV3JpdGFibGVTdHJlYW07XG4gICAgY29uc3QgcmVsZWFzZWRFcnJvciA9IG5ldyBUeXBlRXJyb3IoYFdyaXRlciB3YXMgcmVsZWFzZWQgYW5kIGNhbiBubyBsb25nZXIgYmUgdXNlZCB0byBtb25pdG9yIHRoZSBzdHJlYW0ncyBjbG9zZWRuZXNzYCk7XG4gICAgV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyRW5zdXJlUmVhZHlQcm9taXNlUmVqZWN0ZWQod3JpdGVyLCByZWxlYXNlZEVycm9yKTtcbiAgICAvLyBUaGUgc3RhdGUgdHJhbnNpdGlvbnMgdG8gXCJlcnJvcmVkXCIgYmVmb3JlIHRoZSBzaW5rIGFib3J0KCkgbWV0aG9kIHJ1bnMsIGJ1dCB0aGUgd3JpdGVyLmNsb3NlZCBwcm9taXNlIGlzIG5vdFxuICAgIC8vIHJlamVjdGVkIHVudGlsIGFmdGVyd2FyZHMuIFRoaXMgbWVhbnMgdGhhdCBzaW1wbHkgdGVzdGluZyBzdGF0ZSB3aWxsIG5vdCB3b3JrLlxuICAgIFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlckVuc3VyZUNsb3NlZFByb21pc2VSZWplY3RlZCh3cml0ZXIsIHJlbGVhc2VkRXJyb3IpO1xuICAgIHN0cmVhbS5fd3JpdGVyID0gdW5kZWZpbmVkO1xuICAgIHdyaXRlci5fb3duZXJXcml0YWJsZVN0cmVhbSA9IHVuZGVmaW5lZDtcbn1cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlcldyaXRlKHdyaXRlciwgY2h1bmspIHtcbiAgICBjb25zdCBzdHJlYW0gPSB3cml0ZXIuX293bmVyV3JpdGFibGVTdHJlYW07XG4gICAgY29uc3QgY29udHJvbGxlciA9IHN0cmVhbS5fd3JpdGFibGVTdHJlYW1Db250cm9sbGVyO1xuICAgIGNvbnN0IGNodW5rU2l6ZSA9IFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJHZXRDaHVua1NpemUoY29udHJvbGxlciwgY2h1bmspO1xuICAgIGlmIChzdHJlYW0gIT09IHdyaXRlci5fb3duZXJXcml0YWJsZVN0cmVhbSkge1xuICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChkZWZhdWx0V3JpdGVyTG9ja0V4Y2VwdGlvbignd3JpdGUgdG8nKSk7XG4gICAgfVxuICAgIGNvbnN0IHN0YXRlID0gc3RyZWFtLl9zdGF0ZTtcbiAgICBpZiAoc3RhdGUgPT09ICdlcnJvcmVkJykge1xuICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChzdHJlYW0uX3N0b3JlZEVycm9yKTtcbiAgICB9XG4gICAgaWYgKFdyaXRhYmxlU3RyZWFtQ2xvc2VRdWV1ZWRPckluRmxpZ2h0KHN0cmVhbSkgfHwgc3RhdGUgPT09ICdjbG9zZWQnKSB7XG4gICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKG5ldyBUeXBlRXJyb3IoJ1RoZSBzdHJlYW0gaXMgY2xvc2luZyBvciBjbG9zZWQgYW5kIGNhbm5vdCBiZSB3cml0dGVuIHRvJykpO1xuICAgIH1cbiAgICBpZiAoc3RhdGUgPT09ICdlcnJvcmluZycpIHtcbiAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoc3RyZWFtLl9zdG9yZWRFcnJvcik7XG4gICAgfVxuICAgIGNvbnN0IHByb21pc2UgPSBXcml0YWJsZVN0cmVhbUFkZFdyaXRlUmVxdWVzdChzdHJlYW0pO1xuICAgIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJXcml0ZShjb250cm9sbGVyLCBjaHVuaywgY2h1bmtTaXplKTtcbiAgICByZXR1cm4gcHJvbWlzZTtcbn1cbmNvbnN0IGNsb3NlU2VudGluZWwgPSB7fTtcbi8qKlxuICogQWxsb3dzIGNvbnRyb2wgb2YgYSB7QGxpbmsgV3JpdGFibGVTdHJlYW0gfCB3cml0YWJsZSBzdHJlYW19J3Mgc3RhdGUgYW5kIGludGVybmFsIHF1ZXVlLlxuICpcbiAqIEBwdWJsaWNcbiAqL1xuY2xhc3MgV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0lsbGVnYWwgY29uc3RydWN0b3InKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ2xvc2VzIHRoZSBjb250cm9sbGVkIHdyaXRhYmxlIHN0cmVhbSwgbWFraW5nIGFsbCBmdXR1cmUgaW50ZXJhY3Rpb25zIHdpdGggaXQgZmFpbCB3aXRoIHRoZSBnaXZlbiBlcnJvciBgZWAuXG4gICAgICpcbiAgICAgKiBUaGlzIG1ldGhvZCBpcyByYXJlbHkgdXNlZCwgc2luY2UgdXN1YWxseSBpdCBzdWZmaWNlcyB0byByZXR1cm4gYSByZWplY3RlZCBwcm9taXNlIGZyb20gb25lIG9mIHRoZSB1bmRlcmx5aW5nXG4gICAgICogc2luaydzIG1ldGhvZHMuIEhvd2V2ZXIsIGl0IGNhbiBiZSB1c2VmdWwgZm9yIHN1ZGRlbmx5IHNodXR0aW5nIGRvd24gYSBzdHJlYW0gaW4gcmVzcG9uc2UgdG8gYW4gZXZlbnQgb3V0c2lkZSB0aGVcbiAgICAgKiBub3JtYWwgbGlmZWN5Y2xlIG9mIGludGVyYWN0aW9ucyB3aXRoIHRoZSB1bmRlcmx5aW5nIHNpbmsuXG4gICAgICovXG4gICAgZXJyb3IoZSA9IHVuZGVmaW5lZCkge1xuICAgICAgICBpZiAoIUlzV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlcih0aGlzKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlci5wcm90b3R5cGUuZXJyb3IgY2FuIG9ubHkgYmUgdXNlZCBvbiBhIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXInKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBzdGF0ZSA9IHRoaXMuX2NvbnRyb2xsZWRXcml0YWJsZVN0cmVhbS5fc3RhdGU7XG4gICAgICAgIGlmIChzdGF0ZSAhPT0gJ3dyaXRhYmxlJykge1xuICAgICAgICAgICAgLy8gVGhlIHN0cmVhbSBpcyBjbG9zZWQsIGVycm9yZWQgb3Igd2lsbCBiZSBzb29uLiBUaGUgc2luayBjYW4ndCBkbyBhbnl0aGluZyB1c2VmdWwgaWYgaXQgZ2V0cyBhbiBlcnJvciBoZXJlLCBzb1xuICAgICAgICAgICAgLy8ganVzdCB0cmVhdCBpdCBhcyBhIG5vLW9wLlxuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG4gICAgICAgIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFcnJvcih0aGlzLCBlKTtcbiAgICB9XG4gICAgLyoqIEBpbnRlcm5hbCAqL1xuICAgIFtBYm9ydFN0ZXBzXShyZWFzb24pIHtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gdGhpcy5fYWJvcnRBbGdvcml0aG0ocmVhc29uKTtcbiAgICAgICAgV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckNsZWFyQWxnb3JpdGhtcyh0aGlzKTtcbiAgICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9XG4gICAgLyoqIEBpbnRlcm5hbCAqL1xuICAgIFtFcnJvclN0ZXBzXSgpIHtcbiAgICAgICAgUmVzZXRRdWV1ZSh0aGlzKTtcbiAgICB9XG59XG5PYmplY3QuZGVmaW5lUHJvcGVydGllcyhXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyLnByb3RvdHlwZSwge1xuICAgIGVycm9yOiB7IGVudW1lcmFibGU6IHRydWUgfVxufSk7XG5pZiAodHlwZW9mIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnID09PSAnc3ltYm9sJykge1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyLnByb3RvdHlwZSwgU3ltYm9sUG9seWZpbGwudG9TdHJpbmdUYWcsIHtcbiAgICAgICAgdmFsdWU6ICdXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyJyxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlXG4gICAgfSk7XG59XG4vLyBBYnN0cmFjdCBvcGVyYXRpb25zIGltcGxlbWVudGluZyBpbnRlcmZhY2UgcmVxdWlyZWQgYnkgdGhlIFdyaXRhYmxlU3RyZWFtLlxuZnVuY3Rpb24gSXNXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyKHgpIHtcbiAgICBpZiAoIXR5cGVJc09iamVjdCh4KSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmICghT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHgsICdfY29udHJvbGxlZFdyaXRhYmxlU3RyZWFtJykpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICByZXR1cm4gdHJ1ZTtcbn1cbmZ1bmN0aW9uIFNldFVwV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlcihzdHJlYW0sIGNvbnRyb2xsZXIsIHN0YXJ0QWxnb3JpdGhtLCB3cml0ZUFsZ29yaXRobSwgY2xvc2VBbGdvcml0aG0sIGFib3J0QWxnb3JpdGhtLCBoaWdoV2F0ZXJNYXJrLCBzaXplQWxnb3JpdGhtKSB7XG4gICAgY29udHJvbGxlci5fY29udHJvbGxlZFdyaXRhYmxlU3RyZWFtID0gc3RyZWFtO1xuICAgIHN0cmVhbS5fd3JpdGFibGVTdHJlYW1Db250cm9sbGVyID0gY29udHJvbGxlcjtcbiAgICAvLyBOZWVkIHRvIHNldCB0aGUgc2xvdHMgc28gdGhhdCB0aGUgYXNzZXJ0IGRvZXNuJ3QgZmlyZS4gSW4gdGhlIHNwZWMgdGhlIHNsb3RzIGFscmVhZHkgZXhpc3QgaW1wbGljaXRseS5cbiAgICBjb250cm9sbGVyLl9xdWV1ZSA9IHVuZGVmaW5lZDtcbiAgICBjb250cm9sbGVyLl9xdWV1ZVRvdGFsU2l6ZSA9IHVuZGVmaW5lZDtcbiAgICBSZXNldFF1ZXVlKGNvbnRyb2xsZXIpO1xuICAgIGNvbnRyb2xsZXIuX3N0YXJ0ZWQgPSBmYWxzZTtcbiAgICBjb250cm9sbGVyLl9zdHJhdGVneVNpemVBbGdvcml0aG0gPSBzaXplQWxnb3JpdGhtO1xuICAgIGNvbnRyb2xsZXIuX3N0cmF0ZWd5SFdNID0gaGlnaFdhdGVyTWFyaztcbiAgICBjb250cm9sbGVyLl93cml0ZUFsZ29yaXRobSA9IHdyaXRlQWxnb3JpdGhtO1xuICAgIGNvbnRyb2xsZXIuX2Nsb3NlQWxnb3JpdGhtID0gY2xvc2VBbGdvcml0aG07XG4gICAgY29udHJvbGxlci5fYWJvcnRBbGdvcml0aG0gPSBhYm9ydEFsZ29yaXRobTtcbiAgICBjb25zdCBiYWNrcHJlc3N1cmUgPSBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyR2V0QmFja3ByZXNzdXJlKGNvbnRyb2xsZXIpO1xuICAgIFdyaXRhYmxlU3RyZWFtVXBkYXRlQmFja3ByZXNzdXJlKHN0cmVhbSwgYmFja3ByZXNzdXJlKTtcbiAgICBjb25zdCBzdGFydFJlc3VsdCA9IHN0YXJ0QWxnb3JpdGhtKCk7XG4gICAgY29uc3Qgc3RhcnRQcm9taXNlID0gcHJvbWlzZVJlc29sdmVkV2l0aChzdGFydFJlc3VsdCk7XG4gICAgdXBvblByb21pc2Uoc3RhcnRQcm9taXNlLCAoKSA9PiB7XG4gICAgICAgIGNvbnRyb2xsZXIuX3N0YXJ0ZWQgPSB0cnVlO1xuICAgICAgICBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQWR2YW5jZVF1ZXVlSWZOZWVkZWQoY29udHJvbGxlcik7XG4gICAgfSwgciA9PiB7XG4gICAgICAgIGNvbnRyb2xsZXIuX3N0YXJ0ZWQgPSB0cnVlO1xuICAgICAgICBXcml0YWJsZVN0cmVhbURlYWxXaXRoUmVqZWN0aW9uKHN0cmVhbSwgcik7XG4gICAgfSk7XG59XG5mdW5jdGlvbiBTZXRVcFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJGcm9tVW5kZXJseWluZ1Npbmsoc3RyZWFtLCB1bmRlcmx5aW5nU2luaywgaGlnaFdhdGVyTWFyaywgc2l6ZUFsZ29yaXRobSkge1xuICAgIGNvbnN0IGNvbnRyb2xsZXIgPSBPYmplY3QuY3JlYXRlKFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIucHJvdG90eXBlKTtcbiAgICBsZXQgc3RhcnRBbGdvcml0aG0gPSAoKSA9PiB1bmRlZmluZWQ7XG4gICAgbGV0IHdyaXRlQWxnb3JpdGhtID0gKCkgPT4gcHJvbWlzZVJlc29sdmVkV2l0aCh1bmRlZmluZWQpO1xuICAgIGxldCBjbG9zZUFsZ29yaXRobSA9ICgpID0+IHByb21pc2VSZXNvbHZlZFdpdGgodW5kZWZpbmVkKTtcbiAgICBsZXQgYWJvcnRBbGdvcml0aG0gPSAoKSA9PiBwcm9taXNlUmVzb2x2ZWRXaXRoKHVuZGVmaW5lZCk7XG4gICAgaWYgKHVuZGVybHlpbmdTaW5rLnN0YXJ0ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgc3RhcnRBbGdvcml0aG0gPSAoKSA9PiB1bmRlcmx5aW5nU2luay5zdGFydChjb250cm9sbGVyKTtcbiAgICB9XG4gICAgaWYgKHVuZGVybHlpbmdTaW5rLndyaXRlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgd3JpdGVBbGdvcml0aG0gPSBjaHVuayA9PiB1bmRlcmx5aW5nU2luay53cml0ZShjaHVuaywgY29udHJvbGxlcik7XG4gICAgfVxuICAgIGlmICh1bmRlcmx5aW5nU2luay5jbG9zZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGNsb3NlQWxnb3JpdGhtID0gKCkgPT4gdW5kZXJseWluZ1NpbmsuY2xvc2UoKTtcbiAgICB9XG4gICAgaWYgKHVuZGVybHlpbmdTaW5rLmFib3J0ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgYWJvcnRBbGdvcml0aG0gPSByZWFzb24gPT4gdW5kZXJseWluZ1NpbmsuYWJvcnQocmVhc29uKTtcbiAgICB9XG4gICAgU2V0VXBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyKHN0cmVhbSwgY29udHJvbGxlciwgc3RhcnRBbGdvcml0aG0sIHdyaXRlQWxnb3JpdGhtLCBjbG9zZUFsZ29yaXRobSwgYWJvcnRBbGdvcml0aG0sIGhpZ2hXYXRlck1hcmssIHNpemVBbGdvcml0aG0pO1xufVxuLy8gQ2xlYXJBbGdvcml0aG1zIG1heSBiZSBjYWxsZWQgdHdpY2UuIEVycm9yaW5nIHRoZSBzYW1lIHN0cmVhbSBpbiBtdWx0aXBsZSB3YXlzIHdpbGwgb2Z0ZW4gcmVzdWx0IGluIHJlZHVuZGFudCBjYWxscy5cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbGVhckFsZ29yaXRobXMoY29udHJvbGxlcikge1xuICAgIGNvbnRyb2xsZXIuX3dyaXRlQWxnb3JpdGhtID0gdW5kZWZpbmVkO1xuICAgIGNvbnRyb2xsZXIuX2Nsb3NlQWxnb3JpdGhtID0gdW5kZWZpbmVkO1xuICAgIGNvbnRyb2xsZXIuX2Fib3J0QWxnb3JpdGhtID0gdW5kZWZpbmVkO1xuICAgIGNvbnRyb2xsZXIuX3N0cmF0ZWd5U2l6ZUFsZ29yaXRobSA9IHVuZGVmaW5lZDtcbn1cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbG9zZShjb250cm9sbGVyKSB7XG4gICAgRW5xdWV1ZVZhbHVlV2l0aFNpemUoY29udHJvbGxlciwgY2xvc2VTZW50aW5lbCwgMCk7XG4gICAgV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckFkdmFuY2VRdWV1ZUlmTmVlZGVkKGNvbnRyb2xsZXIpO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckdldENodW5rU2l6ZShjb250cm9sbGVyLCBjaHVuaykge1xuICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBjb250cm9sbGVyLl9zdHJhdGVneVNpemVBbGdvcml0aG0oY2h1bmspO1xuICAgIH1cbiAgICBjYXRjaCAoY2h1bmtTaXplRSkge1xuICAgICAgICBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRXJyb3JJZk5lZWRlZChjb250cm9sbGVyLCBjaHVua1NpemVFKTtcbiAgICAgICAgcmV0dXJuIDE7XG4gICAgfVxufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckdldERlc2lyZWRTaXplKGNvbnRyb2xsZXIpIHtcbiAgICByZXR1cm4gY29udHJvbGxlci5fc3RyYXRlZ3lIV00gLSBjb250cm9sbGVyLl9xdWV1ZVRvdGFsU2l6ZTtcbn1cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJXcml0ZShjb250cm9sbGVyLCBjaHVuaywgY2h1bmtTaXplKSB7XG4gICAgdHJ5IHtcbiAgICAgICAgRW5xdWV1ZVZhbHVlV2l0aFNpemUoY29udHJvbGxlciwgY2h1bmssIGNodW5rU2l6ZSk7XG4gICAgfVxuICAgIGNhdGNoIChlbnF1ZXVlRSkge1xuICAgICAgICBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRXJyb3JJZk5lZWRlZChjb250cm9sbGVyLCBlbnF1ZXVlRSk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3Qgc3RyZWFtID0gY29udHJvbGxlci5fY29udHJvbGxlZFdyaXRhYmxlU3RyZWFtO1xuICAgIGlmICghV3JpdGFibGVTdHJlYW1DbG9zZVF1ZXVlZE9ySW5GbGlnaHQoc3RyZWFtKSAmJiBzdHJlYW0uX3N0YXRlID09PSAnd3JpdGFibGUnKSB7XG4gICAgICAgIGNvbnN0IGJhY2twcmVzc3VyZSA9IFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJHZXRCYWNrcHJlc3N1cmUoY29udHJvbGxlcik7XG4gICAgICAgIFdyaXRhYmxlU3RyZWFtVXBkYXRlQmFja3ByZXNzdXJlKHN0cmVhbSwgYmFja3ByZXNzdXJlKTtcbiAgICB9XG4gICAgV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckFkdmFuY2VRdWV1ZUlmTmVlZGVkKGNvbnRyb2xsZXIpO1xufVxuLy8gQWJzdHJhY3Qgb3BlcmF0aW9ucyBmb3IgdGhlIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIuXG5mdW5jdGlvbiBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQWR2YW5jZVF1ZXVlSWZOZWVkZWQoY29udHJvbGxlcikge1xuICAgIGNvbnN0IHN0cmVhbSA9IGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRXcml0YWJsZVN0cmVhbTtcbiAgICBpZiAoIWNvbnRyb2xsZXIuX3N0YXJ0ZWQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAoc3RyZWFtLl9pbkZsaWdodFdyaXRlUmVxdWVzdCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY29uc3Qgc3RhdGUgPSBzdHJlYW0uX3N0YXRlO1xuICAgIGlmIChzdGF0ZSA9PT0gJ2Vycm9yaW5nJykge1xuICAgICAgICBXcml0YWJsZVN0cmVhbUZpbmlzaEVycm9yaW5nKHN0cmVhbSk7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGNvbnRyb2xsZXIuX3F1ZXVlLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHZhbHVlID0gUGVla1F1ZXVlVmFsdWUoY29udHJvbGxlcik7XG4gICAgaWYgKHZhbHVlID09PSBjbG9zZVNlbnRpbmVsKSB7XG4gICAgICAgIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJQcm9jZXNzQ2xvc2UoY29udHJvbGxlcik7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyUHJvY2Vzc1dyaXRlKGNvbnRyb2xsZXIsIHZhbHVlKTtcbiAgICB9XG59XG5mdW5jdGlvbiBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRXJyb3JJZk5lZWRlZChjb250cm9sbGVyLCBlcnJvcikge1xuICAgIGlmIChjb250cm9sbGVyLl9jb250cm9sbGVkV3JpdGFibGVTdHJlYW0uX3N0YXRlID09PSAnd3JpdGFibGUnKSB7XG4gICAgICAgIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFcnJvcihjb250cm9sbGVyLCBlcnJvcik7XG4gICAgfVxufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlclByb2Nlc3NDbG9zZShjb250cm9sbGVyKSB7XG4gICAgY29uc3Qgc3RyZWFtID0gY29udHJvbGxlci5fY29udHJvbGxlZFdyaXRhYmxlU3RyZWFtO1xuICAgIFdyaXRhYmxlU3RyZWFtTWFya0Nsb3NlUmVxdWVzdEluRmxpZ2h0KHN0cmVhbSk7XG4gICAgRGVxdWV1ZVZhbHVlKGNvbnRyb2xsZXIpO1xuICAgIGNvbnN0IHNpbmtDbG9zZVByb21pc2UgPSBjb250cm9sbGVyLl9jbG9zZUFsZ29yaXRobSgpO1xuICAgIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbGVhckFsZ29yaXRobXMoY29udHJvbGxlcik7XG4gICAgdXBvblByb21pc2Uoc2lua0Nsb3NlUHJvbWlzZSwgKCkgPT4ge1xuICAgICAgICBXcml0YWJsZVN0cmVhbUZpbmlzaEluRmxpZ2h0Q2xvc2Uoc3RyZWFtKTtcbiAgICB9LCByZWFzb24gPT4ge1xuICAgICAgICBXcml0YWJsZVN0cmVhbUZpbmlzaEluRmxpZ2h0Q2xvc2VXaXRoRXJyb3Ioc3RyZWFtLCByZWFzb24pO1xuICAgIH0pO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlclByb2Nlc3NXcml0ZShjb250cm9sbGVyLCBjaHVuaykge1xuICAgIGNvbnN0IHN0cmVhbSA9IGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRXcml0YWJsZVN0cmVhbTtcbiAgICBXcml0YWJsZVN0cmVhbU1hcmtGaXJzdFdyaXRlUmVxdWVzdEluRmxpZ2h0KHN0cmVhbSk7XG4gICAgY29uc3Qgc2lua1dyaXRlUHJvbWlzZSA9IGNvbnRyb2xsZXIuX3dyaXRlQWxnb3JpdGhtKGNodW5rKTtcbiAgICB1cG9uUHJvbWlzZShzaW5rV3JpdGVQcm9taXNlLCAoKSA9PiB7XG4gICAgICAgIFdyaXRhYmxlU3RyZWFtRmluaXNoSW5GbGlnaHRXcml0ZShzdHJlYW0pO1xuICAgICAgICBjb25zdCBzdGF0ZSA9IHN0cmVhbS5fc3RhdGU7XG4gICAgICAgIERlcXVldWVWYWx1ZShjb250cm9sbGVyKTtcbiAgICAgICAgaWYgKCFXcml0YWJsZVN0cmVhbUNsb3NlUXVldWVkT3JJbkZsaWdodChzdHJlYW0pICYmIHN0YXRlID09PSAnd3JpdGFibGUnKSB7XG4gICAgICAgICAgICBjb25zdCBiYWNrcHJlc3N1cmUgPSBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyR2V0QmFja3ByZXNzdXJlKGNvbnRyb2xsZXIpO1xuICAgICAgICAgICAgV3JpdGFibGVTdHJlYW1VcGRhdGVCYWNrcHJlc3N1cmUoc3RyZWFtLCBiYWNrcHJlc3N1cmUpO1xuICAgICAgICB9XG4gICAgICAgIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJBZHZhbmNlUXVldWVJZk5lZWRlZChjb250cm9sbGVyKTtcbiAgICB9LCByZWFzb24gPT4ge1xuICAgICAgICBpZiAoc3RyZWFtLl9zdGF0ZSA9PT0gJ3dyaXRhYmxlJykge1xuICAgICAgICAgICAgV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckNsZWFyQWxnb3JpdGhtcyhjb250cm9sbGVyKTtcbiAgICAgICAgfVxuICAgICAgICBXcml0YWJsZVN0cmVhbUZpbmlzaEluRmxpZ2h0V3JpdGVXaXRoRXJyb3Ioc3RyZWFtLCByZWFzb24pO1xuICAgIH0pO1xufVxuZnVuY3Rpb24gV3JpdGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckdldEJhY2twcmVzc3VyZShjb250cm9sbGVyKSB7XG4gICAgY29uc3QgZGVzaXJlZFNpemUgPSBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyR2V0RGVzaXJlZFNpemUoY29udHJvbGxlcik7XG4gICAgcmV0dXJuIGRlc2lyZWRTaXplIDw9IDA7XG59XG4vLyBBIGNsaWVudCBvZiBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyIG1heSB1c2UgdGhlc2UgZnVuY3Rpb25zIGRpcmVjdGx5IHRvIGJ5cGFzcyBzdGF0ZSBjaGVjay5cbmZ1bmN0aW9uIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFcnJvcihjb250cm9sbGVyLCBlcnJvcikge1xuICAgIGNvbnN0IHN0cmVhbSA9IGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRXcml0YWJsZVN0cmVhbTtcbiAgICBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2xlYXJBbGdvcml0aG1zKGNvbnRyb2xsZXIpO1xuICAgIFdyaXRhYmxlU3RyZWFtU3RhcnRFcnJvcmluZyhzdHJlYW0sIGVycm9yKTtcbn1cbi8vIEhlbHBlciBmdW5jdGlvbnMgZm9yIHRoZSBXcml0YWJsZVN0cmVhbS5cbmZ1bmN0aW9uIHN0cmVhbUJyYW5kQ2hlY2tFeGNlcHRpb24obmFtZSkge1xuICAgIHJldHVybiBuZXcgVHlwZUVycm9yKGBXcml0YWJsZVN0cmVhbS5wcm90b3R5cGUuJHtuYW1lfSBjYW4gb25seSBiZSB1c2VkIG9uIGEgV3JpdGFibGVTdHJlYW1gKTtcbn1cbi8vIEhlbHBlciBmdW5jdGlvbnMgZm9yIHRoZSBXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXIuXG5mdW5jdGlvbiBkZWZhdWx0V3JpdGVyQnJhbmRDaGVja0V4Y2VwdGlvbihuYW1lKSB7XG4gICAgcmV0dXJuIG5ldyBUeXBlRXJyb3IoYFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlci5wcm90b3R5cGUuJHtuYW1lfSBjYW4gb25seSBiZSB1c2VkIG9uIGEgV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyYCk7XG59XG5mdW5jdGlvbiBkZWZhdWx0V3JpdGVyTG9ja0V4Y2VwdGlvbihuYW1lKSB7XG4gICAgcmV0dXJuIG5ldyBUeXBlRXJyb3IoJ0Nhbm5vdCAnICsgbmFtZSArICcgYSBzdHJlYW0gdXNpbmcgYSByZWxlYXNlZCB3cml0ZXInKTtcbn1cbmZ1bmN0aW9uIGRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZSh3cml0ZXIpIHtcbiAgICB3cml0ZXIuX2Nsb3NlZFByb21pc2UgPSBuZXdQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgd3JpdGVyLl9jbG9zZWRQcm9taXNlX3Jlc29sdmUgPSByZXNvbHZlO1xuICAgICAgICB3cml0ZXIuX2Nsb3NlZFByb21pc2VfcmVqZWN0ID0gcmVqZWN0O1xuICAgICAgICB3cml0ZXIuX2Nsb3NlZFByb21pc2VTdGF0ZSA9ICdwZW5kaW5nJztcbiAgICB9KTtcbn1cbmZ1bmN0aW9uIGRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZUFzUmVqZWN0ZWQod3JpdGVyLCByZWFzb24pIHtcbiAgICBkZWZhdWx0V3JpdGVyQ2xvc2VkUHJvbWlzZUluaXRpYWxpemUod3JpdGVyKTtcbiAgICBkZWZhdWx0V3JpdGVyQ2xvc2VkUHJvbWlzZVJlamVjdCh3cml0ZXIsIHJlYXNvbik7XG59XG5mdW5jdGlvbiBkZWZhdWx0V3JpdGVyQ2xvc2VkUHJvbWlzZUluaXRpYWxpemVBc1Jlc29sdmVkKHdyaXRlcikge1xuICAgIGRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlSW5pdGlhbGl6ZSh3cml0ZXIpO1xuICAgIGRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlUmVzb2x2ZSh3cml0ZXIpO1xufVxuZnVuY3Rpb24gZGVmYXVsdFdyaXRlckNsb3NlZFByb21pc2VSZWplY3Qod3JpdGVyLCByZWFzb24pIHtcbiAgICBpZiAod3JpdGVyLl9jbG9zZWRQcm9taXNlX3JlamVjdCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgc2V0UHJvbWlzZUlzSGFuZGxlZFRvVHJ1ZSh3cml0ZXIuX2Nsb3NlZFByb21pc2UpO1xuICAgIHdyaXRlci5fY2xvc2VkUHJvbWlzZV9yZWplY3QocmVhc29uKTtcbiAgICB3cml0ZXIuX2Nsb3NlZFByb21pc2VfcmVzb2x2ZSA9IHVuZGVmaW5lZDtcbiAgICB3cml0ZXIuX2Nsb3NlZFByb21pc2VfcmVqZWN0ID0gdW5kZWZpbmVkO1xuICAgIHdyaXRlci5fY2xvc2VkUHJvbWlzZVN0YXRlID0gJ3JlamVjdGVkJztcbn1cbmZ1bmN0aW9uIGRlZmF1bHRXcml0ZXJDbG9zZWRQcm9taXNlUmVzZXRUb1JlamVjdGVkKHdyaXRlciwgcmVhc29uKSB7XG4gICAgZGVmYXVsdFdyaXRlckNsb3NlZFByb21pc2VJbml0aWFsaXplQXNSZWplY3RlZCh3cml0ZXIsIHJlYXNvbik7XG59XG5mdW5jdGlvbiBkZWZhdWx0V3JpdGVyQ2xvc2VkUHJvbWlzZVJlc29sdmUod3JpdGVyKSB7XG4gICAgaWYgKHdyaXRlci5fY2xvc2VkUHJvbWlzZV9yZXNvbHZlID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB3cml0ZXIuX2Nsb3NlZFByb21pc2VfcmVzb2x2ZSh1bmRlZmluZWQpO1xuICAgIHdyaXRlci5fY2xvc2VkUHJvbWlzZV9yZXNvbHZlID0gdW5kZWZpbmVkO1xuICAgIHdyaXRlci5fY2xvc2VkUHJvbWlzZV9yZWplY3QgPSB1bmRlZmluZWQ7XG4gICAgd3JpdGVyLl9jbG9zZWRQcm9taXNlU3RhdGUgPSAncmVzb2x2ZWQnO1xufVxuZnVuY3Rpb24gZGVmYXVsdFdyaXRlclJlYWR5UHJvbWlzZUluaXRpYWxpemUod3JpdGVyKSB7XG4gICAgd3JpdGVyLl9yZWFkeVByb21pc2UgPSBuZXdQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgd3JpdGVyLl9yZWFkeVByb21pc2VfcmVzb2x2ZSA9IHJlc29sdmU7XG4gICAgICAgIHdyaXRlci5fcmVhZHlQcm9taXNlX3JlamVjdCA9IHJlamVjdDtcbiAgICB9KTtcbiAgICB3cml0ZXIuX3JlYWR5UHJvbWlzZVN0YXRlID0gJ3BlbmRpbmcnO1xufVxuZnVuY3Rpb24gZGVmYXVsdFdyaXRlclJlYWR5UHJvbWlzZUluaXRpYWxpemVBc1JlamVjdGVkKHdyaXRlciwgcmVhc29uKSB7XG4gICAgZGVmYXVsdFdyaXRlclJlYWR5UHJvbWlzZUluaXRpYWxpemUod3JpdGVyKTtcbiAgICBkZWZhdWx0V3JpdGVyUmVhZHlQcm9taXNlUmVqZWN0KHdyaXRlciwgcmVhc29uKTtcbn1cbmZ1bmN0aW9uIGRlZmF1bHRXcml0ZXJSZWFkeVByb21pc2VJbml0aWFsaXplQXNSZXNvbHZlZCh3cml0ZXIpIHtcbiAgICBkZWZhdWx0V3JpdGVyUmVhZHlQcm9taXNlSW5pdGlhbGl6ZSh3cml0ZXIpO1xuICAgIGRlZmF1bHRXcml0ZXJSZWFkeVByb21pc2VSZXNvbHZlKHdyaXRlcik7XG59XG5mdW5jdGlvbiBkZWZhdWx0V3JpdGVyUmVhZHlQcm9taXNlUmVqZWN0KHdyaXRlciwgcmVhc29uKSB7XG4gICAgaWYgKHdyaXRlci5fcmVhZHlQcm9taXNlX3JlamVjdCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgc2V0UHJvbWlzZUlzSGFuZGxlZFRvVHJ1ZSh3cml0ZXIuX3JlYWR5UHJvbWlzZSk7XG4gICAgd3JpdGVyLl9yZWFkeVByb21pc2VfcmVqZWN0KHJlYXNvbik7XG4gICAgd3JpdGVyLl9yZWFkeVByb21pc2VfcmVzb2x2ZSA9IHVuZGVmaW5lZDtcbiAgICB3cml0ZXIuX3JlYWR5UHJvbWlzZV9yZWplY3QgPSB1bmRlZmluZWQ7XG4gICAgd3JpdGVyLl9yZWFkeVByb21pc2VTdGF0ZSA9ICdyZWplY3RlZCc7XG59XG5mdW5jdGlvbiBkZWZhdWx0V3JpdGVyUmVhZHlQcm9taXNlUmVzZXQod3JpdGVyKSB7XG4gICAgZGVmYXVsdFdyaXRlclJlYWR5UHJvbWlzZUluaXRpYWxpemUod3JpdGVyKTtcbn1cbmZ1bmN0aW9uIGRlZmF1bHRXcml0ZXJSZWFkeVByb21pc2VSZXNldFRvUmVqZWN0ZWQod3JpdGVyLCByZWFzb24pIHtcbiAgICBkZWZhdWx0V3JpdGVyUmVhZHlQcm9taXNlSW5pdGlhbGl6ZUFzUmVqZWN0ZWQod3JpdGVyLCByZWFzb24pO1xufVxuZnVuY3Rpb24gZGVmYXVsdFdyaXRlclJlYWR5UHJvbWlzZVJlc29sdmUod3JpdGVyKSB7XG4gICAgaWYgKHdyaXRlci5fcmVhZHlQcm9taXNlX3Jlc29sdmUgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIHdyaXRlci5fcmVhZHlQcm9taXNlX3Jlc29sdmUodW5kZWZpbmVkKTtcbiAgICB3cml0ZXIuX3JlYWR5UHJvbWlzZV9yZXNvbHZlID0gdW5kZWZpbmVkO1xuICAgIHdyaXRlci5fcmVhZHlQcm9taXNlX3JlamVjdCA9IHVuZGVmaW5lZDtcbiAgICB3cml0ZXIuX3JlYWR5UHJvbWlzZVN0YXRlID0gJ2Z1bGZpbGxlZCc7XG59XG5cbmZ1bmN0aW9uIGlzQWJvcnRTaWduYWwodmFsdWUpIHtcbiAgICBpZiAodHlwZW9mIHZhbHVlICE9PSAnb2JqZWN0JyB8fCB2YWx1ZSA9PT0gbnVsbCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHRyeSB7XG4gICAgICAgIHJldHVybiB0eXBlb2YgdmFsdWUuYWJvcnRlZCA9PT0gJ2Jvb2xlYW4nO1xuICAgIH1cbiAgICBjYXRjaCAoX2EpIHtcbiAgICAgICAgLy8gQWJvcnRTaWduYWwucHJvdG90eXBlLmFib3J0ZWQgdGhyb3dzIGlmIGl0cyBicmFuZCBjaGVjayBmYWlsc1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxufVxuXG4vLy8gPHJlZmVyZW5jZSBsaWI9XCJkb21cIiAvPlxuY29uc3QgTmF0aXZlRE9NRXhjZXB0aW9uID0gdHlwZW9mIERPTUV4Y2VwdGlvbiAhPT0gJ3VuZGVmaW5lZCcgPyBET01FeGNlcHRpb24gOiB1bmRlZmluZWQ7XG5cbi8vLyA8cmVmZXJlbmNlIHR5cGVzPVwibm9kZVwiIC8+XG5mdW5jdGlvbiBpc0RPTUV4Y2VwdGlvbkNvbnN0cnVjdG9yKGN0b3IpIHtcbiAgICBpZiAoISh0eXBlb2YgY3RvciA9PT0gJ2Z1bmN0aW9uJyB8fCB0eXBlb2YgY3RvciA9PT0gJ29iamVjdCcpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgdHJ5IHtcbiAgICAgICAgbmV3IGN0b3IoKTtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIGNhdGNoIChfYSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxufVxuZnVuY3Rpb24gY3JlYXRlRE9NRXhjZXB0aW9uUG9seWZpbGwoKSB7XG4gICAgY29uc3QgY3RvciA9IGZ1bmN0aW9uIERPTUV4Y2VwdGlvbihtZXNzYWdlLCBuYW1lKSB7XG4gICAgICAgIHRoaXMubWVzc2FnZSA9IG1lc3NhZ2UgfHwgJyc7XG4gICAgICAgIHRoaXMubmFtZSA9IG5hbWUgfHwgJ0Vycm9yJztcbiAgICAgICAgaWYgKEVycm9yLmNhcHR1cmVTdGFja1RyYWNlKSB7XG4gICAgICAgICAgICBFcnJvci5jYXB0dXJlU3RhY2tUcmFjZSh0aGlzLCB0aGlzLmNvbnN0cnVjdG9yKTtcbiAgICAgICAgfVxuICAgIH07XG4gICAgY3Rvci5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKEVycm9yLnByb3RvdHlwZSk7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KGN0b3IucHJvdG90eXBlLCAnY29uc3RydWN0b3InLCB7IHZhbHVlOiBjdG9yLCB3cml0YWJsZTogdHJ1ZSwgY29uZmlndXJhYmxlOiB0cnVlIH0pO1xuICAgIHJldHVybiBjdG9yO1xufVxuY29uc3QgRE9NRXhjZXB0aW9uJDEgPSBpc0RPTUV4Y2VwdGlvbkNvbnN0cnVjdG9yKE5hdGl2ZURPTUV4Y2VwdGlvbikgPyBOYXRpdmVET01FeGNlcHRpb24gOiBjcmVhdGVET01FeGNlcHRpb25Qb2x5ZmlsbCgpO1xuXG5mdW5jdGlvbiBSZWFkYWJsZVN0cmVhbVBpcGVUbyhzb3VyY2UsIGRlc3QsIHByZXZlbnRDbG9zZSwgcHJldmVudEFib3J0LCBwcmV2ZW50Q2FuY2VsLCBzaWduYWwpIHtcbiAgICBjb25zdCByZWFkZXIgPSBBY3F1aXJlUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyKHNvdXJjZSk7XG4gICAgY29uc3Qgd3JpdGVyID0gQWNxdWlyZVdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlcihkZXN0KTtcbiAgICBzb3VyY2UuX2Rpc3R1cmJlZCA9IHRydWU7XG4gICAgbGV0IHNodXR0aW5nRG93biA9IGZhbHNlO1xuICAgIC8vIFRoaXMgaXMgdXNlZCB0byBrZWVwIHRyYWNrIG9mIHRoZSBzcGVjJ3MgcmVxdWlyZW1lbnQgdGhhdCB3ZSB3YWl0IGZvciBvbmdvaW5nIHdyaXRlcyBkdXJpbmcgc2h1dGRvd24uXG4gICAgbGV0IGN1cnJlbnRXcml0ZSA9IHByb21pc2VSZXNvbHZlZFdpdGgodW5kZWZpbmVkKTtcbiAgICByZXR1cm4gbmV3UHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICAgIGxldCBhYm9ydEFsZ29yaXRobTtcbiAgICAgICAgaWYgKHNpZ25hbCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBhYm9ydEFsZ29yaXRobSA9ICgpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCBlcnJvciA9IG5ldyBET01FeGNlcHRpb24kMSgnQWJvcnRlZCcsICdBYm9ydEVycm9yJyk7XG4gICAgICAgICAgICAgICAgY29uc3QgYWN0aW9ucyA9IFtdO1xuICAgICAgICAgICAgICAgIGlmICghcHJldmVudEFib3J0KSB7XG4gICAgICAgICAgICAgICAgICAgIGFjdGlvbnMucHVzaCgoKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoZGVzdC5fc3RhdGUgPT09ICd3cml0YWJsZScpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gV3JpdGFibGVTdHJlYW1BYm9ydChkZXN0LCBlcnJvcik7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlc29sdmVkV2l0aCh1bmRlZmluZWQpO1xuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgaWYgKCFwcmV2ZW50Q2FuY2VsKSB7XG4gICAgICAgICAgICAgICAgICAgIGFjdGlvbnMucHVzaCgoKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoc291cmNlLl9zdGF0ZSA9PT0gJ3JlYWRhYmxlJykge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBSZWFkYWJsZVN0cmVhbUNhbmNlbChzb3VyY2UsIGVycm9yKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVzb2x2ZWRXaXRoKHVuZGVmaW5lZCk7XG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBzaHV0ZG93bldpdGhBY3Rpb24oKCkgPT4gUHJvbWlzZS5hbGwoYWN0aW9ucy5tYXAoYWN0aW9uID0+IGFjdGlvbigpKSksIHRydWUsIGVycm9yKTtcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBpZiAoc2lnbmFsLmFib3J0ZWQpIHtcbiAgICAgICAgICAgICAgICBhYm9ydEFsZ29yaXRobSgpO1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHNpZ25hbC5hZGRFdmVudExpc3RlbmVyKCdhYm9ydCcsIGFib3J0QWxnb3JpdGhtKTtcbiAgICAgICAgfVxuICAgICAgICAvLyBVc2luZyByZWFkZXIgYW5kIHdyaXRlciwgcmVhZCBhbGwgY2h1bmtzIGZyb20gdGhpcyBhbmQgd3JpdGUgdGhlbSB0byBkZXN0XG4gICAgICAgIC8vIC0gQmFja3ByZXNzdXJlIG11c3QgYmUgZW5mb3JjZWRcbiAgICAgICAgLy8gLSBTaHV0ZG93biBtdXN0IHN0b3AgYWxsIGFjdGl2aXR5XG4gICAgICAgIGZ1bmN0aW9uIHBpcGVMb29wKCkge1xuICAgICAgICAgICAgcmV0dXJuIG5ld1Byb21pc2UoKHJlc29sdmVMb29wLCByZWplY3RMb29wKSA9PiB7XG4gICAgICAgICAgICAgICAgZnVuY3Rpb24gbmV4dChkb25lKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChkb25lKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXNvbHZlTG9vcCgpO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gVXNlIGBQZXJmb3JtUHJvbWlzZVRoZW5gIGluc3RlYWQgb2YgYHVwb25Qcm9taXNlYCB0byBhdm9pZFxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gYWRkaW5nIHVubmVjZXNzYXJ5IGAuY2F0Y2gocmV0aHJvd0Fzc2VydGlvbkVycm9yUmVqZWN0aW9uKWAgaGFuZGxlcnNcbiAgICAgICAgICAgICAgICAgICAgICAgIFBlcmZvcm1Qcm9taXNlVGhlbihwaXBlU3RlcCgpLCBuZXh0LCByZWplY3RMb29wKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBuZXh0KGZhbHNlKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIGZ1bmN0aW9uIHBpcGVTdGVwKCkge1xuICAgICAgICAgICAgaWYgKHNodXR0aW5nRG93bikge1xuICAgICAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVzb2x2ZWRXaXRoKHRydWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIFBlcmZvcm1Qcm9taXNlVGhlbih3cml0ZXIuX3JlYWR5UHJvbWlzZSwgKCkgPT4ge1xuICAgICAgICAgICAgICAgIHJldHVybiBuZXdQcm9taXNlKChyZXNvbHZlUmVhZCwgcmVqZWN0UmVhZCkgPT4ge1xuICAgICAgICAgICAgICAgICAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRSZWFkZXJSZWFkKHJlYWRlciwge1xuICAgICAgICAgICAgICAgICAgICAgICAgX2NodW5rU3RlcHM6IGNodW5rID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjdXJyZW50V3JpdGUgPSBQZXJmb3JtUHJvbWlzZVRoZW4oV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyV3JpdGUod3JpdGVyLCBjaHVuayksIHVuZGVmaW5lZCwgbm9vcCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzb2x2ZVJlYWQoZmFsc2UpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgIF9jbG9zZVN0ZXBzOiAoKSA9PiByZXNvbHZlUmVhZCh0cnVlKSxcbiAgICAgICAgICAgICAgICAgICAgICAgIF9lcnJvclN0ZXBzOiByZWplY3RSZWFkXG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gRXJyb3JzIG11c3QgYmUgcHJvcGFnYXRlZCBmb3J3YXJkXG4gICAgICAgIGlzT3JCZWNvbWVzRXJyb3JlZChzb3VyY2UsIHJlYWRlci5fY2xvc2VkUHJvbWlzZSwgc3RvcmVkRXJyb3IgPT4ge1xuICAgICAgICAgICAgaWYgKCFwcmV2ZW50QWJvcnQpIHtcbiAgICAgICAgICAgICAgICBzaHV0ZG93bldpdGhBY3Rpb24oKCkgPT4gV3JpdGFibGVTdHJlYW1BYm9ydChkZXN0LCBzdG9yZWRFcnJvciksIHRydWUsIHN0b3JlZEVycm9yKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHNodXRkb3duKHRydWUsIHN0b3JlZEVycm9yKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICAgIC8vIEVycm9ycyBtdXN0IGJlIHByb3BhZ2F0ZWQgYmFja3dhcmRcbiAgICAgICAgaXNPckJlY29tZXNFcnJvcmVkKGRlc3QsIHdyaXRlci5fY2xvc2VkUHJvbWlzZSwgc3RvcmVkRXJyb3IgPT4ge1xuICAgICAgICAgICAgaWYgKCFwcmV2ZW50Q2FuY2VsKSB7XG4gICAgICAgICAgICAgICAgc2h1dGRvd25XaXRoQWN0aW9uKCgpID0+IFJlYWRhYmxlU3RyZWFtQ2FuY2VsKHNvdXJjZSwgc3RvcmVkRXJyb3IpLCB0cnVlLCBzdG9yZWRFcnJvcik7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICBzaHV0ZG93bih0cnVlLCBzdG9yZWRFcnJvcik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgICAgICAvLyBDbG9zaW5nIG11c3QgYmUgcHJvcGFnYXRlZCBmb3J3YXJkXG4gICAgICAgIGlzT3JCZWNvbWVzQ2xvc2VkKHNvdXJjZSwgcmVhZGVyLl9jbG9zZWRQcm9taXNlLCAoKSA9PiB7XG4gICAgICAgICAgICBpZiAoIXByZXZlbnRDbG9zZSkge1xuICAgICAgICAgICAgICAgIHNodXRkb3duV2l0aEFjdGlvbigoKSA9PiBXcml0YWJsZVN0cmVhbURlZmF1bHRXcml0ZXJDbG9zZVdpdGhFcnJvclByb3BhZ2F0aW9uKHdyaXRlcikpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgc2h1dGRvd24oKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgICAgIC8vIENsb3NpbmcgbXVzdCBiZSBwcm9wYWdhdGVkIGJhY2t3YXJkXG4gICAgICAgIGlmIChXcml0YWJsZVN0cmVhbUNsb3NlUXVldWVkT3JJbkZsaWdodChkZXN0KSB8fCBkZXN0Ll9zdGF0ZSA9PT0gJ2Nsb3NlZCcpIHtcbiAgICAgICAgICAgIGNvbnN0IGRlc3RDbG9zZWQgPSBuZXcgVHlwZUVycm9yKCd0aGUgZGVzdGluYXRpb24gd3JpdGFibGUgc3RyZWFtIGNsb3NlZCBiZWZvcmUgYWxsIGRhdGEgY291bGQgYmUgcGlwZWQgdG8gaXQnKTtcbiAgICAgICAgICAgIGlmICghcHJldmVudENhbmNlbCkge1xuICAgICAgICAgICAgICAgIHNodXRkb3duV2l0aEFjdGlvbigoKSA9PiBSZWFkYWJsZVN0cmVhbUNhbmNlbChzb3VyY2UsIGRlc3RDbG9zZWQpLCB0cnVlLCBkZXN0Q2xvc2VkKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHNodXRkb3duKHRydWUsIGRlc3RDbG9zZWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHNldFByb21pc2VJc0hhbmRsZWRUb1RydWUocGlwZUxvb3AoKSk7XG4gICAgICAgIGZ1bmN0aW9uIHdhaXRGb3JXcml0ZXNUb0ZpbmlzaCgpIHtcbiAgICAgICAgICAgIC8vIEFub3RoZXIgd3JpdGUgbWF5IGhhdmUgc3RhcnRlZCB3aGlsZSB3ZSB3ZXJlIHdhaXRpbmcgb24gdGhpcyBjdXJyZW50V3JpdGUsIHNvIHdlIGhhdmUgdG8gYmUgc3VyZSB0byB3YWl0XG4gICAgICAgICAgICAvLyBmb3IgdGhhdCB0b28uXG4gICAgICAgICAgICBjb25zdCBvbGRDdXJyZW50V3JpdGUgPSBjdXJyZW50V3JpdGU7XG4gICAgICAgICAgICByZXR1cm4gUGVyZm9ybVByb21pc2VUaGVuKGN1cnJlbnRXcml0ZSwgKCkgPT4gb2xkQ3VycmVudFdyaXRlICE9PSBjdXJyZW50V3JpdGUgPyB3YWl0Rm9yV3JpdGVzVG9GaW5pc2goKSA6IHVuZGVmaW5lZCk7XG4gICAgICAgIH1cbiAgICAgICAgZnVuY3Rpb24gaXNPckJlY29tZXNFcnJvcmVkKHN0cmVhbSwgcHJvbWlzZSwgYWN0aW9uKSB7XG4gICAgICAgICAgICBpZiAoc3RyZWFtLl9zdGF0ZSA9PT0gJ2Vycm9yZWQnKSB7XG4gICAgICAgICAgICAgICAgYWN0aW9uKHN0cmVhbS5fc3RvcmVkRXJyb3IpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgdXBvblJlamVjdGlvbihwcm9taXNlLCBhY3Rpb24pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGZ1bmN0aW9uIGlzT3JCZWNvbWVzQ2xvc2VkKHN0cmVhbSwgcHJvbWlzZSwgYWN0aW9uKSB7XG4gICAgICAgICAgICBpZiAoc3RyZWFtLl9zdGF0ZSA9PT0gJ2Nsb3NlZCcpIHtcbiAgICAgICAgICAgICAgICBhY3Rpb24oKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHVwb25GdWxmaWxsbWVudChwcm9taXNlLCBhY3Rpb24pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGZ1bmN0aW9uIHNodXRkb3duV2l0aEFjdGlvbihhY3Rpb24sIG9yaWdpbmFsSXNFcnJvciwgb3JpZ2luYWxFcnJvcikge1xuICAgICAgICAgICAgaWYgKHNodXR0aW5nRG93bikge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHNodXR0aW5nRG93biA9IHRydWU7XG4gICAgICAgICAgICBpZiAoZGVzdC5fc3RhdGUgPT09ICd3cml0YWJsZScgJiYgIVdyaXRhYmxlU3RyZWFtQ2xvc2VRdWV1ZWRPckluRmxpZ2h0KGRlc3QpKSB7XG4gICAgICAgICAgICAgICAgdXBvbkZ1bGZpbGxtZW50KHdhaXRGb3JXcml0ZXNUb0ZpbmlzaCgpLCBkb1RoZVJlc3QpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgZG9UaGVSZXN0KCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBmdW5jdGlvbiBkb1RoZVJlc3QoKSB7XG4gICAgICAgICAgICAgICAgdXBvblByb21pc2UoYWN0aW9uKCksICgpID0+IGZpbmFsaXplKG9yaWdpbmFsSXNFcnJvciwgb3JpZ2luYWxFcnJvciksIG5ld0Vycm9yID0+IGZpbmFsaXplKHRydWUsIG5ld0Vycm9yKSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZnVuY3Rpb24gc2h1dGRvd24oaXNFcnJvciwgZXJyb3IpIHtcbiAgICAgICAgICAgIGlmIChzaHV0dGluZ0Rvd24pIHtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBzaHV0dGluZ0Rvd24gPSB0cnVlO1xuICAgICAgICAgICAgaWYgKGRlc3QuX3N0YXRlID09PSAnd3JpdGFibGUnICYmICFXcml0YWJsZVN0cmVhbUNsb3NlUXVldWVkT3JJbkZsaWdodChkZXN0KSkge1xuICAgICAgICAgICAgICAgIHVwb25GdWxmaWxsbWVudCh3YWl0Rm9yV3JpdGVzVG9GaW5pc2goKSwgKCkgPT4gZmluYWxpemUoaXNFcnJvciwgZXJyb3IpKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGZpbmFsaXplKGlzRXJyb3IsIGVycm9yKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBmdW5jdGlvbiBmaW5hbGl6ZShpc0Vycm9yLCBlcnJvcikge1xuICAgICAgICAgICAgV3JpdGFibGVTdHJlYW1EZWZhdWx0V3JpdGVyUmVsZWFzZSh3cml0ZXIpO1xuICAgICAgICAgICAgUmVhZGFibGVTdHJlYW1SZWFkZXJHZW5lcmljUmVsZWFzZShyZWFkZXIpO1xuICAgICAgICAgICAgaWYgKHNpZ25hbCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgc2lnbmFsLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ2Fib3J0JywgYWJvcnRBbGdvcml0aG0pO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKGlzRXJyb3IpIHtcbiAgICAgICAgICAgICAgICByZWplY3QoZXJyb3IpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgcmVzb2x2ZSh1bmRlZmluZWQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfSk7XG59XG5cbi8qKlxuICogQWxsb3dzIGNvbnRyb2wgb2YgYSB7QGxpbmsgUmVhZGFibGVTdHJlYW0gfCByZWFkYWJsZSBzdHJlYW19J3Mgc3RhdGUgYW5kIGludGVybmFsIHF1ZXVlLlxuICpcbiAqIEBwdWJsaWNcbiAqL1xuY2xhc3MgUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0lsbGVnYWwgY29uc3RydWN0b3InKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgZGVzaXJlZCBzaXplIHRvIGZpbGwgdGhlIGNvbnRyb2xsZWQgc3RyZWFtJ3MgaW50ZXJuYWwgcXVldWUuIEl0IGNhbiBiZSBuZWdhdGl2ZSwgaWYgdGhlIHF1ZXVlIGlzXG4gICAgICogb3Zlci1mdWxsLiBBbiB1bmRlcmx5aW5nIHNvdXJjZSBvdWdodCB0byB1c2UgdGhpcyBpbmZvcm1hdGlvbiB0byBkZXRlcm1pbmUgd2hlbiBhbmQgaG93IHRvIGFwcGx5IGJhY2twcmVzc3VyZS5cbiAgICAgKi9cbiAgICBnZXQgZGVzaXJlZFNpemUoKSB7XG4gICAgICAgIGlmICghSXNSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBkZWZhdWx0Q29udHJvbGxlckJyYW5kQ2hlY2tFeGNlcHRpb24oJ2Rlc2lyZWRTaXplJyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJHZXREZXNpcmVkU2l6ZSh0aGlzKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogQ2xvc2VzIHRoZSBjb250cm9sbGVkIHJlYWRhYmxlIHN0cmVhbS4gQ29uc3VtZXJzIHdpbGwgc3RpbGwgYmUgYWJsZSB0byByZWFkIGFueSBwcmV2aW91c2x5LWVucXVldWVkIGNodW5rcyBmcm9tXG4gICAgICogdGhlIHN0cmVhbSwgYnV0IG9uY2UgdGhvc2UgYXJlIHJlYWQsIHRoZSBzdHJlYW0gd2lsbCBiZWNvbWUgY2xvc2VkLlxuICAgICAqL1xuICAgIGNsb3NlKCkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlcih0aGlzKSkge1xuICAgICAgICAgICAgdGhyb3cgZGVmYXVsdENvbnRyb2xsZXJCcmFuZENoZWNrRXhjZXB0aW9uKCdjbG9zZScpO1xuICAgICAgICB9XG4gICAgICAgIGlmICghUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckNhbkNsb3NlT3JFbnF1ZXVlKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdUaGUgc3RyZWFtIGlzIG5vdCBpbiBhIHN0YXRlIHRoYXQgcGVybWl0cyBjbG9zZScpO1xuICAgICAgICB9XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbG9zZSh0aGlzKTtcbiAgICB9XG4gICAgZW5xdWV1ZShjaHVuayA9IHVuZGVmaW5lZCkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlcih0aGlzKSkge1xuICAgICAgICAgICAgdGhyb3cgZGVmYXVsdENvbnRyb2xsZXJCcmFuZENoZWNrRXhjZXB0aW9uKCdlbnF1ZXVlJyk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKCFSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2FuQ2xvc2VPckVucXVldWUodGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ1RoZSBzdHJlYW0gaXMgbm90IGluIGEgc3RhdGUgdGhhdCBwZXJtaXRzIGVucXVldWUnKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckVucXVldWUodGhpcywgY2h1bmspO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBFcnJvcnMgdGhlIGNvbnRyb2xsZWQgcmVhZGFibGUgc3RyZWFtLCBtYWtpbmcgYWxsIGZ1dHVyZSBpbnRlcmFjdGlvbnMgd2l0aCBpdCBmYWlsIHdpdGggdGhlIGdpdmVuIGVycm9yIGBlYC5cbiAgICAgKi9cbiAgICBlcnJvcihlID0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGlmICghSXNSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBkZWZhdWx0Q29udHJvbGxlckJyYW5kQ2hlY2tFeGNlcHRpb24oJ2Vycm9yJyk7XG4gICAgICAgIH1cbiAgICAgICAgUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckVycm9yKHRoaXMsIGUpO1xuICAgIH1cbiAgICAvKiogQGludGVybmFsICovXG4gICAgW0NhbmNlbFN0ZXBzXShyZWFzb24pIHtcbiAgICAgICAgUmVzZXRRdWV1ZSh0aGlzKTtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gdGhpcy5fY2FuY2VsQWxnb3JpdGhtKHJlYXNvbik7XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbGVhckFsZ29yaXRobXModGhpcyk7XG4gICAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfVxuICAgIC8qKiBAaW50ZXJuYWwgKi9cbiAgICBbUHVsbFN0ZXBzXShyZWFkUmVxdWVzdCkge1xuICAgICAgICBjb25zdCBzdHJlYW0gPSB0aGlzLl9jb250cm9sbGVkUmVhZGFibGVTdHJlYW07XG4gICAgICAgIGlmICh0aGlzLl9xdWV1ZS5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBjb25zdCBjaHVuayA9IERlcXVldWVWYWx1ZSh0aGlzKTtcbiAgICAgICAgICAgIGlmICh0aGlzLl9jbG9zZVJlcXVlc3RlZCAmJiB0aGlzLl9xdWV1ZS5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgICAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2xlYXJBbGdvcml0aG1zKHRoaXMpO1xuICAgICAgICAgICAgICAgIFJlYWRhYmxlU3RyZWFtQ2xvc2Uoc3RyZWFtKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDYWxsUHVsbElmTmVlZGVkKHRoaXMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmVhZFJlcXVlc3QuX2NodW5rU3RlcHMoY2h1bmspO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgUmVhZGFibGVTdHJlYW1BZGRSZWFkUmVxdWVzdChzdHJlYW0sIHJlYWRSZXF1ZXN0KTtcbiAgICAgICAgICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDYWxsUHVsbElmTmVlZGVkKHRoaXMpO1xuICAgICAgICB9XG4gICAgfVxufVxuT2JqZWN0LmRlZmluZVByb3BlcnRpZXMoUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlci5wcm90b3R5cGUsIHtcbiAgICBjbG9zZTogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgZW5xdWV1ZTogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgZXJyb3I6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIGRlc2lyZWRTaXplOiB7IGVudW1lcmFibGU6IHRydWUgfVxufSk7XG5pZiAodHlwZW9mIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnID09PSAnc3ltYm9sJykge1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyLnByb3RvdHlwZSwgU3ltYm9sUG9seWZpbGwudG9TdHJpbmdUYWcsIHtcbiAgICAgICAgdmFsdWU6ICdSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyJyxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlXG4gICAgfSk7XG59XG4vLyBBYnN0cmFjdCBvcGVyYXRpb25zIGZvciB0aGUgUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlci5cbmZ1bmN0aW9uIElzUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlcih4KSB7XG4gICAgaWYgKCF0eXBlSXNPYmplY3QoeCkpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBpZiAoIU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbCh4LCAnX2NvbnRyb2xsZWRSZWFkYWJsZVN0cmVhbScpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG59XG5mdW5jdGlvbiBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2FsbFB1bGxJZk5lZWRlZChjb250cm9sbGVyKSB7XG4gICAgY29uc3Qgc2hvdWxkUHVsbCA9IFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJTaG91bGRDYWxsUHVsbChjb250cm9sbGVyKTtcbiAgICBpZiAoIXNob3VsZFB1bGwpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBpZiAoY29udHJvbGxlci5fcHVsbGluZykge1xuICAgICAgICBjb250cm9sbGVyLl9wdWxsQWdhaW4gPSB0cnVlO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnRyb2xsZXIuX3B1bGxpbmcgPSB0cnVlO1xuICAgIGNvbnN0IHB1bGxQcm9taXNlID0gY29udHJvbGxlci5fcHVsbEFsZ29yaXRobSgpO1xuICAgIHVwb25Qcm9taXNlKHB1bGxQcm9taXNlLCAoKSA9PiB7XG4gICAgICAgIGNvbnRyb2xsZXIuX3B1bGxpbmcgPSBmYWxzZTtcbiAgICAgICAgaWYgKGNvbnRyb2xsZXIuX3B1bGxBZ2Fpbikge1xuICAgICAgICAgICAgY29udHJvbGxlci5fcHVsbEFnYWluID0gZmFsc2U7XG4gICAgICAgICAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2FsbFB1bGxJZk5lZWRlZChjb250cm9sbGVyKTtcbiAgICAgICAgfVxuICAgIH0sIGUgPT4ge1xuICAgICAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRXJyb3IoY29udHJvbGxlciwgZSk7XG4gICAgfSk7XG59XG5mdW5jdGlvbiBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyU2hvdWxkQ2FsbFB1bGwoY29udHJvbGxlcikge1xuICAgIGNvbnN0IHN0cmVhbSA9IGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRSZWFkYWJsZVN0cmVhbTtcbiAgICBpZiAoIVJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDYW5DbG9zZU9yRW5xdWV1ZShjb250cm9sbGVyKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmICghY29udHJvbGxlci5fc3RhcnRlZCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmIChJc1JlYWRhYmxlU3RyZWFtTG9ja2VkKHN0cmVhbSkgJiYgUmVhZGFibGVTdHJlYW1HZXROdW1SZWFkUmVxdWVzdHMoc3RyZWFtKSA+IDApIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIGNvbnN0IGRlc2lyZWRTaXplID0gUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckdldERlc2lyZWRTaXplKGNvbnRyb2xsZXIpO1xuICAgIGlmIChkZXNpcmVkU2l6ZSA+IDApIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbGVhckFsZ29yaXRobXMoY29udHJvbGxlcikge1xuICAgIGNvbnRyb2xsZXIuX3B1bGxBbGdvcml0aG0gPSB1bmRlZmluZWQ7XG4gICAgY29udHJvbGxlci5fY2FuY2VsQWxnb3JpdGhtID0gdW5kZWZpbmVkO1xuICAgIGNvbnRyb2xsZXIuX3N0cmF0ZWd5U2l6ZUFsZ29yaXRobSA9IHVuZGVmaW5lZDtcbn1cbi8vIEEgY2xpZW50IG9mIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIgbWF5IHVzZSB0aGVzZSBmdW5jdGlvbnMgZGlyZWN0bHkgdG8gYnlwYXNzIHN0YXRlIGNoZWNrLlxuZnVuY3Rpb24gUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckNsb3NlKGNvbnRyb2xsZXIpIHtcbiAgICBpZiAoIVJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDYW5DbG9zZU9yRW5xdWV1ZShjb250cm9sbGVyKSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHN0cmVhbSA9IGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRSZWFkYWJsZVN0cmVhbTtcbiAgICBjb250cm9sbGVyLl9jbG9zZVJlcXVlc3RlZCA9IHRydWU7XG4gICAgaWYgKGNvbnRyb2xsZXIuX3F1ZXVlLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2xlYXJBbGdvcml0aG1zKGNvbnRyb2xsZXIpO1xuICAgICAgICBSZWFkYWJsZVN0cmVhbUNsb3NlKHN0cmVhbSk7XG4gICAgfVxufVxuZnVuY3Rpb24gUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckVucXVldWUoY29udHJvbGxlciwgY2h1bmspIHtcbiAgICBpZiAoIVJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDYW5DbG9zZU9yRW5xdWV1ZShjb250cm9sbGVyKSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IHN0cmVhbSA9IGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRSZWFkYWJsZVN0cmVhbTtcbiAgICBpZiAoSXNSZWFkYWJsZVN0cmVhbUxvY2tlZChzdHJlYW0pICYmIFJlYWRhYmxlU3RyZWFtR2V0TnVtUmVhZFJlcXVlc3RzKHN0cmVhbSkgPiAwKSB7XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtRnVsZmlsbFJlYWRSZXF1ZXN0KHN0cmVhbSwgY2h1bmssIGZhbHNlKTtcbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIGxldCBjaHVua1NpemU7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBjaHVua1NpemUgPSBjb250cm9sbGVyLl9zdHJhdGVneVNpemVBbGdvcml0aG0oY2h1bmspO1xuICAgICAgICB9XG4gICAgICAgIGNhdGNoIChjaHVua1NpemVFKSB7XG4gICAgICAgICAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRXJyb3IoY29udHJvbGxlciwgY2h1bmtTaXplRSk7XG4gICAgICAgICAgICB0aHJvdyBjaHVua1NpemVFO1xuICAgICAgICB9XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBFbnF1ZXVlVmFsdWVXaXRoU2l6ZShjb250cm9sbGVyLCBjaHVuaywgY2h1bmtTaXplKTtcbiAgICAgICAgfVxuICAgICAgICBjYXRjaCAoZW5xdWV1ZUUpIHtcbiAgICAgICAgICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFcnJvcihjb250cm9sbGVyLCBlbnF1ZXVlRSk7XG4gICAgICAgICAgICB0aHJvdyBlbnF1ZXVlRTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2FsbFB1bGxJZk5lZWRlZChjb250cm9sbGVyKTtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFcnJvcihjb250cm9sbGVyLCBlKSB7XG4gICAgY29uc3Qgc3RyZWFtID0gY29udHJvbGxlci5fY29udHJvbGxlZFJlYWRhYmxlU3RyZWFtO1xuICAgIGlmIChzdHJlYW0uX3N0YXRlICE9PSAncmVhZGFibGUnKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgUmVzZXRRdWV1ZShjb250cm9sbGVyKTtcbiAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2xlYXJBbGdvcml0aG1zKGNvbnRyb2xsZXIpO1xuICAgIFJlYWRhYmxlU3RyZWFtRXJyb3Ioc3RyZWFtLCBlKTtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJHZXREZXNpcmVkU2l6ZShjb250cm9sbGVyKSB7XG4gICAgY29uc3Qgc3RhdGUgPSBjb250cm9sbGVyLl9jb250cm9sbGVkUmVhZGFibGVTdHJlYW0uX3N0YXRlO1xuICAgIGlmIChzdGF0ZSA9PT0gJ2Vycm9yZWQnKSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICBpZiAoc3RhdGUgPT09ICdjbG9zZWQnKSB7XG4gICAgICAgIHJldHVybiAwO1xuICAgIH1cbiAgICByZXR1cm4gY29udHJvbGxlci5fc3RyYXRlZ3lIV00gLSBjb250cm9sbGVyLl9xdWV1ZVRvdGFsU2l6ZTtcbn1cbi8vIFRoaXMgaXMgdXNlZCBpbiB0aGUgaW1wbGVtZW50YXRpb24gb2YgVHJhbnNmb3JtU3RyZWFtLlxuZnVuY3Rpb24gUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckhhc0JhY2twcmVzc3VyZShjb250cm9sbGVyKSB7XG4gICAgaWYgKFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJTaG91bGRDYWxsUHVsbChjb250cm9sbGVyKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xufVxuZnVuY3Rpb24gUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckNhbkNsb3NlT3JFbnF1ZXVlKGNvbnRyb2xsZXIpIHtcbiAgICBjb25zdCBzdGF0ZSA9IGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRSZWFkYWJsZVN0cmVhbS5fc3RhdGU7XG4gICAgaWYgKCFjb250cm9sbGVyLl9jbG9zZVJlcXVlc3RlZCAmJiBzdGF0ZSA9PT0gJ3JlYWRhYmxlJykge1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xufVxuZnVuY3Rpb24gU2V0VXBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyKHN0cmVhbSwgY29udHJvbGxlciwgc3RhcnRBbGdvcml0aG0sIHB1bGxBbGdvcml0aG0sIGNhbmNlbEFsZ29yaXRobSwgaGlnaFdhdGVyTWFyaywgc2l6ZUFsZ29yaXRobSkge1xuICAgIGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRSZWFkYWJsZVN0cmVhbSA9IHN0cmVhbTtcbiAgICBjb250cm9sbGVyLl9xdWV1ZSA9IHVuZGVmaW5lZDtcbiAgICBjb250cm9sbGVyLl9xdWV1ZVRvdGFsU2l6ZSA9IHVuZGVmaW5lZDtcbiAgICBSZXNldFF1ZXVlKGNvbnRyb2xsZXIpO1xuICAgIGNvbnRyb2xsZXIuX3N0YXJ0ZWQgPSBmYWxzZTtcbiAgICBjb250cm9sbGVyLl9jbG9zZVJlcXVlc3RlZCA9IGZhbHNlO1xuICAgIGNvbnRyb2xsZXIuX3B1bGxBZ2FpbiA9IGZhbHNlO1xuICAgIGNvbnRyb2xsZXIuX3B1bGxpbmcgPSBmYWxzZTtcbiAgICBjb250cm9sbGVyLl9zdHJhdGVneVNpemVBbGdvcml0aG0gPSBzaXplQWxnb3JpdGhtO1xuICAgIGNvbnRyb2xsZXIuX3N0cmF0ZWd5SFdNID0gaGlnaFdhdGVyTWFyaztcbiAgICBjb250cm9sbGVyLl9wdWxsQWxnb3JpdGhtID0gcHVsbEFsZ29yaXRobTtcbiAgICBjb250cm9sbGVyLl9jYW5jZWxBbGdvcml0aG0gPSBjYW5jZWxBbGdvcml0aG07XG4gICAgc3RyZWFtLl9yZWFkYWJsZVN0cmVhbUNvbnRyb2xsZXIgPSBjb250cm9sbGVyO1xuICAgIGNvbnN0IHN0YXJ0UmVzdWx0ID0gc3RhcnRBbGdvcml0aG0oKTtcbiAgICB1cG9uUHJvbWlzZShwcm9taXNlUmVzb2x2ZWRXaXRoKHN0YXJ0UmVzdWx0KSwgKCkgPT4ge1xuICAgICAgICBjb250cm9sbGVyLl9zdGFydGVkID0gdHJ1ZTtcbiAgICAgICAgUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckNhbGxQdWxsSWZOZWVkZWQoY29udHJvbGxlcik7XG4gICAgfSwgciA9PiB7XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFcnJvcihjb250cm9sbGVyLCByKTtcbiAgICB9KTtcbn1cbmZ1bmN0aW9uIFNldFVwUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckZyb21VbmRlcmx5aW5nU291cmNlKHN0cmVhbSwgdW5kZXJseWluZ1NvdXJjZSwgaGlnaFdhdGVyTWFyaywgc2l6ZUFsZ29yaXRobSkge1xuICAgIGNvbnN0IGNvbnRyb2xsZXIgPSBPYmplY3QuY3JlYXRlKFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIucHJvdG90eXBlKTtcbiAgICBsZXQgc3RhcnRBbGdvcml0aG0gPSAoKSA9PiB1bmRlZmluZWQ7XG4gICAgbGV0IHB1bGxBbGdvcml0aG0gPSAoKSA9PiBwcm9taXNlUmVzb2x2ZWRXaXRoKHVuZGVmaW5lZCk7XG4gICAgbGV0IGNhbmNlbEFsZ29yaXRobSA9ICgpID0+IHByb21pc2VSZXNvbHZlZFdpdGgodW5kZWZpbmVkKTtcbiAgICBpZiAodW5kZXJseWluZ1NvdXJjZS5zdGFydCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHN0YXJ0QWxnb3JpdGhtID0gKCkgPT4gdW5kZXJseWluZ1NvdXJjZS5zdGFydChjb250cm9sbGVyKTtcbiAgICB9XG4gICAgaWYgKHVuZGVybHlpbmdTb3VyY2UucHVsbCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHB1bGxBbGdvcml0aG0gPSAoKSA9PiB1bmRlcmx5aW5nU291cmNlLnB1bGwoY29udHJvbGxlcik7XG4gICAgfVxuICAgIGlmICh1bmRlcmx5aW5nU291cmNlLmNhbmNlbCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGNhbmNlbEFsZ29yaXRobSA9IHJlYXNvbiA9PiB1bmRlcmx5aW5nU291cmNlLmNhbmNlbChyZWFzb24pO1xuICAgIH1cbiAgICBTZXRVcFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIoc3RyZWFtLCBjb250cm9sbGVyLCBzdGFydEFsZ29yaXRobSwgcHVsbEFsZ29yaXRobSwgY2FuY2VsQWxnb3JpdGhtLCBoaWdoV2F0ZXJNYXJrLCBzaXplQWxnb3JpdGhtKTtcbn1cbi8vIEhlbHBlciBmdW5jdGlvbnMgZm9yIHRoZSBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyLlxuZnVuY3Rpb24gZGVmYXVsdENvbnRyb2xsZXJCcmFuZENoZWNrRXhjZXB0aW9uKG5hbWUpIHtcbiAgICByZXR1cm4gbmV3IFR5cGVFcnJvcihgUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlci5wcm90b3R5cGUuJHtuYW1lfSBjYW4gb25seSBiZSB1c2VkIG9uIGEgUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlcmApO1xufVxuXG5mdW5jdGlvbiBSZWFkYWJsZVN0cmVhbVRlZShzdHJlYW0sIGNsb25lRm9yQnJhbmNoMikge1xuICAgIGNvbnN0IHJlYWRlciA9IEFjcXVpcmVSZWFkYWJsZVN0cmVhbURlZmF1bHRSZWFkZXIoc3RyZWFtKTtcbiAgICBsZXQgcmVhZGluZyA9IGZhbHNlO1xuICAgIGxldCBjYW5jZWxlZDEgPSBmYWxzZTtcbiAgICBsZXQgY2FuY2VsZWQyID0gZmFsc2U7XG4gICAgbGV0IHJlYXNvbjE7XG4gICAgbGV0IHJlYXNvbjI7XG4gICAgbGV0IGJyYW5jaDE7XG4gICAgbGV0IGJyYW5jaDI7XG4gICAgbGV0IHJlc29sdmVDYW5jZWxQcm9taXNlO1xuICAgIGNvbnN0IGNhbmNlbFByb21pc2UgPSBuZXdQcm9taXNlKHJlc29sdmUgPT4ge1xuICAgICAgICByZXNvbHZlQ2FuY2VsUHJvbWlzZSA9IHJlc29sdmU7XG4gICAgfSk7XG4gICAgZnVuY3Rpb24gcHVsbEFsZ29yaXRobSgpIHtcbiAgICAgICAgaWYgKHJlYWRpbmcpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVzb2x2ZWRXaXRoKHVuZGVmaW5lZCk7XG4gICAgICAgIH1cbiAgICAgICAgcmVhZGluZyA9IHRydWU7XG4gICAgICAgIGNvbnN0IHJlYWRSZXF1ZXN0ID0ge1xuICAgICAgICAgICAgX2NodW5rU3RlcHM6IHZhbHVlID0+IHtcbiAgICAgICAgICAgICAgICAvLyBUaGlzIG5lZWRzIHRvIGJlIGRlbGF5ZWQgYSBtaWNyb3Rhc2sgYmVjYXVzZSBpdCB0YWtlcyBhdCBsZWFzdCBhIG1pY3JvdGFzayB0byBkZXRlY3QgZXJyb3JzICh1c2luZ1xuICAgICAgICAgICAgICAgIC8vIHJlYWRlci5fY2xvc2VkUHJvbWlzZSBiZWxvdyksIGFuZCB3ZSB3YW50IGVycm9ycyBpbiBzdHJlYW0gdG8gZXJyb3IgYm90aCBicmFuY2hlcyBpbW1lZGlhdGVseS4gV2UgY2Fubm90IGxldFxuICAgICAgICAgICAgICAgIC8vIHN1Y2Nlc3NmdWwgc3luY2hyb25vdXNseS1hdmFpbGFibGUgcmVhZHMgZ2V0IGFoZWFkIG9mIGFzeW5jaHJvbm91c2x5LWF2YWlsYWJsZSBlcnJvcnMuXG4gICAgICAgICAgICAgICAgcXVldWVNaWNyb3Rhc2soKCkgPT4ge1xuICAgICAgICAgICAgICAgICAgICByZWFkaW5nID0gZmFsc2U7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHZhbHVlMSA9IHZhbHVlO1xuICAgICAgICAgICAgICAgICAgICBjb25zdCB2YWx1ZTIgPSB2YWx1ZTtcbiAgICAgICAgICAgICAgICAgICAgLy8gVGhlcmUgaXMgbm8gd2F5IHRvIGFjY2VzcyB0aGUgY2xvbmluZyBjb2RlIHJpZ2h0IG5vdyBpbiB0aGUgcmVmZXJlbmNlIGltcGxlbWVudGF0aW9uLlxuICAgICAgICAgICAgICAgICAgICAvLyBJZiB3ZSBhZGQgb25lIHRoZW4gd2UnbGwgbmVlZCBhbiBpbXBsZW1lbnRhdGlvbiBmb3Igc2VyaWFsaXphYmxlIG9iamVjdHMuXG4gICAgICAgICAgICAgICAgICAgIC8vIGlmICghY2FuY2VsZWQyICYmIGNsb25lRm9yQnJhbmNoMikge1xuICAgICAgICAgICAgICAgICAgICAvLyAgIHZhbHVlMiA9IFN0cnVjdHVyZWREZXNlcmlhbGl6ZShTdHJ1Y3R1cmVkU2VyaWFsaXplKHZhbHVlMikpO1xuICAgICAgICAgICAgICAgICAgICAvLyB9XG4gICAgICAgICAgICAgICAgICAgIGlmICghY2FuY2VsZWQxKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRW5xdWV1ZShicmFuY2gxLl9yZWFkYWJsZVN0cmVhbUNvbnRyb2xsZXIsIHZhbHVlMSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgaWYgKCFjYW5jZWxlZDIpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFbnF1ZXVlKGJyYW5jaDIuX3JlYWRhYmxlU3RyZWFtQ29udHJvbGxlciwgdmFsdWUyKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICByZXNvbHZlQ2FuY2VsUHJvbWlzZSh1bmRlZmluZWQpO1xuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIF9jbG9zZVN0ZXBzOiAoKSA9PiB7XG4gICAgICAgICAgICAgICAgcmVhZGluZyA9IGZhbHNlO1xuICAgICAgICAgICAgICAgIGlmICghY2FuY2VsZWQxKSB7XG4gICAgICAgICAgICAgICAgICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbG9zZShicmFuY2gxLl9yZWFkYWJsZVN0cmVhbUNvbnRyb2xsZXIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBpZiAoIWNhbmNlbGVkMikge1xuICAgICAgICAgICAgICAgICAgICBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2xvc2UoYnJhbmNoMi5fcmVhZGFibGVTdHJlYW1Db250cm9sbGVyKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgX2Vycm9yU3RlcHM6ICgpID0+IHtcbiAgICAgICAgICAgICAgICByZWFkaW5nID0gZmFsc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlclJlYWQocmVhZGVyLCByZWFkUmVxdWVzdCk7XG4gICAgICAgIHJldHVybiBwcm9taXNlUmVzb2x2ZWRXaXRoKHVuZGVmaW5lZCk7XG4gICAgfVxuICAgIGZ1bmN0aW9uIGNhbmNlbDFBbGdvcml0aG0ocmVhc29uKSB7XG4gICAgICAgIGNhbmNlbGVkMSA9IHRydWU7XG4gICAgICAgIHJlYXNvbjEgPSByZWFzb247XG4gICAgICAgIGlmIChjYW5jZWxlZDIpIHtcbiAgICAgICAgICAgIGNvbnN0IGNvbXBvc2l0ZVJlYXNvbiA9IENyZWF0ZUFycmF5RnJvbUxpc3QoW3JlYXNvbjEsIHJlYXNvbjJdKTtcbiAgICAgICAgICAgIGNvbnN0IGNhbmNlbFJlc3VsdCA9IFJlYWRhYmxlU3RyZWFtQ2FuY2VsKHN0cmVhbSwgY29tcG9zaXRlUmVhc29uKTtcbiAgICAgICAgICAgIHJlc29sdmVDYW5jZWxQcm9taXNlKGNhbmNlbFJlc3VsdCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGNhbmNlbFByb21pc2U7XG4gICAgfVxuICAgIGZ1bmN0aW9uIGNhbmNlbDJBbGdvcml0aG0ocmVhc29uKSB7XG4gICAgICAgIGNhbmNlbGVkMiA9IHRydWU7XG4gICAgICAgIHJlYXNvbjIgPSByZWFzb247XG4gICAgICAgIGlmIChjYW5jZWxlZDEpIHtcbiAgICAgICAgICAgIGNvbnN0IGNvbXBvc2l0ZVJlYXNvbiA9IENyZWF0ZUFycmF5RnJvbUxpc3QoW3JlYXNvbjEsIHJlYXNvbjJdKTtcbiAgICAgICAgICAgIGNvbnN0IGNhbmNlbFJlc3VsdCA9IFJlYWRhYmxlU3RyZWFtQ2FuY2VsKHN0cmVhbSwgY29tcG9zaXRlUmVhc29uKTtcbiAgICAgICAgICAgIHJlc29sdmVDYW5jZWxQcm9taXNlKGNhbmNlbFJlc3VsdCk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIGNhbmNlbFByb21pc2U7XG4gICAgfVxuICAgIGZ1bmN0aW9uIHN0YXJ0QWxnb3JpdGhtKCkge1xuICAgICAgICAvLyBkbyBub3RoaW5nXG4gICAgfVxuICAgIGJyYW5jaDEgPSBDcmVhdGVSZWFkYWJsZVN0cmVhbShzdGFydEFsZ29yaXRobSwgcHVsbEFsZ29yaXRobSwgY2FuY2VsMUFsZ29yaXRobSk7XG4gICAgYnJhbmNoMiA9IENyZWF0ZVJlYWRhYmxlU3RyZWFtKHN0YXJ0QWxnb3JpdGhtLCBwdWxsQWxnb3JpdGhtLCBjYW5jZWwyQWxnb3JpdGhtKTtcbiAgICB1cG9uUmVqZWN0aW9uKHJlYWRlci5fY2xvc2VkUHJvbWlzZSwgKHIpID0+IHtcbiAgICAgICAgUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckVycm9yKGJyYW5jaDEuX3JlYWRhYmxlU3RyZWFtQ29udHJvbGxlciwgcik7XG4gICAgICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFcnJvcihicmFuY2gyLl9yZWFkYWJsZVN0cmVhbUNvbnRyb2xsZXIsIHIpO1xuICAgICAgICByZXNvbHZlQ2FuY2VsUHJvbWlzZSh1bmRlZmluZWQpO1xuICAgIH0pO1xuICAgIHJldHVybiBbYnJhbmNoMSwgYnJhbmNoMl07XG59XG5cbmZ1bmN0aW9uIGNvbnZlcnRVbmRlcmx5aW5nRGVmYXVsdE9yQnl0ZVNvdXJjZShzb3VyY2UsIGNvbnRleHQpIHtcbiAgICBhc3NlcnREaWN0aW9uYXJ5KHNvdXJjZSwgY29udGV4dCk7XG4gICAgY29uc3Qgb3JpZ2luYWwgPSBzb3VyY2U7XG4gICAgY29uc3QgYXV0b0FsbG9jYXRlQ2h1bmtTaXplID0gb3JpZ2luYWwgPT09IG51bGwgfHwgb3JpZ2luYWwgPT09IHZvaWQgMCA/IHZvaWQgMCA6IG9yaWdpbmFsLmF1dG9BbGxvY2F0ZUNodW5rU2l6ZTtcbiAgICBjb25zdCBjYW5jZWwgPSBvcmlnaW5hbCA9PT0gbnVsbCB8fCBvcmlnaW5hbCA9PT0gdm9pZCAwID8gdm9pZCAwIDogb3JpZ2luYWwuY2FuY2VsO1xuICAgIGNvbnN0IHB1bGwgPSBvcmlnaW5hbCA9PT0gbnVsbCB8fCBvcmlnaW5hbCA9PT0gdm9pZCAwID8gdm9pZCAwIDogb3JpZ2luYWwucHVsbDtcbiAgICBjb25zdCBzdGFydCA9IG9yaWdpbmFsID09PSBudWxsIHx8IG9yaWdpbmFsID09PSB2b2lkIDAgPyB2b2lkIDAgOiBvcmlnaW5hbC5zdGFydDtcbiAgICBjb25zdCB0eXBlID0gb3JpZ2luYWwgPT09IG51bGwgfHwgb3JpZ2luYWwgPT09IHZvaWQgMCA/IHZvaWQgMCA6IG9yaWdpbmFsLnR5cGU7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgYXV0b0FsbG9jYXRlQ2h1bmtTaXplOiBhdXRvQWxsb2NhdGVDaHVua1NpemUgPT09IHVuZGVmaW5lZCA/XG4gICAgICAgICAgICB1bmRlZmluZWQgOlxuICAgICAgICAgICAgY29udmVydFVuc2lnbmVkTG9uZ0xvbmdXaXRoRW5mb3JjZVJhbmdlKGF1dG9BbGxvY2F0ZUNodW5rU2l6ZSwgYCR7Y29udGV4dH0gaGFzIG1lbWJlciAnYXV0b0FsbG9jYXRlQ2h1bmtTaXplJyB0aGF0YCksXG4gICAgICAgIGNhbmNlbDogY2FuY2VsID09PSB1bmRlZmluZWQgP1xuICAgICAgICAgICAgdW5kZWZpbmVkIDpcbiAgICAgICAgICAgIGNvbnZlcnRVbmRlcmx5aW5nU291cmNlQ2FuY2VsQ2FsbGJhY2soY2FuY2VsLCBvcmlnaW5hbCwgYCR7Y29udGV4dH0gaGFzIG1lbWJlciAnY2FuY2VsJyB0aGF0YCksXG4gICAgICAgIHB1bGw6IHB1bGwgPT09IHVuZGVmaW5lZCA/XG4gICAgICAgICAgICB1bmRlZmluZWQgOlxuICAgICAgICAgICAgY29udmVydFVuZGVybHlpbmdTb3VyY2VQdWxsQ2FsbGJhY2socHVsbCwgb3JpZ2luYWwsIGAke2NvbnRleHR9IGhhcyBtZW1iZXIgJ3B1bGwnIHRoYXRgKSxcbiAgICAgICAgc3RhcnQ6IHN0YXJ0ID09PSB1bmRlZmluZWQgP1xuICAgICAgICAgICAgdW5kZWZpbmVkIDpcbiAgICAgICAgICAgIGNvbnZlcnRVbmRlcmx5aW5nU291cmNlU3RhcnRDYWxsYmFjayhzdGFydCwgb3JpZ2luYWwsIGAke2NvbnRleHR9IGhhcyBtZW1iZXIgJ3N0YXJ0JyB0aGF0YCksXG4gICAgICAgIHR5cGU6IHR5cGUgPT09IHVuZGVmaW5lZCA/IHVuZGVmaW5lZCA6IGNvbnZlcnRSZWFkYWJsZVN0cmVhbVR5cGUodHlwZSwgYCR7Y29udGV4dH0gaGFzIG1lbWJlciAndHlwZScgdGhhdGApXG4gICAgfTtcbn1cbmZ1bmN0aW9uIGNvbnZlcnRVbmRlcmx5aW5nU291cmNlQ2FuY2VsQ2FsbGJhY2soZm4sIG9yaWdpbmFsLCBjb250ZXh0KSB7XG4gICAgYXNzZXJ0RnVuY3Rpb24oZm4sIGNvbnRleHQpO1xuICAgIHJldHVybiAocmVhc29uKSA9PiBwcm9taXNlQ2FsbChmbiwgb3JpZ2luYWwsIFtyZWFzb25dKTtcbn1cbmZ1bmN0aW9uIGNvbnZlcnRVbmRlcmx5aW5nU291cmNlUHVsbENhbGxiYWNrKGZuLCBvcmlnaW5hbCwgY29udGV4dCkge1xuICAgIGFzc2VydEZ1bmN0aW9uKGZuLCBjb250ZXh0KTtcbiAgICByZXR1cm4gKGNvbnRyb2xsZXIpID0+IHByb21pc2VDYWxsKGZuLCBvcmlnaW5hbCwgW2NvbnRyb2xsZXJdKTtcbn1cbmZ1bmN0aW9uIGNvbnZlcnRVbmRlcmx5aW5nU291cmNlU3RhcnRDYWxsYmFjayhmbiwgb3JpZ2luYWwsIGNvbnRleHQpIHtcbiAgICBhc3NlcnRGdW5jdGlvbihmbiwgY29udGV4dCk7XG4gICAgcmV0dXJuIChjb250cm9sbGVyKSA9PiByZWZsZWN0Q2FsbChmbiwgb3JpZ2luYWwsIFtjb250cm9sbGVyXSk7XG59XG5mdW5jdGlvbiBjb252ZXJ0UmVhZGFibGVTdHJlYW1UeXBlKHR5cGUsIGNvbnRleHQpIHtcbiAgICB0eXBlID0gYCR7dHlwZX1gO1xuICAgIGlmICh0eXBlICE9PSAnYnl0ZXMnKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoYCR7Y29udGV4dH0gJyR7dHlwZX0nIGlzIG5vdCBhIHZhbGlkIGVudW1lcmF0aW9uIHZhbHVlIGZvciBSZWFkYWJsZVN0cmVhbVR5cGVgKTtcbiAgICB9XG4gICAgcmV0dXJuIHR5cGU7XG59XG5cbmZ1bmN0aW9uIGNvbnZlcnRSZWFkZXJPcHRpb25zKG9wdGlvbnMsIGNvbnRleHQpIHtcbiAgICBhc3NlcnREaWN0aW9uYXJ5KG9wdGlvbnMsIGNvbnRleHQpO1xuICAgIGNvbnN0IG1vZGUgPSBvcHRpb25zID09PSBudWxsIHx8IG9wdGlvbnMgPT09IHZvaWQgMCA/IHZvaWQgMCA6IG9wdGlvbnMubW9kZTtcbiAgICByZXR1cm4ge1xuICAgICAgICBtb2RlOiBtb2RlID09PSB1bmRlZmluZWQgPyB1bmRlZmluZWQgOiBjb252ZXJ0UmVhZGFibGVTdHJlYW1SZWFkZXJNb2RlKG1vZGUsIGAke2NvbnRleHR9IGhhcyBtZW1iZXIgJ21vZGUnIHRoYXRgKVxuICAgIH07XG59XG5mdW5jdGlvbiBjb252ZXJ0UmVhZGFibGVTdHJlYW1SZWFkZXJNb2RlKG1vZGUsIGNvbnRleHQpIHtcbiAgICBtb2RlID0gYCR7bW9kZX1gO1xuICAgIGlmIChtb2RlICE9PSAnYnlvYicpIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihgJHtjb250ZXh0fSAnJHttb2RlfScgaXMgbm90IGEgdmFsaWQgZW51bWVyYXRpb24gdmFsdWUgZm9yIFJlYWRhYmxlU3RyZWFtUmVhZGVyTW9kZWApO1xuICAgIH1cbiAgICByZXR1cm4gbW9kZTtcbn1cblxuZnVuY3Rpb24gY29udmVydEl0ZXJhdG9yT3B0aW9ucyhvcHRpb25zLCBjb250ZXh0KSB7XG4gICAgYXNzZXJ0RGljdGlvbmFyeShvcHRpb25zLCBjb250ZXh0KTtcbiAgICBjb25zdCBwcmV2ZW50Q2FuY2VsID0gb3B0aW9ucyA9PT0gbnVsbCB8fCBvcHRpb25zID09PSB2b2lkIDAgPyB2b2lkIDAgOiBvcHRpb25zLnByZXZlbnRDYW5jZWw7XG4gICAgcmV0dXJuIHsgcHJldmVudENhbmNlbDogQm9vbGVhbihwcmV2ZW50Q2FuY2VsKSB9O1xufVxuXG5mdW5jdGlvbiBjb252ZXJ0UGlwZU9wdGlvbnMob3B0aW9ucywgY29udGV4dCkge1xuICAgIGFzc2VydERpY3Rpb25hcnkob3B0aW9ucywgY29udGV4dCk7XG4gICAgY29uc3QgcHJldmVudEFib3J0ID0gb3B0aW9ucyA9PT0gbnVsbCB8fCBvcHRpb25zID09PSB2b2lkIDAgPyB2b2lkIDAgOiBvcHRpb25zLnByZXZlbnRBYm9ydDtcbiAgICBjb25zdCBwcmV2ZW50Q2FuY2VsID0gb3B0aW9ucyA9PT0gbnVsbCB8fCBvcHRpb25zID09PSB2b2lkIDAgPyB2b2lkIDAgOiBvcHRpb25zLnByZXZlbnRDYW5jZWw7XG4gICAgY29uc3QgcHJldmVudENsb3NlID0gb3B0aW9ucyA9PT0gbnVsbCB8fCBvcHRpb25zID09PSB2b2lkIDAgPyB2b2lkIDAgOiBvcHRpb25zLnByZXZlbnRDbG9zZTtcbiAgICBjb25zdCBzaWduYWwgPSBvcHRpb25zID09PSBudWxsIHx8IG9wdGlvbnMgPT09IHZvaWQgMCA/IHZvaWQgMCA6IG9wdGlvbnMuc2lnbmFsO1xuICAgIGlmIChzaWduYWwgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBhc3NlcnRBYm9ydFNpZ25hbChzaWduYWwsIGAke2NvbnRleHR9IGhhcyBtZW1iZXIgJ3NpZ25hbCcgdGhhdGApO1xuICAgIH1cbiAgICByZXR1cm4ge1xuICAgICAgICBwcmV2ZW50QWJvcnQ6IEJvb2xlYW4ocHJldmVudEFib3J0KSxcbiAgICAgICAgcHJldmVudENhbmNlbDogQm9vbGVhbihwcmV2ZW50Q2FuY2VsKSxcbiAgICAgICAgcHJldmVudENsb3NlOiBCb29sZWFuKHByZXZlbnRDbG9zZSksXG4gICAgICAgIHNpZ25hbFxuICAgIH07XG59XG5mdW5jdGlvbiBhc3NlcnRBYm9ydFNpZ25hbChzaWduYWwsIGNvbnRleHQpIHtcbiAgICBpZiAoIWlzQWJvcnRTaWduYWwoc2lnbmFsKSkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKGAke2NvbnRleHR9IGlzIG5vdCBhbiBBYm9ydFNpZ25hbC5gKTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGNvbnZlcnRSZWFkYWJsZVdyaXRhYmxlUGFpcihwYWlyLCBjb250ZXh0KSB7XG4gICAgYXNzZXJ0RGljdGlvbmFyeShwYWlyLCBjb250ZXh0KTtcbiAgICBjb25zdCByZWFkYWJsZSA9IHBhaXIgPT09IG51bGwgfHwgcGFpciA9PT0gdm9pZCAwID8gdm9pZCAwIDogcGFpci5yZWFkYWJsZTtcbiAgICBhc3NlcnRSZXF1aXJlZEZpZWxkKHJlYWRhYmxlLCAncmVhZGFibGUnLCAnUmVhZGFibGVXcml0YWJsZVBhaXInKTtcbiAgICBhc3NlcnRSZWFkYWJsZVN0cmVhbShyZWFkYWJsZSwgYCR7Y29udGV4dH0gaGFzIG1lbWJlciAncmVhZGFibGUnIHRoYXRgKTtcbiAgICBjb25zdCB3cml0YWJsZSA9IHBhaXIgPT09IG51bGwgfHwgcGFpciA9PT0gdm9pZCAwID8gdm9pZCAwIDogcGFpci53cml0YWJsZTtcbiAgICBhc3NlcnRSZXF1aXJlZEZpZWxkKHdyaXRhYmxlLCAnd3JpdGFibGUnLCAnUmVhZGFibGVXcml0YWJsZVBhaXInKTtcbiAgICBhc3NlcnRXcml0YWJsZVN0cmVhbSh3cml0YWJsZSwgYCR7Y29udGV4dH0gaGFzIG1lbWJlciAnd3JpdGFibGUnIHRoYXRgKTtcbiAgICByZXR1cm4geyByZWFkYWJsZSwgd3JpdGFibGUgfTtcbn1cblxuLyoqXG4gKiBBIHJlYWRhYmxlIHN0cmVhbSByZXByZXNlbnRzIGEgc291cmNlIG9mIGRhdGEsIGZyb20gd2hpY2ggeW91IGNhbiByZWFkLlxuICpcbiAqIEBwdWJsaWNcbiAqL1xuY2xhc3MgUmVhZGFibGVTdHJlYW0ge1xuICAgIGNvbnN0cnVjdG9yKHJhd1VuZGVybHlpbmdTb3VyY2UgPSB7fSwgcmF3U3RyYXRlZ3kgPSB7fSkge1xuICAgICAgICBpZiAocmF3VW5kZXJseWluZ1NvdXJjZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICByYXdVbmRlcmx5aW5nU291cmNlID0gbnVsbDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGFzc2VydE9iamVjdChyYXdVbmRlcmx5aW5nU291cmNlLCAnRmlyc3QgcGFyYW1ldGVyJyk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3Qgc3RyYXRlZ3kgPSBjb252ZXJ0UXVldWluZ1N0cmF0ZWd5KHJhd1N0cmF0ZWd5LCAnU2Vjb25kIHBhcmFtZXRlcicpO1xuICAgICAgICBjb25zdCB1bmRlcmx5aW5nU291cmNlID0gY29udmVydFVuZGVybHlpbmdEZWZhdWx0T3JCeXRlU291cmNlKHJhd1VuZGVybHlpbmdTb3VyY2UsICdGaXJzdCBwYXJhbWV0ZXInKTtcbiAgICAgICAgSW5pdGlhbGl6ZVJlYWRhYmxlU3RyZWFtKHRoaXMpO1xuICAgICAgICBpZiAodW5kZXJseWluZ1NvdXJjZS50eXBlID09PSAnYnl0ZXMnKSB7XG4gICAgICAgICAgICBpZiAoc3RyYXRlZ3kuc2l6ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IFJhbmdlRXJyb3IoJ1RoZSBzdHJhdGVneSBmb3IgYSBieXRlIHN0cmVhbSBjYW5ub3QgaGF2ZSBhIHNpemUgZnVuY3Rpb24nKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGNvbnN0IGhpZ2hXYXRlck1hcmsgPSBFeHRyYWN0SGlnaFdhdGVyTWFyayhzdHJhdGVneSwgMCk7XG4gICAgICAgICAgICBTZXRVcFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXJGcm9tVW5kZXJseWluZ1NvdXJjZSh0aGlzLCB1bmRlcmx5aW5nU291cmNlLCBoaWdoV2F0ZXJNYXJrKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGNvbnN0IHNpemVBbGdvcml0aG0gPSBFeHRyYWN0U2l6ZUFsZ29yaXRobShzdHJhdGVneSk7XG4gICAgICAgICAgICBjb25zdCBoaWdoV2F0ZXJNYXJrID0gRXh0cmFjdEhpZ2hXYXRlck1hcmsoc3RyYXRlZ3ksIDEpO1xuICAgICAgICAgICAgU2V0VXBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRnJvbVVuZGVybHlpbmdTb3VyY2UodGhpcywgdW5kZXJseWluZ1NvdXJjZSwgaGlnaFdhdGVyTWFyaywgc2l6ZUFsZ29yaXRobSk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgLyoqXG4gICAgICogV2hldGhlciBvciBub3QgdGhlIHJlYWRhYmxlIHN0cmVhbSBpcyBsb2NrZWQgdG8gYSB7QGxpbmsgUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyIHwgcmVhZGVyfS5cbiAgICAgKi9cbiAgICBnZXQgbG9ja2VkKCkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVTdHJlYW0odGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IHN0cmVhbUJyYW5kQ2hlY2tFeGNlcHRpb24kMSgnbG9ja2VkJyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIElzUmVhZGFibGVTdHJlYW1Mb2NrZWQodGhpcyk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENhbmNlbHMgdGhlIHN0cmVhbSwgc2lnbmFsaW5nIGEgbG9zcyBvZiBpbnRlcmVzdCBpbiB0aGUgc3RyZWFtIGJ5IGEgY29uc3VtZXIuXG4gICAgICpcbiAgICAgKiBUaGUgc3VwcGxpZWQgYHJlYXNvbmAgYXJndW1lbnQgd2lsbCBiZSBnaXZlbiB0byB0aGUgdW5kZXJseWluZyBzb3VyY2UncyB7QGxpbmsgVW5kZXJseWluZ1NvdXJjZS5jYW5jZWwgfCBjYW5jZWwoKX1cbiAgICAgKiBtZXRob2QsIHdoaWNoIG1pZ2h0IG9yIG1pZ2h0IG5vdCB1c2UgaXQuXG4gICAgICovXG4gICAgY2FuY2VsKHJlYXNvbiA9IHVuZGVmaW5lZCkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVTdHJlYW0odGhpcykpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKHN0cmVhbUJyYW5kQ2hlY2tFeGNlcHRpb24kMSgnY2FuY2VsJykpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChJc1JlYWRhYmxlU3RyZWFtTG9ja2VkKHRoaXMpKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChuZXcgVHlwZUVycm9yKCdDYW5ub3QgY2FuY2VsIGEgc3RyZWFtIHRoYXQgYWxyZWFkeSBoYXMgYSByZWFkZXInKSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIFJlYWRhYmxlU3RyZWFtQ2FuY2VsKHRoaXMsIHJlYXNvbik7XG4gICAgfVxuICAgIGdldFJlYWRlcihyYXdPcHRpb25zID0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGlmICghSXNSZWFkYWJsZVN0cmVhbSh0aGlzKSkge1xuICAgICAgICAgICAgdGhyb3cgc3RyZWFtQnJhbmRDaGVja0V4Y2VwdGlvbiQxKCdnZXRSZWFkZXInKTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBvcHRpb25zID0gY29udmVydFJlYWRlck9wdGlvbnMocmF3T3B0aW9ucywgJ0ZpcnN0IHBhcmFtZXRlcicpO1xuICAgICAgICBpZiAob3B0aW9ucy5tb2RlID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHJldHVybiBBY3F1aXJlUmVhZGFibGVTdHJlYW1EZWZhdWx0UmVhZGVyKHRoaXMpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBBY3F1aXJlUmVhZGFibGVTdHJlYW1CWU9CUmVhZGVyKHRoaXMpO1xuICAgIH1cbiAgICBwaXBlVGhyb3VnaChyYXdUcmFuc2Zvcm0sIHJhd09wdGlvbnMgPSB7fSkge1xuICAgICAgICBpZiAoIUlzUmVhZGFibGVTdHJlYW0odGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IHN0cmVhbUJyYW5kQ2hlY2tFeGNlcHRpb24kMSgncGlwZVRocm91Z2gnKTtcbiAgICAgICAgfVxuICAgICAgICBhc3NlcnRSZXF1aXJlZEFyZ3VtZW50KHJhd1RyYW5zZm9ybSwgMSwgJ3BpcGVUaHJvdWdoJyk7XG4gICAgICAgIGNvbnN0IHRyYW5zZm9ybSA9IGNvbnZlcnRSZWFkYWJsZVdyaXRhYmxlUGFpcihyYXdUcmFuc2Zvcm0sICdGaXJzdCBwYXJhbWV0ZXInKTtcbiAgICAgICAgY29uc3Qgb3B0aW9ucyA9IGNvbnZlcnRQaXBlT3B0aW9ucyhyYXdPcHRpb25zLCAnU2Vjb25kIHBhcmFtZXRlcicpO1xuICAgICAgICBpZiAoSXNSZWFkYWJsZVN0cmVhbUxvY2tlZCh0aGlzKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignUmVhZGFibGVTdHJlYW0ucHJvdG90eXBlLnBpcGVUaHJvdWdoIGNhbm5vdCBiZSB1c2VkIG9uIGEgbG9ja2VkIFJlYWRhYmxlU3RyZWFtJyk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKElzV3JpdGFibGVTdHJlYW1Mb2NrZWQodHJhbnNmb3JtLndyaXRhYmxlKSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignUmVhZGFibGVTdHJlYW0ucHJvdG90eXBlLnBpcGVUaHJvdWdoIGNhbm5vdCBiZSB1c2VkIG9uIGEgbG9ja2VkIFdyaXRhYmxlU3RyZWFtJyk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgcHJvbWlzZSA9IFJlYWRhYmxlU3RyZWFtUGlwZVRvKHRoaXMsIHRyYW5zZm9ybS53cml0YWJsZSwgb3B0aW9ucy5wcmV2ZW50Q2xvc2UsIG9wdGlvbnMucHJldmVudEFib3J0LCBvcHRpb25zLnByZXZlbnRDYW5jZWwsIG9wdGlvbnMuc2lnbmFsKTtcbiAgICAgICAgc2V0UHJvbWlzZUlzSGFuZGxlZFRvVHJ1ZShwcm9taXNlKTtcbiAgICAgICAgcmV0dXJuIHRyYW5zZm9ybS5yZWFkYWJsZTtcbiAgICB9XG4gICAgcGlwZVRvKGRlc3RpbmF0aW9uLCByYXdPcHRpb25zID0ge30pIHtcbiAgICAgICAgaWYgKCFJc1JlYWRhYmxlU3RyZWFtKHRoaXMpKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChzdHJlYW1CcmFuZENoZWNrRXhjZXB0aW9uJDEoJ3BpcGVUbycpKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoZGVzdGluYXRpb24gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgoYFBhcmFtZXRlciAxIGlzIHJlcXVpcmVkIGluICdwaXBlVG8nLmApO1xuICAgICAgICB9XG4gICAgICAgIGlmICghSXNXcml0YWJsZVN0cmVhbShkZXN0aW5hdGlvbikpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKG5ldyBUeXBlRXJyb3IoYFJlYWRhYmxlU3RyZWFtLnByb3RvdHlwZS5waXBlVG8ncyBmaXJzdCBhcmd1bWVudCBtdXN0IGJlIGEgV3JpdGFibGVTdHJlYW1gKSk7XG4gICAgICAgIH1cbiAgICAgICAgbGV0IG9wdGlvbnM7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgICBvcHRpb25zID0gY29udmVydFBpcGVPcHRpb25zKHJhd09wdGlvbnMsICdTZWNvbmQgcGFyYW1ldGVyJyk7XG4gICAgICAgIH1cbiAgICAgICAgY2F0Y2ggKGUpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKGUpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChJc1JlYWRhYmxlU3RyZWFtTG9ja2VkKHRoaXMpKSB7XG4gICAgICAgICAgICByZXR1cm4gcHJvbWlzZVJlamVjdGVkV2l0aChuZXcgVHlwZUVycm9yKCdSZWFkYWJsZVN0cmVhbS5wcm90b3R5cGUucGlwZVRvIGNhbm5vdCBiZSB1c2VkIG9uIGEgbG9ja2VkIFJlYWRhYmxlU3RyZWFtJykpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChJc1dyaXRhYmxlU3RyZWFtTG9ja2VkKGRlc3RpbmF0aW9uKSkge1xuICAgICAgICAgICAgcmV0dXJuIHByb21pc2VSZWplY3RlZFdpdGgobmV3IFR5cGVFcnJvcignUmVhZGFibGVTdHJlYW0ucHJvdG90eXBlLnBpcGVUbyBjYW5ub3QgYmUgdXNlZCBvbiBhIGxvY2tlZCBXcml0YWJsZVN0cmVhbScpKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gUmVhZGFibGVTdHJlYW1QaXBlVG8odGhpcywgZGVzdGluYXRpb24sIG9wdGlvbnMucHJldmVudENsb3NlLCBvcHRpb25zLnByZXZlbnRBYm9ydCwgb3B0aW9ucy5wcmV2ZW50Q2FuY2VsLCBvcHRpb25zLnNpZ25hbCk7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFRlZXMgdGhpcyByZWFkYWJsZSBzdHJlYW0sIHJldHVybmluZyBhIHR3by1lbGVtZW50IGFycmF5IGNvbnRhaW5pbmcgdGhlIHR3byByZXN1bHRpbmcgYnJhbmNoZXMgYXNcbiAgICAgKiBuZXcge0BsaW5rIFJlYWRhYmxlU3RyZWFtfSBpbnN0YW5jZXMuXG4gICAgICpcbiAgICAgKiBUZWVpbmcgYSBzdHJlYW0gd2lsbCBsb2NrIGl0LCBwcmV2ZW50aW5nIGFueSBvdGhlciBjb25zdW1lciBmcm9tIGFjcXVpcmluZyBhIHJlYWRlci5cbiAgICAgKiBUbyBjYW5jZWwgdGhlIHN0cmVhbSwgY2FuY2VsIGJvdGggb2YgdGhlIHJlc3VsdGluZyBicmFuY2hlczsgYSBjb21wb3NpdGUgY2FuY2VsbGF0aW9uIHJlYXNvbiB3aWxsIHRoZW4gYmVcbiAgICAgKiBwcm9wYWdhdGVkIHRvIHRoZSBzdHJlYW0ncyB1bmRlcmx5aW5nIHNvdXJjZS5cbiAgICAgKlxuICAgICAqIE5vdGUgdGhhdCB0aGUgY2h1bmtzIHNlZW4gaW4gZWFjaCBicmFuY2ggd2lsbCBiZSB0aGUgc2FtZSBvYmplY3QuIElmIHRoZSBjaHVua3MgYXJlIG5vdCBpbW11dGFibGUsXG4gICAgICogdGhpcyBjb3VsZCBhbGxvdyBpbnRlcmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIGJyYW5jaGVzLlxuICAgICAqL1xuICAgIHRlZSgpIHtcbiAgICAgICAgaWYgKCFJc1JlYWRhYmxlU3RyZWFtKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBzdHJlYW1CcmFuZENoZWNrRXhjZXB0aW9uJDEoJ3RlZScpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IGJyYW5jaGVzID0gUmVhZGFibGVTdHJlYW1UZWUodGhpcyk7XG4gICAgICAgIHJldHVybiBDcmVhdGVBcnJheUZyb21MaXN0KGJyYW5jaGVzKTtcbiAgICB9XG4gICAgdmFsdWVzKHJhd09wdGlvbnMgPSB1bmRlZmluZWQpIHtcbiAgICAgICAgaWYgKCFJc1JlYWRhYmxlU3RyZWFtKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBzdHJlYW1CcmFuZENoZWNrRXhjZXB0aW9uJDEoJ3ZhbHVlcycpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IG9wdGlvbnMgPSBjb252ZXJ0SXRlcmF0b3JPcHRpb25zKHJhd09wdGlvbnMsICdGaXJzdCBwYXJhbWV0ZXInKTtcbiAgICAgICAgcmV0dXJuIEFjcXVpcmVSZWFkYWJsZVN0cmVhbUFzeW5jSXRlcmF0b3IodGhpcywgb3B0aW9ucy5wcmV2ZW50Q2FuY2VsKTtcbiAgICB9XG59XG5PYmplY3QuZGVmaW5lUHJvcGVydGllcyhSZWFkYWJsZVN0cmVhbS5wcm90b3R5cGUsIHtcbiAgICBjYW5jZWw6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIGdldFJlYWRlcjogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgcGlwZVRocm91Z2g6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIHBpcGVUbzogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgdGVlOiB7IGVudW1lcmFibGU6IHRydWUgfSxcbiAgICB2YWx1ZXM6IHsgZW51bWVyYWJsZTogdHJ1ZSB9LFxuICAgIGxvY2tlZDogeyBlbnVtZXJhYmxlOiB0cnVlIH1cbn0pO1xuaWYgKHR5cGVvZiBTeW1ib2xQb2x5ZmlsbC50b1N0cmluZ1RhZyA9PT0gJ3N5bWJvbCcpIHtcbiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoUmVhZGFibGVTdHJlYW0ucHJvdG90eXBlLCBTeW1ib2xQb2x5ZmlsbC50b1N0cmluZ1RhZywge1xuICAgICAgICB2YWx1ZTogJ1JlYWRhYmxlU3RyZWFtJyxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlXG4gICAgfSk7XG59XG5pZiAodHlwZW9mIFN5bWJvbFBvbHlmaWxsLmFzeW5jSXRlcmF0b3IgPT09ICdzeW1ib2wnKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KFJlYWRhYmxlU3RyZWFtLnByb3RvdHlwZSwgU3ltYm9sUG9seWZpbGwuYXN5bmNJdGVyYXRvciwge1xuICAgICAgICB2YWx1ZTogUmVhZGFibGVTdHJlYW0ucHJvdG90eXBlLnZhbHVlcyxcbiAgICAgICAgd3JpdGFibGU6IHRydWUsXG4gICAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZVxuICAgIH0pO1xufVxuLy8gQWJzdHJhY3Qgb3BlcmF0aW9ucyBmb3IgdGhlIFJlYWRhYmxlU3RyZWFtLlxuLy8gVGhyb3dzIGlmIGFuZCBvbmx5IGlmIHN0YXJ0QWxnb3JpdGhtIHRocm93cy5cbmZ1bmN0aW9uIENyZWF0ZVJlYWRhYmxlU3RyZWFtKHN0YXJ0QWxnb3JpdGhtLCBwdWxsQWxnb3JpdGhtLCBjYW5jZWxBbGdvcml0aG0sIGhpZ2hXYXRlck1hcmsgPSAxLCBzaXplQWxnb3JpdGhtID0gKCkgPT4gMSkge1xuICAgIGNvbnN0IHN0cmVhbSA9IE9iamVjdC5jcmVhdGUoUmVhZGFibGVTdHJlYW0ucHJvdG90eXBlKTtcbiAgICBJbml0aWFsaXplUmVhZGFibGVTdHJlYW0oc3RyZWFtKTtcbiAgICBjb25zdCBjb250cm9sbGVyID0gT2JqZWN0LmNyZWF0ZShSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyLnByb3RvdHlwZSk7XG4gICAgU2V0VXBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyKHN0cmVhbSwgY29udHJvbGxlciwgc3RhcnRBbGdvcml0aG0sIHB1bGxBbGdvcml0aG0sIGNhbmNlbEFsZ29yaXRobSwgaGlnaFdhdGVyTWFyaywgc2l6ZUFsZ29yaXRobSk7XG4gICAgcmV0dXJuIHN0cmVhbTtcbn1cbmZ1bmN0aW9uIEluaXRpYWxpemVSZWFkYWJsZVN0cmVhbShzdHJlYW0pIHtcbiAgICBzdHJlYW0uX3N0YXRlID0gJ3JlYWRhYmxlJztcbiAgICBzdHJlYW0uX3JlYWRlciA9IHVuZGVmaW5lZDtcbiAgICBzdHJlYW0uX3N0b3JlZEVycm9yID0gdW5kZWZpbmVkO1xuICAgIHN0cmVhbS5fZGlzdHVyYmVkID0gZmFsc2U7XG59XG5mdW5jdGlvbiBJc1JlYWRhYmxlU3RyZWFtKHgpIHtcbiAgICBpZiAoIXR5cGVJc09iamVjdCh4KSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmICghT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHgsICdfcmVhZGFibGVTdHJlYW1Db250cm9sbGVyJykpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICByZXR1cm4gdHJ1ZTtcbn1cbmZ1bmN0aW9uIElzUmVhZGFibGVTdHJlYW1Mb2NrZWQoc3RyZWFtKSB7XG4gICAgaWYgKHN0cmVhbS5fcmVhZGVyID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICByZXR1cm4gdHJ1ZTtcbn1cbi8vIFJlYWRhYmxlU3RyZWFtIEFQSSBleHBvc2VkIGZvciBjb250cm9sbGVycy5cbmZ1bmN0aW9uIFJlYWRhYmxlU3RyZWFtQ2FuY2VsKHN0cmVhbSwgcmVhc29uKSB7XG4gICAgc3RyZWFtLl9kaXN0dXJiZWQgPSB0cnVlO1xuICAgIGlmIChzdHJlYW0uX3N0YXRlID09PSAnY2xvc2VkJykge1xuICAgICAgICByZXR1cm4gcHJvbWlzZVJlc29sdmVkV2l0aCh1bmRlZmluZWQpO1xuICAgIH1cbiAgICBpZiAoc3RyZWFtLl9zdGF0ZSA9PT0gJ2Vycm9yZWQnKSB7XG4gICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKHN0cmVhbS5fc3RvcmVkRXJyb3IpO1xuICAgIH1cbiAgICBSZWFkYWJsZVN0cmVhbUNsb3NlKHN0cmVhbSk7XG4gICAgY29uc3Qgc291cmNlQ2FuY2VsUHJvbWlzZSA9IHN0cmVhbS5fcmVhZGFibGVTdHJlYW1Db250cm9sbGVyW0NhbmNlbFN0ZXBzXShyZWFzb24pO1xuICAgIHJldHVybiB0cmFuc2Zvcm1Qcm9taXNlV2l0aChzb3VyY2VDYW5jZWxQcm9taXNlLCBub29wKTtcbn1cbmZ1bmN0aW9uIFJlYWRhYmxlU3RyZWFtQ2xvc2Uoc3RyZWFtKSB7XG4gICAgc3RyZWFtLl9zdGF0ZSA9ICdjbG9zZWQnO1xuICAgIGNvbnN0IHJlYWRlciA9IHN0cmVhbS5fcmVhZGVyO1xuICAgIGlmIChyZWFkZXIgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChJc1JlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlcihyZWFkZXIpKSB7XG4gICAgICAgIHJlYWRlci5fcmVhZFJlcXVlc3RzLmZvckVhY2gocmVhZFJlcXVlc3QgPT4ge1xuICAgICAgICAgICAgcmVhZFJlcXVlc3QuX2Nsb3NlU3RlcHMoKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHJlYWRlci5fcmVhZFJlcXVlc3RzID0gbmV3IFNpbXBsZVF1ZXVlKCk7XG4gICAgfVxuICAgIGRlZmF1bHRSZWFkZXJDbG9zZWRQcm9taXNlUmVzb2x2ZShyZWFkZXIpO1xufVxuZnVuY3Rpb24gUmVhZGFibGVTdHJlYW1FcnJvcihzdHJlYW0sIGUpIHtcbiAgICBzdHJlYW0uX3N0YXRlID0gJ2Vycm9yZWQnO1xuICAgIHN0cmVhbS5fc3RvcmVkRXJyb3IgPSBlO1xuICAgIGNvbnN0IHJlYWRlciA9IHN0cmVhbS5fcmVhZGVyO1xuICAgIGlmIChyZWFkZXIgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChJc1JlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlcihyZWFkZXIpKSB7XG4gICAgICAgIHJlYWRlci5fcmVhZFJlcXVlc3RzLmZvckVhY2gocmVhZFJlcXVlc3QgPT4ge1xuICAgICAgICAgICAgcmVhZFJlcXVlc3QuX2Vycm9yU3RlcHMoZSk7XG4gICAgICAgIH0pO1xuICAgICAgICByZWFkZXIuX3JlYWRSZXF1ZXN0cyA9IG5ldyBTaW1wbGVRdWV1ZSgpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgcmVhZGVyLl9yZWFkSW50b1JlcXVlc3RzLmZvckVhY2gocmVhZEludG9SZXF1ZXN0ID0+IHtcbiAgICAgICAgICAgIHJlYWRJbnRvUmVxdWVzdC5fZXJyb3JTdGVwcyhlKTtcbiAgICAgICAgfSk7XG4gICAgICAgIHJlYWRlci5fcmVhZEludG9SZXF1ZXN0cyA9IG5ldyBTaW1wbGVRdWV1ZSgpO1xuICAgIH1cbiAgICBkZWZhdWx0UmVhZGVyQ2xvc2VkUHJvbWlzZVJlamVjdChyZWFkZXIsIGUpO1xufVxuLy8gSGVscGVyIGZ1bmN0aW9ucyBmb3IgdGhlIFJlYWRhYmxlU3RyZWFtLlxuZnVuY3Rpb24gc3RyZWFtQnJhbmRDaGVja0V4Y2VwdGlvbiQxKG5hbWUpIHtcbiAgICByZXR1cm4gbmV3IFR5cGVFcnJvcihgUmVhZGFibGVTdHJlYW0ucHJvdG90eXBlLiR7bmFtZX0gY2FuIG9ubHkgYmUgdXNlZCBvbiBhIFJlYWRhYmxlU3RyZWFtYCk7XG59XG5cbmZ1bmN0aW9uIGNvbnZlcnRRdWV1aW5nU3RyYXRlZ3lJbml0KGluaXQsIGNvbnRleHQpIHtcbiAgICBhc3NlcnREaWN0aW9uYXJ5KGluaXQsIGNvbnRleHQpO1xuICAgIGNvbnN0IGhpZ2hXYXRlck1hcmsgPSBpbml0ID09PSBudWxsIHx8IGluaXQgPT09IHZvaWQgMCA/IHZvaWQgMCA6IGluaXQuaGlnaFdhdGVyTWFyaztcbiAgICBhc3NlcnRSZXF1aXJlZEZpZWxkKGhpZ2hXYXRlck1hcmssICdoaWdoV2F0ZXJNYXJrJywgJ1F1ZXVpbmdTdHJhdGVneUluaXQnKTtcbiAgICByZXR1cm4ge1xuICAgICAgICBoaWdoV2F0ZXJNYXJrOiBjb252ZXJ0VW5yZXN0cmljdGVkRG91YmxlKGhpZ2hXYXRlck1hcmspXG4gICAgfTtcbn1cblxuY29uc3QgYnl0ZUxlbmd0aFNpemVGdW5jdGlvbiA9IGZ1bmN0aW9uIHNpemUoY2h1bmspIHtcbiAgICByZXR1cm4gY2h1bmsuYnl0ZUxlbmd0aDtcbn07XG4vKipcbiAqIEEgcXVldWluZyBzdHJhdGVneSB0aGF0IGNvdW50cyB0aGUgbnVtYmVyIG9mIGJ5dGVzIGluIGVhY2ggY2h1bmsuXG4gKlxuICogQHB1YmxpY1xuICovXG5jbGFzcyBCeXRlTGVuZ3RoUXVldWluZ1N0cmF0ZWd5IHtcbiAgICBjb25zdHJ1Y3RvcihvcHRpb25zKSB7XG4gICAgICAgIGFzc2VydFJlcXVpcmVkQXJndW1lbnQob3B0aW9ucywgMSwgJ0J5dGVMZW5ndGhRdWV1aW5nU3RyYXRlZ3knKTtcbiAgICAgICAgb3B0aW9ucyA9IGNvbnZlcnRRdWV1aW5nU3RyYXRlZ3lJbml0KG9wdGlvbnMsICdGaXJzdCBwYXJhbWV0ZXInKTtcbiAgICAgICAgdGhpcy5fYnl0ZUxlbmd0aFF1ZXVpbmdTdHJhdGVneUhpZ2hXYXRlck1hcmsgPSBvcHRpb25zLmhpZ2hXYXRlck1hcms7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFJldHVybnMgdGhlIGhpZ2ggd2F0ZXIgbWFyayBwcm92aWRlZCB0byB0aGUgY29uc3RydWN0b3IuXG4gICAgICovXG4gICAgZ2V0IGhpZ2hXYXRlck1hcmsoKSB7XG4gICAgICAgIGlmICghSXNCeXRlTGVuZ3RoUXVldWluZ1N0cmF0ZWd5KHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBieXRlTGVuZ3RoQnJhbmRDaGVja0V4Y2VwdGlvbignaGlnaFdhdGVyTWFyaycpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLl9ieXRlTGVuZ3RoUXVldWluZ1N0cmF0ZWd5SGlnaFdhdGVyTWFyaztcbiAgICB9XG4gICAgLyoqXG4gICAgICogTWVhc3VyZXMgdGhlIHNpemUgb2YgYGNodW5rYCBieSByZXR1cm5pbmcgdGhlIHZhbHVlIG9mIGl0cyBgYnl0ZUxlbmd0aGAgcHJvcGVydHkuXG4gICAgICovXG4gICAgZ2V0IHNpemUoKSB7XG4gICAgICAgIGlmICghSXNCeXRlTGVuZ3RoUXVldWluZ1N0cmF0ZWd5KHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBieXRlTGVuZ3RoQnJhbmRDaGVja0V4Y2VwdGlvbignc2l6ZScpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBieXRlTGVuZ3RoU2l6ZUZ1bmN0aW9uO1xuICAgIH1cbn1cbk9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKEJ5dGVMZW5ndGhRdWV1aW5nU3RyYXRlZ3kucHJvdG90eXBlLCB7XG4gICAgaGlnaFdhdGVyTWFyazogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgc2l6ZTogeyBlbnVtZXJhYmxlOiB0cnVlIH1cbn0pO1xuaWYgKHR5cGVvZiBTeW1ib2xQb2x5ZmlsbC50b1N0cmluZ1RhZyA9PT0gJ3N5bWJvbCcpIHtcbiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoQnl0ZUxlbmd0aFF1ZXVpbmdTdHJhdGVneS5wcm90b3R5cGUsIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnLCB7XG4gICAgICAgIHZhbHVlOiAnQnl0ZUxlbmd0aFF1ZXVpbmdTdHJhdGVneScsXG4gICAgICAgIGNvbmZpZ3VyYWJsZTogdHJ1ZVxuICAgIH0pO1xufVxuLy8gSGVscGVyIGZ1bmN0aW9ucyBmb3IgdGhlIEJ5dGVMZW5ndGhRdWV1aW5nU3RyYXRlZ3kuXG5mdW5jdGlvbiBieXRlTGVuZ3RoQnJhbmRDaGVja0V4Y2VwdGlvbihuYW1lKSB7XG4gICAgcmV0dXJuIG5ldyBUeXBlRXJyb3IoYEJ5dGVMZW5ndGhRdWV1aW5nU3RyYXRlZ3kucHJvdG90eXBlLiR7bmFtZX0gY2FuIG9ubHkgYmUgdXNlZCBvbiBhIEJ5dGVMZW5ndGhRdWV1aW5nU3RyYXRlZ3lgKTtcbn1cbmZ1bmN0aW9uIElzQnl0ZUxlbmd0aFF1ZXVpbmdTdHJhdGVneSh4KSB7XG4gICAgaWYgKCF0eXBlSXNPYmplY3QoeCkpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBpZiAoIU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbCh4LCAnX2J5dGVMZW5ndGhRdWV1aW5nU3RyYXRlZ3lIaWdoV2F0ZXJNYXJrJykpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICByZXR1cm4gdHJ1ZTtcbn1cblxuY29uc3QgY291bnRTaXplRnVuY3Rpb24gPSBmdW5jdGlvbiBzaXplKCkge1xuICAgIHJldHVybiAxO1xufTtcbi8qKlxuICogQSBxdWV1aW5nIHN0cmF0ZWd5IHRoYXQgY291bnRzIHRoZSBudW1iZXIgb2YgY2h1bmtzLlxuICpcbiAqIEBwdWJsaWNcbiAqL1xuY2xhc3MgQ291bnRRdWV1aW5nU3RyYXRlZ3kge1xuICAgIGNvbnN0cnVjdG9yKG9wdGlvbnMpIHtcbiAgICAgICAgYXNzZXJ0UmVxdWlyZWRBcmd1bWVudChvcHRpb25zLCAxLCAnQ291bnRRdWV1aW5nU3RyYXRlZ3knKTtcbiAgICAgICAgb3B0aW9ucyA9IGNvbnZlcnRRdWV1aW5nU3RyYXRlZ3lJbml0KG9wdGlvbnMsICdGaXJzdCBwYXJhbWV0ZXInKTtcbiAgICAgICAgdGhpcy5fY291bnRRdWV1aW5nU3RyYXRlZ3lIaWdoV2F0ZXJNYXJrID0gb3B0aW9ucy5oaWdoV2F0ZXJNYXJrO1xuICAgIH1cbiAgICAvKipcbiAgICAgKiBSZXR1cm5zIHRoZSBoaWdoIHdhdGVyIG1hcmsgcHJvdmlkZWQgdG8gdGhlIGNvbnN0cnVjdG9yLlxuICAgICAqL1xuICAgIGdldCBoaWdoV2F0ZXJNYXJrKCkge1xuICAgICAgICBpZiAoIUlzQ291bnRRdWV1aW5nU3RyYXRlZ3kodGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IGNvdW50QnJhbmRDaGVja0V4Y2VwdGlvbignaGlnaFdhdGVyTWFyaycpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLl9jb3VudFF1ZXVpbmdTdHJhdGVneUhpZ2hXYXRlck1hcms7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIE1lYXN1cmVzIHRoZSBzaXplIG9mIGBjaHVua2AgYnkgYWx3YXlzIHJldHVybmluZyAxLlxuICAgICAqIFRoaXMgZW5zdXJlcyB0aGF0IHRoZSB0b3RhbCBxdWV1ZSBzaXplIGlzIGEgY291bnQgb2YgdGhlIG51bWJlciBvZiBjaHVua3MgaW4gdGhlIHF1ZXVlLlxuICAgICAqL1xuICAgIGdldCBzaXplKCkge1xuICAgICAgICBpZiAoIUlzQ291bnRRdWV1aW5nU3RyYXRlZ3kodGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IGNvdW50QnJhbmRDaGVja0V4Y2VwdGlvbignc2l6ZScpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBjb3VudFNpemVGdW5jdGlvbjtcbiAgICB9XG59XG5PYmplY3QuZGVmaW5lUHJvcGVydGllcyhDb3VudFF1ZXVpbmdTdHJhdGVneS5wcm90b3R5cGUsIHtcbiAgICBoaWdoV2F0ZXJNYXJrOiB7IGVudW1lcmFibGU6IHRydWUgfSxcbiAgICBzaXplOiB7IGVudW1lcmFibGU6IHRydWUgfVxufSk7XG5pZiAodHlwZW9mIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnID09PSAnc3ltYm9sJykge1xuICAgIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShDb3VudFF1ZXVpbmdTdHJhdGVneS5wcm90b3R5cGUsIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnLCB7XG4gICAgICAgIHZhbHVlOiAnQ291bnRRdWV1aW5nU3RyYXRlZ3knLFxuICAgICAgICBjb25maWd1cmFibGU6IHRydWVcbiAgICB9KTtcbn1cbi8vIEhlbHBlciBmdW5jdGlvbnMgZm9yIHRoZSBDb3VudFF1ZXVpbmdTdHJhdGVneS5cbmZ1bmN0aW9uIGNvdW50QnJhbmRDaGVja0V4Y2VwdGlvbihuYW1lKSB7XG4gICAgcmV0dXJuIG5ldyBUeXBlRXJyb3IoYENvdW50UXVldWluZ1N0cmF0ZWd5LnByb3RvdHlwZS4ke25hbWV9IGNhbiBvbmx5IGJlIHVzZWQgb24gYSBDb3VudFF1ZXVpbmdTdHJhdGVneWApO1xufVxuZnVuY3Rpb24gSXNDb3VudFF1ZXVpbmdTdHJhdGVneSh4KSB7XG4gICAgaWYgKCF0eXBlSXNPYmplY3QoeCkpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBpZiAoIU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbCh4LCAnX2NvdW50UXVldWluZ1N0cmF0ZWd5SGlnaFdhdGVyTWFyaycpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG59XG5cbmZ1bmN0aW9uIGNvbnZlcnRUcmFuc2Zvcm1lcihvcmlnaW5hbCwgY29udGV4dCkge1xuICAgIGFzc2VydERpY3Rpb25hcnkob3JpZ2luYWwsIGNvbnRleHQpO1xuICAgIGNvbnN0IGZsdXNoID0gb3JpZ2luYWwgPT09IG51bGwgfHwgb3JpZ2luYWwgPT09IHZvaWQgMCA/IHZvaWQgMCA6IG9yaWdpbmFsLmZsdXNoO1xuICAgIGNvbnN0IHJlYWRhYmxlVHlwZSA9IG9yaWdpbmFsID09PSBudWxsIHx8IG9yaWdpbmFsID09PSB2b2lkIDAgPyB2b2lkIDAgOiBvcmlnaW5hbC5yZWFkYWJsZVR5cGU7XG4gICAgY29uc3Qgc3RhcnQgPSBvcmlnaW5hbCA9PT0gbnVsbCB8fCBvcmlnaW5hbCA9PT0gdm9pZCAwID8gdm9pZCAwIDogb3JpZ2luYWwuc3RhcnQ7XG4gICAgY29uc3QgdHJhbnNmb3JtID0gb3JpZ2luYWwgPT09IG51bGwgfHwgb3JpZ2luYWwgPT09IHZvaWQgMCA/IHZvaWQgMCA6IG9yaWdpbmFsLnRyYW5zZm9ybTtcbiAgICBjb25zdCB3cml0YWJsZVR5cGUgPSBvcmlnaW5hbCA9PT0gbnVsbCB8fCBvcmlnaW5hbCA9PT0gdm9pZCAwID8gdm9pZCAwIDogb3JpZ2luYWwud3JpdGFibGVUeXBlO1xuICAgIHJldHVybiB7XG4gICAgICAgIGZsdXNoOiBmbHVzaCA9PT0gdW5kZWZpbmVkID9cbiAgICAgICAgICAgIHVuZGVmaW5lZCA6XG4gICAgICAgICAgICBjb252ZXJ0VHJhbnNmb3JtZXJGbHVzaENhbGxiYWNrKGZsdXNoLCBvcmlnaW5hbCwgYCR7Y29udGV4dH0gaGFzIG1lbWJlciAnZmx1c2gnIHRoYXRgKSxcbiAgICAgICAgcmVhZGFibGVUeXBlLFxuICAgICAgICBzdGFydDogc3RhcnQgPT09IHVuZGVmaW5lZCA/XG4gICAgICAgICAgICB1bmRlZmluZWQgOlxuICAgICAgICAgICAgY29udmVydFRyYW5zZm9ybWVyU3RhcnRDYWxsYmFjayhzdGFydCwgb3JpZ2luYWwsIGAke2NvbnRleHR9IGhhcyBtZW1iZXIgJ3N0YXJ0JyB0aGF0YCksXG4gICAgICAgIHRyYW5zZm9ybTogdHJhbnNmb3JtID09PSB1bmRlZmluZWQgP1xuICAgICAgICAgICAgdW5kZWZpbmVkIDpcbiAgICAgICAgICAgIGNvbnZlcnRUcmFuc2Zvcm1lclRyYW5zZm9ybUNhbGxiYWNrKHRyYW5zZm9ybSwgb3JpZ2luYWwsIGAke2NvbnRleHR9IGhhcyBtZW1iZXIgJ3RyYW5zZm9ybScgdGhhdGApLFxuICAgICAgICB3cml0YWJsZVR5cGVcbiAgICB9O1xufVxuZnVuY3Rpb24gY29udmVydFRyYW5zZm9ybWVyRmx1c2hDYWxsYmFjayhmbiwgb3JpZ2luYWwsIGNvbnRleHQpIHtcbiAgICBhc3NlcnRGdW5jdGlvbihmbiwgY29udGV4dCk7XG4gICAgcmV0dXJuIChjb250cm9sbGVyKSA9PiBwcm9taXNlQ2FsbChmbiwgb3JpZ2luYWwsIFtjb250cm9sbGVyXSk7XG59XG5mdW5jdGlvbiBjb252ZXJ0VHJhbnNmb3JtZXJTdGFydENhbGxiYWNrKGZuLCBvcmlnaW5hbCwgY29udGV4dCkge1xuICAgIGFzc2VydEZ1bmN0aW9uKGZuLCBjb250ZXh0KTtcbiAgICByZXR1cm4gKGNvbnRyb2xsZXIpID0+IHJlZmxlY3RDYWxsKGZuLCBvcmlnaW5hbCwgW2NvbnRyb2xsZXJdKTtcbn1cbmZ1bmN0aW9uIGNvbnZlcnRUcmFuc2Zvcm1lclRyYW5zZm9ybUNhbGxiYWNrKGZuLCBvcmlnaW5hbCwgY29udGV4dCkge1xuICAgIGFzc2VydEZ1bmN0aW9uKGZuLCBjb250ZXh0KTtcbiAgICByZXR1cm4gKGNodW5rLCBjb250cm9sbGVyKSA9PiBwcm9taXNlQ2FsbChmbiwgb3JpZ2luYWwsIFtjaHVuaywgY29udHJvbGxlcl0pO1xufVxuXG4vLyBDbGFzcyBUcmFuc2Zvcm1TdHJlYW1cbi8qKlxuICogQSB0cmFuc2Zvcm0gc3RyZWFtIGNvbnNpc3RzIG9mIGEgcGFpciBvZiBzdHJlYW1zOiBhIHtAbGluayBXcml0YWJsZVN0cmVhbSB8IHdyaXRhYmxlIHN0cmVhbX0sXG4gKiBrbm93biBhcyBpdHMgd3JpdGFibGUgc2lkZSwgYW5kIGEge0BsaW5rIFJlYWRhYmxlU3RyZWFtIHwgcmVhZGFibGUgc3RyZWFtfSwga25vd24gYXMgaXRzIHJlYWRhYmxlIHNpZGUuXG4gKiBJbiBhIG1hbm5lciBzcGVjaWZpYyB0byB0aGUgdHJhbnNmb3JtIHN0cmVhbSBpbiBxdWVzdGlvbiwgd3JpdGVzIHRvIHRoZSB3cml0YWJsZSBzaWRlIHJlc3VsdCBpbiBuZXcgZGF0YSBiZWluZ1xuICogbWFkZSBhdmFpbGFibGUgZm9yIHJlYWRpbmcgZnJvbSB0aGUgcmVhZGFibGUgc2lkZS5cbiAqXG4gKiBAcHVibGljXG4gKi9cbmNsYXNzIFRyYW5zZm9ybVN0cmVhbSB7XG4gICAgY29uc3RydWN0b3IocmF3VHJhbnNmb3JtZXIgPSB7fSwgcmF3V3JpdGFibGVTdHJhdGVneSA9IHt9LCByYXdSZWFkYWJsZVN0cmF0ZWd5ID0ge30pIHtcbiAgICAgICAgaWYgKHJhd1RyYW5zZm9ybWVyID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHJhd1RyYW5zZm9ybWVyID0gbnVsbDtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCB3cml0YWJsZVN0cmF0ZWd5ID0gY29udmVydFF1ZXVpbmdTdHJhdGVneShyYXdXcml0YWJsZVN0cmF0ZWd5LCAnU2Vjb25kIHBhcmFtZXRlcicpO1xuICAgICAgICBjb25zdCByZWFkYWJsZVN0cmF0ZWd5ID0gY29udmVydFF1ZXVpbmdTdHJhdGVneShyYXdSZWFkYWJsZVN0cmF0ZWd5LCAnVGhpcmQgcGFyYW1ldGVyJyk7XG4gICAgICAgIGNvbnN0IHRyYW5zZm9ybWVyID0gY29udmVydFRyYW5zZm9ybWVyKHJhd1RyYW5zZm9ybWVyLCAnRmlyc3QgcGFyYW1ldGVyJyk7XG4gICAgICAgIGlmICh0cmFuc2Zvcm1lci5yZWFkYWJsZVR5cGUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFJhbmdlRXJyb3IoJ0ludmFsaWQgcmVhZGFibGVUeXBlIHNwZWNpZmllZCcpO1xuICAgICAgICB9XG4gICAgICAgIGlmICh0cmFuc2Zvcm1lci53cml0YWJsZVR5cGUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IFJhbmdlRXJyb3IoJ0ludmFsaWQgd3JpdGFibGVUeXBlIHNwZWNpZmllZCcpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHJlYWRhYmxlSGlnaFdhdGVyTWFyayA9IEV4dHJhY3RIaWdoV2F0ZXJNYXJrKHJlYWRhYmxlU3RyYXRlZ3ksIDApO1xuICAgICAgICBjb25zdCByZWFkYWJsZVNpemVBbGdvcml0aG0gPSBFeHRyYWN0U2l6ZUFsZ29yaXRobShyZWFkYWJsZVN0cmF0ZWd5KTtcbiAgICAgICAgY29uc3Qgd3JpdGFibGVIaWdoV2F0ZXJNYXJrID0gRXh0cmFjdEhpZ2hXYXRlck1hcmsod3JpdGFibGVTdHJhdGVneSwgMSk7XG4gICAgICAgIGNvbnN0IHdyaXRhYmxlU2l6ZUFsZ29yaXRobSA9IEV4dHJhY3RTaXplQWxnb3JpdGhtKHdyaXRhYmxlU3RyYXRlZ3kpO1xuICAgICAgICBsZXQgc3RhcnRQcm9taXNlX3Jlc29sdmU7XG4gICAgICAgIGNvbnN0IHN0YXJ0UHJvbWlzZSA9IG5ld1Byb21pc2UocmVzb2x2ZSA9PiB7XG4gICAgICAgICAgICBzdGFydFByb21pc2VfcmVzb2x2ZSA9IHJlc29sdmU7XG4gICAgICAgIH0pO1xuICAgICAgICBJbml0aWFsaXplVHJhbnNmb3JtU3RyZWFtKHRoaXMsIHN0YXJ0UHJvbWlzZSwgd3JpdGFibGVIaWdoV2F0ZXJNYXJrLCB3cml0YWJsZVNpemVBbGdvcml0aG0sIHJlYWRhYmxlSGlnaFdhdGVyTWFyaywgcmVhZGFibGVTaXplQWxnb3JpdGhtKTtcbiAgICAgICAgU2V0VXBUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0Q29udHJvbGxlckZyb21UcmFuc2Zvcm1lcih0aGlzLCB0cmFuc2Zvcm1lcik7XG4gICAgICAgIGlmICh0cmFuc2Zvcm1lci5zdGFydCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICBzdGFydFByb21pc2VfcmVzb2x2ZSh0cmFuc2Zvcm1lci5zdGFydCh0aGlzLl90cmFuc2Zvcm1TdHJlYW1Db250cm9sbGVyKSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBzdGFydFByb21pc2VfcmVzb2x2ZSh1bmRlZmluZWQpO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8qKlxuICAgICAqIFRoZSByZWFkYWJsZSBzaWRlIG9mIHRoZSB0cmFuc2Zvcm0gc3RyZWFtLlxuICAgICAqL1xuICAgIGdldCByZWFkYWJsZSgpIHtcbiAgICAgICAgaWYgKCFJc1RyYW5zZm9ybVN0cmVhbSh0aGlzKSkge1xuICAgICAgICAgICAgdGhyb3cgc3RyZWFtQnJhbmRDaGVja0V4Y2VwdGlvbiQyKCdyZWFkYWJsZScpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB0aGlzLl9yZWFkYWJsZTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogVGhlIHdyaXRhYmxlIHNpZGUgb2YgdGhlIHRyYW5zZm9ybSBzdHJlYW0uXG4gICAgICovXG4gICAgZ2V0IHdyaXRhYmxlKCkge1xuICAgICAgICBpZiAoIUlzVHJhbnNmb3JtU3RyZWFtKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBzdHJlYW1CcmFuZENoZWNrRXhjZXB0aW9uJDIoJ3dyaXRhYmxlJyk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHRoaXMuX3dyaXRhYmxlO1xuICAgIH1cbn1cbk9iamVjdC5kZWZpbmVQcm9wZXJ0aWVzKFRyYW5zZm9ybVN0cmVhbS5wcm90b3R5cGUsIHtcbiAgICByZWFkYWJsZTogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgd3JpdGFibGU6IHsgZW51bWVyYWJsZTogdHJ1ZSB9XG59KTtcbmlmICh0eXBlb2YgU3ltYm9sUG9seWZpbGwudG9TdHJpbmdUYWcgPT09ICdzeW1ib2wnKSB7XG4gICAgT2JqZWN0LmRlZmluZVByb3BlcnR5KFRyYW5zZm9ybVN0cmVhbS5wcm90b3R5cGUsIFN5bWJvbFBvbHlmaWxsLnRvU3RyaW5nVGFnLCB7XG4gICAgICAgIHZhbHVlOiAnVHJhbnNmb3JtU3RyZWFtJyxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlXG4gICAgfSk7XG59XG5mdW5jdGlvbiBJbml0aWFsaXplVHJhbnNmb3JtU3RyZWFtKHN0cmVhbSwgc3RhcnRQcm9taXNlLCB3cml0YWJsZUhpZ2hXYXRlck1hcmssIHdyaXRhYmxlU2l6ZUFsZ29yaXRobSwgcmVhZGFibGVIaWdoV2F0ZXJNYXJrLCByZWFkYWJsZVNpemVBbGdvcml0aG0pIHtcbiAgICBmdW5jdGlvbiBzdGFydEFsZ29yaXRobSgpIHtcbiAgICAgICAgcmV0dXJuIHN0YXJ0UHJvbWlzZTtcbiAgICB9XG4gICAgZnVuY3Rpb24gd3JpdGVBbGdvcml0aG0oY2h1bmspIHtcbiAgICAgICAgcmV0dXJuIFRyYW5zZm9ybVN0cmVhbURlZmF1bHRTaW5rV3JpdGVBbGdvcml0aG0oc3RyZWFtLCBjaHVuayk7XG4gICAgfVxuICAgIGZ1bmN0aW9uIGFib3J0QWxnb3JpdGhtKHJlYXNvbikge1xuICAgICAgICByZXR1cm4gVHJhbnNmb3JtU3RyZWFtRGVmYXVsdFNpbmtBYm9ydEFsZ29yaXRobShzdHJlYW0sIHJlYXNvbik7XG4gICAgfVxuICAgIGZ1bmN0aW9uIGNsb3NlQWxnb3JpdGhtKCkge1xuICAgICAgICByZXR1cm4gVHJhbnNmb3JtU3RyZWFtRGVmYXVsdFNpbmtDbG9zZUFsZ29yaXRobShzdHJlYW0pO1xuICAgIH1cbiAgICBzdHJlYW0uX3dyaXRhYmxlID0gQ3JlYXRlV3JpdGFibGVTdHJlYW0oc3RhcnRBbGdvcml0aG0sIHdyaXRlQWxnb3JpdGhtLCBjbG9zZUFsZ29yaXRobSwgYWJvcnRBbGdvcml0aG0sIHdyaXRhYmxlSGlnaFdhdGVyTWFyaywgd3JpdGFibGVTaXplQWxnb3JpdGhtKTtcbiAgICBmdW5jdGlvbiBwdWxsQWxnb3JpdGhtKCkge1xuICAgICAgICByZXR1cm4gVHJhbnNmb3JtU3RyZWFtRGVmYXVsdFNvdXJjZVB1bGxBbGdvcml0aG0oc3RyZWFtKTtcbiAgICB9XG4gICAgZnVuY3Rpb24gY2FuY2VsQWxnb3JpdGhtKHJlYXNvbikge1xuICAgICAgICBUcmFuc2Zvcm1TdHJlYW1FcnJvcldyaXRhYmxlQW5kVW5ibG9ja1dyaXRlKHN0cmVhbSwgcmVhc29uKTtcbiAgICAgICAgcmV0dXJuIHByb21pc2VSZXNvbHZlZFdpdGgodW5kZWZpbmVkKTtcbiAgICB9XG4gICAgc3RyZWFtLl9yZWFkYWJsZSA9IENyZWF0ZVJlYWRhYmxlU3RyZWFtKHN0YXJ0QWxnb3JpdGhtLCBwdWxsQWxnb3JpdGhtLCBjYW5jZWxBbGdvcml0aG0sIHJlYWRhYmxlSGlnaFdhdGVyTWFyaywgcmVhZGFibGVTaXplQWxnb3JpdGhtKTtcbiAgICAvLyBUaGUgW1tiYWNrcHJlc3N1cmVdXSBzbG90IGlzIHNldCB0byB1bmRlZmluZWQgc28gdGhhdCBpdCBjYW4gYmUgaW5pdGlhbGlzZWQgYnkgVHJhbnNmb3JtU3RyZWFtU2V0QmFja3ByZXNzdXJlLlxuICAgIHN0cmVhbS5fYmFja3ByZXNzdXJlID0gdW5kZWZpbmVkO1xuICAgIHN0cmVhbS5fYmFja3ByZXNzdXJlQ2hhbmdlUHJvbWlzZSA9IHVuZGVmaW5lZDtcbiAgICBzdHJlYW0uX2JhY2twcmVzc3VyZUNoYW5nZVByb21pc2VfcmVzb2x2ZSA9IHVuZGVmaW5lZDtcbiAgICBUcmFuc2Zvcm1TdHJlYW1TZXRCYWNrcHJlc3N1cmUoc3RyZWFtLCB0cnVlKTtcbiAgICBzdHJlYW0uX3RyYW5zZm9ybVN0cmVhbUNvbnRyb2xsZXIgPSB1bmRlZmluZWQ7XG59XG5mdW5jdGlvbiBJc1RyYW5zZm9ybVN0cmVhbSh4KSB7XG4gICAgaWYgKCF0eXBlSXNPYmplY3QoeCkpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH1cbiAgICBpZiAoIU9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbCh4LCAnX3RyYW5zZm9ybVN0cmVhbUNvbnRyb2xsZXInKSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIHJldHVybiB0cnVlO1xufVxuLy8gVGhpcyBpcyBhIG5vLW9wIGlmIGJvdGggc2lkZXMgYXJlIGFscmVhZHkgZXJyb3JlZC5cbmZ1bmN0aW9uIFRyYW5zZm9ybVN0cmVhbUVycm9yKHN0cmVhbSwgZSkge1xuICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFcnJvcihzdHJlYW0uX3JlYWRhYmxlLl9yZWFkYWJsZVN0cmVhbUNvbnRyb2xsZXIsIGUpO1xuICAgIFRyYW5zZm9ybVN0cmVhbUVycm9yV3JpdGFibGVBbmRVbmJsb2NrV3JpdGUoc3RyZWFtLCBlKTtcbn1cbmZ1bmN0aW9uIFRyYW5zZm9ybVN0cmVhbUVycm9yV3JpdGFibGVBbmRVbmJsb2NrV3JpdGUoc3RyZWFtLCBlKSB7XG4gICAgVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbGVhckFsZ29yaXRobXMoc3RyZWFtLl90cmFuc2Zvcm1TdHJlYW1Db250cm9sbGVyKTtcbiAgICBXcml0YWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyRXJyb3JJZk5lZWRlZChzdHJlYW0uX3dyaXRhYmxlLl93cml0YWJsZVN0cmVhbUNvbnRyb2xsZXIsIGUpO1xuICAgIGlmIChzdHJlYW0uX2JhY2twcmVzc3VyZSkge1xuICAgICAgICAvLyBQcmV0ZW5kIHRoYXQgcHVsbCgpIHdhcyBjYWxsZWQgdG8gcGVybWl0IGFueSBwZW5kaW5nIHdyaXRlKCkgY2FsbHMgdG8gY29tcGxldGUuIFRyYW5zZm9ybVN0cmVhbVNldEJhY2twcmVzc3VyZSgpXG4gICAgICAgIC8vIGNhbm5vdCBiZSBjYWxsZWQgZnJvbSBlbnF1ZXVlKCkgb3IgcHVsbCgpIG9uY2UgdGhlIFJlYWRhYmxlU3RyZWFtIGlzIGVycm9yZWQsIHNvIHRoaXMgd2lsbCB3aWxsIGJlIHRoZSBmaW5hbCB0aW1lXG4gICAgICAgIC8vIF9iYWNrcHJlc3N1cmUgaXMgc2V0LlxuICAgICAgICBUcmFuc2Zvcm1TdHJlYW1TZXRCYWNrcHJlc3N1cmUoc3RyZWFtLCBmYWxzZSk7XG4gICAgfVxufVxuZnVuY3Rpb24gVHJhbnNmb3JtU3RyZWFtU2V0QmFja3ByZXNzdXJlKHN0cmVhbSwgYmFja3ByZXNzdXJlKSB7XG4gICAgLy8gUGFzc2VzIGFsc28gd2hlbiBjYWxsZWQgZHVyaW5nIGNvbnN0cnVjdGlvbi5cbiAgICBpZiAoc3RyZWFtLl9iYWNrcHJlc3N1cmVDaGFuZ2VQcm9taXNlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgc3RyZWFtLl9iYWNrcHJlc3N1cmVDaGFuZ2VQcm9taXNlX3Jlc29sdmUoKTtcbiAgICB9XG4gICAgc3RyZWFtLl9iYWNrcHJlc3N1cmVDaGFuZ2VQcm9taXNlID0gbmV3UHJvbWlzZShyZXNvbHZlID0+IHtcbiAgICAgICAgc3RyZWFtLl9iYWNrcHJlc3N1cmVDaGFuZ2VQcm9taXNlX3Jlc29sdmUgPSByZXNvbHZlO1xuICAgIH0pO1xuICAgIHN0cmVhbS5fYmFja3ByZXNzdXJlID0gYmFja3ByZXNzdXJlO1xufVxuLy8gQ2xhc3MgVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJcbi8qKlxuICogQWxsb3dzIGNvbnRyb2wgb2YgdGhlIHtAbGluayBSZWFkYWJsZVN0cmVhbX0gYW5kIHtAbGluayBXcml0YWJsZVN0cmVhbX0gb2YgdGhlIGFzc29jaWF0ZWQge0BsaW5rIFRyYW5zZm9ybVN0cmVhbX0uXG4gKlxuICogQHB1YmxpY1xuICovXG5jbGFzcyBUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0Q29udHJvbGxlciB7XG4gICAgY29uc3RydWN0b3IoKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0lsbGVnYWwgY29uc3RydWN0b3InKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogUmV0dXJucyB0aGUgZGVzaXJlZCBzaXplIHRvIGZpbGwgdGhlIHJlYWRhYmxlIHNpZGXigJlzIGludGVybmFsIHF1ZXVlLiBJdCBjYW4gYmUgbmVnYXRpdmUsIGlmIHRoZSBxdWV1ZSBpcyBvdmVyLWZ1bGwuXG4gICAgICovXG4gICAgZ2V0IGRlc2lyZWRTaXplKCkge1xuICAgICAgICBpZiAoIUlzVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIodGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IGRlZmF1bHRDb250cm9sbGVyQnJhbmRDaGVja0V4Y2VwdGlvbiQxKCdkZXNpcmVkU2l6ZScpO1xuICAgICAgICB9XG4gICAgICAgIGNvbnN0IHJlYWRhYmxlQ29udHJvbGxlciA9IHRoaXMuX2NvbnRyb2xsZWRUcmFuc2Zvcm1TdHJlYW0uX3JlYWRhYmxlLl9yZWFkYWJsZVN0cmVhbUNvbnRyb2xsZXI7XG4gICAgICAgIHJldHVybiBSZWFkYWJsZVN0cmVhbURlZmF1bHRDb250cm9sbGVyR2V0RGVzaXJlZFNpemUocmVhZGFibGVDb250cm9sbGVyKTtcbiAgICB9XG4gICAgZW5xdWV1ZShjaHVuayA9IHVuZGVmaW5lZCkge1xuICAgICAgICBpZiAoIUlzVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIodGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IGRlZmF1bHRDb250cm9sbGVyQnJhbmRDaGVja0V4Y2VwdGlvbiQxKCdlbnF1ZXVlJyk7XG4gICAgICAgIH1cbiAgICAgICAgVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFbnF1ZXVlKHRoaXMsIGNodW5rKTtcbiAgICB9XG4gICAgLyoqXG4gICAgICogRXJyb3JzIGJvdGggdGhlIHJlYWRhYmxlIHNpZGUgYW5kIHRoZSB3cml0YWJsZSBzaWRlIG9mIHRoZSBjb250cm9sbGVkIHRyYW5zZm9ybSBzdHJlYW0sIG1ha2luZyBhbGwgZnV0dXJlXG4gICAgICogaW50ZXJhY3Rpb25zIHdpdGggaXQgZmFpbCB3aXRoIHRoZSBnaXZlbiBlcnJvciBgZWAuIEFueSBjaHVua3MgcXVldWVkIGZvciB0cmFuc2Zvcm1hdGlvbiB3aWxsIGJlIGRpc2NhcmRlZC5cbiAgICAgKi9cbiAgICBlcnJvcihyZWFzb24gPSB1bmRlZmluZWQpIHtcbiAgICAgICAgaWYgKCFJc1RyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyKHRoaXMpKSB7XG4gICAgICAgICAgICB0aHJvdyBkZWZhdWx0Q29udHJvbGxlckJyYW5kQ2hlY2tFeGNlcHRpb24kMSgnZXJyb3InKTtcbiAgICAgICAgfVxuICAgICAgICBUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0Q29udHJvbGxlckVycm9yKHRoaXMsIHJlYXNvbik7XG4gICAgfVxuICAgIC8qKlxuICAgICAqIENsb3NlcyB0aGUgcmVhZGFibGUgc2lkZSBhbmQgZXJyb3JzIHRoZSB3cml0YWJsZSBzaWRlIG9mIHRoZSBjb250cm9sbGVkIHRyYW5zZm9ybSBzdHJlYW0uIFRoaXMgaXMgdXNlZnVsIHdoZW4gdGhlXG4gICAgICogdHJhbnNmb3JtZXIgb25seSBuZWVkcyB0byBjb25zdW1lIGEgcG9ydGlvbiBvZiB0aGUgY2h1bmtzIHdyaXR0ZW4gdG8gdGhlIHdyaXRhYmxlIHNpZGUuXG4gICAgICovXG4gICAgdGVybWluYXRlKCkge1xuICAgICAgICBpZiAoIUlzVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIodGhpcykpIHtcbiAgICAgICAgICAgIHRocm93IGRlZmF1bHRDb250cm9sbGVyQnJhbmRDaGVja0V4Y2VwdGlvbiQxKCd0ZXJtaW5hdGUnKTtcbiAgICAgICAgfVxuICAgICAgICBUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0Q29udHJvbGxlclRlcm1pbmF0ZSh0aGlzKTtcbiAgICB9XG59XG5PYmplY3QuZGVmaW5lUHJvcGVydGllcyhUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0Q29udHJvbGxlci5wcm90b3R5cGUsIHtcbiAgICBlbnF1ZXVlOiB7IGVudW1lcmFibGU6IHRydWUgfSxcbiAgICBlcnJvcjogeyBlbnVtZXJhYmxlOiB0cnVlIH0sXG4gICAgdGVybWluYXRlOiB7IGVudW1lcmFibGU6IHRydWUgfSxcbiAgICBkZXNpcmVkU2l6ZTogeyBlbnVtZXJhYmxlOiB0cnVlIH1cbn0pO1xuaWYgKHR5cGVvZiBTeW1ib2xQb2x5ZmlsbC50b1N0cmluZ1RhZyA9PT0gJ3N5bWJvbCcpIHtcbiAgICBPYmplY3QuZGVmaW5lUHJvcGVydHkoVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIucHJvdG90eXBlLCBTeW1ib2xQb2x5ZmlsbC50b1N0cmluZ1RhZywge1xuICAgICAgICB2YWx1ZTogJ1RyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyJyxcbiAgICAgICAgY29uZmlndXJhYmxlOiB0cnVlXG4gICAgfSk7XG59XG4vLyBUcmFuc2Zvcm0gU3RyZWFtIERlZmF1bHQgQ29udHJvbGxlciBBYnN0cmFjdCBPcGVyYXRpb25zXG5mdW5jdGlvbiBJc1RyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyKHgpIHtcbiAgICBpZiAoIXR5cGVJc09iamVjdCh4KSkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuICAgIGlmICghT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHgsICdfY29udHJvbGxlZFRyYW5zZm9ybVN0cmVhbScpKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG59XG5mdW5jdGlvbiBTZXRVcFRyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyKHN0cmVhbSwgY29udHJvbGxlciwgdHJhbnNmb3JtQWxnb3JpdGhtLCBmbHVzaEFsZ29yaXRobSkge1xuICAgIGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRUcmFuc2Zvcm1TdHJlYW0gPSBzdHJlYW07XG4gICAgc3RyZWFtLl90cmFuc2Zvcm1TdHJlYW1Db250cm9sbGVyID0gY29udHJvbGxlcjtcbiAgICBjb250cm9sbGVyLl90cmFuc2Zvcm1BbGdvcml0aG0gPSB0cmFuc2Zvcm1BbGdvcml0aG07XG4gICAgY29udHJvbGxlci5fZmx1c2hBbGdvcml0aG0gPSBmbHVzaEFsZ29yaXRobTtcbn1cbmZ1bmN0aW9uIFNldFVwVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJGcm9tVHJhbnNmb3JtZXIoc3RyZWFtLCB0cmFuc2Zvcm1lcikge1xuICAgIGNvbnN0IGNvbnRyb2xsZXIgPSBPYmplY3QuY3JlYXRlKFRyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyLnByb3RvdHlwZSk7XG4gICAgbGV0IHRyYW5zZm9ybUFsZ29yaXRobSA9IChjaHVuaykgPT4ge1xuICAgICAgICB0cnkge1xuICAgICAgICAgICAgVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFbnF1ZXVlKGNvbnRyb2xsZXIsIGNodW5rKTtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVzb2x2ZWRXaXRoKHVuZGVmaW5lZCk7XG4gICAgICAgIH1cbiAgICAgICAgY2F0Y2ggKHRyYW5zZm9ybVJlc3VsdEUpIHtcbiAgICAgICAgICAgIHJldHVybiBwcm9taXNlUmVqZWN0ZWRXaXRoKHRyYW5zZm9ybVJlc3VsdEUpO1xuICAgICAgICB9XG4gICAgfTtcbiAgICBsZXQgZmx1c2hBbGdvcml0aG0gPSAoKSA9PiBwcm9taXNlUmVzb2x2ZWRXaXRoKHVuZGVmaW5lZCk7XG4gICAgaWYgKHRyYW5zZm9ybWVyLnRyYW5zZm9ybSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHRyYW5zZm9ybUFsZ29yaXRobSA9IGNodW5rID0+IHRyYW5zZm9ybWVyLnRyYW5zZm9ybShjaHVuaywgY29udHJvbGxlcik7XG4gICAgfVxuICAgIGlmICh0cmFuc2Zvcm1lci5mbHVzaCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGZsdXNoQWxnb3JpdGhtID0gKCkgPT4gdHJhbnNmb3JtZXIuZmx1c2goY29udHJvbGxlcik7XG4gICAgfVxuICAgIFNldFVwVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIoc3RyZWFtLCBjb250cm9sbGVyLCB0cmFuc2Zvcm1BbGdvcml0aG0sIGZsdXNoQWxnb3JpdGhtKTtcbn1cbmZ1bmN0aW9uIFRyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyQ2xlYXJBbGdvcml0aG1zKGNvbnRyb2xsZXIpIHtcbiAgICBjb250cm9sbGVyLl90cmFuc2Zvcm1BbGdvcml0aG0gPSB1bmRlZmluZWQ7XG4gICAgY29udHJvbGxlci5fZmx1c2hBbGdvcml0aG0gPSB1bmRlZmluZWQ7XG59XG5mdW5jdGlvbiBUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0Q29udHJvbGxlckVucXVldWUoY29udHJvbGxlciwgY2h1bmspIHtcbiAgICBjb25zdCBzdHJlYW0gPSBjb250cm9sbGVyLl9jb250cm9sbGVkVHJhbnNmb3JtU3RyZWFtO1xuICAgIGNvbnN0IHJlYWRhYmxlQ29udHJvbGxlciA9IHN0cmVhbS5fcmVhZGFibGUuX3JlYWRhYmxlU3RyZWFtQ29udHJvbGxlcjtcbiAgICBpZiAoIVJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDYW5DbG9zZU9yRW5xdWV1ZShyZWFkYWJsZUNvbnRyb2xsZXIpKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ1JlYWRhYmxlIHNpZGUgaXMgbm90IGluIGEgc3RhdGUgdGhhdCBwZXJtaXRzIGVucXVldWUnKTtcbiAgICB9XG4gICAgLy8gV2UgdGhyb3R0bGUgdHJhbnNmb3JtIGludm9jYXRpb25zIGJhc2VkIG9uIHRoZSBiYWNrcHJlc3N1cmUgb2YgdGhlIFJlYWRhYmxlU3RyZWFtLCBidXQgd2Ugc3RpbGxcbiAgICAvLyBhY2NlcHQgVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJFbnF1ZXVlKCkgY2FsbHMuXG4gICAgdHJ5IHtcbiAgICAgICAgUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckVucXVldWUocmVhZGFibGVDb250cm9sbGVyLCBjaHVuayk7XG4gICAgfVxuICAgIGNhdGNoIChlKSB7XG4gICAgICAgIC8vIFRoaXMgaGFwcGVucyB3aGVuIHJlYWRhYmxlU3RyYXRlZ3kuc2l6ZSgpIHRocm93cy5cbiAgICAgICAgVHJhbnNmb3JtU3RyZWFtRXJyb3JXcml0YWJsZUFuZFVuYmxvY2tXcml0ZShzdHJlYW0sIGUpO1xuICAgICAgICB0aHJvdyBzdHJlYW0uX3JlYWRhYmxlLl9zdG9yZWRFcnJvcjtcbiAgICB9XG4gICAgY29uc3QgYmFja3ByZXNzdXJlID0gUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckhhc0JhY2twcmVzc3VyZShyZWFkYWJsZUNvbnRyb2xsZXIpO1xuICAgIGlmIChiYWNrcHJlc3N1cmUgIT09IHN0cmVhbS5fYmFja3ByZXNzdXJlKSB7XG4gICAgICAgIFRyYW5zZm9ybVN0cmVhbVNldEJhY2twcmVzc3VyZShzdHJlYW0sIHRydWUpO1xuICAgIH1cbn1cbmZ1bmN0aW9uIFRyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyRXJyb3IoY29udHJvbGxlciwgZSkge1xuICAgIFRyYW5zZm9ybVN0cmVhbUVycm9yKGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRUcmFuc2Zvcm1TdHJlYW0sIGUpO1xufVxuZnVuY3Rpb24gVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJQZXJmb3JtVHJhbnNmb3JtKGNvbnRyb2xsZXIsIGNodW5rKSB7XG4gICAgY29uc3QgdHJhbnNmb3JtUHJvbWlzZSA9IGNvbnRyb2xsZXIuX3RyYW5zZm9ybUFsZ29yaXRobShjaHVuayk7XG4gICAgcmV0dXJuIHRyYW5zZm9ybVByb21pc2VXaXRoKHRyYW5zZm9ybVByb21pc2UsIHVuZGVmaW5lZCwgciA9PiB7XG4gICAgICAgIFRyYW5zZm9ybVN0cmVhbUVycm9yKGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRUcmFuc2Zvcm1TdHJlYW0sIHIpO1xuICAgICAgICB0aHJvdyByO1xuICAgIH0pO1xufVxuZnVuY3Rpb24gVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJUZXJtaW5hdGUoY29udHJvbGxlcikge1xuICAgIGNvbnN0IHN0cmVhbSA9IGNvbnRyb2xsZXIuX2NvbnRyb2xsZWRUcmFuc2Zvcm1TdHJlYW07XG4gICAgY29uc3QgcmVhZGFibGVDb250cm9sbGVyID0gc3RyZWFtLl9yZWFkYWJsZS5fcmVhZGFibGVTdHJlYW1Db250cm9sbGVyO1xuICAgIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbG9zZShyZWFkYWJsZUNvbnRyb2xsZXIpO1xuICAgIGNvbnN0IGVycm9yID0gbmV3IFR5cGVFcnJvcignVHJhbnNmb3JtU3RyZWFtIHRlcm1pbmF0ZWQnKTtcbiAgICBUcmFuc2Zvcm1TdHJlYW1FcnJvcldyaXRhYmxlQW5kVW5ibG9ja1dyaXRlKHN0cmVhbSwgZXJyb3IpO1xufVxuLy8gVHJhbnNmb3JtU3RyZWFtRGVmYXVsdFNpbmsgQWxnb3JpdGhtc1xuZnVuY3Rpb24gVHJhbnNmb3JtU3RyZWFtRGVmYXVsdFNpbmtXcml0ZUFsZ29yaXRobShzdHJlYW0sIGNodW5rKSB7XG4gICAgY29uc3QgY29udHJvbGxlciA9IHN0cmVhbS5fdHJhbnNmb3JtU3RyZWFtQ29udHJvbGxlcjtcbiAgICBpZiAoc3RyZWFtLl9iYWNrcHJlc3N1cmUpIHtcbiAgICAgICAgY29uc3QgYmFja3ByZXNzdXJlQ2hhbmdlUHJvbWlzZSA9IHN0cmVhbS5fYmFja3ByZXNzdXJlQ2hhbmdlUHJvbWlzZTtcbiAgICAgICAgcmV0dXJuIHRyYW5zZm9ybVByb21pc2VXaXRoKGJhY2twcmVzc3VyZUNoYW5nZVByb21pc2UsICgpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHdyaXRhYmxlID0gc3RyZWFtLl93cml0YWJsZTtcbiAgICAgICAgICAgIGNvbnN0IHN0YXRlID0gd3JpdGFibGUuX3N0YXRlO1xuICAgICAgICAgICAgaWYgKHN0YXRlID09PSAnZXJyb3JpbmcnKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgd3JpdGFibGUuX3N0b3JlZEVycm9yO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIFRyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyUGVyZm9ybVRyYW5zZm9ybShjb250cm9sbGVyLCBjaHVuayk7XG4gICAgICAgIH0pO1xuICAgIH1cbiAgICByZXR1cm4gVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJQZXJmb3JtVHJhbnNmb3JtKGNvbnRyb2xsZXIsIGNodW5rKTtcbn1cbmZ1bmN0aW9uIFRyYW5zZm9ybVN0cmVhbURlZmF1bHRTaW5rQWJvcnRBbGdvcml0aG0oc3RyZWFtLCByZWFzb24pIHtcbiAgICAvLyBhYm9ydCgpIGlzIG5vdCBjYWxsZWQgc3luY2hyb25vdXNseSwgc28gaXQgaXMgcG9zc2libGUgZm9yIGFib3J0KCkgdG8gYmUgY2FsbGVkIHdoZW4gdGhlIHN0cmVhbSBpcyBhbHJlYWR5XG4gICAgLy8gZXJyb3JlZC5cbiAgICBUcmFuc2Zvcm1TdHJlYW1FcnJvcihzdHJlYW0sIHJlYXNvbik7XG4gICAgcmV0dXJuIHByb21pc2VSZXNvbHZlZFdpdGgodW5kZWZpbmVkKTtcbn1cbmZ1bmN0aW9uIFRyYW5zZm9ybVN0cmVhbURlZmF1bHRTaW5rQ2xvc2VBbGdvcml0aG0oc3RyZWFtKSB7XG4gICAgLy8gc3RyZWFtLl9yZWFkYWJsZSBjYW5ub3QgY2hhbmdlIGFmdGVyIGNvbnN0cnVjdGlvbiwgc28gY2FjaGluZyBpdCBhY3Jvc3MgYSBjYWxsIHRvIHVzZXIgY29kZSBpcyBzYWZlLlxuICAgIGNvbnN0IHJlYWRhYmxlID0gc3RyZWFtLl9yZWFkYWJsZTtcbiAgICBjb25zdCBjb250cm9sbGVyID0gc3RyZWFtLl90cmFuc2Zvcm1TdHJlYW1Db250cm9sbGVyO1xuICAgIGNvbnN0IGZsdXNoUHJvbWlzZSA9IGNvbnRyb2xsZXIuX2ZsdXNoQWxnb3JpdGhtKCk7XG4gICAgVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXJDbGVhckFsZ29yaXRobXMoY29udHJvbGxlcik7XG4gICAgLy8gUmV0dXJuIGEgcHJvbWlzZSB0aGF0IGlzIGZ1bGZpbGxlZCB3aXRoIHVuZGVmaW5lZCBvbiBzdWNjZXNzLlxuICAgIHJldHVybiB0cmFuc2Zvcm1Qcm9taXNlV2l0aChmbHVzaFByb21pc2UsICgpID0+IHtcbiAgICAgICAgaWYgKHJlYWRhYmxlLl9zdGF0ZSA9PT0gJ2Vycm9yZWQnKSB7XG4gICAgICAgICAgICB0aHJvdyByZWFkYWJsZS5fc3RvcmVkRXJyb3I7XG4gICAgICAgIH1cbiAgICAgICAgUmVhZGFibGVTdHJlYW1EZWZhdWx0Q29udHJvbGxlckNsb3NlKHJlYWRhYmxlLl9yZWFkYWJsZVN0cmVhbUNvbnRyb2xsZXIpO1xuICAgIH0sIHIgPT4ge1xuICAgICAgICBUcmFuc2Zvcm1TdHJlYW1FcnJvcihzdHJlYW0sIHIpO1xuICAgICAgICB0aHJvdyByZWFkYWJsZS5fc3RvcmVkRXJyb3I7XG4gICAgfSk7XG59XG4vLyBUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0U291cmNlIEFsZ29yaXRobXNcbmZ1bmN0aW9uIFRyYW5zZm9ybVN0cmVhbURlZmF1bHRTb3VyY2VQdWxsQWxnb3JpdGhtKHN0cmVhbSkge1xuICAgIC8vIEludmFyaWFudC4gRW5mb3JjZWQgYnkgdGhlIHByb21pc2VzIHJldHVybmVkIGJ5IHN0YXJ0KCkgYW5kIHB1bGwoKS5cbiAgICBUcmFuc2Zvcm1TdHJlYW1TZXRCYWNrcHJlc3N1cmUoc3RyZWFtLCBmYWxzZSk7XG4gICAgLy8gUHJldmVudCB0aGUgbmV4dCBwdWxsKCkgY2FsbCB1bnRpbCB0aGVyZSBpcyBiYWNrcHJlc3N1cmUuXG4gICAgcmV0dXJuIHN0cmVhbS5fYmFja3ByZXNzdXJlQ2hhbmdlUHJvbWlzZTtcbn1cbi8vIEhlbHBlciBmdW5jdGlvbnMgZm9yIHRoZSBUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0Q29udHJvbGxlci5cbmZ1bmN0aW9uIGRlZmF1bHRDb250cm9sbGVyQnJhbmRDaGVja0V4Y2VwdGlvbiQxKG5hbWUpIHtcbiAgICByZXR1cm4gbmV3IFR5cGVFcnJvcihgVHJhbnNmb3JtU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIucHJvdG90eXBlLiR7bmFtZX0gY2FuIG9ubHkgYmUgdXNlZCBvbiBhIFRyYW5zZm9ybVN0cmVhbURlZmF1bHRDb250cm9sbGVyYCk7XG59XG4vLyBIZWxwZXIgZnVuY3Rpb25zIGZvciB0aGUgVHJhbnNmb3JtU3RyZWFtLlxuZnVuY3Rpb24gc3RyZWFtQnJhbmRDaGVja0V4Y2VwdGlvbiQyKG5hbWUpIHtcbiAgICByZXR1cm4gbmV3IFR5cGVFcnJvcihgVHJhbnNmb3JtU3RyZWFtLnByb3RvdHlwZS4ke25hbWV9IGNhbiBvbmx5IGJlIHVzZWQgb24gYSBUcmFuc2Zvcm1TdHJlYW1gKTtcbn1cblxuZXhwb3J0IHsgQnl0ZUxlbmd0aFF1ZXVpbmdTdHJhdGVneSwgQ291bnRRdWV1aW5nU3RyYXRlZ3ksIFJlYWRhYmxlQnl0ZVN0cmVhbUNvbnRyb2xsZXIsIFJlYWRhYmxlU3RyZWFtLCBSZWFkYWJsZVN0cmVhbUJZT0JSZWFkZXIsIFJlYWRhYmxlU3RyZWFtQllPQlJlcXVlc3QsIFJlYWRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIsIFJlYWRhYmxlU3RyZWFtRGVmYXVsdFJlYWRlciwgVHJhbnNmb3JtU3RyZWFtLCBUcmFuc2Zvcm1TdHJlYW1EZWZhdWx0Q29udHJvbGxlciwgV3JpdGFibGVTdHJlYW0sIFdyaXRhYmxlU3RyZWFtRGVmYXVsdENvbnRyb2xsZXIsIFdyaXRhYmxlU3RyZWFtRGVmYXVsdFdyaXRlciB9O1xuLy8jIHNvdXJjZU1hcHBpbmdVUkw9cG9ueWZpbGwuZXMyMDE4Lm1qcy5tYXBcbiIsIm1vZHVsZS5leHBvcnRzID0gcmVxdWlyZShcImNyeXB0b1wiKTs7IiwibW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKFwiaHR0cFwiKTs7IiwibW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKFwiaHR0cHNcIik7OyIsIm1vZHVsZS5leHBvcnRzID0gcmVxdWlyZShcInN0cmVhbVwiKTs7IiwibW9kdWxlLmV4cG9ydHMgPSByZXF1aXJlKFwidXJsXCIpOzsiLCJtb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoXCJ1dGlsXCIpOzsiLCJtb2R1bGUuZXhwb3J0cyA9IHJlcXVpcmUoXCJ6bGliXCIpOzsiLCIvLyBUaGUgbW9kdWxlIGNhY2hlXG52YXIgX193ZWJwYWNrX21vZHVsZV9jYWNoZV9fID0ge307XG5cbi8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG5mdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuXHRpZihfX3dlYnBhY2tfbW9kdWxlX2NhY2hlX19bbW9kdWxlSWRdKSB7XG5cdFx0cmV0dXJuIF9fd2VicGFja19tb2R1bGVfY2FjaGVfX1ttb2R1bGVJZF0uZXhwb3J0cztcblx0fVxuXHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuXHR2YXIgbW9kdWxlID0gX193ZWJwYWNrX21vZHVsZV9jYWNoZV9fW21vZHVsZUlkXSA9IHtcblx0XHQvLyBubyBtb2R1bGUuaWQgbmVlZGVkXG5cdFx0Ly8gbm8gbW9kdWxlLmxvYWRlZCBuZWVkZWRcblx0XHRleHBvcnRzOiB7fVxuXHR9O1xuXG5cdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuXHRfX3dlYnBhY2tfbW9kdWxlc19fW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuXHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuXHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG59XG5cbiIsIi8vIG1vZHVsZSBleHBvcnRzIG11c3QgYmUgcmV0dXJuZWQgZnJvbSBydW50aW1lIHNvIGVudHJ5IGlubGluaW5nIGlzIGRpc2FibGVkXG4vLyBzdGFydHVwXG4vLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbnJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKDk5MCk7XG4iLCIvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9ucyBmb3IgaGFybW9ueSBleHBvcnRzXG5fX3dlYnBhY2tfcmVxdWlyZV9fLmQgPSAoZXhwb3J0cywgZGVmaW5pdGlvbikgPT4ge1xuXHRmb3IodmFyIGtleSBpbiBkZWZpbml0aW9uKSB7XG5cdFx0aWYoX193ZWJwYWNrX3JlcXVpcmVfXy5vKGRlZmluaXRpb24sIGtleSkgJiYgIV9fd2VicGFja19yZXF1aXJlX18ubyhleHBvcnRzLCBrZXkpKSB7XG5cdFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywga2V5LCB7IGVudW1lcmFibGU6IHRydWUsIGdldDogZGVmaW5pdGlvbltrZXldIH0pO1xuXHRcdH1cblx0fVxufTsiLCJfX3dlYnBhY2tfcmVxdWlyZV9fLm8gPSAob2JqLCBwcm9wKSA9PiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqLCBwcm9wKSIsIi8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbl9fd2VicGFja19yZXF1aXJlX18uciA9IChleHBvcnRzKSA9PiB7XG5cdGlmKHR5cGVvZiBTeW1ib2wgIT09ICd1bmRlZmluZWQnICYmIFN5bWJvbC50b1N0cmluZ1RhZykge1xuXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBTeW1ib2wudG9TdHJpbmdUYWcsIHsgdmFsdWU6ICdNb2R1bGUnIH0pO1xuXHR9XG5cdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCAnX19lc01vZHVsZScsIHsgdmFsdWU6IHRydWUgfSk7XG59OyJdLCJzb3VyY2VSb290IjoiIn0= + +/***/ }), + +/***/ 622: +/***/ (function(module) { + +module.exports = require("path"); + +/***/ }), + +/***/ 669: +/***/ (function(module) { + +module.exports = require("util"); + +/***/ }), + +/***/ 747: +/***/ (function(module) { + +module.exports = require("fs"); + +/***/ }), + +/***/ 761: +/***/ (function(module) { + +module.exports = require("zlib"); + +/***/ }), + +/***/ 762: +/***/ (function(module) { + +// API +module.exports = abort; + +/** + * Aborts leftover active jobs + * + * @param {object} state - current state object + */ +function abort(state) +{ + Object.keys(state.jobs).forEach(clean.bind(state)); + + // reset leftover jobs + state.jobs = {}; +} + +/** + * Cleans up leftover job by invoking abort function for the provided job id + * + * @this state + * @param {string|number} key - job id to abort + */ +function clean(key) +{ + if (typeof this.jobs[key] == 'function') + { + this.jobs[key](); + } +} + + +/***/ }), + +/***/ 769: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; +/*! + * mime-types + * Copyright(c) 2014 Jonathan Ong + * Copyright(c) 2015 Douglas Christopher Wilson + * MIT Licensed + */ + + + +/** + * Module dependencies. + * @private + */ + +var db = __webpack_require__(128) +var extname = __webpack_require__(622).extname + +/** + * Module variables. + * @private + */ + +var EXTRACT_TYPE_REGEXP = /^\s*([^;\s]*)(?:;|\s|$)/ +var TEXT_TYPE_REGEXP = /^text\//i + +/** + * Module exports. + * @public + */ + +exports.charset = charset +exports.charsets = { lookup: charset } +exports.contentType = contentType +exports.extension = extension +exports.extensions = Object.create(null) +exports.lookup = lookup +exports.types = Object.create(null) + +// Populate the extensions/types maps +populateMaps(exports.extensions, exports.types) + +/** + * Get the default charset for a MIME type. + * + * @param {string} type + * @return {boolean|string} + */ + +function charset (type) { + if (!type || typeof type !== 'string') { + return false + } + + // TODO: use media-typer + var match = EXTRACT_TYPE_REGEXP.exec(type) + var mime = match && db[match[1].toLowerCase()] + + if (mime && mime.charset) { + return mime.charset + } + + // default text/* to utf-8 + if (match && TEXT_TYPE_REGEXP.test(match[1])) { + return 'UTF-8' + } + + return false +} + +/** + * Create a full Content-Type header given a MIME type or extension. + * + * @param {string} str + * @return {boolean|string} + */ + +function contentType (str) { + // TODO: should this even be in this module? + if (!str || typeof str !== 'string') { + return false + } + + var mime = str.indexOf('/') === -1 + ? exports.lookup(str) + : str + + if (!mime) { + return false + } + + // TODO: use content-type or other module + if (mime.indexOf('charset') === -1) { + var charset = exports.charset(mime) + if (charset) mime += '; charset=' + charset.toLowerCase() + } + + return mime +} + +/** + * Get the default extension for a MIME type. + * + * @param {string} type + * @return {boolean|string} + */ + +function extension (type) { + if (!type || typeof type !== 'string') { + return false + } + + // TODO: use media-typer + var match = EXTRACT_TYPE_REGEXP.exec(type) + + // get extensions + var exts = match && exports.extensions[match[1].toLowerCase()] + + if (!exts || !exts.length) { + return false + } + + return exts[0] +} + +/** + * Lookup the MIME type for a file path/extension. + * + * @param {string} path + * @return {boolean|string} + */ + +function lookup (path) { + if (!path || typeof path !== 'string') { + return false + } + + // get the extension ("ext" or ".ext" or full path) + var extension = extname('x.' + path) + .toLowerCase() + .substr(1) + + if (!extension) { + return false + } + + return exports.types[extension] || false +} + +/** + * Populate the extensions and types maps. + * @private + */ + +function populateMaps (extensions, types) { + // source preference (least -> most) + var preference = ['nginx', 'apache', undefined, 'iana'] + + Object.keys(db).forEach(function forEachMimeType (type) { + var mime = db[type] + var exts = mime.extensions + + if (!exts || !exts.length) { + return + } + + // mime -> extensions + extensions[type] = exts + + // extension -> mime + for (var i = 0; i < exts.length; i++) { + var extension = exts[i] + + if (types[extension]) { + var from = preference.indexOf(db[types[extension]].source) + var to = preference.indexOf(mime.source) + + if (types[extension] !== 'application/octet-stream' && + (from > to || (from === to && types[extension].substr(0, 12) === 'application/'))) { + // skip the remapping + continue + } + } + + // set the extension -> mime + types[extension] = type + } + }) +} + + +/***/ }), + +/***/ 792: +/***/ (function(module, __unusedexports, __webpack_require__) { + +var defer = __webpack_require__(154); + +// API +module.exports = async; + +/** + * Runs provided callback asynchronously + * even if callback itself is not + * + * @param {function} callback - callback to invoke + * @returns {function} - augmented callback + */ +function async(callback) +{ + var isAsync = false; + + // check if async happened + defer(function() { isAsync = true; }); + + return function async_callback(err, result) + { + if (isAsync) + { + callback(err, result); + } + else + { + defer(function nextTick_callback() + { + callback(err, result); + }); + } + }; +} + + +/***/ }), + +/***/ 835: +/***/ (function(module) { + +module.exports = require("url"); + +/***/ }) + +/******/ }); \ No newline at end of file diff --git a/.github/actions/send-email/index.js b/.github/actions/send-email/index.js new file mode 100644 index 0000000000..38be9f04fc --- /dev/null +++ b/.github/actions/send-email/index.js @@ -0,0 +1,82 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const core = require('@actions/core'); +const formData = require('form-data'); +const Mailgun = require('mailgun.js'); + +const mailgun = new Mailgun(formData); +const optionalFields = ['cc', 'text', 'html']; + +function loadConfig() { + return { + apiKey: core.getInput('api-key'), + domain: core.getInput('domain'), + to: core.getInput('to'), + from: core.getInput('from'), + cc: core.getInput('cc'), + subject: core.getInput('subject'), + text: core.getInput('text'), + html: core.getInput('html'), + } +} + +function validate(config) { + for (param in config) { + if (optionalFields.includes(param)) { + continue; + } + validateRequiredParameter(config[param], `'${param}'`); + } +} + +function validateRequiredParameter(value, name) { + if (!isNonEmptyString(value)) { + throw new Error(`${name} must be a non-empty string.`); + } +} + +function sendEmail(config) { + const mg = mailgun.client({ + username: 'api', + key: config.apiKey, + }); + + return mg.messages + .create(config.domain, { + from: config.from, + to: config.to, + cc: config.cc, + subject: config.subject, + text: config.text, + html: config.html, + }) + .then((resp) => { + core.setOutput('response', resp.message); + return; + }) + .catch((err) => { + core.setFailed(err.message); + }); +} + +function isNonEmptyString(value) { + return typeof value === 'string' && value !== ''; +} + +const config = loadConfig(); +validate(config); +sendEmail(config); diff --git a/.github/actions/send-email/package-lock.json b/.github/actions/send-email/package-lock.json new file mode 100644 index 0000000000..f75907db07 --- /dev/null +++ b/.github/actions/send-email/package-lock.json @@ -0,0 +1,173 @@ +{ + "name": "send-email", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@actions/core": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz", + "integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" + }, + "@zeit/ncc": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.21.1.tgz", + "integrity": "sha512-M9WzgquSOt2nsjRkYM9LRylBLmmlwNCwYbm3Up3PDEshfvdmIfqpFNSK8EJvR18NwZjGHE5z2avlDtYQx2JQnw==", + "dev": true + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "fetch-blob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-2.1.1.tgz", + "integrity": "sha512-Uf+gxPCe1hTOFXwkxYyckn8iUSk6CFXGy5VENZKifovUTZC9eUODWSBhOBS7zICGrAetKzdwLMr85KhIcePMAQ==" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "ky": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/ky/-/ky-0.25.1.tgz", + "integrity": "sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA==" + }, + "ky-universal": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/ky-universal/-/ky-universal-0.8.2.tgz", + "integrity": "sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ==", + "requires": { + "abort-controller": "^3.0.0", + "node-fetch": "3.0.0-beta.9" + } + }, + "mailgun.js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-3.3.0.tgz", + "integrity": "sha512-Ikcl9Lp18oXu8/ht6Ow3b2yRYBEa4S70YvquOM2O4FA4NDboa8btZIMEQRRFAmBEfEbWOPrl/Z6E8kL8vnyonQ==", + "requires": { + "bluebird": "^3.7.2", + "btoa": "^1.1.2", + "ky": "^0.25.1", + "ky-universal": "^0.8.2", + "url": "^0.11.0", + "url-join": "0.0.1", + "web-streams-polyfill": "^3.0.1", + "webpack-merge": "^5.4.0" + } + }, + "node-fetch": { + "version": "3.0.0-beta.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.0.0-beta.9.tgz", + "integrity": "sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==", + "requires": { + "data-uri-to-buffer": "^3.0.1", + "fetch-blob": "^2.1.1" + }, + "dependencies": { + "data-uri-to-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", + "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==" + } + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "requires": { + "kind-of": "^6.0.2" + } + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "url-join": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", + "integrity": "sha1-HbSK1CLTQCRpqH99l73r/k+x48g=" + }, + "web-streams-polyfill": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.0.2.tgz", + "integrity": "sha512-JTNkNbAKoSo8NKiqu2UUaqRFCDWWZaCOsXuJEsToWopikTA0YHKKUf91GNkS/SnD8JixOkJjVsiacNlrFnRECA==" + }, + "webpack-merge": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", + "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==" + } + } +} diff --git a/.github/actions/send-email/package.json b/.github/actions/send-email/package.json new file mode 100644 index 0000000000..678a63992a --- /dev/null +++ b/.github/actions/send-email/package.json @@ -0,0 +1,23 @@ +{ + "name": "send-email", + "version": "1.0.0", + "description": "Send Emails from GitHub Actions workflows using Mailgun.", + "main": "index.js", + "scripts": { + "pack": "ncc build" + }, + "keywords": [ + "Firebase", + "Release", + "Automation" + ], + "author": "Firebase (https://firebase.google.com/)", + "license": "Apache-2.0", + "dependencies": { + "@actions/core": "^1.2.6", + "mailgun.js": "^3.3.0" + }, + "devDependencies": { + "@zeit/ncc": "^0.21.1" + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5b8677630..604dba4eef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,10 +17,17 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - - name: Install, build and test + - name: Install and build run: | npm ci npm run build npm run build:tests - npm test - npm run api-extractor + - name: Lint and run unit tests + run: npm test + - name: Run api-extractor + run: npm run api-extractor + - name: Run emulator-based integration tests + run: | + npm install -g firebase-tools + firebase emulators:exec --project fake-project-id --only auth,database,firestore \ + 'npx mocha \"test/integration/{auth,database,firestore}.spec.ts\" --slow 5000 --timeout 20000 --require ts-node/register' diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000000..a36144816b --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,101 @@ +# Copyright 2021 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Nightly Builds + +on: + # Runs every day at 06:00 AM (PT) and 08:00 PM (PT) / 04:00 AM (UTC) and 02:00 PM (UTC) + # or on 'firebase_nightly_build' repository dispatch event. + schedule: + - cron: "0 4,14 * * *" + repository_dispatch: + types: [firebase_nightly_build] + +jobs: + nightly: + + runs-on: ubuntu-latest + + steps: + - name: Checkout source for staging + uses: actions/checkout@v2 + with: + ref: ${{ github.event.client_payload.ref || github.ref }} + + - name: Set up Node.js + uses: actions/setup-node@v1 + with: + node-version: 10.x + + - name: Install and build + run: | + npm ci + npm run build + npm run build:tests + + - name: Run unit tests + run: npm test + + - name: Verify public API + run: npm run api-extractor + + - name: Run integration tests + run: ./.github/scripts/run_integration_tests.sh + env: + FIREBASE_SERVICE_ACCT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCT_KEY }} + FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }} + + - name: Package release artifacts + run: | + npm pack + mkdir -p dist + cp *.tgz dist/ + + # Attach the packaged artifacts to the workflow output. These can be manually + # downloaded for later inspection if necessary. + - name: Archive artifacts + uses: actions/upload-artifact@v1 + with: + name: dist + path: dist + + - name: Send email on failure + if: failure() + uses: ./.github/actions/send-email + with: + api-key: ${{ secrets.OSS_BOT_MAILGUN_KEY }} + domain: ${{ secrets.OSS_BOT_MAILGUN_DOMAIN }} + from: 'GitHub ' + to: ${{ secrets.FIREBASE_ADMIN_GITHUB_EMAIL }} + subject: 'Nightly build ${{github.run_id}} of ${{github.repository}} failed!' + html: > + Nightly workflow ${{github.run_id}} failed on: ${{github.repository}} +

Navigate to the + failed workflow. + continue-on-error: true + + - name: Send email on cancelled + if: cancelled() + uses: ./.github/actions/send-email + with: + api-key: ${{ secrets.OSS_BOT_MAILGUN_KEY }} + domain: ${{ secrets.OSS_BOT_MAILGUN_DOMAIN }} + from: 'GitHub ' + to: ${{ secrets.FIREBASE_ADMIN_GITHUB_EMAIL }} + subject: 'Nightly build ${{github.run_id}} of ${{github.repository}} cancelled!' + html: > + Nightly workflow ${{github.run_id}} cancelled on: ${{github.repository}} +

Navigate to the + cancelled workflow. + continue-on-error: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 42d573c035..7ac6a71cb1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,6 +123,8 @@ There are two test suites: unit and integration. The unit test suite is intended development, and the integration test suite is intended to be run before packaging up release candidates. +#### Unit Tests + To run the unit test suite: ```bash @@ -135,7 +137,26 @@ If you wish to skip the linter, and only run the unit tests: $ npm run test:unit ``` -The integration tests run against an actual Firebase project. Create a new +#### Integration Tests with Emulator Suite + +Some of the integration tests work with the Emulator Suite and you can run them +without an actual Firebase project. + +First, make sure to [install Firebase CLI](https://firebase.google.com/docs/cli#install_the_firebase_cli). +And then: + +```bash + firebase emulators:exec --project fake-project-id --only auth,database,firestore \ + 'npx mocha \"test/integration/{auth,database,firestore}.spec.ts\" --slow 5000 --timeout 20000 --require ts-node/register' +``` + +Currently, only the Auth, Database, and Firestore test suites work. Some test +cases will be automatically skipped due to lack of emulator support. The section +below covers how to run the full test suite against an actual Firebase project. + +#### Integration Tests with an actual Firebase project + +Other integration tests require an actual Firebase project. Create a new project in the [Firebase Console](https://console.firebase.google.com), if you do not already have one suitable for running the tests against. Then obtain the following credentials from the project: @@ -146,7 +167,7 @@ following credentials from the project: 2. *Web API key*: This is displayed in the "Settings > General" tab of the console. Copy it and save to a new text file at `test/resources/apikey.txt`. -Then set up your Firebase/GCP project as follows: +Then set up your Firebase/Google Cloud project as follows: 1. Enable Firestore: Go to the Firebase Console, and select "Database" from the "Develop" menu. Click on the "Create database" button. You may choose @@ -160,15 +181,15 @@ Then set up your Firebase/GCP project as follows: https://console.developers.google.com/apis/api/firebaseml.googleapis.com/overview) and make sure your project is selected. If the API is not already enabled, click Enable. 4. Enable the IAM API: Go to the - [Google Cloud Platform Console](https://console.cloud.google.com) and make - sure your Firebase/GCP project is selected. Select "APIs & Services > + [Google Cloud Console](https://console.cloud.google.com) and make + sure your Firebase/Google Cloud project is selected. Select "APIs & Services > Dashboard" from the main menu, and click the "ENABLE APIS AND SERVICES" button. Search for and enable the "Identity and Access Management (IAM) API". 5. Grant your service account the 'Firebase Authentication Admin' role. This is required to ensure that exported user records contain the password hashes of the user accounts: - 1. Go to [Google Cloud Platform Console / IAM & admin](https://console.cloud.google.com/iam-admin). + 1. Go to [Google Cloud Console / IAM & admin](https://console.cloud.google.com/iam-admin). 2. Find your service account in the list, and click the 'pencil' icon to edit it's permissions. 3. Click 'ADD ANOTHER ROLE' and choose 'Firebase Authentication Admin'. 4. Click 'SAVE'. diff --git a/docgen/content-sources/node/HOME.md b/docgen/content-sources/node/HOME.md index bf22c94da8..4e253f7733 100644 --- a/docgen/content-sources/node/HOME.md +++ b/docgen/content-sources/node/HOME.md @@ -1,6 +1,6 @@ # Firebase Admin Node.js SDK Reference -The Admin SDK lets you interact with Firebase from privileged environments. +The Admin SDK is a set of server libraries that lets you interact with Firebase from privileged environments. You can install it via our [npm package](https://www.npmjs.com/package/firebase-admin). To get started using the Firebase Admin Node.js SDK, see diff --git a/docgen/content-sources/node/toc.yaml b/docgen/content-sources/node/toc.yaml index a563b7417a..487d3fcc39 100644 --- a/docgen/content-sources/node/toc.yaml +++ b/docgen/content-sources/node/toc.yaml @@ -94,6 +94,8 @@ toc: path: /docs/reference/admin/node/admin.auth.UserProviderRequest - title: "UserRecord" path: /docs/reference/admin/node/admin.auth.UserRecord + - title: "UserProvider" + path: /docs/reference/admin/node/admin.auth.UserProvider - title: "SessionCookieOptions" path: /docs/reference/admin/node/admin.auth.SessionCookieOptions - title: "BaseAuth" diff --git a/docgen/generate-docs.js b/docgen/generate-docs.js index 7025ff8da5..af35956e57 100644 --- a/docgen/generate-docs.js +++ b/docgen/generate-docs.js @@ -47,7 +47,11 @@ const contentPath = path.resolve(`${__dirname}/content-sources/node`); const tempHomePath = path.resolve(`${contentPath}/HOME_TEMP.md`); const devsitePath = `/docs/reference/admin/node/`; -const firestoreExcludes = ['v1', 'v1beta1', 'setLogFunction','DocumentData']; +const firestoreExcludes = [ + 'v1', 'v1beta1', 'setLogFunction','DocumentData', + 'BulkWriterOptions', 'DocumentChangeType', 'FirestoreDataConverter', + 'GrpcStatus', 'Precondition', 'ReadOptions', 'UpdateData', 'Settings', +]; const firestoreHtmlPath = `${docPath}/admin.firestore.html`; const firestoreHeader = `

Type aliases

@@ -279,6 +283,18 @@ function updateHtml(htmlPath, contentBlock) { const dom = new jsdom.JSDOM(fs.readFileSync(htmlPath)); const contentNode = dom.window.document.body.querySelector('.col-12'); + // Recent versions of Typedoc generates an additional index section and a variables + // section for namespaces with re-exports. We iterate through these nodes and remove + // them from the output. + const sections = []; + contentNode.childNodes.forEach((child) => { + if (child.nodeName === 'SECTION') { + sections.push(child); + } + }); + contentNode.removeChild(sections[1]); + contentNode.removeChild(sections[2]); + const newSection = new jsdom.JSDOM(contentBlock); contentNode.appendChild(newSection.window.document.body.firstChild); fs.writeFileSync(htmlPath, dom.window.document.documentElement.outerHTML); diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 82ac60b019..8e45cf81f9 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -61,6 +61,7 @@ export abstract class BaseAuth { getUser(uid: string): Promise; getUserByEmail(email: string): Promise; getUserByPhoneNumber(phoneNumber: string): Promise; + getUserByProviderUid(providerId: string, uid: string): Promise; getUsers(identifiers: UserIdentifier[]): Promise; importUsers(users: UserImportRecord[], options?: UserImportOptions): Promise; listProviderConfigs(options: AuthProviderConfigFilter): Promise; @@ -264,6 +265,8 @@ export interface SessionCookieOptions { // @public export class Tenant { + // (undocumented) + readonly anonymousSignInEnabled: boolean; readonly displayName?: string; get emailSignInConfig(): EmailSignInProviderConfig | undefined; get multiFactorConfig(): MultiFactorConfig | undefined; @@ -324,10 +327,13 @@ export interface UpdateRequest { password?: string; phoneNumber?: string | null; photoURL?: string | null; + providersToUnlink?: string[]; + providerToLink?: UserProvider; } // @public export interface UpdateTenantRequest { + anonymousSignInEnabled?: boolean; displayName?: string; emailSignInConfig?: EmailSignInProviderConfig; multiFactorConfig?: MultiFactorConfig; @@ -405,6 +411,16 @@ export interface UserMetadataRequest { lastSignInTime?: string; } +// @public +export interface UserProvider { + displayName?: string; + email?: string; + phoneNumber?: string; + photoURL?: string; + providerId?: string; + uid?: string; +} + // @public export interface UserProviderRequest { displayName?: string; diff --git a/package-lock.json b/package-lock.json index 5ec1476a52..f4875def9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -176,9 +176,9 @@ "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" }, "@firebase/auth": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.15.2.tgz", - "integrity": "sha512-2n32PBi6x9jVhc0E/ewKLUCYYTzFEXL4PNkvrrlGKbzeTBEkkyzfgUX7OV9UF5wUOG+gurtUthuur1zspZ/9hg==", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.2.tgz", + "integrity": "sha512-68TlDL0yh3kF8PiCzI8m8RWd/bf/xCLUsdz1NZ2Dwea0sp6e2WAhu0sem1GfhwuEwL+Ns4jCdX7qbe/OQlkVEA==", "dev": true, "requires": { "@firebase/auth-types": "0.10.1" diff --git a/package.json b/package.json index aa065969fd..e50e67dc71 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ }, "devDependencies": { "@firebase/app": "^0.6.13", - "@firebase/auth": "^0.15.2", + "@firebase/auth": "^0.16.2", "@firebase/auth-types": "^0.10.1", "@microsoft/api-extractor": "^7.11.2", "@types/bcrypt": "^2.0.0", diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 23ca6001c7..2ccf7cd04a 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -16,7 +16,7 @@ */ import { AppOptions, app } from '../firebase-namespace-api'; -import { Credential, GoogleOAuthAccessToken } from './credential'; +import { Credential } from './credential'; import { getApplicationDefault } from './credential-internal'; import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; @@ -34,6 +34,8 @@ import { ProjectManagement } from '../project-management/index'; import { SecurityRules } from '../security-rules/index'; import { RemoteConfig } from '../remote-config/index'; +const TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1000; + /** * Type representing a callback which is called every time an app lifecycle event occurs. */ @@ -52,129 +54,80 @@ export interface FirebaseAccessToken { * Internals of a FirebaseApp instance. */ export class FirebaseAppInternals { - private isDeleted_ = false; private cachedToken_: FirebaseAccessToken; - private cachedTokenPromise_: Promise | null; private tokenListeners_: Array<(token: string) => void>; - private tokenRefreshTimeout_: NodeJS.Timer; constructor(private credential_: Credential) { this.tokenListeners_ = []; } - /** - * Gets an auth token for the associated app. - * - * @param {boolean} forceRefresh Whether or not to force a token refresh. - * @return {Promise} A Promise that will be fulfilled with the current or - * new token. - */ - public getToken(forceRefresh?: boolean): Promise { - const expired = this.cachedToken_ && this.cachedToken_.expirationTime < Date.now(); - if (this.cachedTokenPromise_ && !forceRefresh && !expired) { - return this.cachedTokenPromise_ - .catch((error) => { - // Update the cached token promise to avoid caching errors. Set it to resolve with the - // cached token if we have one (and return that promise since the token has still not - // expired). - if (this.cachedToken_) { - this.cachedTokenPromise_ = Promise.resolve(this.cachedToken_); - return this.cachedTokenPromise_; - } - - // Otherwise, set the cached token promise to null so that it will force a refresh next - // time getToken() is called. - this.cachedTokenPromise_ = null; - - // And re-throw the caught error. - throw error; - }); - } else { - // Clear the outstanding token refresh timeout. This is a noop if the timeout is undefined. - clearTimeout(this.tokenRefreshTimeout_); - - // this.credential_ may be an external class; resolving it in a promise helps us - // protect against exceptions and upgrades the result to a promise in all cases. - this.cachedTokenPromise_ = Promise.resolve(this.credential_.getAccessToken()) - .then((result: GoogleOAuthAccessToken) => { - // Since the developer can provide the credential implementation, we want to weakly verify - // the return type until the type is properly exported. - if (!validator.isNonNullObject(result) || - typeof result.expires_in !== 'number' || - typeof result.access_token !== 'string') { - throw new FirebaseAppError( - AppErrorCodes.INVALID_CREDENTIAL, - `Invalid access token generated: "${JSON.stringify(result)}". Valid access ` + - 'tokens must be an object with the "expires_in" (number) and "access_token" ' + - '(string) properties.', - ); - } - - const token: FirebaseAccessToken = { - accessToken: result.access_token, - expirationTime: Date.now() + (result.expires_in * 1000), - }; - - const hasAccessTokenChanged = (this.cachedToken_ && this.cachedToken_.accessToken !== token.accessToken); - const hasExpirationChanged = (this.cachedToken_ && this.cachedToken_.expirationTime !== token.expirationTime); - if (!this.cachedToken_ || hasAccessTokenChanged || hasExpirationChanged) { - this.cachedToken_ = token; - this.tokenListeners_.forEach((listener) => { - listener(token.accessToken); - }); - } - - // Establish a timeout to proactively refresh the token every minute starting at five - // minutes before it expires. Once a token refresh succeeds, no further retries are - // needed; if it fails, retry every minute until the token expires (resulting in a total - // of four retries: at 4, 3, 2, and 1 minutes). - let refreshTimeInSeconds = (result.expires_in - (5 * 60)); - let numRetries = 4; - - // In the rare cases the token is short-lived (that is, it expires in less than five - // minutes from when it was fetched), establish the timeout to refresh it after the - // current minute ends and update the number of retries that should be attempted before - // the token expires. - if (refreshTimeInSeconds <= 0) { - refreshTimeInSeconds = result.expires_in % 60; - numRetries = Math.floor(result.expires_in / 60) - 1; - } - - // The token refresh timeout keeps the Node.js process alive, so only create it if this - // instance has not already been deleted. - if (numRetries && !this.isDeleted_) { - this.setTokenRefreshTimeout(refreshTimeInSeconds * 1000, numRetries); - } - - return token; - }) - .catch((error) => { - let errorMessage = (typeof error === 'string') ? error : error.message; - - errorMessage = 'Credential implementation provided to initializeApp() via the ' + - '"credential" property failed to fetch a valid Google OAuth2 access token with the ' + - `following error: "${errorMessage}".`; - - if (errorMessage.indexOf('invalid_grant') !== -1) { - errorMessage += ' There are two likely causes: (1) your server time is not properly ' + - 'synced or (2) your certificate key file has been revoked. To solve (1), re-sync the ' + - 'time on your server. To solve (2), make sure the key ID for your key file is still ' + - 'present at https://console.firebase.google.com/iam-admin/serviceaccounts/project. If ' + - 'not, generate a new key file at ' + - 'https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk.'; - } - - throw new FirebaseAppError(AppErrorCodes.INVALID_CREDENTIAL, errorMessage); - }); - - return this.cachedTokenPromise_; + public getToken(forceRefresh = false): Promise { + if (forceRefresh || this.shouldRefresh()) { + return this.refreshToken(); } + + return Promise.resolve(this.cachedToken_); + } + + private refreshToken(): Promise { + return Promise.resolve(this.credential_.getAccessToken()) + .then((result) => { + // Since the developer can provide the credential implementation, we want to weakly verify + // the return type until the type is properly exported. + if (!validator.isNonNullObject(result) || + typeof result.expires_in !== 'number' || + typeof result.access_token !== 'string') { + throw new FirebaseAppError( + AppErrorCodes.INVALID_CREDENTIAL, + `Invalid access token generated: "${JSON.stringify(result)}". Valid access ` + + 'tokens must be an object with the "expires_in" (number) and "access_token" ' + + '(string) properties.', + ); + } + + const token = { + accessToken: result.access_token, + expirationTime: Date.now() + (result.expires_in * 1000), + }; + if (!this.cachedToken_ + || this.cachedToken_.accessToken !== token.accessToken + || this.cachedToken_.expirationTime !== token.expirationTime) { + this.cachedToken_ = token; + this.tokenListeners_.forEach((listener) => { + listener(token.accessToken); + }); + } + + return token; + }) + .catch((error) => { + let errorMessage = (typeof error === 'string') ? error : error.message; + + errorMessage = 'Credential implementation provided to initializeApp() via the ' + + '"credential" property failed to fetch a valid Google OAuth2 access token with the ' + + `following error: "${errorMessage}".`; + + if (errorMessage.indexOf('invalid_grant') !== -1) { + errorMessage += ' There are two likely causes: (1) your server time is not properly ' + + 'synced or (2) your certificate key file has been revoked. To solve (1), re-sync the ' + + 'time on your server. To solve (2), make sure the key ID for your key file is still ' + + 'present at https://console.firebase.google.com/iam-admin/serviceaccounts/project. If ' + + 'not, generate a new key file at ' + + 'https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk.'; + } + + throw new FirebaseAppError(AppErrorCodes.INVALID_CREDENTIAL, errorMessage); + }); + } + + private shouldRefresh(): boolean { + return !this.cachedToken_ || (this.cachedToken_.expirationTime - Date.now()) <= TOKEN_EXPIRY_THRESHOLD_MILLIS; } /** * Adds a listener that is called each time a token changes. * - * @param {function(string)} listener The listener that will be called with each new token. + * @param listener The listener that will be called with each new token. */ public addAuthTokenListener(listener: (token: string) => void): void { this.tokenListeners_.push(listener); @@ -186,42 +139,11 @@ export class FirebaseAppInternals { /** * Removes a token listener. * - * @param {function(string)} listener The listener to remove. + * @param listener The listener to remove. */ public removeAuthTokenListener(listener: (token: string) => void): void { this.tokenListeners_ = this.tokenListeners_.filter((other) => other !== listener); } - - /** - * Deletes the FirebaseAppInternals instance. - */ - public delete(): void { - this.isDeleted_ = true; - - // Clear the token refresh timeout so it doesn't keep the Node.js process alive. - clearTimeout(this.tokenRefreshTimeout_); - } - - /** - * Establishes timeout to refresh the Google OAuth2 access token used by the SDK. - * - * @param {number} delayInMilliseconds The delay to use for the timeout. - * @param {number} numRetries The number of times to retry fetching a new token if the prior fetch - * failed. - */ - private setTokenRefreshTimeout(delayInMilliseconds: number, numRetries: number): void { - this.tokenRefreshTimeout_ = setTimeout(() => { - this.getToken(/* forceRefresh */ true) - .catch(() => { - // Ignore the error since this might just be an intermittent failure. If we really cannot - // refresh the token, an error will be logged once the existing token expires and we try - // to fetch a fresh one. - if (numRetries > 0) { - this.setTokenRefreshTimeout(60 * 1000, numRetries - 1); - } - }); - }, delayInMilliseconds); - } } /** @@ -398,8 +320,6 @@ export class FirebaseApp implements app.App { this.checkDestroyed_(); this.firebaseInternals_.removeApp(this.name_); - this.INTERNAL.delete(); - return Promise.all(Object.keys(this.services_).map((serviceName) => { const service = this.services_[serviceName]; if (isStateful(service)) { diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 8de0f1f29e..86abb109e3 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -390,6 +390,8 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat phoneNumber: true, customAttributes: true, validSince: true, + // Pass linkProviderUserInfo only for updates (i.e. not for uploads.) + linkProviderUserInfo: !uploadAccountRequest, // Pass tenantId only for uploadAccount requests. tenantId: uploadAccountRequest, passwordHash: uploadAccountRequest, @@ -538,6 +540,12 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat validateProviderUserInfo(providerUserInfoEntry); }); } + + // linkProviderUserInfo must be a (single) UserProvider value. + if (typeof request.linkProviderUserInfo !== 'undefined') { + validateProviderUserInfo(request.linkProviderUserInfo); + } + // mfaInfo is used for importUsers. // mfa.enrollments is used for setAccountInfo. // enrollments has to be an array of valid AuthFactorInfo requests. @@ -1139,6 +1147,21 @@ export abstract class AbstractAuthRequestHandler { return this.invokeRequestHandler(this.getAuthUrlBuilder(), FIREBASE_AUTH_GET_ACCOUNT_INFO, request); } + public getAccountInfoByFederatedUid(providerId: string, rawId: string): Promise { + if (!validator.isNonEmptyString(providerId) || !validator.isNonEmptyString(rawId)) { + throw new FirebaseAuthError(AuthClientErrorCode.INVALID_PROVIDER_ID); + } + + const request = { + federatedUserId: [{ + providerId, + rawId, + }], + }; + + return this.invokeRequestHandler(this.getAuthUrlBuilder(), FIREBASE_AUTH_GET_ACCOUNT_INFO, request); + } + /** * Looks up multiple users by their identifiers (uid, email, etc). * @@ -1351,6 +1374,33 @@ export abstract class AbstractAuthRequestHandler { 'Properties argument must be a non-null object.', ), ); + } else if (validator.isNonNullObject(properties.providerToLink)) { + // TODO(rsgowman): These checks overlap somewhat with + // validateProviderUserInfo. It may be possible to refactor a bit. + if (!validator.isNonEmptyString(properties.providerToLink.providerId)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + 'providerToLink.providerId of properties argument must be a non-empty string.'); + } + if (!validator.isNonEmptyString(properties.providerToLink.uid)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + 'providerToLink.uid of properties argument must be a non-empty string.'); + } + } else if (typeof properties.providersToUnlink !== 'undefined') { + if (!validator.isArray(properties.providersToUnlink)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + 'providersToUnlink of properties argument must be an array of strings.'); + } + + properties.providersToUnlink.forEach((providerId) => { + if (!validator.isNonEmptyString(providerId)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + 'providersToUnlink of properties argument must be an array of strings.'); + } + }); } // Build the setAccountInfo request. @@ -1385,13 +1435,25 @@ export abstract class AbstractAuthRequestHandler { // It will be removed from the backend request and an additional parameter // deleteProvider: ['phone'] with an array of providerIds (phone in this case), // will be passed. - // Currently this applies to phone provider only. if (request.phoneNumber === null) { - request.deleteProvider = ['phone']; + request.deleteProvider ? request.deleteProvider.push('phone') : request.deleteProvider = ['phone']; delete request.phoneNumber; - } else { - // Doesn't apply to other providers in admin SDK. - delete request.deleteProvider; + } + + if (typeof(request.providerToLink) !== 'undefined') { + request.linkProviderUserInfo = deepCopy(request.providerToLink); + delete request.providerToLink; + + request.linkProviderUserInfo.rawId = request.linkProviderUserInfo.uid; + delete request.linkProviderUserInfo.uid; + } + + if (typeof(request.providersToUnlink) !== 'undefined') { + if (!validator.isArray(request.deleteProvider)) { + request.deleteProvider = []; + } + request.deleteProvider = request.deleteProvider.concat(request.providersToUnlink); + delete request.providersToUnlink; } // Rewrite photoURL to photoUrl. @@ -2177,10 +2239,6 @@ function emulatorHost(): string | undefined { /** * When true the SDK should communicate with the Auth Emulator for all API * calls and also produce unsigned tokens. - * - * This alone does NOT short-circuit ID Token verification. - * For security reasons that must be explicitly disabled through - * setJwtVerificationEnabled(false); */ export function useEmulator(): boolean { return !!emulatorHost(); diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index be6f2ccb6c..793eb3addc 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -156,8 +156,65 @@ export interface UpdateRequest { * The user's updated multi-factor related properties. */ multiFactor?: MultiFactorUpdateSettings; + + /** + * Links this user to the specified provider. + * + * Linking a provider to an existing user account does not invalidate the + * refresh token of that account. In other words, the existing account + * would continue to be able to access resources, despite not having used + * the newly linked provider to log in. If you wish to force the user to + * authenticate with this new provider, you need to (a) revoke their + * refresh token (see + * https://firebase.google.com/docs/auth/admin/manage-sessions#revoke_refresh_tokens), + * and (b) ensure no other authentication methods are present on this + * account. + */ + providerToLink?: UserProvider; + + /** + * Unlinks this user from the specified providers. + */ + providersToUnlink?: string[]; } +/** + * Represents a user identity provider that can be associated with a Firebase user. + */ +export interface UserProvider { + + /** + * The user identifier for the linked provider. + */ + uid?: string; + + /** + * The display name for the linked provider. + */ + displayName?: string; + + /** + * The email for the linked provider. + */ + email?: string; + + /** + * The phone number for the linked provider. + */ + phoneNumber?: string; + + /** + * The photo URL for the linked provider. + */ + photoURL?: string; + + /** + * The linked provider ID (for example, "google.com" for the Google provider). + */ + providerId?: string; +} + + /** * Interface representing the properties to set on a new user record to be * created. diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts index c3a4f406e5..a68299237a 100644 --- a/src/auth/base-auth.ts +++ b/src/auth/base-auth.ts @@ -16,12 +16,13 @@ import { App, FirebaseArrayIndexError } from '../app'; import { AuthClientErrorCode, ErrorInfo, FirebaseAuthError } from '../utils/error'; +import { deepCopy } from '../utils/deep-copy'; import * as validator from '../utils/validator'; import { AbstractAuthRequestHandler, useEmulator } from './auth-api-request'; import { FirebaseTokenGenerator, EmulatedSigner, cryptoSignerFromApp } from './token-generator'; import { - FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier, ALGORITHM_RS256, + FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier, DecodedIdToken, } from './token-verifier'; import { @@ -186,15 +187,16 @@ export abstract class BaseAuth { * promise. */ public verifyIdToken(idToken: string, checkRevoked = false): Promise { - return this.idTokenVerifier.verifyJWT(idToken) + const isEmulator = useEmulator(); + return this.idTokenVerifier.verifyJWT(idToken, isEmulator) .then((decodedIdToken: DecodedIdToken) => { // Whether to check if the token was revoked. - if (!checkRevoked) { - return decodedIdToken; + if (checkRevoked || isEmulator) { + return this.verifyDecodedJWTNotRevoked( + decodedIdToken, + AuthClientErrorCode.ID_TOKEN_REVOKED); } - return this.verifyDecodedJWTNotRevoked( - decodedIdToken, - AuthClientErrorCode.ID_TOKEN_REVOKED); + return decodedIdToken; }); } @@ -258,6 +260,36 @@ export abstract class BaseAuth { }); } + /** + * Gets the user data for the user corresponding to a given provider id. + * + * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) + * for code samples and detailed documentation. + * + * @param providerId The provider ID, for example, "google.com" for the + * Google provider. + * @param uid The user identifier for the given provider. + * + * @return A promise fulfilled with the user data corresponding to the + * given provider id. + */ + public getUserByProviderUid(providerId: string, uid: string): Promise { + // Although we don't really advertise it, we want to also handle + // non-federated idps with this call. So if we detect one of them, we'll + // reroute this request appropriately. + if (providerId === 'phone') { + return this.getUserByPhoneNumber(uid); + } else if (providerId === 'email') { + return this.getUserByEmail(uid); + } + + return this.authRequestHandler.getAccountInfoByFederatedUid(providerId, uid) + .then((response: any) => { + // Returns the user record populated with server response. + return new UserRecord(response.users[0]); + }); + } + /** * Gets the user data corresponding to the specified identifiers. * @@ -477,6 +509,50 @@ export abstract class BaseAuth { * updated user data. */ public updateUser(uid: string, properties: UpdateRequest): Promise { + // Although we don't really advertise it, we want to also handle linking of + // non-federated idps with this call. So if we detect one of them, we'll + // adjust the properties parameter appropriately. This *does* imply that a + // conflict could arise, e.g. if the user provides a phoneNumber property, + // but also provides a providerToLink with a 'phone' provider id. In that + // case, we'll throw an error. + properties = deepCopy(properties); + + if (properties?.providerToLink) { + if (properties.providerToLink.providerId === 'email') { + if (typeof properties.email !== 'undefined') { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + "Both UpdateRequest.email and UpdateRequest.providerToLink.providerId='email' were set. To " + + 'link to the email/password provider, only specify the UpdateRequest.email field.'); + } + properties.email = properties.providerToLink.uid; + delete properties.providerToLink; + } else if (properties.providerToLink.providerId === 'phone') { + if (typeof properties.phoneNumber !== 'undefined') { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + "Both UpdateRequest.phoneNumber and UpdateRequest.providerToLink.providerId='phone' were set. To " + + 'link to a phone provider, only specify the UpdateRequest.phoneNumber field.'); + } + properties.phoneNumber = properties.providerToLink.uid; + delete properties.providerToLink; + } + } + if (properties?.providersToUnlink) { + if (properties.providersToUnlink.indexOf('phone') !== -1) { + // If we've been told to unlink the phone provider both via setting + // phoneNumber to null *and* by setting providersToUnlink to include + // 'phone', then we'll reject that. Though it might also be reasonable + // to relax this restriction and just unlink it. + if (properties.phoneNumber === null) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + "Both UpdateRequest.phoneNumber=null and UpdateRequest.providersToUnlink=['phone'] were set. To " + + 'unlink from a phone provider, only specify the UpdateRequest.phoneNumber=null field.'); + } + } + } + return this.authRequestHandler.updateExistingAccount(uid, properties) .then((existingUid) => { // Return the corresponding user record. @@ -615,15 +691,16 @@ export abstract class BaseAuth { */ public verifySessionCookie( sessionCookie: string, checkRevoked = false): Promise { - return this.sessionCookieVerifier.verifyJWT(sessionCookie) + const isEmulator = useEmulator(); + return this.sessionCookieVerifier.verifyJWT(sessionCookie, isEmulator) .then((decodedIdToken: DecodedIdToken) => { // Whether to check if the token was revoked. - if (!checkRevoked) { - return decodedIdToken; + if (checkRevoked || isEmulator) { + return this.verifyDecodedJWTNotRevoked( + decodedIdToken, + AuthClientErrorCode.SESSION_COOKIE_REVOKED); } - return this.verifyDecodedJWTNotRevoked( - decodedIdToken, - AuthClientErrorCode.SESSION_COOKIE_REVOKED); + return decodedIdToken; }); } @@ -991,26 +1068,4 @@ export abstract class BaseAuth { return decodedIdToken; }); } - - /** - * Enable or disable ID token verification. This is used to safely short-circuit token verification with the - * Auth emulator. When disabled ONLY unsigned tokens will pass verification, production tokens will not pass. - * - * WARNING: This is a dangerous method that will compromise your app's security and break your app in - * production. Developers should never call this method, it is for internal testing use only. - * - * @internal - */ - // @ts-expect-error: this method appears unused but is used privately. - private setJwtVerificationEnabled(enabled: boolean): void { - if (!enabled && !useEmulator()) { - // We only allow verification to be disabled in conjunction with - // the emulator environment variable. - throw new Error('This method is only available when connected to the Authentication emulator.'); - } - - const algorithm = enabled ? ALGORITHM_RS256 : 'none'; - this.idTokenVerifier.setAlgorithm(algorithm); - this.sessionCookieVerifier.setAlgorithm(algorithm); - } } \ No newline at end of file diff --git a/src/auth/index.ts b/src/auth/index.ts index 403deafedf..272762f0b5 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -71,6 +71,7 @@ export { OIDCUpdateAuthProviderRequest, SAMLAuthProviderConfig, SAMLUpdateAuthProviderRequest, + UserProvider, UpdateAuthProviderRequest, UpdateMultiFactorInfoRequest, UpdatePhoneMultiFactorInfoRequest, diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index 70b9daeb95..c6359a1e85 100644 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -39,6 +39,11 @@ export interface UpdateTenantRequest { */ emailSignInConfig?: EmailSignInProviderConfig; + /** + * Whether the anonymous provider is enabled. + */ + anonymousSignInEnabled?: boolean; + /** * The multi-factor auth configuration to update on the tenant. */ @@ -60,6 +65,7 @@ export type CreateTenantRequest = UpdateTenantRequest; /** The corresponding server side representation of a TenantOptions object. */ export interface TenantOptionsServerRequest extends EmailSignInConfigServerRequest { displayName?: string; + enableAnonymousUser?: boolean; mfaConfig?: MultiFactorAuthServerConfig; testPhoneNumbers?: {[key: string]: string}; } @@ -70,6 +76,7 @@ export interface TenantServerResponse { displayName?: string; allowPasswordSignup?: boolean; enableEmailLinkSignin?: boolean; + enableAnonymousUser?: boolean; mfaConfig?: MultiFactorAuthServerConfig; testPhoneNumbers?: {[key: string]: string}; } @@ -106,6 +113,8 @@ export class Tenant { */ public readonly displayName?: string; + public readonly anonymousSignInEnabled: boolean; + /** * The map containing the test phone number / code pairs for the tenant. */ @@ -133,6 +142,9 @@ export class Tenant { if (typeof tenantOptions.displayName !== 'undefined') { request.displayName = tenantOptions.displayName; } + if (typeof tenantOptions.anonymousSignInEnabled !== 'undefined') { + request.enableAnonymousUser = tenantOptions.anonymousSignInEnabled; + } if (typeof tenantOptions.multiFactorConfig !== 'undefined') { request.mfaConfig = MultiFactorAuthConfig.buildServerRequest(tenantOptions.multiFactorConfig); } @@ -170,6 +182,7 @@ export class Tenant { const validKeys = { displayName: true, emailSignInConfig: true, + anonymousSignInEnabled: true, multiFactorConfig: true, testPhoneNumbers: true, }; @@ -245,6 +258,7 @@ export class Tenant { allowPasswordSignup: false, }); } + this.anonymousSignInEnabled = !!response.enableAnonymousUser; if (typeof response.mfaConfig !== 'undefined') { this.multiFactorConfig_ = new MultiFactorAuthConfig(response.mfaConfig); } @@ -276,6 +290,7 @@ export class Tenant { displayName: this.displayName, emailSignInConfig: this.emailSignInConfig_?.toJSON(), multiFactorConfig: this.multiFactorConfig_?.toJSON(), + anonymousSignInEnabled: this.anonymousSignInEnabled, testPhoneNumbers: this.testPhoneNumbers, }; if (typeof json.multiFactorConfig === 'undefined') { diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 71ba252c4e..b07ad29c14 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -17,9 +17,11 @@ import { AuthClientErrorCode, FirebaseAuthError, ErrorInfo } from '../utils/error'; import * as util from '../utils/index'; import * as validator from '../utils/validator'; -import * as jwt from 'jsonwebtoken'; -import { HttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; -import { App } from '../app'; +import { + DecodedToken, decodeJwt, JwtError, JwtErrorCode, EmulatorSignatureVerifier, + PublicKeySignatureVerifier, ALGORITHM_RS256, SignatureVerifier, +} from '../utils/jwt'; +import { App } from '../app/index'; /** * Interface representing a decoded Firebase ID token, returned from the @@ -177,8 +179,6 @@ export interface DecodedIdToken { // Audience to use for Firebase Auth Custom tokens const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit'; -export const ALGORITHM_RS256 = 'RS256'; - // URL containing the public keys for the Google certs (whose private keys are used to sign Firebase // Auth ID tokens) const CLIENT_CERT_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'; @@ -186,6 +186,8 @@ const CLIENT_CERT_URL = 'https://www.googleapis.com/robot/v1/metadata/x509/secur // URL containing the public keys for Firebase session cookies. This will be updated to a different URL soon. const SESSION_COOKIE_CERT_URL = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys'; +const EMULATOR_VERIFIER = new EmulatorSignatureVerifier(); + /** * User facing token information related to the Firebase ID token. * @@ -236,12 +238,11 @@ export interface FirebaseTokenInfo { * @internal */ export class FirebaseTokenVerifier { - private publicKeys: {[key: string]: string}; - private publicKeysExpireAt: number; + private readonly shortNameArticle: string; + private readonly signatureVerifier: SignatureVerifier; - constructor(private clientCertUrl: string, private algorithm: jwt.Algorithm, - private issuer: string, private tokenInfo: FirebaseTokenInfo, + constructor(clientCertUrl: string, private issuer: string, private tokenInfo: FirebaseTokenInfo, private readonly app: App) { if (!validator.isURL(clientCertUrl)) { @@ -249,11 +250,6 @@ export class FirebaseTokenVerifier { AuthClientErrorCode.INVALID_ARGUMENT, 'The provided public client certificate URL is an invalid URL.', ); - } else if (!validator.isNonEmptyString(algorithm)) { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - 'The provided JWT algorithm is an empty string.', - ); } else if (!validator.isURL(issuer)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -292,17 +288,20 @@ export class FirebaseTokenVerifier { } this.shortNameArticle = tokenInfo.shortName.charAt(0).match(/[aeiou]/i) ? 'an' : 'a'; + this.signatureVerifier = + PublicKeySignatureVerifier.withCertificateUrl(clientCertUrl, app.options.httpAgent); + // For backward compatibility, the project ID is validated in the verification call. } /** * Verifies the format and signature of a Firebase Auth JWT token. * - * @param {string} jwtToken The Firebase Auth JWT token to verify. - * @return {Promise} A promise fulfilled with the decoded claims of the Firebase Auth ID - * token. + * @param jwtToken The Firebase Auth JWT token to verify. + * @param isEmulator Whether to accept Auth Emulator tokens. + * @return A promise fulfilled with the decoded claims of the Firebase Auth ID token. */ - public verifyJWT(jwtToken: string): Promise { + public verifyJWT(jwtToken: string, isEmulator = false): Promise { if (!validator.isString(jwtToken)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, @@ -310,32 +309,67 @@ export class FirebaseTokenVerifier { ); } - return util.findProjectId(this.app) + return this.ensureProjectId() .then((projectId) => { - return this.verifyJWTWithProjectId(jwtToken, projectId); + return this.decodeAndVerify(jwtToken, projectId, isEmulator); + }) + .then((decoded) => { + const decodedIdToken = decoded.payload as DecodedIdToken; + decodedIdToken.uid = decodedIdToken.sub; + return decodedIdToken; }); } - /** - * Override the JWT signing algorithm. - * @param algorithm the new signing algorithm. - */ - public setAlgorithm(algorithm: jwt.Algorithm): void { - this.algorithm = algorithm; + private ensureProjectId(): Promise { + return util.findProjectId(this.app) + .then((projectId) => { + if (!validator.isNonEmptyString(projectId)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_CREDENTIAL, + 'Must initialize app with a cert credential or set your Firebase project ID as the ' + + `GOOGLE_CLOUD_PROJECT environment variable to call ${this.tokenInfo.verifyApiName}.`, + ); + } + return Promise.resolve(projectId); + }) } - private verifyJWTWithProjectId(jwtToken: string, projectId: string | null): Promise { - if (!validator.isNonEmptyString(projectId)) { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CREDENTIAL, - 'Must initialize app with a cert credential or set your Firebase project ID as the ' + - `GOOGLE_CLOUD_PROJECT environment variable to call ${this.tokenInfo.verifyApiName}.`, - ); - } + private decodeAndVerify(token: string, projectId: string, isEmulator: boolean): Promise { + return this.safeDecode(token) + .then((decodedToken) => { + this.verifyContent(decodedToken, projectId, isEmulator); + return this.verifySignature(token, isEmulator) + .then(() => decodedToken); + }); + } - const fullDecodedToken: any = jwt.decode(jwtToken, { - complete: true, - }); + private safeDecode(jwtToken: string): Promise { + return decodeJwt(jwtToken) + .catch((err: JwtError) => { + if (err.code == JwtErrorCode.INVALID_ARGUMENT) { + const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` + + `for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`; + const errorMessage = `Decoding ${this.tokenInfo.jwtName} failed. Make sure you passed ` + + `the entire string JWT which represents ${this.shortNameArticle} ` + + `${this.tokenInfo.shortName}.` + verifyJwtTokenDocsMessage; + throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, + errorMessage); + } + throw new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR, err.message); + }); + } + + /** + * Verifies the content of a Firebase Auth JWT. + * + * @param fullDecodedToken The decoded JWT. + * @param projectId The Firebase Project Id. + * @param isEmulator Whether the token is an Emulator token. + */ + private verifyContent( + fullDecodedToken: DecodedToken, + projectId: string | null, + isEmulator: boolean): void { const header = fullDecodedToken && fullDecodedToken.header; const payload = fullDecodedToken && fullDecodedToken.payload; @@ -346,10 +380,7 @@ export class FirebaseTokenVerifier { `for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`; let errorMessage: string | undefined; - if (!fullDecodedToken) { - errorMessage = `Decoding ${this.tokenInfo.jwtName} failed. Make sure you passed the entire string JWT ` + - `which represents ${this.shortNameArticle} ${this.tokenInfo.shortName}.` + verifyJwtTokenDocsMessage; - } else if (typeof header.kid === 'undefined' && this.algorithm !== 'none') { + if (!isEmulator && typeof header.kid === 'undefined') { const isCustomToken = (payload.aud === FIREBASE_AUDIENCE); const isLegacyCustomToken = (header.alg === 'HS256' && payload.v === 0 && 'd' in payload && 'uid' in payload.d); @@ -364,8 +395,8 @@ export class FirebaseTokenVerifier { } errorMessage += verifyJwtTokenDocsMessage; - } else if (header.alg !== this.algorithm) { - errorMessage = `${this.tokenInfo.jwtName} has incorrect algorithm. Expected "` + this.algorithm + '" but got ' + + } else if (!isEmulator && header.alg !== ALGORITHM_RS256) { + errorMessage = `${this.tokenInfo.jwtName} has incorrect algorithm. Expected "` + ALGORITHM_RS256 + '" but got ' + '"' + header.alg + '".' + verifyJwtTokenDocsMessage; } else if (payload.aud !== projectId) { errorMessage = `${this.tokenInfo.jwtName} has incorrect "aud" (audience) claim. Expected "` + @@ -373,7 +404,7 @@ export class FirebaseTokenVerifier { verifyJwtTokenDocsMessage; } else if (payload.iss !== this.issuer + projectId) { errorMessage = `${this.tokenInfo.jwtName} has incorrect "iss" (issuer) claim. Expected ` + - `"${this.issuer}"` + projectId + '" but got "' + + `"${this.issuer}` + projectId + '" but got "' + payload.iss + '".' + projectIdMatchMessage + verifyJwtTokenDocsMessage; } else if (typeof payload.sub !== 'string') { errorMessage = `${this.tokenInfo.jwtName} has no "sub" (subject) claim.` + verifyJwtTokenDocsMessage; @@ -384,135 +415,56 @@ export class FirebaseTokenVerifier { verifyJwtTokenDocsMessage; } if (errorMessage) { - return Promise.reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage)); - } - - // When the algorithm is set to 'none' there will be no signature and therefore we don't check - // the public keys. - if (this.algorithm === 'none') { - return this.verifyJwtSignatureWithKey(jwtToken, null); + throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); } - - return this.fetchPublicKeys().then((publicKeys) => { - if (!Object.prototype.hasOwnProperty.call(publicKeys, header.kid)) { - return Promise.reject( - new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - `${this.tokenInfo.jwtName} has "kid" claim which does not correspond to a known public key. ` + - `Most likely the ${this.tokenInfo.shortName} is expired, so get a fresh token from your ` + - 'client app and try again.', - ), - ); - } else { - return this.verifyJwtSignatureWithKey(jwtToken, publicKeys[header.kid]); - } - - }); } - /** - * Verifies the JWT signature using the provided public key. - * @param {string} jwtToken The JWT token to verify. - * @param {string} publicKey The public key certificate. - * @return {Promise} A promise that resolves with the decoded JWT claims on successful - * verification. - */ - private verifyJwtSignatureWithKey(jwtToken: string, publicKey: string | null): Promise { - const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` + - `for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`; - return new Promise((resolve, reject) => { - jwt.verify(jwtToken, publicKey || '', { - algorithms: [this.algorithm], - }, (error: jwt.VerifyErrors | null, decodedToken: object | undefined) => { - if (error) { - if (error.name === 'TokenExpiredError') { - const errorMessage = `${this.tokenInfo.jwtName} has expired. Get a fresh ${this.tokenInfo.shortName}` + - ` from your client app and try again (auth/${this.tokenInfo.expiredErrorCode.code}).` + - verifyJwtTokenDocsMessage; - return reject(new FirebaseAuthError(this.tokenInfo.expiredErrorCode, errorMessage)); - } else if (error.name === 'JsonWebTokenError') { - const errorMessage = `${this.tokenInfo.jwtName} has invalid signature.` + verifyJwtTokenDocsMessage; - return reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage)); - } - return reject(new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, error.message)); - } else { - const decodedIdToken = (decodedToken as DecodedIdToken); - decodedIdToken.uid = decodedIdToken.sub; - resolve(decodedIdToken); - } + private verifySignature(jwtToken: string, isEmulator: boolean): + Promise { + const verifier = isEmulator ? EMULATOR_VERIFIER : this.signatureVerifier; + return verifier.verify(jwtToken) + .catch((error) => { + throw this.mapJwtErrorToAuthError(error); }); - }); } /** - * Fetches the public keys for the Google certs. + * Maps JwtError to FirebaseAuthError * - * @return {Promise} A promise fulfilled with public keys for the Google certs. + * @param error JwtError to be mapped. + * @returns FirebaseAuthError or Error instance. */ - private fetchPublicKeys(): Promise<{[key: string]: string}> { - const publicKeysExist = (typeof this.publicKeys !== 'undefined'); - const publicKeysExpiredExists = (typeof this.publicKeysExpireAt !== 'undefined'); - const publicKeysStillValid = (publicKeysExpiredExists && Date.now() < this.publicKeysExpireAt); - if (publicKeysExist && publicKeysStillValid) { - return Promise.resolve(this.publicKeys); + private mapJwtErrorToAuthError(error: JwtError): Error { + const verifyJwtTokenDocsMessage = ` See ${this.tokenInfo.url} ` + + `for details on how to retrieve ${this.shortNameArticle} ${this.tokenInfo.shortName}.`; + if (error.code === JwtErrorCode.TOKEN_EXPIRED) { + const errorMessage = `${this.tokenInfo.jwtName} has expired. Get a fresh ${this.tokenInfo.shortName}` + + ` from your client app and try again (auth/${this.tokenInfo.expiredErrorCode.code}).` + + verifyJwtTokenDocsMessage; + return new FirebaseAuthError(this.tokenInfo.expiredErrorCode, errorMessage); + } else if (error.code === JwtErrorCode.INVALID_SIGNATURE) { + const errorMessage = `${this.tokenInfo.jwtName} has invalid signature.` + verifyJwtTokenDocsMessage; + return new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); + } else if (error.code === JwtErrorCode.NO_MATCHING_KID) { + const errorMessage = `${this.tokenInfo.jwtName} has "kid" claim which does not ` + + `correspond to a known public key. Most likely the ${this.tokenInfo.shortName} ` + + 'is expired, so get a fresh token from your client app and try again.'; + return new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, errorMessage); } - - const client = new HttpClient(); - const request: HttpRequestConfig = { - method: 'GET', - url: this.clientCertUrl, - httpAgent: this.app.options.httpAgent, - }; - return client.send(request).then((resp) => { - if (!resp.isJson() || resp.data.error) { - // Treat all non-json messages and messages with an 'error' field as - // error responses. - throw new HttpError(resp); - } - if (Object.prototype.hasOwnProperty.call(resp.headers, 'cache-control')) { - const cacheControlHeader: string = resp.headers['cache-control']; - const parts = cacheControlHeader.split(','); - parts.forEach((part) => { - const subParts = part.trim().split('='); - if (subParts[0] === 'max-age') { - const maxAge: number = +subParts[1]; - this.publicKeysExpireAt = Date.now() + (maxAge * 1000); - } - }); - } - this.publicKeys = resp.data; - return resp.data; - }).catch((err) => { - if (err instanceof HttpError) { - let errorMessage = 'Error fetching public keys for Google certs: '; - const resp = err.response; - if (resp.isJson() && resp.data.error) { - errorMessage += `${resp.data.error}`; - if (resp.data.error_description) { - errorMessage += ' (' + resp.data.error_description + ')'; - } - } else { - errorMessage += `${resp.text}`; - } - throw new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR, errorMessage); - } - throw err; - }); + return new FirebaseAuthError(AuthClientErrorCode.INVALID_ARGUMENT, error.message); } } /** * Creates a new FirebaseTokenVerifier to verify Firebase ID tokens. * - * @param {FirebaseApp} app Firebase app instance. - * @return {FirebaseTokenVerifier} - * * @internal + * @param app Firebase app instance. + * @return FirebaseTokenVerifier */ export function createIdTokenVerifier(app: App): FirebaseTokenVerifier { return new FirebaseTokenVerifier( CLIENT_CERT_URL, - ALGORITHM_RS256, 'https://securetoken.google.com/', ID_TOKEN_INFO, app @@ -522,15 +474,13 @@ export function createIdTokenVerifier(app: App): FirebaseTokenVerifier { /** * Creates a new FirebaseTokenVerifier to verify Firebase session cookies. * - * @param {FirebaseApp} app Firebase app instance. - * @return {FirebaseTokenVerifier} - * * @internal + * @param app Firebase app instance. + * @return FirebaseTokenVerifier */ export function createSessionCookieVerifier(app: App): FirebaseTokenVerifier { return new FirebaseTokenVerifier( SESSION_COOKIE_CERT_URL, - ALGORITHM_RS256, 'https://session.firebase.google.com/', SESSION_COOKIE_INFO, app diff --git a/src/database/database.ts b/src/database/database.ts index 94a00335bd..e3ff53b010 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -54,9 +54,13 @@ export interface Database extends FirebaseDatabase { setRules(source: string | Buffer | object): Promise; } +const TOKEN_REFRESH_THRESHOLD_MILLIS = 5 * 60 * 1000; + export class DatabaseService { private readonly appInternal: App; + private tokenListener: (token: string) => void; + private tokenRefreshTimeout: NodeJS.Timeout; private databases: { [dbUrl: string]: Database; @@ -72,10 +76,19 @@ export class DatabaseService { this.appInternal = app; } + private get firebsaeApp(): FirebaseApp { + return this.app as FirebaseApp; + } + /** * @internal */ public delete(): Promise { + if (this.tokenListener) { + this.firebsaeApp.INTERNAL.removeAuthTokenListener(this.tokenListener); + clearTimeout(this.tokenRefreshTimeout); + } + const promises = []; for (const dbUrl of Object.keys(this.databases)) { const db: DatabaseImpl = ((this.databases[dbUrl] as any) as DatabaseImpl); @@ -122,9 +135,43 @@ export class DatabaseService { this.databases[dbUrl] = db; } + + if (!this.tokenListener) { + this.tokenListener = this.onTokenChange.bind(this); + this.firebsaeApp.INTERNAL.addAuthTokenListener(this.tokenListener); + } + return db; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private onTokenChange(_: string): void { + this.firebsaeApp.INTERNAL.getToken() + .then((token) => { + const delayMillis = token.expirationTime - TOKEN_REFRESH_THRESHOLD_MILLIS - Date.now(); + // If the new token is set to expire soon (unlikely), do nothing. Somebody will eventually + // notice and refresh the token, at which point this callback will fire again. + if (delayMillis > 0) { + this.scheduleTokenRefresh(delayMillis); + } + }) + .catch((err) => { + console.error('Unexpected error while attempting to schedule a token refresh:', err); + }); + } + + private scheduleTokenRefresh(delayMillis: number): void { + clearTimeout(this.tokenRefreshTimeout); + this.tokenRefreshTimeout = setTimeout(() => { + this.firebsaeApp.INTERNAL.getToken(/*forceRefresh=*/ true) + .catch(() => { + // Ignore the error since this might just be an intermittent failure. If we really cannot + // refresh the token, an error will be logged once the existing token expires and we try + // to fetch a fresh one. + }); + }, delayMillis); + } + private ensureUrl(url?: string): string { if (typeof url !== 'undefined') { return url; @@ -149,7 +196,13 @@ class DatabaseRulesClient { private readonly httpClient: AuthorizedHttpClient; constructor(app: App, dbUrl: string) { - const parsedUrl = new URL(dbUrl); + let parsedUrl = new URL(dbUrl); + const emulatorHost = process.env.FIREBASE_DATABASE_EMULATOR_HOST; + if (emulatorHost) { + const namespace = extractNamespace(parsedUrl); + parsedUrl = new URL(`http://${emulatorHost}?ns=${namespace}`); + } + parsedUrl.pathname = path.join(parsedUrl.pathname, RULES_URL_PATH); this.dbUrl = parsedUrl.toString(); this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); @@ -159,7 +212,7 @@ class DatabaseRulesClient { * Gets the currently applied security rules as a string. The return value consists of * the rules source including comments. * - * @return {Promise} A promise fulfilled with the rules as a raw string. + * @return A promise fulfilled with the rules as a raw string. */ public getRules(): Promise { const req: HttpRequestConfig = { @@ -259,3 +312,14 @@ class DatabaseRulesClient { return `${intro}: ${err.response.text}`; } } + +function extractNamespace(parsedUrl: URL): string { + const ns = parsedUrl.searchParams.get('ns'); + if (ns) { + return ns; + } + + const hostname = parsedUrl.hostname; + const dotIndex = hostname.indexOf('.'); + return hostname.substring(0, dotIndex).toLowerCase(); +} diff --git a/src/storage/storage.ts b/src/storage/storage.ts index 143ea25575..dc36b72f52 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -45,6 +45,10 @@ export class Storage { }); } + if (!process.env.STORAGE_EMULATOR_HOST && process.env.FIREBASE_STORAGE_EMULATOR_HOST) { + process.env.STORAGE_EMULATOR_HOST = process.env.FIREBASE_STORAGE_EMULATOR_HOST; + } + let storage: typeof StorageClient; try { storage = require('@google-cloud/storage').Storage; diff --git a/src/utils/error.ts b/src/utils/error.ts index 3f8fa28495..91fe79debb 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { FirebaseError as FireabseErrorInterface } from '../app'; +import { FirebaseError as FirebaseErrorInterface } from '../app'; import { deepCopy } from '../utils/deep-copy'; /** @@ -39,7 +39,7 @@ interface ServerToClientCode { * @param {ErrorInfo} errorInfo The error information (code and message). * @constructor */ -export class FirebaseError extends Error implements FireabseErrorInterface { +export class FirebaseError extends Error implements FirebaseErrorInterface { constructor(private errorInfo: ErrorInfo) { super(errorInfo.message); diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts new file mode 100644 index 0000000000..d048567061 --- /dev/null +++ b/src/utils/jwt.ts @@ -0,0 +1,275 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as validator from './validator'; +import * as jwt from 'jsonwebtoken'; +import { HttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; +import { Agent } from 'http'; + +export const ALGORITHM_RS256: jwt.Algorithm = 'RS256' as const; + +// `jsonwebtoken` converts errors from the `getKey` callback to its own `JsonWebTokenError` type +// and prefixes the error message with the following. Use the prefix to identify errors thrown +// from the key provider callback. +// https://github.com/auth0/node-jsonwebtoken/blob/d71e383862fc735991fd2e759181480f066bf138/verify.js#L96 +const JWT_CALLBACK_ERROR_PREFIX = 'error in secret or public key callback: '; + +const NO_MATCHING_KID_ERROR_MESSAGE = 'no-matching-kid-error'; + +export type Dictionary = { [key: string]: any } + +export type DecodedToken = { + header: Dictionary; + payload: Dictionary; +} + +export interface SignatureVerifier { + verify(token: string): Promise; +} + +interface KeyFetcher { + fetchPublicKeys(): Promise<{ [key: string]: string }>; +} + +/** + * Class to fetch public keys from a client certificates URL. + */ +export class UrlKeyFetcher implements KeyFetcher { + private publicKeys: { [key: string]: string }; + private publicKeysExpireAt = 0; + + constructor(private clientCertUrl: string, private readonly httpAgent?: Agent) { + if (!validator.isURL(clientCertUrl)) { + throw new Error( + 'The provided public client certificate URL is not a valid URL.', + ); + } + } + + /** + * Fetches the public keys for the Google certs. + * + * @return A promise fulfilled with public keys for the Google certs. + */ + public fetchPublicKeys(): Promise<{ [key: string]: string }> { + if (this.shouldRefresh()) { + return this.refresh(); + } + return Promise.resolve(this.publicKeys); + } + + /** + * Checks if the cached public keys need to be refreshed. + * + * @returns Whether the keys should be fetched from the client certs url or not. + */ + private shouldRefresh(): boolean { + return !this.publicKeys || this.publicKeysExpireAt <= Date.now(); + } + + private refresh(): Promise<{ [key: string]: string }> { + const client = new HttpClient(); + const request: HttpRequestConfig = { + method: 'GET', + url: this.clientCertUrl, + httpAgent: this.httpAgent, + }; + return client.send(request).then((resp) => { + if (!resp.isJson() || resp.data.error) { + // Treat all non-json messages and messages with an 'error' field as + // error responses. + throw new HttpError(resp); + } + // reset expire at from previous set of keys. + this.publicKeysExpireAt = 0; + if (Object.prototype.hasOwnProperty.call(resp.headers, 'cache-control')) { + const cacheControlHeader: string = resp.headers['cache-control']; + const parts = cacheControlHeader.split(','); + parts.forEach((part) => { + const subParts = part.trim().split('='); + if (subParts[0] === 'max-age') { + const maxAge: number = +subParts[1]; + this.publicKeysExpireAt = Date.now() + (maxAge * 1000); + } + }); + } + this.publicKeys = resp.data; + return resp.data; + }).catch((err) => { + if (err instanceof HttpError) { + let errorMessage = 'Error fetching public keys for Google certs: '; + const resp = err.response; + if (resp.isJson() && resp.data.error) { + errorMessage += `${resp.data.error}`; + if (resp.data.error_description) { + errorMessage += ' (' + resp.data.error_description + ')'; + } + } else { + errorMessage += `${resp.text}`; + } + throw new Error(errorMessage); + } + throw err; + }); + } +} + +/** + * Class for verifing JWT signature with a public key. + */ +export class PublicKeySignatureVerifier implements SignatureVerifier { + constructor(private keyFetcher: KeyFetcher) { + if (!validator.isNonNullObject(keyFetcher)) { + throw new Error('The provided key fetcher is not an object or null.'); + } + } + + public static withCertificateUrl(clientCertUrl: string, httpAgent?: Agent): PublicKeySignatureVerifier { + return new PublicKeySignatureVerifier(new UrlKeyFetcher(clientCertUrl, httpAgent)); + } + + public verify(token: string): Promise { + if (!validator.isString(token)) { + return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, + 'The provided token must be a string.')); + } + + return verifyJwtSignature(token, getKeyCallback(this.keyFetcher), { algorithms: [ALGORITHM_RS256] }); + } +} + +/** + * Class for verifing unsigned (emulator) JWTs. + */ +export class EmulatorSignatureVerifier implements SignatureVerifier { + public verify(token: string): Promise { + // Signature checks skipped for emulator; no need to fetch public keys. + return verifyJwtSignature(token, ''); + } +} + +/** + * Provides a callback to fetch public keys. + * + * @param fetcher KeyFetcher to fetch the keys from. + * @returns A callback function that can be used to get keys in `jsonwebtoken`. + */ +function getKeyCallback(fetcher: KeyFetcher): jwt.GetPublicKeyOrSecret { + return (header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) => { + const kid = header.kid || ''; + fetcher.fetchPublicKeys().then((publicKeys) => { + if (!Object.prototype.hasOwnProperty.call(publicKeys, kid)) { + callback(new Error(NO_MATCHING_KID_ERROR_MESSAGE)); + } else { + callback(null, publicKeys[kid]); + } + }) + .catch(error => { + callback(error); + }); + } +} + +/** + * Verifies the signature of a JWT using the provided secret or a function to fetch + * the secret or public key. + * + * @param token The JWT to be verfied. + * @param secretOrPublicKey The secret or a function to fetch the secret or public key. + * @param options JWT verification options. + * @returns A Promise resolving for a token with a valid signature. + */ +export function verifyJwtSignature(token: string, secretOrPublicKey: jwt.Secret | jwt.GetPublicKeyOrSecret, + options?: jwt.VerifyOptions): Promise { + if (!validator.isString(token)) { + return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, + 'The provided token must be a string.')); + } + + return new Promise((resolve, reject) => { + jwt.verify(token, secretOrPublicKey, options, + (error: jwt.VerifyErrors | null) => { + if (!error) { + return resolve(); + } + if (error.name === 'TokenExpiredError') { + return reject(new JwtError(JwtErrorCode.TOKEN_EXPIRED, + 'The provided token has expired. Get a fresh token from your ' + + 'client app and try again.')); + } else if (error.name === 'JsonWebTokenError') { + if (error.message && error.message.includes(JWT_CALLBACK_ERROR_PREFIX)) { + const message = error.message.split(JWT_CALLBACK_ERROR_PREFIX).pop() || 'Error fetching public keys.'; + const code = (message === NO_MATCHING_KID_ERROR_MESSAGE) ? JwtErrorCode.NO_MATCHING_KID : + JwtErrorCode.KEY_FETCH_ERROR; + return reject(new JwtError(code, message)); + } + } + return reject(new JwtError(JwtErrorCode.INVALID_SIGNATURE, error.message)); + }); + }); +} + +/** + * Decodes general purpose Firebase JWTs. + * + * @param jwtToken JWT token to be decoded. + * @returns Decoded token containing the header and payload. + */ +export function decodeJwt(jwtToken: string): Promise { + if (!validator.isString(jwtToken)) { + return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, + 'The provided token must be a string.')); + } + + const fullDecodedToken: any = jwt.decode(jwtToken, { + complete: true, + }); + + if (!fullDecodedToken) { + return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, + 'Decoding token failed.')); + } + + const header = fullDecodedToken?.header; + const payload = fullDecodedToken?.payload; + return Promise.resolve({ header, payload }); +} + +/** + * Jwt error code structure. + * + * @param code The error code. + * @param message The error message. + * @constructor + */ +export class JwtError extends Error { + constructor(readonly code: JwtErrorCode, readonly message: string) { + super(message); + (this as any).__proto__ = JwtError.prototype; + } +} + +/** + * JWT error codes. + */ +export enum JwtErrorCode { + INVALID_ARGUMENT = 'invalid-argument', + INVALID_CREDENTIAL = 'invalid-credential', + TOKEN_EXPIRED = 'token-expired', + INVALID_SIGNATURE = 'invalid-token', + NO_MATCHING_KID = 'no-matching-kid-error', + KEY_FETCH_ERROR = 'key-fetch-error', +} diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index f16b7546fe..07499ec3b8 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -41,6 +41,8 @@ chai.use(chaiAsPromised); const expect = chai.expect; +const authEmulatorHost = process.env.FIREBASE_AUTH_EMULATOR_HOST; + const newUserUid = generateRandomString(20); const nonexistentUid = generateRandomString(20); const newMultiFactorUserUid = generateRandomString(20); @@ -106,6 +108,9 @@ describe('admin.auth', () => { apiKey, authDomain: projectId + '.firebaseapp.com', }); + if (authEmulatorHost) { + (clientAuth() as any).useEmulator('http://' + authEmulatorHost); + } return cleanup(); }); @@ -141,7 +146,10 @@ describe('admin.auth', () => { }); }); - it('createUser() creates a new user with enrolled second factors', () => { + it('createUser() creates a new user with enrolled second factors', function () { + if (authEmulatorHost) { + return this.skip(); // Not yet supported in Auth Emulator. + } const enrolledFactors = [ { phoneNumber: '+16505550001', @@ -217,6 +225,56 @@ describe('admin.auth', () => { }); }); + it('getUserByProviderUid() returns a user record with the matching provider id', async () => { + // TODO(rsgowman): Once we can link a provider id with a user, just do that + // here instead of creating a new user. + const randomUid = 'import_' + generateRandomString(20).toLowerCase(); + const importUser: UserImportRecord = { + uid: randomUid, + email: 'user@example.com', + phoneNumber: '+15555550000', + emailVerified: true, + disabled: false, + metadata: { + lastSignInTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + creationTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + }, + providerData: [{ + displayName: 'User Name', + email: 'user@example.com', + phoneNumber: '+15555550000', + photoURL: 'http://example.com/user', + providerId: 'google.com', + uid: 'google_uid', + }], + }; + + await getAuth().importUsers([importUser]); + + try { + await getAuth().getUserByProviderUid('google.com', 'google_uid') + .then((userRecord) => { + expect(userRecord.uid).to.equal(importUser.uid); + }); + } finally { + await safeDelete(importUser.uid); + } + }); + + it('getUserByProviderUid() redirects to getUserByEmail if given an email', () => { + return getAuth().getUserByProviderUid('email', mockUserData.email) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + + it('getUserByProviderUid() redirects to getUserByPhoneNumber if given a phone number', () => { + return getAuth().getUserByProviderUid('phone', mockUserData.phoneNumber) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + describe('getUsers()', () => { /** * Filters a list of object to another list of objects that only contains @@ -357,7 +415,7 @@ describe('admin.auth', () => { const metadata = userRecord!.metadata; expect(metadata.lastRefreshTime).to.exist; - expect(isUTCString(metadata.lastRefreshTime!)); + expect(isUTCString(metadata.lastRefreshTime!)).to.be.true; const creationTime = new Date(metadata.creationTime).getTime(); const lastRefreshTime = new Date(metadata.lastRefreshTime!).getTime(); expect(creationTime).lte(lastRefreshTime); @@ -414,7 +472,7 @@ describe('admin.auth', () => { }); }); - it('revokeRefreshTokens() invalidates existing sessions and ID tokens', () => { + it('revokeRefreshTokens() invalidates existing sessions and ID tokens', async () => { let currentIdToken: string; let currentUser: User; // Sign in with an email and password account. @@ -437,9 +495,14 @@ describe('admin.auth', () => { ), 1000)); }) .then(() => { - // verifyIdToken without checking revocation should still succeed. - return getAuth().verifyIdToken(currentIdToken) - .should.eventually.be.fulfilled; + const verifyingIdToken = getAuth().verifyIdToken(currentIdToken) + if (authEmulatorHost) { + // Check revocation is forced in emulator-mode and this should throw. + return verifyingIdToken.should.eventually.be.rejected; + } else { + // verifyIdToken without checking revocation should still succeed. + return verifyingIdToken.should.eventually.be.fulfilled; + } }) .then(() => { // verifyIdToken while checking for revocation should fail. @@ -524,68 +587,183 @@ describe('admin.auth', () => { }); }); - it('updateUser() updates the user record with the given parameters', () => { - const updatedDisplayName = 'Updated User ' + newUserUid; - const now = new Date(1476235905000).toUTCString(); - // Update user with enrolled second factors. - const enrolledFactors = [ - { - uid: 'mfaUid1', - phoneNumber: '+16505550001', - displayName: 'Work phone number', - factorId: 'phone', - enrollmentTime: now, - }, - { - uid: 'mfaUid2', - phoneNumber: '+16505550002', - displayName: 'Personal phone number', - factorId: 'phone', - enrollmentTime: now, - }, - ]; - return getAuth().updateUser(newUserUid, { - email: updatedEmail, - phoneNumber: updatedPhone, - emailVerified: true, - displayName: updatedDisplayName, - multiFactor: { - enrolledFactors, - }, - }) - .then((userRecord) => { - expect(userRecord.emailVerified).to.be.true; - expect(userRecord.displayName).to.equal(updatedDisplayName); - // Confirm expected email. - expect(userRecord.email).to.equal(updatedEmail); - // Confirm expected phone number. - expect(userRecord.phoneNumber).to.equal(updatedPhone); - // Confirm second factors added to user. - const actualUserRecord: {[key: string]: any} = userRecord.toJSON(); - expect(actualUserRecord.multiFactor.enrolledFactors.length).to.equal(2); - expect(actualUserRecord.multiFactor.enrolledFactors).to.deep.equal(enrolledFactors); - // Update list of second factors. - return getAuth().updateUser(newUserUid, { - multiFactor: { - enrolledFactors: [enrolledFactors[0]], - }, - }); + describe('updateUser()', () => { + /** + * Creates a new user for testing purposes. The user's uid will be + * '$name_$tenRandomChars' and email will be + * '$name_$tenRandomChars@example.com'. + */ + // TODO(rsgowman): This function could usefully be employed throughout this file. + function createTestUser(name: string): Promise { + const tenRandomChars = generateRandomString(10); + return getAuth().createUser({ + uid: name + '_' + tenRandomChars, + displayName: name, + email: name + '_' + tenRandomChars + '@example.com', + }); + } + + let updateUser: UserRecord; + before(async () => { + updateUser = await createTestUser('UpdateUser'); + }); + + after(() => { + return safeDelete(updateUser.uid); + }); + + it('updates the user record with the given parameters', () => { + const updatedDisplayName = 'Updated User ' + updateUser.uid; + return getAuth().updateUser(updateUser.uid, { + email: updatedEmail, + phoneNumber: updatedPhone, + emailVerified: true, + displayName: updatedDisplayName, }) - .then((userRecord) => { - expect(userRecord.multiFactor!.enrolledFactors.length).to.equal(1); - const actualUserRecord: {[key: string]: any} = userRecord.toJSON(); - expect(actualUserRecord.multiFactor.enrolledFactors[0]).to.deep.equal(enrolledFactors[0]); - // Remove all second factors. - return getAuth().updateUser(newUserUid, { - multiFactor: { - enrolledFactors: null, - }, + .then((userRecord) => { + expect(userRecord.emailVerified).to.be.true; + expect(userRecord.displayName).to.equal(updatedDisplayName); + // Confirm expected email. + expect(userRecord.email).to.equal(updatedEmail); + // Confirm expected phone number. + expect(userRecord.phoneNumber).to.equal(updatedPhone); }); + }); + + it('creates, updates, and removes second factors', function () { + if (authEmulatorHost) { + return this.skip(); // Not yet supported in Auth Emulator. + } + + const now = new Date(1476235905000).toUTCString(); + // Update user with enrolled second factors. + const enrolledFactors = [ + { + uid: 'mfaUid1', + phoneNumber: '+16505550001', + displayName: 'Work phone number', + factorId: 'phone', + enrollmentTime: now, + }, + { + uid: 'mfaUid2', + phoneNumber: '+16505550002', + displayName: 'Personal phone number', + factorId: 'phone', + enrollmentTime: now, + }, + ]; + return getAuth().updateUser(updateUser.uid, { + multiFactor: { + enrolledFactors, + }, }) - .then((userRecord) => { - // Confirm all second factors removed. - expect(userRecord.multiFactor).to.be.undefined; + .then((userRecord) => { + // Confirm second factors added to user. + const actualUserRecord: {[key: string]: any} = userRecord.toJSON(); + expect(actualUserRecord.multiFactor.enrolledFactors.length).to.equal(2); + expect(actualUserRecord.multiFactor.enrolledFactors).to.deep.equal(enrolledFactors); + // Update list of second factors. + return getAuth().updateUser(updateUser.uid, { + multiFactor: { + enrolledFactors: [enrolledFactors[0]], + }, + }); + }) + .then((userRecord) => { + expect(userRecord.multiFactor!.enrolledFactors.length).to.equal(1); + const actualUserRecord: {[key: string]: any} = userRecord.toJSON(); + expect(actualUserRecord.multiFactor.enrolledFactors[0]).to.deep.equal(enrolledFactors[0]); + // Remove all second factors. + return getAuth().updateUser(updateUser.uid, { + multiFactor: { + enrolledFactors: null, + }, + }); + }) + .then((userRecord) => { + // Confirm all second factors removed. + expect(userRecord.multiFactor).to.be.undefined; + }); + }); + + it('can link/unlink with a federated provider', async function () { + if (authEmulatorHost) { + return this.skip(); // Not yet supported in Auth Emulator. + } + const googleFederatedUid = 'google_uid_' + generateRandomString(10); + let userRecord = await getAuth().updateUser(updateUser.uid, { + providerToLink: { + providerId: 'google.com', + uid: googleFederatedUid, + }, }); + + let providerUids = userRecord.providerData.map((userInfo) => userInfo.uid); + let providerIds = userRecord.providerData.map((userInfo) => userInfo.providerId); + expect(providerUids).to.deep.include(googleFederatedUid); + expect(providerIds).to.deep.include('google.com'); + + userRecord = await getAuth().updateUser(updateUser.uid, { + providersToUnlink: ['google.com'], + }); + + providerUids = userRecord.providerData.map((userInfo) => userInfo.uid); + providerIds = userRecord.providerData.map((userInfo) => userInfo.providerId); + expect(providerUids).to.not.deep.include(googleFederatedUid); + expect(providerIds).to.not.deep.include('google.com'); + }); + + it('can unlink multiple providers at once, incl a non-federated provider', async function () { + if (authEmulatorHost) { + return this.skip(); // Not yet supported in Auth Emulator. + } + await deletePhoneNumberUser('+15555550001'); + + const googleFederatedUid = 'google_uid_' + generateRandomString(10); + const facebookFederatedUid = 'facebook_uid_' + generateRandomString(10); + + let userRecord = await getAuth().updateUser(updateUser.uid, { + phoneNumber: '+15555550001', + providerToLink: { + providerId: 'google.com', + uid: googleFederatedUid, + }, + }); + userRecord = await getAuth().updateUser(updateUser.uid, { + providerToLink: { + providerId: 'facebook.com', + uid: facebookFederatedUid, + }, + }); + + let providerUids = userRecord.providerData.map((userInfo) => userInfo.uid); + let providerIds = userRecord.providerData.map((userInfo) => userInfo.providerId); + expect(providerUids).to.deep.include.members([googleFederatedUid, facebookFederatedUid, '+15555550001']); + expect(providerIds).to.deep.include.members(['google.com', 'facebook.com', 'phone']); + + userRecord = await getAuth().updateUser(updateUser.uid, { + providersToUnlink: ['google.com', 'facebook.com', 'phone'], + }); + + providerUids = userRecord.providerData.map((userInfo) => userInfo.uid); + providerIds = userRecord.providerData.map((userInfo) => userInfo.providerId); + expect(providerUids).to.not.deep.include.members([googleFederatedUid, facebookFederatedUid, '+15555550001']); + expect(providerIds).to.not.deep.include.members(['google.com', 'facebook.com', 'phone']); + }); + + it('noops successfully when given an empty providersToUnlink list', async () => { + const userRecord = await createTestUser('NoopWithEmptyProvidersToDeleteUser'); + try { + const updatedUserRecord = await getAuth().updateUser(userRecord.uid, { + providersToUnlink: [], + }); + + expect(updatedUserRecord).to.deep.equal(userRecord); + } finally { + safeDelete(userRecord.uid); + } + }); }); it('getUser() fails when called with a non-existing UID', () => { @@ -603,6 +781,11 @@ describe('admin.auth', () => { .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); + it('getUserByProviderUid() fails when called with a non-existing provider id', () => { + return getAuth().getUserByProviderUid('google.com', nonexistentUid) + .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); + }); + it('updateUser() fails when called with a non-existing UID', () => { return getAuth().updateUser(nonexistentUid, { emailVerified: true, @@ -659,6 +842,49 @@ describe('admin.auth', () => { .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); + if (authEmulatorHost) { + describe('Auth emulator support', () => { + const uid = 'authEmulatorUser'; + before(() => { + return getAuth().createUser({ + uid, + email: 'lastRefreshTimeUser@example.com', + password: 'p4ssword', + }); + }); + after(() => { + return getAuth().deleteUser(uid); + }); + + it('verifyIdToken() succeeds when called with an unsigned token', () => { + const unsignedToken = mocks.generateIdToken({ + algorithm: 'none', + audience: projectId, + issuer: 'https://securetoken.google.com/' + projectId, + subject: uid, + }); + return getAuth().verifyIdToken(unsignedToken); + }); + + it('verifyIdToken() fails when called with a token with wrong project', () => { + const unsignedToken = mocks.generateIdToken({ algorithm: 'none', audience: 'nosuch' }); + return getAuth().verifyIdToken(unsignedToken) + .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); + }); + + it('verifyIdToken() fails when called with a token that does not belong to a user', () => { + const unsignedToken = mocks.generateIdToken({ + algorithm: 'none', + audience: projectId, + issuer: 'https://securetoken.google.com/' + projectId, + subject: 'nosuch', + }); + return getAuth().verifyIdToken(unsignedToken) + .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); + }); + }); + } + describe('Link operations', () => { const uid = generateRandomString(20).toLowerCase(); const email = uid + '@example.com'; @@ -768,6 +994,7 @@ describe('admin.auth', () => { enabled: true, passwordRequired: true, }, + anonymousSignInEnabled: false, multiFactorConfig: { state: 'ENABLED', factorIds: ['phone'], @@ -783,6 +1010,7 @@ describe('admin.auth', () => { enabled: false, passwordRequired: true, }, + anonymousSignInEnabled: false, multiFactorConfig: { state: 'DISABLED', factorIds: [], @@ -797,6 +1025,7 @@ describe('admin.auth', () => { enabled: true, passwordRequired: false, }, + anonymousSignInEnabled: false, multiFactorConfig: { state: 'ENABLED', factorIds: ['phone'], @@ -839,6 +1068,20 @@ describe('admin.auth', () => { }); }); + it('createTenant() can enable anonymous users', async () => { + const tenant = await getAuth().tenantManager().createTenant({ + displayName: 'testTenantWithAnon', + emailSignInConfig: { + enabled: false, + passwordRequired: true, + }, + anonymousSignInEnabled: true, + }); + createdTenants.push(tenant.tenantId); + + expect(tenant.anonymousSignInEnabled).to.be.true; + }); + // Sanity check user management + email link generation + custom attribute APIs. // TODO: Confirm behavior in client SDK when it starts supporting it. describe('supports user management, email link generation, custom attribute and token revocation APIs', () => { @@ -1182,6 +1425,25 @@ describe('admin.auth', () => { }); }); + it('updateTenant() should be able to enable/disable anon provider', async () => { + const tenantManager = getAuth().tenantManager(); + let tenant = await tenantManager.createTenant({ + displayName: 'testTenantUpdateAnon', + }); + createdTenants.push(tenant.tenantId); + expect(tenant.anonymousSignInEnabled).to.be.false; + + tenant = await tenantManager.updateTenant(tenant.tenantId, { + anonymousSignInEnabled: true, + }); + expect(tenant.anonymousSignInEnabled).to.be.true; + + tenant = await tenantManager.updateTenant(tenant.tenantId, { + anonymousSignInEnabled: false, + }); + expect(tenant.anonymousSignInEnabled).to.be.false; + }); + it('listTenants() should resolve with expected number of tenants', () => { const allTenantIds: string[] = []; const tenantOptions2 = deepCopy(tenantOptions); @@ -1257,7 +1519,10 @@ describe('admin.auth', () => { }; // Clean up temp configurations used for test. - before(() => { + before(function () { + if (authEmulatorHost) { + return this.skip(); // Not implemented. + } return removeTempConfigs().then(() => getAuth().createProviderConfig(authProviderConfig1)); }); @@ -1389,7 +1654,10 @@ describe('admin.auth', () => { }; // Clean up temp configurations used for test. - before(() => { + before(function () { + if (authEmulatorHost) { + return this.skip(); // Not implemented. + } return removeTempConfigs().then(() => getAuth().createProviderConfig(authProviderConfig1)); }); @@ -1488,7 +1756,6 @@ describe('admin.auth', () => { it('deleteUser() deletes the user with the given UID', () => { return Promise.all([ getAuth().deleteUser(newUserUid), - getAuth().deleteUser(newMultiFactorUserUid), getAuth().deleteUser(uidFromCreateUserWithoutUid), ]).should.eventually.be.fulfilled; }); @@ -1614,8 +1881,14 @@ describe('admin.auth', () => { ), 1000)); }) .then(() => { - return getAuth().verifySessionCookie(currentSessionCookie) - .should.eventually.be.fulfilled; + const verifyingSessionCookie = getAuth().verifySessionCookie(currentSessionCookie); + if (authEmulatorHost) { + // Check revocation is forced in emulator-mode and this should throw. + return verifyingSessionCookie.should.eventually.be.rejected; + } else { + // verifyIdToken without checking revocation should still succeed. + return verifyingSessionCookie.should.eventually.be.fulfilled; + } }) .then(() => { return getAuth().verifySessionCookie(currentSessionCookie, true) @@ -1828,7 +2101,10 @@ describe('admin.auth', () => { ]; fixtures.forEach((fixture) => { - it(`successfully imports users with ${fixture.name} to Firebase Auth.`, () => { + it(`successfully imports users with ${fixture.name} to Firebase Auth.`, function () { + if (authEmulatorHost) { + return this.skip(); // Auth Emulator does not support real hashes. + } importUserRecord = { uid: randomUid, email: randomUid + '@example.com', @@ -1897,10 +2173,13 @@ describe('admin.auth', () => { expect(JSON.stringify(actualUserRecord[key])) .to.be.equal(JSON.stringify((importUserRecord as any)[key])); } - }).should.eventually.be.fulfilled; + }); }); - it('successfully imports users with enrolled second factors', () => { + it('successfully imports users with enrolled second factors', function () { + if (authEmulatorHost) { + return this.skip(); // Not yet implemented. + } const uid = generateRandomString(20).toLowerCase(); const email = uid + '@example.com'; const now = new Date(1476235905000).toUTCString(); @@ -1962,25 +2241,41 @@ describe('admin.auth', () => { it('fails when invalid users are provided', () => { const users = [ - { uid: generateRandomString(20).toLowerCase(), phoneNumber: '+1error' }, { uid: generateRandomString(20).toLowerCase(), email: 'invalid' }, - { uid: generateRandomString(20).toLowerCase(), phoneNumber: '+1invalid' }, { uid: generateRandomString(20).toLowerCase(), emailVerified: 'invalid' } as any, ]; return getAuth().importUsers(users) .then((result) => { expect(result.successCount).to.equal(0); - expect(result.failureCount).to.equal(4); - expect(result.errors.length).to.equal(4); + expect(result.failureCount).to.equal(2); + expect(result.errors.length).to.equal(2); + expect(result.errors[0].index).to.equal(0); + expect(result.errors[0].error.code).to.equals('auth/invalid-email'); + expect(result.errors[1].index).to.equal(1); + expect(result.errors[1].error.code).to.equals('auth/invalid-email-verified'); + }); + }); + + it('fails when users with invalid phone numbers are provided', function () { + if (authEmulatorHost) { + // Auth Emulator's phoneNumber validation is also lax and won't throw. + return this.skip(); + } + const users = [ + // These phoneNumbers passes local (lax) validator but fails remotely. + { uid: generateRandomString(20).toLowerCase(), phoneNumber: '+1error' }, + { uid: generateRandomString(20).toLowerCase(), phoneNumber: '+1invalid' }, + ]; + return getAuth().importUsers(users) + .then((result) => { + expect(result.successCount).to.equal(0); + expect(result.failureCount).to.equal(2); + expect(result.errors.length).to.equal(2); expect(result.errors[0].index).to.equal(0); expect(result.errors[0].error.code).to.equals('auth/invalid-user-import'); expect(result.errors[1].index).to.equal(1); - expect(result.errors[1].error.code).to.equals('auth/invalid-email'); - expect(result.errors[2].index).to.equal(2); - expect(result.errors[2].error.code).to.equals('auth/invalid-user-import'); - expect(result.errors[3].index).to.equal(3); - expect(result.errors[3].error.code).to.equals('auth/invalid-email-verified'); - }).should.eventually.be.fulfilled; + expect(result.errors[1].error.code).to.equals('auth/invalid-user-import'); + }); }); }); }); @@ -2136,12 +2431,11 @@ function safeDelete(uid: string): Promise { * @param uids The list of user identifiers to delete. * @return A promise that resolves when delete operation resolves. */ -function deleteUsersWithDelay(uids: string[]): Promise { - return new Promise((resolve) => { - setTimeout(resolve, 1000); - }).then(() => { - return getAuth().deleteUsers(uids); - }); +async function deleteUsersWithDelay(uids: string[]): Promise { + if (!authEmulatorHost) { + await new Promise((resolve) => { setTimeout(resolve, 1000); }); + } + return getAuth().deleteUsers(uids); } /** diff --git a/test/integration/database.spec.ts b/test/integration/database.spec.ts index 0e522103ad..40b35484eb 100644 --- a/test/integration/database.spec.ts +++ b/test/integration/database.spec.ts @@ -16,11 +16,11 @@ import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { defaultApp, nullApp, nonNullApp, cmdArgs, databaseUrl } from './setup'; import * as admin from '../../lib/index'; import { Database, DataSnapshot, EventType, Reference, ServerValue, getDatabase, getDatabaseWithUrl, } from '../../lib/database/index'; +import { defaultApp, nullApp, nonNullApp, cmdArgs, databaseUrl, isEmulator } from './setup'; // eslint-disable-next-line @typescript-eslint/no-var-requires const chalk = require('chalk'); @@ -73,8 +73,14 @@ describe('admin.database', () => { .should.eventually.be.fulfilled; }); - it('App with null auth overrides is blocked by security rules', () => { - return getDatabase(nullApp).ref('blocked').set(ServerValue.TIMESTAMP) + it('App with null auth overrides is blocked by security rules', function () { + if (isEmulator) { + // RTDB emulator has open security rules by default and won't block this. + // TODO(https://github.com/firebase/firebase-admin-node/issues/1149): + // remove this once updating security rules through admin is in place. + return this.skip(); + } + return getDatabase(nullApp).ref('blocked').set(admin.database.ServerValue.TIMESTAMP) .should.eventually.be.rejectedWith('PERMISSION_DENIED: Permission denied'); }); @@ -170,13 +176,21 @@ describe('admin.database', () => { }); }); - it('getDatabase().getRules() returns currently defined rules as a string', () => { + it('admin.database().getRules() returns currently defined rules as a string', function () { + if (isEmulator) { + // https://github.com/firebase/firebase-admin-node/issues/1149 + return this.skip(); + } return getDatabase().getRules().then((result) => { return expect(result).to.be.not.empty; }); }); - it('getDatabase().getRulesJSON() returns currently defined rules as an object', () => { + it('admin.database().getRulesJSON() returns currently defined rules as an object', function () { + if (isEmulator) { + // https://github.com/firebase/firebase-admin-node/issues/1149 + return this.skip(); + } return getDatabase().getRulesJSON().then((result) => { return expect(result).to.be.not.undefined; }); diff --git a/test/integration/setup.ts b/test/integration/setup.ts index 85bfe5703b..fa217120f2 100644 --- a/test/integration/setup.ts +++ b/test/integration/setup.ts @@ -37,51 +37,72 @@ export let noServiceAccountApp: App; export let cmdArgs: any; +export const isEmulator = !!process.env.FIREBASE_EMULATOR_HUB; + before(() => { - /* tslint:disable:no-console */ - let serviceAccount: any; - try { - serviceAccount = require('../resources/key.json'); - } catch (error) { - console.log(chalk.red( - 'The integration test suite requires a service account JSON file for a ' + - 'Firebase project to be saved to `test/resources/key.json`.', - error, - )); - throw error; - } + let getCredential: () => {credential?: Credential}; + let serviceAccountId: string; - try { - apiKey = fs.readFileSync(path.join(__dirname, '../resources/apikey.txt')).toString().trim(); - } catch (error) { - console.log(chalk.red( - 'The integration test suite requires an API key for a ' + - 'Firebase project to be saved to `test/resources/apikey.txt`.', - error, + /* tslint:disable:no-console */ + if (isEmulator) { + console.log(chalk.yellow( + 'Running integration tests against Emulator Suite. ' + + 'Some tests may be skipped due to lack of emulator support.', )); - throw error; + getCredential = () => ({}); + projectId = process.env.GCLOUD_PROJECT!; + apiKey = 'fake-api-key'; + serviceAccountId = 'fake-client-email@example.com'; + } else { + let serviceAccount: any; + try { + serviceAccount = require('../resources/key.json'); + } catch (error) { + console.log(chalk.red( + 'The integration test suite requires a service account JSON file for a ' + + 'Firebase project to be saved to `test/resources/key.json`.', + error, + )); + throw error; + } + + try { + apiKey = fs.readFileSync(path.join(__dirname, '../resources/apikey.txt')).toString().trim(); + } catch (error) { + console.log(chalk.red( + 'The integration test suite requires an API key for a ' + + 'Firebase project to be saved to `test/resources/apikey.txt`.', + error, + )); + throw error; + } + getCredential = () => ({ credential: cert(serviceAccount) }); + projectId = serviceAccount.project_id; + serviceAccountId = serviceAccount.client_email; } /* tslint:enable:no-console */ - projectId = serviceAccount.project_id; databaseUrl = 'https://' + projectId + '.firebaseio.com'; storageBucket = projectId + '.appspot.com'; defaultApp = initializeApp({ - credential: cert(serviceAccount), + ...getCredential(), + projectId, databaseURL: databaseUrl, storageBucket, }); nullApp = initializeApp({ - credential: cert(serviceAccount), + ...getCredential(), + projectId, databaseURL: databaseUrl, databaseAuthVariableOverride: null, storageBucket, }, 'null'); nonNullApp = initializeApp({ - credential: cert(serviceAccount), + ...getCredential(), + projectId, databaseURL: databaseUrl, databaseAuthVariableOverride: { uid: generateRandomString(20), @@ -89,9 +110,14 @@ before(() => { storageBucket, }, 'nonNull'); + const noServiceAccountAppCreds = getCredential(); + if (noServiceAccountAppCreds.credential) { + noServiceAccountAppCreds.credential = new CertificatelessCredential( + noServiceAccountAppCreds.credential) + } noServiceAccountApp = initializeApp({ - credential: new CertificatelessCredential(cert(serviceAccount)), - serviceAccountId: serviceAccount.client_email, + ...noServiceAccountAppCreds, + serviceAccountId, projectId, }, 'noServiceAccount'); diff --git a/test/unit/app/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts index fe111993fd..5d34491ef4 100644 --- a/test/unit/app/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -85,9 +85,6 @@ describe('FirebaseApp', () => { }); clock = sinon.useFakeTimers(1000); - - mockApp = mocks.app(); - firebaseConfigVar = process.env[FIREBASE_CONFIG_VAR]; delete process.env[FIREBASE_CONFIG_VAR]; firebaseNamespace = new FirebaseNamespace(); @@ -762,204 +759,6 @@ describe('FirebaseApp', () => { }); }); - it('retries to proactively refresh the token if a proactive refresh attempt fails', () => { - // Force a token refresh. - return mockApp.INTERNAL.getToken(true).then((token1) => { - // Stub the getToken() method to return a rejected promise. - getTokenStub.restore(); - expect(mockApp.options.credential).to.exist; - getTokenStub = sinon.stub(mockApp.options.credential!, 'getAccessToken') - .rejects(new Error('Intentionally rejected')); - - // Forward the clock to exactly five minutes before expiry. - const expiryInMilliseconds = token1.expirationTime - Date.now(); - clock.tick(expiryInMilliseconds - (5 * ONE_MINUTE_IN_MILLISECONDS)); - - // Forward the clock to exactly four minutes before expiry. - clock.tick(60 * 1000); - - // Restore the stubbed getAccessToken() method. - getTokenStub.restore(); - getTokenStub = sinon.stub(ServiceAccountCredential.prototype, 'getAccessToken').resolves({ - access_token: 'mock-access-token', // eslint-disable-line @typescript-eslint/camelcase - expires_in: 3600, // eslint-disable-line @typescript-eslint/camelcase - }); - - return mockApp.INTERNAL.getToken().then((token2) => { - // Ensure the token has not been proactively refreshed. - expect(token1).to.deep.equal(token2); - expect(getTokenStub).to.have.not.been.called; - - // Forward the clock to exactly three minutes before expiry. - clock.tick(60 * 1000); - - return mockApp.INTERNAL.getToken().then((token3) => { - // Ensure the token was proactively refreshed. - expect(token1).to.not.deep.equal(token3); - expect(getTokenStub).to.have.been.calledOnce; - }); - }); - }); - }); - - it('stops retrying to proactively refresh the token after five attempts', () => { - // Force a token refresh. - let originalToken: FirebaseAccessToken; - return mockApp.INTERNAL.getToken(true).then((token) => { - originalToken = token; - - // Stub the credential's getAccessToken() method to always return a rejected promise. - getTokenStub.restore(); - expect(mockApp.options.credential).to.exist; - getTokenStub = sinon.stub(mockApp.options.credential!, 'getAccessToken') - .rejects(new Error('Intentionally rejected')); - - // Expect the call count to initially be zero. - expect(getTokenStub.callCount).to.equal(0); - - // Forward the clock to exactly five minutes before expiry. - const expiryInMilliseconds = token.expirationTime - Date.now(); - clock.tick(expiryInMilliseconds - (5 * ONE_MINUTE_IN_MILLISECONDS)); - - // Due to synchronous timing issues when the timer is mocked, make a call to getToken() - // without forcing a refresh to ensure there is enough time for the underlying token refresh - // timeout to fire and complete. - return mockApp.INTERNAL.getToken(); - }).then((token) => { - // Ensure the token was attempted to be proactively refreshed one time. - expect(getTokenStub.callCount).to.equal(1); - - // Ensure the proactive refresh failed. - expect(token).to.deep.equal(originalToken); - - // Forward the clock to four minutes before expiry. - clock.tick(ONE_MINUTE_IN_MILLISECONDS); - - // See note above about calling getToken(). - return mockApp.INTERNAL.getToken(); - }).then((token) => { - // Ensure the token was attempted to be proactively refreshed two times. - expect(getTokenStub.callCount).to.equal(2); - - // Ensure the proactive refresh failed. - expect(token).to.deep.equal(originalToken); - - // Forward the clock to three minutes before expiry. - clock.tick(ONE_MINUTE_IN_MILLISECONDS); - - // See note above about calling getToken(). - return mockApp.INTERNAL.getToken(); - }).then((token) => { - // Ensure the token was attempted to be proactively refreshed three times. - expect(getTokenStub.callCount).to.equal(3); - - // Ensure the proactive refresh failed. - expect(token).to.deep.equal(originalToken); - - // Forward the clock to two minutes before expiry. - clock.tick(ONE_MINUTE_IN_MILLISECONDS); - - // See note above about calling getToken(). - return mockApp.INTERNAL.getToken(); - }).then((token) => { - // Ensure the token was attempted to be proactively refreshed four times. - expect(getTokenStub.callCount).to.equal(4); - - // Ensure the proactive refresh failed. - expect(token).to.deep.equal(originalToken); - - // Forward the clock to one minute before expiry. - clock.tick(ONE_MINUTE_IN_MILLISECONDS); - - // See note above about calling getToken(). - return mockApp.INTERNAL.getToken(); - }).then((token) => { - // Ensure the token was attempted to be proactively refreshed five times. - expect(getTokenStub.callCount).to.equal(5); - - // Ensure the proactive refresh failed. - expect(token).to.deep.equal(originalToken); - - // Forward the clock to expiry. - clock.tick(ONE_MINUTE_IN_MILLISECONDS); - - // See note above about calling getToken(). - return mockApp.INTERNAL.getToken(); - }).then((token) => { - // Ensure the token was not attempted to be proactively refreshed a sixth time. - expect(getTokenStub.callCount).to.equal(5); - - // Ensure the token has never been refresh. - expect(token).to.deep.equal(originalToken); - }); - }); - - it('resets the proactive refresh timeout upon a force refresh', () => { - // Force a token refresh. - return mockApp.INTERNAL.getToken(true).then((token1) => { - // Forward the clock to five minutes and one second before expiry. - let expiryInMilliseconds = token1.expirationTime - Date.now(); - clock.tick(expiryInMilliseconds - (5 * ONE_MINUTE_IN_MILLISECONDS) - 1000); - - // Force a token refresh. - return mockApp.INTERNAL.getToken(true).then((token2) => { - // Ensure the token was force refreshed. - expect(token1).to.not.deep.equal(token2); - expect(getTokenStub).to.have.been.calledTwice; - - // Forward the clock to exactly five minutes before the original token's expiry. - clock.tick(1000); - - return mockApp.INTERNAL.getToken().then((token3) => { - // Ensure the token hasn't changed, meaning the proactive refresh was canceled. - expect(token2).to.deep.equal(token3); - expect(getTokenStub).to.have.been.calledTwice; - - // Forward the clock to exactly five minutes before the refreshed token's expiry. - expiryInMilliseconds = token3.expirationTime - Date.now(); - clock.tick(expiryInMilliseconds - (5 * ONE_MINUTE_IN_MILLISECONDS)); - - return mockApp.INTERNAL.getToken().then((token4) => { - // Ensure the token was proactively refreshed. - expect(token3).to.not.deep.equal(token4); - expect(getTokenStub).to.have.been.calledThrice; - }); - }); - }); - }); - }); - - it('proactively refreshes the token at the next full minute if it expires in five minutes or less', () => { - // Turn off default mocking of one hour access tokens and replace it with a short-lived token. - getTokenStub.restore(); - expect(mockApp.options.credential).to.exist; - getTokenStub = sinon.stub(mockApp.options.credential!, 'getAccessToken').resolves({ - access_token: utils.generateRandomAccessToken(), // eslint-disable-line @typescript-eslint/camelcase - expires_in: 3 * 60 + 10, // eslint-disable-line @typescript-eslint/camelcase - }); - // Expect the call count to initially be zero. - expect(getTokenStub.callCount).to.equal(0); - - // Force a token refresh. - return mockApp.INTERNAL.getToken(true).then((token1) => { - - // Move the clock forward to three minutes and one second before expiry. - clock.tick(9 * 1000); - expect(getTokenStub.callCount).to.equal(1); - - // Move the clock forward to exactly three minutes before expiry. - clock.tick(1000); - - // Expect the underlying getAccessToken() method to have been called once. - expect(getTokenStub.callCount).to.equal(2); - - return mockApp.INTERNAL.getToken().then((token2) => { - // Ensure the token was proactively refreshed. - expect(token1).to.not.deep.equal(token2); - }); - }); - }); - it('Includes the original error in exception', () => { getTokenStub.restore(); const mockError = new FirebaseAppError( diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index 2eb4f75fe5..ffe921371d 100644 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -353,6 +353,12 @@ describe('FIREBASE_AUTH_GET_ACCOUNT_INFO', () => { return requestValidator(validRequest); }).not.to.throw(); }); + it('should succeed with federatedUserId passed', () => { + const validRequest = { federatedUserId: { providerId: 'google.com', rawId: 'google_uid_1234' } }; + expect(() => { + return requestValidator(validRequest); + }).not.to.throw(); + }); it('should fail when neither localId, email or phoneNumber are passed', () => { const invalidRequest = { bla: ['1234'] }; expect(() => { diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index d896e75f20..fb55544924 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -237,6 +237,8 @@ function getSAMLConfigServerResponse(providerId: string): SAMLConfigServerRespon } +const INVALID_PROVIDER_IDS = [ + undefined, null, NaN, 0, 1, true, false, '', [], [1, 'a'], {}, { a: 1 }, _.noop]; const TENANT_ID = 'tenantId'; const AUTH_CONFIGS: AuthTest[] = [ { @@ -1147,6 +1149,120 @@ AUTH_CONFIGS.forEach((testConfig) => { }); }); + describe('getUserByProviderUid()', () => { + const providerId = 'google.com'; + const providerUid = 'google_uid'; + const tenantId = testConfig.supportsTenantManagement ? undefined : TENANT_ID; + const expectedGetAccountInfoResult = getValidGetAccountInfoResponse(tenantId); + const expectedUserRecord = getValidUserRecord(expectedGetAccountInfoResult); + const expectedError = new FirebaseAuthError(AuthClientErrorCode.USER_NOT_FOUND); + + // Stubs used to simulate underlying api calls. + let stubs: sinon.SinonStub[] = []; + beforeEach(() => sinon.spy(validator, 'isEmail')); + afterEach(() => { + (validator.isEmail as any).restore(); + _.forEach(stubs, (stub) => stub.restore()); + stubs = []; + }); + + it('should be rejected given no provider id', () => { + expect(() => (auth as any).getUserByProviderUid()) + .to.throw(FirebaseAuthError) + .with.property('code', 'auth/invalid-provider-id'); + }); + + it('should be rejected given an invalid provider id', () => { + expect(() => auth.getUserByProviderUid('', 'uid')) + .to.throw(FirebaseAuthError) + .with.property('code', 'auth/invalid-provider-id'); + }); + + it('should be rejected given an invalid provider uid', () => { + expect(() => auth.getUserByProviderUid('id', '')) + .to.throw(FirebaseAuthError) + .with.property('code', 'auth/invalid-provider-id'); + }); + + it('should be rejected given an app which returns null access tokens', () => { + return nullAccessTokenAuth.getUserByProviderUid(providerId, providerUid) + .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); + }); + + it('should be rejected given an app which returns invalid access tokens', () => { + return malformedAccessTokenAuth.getUserByProviderUid(providerId, providerUid) + .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); + }); + + it('should be rejected given an app which fails to generate access tokens', () => { + return rejectedPromiseAccessTokenAuth.getUserByProviderUid(providerId, providerUid) + .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); + }); + + it('should resolve with a UserRecord on success', () => { + // Stub getAccountInfoByEmail to return expected result. + const stub = sinon.stub(testConfig.RequestHandler.prototype, 'getAccountInfoByFederatedUid') + .resolves(expectedGetAccountInfoResult); + stubs.push(stub); + return auth.getUserByProviderUid(providerId, providerUid) + .then((userRecord) => { + // Confirm underlying API called with expected parameters. + expect(stub).to.have.been.calledOnce.and.calledWith(providerId, providerUid); + // Confirm expected user record response returned. + expect(userRecord).to.deep.equal(expectedUserRecord); + }); + }); + + describe('non-federated providers', () => { + let invokeRequestHandlerStub: sinon.SinonStub; + beforeEach(() => { + invokeRequestHandlerStub = sinon.stub(testConfig.RequestHandler.prototype, 'invokeRequestHandler') + .resolves({ + // nothing here is checked; we just need enough to not crash. + users: [{ + localId: 1, + }], + }); + + }); + afterEach(() => { + invokeRequestHandlerStub.restore(); + }); + + it('phone lookups should use phoneNumber field', async () => { + await auth.getUserByProviderUid('phone', '+15555550001'); + expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( + sinon.match.any, sinon.match.any, { + phoneNumber: ['+15555550001'], + }); + }); + + it('email lookups should use email field', async () => { + await auth.getUserByProviderUid('email', 'user@example.com'); + expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( + sinon.match.any, sinon.match.any, { + email: ['user@example.com'], + }); + }); + }); + + it('should throw an error when the backend returns an error', () => { + // Stub getAccountInfoByFederatedUid to throw a backend error. + const stub = sinon.stub(testConfig.RequestHandler.prototype, 'getAccountInfoByFederatedUid') + .rejects(expectedError); + stubs.push(stub); + return auth.getUserByProviderUid(providerId, providerUid) + .then(() => { + throw new Error('Unexpected success'); + }, (error) => { + // Confirm underlying API called with expected parameters. + expect(stub).to.have.been.calledOnce.and.calledWith(providerId, providerUid); + // Confirm expected error returned. + expect(error).to.equal(expectedError); + }); + }); + }); + describe('getUsers()', () => { let stubs: sinon.SinonStub[] = []; @@ -1510,6 +1626,10 @@ AUTH_CONFIGS.forEach((testConfig) => { emailVerified: expectedUserRecord.emailVerified, password: 'password', phoneNumber: expectedUserRecord.phoneNumber, + providerToLink: { + providerId: 'google.com', + uid: 'google_uid', + }, }; // Stubs used to simulate underlying api calls. let stubs: sinon.SinonStub[] = []; @@ -1553,10 +1673,195 @@ AUTH_CONFIGS.forEach((testConfig) => { }) .catch((error) => { expect(error).to.have.property('code', 'auth/argument-error'); - expect(validator.isNonNullObject).to.have.been.calledOnce.and.calledWith(null); + expect(validator.isNonNullObject).to.have.been.calledWith(null); + }); + }); + + const invalidUpdateRequests: UpdateRequest[] = [ + { providerToLink: { uid: 'google_uid' } }, + { providerToLink: { providerId: 'google.com' } }, + { providerToLink: { providerId: 'google.com', uid: '' } }, + { providerToLink: { providerId: '', uid: 'google_uid' } }, + ]; + invalidUpdateRequests.forEach((invalidUpdateRequest) => { + it('should be rejected given an UpdateRequest with an invalid providerToLink parameter', () => { + expect(() => { + auth.updateUser(uid, invalidUpdateRequest); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + }); + + it('should rename providerToLink property to linkProviderUserInfo', async () => { + const invokeRequestHandlerStub = sinon.stub(testConfig.RequestHandler.prototype, 'invokeRequestHandler') + .resolves({ + localId: uid, + }); + + // Stub getAccountInfoByUid to return a valid result (unchecked; we + // just need it to be valid so as to not crash.) + const getUserStub = sinon.stub(testConfig.RequestHandler.prototype, 'getAccountInfoByUid') + .resolves(expectedGetAccountInfoResult); + + stubs.push(invokeRequestHandlerStub); + stubs.push(getUserStub); + + await auth.updateUser(uid, { + providerToLink: { + providerId: 'google.com', + uid: 'google_uid', + }, + }); + + expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( + sinon.match.any, sinon.match.any, { + localId: uid, + linkProviderUserInfo: { + providerId: 'google.com', + rawId: 'google_uid', + }, + }); + }); + + INVALID_PROVIDER_IDS.forEach((invalidProviderId) => { + it('should be rejected given a deleteProvider list with an invalid provider ID ' + + JSON.stringify(invalidProviderId), () => { + expect(() => { + auth.updateUser(uid, { + providersToUnlink: [ invalidProviderId as any ], + }); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + }); + + it('should merge deletion of phone provider with the providersToUnlink list', async () => { + const invokeRequestHandlerStub = sinon.stub(testConfig.RequestHandler.prototype, 'invokeRequestHandler') + .resolves({ + localId: uid, + }); + + // Stub getAccountInfoByUid to return a valid result (unchecked; we + // just need it to be valid so as to not crash.) + const getUserStub = sinon.stub(testConfig.RequestHandler.prototype, 'getAccountInfoByUid') + .resolves(expectedGetAccountInfoResult); + + stubs.push(invokeRequestHandlerStub); + stubs.push(getUserStub); + + await auth.updateUser(uid, { + phoneNumber: null, + providersToUnlink: [ 'google.com' ], + }); + + expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( + sinon.match.any, sinon.match.any, { + localId: uid, + deleteProvider: [ 'phone', 'google.com' ], }); }); + describe('non-federated providers', () => { + let invokeRequestHandlerStub: sinon.SinonStub; + let getAccountInfoByUidStub: sinon.SinonStub; + beforeEach(() => { + invokeRequestHandlerStub = sinon.stub(testConfig.RequestHandler.prototype, 'invokeRequestHandler') + .resolves({ + // nothing here is checked; we just need enough to not crash. + users: [{ + localId: 1, + }], + }); + + getAccountInfoByUidStub = sinon.stub(testConfig.RequestHandler.prototype, 'getAccountInfoByUid') + .resolves({ + // nothing here is checked; we just need enough to not crash. + users: [{ + localId: 1, + }], + }); + }); + afterEach(() => { + invokeRequestHandlerStub.restore(); + getAccountInfoByUidStub.restore(); + }); + + it('specifying both email and providerId=email should be rejected', () => { + expect(() => { + auth.updateUser(uid, { + email: 'user@example.com', + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + + it('specifying both phoneNumber and providerId=phone should be rejected', () => { + expect(() => { + auth.updateUser(uid, { + phoneNumber: '+15555550001', + providerToLink: { + providerId: 'phone', + uid: '+15555550001', + }, + }); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + + it('email linking should use email field', async () => { + await auth.updateUser(uid, { + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }); + expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( + sinon.match.any, sinon.match.any, { + localId: uid, + email: 'user@example.com', + }); + }); + + it('phone linking should use phoneNumber field', async () => { + await auth.updateUser(uid, { + providerToLink: { + providerId: 'phone', + uid: '+15555550001', + }, + }); + expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( + sinon.match.any, sinon.match.any, { + localId: uid, + phoneNumber: '+15555550001', + }); + }); + + it('specifying both phoneNumber=null and providersToUnlink=phone should be rejected', () => { + expect(() => { + auth.updateUser(uid, { + phoneNumber: null, + providersToUnlink: ['phone'], + }); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + + it('doesnt mutate the properties parameter', async () => { + const properties: UpdateRequest = { + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }; + await auth.updateUser(uid, properties); + expect(properties).to.deep.equal({ + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }); + }); + }); + it('should be rejected given an app which returns null access tokens', () => { return nullAccessTokenAuth.updateUser(uid, propertiesToEdit) .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); @@ -2369,9 +2674,7 @@ AUTH_CONFIGS.forEach((testConfig) => { .should.eventually.be.rejected.and.have.property('code', 'auth/invalid-provider-id'); }); - const invalidProviderIds = [ - undefined, null, NaN, 0, 1, true, false, '', [], [1, 'a'], {}, { a: 1 }, _.noop]; - invalidProviderIds.forEach((invalidProviderId) => { + INVALID_PROVIDER_IDS.forEach((invalidProviderId) => { it(`should be rejected given an invalid provider ID "${JSON.stringify(invalidProviderId)}"`, () => { return (auth as Auth).getProviderConfig(invalidProviderId as any) .then(() => { @@ -2742,15 +3045,16 @@ AUTH_CONFIGS.forEach((testConfig) => { .should.eventually.be.rejected.and.have.property('code', 'auth/invalid-provider-id'); }); - it('should be rejected given an invalid provider ID', () => { - const invalidProviderId = ''; - return (auth as Auth).deleteProviderConfig(invalidProviderId) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/invalid-provider-id'); - }); + INVALID_PROVIDER_IDS.forEach((invalidProviderId) => { + it(`should be rejected given an invalid provider ID "${JSON.stringify(invalidProviderId)}"`, () => { + return (auth as Auth).deleteProviderConfig(invalidProviderId as any) + .then(() => { + throw new Error('Unexpected success'); + }) + .catch((error) => { + expect(error).to.have.property('code', 'auth/invalid-provider-id'); + }); + }); }); it('should be rejected given an app which returns null access tokens', () => { @@ -2861,15 +3165,16 @@ AUTH_CONFIGS.forEach((testConfig) => { .should.eventually.be.rejected.and.have.property('code', 'auth/invalid-provider-id'); }); - it('should be rejected given an invalid provider ID', () => { - const invalidProviderId = ''; - return (auth as Auth).updateProviderConfig(invalidProviderId, oidcConfigOptions) - .then(() => { - throw new Error('Unexpected success'); - }) - .catch((error) => { - expect(error).to.have.property('code', 'auth/invalid-provider-id'); - }); + INVALID_PROVIDER_IDS.forEach((invalidProviderId) => { + it(`should be rejected given an invalid provider ID "${JSON.stringify(invalidProviderId)}"`, () => { + return (auth as Auth).updateProviderConfig(invalidProviderId as any, oidcConfigOptions) + .then(() => { + throw new Error('Unexpected success'); + }) + .catch((error) => { + expect(error).to.have.property('code', 'auth/invalid-provider-id'); + }); + }); }); it('should be rejected given no options', () => { @@ -3189,14 +3494,22 @@ AUTH_CONFIGS.forEach((testConfig) => { describe('auth emulator support', () => { let mockAuth = testConfig.init(mocks.app()); + const userRecord = getValidUserRecord(getValidGetAccountInfoResponse()); + const validSince = new Date(userRecord.tokensValidAfterTime!); + + const stubs: sinon.SinonStub[] = []; + let clock: sinon.SinonFakeTimers; beforeEach(() => { - process.env.FIREBASE_AUTH_EMULATOR_HOST = 'localhost:9099'; + process.env.FIREBASE_AUTH_EMULATOR_HOST = '127.0.0.1:9099'; mockAuth = testConfig.init(mocks.app()); + clock = sinon.useFakeTimers(validSince.getTime()); }); afterEach(() => { + _.forEach(stubs, (s) => s.restore()); delete process.env.FIREBASE_AUTH_EMULATOR_HOST; + clock.restore(); }); it('createCustomToken() generates an unsigned token', async () => { @@ -3211,39 +3524,78 @@ AUTH_CONFIGS.forEach((testConfig) => { jwt.verify(token, '', { algorithms: ['none'] }); }); - it('verifyIdToken() rejects an unsigned token when only the env var is set', async () => { + it('verifyIdToken() should reject revoked ID tokens', () => { + const uid = userRecord.uid; + // One second before validSince. + const oneSecBeforeValidSince = Math.floor(validSince.getTime() / 1000 - 1); + const getUserStub = sinon.stub(testConfig.Auth.prototype, 'getUser') + .resolves(userRecord); + stubs.push(getUserStub); + const unsignedToken = mocks.generateIdToken({ - algorithm: 'none' + algorithm: 'none', + subject: uid, + }, { + iat: oneSecBeforeValidSince, + auth_time: oneSecBeforeValidSince, // eslint-disable-line @typescript-eslint/camelcase }); - await expect(mockAuth.verifyIdToken(unsignedToken)) - .to.be.rejectedWith('Firebase ID token has incorrect algorithm. Expected "RS256"'); + // verifyIdToken should force checking revocation in emulator mode, + // even if checkRevoked=false. + return mockAuth.verifyIdToken(unsignedToken, false) + .then(() => { + throw new Error('Unexpected success'); + }, (error) => { + // Confirm expected error returned. + expect(error).to.have.property('code', 'auth/id-token-revoked'); + // Confirm underlying API called with expected parameters. + expect(getUserStub).to.have.been.calledOnce.and.calledWith(uid); + }); }); - it('verifyIdToken() accepts an unsigned token when private method is called and env var is set', async () => { - (mockAuth as any).setJwtVerificationEnabled(false); + it('verifySessionCookie() should reject revoked session cookies', () => { + const uid = userRecord.uid; + // One second before validSince. + const oneSecBeforeValidSince = Math.floor(validSince.getTime() / 1000 - 1); + const getUserStub = sinon.stub(testConfig.Auth.prototype, 'getUser') + .resolves(userRecord); + stubs.push(getUserStub); - let claims = {}; - if (testConfig.Auth === TenantAwareAuth) { - claims = { - firebase: { - tenant: TENANT_ID - } - } - } + const unsignedToken = mocks.generateIdToken({ + algorithm: 'none', + subject: uid, + issuer: 'https://session.firebase.google.com/' + mocks.projectId, + }, { + iat: oneSecBeforeValidSince, + auth_time: oneSecBeforeValidSince, // eslint-disable-line @typescript-eslint/camelcase + }); + + // verifySessionCookie should force checking revocation in emulator + // mode, even if checkRevoked=false. + return mockAuth.verifySessionCookie(unsignedToken, false) + .then(() => { + throw new Error('Unexpected success'); + }, (error) => { + // Confirm expected error returned. + expect(error).to.have.property('code', 'auth/session-cookie-revoked'); + // Confirm underlying API called with expected parameters. + expect(getUserStub).to.have.been.calledOnce.and.calledWith(uid); + }); + }); + it('verifyIdToken() rejects an unsigned token if auth emulator is unreachable', async () => { const unsignedToken = mocks.generateIdToken({ algorithm: 'none' - }, claims); + }); - const decoded = await mockAuth.verifyIdToken(unsignedToken); - expect(decoded).to.be.ok; - }); + const errorMessage = 'Error while making request: connect ECONNREFUSED 127.0.0.1. Error code: ECONNREFUSED'; + const getUserStub = sinon.stub(testConfig.Auth.prototype, 'getUser').rejects(new Error(errorMessage)); + stubs.push(getUserStub); - it('private method throws when env var is unset', async () => { - delete process.env.FIREBASE_AUTH_EMULATOR_HOST; - await expect(() => (mockAuth as any).setJwtVerificationEnabled(false)) - .to.throw('This method is only available when connected to the Authentication emulator.') + // Since revocation check is forced on in emulator mode, this will call + // the getUser method and get rejected (instead of succeed locally). + await expect(mockAuth.verifyIdToken(unsignedToken)) + .to.be.rejectedWith(errorMessage); }); }); }); diff --git a/test/unit/auth/tenant-manager.spec.ts b/test/unit/auth/tenant-manager.spec.ts index 720ba32b01..8a8f0617f2 100644 --- a/test/unit/auth/tenant-manager.spec.ts +++ b/test/unit/auth/tenant-manager.spec.ts @@ -49,6 +49,7 @@ describe('TenantManager', () => { displayName: 'TENANT-DISPLAY-NAME', allowPasswordSignup: true, enableEmailLinkSignin: false, + enableAnonymousUser: true, }; before(() => { @@ -385,6 +386,7 @@ describe('TenantManager', () => { enabled: true, passwordRequired: true, }, + anonymousSignInEnabled: true, }; const expectedTenant = new Tenant(GET_TENANT_RESPONSE); const expectedError = new FirebaseAuthError( @@ -477,6 +479,7 @@ describe('TenantManager', () => { enabled: true, passwordRequired: true, }, + anonymousSignInEnabled: true, }; const expectedTenant = new Tenant(GET_TENANT_RESPONSE); const expectedError = new FirebaseAuthError( diff --git a/test/unit/auth/tenant.spec.ts b/test/unit/auth/tenant.spec.ts index fbb94d3b23..0f14856faa 100644 --- a/test/unit/auth/tenant.spec.ts +++ b/test/unit/auth/tenant.spec.ts @@ -349,6 +349,7 @@ describe('Tenant', () => { enabled: true, passwordRequired: false, }, + anonymousSignInEnabled: false, multiFactorConfig: deepCopy(clientRequest.multiFactorConfig), testPhoneNumbers: deepCopy(clientRequest.testPhoneNumbers), }); @@ -366,6 +367,7 @@ describe('Tenant', () => { enabled: true, passwordRequired: false, }, + anonymousSignInEnabled: false, }); }); }); diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 9553d4375c..52cd6d3c25 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -16,25 +16,23 @@ 'use strict'; -// Use untyped import syntax for Node built-ins -import https = require('https'); - import * as _ from 'lodash'; import * as chai from 'chai'; import * as nock from 'nock'; import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; +import { Agent } from 'http'; + import LegacyFirebaseTokenGenerator = require('firebase-token-generator'); import * as mocks from '../../resources/mocks'; import { FirebaseTokenGenerator, ServiceAccountSigner } from '../../../src/auth/token-generator'; import * as verifier from '../../../src/auth/token-verifier'; - import { ServiceAccountCredential } from '../../../src/app/credential-internal'; -import { AuthClientErrorCode } from '../../../src/utils/error'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { Algorithm } from 'jsonwebtoken'; +import { AuthClientErrorCode } from '../../../src/utils/error'; +import { JwtError, JwtErrorCode, PublicKeySignatureVerifier } from '../../../src/utils/jwt'; chai.should(); chai.use(sinonChai); @@ -43,76 +41,12 @@ chai.use(chaiAsPromised); const expect = chai.expect; const ONE_HOUR_IN_SECONDS = 60 * 60; -const idTokenPublicCertPath = '/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'; - -/** - * Returns a mocked out success response from the URL containing the public keys for the Google certs. - * - * @param {string=} path URL path to which the mock request should be made. If not specified, defaults - * to the URL path of ID token public key certificates. - * @return {Object} A nock response object. - */ -function mockFetchPublicKeys(path: string = idTokenPublicCertPath): nock.Scope { - const mockedResponse: {[key: string]: string} = {}; - mockedResponse[mocks.certificateObject.private_key_id] = mocks.keyPairs[0].public; - return nock('https://www.googleapis.com') - .get(path) - .reply(200, mockedResponse, { - 'cache-control': 'public, max-age=1, must-revalidate, no-transform', - }); -} - -/** - * Returns a mocked out success response from the URL containing the public keys for the Google certs - * which contains a public key which won't match the mocked token. - * - * @return {Object} A nock response object. - */ -function mockFetchWrongPublicKeys(): nock.Scope { - const mockedResponse: {[key: string]: string} = {}; - mockedResponse[mocks.certificateObject.private_key_id] = mocks.keyPairs[1].public; - return nock('https://www.googleapis.com') - .get('/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com') - .reply(200, mockedResponse, { - 'cache-control': 'public, max-age=1, must-revalidate, no-transform', - }); -} - -/** - * Returns a mocked out error response from the URL containing the public keys for the Google certs. - * The status code is 200 but the response itself will contain an 'error' key. - * - * @return {Object} A nock response object. - */ -function mockFetchPublicKeysWithErrorResponse(): nock.Scope { - return nock('https://www.googleapis.com') - .get('/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com') - .reply(200, { - error: 'message', - error_description: 'description', // eslint-disable-line @typescript-eslint/camelcase - }); -} - -/** - * Returns a mocked out failed response from the URL containing the public keys for the Google certs. - * The status code is non-200 and the response itself will fail. - * - * @return {Object} A nock response object. - */ -function mockFailedFetchPublicKeys(): nock.Scope { - return nock('https://www.googleapis.com') - .get('/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com') - .replyWithError('message'); -} function createTokenVerifier( app: FirebaseApp, - options: { algorithm?: Algorithm } = {} ): verifier.FirebaseTokenVerifier { - const algorithm = options.algorithm || 'RS256'; return new verifier.FirebaseTokenVerifier( 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', - algorithm, 'https://securetoken.google.com/', verifier.ID_TOKEN_INFO, app @@ -125,14 +59,12 @@ describe('FirebaseTokenVerifier', () => { let tokenVerifier: verifier.FirebaseTokenVerifier; let tokenGenerator: FirebaseTokenGenerator; let clock: sinon.SinonFakeTimers | undefined; - let httpsSpy: sinon.SinonSpy; beforeEach(() => { // Needed to generate custom token for testing. app = mocks.app(); const cert = new ServiceAccountCredential(mocks.certificateObject); tokenGenerator = new FirebaseTokenGenerator(new ServiceAccountSigner(cert)); tokenVerifier = createTokenVerifier(app); - httpsSpy = sinon.spy(https, 'request'); }); afterEach(() => { @@ -140,7 +72,6 @@ describe('FirebaseTokenVerifier', () => { clock.restore(); clock = undefined; } - httpsSpy.restore(); }); after(() => { @@ -152,7 +83,6 @@ describe('FirebaseTokenVerifier', () => { expect(() => { tokenVerifier = new verifier.FirebaseTokenVerifier( 'https://www.example.com/publicKeys', - 'RS256', 'https://www.example.com/issuer/', { url: 'https://docs.example.com/verify-tokens', @@ -172,7 +102,6 @@ describe('FirebaseTokenVerifier', () => { expect(() => { new verifier.FirebaseTokenVerifier( invalidCertUrl as any, - 'RS256', 'https://www.example.com/issuer/', verifier.ID_TOKEN_INFO, app, @@ -181,27 +110,12 @@ describe('FirebaseTokenVerifier', () => { }); }); - const invalidAlgorithms = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop, '']; - invalidAlgorithms.forEach((invalidAlgorithm) => { - it('should throw given an invalid algorithm: ' + JSON.stringify(invalidAlgorithm), () => { - expect(() => { - new verifier.FirebaseTokenVerifier( - 'https://www.example.com/publicKeys', - invalidAlgorithm as any, - 'https://www.example.com/issuer/', - verifier.ID_TOKEN_INFO, - app); - }).to.throw('The provided JWT algorithm is an empty string.'); - }); - }); - const invalidIssuers = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop, 'file://invalid']; invalidIssuers.forEach((invalidIssuer) => { it('should throw given a non-URL issuer: ' + JSON.stringify(invalidIssuer), () => { expect(() => { new verifier.FirebaseTokenVerifier( 'https://www.example.com/publicKeys', - 'RS256', invalidIssuer as any, verifier.ID_TOKEN_INFO, app, @@ -216,7 +130,6 @@ describe('FirebaseTokenVerifier', () => { expect(() => { new verifier.FirebaseTokenVerifier( 'https://www.example.com/publicKeys', - 'RS256', 'https://www.example.com/issuer/', { url: 'https://docs.example.com/verify-tokens', @@ -237,7 +150,6 @@ describe('FirebaseTokenVerifier', () => { expect(() => { new verifier.FirebaseTokenVerifier( 'https://www.example.com/publicKeys', - 'RS256', 'https://www.example.com/issuer/', { url: 'https://docs.example.com/verify-tokens', @@ -258,7 +170,6 @@ describe('FirebaseTokenVerifier', () => { expect(() => { new verifier.FirebaseTokenVerifier( 'https://www.example.com/publicKeys', - 'RS256', 'https://www.example.com/issuer/', { url: 'https://docs.example.com/verify-tokens', @@ -279,7 +190,6 @@ describe('FirebaseTokenVerifier', () => { expect(() => { new verifier.FirebaseTokenVerifier( 'https://www.example.com/publicKeys', - 'RS256', 'https://www.example.com/issuer/', { url: 'https://docs.example.com/verify-tokens', @@ -297,10 +207,14 @@ describe('FirebaseTokenVerifier', () => { describe('verifyJWT()', () => { let mockedRequests: nock.Scope[] = []; + let stubs: sinon.SinonStub[] = []; afterEach(() => { _.forEach(mockedRequests, (mockedRequest) => mockedRequest.done()); mockedRequests = []; + + _.forEach(stubs, (stub) => stub.restore()); + stubs = []; }); it('should throw given no Firebase JWT token', () => { @@ -331,7 +245,6 @@ describe('FirebaseTokenVerifier', () => { it('should throw if the token verifier was initialized with no "project_id"', () => { const tokenVerifierWithNoProjectId = new verifier.FirebaseTokenVerifier( 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', - 'RS256', 'https://securetoken.google.com/', verifier.ID_TOKEN_INFO, mocks.mockCredentialApp(), @@ -351,21 +264,6 @@ describe('FirebaseTokenVerifier', () => { .should.eventually.be.rejectedWith('Firebase ID token has no "kid" claim'); }); - it('should be rejected given a Firebase JWT token with a kid which does not match any of the ' + - 'actual public keys', () => { - mockedRequests.push(mockFetchPublicKeys()); - - const mockIdToken = mocks.generateIdToken({ - header: { - kid: 'wrongkid', - }, - }); - - return tokenVerifier.verifyJWT(mockIdToken) - .should.eventually.be.rejectedWith('Firebase ID token has "kid" claim which does not ' + - 'correspond to a known public key'); - }); - it('should be rejected given a Firebase JWT token with an incorrect algorithm', () => { const mockIdToken = mocks.generateIdToken({ algorithm: 'HS256', @@ -392,8 +290,26 @@ describe('FirebaseTokenVerifier', () => { .should.eventually.be.rejectedWith('Firebase ID token has incorrect "iss" (issuer) claim'); }); + it('should be rejected when the verifier throws no maching kid error', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .rejects(new JwtError(JwtErrorCode.NO_MATCHING_KID, 'No matching key ID.')); + stubs.push(verifierStub); + + const mockIdToken = mocks.generateIdToken({ + header: { + kid: 'wrongkid', + }, + }); + + return tokenVerifier.verifyJWT(mockIdToken) + .should.eventually.be.rejectedWith('Firebase ID token has "kid" claim which does not ' + + 'correspond to a known public key'); + }); + it('should be rejected given a Firebase JWT token with a subject with greater than 128 characters', () => { - mockedRequests.push(mockFetchPublicKeys()); + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .resolves(); + stubs.push(verifierStub); // uid of length 128 should be fulfilled let uid = Array(129).join('a'); @@ -414,62 +330,59 @@ describe('FirebaseTokenVerifier', () => { }); }); - it('should be rejected given an expired Firebase JWT token', () => { - mockedRequests.push(mockFetchPublicKeys()); - - clock = sinon.useFakeTimers(1000); + it('should be rejected when the verifier throws for expired Firebase JWT token', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .rejects(new JwtError(JwtErrorCode.TOKEN_EXPIRED, 'Expired token.')); + stubs.push(verifierStub); const mockIdToken = mocks.generateIdToken(); - clock.tick((ONE_HOUR_IN_SECONDS * 1000) - 1); - - // Token should still be valid - return tokenVerifier.verifyJWT(mockIdToken).then(() => { - clock!.tick(1); - - // Token should now be invalid - return tokenVerifier.verifyJWT(mockIdToken) - .should.eventually.be.rejectedWith('Firebase ID token has expired. Get a fresh ID token from your client ' + - 'app and try again (auth/id-token-expired)') - .and.have.property('code', 'auth/id-token-expired'); - }); + return tokenVerifier.verifyJWT(mockIdToken) + .should.eventually.be.rejectedWith('Firebase ID token has expired. Get a fresh ID token from your client ' + + 'app and try again (auth/id-token-expired)') + .and.have.property('code', 'auth/id-token-expired'); }); - it('should be rejected given an expired Firebase session cookie', () => { + it('should be rejected when the verifier throws for expired Firebase session cookie', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .rejects(new JwtError(JwtErrorCode.TOKEN_EXPIRED, 'Expired token.')); + stubs.push(verifierStub); + const tokenVerifierSessionCookie = new verifier.FirebaseTokenVerifier( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys', - 'RS256', 'https://session.firebase.google.com/', verifier.SESSION_COOKIE_INFO, app, ); - mockedRequests.push(mockFetchPublicKeys('/identitytoolkit/v3/relyingparty/publicKeys')); - - clock = sinon.useFakeTimers(1000); const mockSessionCookie = mocks.generateSessionCookie(); - clock.tick((ONE_HOUR_IN_SECONDS * 1000) - 1); + return tokenVerifierSessionCookie.verifyJWT(mockSessionCookie) + .should.eventually.be.rejectedWith('Firebase session cookie has expired. Get a fresh session cookie from ' + + 'your client app and try again (auth/session-cookie-expired).') + .and.have.property('code', 'auth/session-cookie-expired'); + }); - // Cookie should still be valid - return tokenVerifierSessionCookie.verifyJWT(mockSessionCookie).then(() => { - clock!.tick(1); + it('should be rejected when the verifier throws invalid signature for a Firebase JWT token.', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .rejects(new JwtError(JwtErrorCode.INVALID_SIGNATURE, 'invalid signature.')); + stubs.push(verifierStub); - // Cookie should now be invalid - return tokenVerifierSessionCookie.verifyJWT(mockSessionCookie) - .should.eventually.be.rejectedWith('Firebase session cookie has expired. Get a fresh session cookie from ' + - 'your client app and try again (auth/session-cookie-expired).') - .and.have.property('code', 'auth/session-cookie-expired'); - }); + const mockIdToken = mocks.generateIdToken(); + + return tokenVerifier.verifyJWT(mockIdToken) + .should.eventually.be.rejectedWith('Firebase ID token has invalid signature'); }); - it('should be rejected given a Firebase JWT token which was not signed with the kid it specifies', () => { - mockedRequests.push(mockFetchWrongPublicKeys()); + it('should be rejected when the verifier throws key fetch error.', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .rejects(new JwtError(JwtErrorCode.KEY_FETCH_ERROR, 'Error fetching public keys.')); + stubs.push(verifierStub); const mockIdToken = mocks.generateIdToken(); return tokenVerifier.verifyJWT(mockIdToken) - .should.eventually.be.rejectedWith('Firebase ID token has invalid signature'); + .should.eventually.be.rejectedWith('Error fetching public keys.'); }); it('should be rejected given a custom token with error using article "an" before JWT short name', () => { @@ -483,7 +396,6 @@ describe('FirebaseTokenVerifier', () => { it('should be rejected given a custom token with error using article "a" before JWT short name', () => { const tokenVerifierSessionCookie = new verifier.FirebaseTokenVerifier( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys', - 'RS256', 'https://session.firebase.google.com/', verifier.SESSION_COOKIE_INFO, app, @@ -509,7 +421,6 @@ describe('FirebaseTokenVerifier', () => { it('should be rejected given a legacy custom token with error using article "a" before JWT short name', () => { const tokenVerifierSessionCookie = new verifier.FirebaseTokenVerifier( 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/publicKeys', - 'RS256', 'https://session.firebase.google.com/', verifier.SESSION_COOKIE_INFO, app, @@ -524,8 +435,26 @@ describe('FirebaseTokenVerifier', () => { 'verifySessionCookie() expects a session cookie, but was given a legacy custom token'); }); + it('AppOptions.httpAgent should be passed to the verifier', () => { + const mockAppWithAgent = mocks.appWithOptions({ + httpAgent: new Agent() + }); + const agentForApp = mockAppWithAgent.options.httpAgent; + const verifierSpy = sinon.spy(PublicKeySignatureVerifier, 'withCertificateUrl'); + + expect(verifierSpy.args).to.be.empty; + + createTokenVerifier(mockAppWithAgent); + + expect(verifierSpy.args[0][1]).to.equal(agentForApp); + + verifierSpy.restore(); + }); + it('should be fulfilled with decoded claims given a valid Firebase JWT token', () => { - mockedRequests.push(mockFetchPublicKeys()); + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .resolves(); + stubs.push(verifierStub); clock = sinon.useFakeTimers(1000); @@ -544,16 +473,17 @@ describe('FirebaseTokenVerifier', () => { }); }); - it('should decode an unsigned token when the algorithm is set to none (emulator)', async () => { + it('should decode an unsigned token if isEmulator=true', async () => { clock = sinon.useFakeTimers(1000); - const emulatorVerifier = createTokenVerifier(app, { algorithm: 'none' }); + const emulatorVerifier = createTokenVerifier(app); const mockIdToken = mocks.generateIdToken({ algorithm: 'none', header: {} }); - const decoded = await emulatorVerifier.verifyJWT(mockIdToken); + const isEmulator = true; + const decoded = await emulatorVerifier.verifyJWT(mockIdToken, isEmulator); expect(decoded).to.deep.equal({ one: 'uno', two: 'dos', @@ -566,16 +496,6 @@ describe('FirebaseTokenVerifier', () => { }); }); - it('should not decode a signed token when the algorithm is set to none (emulator)', async () => { - clock = sinon.useFakeTimers(1000); - - const emulatorVerifier = createTokenVerifier(app, { algorithm: 'none' }); - const mockIdToken = mocks.generateIdToken(); - - await emulatorVerifier.verifyJWT(mockIdToken) - .should.eventually.be.rejectedWith('Firebase ID token has incorrect algorithm. Expected "none"'); - }); - it('should not decode an unsigned token when the algorithm is not overridden (emulator)', async () => { clock = sinon.useFakeTimers(1000); @@ -592,110 +512,5 @@ describe('FirebaseTokenVerifier', () => { await tokenVerifier.verifyJWT(idTokenNoHeader) .should.eventually.be.rejectedWith('Firebase ID token has no "kid" claim.'); }); - - it('should use the given HTTP Agent', () => { - const agent = new https.Agent(); - const appWithAgent = mocks.appWithOptions({ - credential: mocks.credential, - httpAgent: agent, - }); - tokenVerifier = new verifier.FirebaseTokenVerifier( - 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', - 'RS256', - 'https://securetoken.google.com/', - verifier.ID_TOKEN_INFO, - appWithAgent, - ); - mockedRequests.push(mockFetchPublicKeys()); - - clock = sinon.useFakeTimers(1000); - - const mockIdToken = mocks.generateIdToken(); - - return tokenVerifier.verifyJWT(mockIdToken) - .then(() => { - expect(https.request).to.have.been.calledOnce; - expect(httpsSpy.args[0][0].agent).to.equal(agent); - }); - }); - - it('should not fetch the Google cert public keys until the first time verifyJWT() is called', () => { - mockedRequests.push(mockFetchPublicKeys()); - - const testTokenVerifier = new verifier.FirebaseTokenVerifier( - 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', - 'RS256', - 'https://securetoken.google.com/', - verifier.ID_TOKEN_INFO, - app, - ); - expect(https.request).not.to.have.been.called; - - const mockIdToken = mocks.generateIdToken(); - - return testTokenVerifier.verifyJWT(mockIdToken) - .then(() => expect(https.request).to.have.been.calledOnce); - }); - - it('should not re-fetch the Google cert public keys every time verifyJWT() is called', () => { - mockedRequests.push(mockFetchPublicKeys()); - - const mockIdToken = mocks.generateIdToken(); - - return tokenVerifier.verifyJWT(mockIdToken).then(() => { - expect(https.request).to.have.been.calledOnce; - return tokenVerifier.verifyJWT(mockIdToken); - }).then(() => expect(https.request).to.have.been.calledOnce); - }); - - it('should refresh the Google cert public keys after the "max-age" on the request expires', () => { - mockedRequests.push(mockFetchPublicKeys()); - mockedRequests.push(mockFetchPublicKeys()); - mockedRequests.push(mockFetchPublicKeys()); - - clock = sinon.useFakeTimers(1000); - - const mockIdToken = mocks.generateIdToken(); - - return tokenVerifier.verifyJWT(mockIdToken).then(() => { - expect(https.request).to.have.been.calledOnce; - clock!.tick(999); - return tokenVerifier.verifyJWT(mockIdToken); - }).then(() => { - expect(https.request).to.have.been.calledOnce; - clock!.tick(1); - return tokenVerifier.verifyJWT(mockIdToken); - }).then(() => { - // One second has passed - expect(https.request).to.have.been.calledTwice; - clock!.tick(999); - return tokenVerifier.verifyJWT(mockIdToken); - }).then(() => { - expect(https.request).to.have.been.calledTwice; - clock!.tick(1); - return tokenVerifier.verifyJWT(mockIdToken); - }).then(() => { - // Two seconds have passed - expect(https.request).to.have.been.calledThrice; - }); - }); - - it('should be rejected if fetching the Google public keys fails', () => { - mockedRequests.push(mockFailedFetchPublicKeys()); - - const mockIdToken = mocks.generateIdToken(); - - return tokenVerifier.verifyJWT(mockIdToken) - .should.eventually.be.rejectedWith('message'); - }); - - it('should be rejected if fetching the Google public keys returns a response with an error message', () => { - mockedRequests.push(mockFetchPublicKeysWithErrorResponse()); - - const mockIdToken = mocks.generateIdToken(); - - return tokenVerifier.verifyJWT(mockIdToken) - .should.eventually.be.rejectedWith('Error fetching public keys for Google certs: message (description)'); - }); }); }); diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index af0fd747fa..23e9b54a07 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -23,8 +23,8 @@ import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { DatabaseService } from '../../../src/database/database'; -import { Database } from '../../../src/database/index'; +import { Database, DatabaseService } from '../../../src/database/database'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import * as utils from '../utils'; import { HttpClient, HttpRequestConfig } from '../../../src/utils/api-request'; @@ -46,7 +46,7 @@ describe('Database', () => { describe('Constructor', () => { const invalidApps = [null, NaN, 0, 1, true, false, '', 'a', [], [1, 'a'], {}, { a: 1 }, _.noop]; invalidApps.forEach((invalidApp) => { - it(`should throw given invalid app: ${ JSON.stringify(invalidApp) }`, () => { + it(`should throw given invalid app: ${JSON.stringify(invalidApp)}`, () => { expect(() => { const databaseAny: any = DatabaseService; return new databaseAny(invalidApp); @@ -116,6 +116,181 @@ describe('Database', () => { }); }); + describe('Token refresh', () => { + const MINUTE_IN_MILLIS = 60 * 1000; + + let clock: sinon.SinonFakeTimers; + let getTokenStub: sinon.SinonStub; + + beforeEach(() => { + getTokenStub = stubCredentials(); + clock = sinon.useFakeTimers(1000); + }); + + afterEach(() => { + getTokenStub.restore(); + clock.restore(); + }); + + function stubCredentials(options?: { + accessToken?: string; + expiresIn?: number; + err?: any; + }): sinon.SinonStub { + if (options?.err) { + return sinon.stub(ServiceAccountCredential.prototype, 'getAccessToken') + .rejects(options.err); + } + + return sinon.stub(ServiceAccountCredential.prototype, 'getAccessToken') + .resolves({ + access_token: options?.accessToken || 'mock-access-token', // eslint-disable-line @typescript-eslint/camelcase + expires_in: options?.expiresIn || 3600, // eslint-disable-line @typescript-eslint/camelcase + }); + } + + it('should refresh the token 5 minutes before expiration', () => { + database.getDatabase(); + expect(getTokenStub).to.have.not.been.called; + return mockApp.INTERNAL.getToken() + .then((token) => { + expect(getTokenStub).to.have.been.calledOnce; + + const expiryInMillis = token.expirationTime - Date.now(); + clock.tick(expiryInMillis - (5 * MINUTE_IN_MILLIS) - 1000); + expect(getTokenStub).to.have.been.calledOnce; + + clock.tick(1000); + expect(getTokenStub).to.have.been.calledTwice; + }); + }); + + it('should not start multiple token refresher tasks', () => { + database.getDatabase(); + database.getDatabase('https://other-database.firebaseio.com'); + expect(getTokenStub).to.have.not.been.called; + return mockApp.INTERNAL.getToken() + .then((token) => { + expect(getTokenStub).to.have.been.calledOnce; + + const expiryInMillis = token.expirationTime - Date.now(); + clock.tick(expiryInMillis - (5 * MINUTE_IN_MILLIS)); + expect(getTokenStub).to.have.been.calledTwice; + }); + }); + + it('should reschedule the token refresher when the underlying token changes', () => { + database.getDatabase(); + return mockApp.INTERNAL.getToken() + .then((token1) => { + expect(getTokenStub).to.have.been.calledOnce; + + // Forward the clock to 30 minutes before expiry. + const expiryInMillis = token1.expirationTime - Date.now(); + clock.tick(expiryInMillis - (30 * MINUTE_IN_MILLIS)); + + // Force a token refresh + return mockApp.INTERNAL.getToken(true) + .then((token2) => { + expect(getTokenStub).to.have.been.calledTwice; + // Forward the clock to 5 minutes before old expiry time. + clock.tick(25 * MINUTE_IN_MILLIS); + expect(getTokenStub).to.have.been.calledTwice; + + // Forward the clock 1 second past old expiry time. + clock.tick(5 * MINUTE_IN_MILLIS + 1000); + expect(getTokenStub).to.have.been.calledTwice; + + const newExpiryTimeInMillis = token2.expirationTime - Date.now(); + clock.tick(newExpiryTimeInMillis - (5 * MINUTE_IN_MILLIS)); + expect(getTokenStub).to.have.been.calledThrice; + }); + }); + }); + + // Currently doesn't work as expected since onTokenChange() can force a token refresh + // by calling getToken(). Skipping for now. + xit('should not reschedule when the token is about to expire in 5 minutes', () => { + database.getDatabase(); + return mockApp.INTERNAL.getToken() + .then((token1) => { + expect(getTokenStub).to.have.been.calledOnce; + + // Forward the clock to 30 minutes before expiry. + const expiryInMillis = token1.expirationTime - Date.now(); + clock.tick(expiryInMillis - (30 * MINUTE_IN_MILLIS)); + + getTokenStub.restore(); + getTokenStub = stubCredentials({ expiresIn: 5 * 60 }); + // Force a token refresh + return mockApp.INTERNAL.getToken(true); + }) + .then((token2) => { + expect(getTokenStub).to.have.been.calledOnce; + + const newExpiryTimeInMillis = token2.expirationTime - Date.now(); + clock.tick(newExpiryTimeInMillis); + expect(getTokenStub).to.have.been.calledOnce; + + getTokenStub.restore(); + getTokenStub = stubCredentials({ expiresIn: 60 * 60 }); + // Force a token refresh + return mockApp.INTERNAL.getToken(true); + }) + .then((token3) => { + expect(getTokenStub).to.have.been.calledOnce; + + const newExpiryTimeInMillis = token3.expirationTime - Date.now(); + clock.tick(newExpiryTimeInMillis - (5 * MINUTE_IN_MILLIS)); + expect(getTokenStub).to.have.been.calledTwice; + }); + }); + + it('should gracefully handle errors during token refresh', () => { + database.getDatabase(); + return mockApp.INTERNAL.getToken() + .then((token1) => { + expect(getTokenStub).to.have.been.calledOnce; + + getTokenStub.restore(); + getTokenStub = stubCredentials({ err: new Error('Test error') }); + expect(getTokenStub).to.have.not.been.called; + + const expiryInMillis = token1.expirationTime - Date.now(); + clock.tick(expiryInMillis); + expect(getTokenStub).to.have.been.calledOnce; + + getTokenStub.restore(); + getTokenStub = stubCredentials(); + expect(getTokenStub).to.have.not.been.called; + // Force a token refresh + return mockApp.INTERNAL.getToken(true); + }) + .then((token2) => { + expect(getTokenStub).to.have.been.calledOnce; + + const newExpiryTimeInMillis = token2.expirationTime - Date.now(); + clock.tick(newExpiryTimeInMillis - (5 * MINUTE_IN_MILLIS)); + expect(getTokenStub).to.have.been.calledTwice; + }); + }); + + it('should stop the token refresher task at delete', () => { + database.getDatabase(); + return mockApp.INTERNAL.getToken() + .then((token) => { + expect(getTokenStub).to.have.been.calledOnce; + return database.delete() + .then(() => { + // Forward the clock to five minutes before expiry. + const expiryInMillis = token.expirationTime - Date.now(); + clock.tick(expiryInMillis - (5 * MINUTE_IN_MILLIS)); + expect(getTokenStub).to.have.been.calledOnce; + }); + }); + }); + }); + describe('Rules', () => { const mockAccessToken: string = utils.generateRandomAccessToken(); let getTokenStub: sinon.SinonStub; @@ -152,11 +327,8 @@ describe('Database', () => { }`; const rulesPath = '.settings/rules.json'; - function callParamsForGet( - strict = false, - url = `https://databasename.firebaseio.com/${rulesPath}`, - ): HttpRequestConfig { - + function callParamsForGet(options?: { strict?: boolean; url?: string }): HttpRequestConfig { + const url = options?.url || `https://databasename.firebaseio.com/${rulesPath}`; const params: HttpRequestConfig = { method: 'GET', url, @@ -165,7 +337,7 @@ describe('Database', () => { }, }; - if (strict) { + if (options?.strict) { params.data = { format: 'strict' }; } @@ -213,7 +385,7 @@ describe('Database', () => { return db.getRules().then((result) => { expect(result).to.equal(rulesString); return expect(stub).to.have.been.calledOnce.and.calledWith( - callParamsForGet(false, `https://custom.firebaseio.com/${rulesPath}`)); + callParamsForGet({ url: `https://custom.firebaseio.com/${rulesPath}` })); }); }); @@ -223,7 +395,7 @@ describe('Database', () => { return db.getRules().then((result) => { expect(result).to.equal(rulesString); return expect(stub).to.have.been.calledOnce.and.calledWith( - callParamsForGet(false, `http://localhost:9000/${rulesPath}?ns=foo`)); + callParamsForGet({ url: `http://localhost:9000/${rulesPath}?ns=foo` })); }); }); @@ -257,7 +429,7 @@ describe('Database', () => { return db.getRulesJSON().then((result) => { expect(result).to.deep.equal(rules); return expect(stub).to.have.been.calledOnce.and.calledWith( - callParamsForGet(true)); + callParamsForGet({ strict: true })); }); }); @@ -267,7 +439,7 @@ describe('Database', () => { return db.getRulesJSON().then((result) => { expect(result).to.deep.equal(rules); return expect(stub).to.have.been.calledOnce.and.calledWith( - callParamsForGet(true, `https://custom.firebaseio.com/${rulesPath}`)); + callParamsForGet({ strict: true, url: `https://custom.firebaseio.com/${rulesPath}` })); }); }); @@ -277,7 +449,7 @@ describe('Database', () => { return db.getRulesJSON().then((result) => { expect(result).to.deep.equal(rules); return expect(stub).to.have.been.calledOnce.and.calledWith( - callParamsForGet(true, `http://localhost:9000/${rulesPath}?ns=foo`)); + callParamsForGet({ strict: true, url: `http://localhost:9000/${rulesPath}?ns=foo` })); }); }); @@ -407,5 +579,101 @@ describe('Database', () => { return db.setRules(rules).should.eventually.be.rejectedWith('network error'); }); }); + + describe('emulator mode', () => { + interface EmulatorTestConfig { + name: string; + setUp: () => FirebaseApp; + tearDown?: () => void; + url: string; + } + + const configs: EmulatorTestConfig[] = [ + { + name: 'with environment variable', + setUp: () => { + process.env.FIREBASE_DATABASE_EMULATOR_HOST = 'localhost:9090'; + return mocks.app(); + }, + tearDown: () => { + delete process.env.FIREBASE_DATABASE_EMULATOR_HOST; + }, + url: `http://localhost:9090/${rulesPath}?ns=databasename`, + }, + { + name: 'with app options', + setUp: () => { + return mocks.appWithOptions({ + databaseURL: 'http://localhost:9091?ns=databasename', + }); + }, + url: `http://localhost:9091/${rulesPath}?ns=databasename`, + }, + { + name: 'with environment variable overriding app options', + setUp: () => { + process.env.FIREBASE_DATABASE_EMULATOR_HOST = 'localhost:9090'; + return mocks.appWithOptions({ + databaseURL: 'http://localhost:9091?ns=databasename', + }); + }, + tearDown: () => { + delete process.env.FIREBASE_DATABASE_EMULATOR_HOST; + }, + url: `http://localhost:9090/${rulesPath}?ns=databasename`, + }, + ]; + + configs.forEach((config) => { + describe(config.name, () => { + let emulatorApp: FirebaseApp; + let emulatorDatabase: DatabaseService; + + before(() => { + emulatorApp = config.setUp(); + emulatorDatabase = new DatabaseService(emulatorApp); + }); + + after(() => { + if (config.tearDown) { + config.tearDown(); + } + + return emulatorDatabase.delete().then(() => { + return emulatorApp.delete(); + }); + }); + + it('getRules should connect to the emulator', () => { + const db: Database = emulatorDatabase.getDatabase(); + const stub = stubSuccessfulResponse(rules); + return db.getRules().then((result) => { + expect(result).to.equal(rulesString); + return expect(stub).to.have.been.calledOnce.and.calledWith( + callParamsForGet({ url: config.url })); + }); + }); + + it('getRulesJSON should connect to the emulator', () => { + const db: Database = emulatorDatabase.getDatabase(); + const stub = stubSuccessfulResponse(rules); + return db.getRulesJSON().then((result) => { + expect(result).to.equal(rules); + return expect(stub).to.have.been.calledOnce.and.calledWith( + callParamsForGet({ strict: true, url: config.url })); + }); + }); + + it('setRules should connect to the emulator', () => { + const db: Database = emulatorDatabase.getDatabase(); + const stub = stubSuccessfulResponse({}); + return db.setRules(rulesString).then(() => { + return expect(stub).to.have.been.calledOnce.and.calledWith( + callParamsForPut(rulesString, config.url)); + }); + }); + }); + }); + }); }); }); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index b6bded8145..b5eade74b1 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -27,6 +27,7 @@ import './utils/index.spec'; import './utils/error.spec'; import './utils/validator.spec'; import './utils/api-request.spec'; +import './utils/jwt.spec'; // Auth import './auth/auth.spec'; diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index 3277edeefe..215d9b4472 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -24,18 +24,17 @@ import * as sinon from 'sinon'; import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; -import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; - import { FirebaseApp } from '../../../src/app/firebase-app'; import { Message, MessagingOptions, MessagingPayload, MessagingDevicesResponse, MessagingDeviceGroupResponse, MessagingTopicManagementResponse, BatchResponse, - SendResponse, MulticastMessage, Messaging, + SendResponse, MulticastMessage, Messaging, TokenMessage, TopicMessage, ConditionMessage, } from '../../../src/messaging/index'; import { BLACKLISTED_OPTIONS_KEYS, BLACKLISTED_DATA_PAYLOAD_KEYS } from '../../../src/messaging/messaging-internal'; import { HttpClient } from '../../../src/utils/api-request'; import { getSdkVersion } from '../../../src/utils/index'; +import * as utils from '../utils'; chai.should(); chai.use(sinonChai); @@ -816,6 +815,32 @@ describe('Messaging', () => { [validMessage], ).should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); }); + + // This test was added to also verify https://github.com/firebase/firebase-admin-node/issues/1146 + it('should be fulfilled when called with different message types', () => { + const messageIds = [ + 'projects/projec_id/messages/1', + 'projects/projec_id/messages/2', + 'projects/projec_id/messages/3', + ]; + const tokenMessage: TokenMessage = { token: 'test' }; + const topicMessage: TopicMessage = { topic: 'test' }; + const conditionMessage: ConditionMessage = { condition: 'test' }; + const messages: Message[] = [tokenMessage, topicMessage, conditionMessage]; + + mockedRequests.push(mockBatchRequest(messageIds)); + + return messaging.sendAll(messages) + .then((response: BatchResponse) => { + expect(response.successCount).to.equal(3); + expect(response.failureCount).to.equal(0); + response.responses.forEach((resp, idx) => { + expect(resp.success).to.be.true; + expect(resp.messageId).to.equal(messageIds[idx]); + expect(resp.error).to.be.undefined; + }); + }); + }); }); describe('sendMulticast()', () => { @@ -880,7 +905,7 @@ describe('Messaging', () => { expect(messages.length).to.equal(3); expect(stub!.args[0][1]).to.be.undefined; messages.forEach((message, idx) => { - expect((message as any).token).to.equal(tokens[idx]); + expect((message as TokenMessage).token).to.equal(tokens[idx]); expect(message.android).to.be.undefined; expect(message.apns).to.be.undefined; expect(message.data).to.be.undefined; @@ -910,7 +935,7 @@ describe('Messaging', () => { expect(messages.length).to.equal(3); expect(stub!.args[0][1]).to.be.undefined; messages.forEach((message, idx) => { - expect((message as any).token).to.equal(tokens[idx]); + expect((message as TokenMessage).token).to.equal(tokens[idx]); expect(message.android).to.deep.equal(multicast.android); expect(message.apns).to.be.deep.equal(multicast.apns); expect(message.data).to.be.deep.equal(multicast.data); diff --git a/test/unit/storage/storage.spec.ts b/test/unit/storage/storage.spec.ts index 5f59250930..985b9b29af 100644 --- a/test/unit/storage/storage.spec.ts +++ b/test/unit/storage/storage.spec.ts @@ -113,4 +113,24 @@ describe('Storage', () => { expect(storage.bucket('foo').name).to.equal('foo'); }); }); + + describe('Emulator mode', () => { + const EMULATOR_HOST = 'http://localhost:9199'; + + before(() => { + delete process.env.STORAGE_EMULATOR_HOST; + process.env.FIREBASE_STORAGE_EMULATOR_HOST = EMULATOR_HOST; + }); + + it('sets STORAGE_EMULATOR_HOST if FIREBASE_STORAGE_EMULATOR_HOST is set', () => { + new Storage(mockApp); + + expect(process.env.STORAGE_EMULATOR_HOST).to.equal(EMULATOR_HOST); + }); + + after(() => { + delete process.env.STORAGE_EMULATOR_HOST; + delete process.env.FIREBASE_STORAGE_EMULATOR_HOST; + }); + }) }); diff --git a/test/unit/utils/jwt.spec.ts b/test/unit/utils/jwt.spec.ts new file mode 100644 index 0000000000..525608feef --- /dev/null +++ b/test/unit/utils/jwt.spec.ts @@ -0,0 +1,541 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +// Use untyped import syntax for Node built-ins +import https = require('https'); + +import * as _ from 'lodash'; +import * as chai from 'chai'; +import * as nock from 'nock'; +import * as sinon from 'sinon'; +//import * as sinonChai from 'sinon-chai'; +//import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import * as jwtUtil from '../../../src/utils/jwt'; + +const expect = chai.expect; + +const ONE_HOUR_IN_SECONDS = 60 * 60; +const publicCertPath = '/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'; + +/** + * Returns a mocked out success response from the URL containing the public keys for the Google certs. + * + * @param {string=} path URL path to which the mock request should be made. If not specified, defaults + * to the URL path of ID token public key certificates. + * @return {Object} A nock response object. + */ +function mockFetchPublicKeys(path: string = publicCertPath): nock.Scope { + const mockedResponse: { [key: string]: string } = {}; + mockedResponse[mocks.certificateObject.private_key_id] = mocks.keyPairs[0].public; + return nock('https://www.googleapis.com') + .get(path) + .reply(200, mockedResponse, { + 'cache-control': 'public, max-age=1, must-revalidate, no-transform', + }); +} + +/** + * Returns a mocked out error response from the URL containing the public keys for the Google certs. + * The status code is 200 but the response itself will contain an 'error' key. + * + * @return {Object} A nock response object. + */ + +function mockFetchPublicKeysWithErrorResponse(): nock.Scope { + return nock('https://www.googleapis.com') + .get('/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com') + .reply(200, { + error: 'message', + error_description: 'description', // eslint-disable-line @typescript-eslint/camelcase + }); +} + +/** + * Returns a mocked out failed response from the URL containing the public keys for the Google certs. + * The status code is non-200 and the response itself will fail. + * + * @return {Object} A nock response object. + */ + +function mockFailedFetchPublicKeys(): nock.Scope { + return nock('https://www.googleapis.com') + .get('/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com') + .replyWithError('message'); +} + + +const TOKEN_PAYLOAD = { + one: 'uno', + two: 'dos', + iat: 1, + exp: ONE_HOUR_IN_SECONDS + 1, + aud: mocks.projectId, + iss: 'https://securetoken.google.com/' + mocks.projectId, + sub: mocks.uid, +}; + +const DECODED_SIGNED_TOKEN: jwtUtil.DecodedToken = { + header: { + alg: 'RS256', + kid: 'aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd', + typ: 'JWT', + }, + payload: TOKEN_PAYLOAD +}; + +const DECODED_UNSIGNED_TOKEN: jwtUtil.DecodedToken = { + header: { + alg: 'none', + typ: 'JWT', + }, + payload: TOKEN_PAYLOAD +}; + +const VALID_PUBLIC_KEYS_RESPONSE: { [key: string]: string } = {}; +VALID_PUBLIC_KEYS_RESPONSE[mocks.certificateObject.private_key_id] = mocks.keyPairs[0].public; + +describe('decodeJwt', () => { + let clock: sinon.SinonFakeTimers | undefined; + + afterEach(() => { + if (clock) { + clock.restore(); + clock = undefined; + } + }); + + it('should reject given no token', () => { + return (jwtUtil.decodeJwt as any)() + .should.eventually.be.rejectedWith('The provided token must be a string.'); + }); + + const invalidIdTokens = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop]; + invalidIdTokens.forEach((invalidIdToken) => { + it('should reject given a non-string token: ' + JSON.stringify(invalidIdToken), () => { + return jwtUtil.decodeJwt(invalidIdToken as any) + .should.eventually.be.rejectedWith('The provided token must be a string.'); + }); + }); + + it('should reject given an empty string token', () => { + return jwtUtil.decodeJwt('') + .should.eventually.be.rejectedWith('Decoding token failed.'); + }); + + it('should reject given an invalid token', () => { + return jwtUtil.decodeJwt('invalid-token') + .should.eventually.be.rejectedWith('Decoding token failed.'); + }); + + it('should be fulfilled with decoded claims given a valid signed token', () => { + clock = sinon.useFakeTimers(1000); + + const mockIdToken = mocks.generateIdToken(); + + return jwtUtil.decodeJwt(mockIdToken) + .should.eventually.be.fulfilled.and.deep.equal(DECODED_SIGNED_TOKEN); + }); + + it('should be fulfilled with decoded claims given a valid unsigned token', () => { + clock = sinon.useFakeTimers(1000); + + const mockIdToken = mocks.generateIdToken({ + algorithm: 'none', + header: {} + }); + + return jwtUtil.decodeJwt(mockIdToken) + .should.eventually.be.fulfilled.and.deep.equal(DECODED_UNSIGNED_TOKEN); + }); +}); + + +describe('verifyJwtSignature', () => { + let clock: sinon.SinonFakeTimers | undefined; + + afterEach(() => { + if (clock) { + clock.restore(); + clock = undefined; + } + }); + + it('should throw given no token', () => { + return (jwtUtil.verifyJwtSignature as any)() + .should.eventually.be.rejectedWith('The provided token must be a string.'); + }); + + const invalidIdTokens = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop]; + invalidIdTokens.forEach((invalidIdToken) => { + it('should reject given a non-string token: ' + JSON.stringify(invalidIdToken), () => { + return jwtUtil.verifyJwtSignature(invalidIdToken as any, mocks.keyPairs[0].public) + .should.eventually.be.rejectedWith('The provided token must be a string.'); + }); + }); + + it('should reject given an empty string token', () => { + return jwtUtil.verifyJwtSignature('', mocks.keyPairs[0].public) + .should.eventually.be.rejectedWith('jwt must be provided'); + }); + + it('should be fulfilled given a valid signed token and public key', () => { + const mockIdToken = mocks.generateIdToken(); + + return jwtUtil.verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, + { algorithms: [jwtUtil.ALGORITHM_RS256] }) + .should.eventually.be.fulfilled; + }); + + it('should be fulfilled given a valid unsigned (emulator) token and no public key', () => { + const mockIdToken = mocks.generateIdToken({ + algorithm: 'none', + header: {} + }); + + return jwtUtil.verifyJwtSignature(mockIdToken, '') + .should.eventually.be.fulfilled; + }); + + it('should be fulfilled given a valid signed token and a function to provide public keys', () => { + const mockIdToken = mocks.generateIdToken(); + const getKeyCallback = (_: any, callback: any): void => callback(null, mocks.keyPairs[0].public); + + return jwtUtil.verifyJwtSignature(mockIdToken, getKeyCallback, + { algorithms: [jwtUtil.ALGORITHM_RS256] }) + .should.eventually.be.fulfilled; + }); + + it('should be rejected when the given algorithm does not match the token', () => { + const mockIdToken = mocks.generateIdToken(); + + return jwtUtil.verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, + { algorithms: ['RS384'] }) + .should.eventually.be.rejectedWith('invalid algorithm') + .with.property('code', jwtUtil.JwtErrorCode.INVALID_SIGNATURE); + }); + + it('should be rejected given an expired token', () => { + clock = sinon.useFakeTimers(1000); + const mockIdToken = mocks.generateIdToken(); + clock.tick((ONE_HOUR_IN_SECONDS * 1000) - 1); + + // token should still be valid + return jwtUtil.verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, + { algorithms: [jwtUtil.ALGORITHM_RS256] }) + .then(() => { + clock!.tick(1); + + // token should now be invalid + return jwtUtil.verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, + { algorithms: [jwtUtil.ALGORITHM_RS256] }) + .should.eventually.be.rejectedWith( + 'The provided token has expired. Get a fresh token from your client app and try again.' + ) + .with.property('code', jwtUtil.JwtErrorCode.TOKEN_EXPIRED); + }); + }); + + it('should be rejected with correct public key fetch error.', () => { + const mockIdToken = mocks.generateIdToken(); + const getKeyCallback = (_: any, callback: any): void => + callback(new Error('key fetch failed.')); + + return jwtUtil.verifyJwtSignature(mockIdToken, getKeyCallback, + { algorithms: [jwtUtil.ALGORITHM_RS256] }) + .should.eventually.be.rejectedWith('key fetch failed.') + .with.property('code', jwtUtil.JwtErrorCode.KEY_FETCH_ERROR); + }); + + it('should be rejected with correct no matching key id found error.', () => { + const mockIdToken = mocks.generateIdToken(); + const getKeyCallback = (_: any, callback: any): void => + callback(new Error('no-matching-kid-error')); + + return jwtUtil.verifyJwtSignature(mockIdToken, getKeyCallback, + { algorithms: [jwtUtil.ALGORITHM_RS256] }) + .should.eventually.be.rejectedWith('no-matching-kid-error') + .with.property('code', jwtUtil.JwtErrorCode.NO_MATCHING_KID); + }); + + it('should be rejected given a public key that does not match the token.', () => { + const mockIdToken = mocks.generateIdToken(); + + return jwtUtil.verifyJwtSignature(mockIdToken, mocks.keyPairs[1].public, + { algorithms: [jwtUtil.ALGORITHM_RS256] }) + .should.eventually.be.rejectedWith('invalid signature') + .with.property('code', jwtUtil.JwtErrorCode.INVALID_SIGNATURE); + }); + + it('should be rejected given an invalid JWT.', () => { + return jwtUtil.verifyJwtSignature('invalid-token', mocks.keyPairs[0].public) + .should.eventually.be.rejectedWith('jwt malformed') + .with.property('code', jwtUtil.JwtErrorCode.INVALID_SIGNATURE); + }); +}); + +describe('PublicKeySignatureVerifier', () => { + let stubs: sinon.SinonStub[] = []; + const verifier = new jwtUtil.PublicKeySignatureVerifier( + new jwtUtil.UrlKeyFetcher('https://www.example.com/publicKeys')); + + afterEach(() => { + _.forEach(stubs, (stub) => stub.restore()); + stubs = []; + }); + + describe('Constructor', () => { + it('should not throw when valid key fetcher is provided', () => { + expect(() => { + new jwtUtil.PublicKeySignatureVerifier( + new jwtUtil.UrlKeyFetcher('https://www.example.com/publicKeys')); + }).not.to.throw(); + }); + + const invalidKeyFetchers = [null, NaN, 0, 1, true, false, [], ['a'], _.noop, '', 'a']; + invalidKeyFetchers.forEach((invalidKeyFetcher) => { + it('should throw given an invalid key fetcher: ' + JSON.stringify(invalidKeyFetcher), () => { + expect(() => { + new jwtUtil.PublicKeySignatureVerifier(invalidKeyFetchers as any); + }).to.throw('The provided key fetcher is not an object or null.'); + }); + }); + }); + + describe('withCertificateUrl', () => { + it('should return a PublicKeySignatureVerifier instance when a valid cert url is provided', () => { + expect( + jwtUtil.PublicKeySignatureVerifier.withCertificateUrl('https://www.example.com/publicKeys') + ).to.be.an.instanceOf(jwtUtil.PublicKeySignatureVerifier); + }); + }); + + describe('verify', () => { + it('should throw given no token', () => { + return (verifier.verify as any)() + .should.eventually.be.rejectedWith('The provided token must be a string.'); + }); + + const invalidIdTokens = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop]; + invalidIdTokens.forEach((invalidIdToken) => { + it('should reject given a non-string token: ' + JSON.stringify(invalidIdToken), () => { + return verifier.verify(invalidIdToken as any) + .should.eventually.be.rejectedWith('The provided token must be a string.'); + }); + }); + + it('should reject given an empty string token', () => { + return verifier.verify('') + .should.eventually.be.rejectedWith('jwt must be provided'); + }); + + it('should be fullfilled given a valid token', () => { + const keyFetcherStub = sinon.stub(jwtUtil.UrlKeyFetcher.prototype, 'fetchPublicKeys') + .resolves(VALID_PUBLIC_KEYS_RESPONSE); + stubs.push(keyFetcherStub); + const mockIdToken = mocks.generateIdToken(); + + return verifier.verify(mockIdToken).should.eventually.be.fulfilled; + }); + + it('should be rejected given a token with an incorrect algorithm', () => { + const keyFetcherStub = sinon.stub(jwtUtil.UrlKeyFetcher.prototype, 'fetchPublicKeys') + .resolves(VALID_PUBLIC_KEYS_RESPONSE); + stubs.push(keyFetcherStub); + const mockIdToken = mocks.generateIdToken({ + algorithm: 'HS256', + }); + + return verifier.verify(mockIdToken).should.eventually.be + .rejectedWith('invalid algorithm') + .with.property('code', jwtUtil.JwtErrorCode.INVALID_SIGNATURE); + }); + + // tests to cover the private getKeyCallback function. + it('should reject when no matching kid found', () => { + const keyFetcherStub = sinon.stub(jwtUtil.UrlKeyFetcher.prototype, 'fetchPublicKeys') + .resolves({ 'not-a-matching-key': 'public-key' }); + stubs.push(keyFetcherStub); + const mockIdToken = mocks.generateIdToken(); + + return verifier.verify(mockIdToken).should.eventually.be + .rejectedWith('no-matching-kid-error') + .with.property('code', jwtUtil.JwtErrorCode.NO_MATCHING_KID); + }); + + it('should reject when an error occurs while fetching the keys', () => { + const keyFetcherStub = sinon.stub(jwtUtil.UrlKeyFetcher.prototype, 'fetchPublicKeys') + .rejects(new Error('Error fetching public keys.')); + stubs.push(keyFetcherStub); + const mockIdToken = mocks.generateIdToken(); + + return verifier.verify(mockIdToken).should.eventually.be + .rejectedWith('Error fetching public keys.') + .with.property('code', jwtUtil.JwtErrorCode.KEY_FETCH_ERROR); + }); + }); +}); + +describe('EmulatorSignatureVerifier', () => { + const emulatorVerifier = new jwtUtil.EmulatorSignatureVerifier(); + + describe('verify', () => { + it('should be fullfilled given a valid unsigned (emulator) token', () => { + const mockIdToken = mocks.generateIdToken({ + algorithm: 'none', + header: {} + }); + + return emulatorVerifier.verify(mockIdToken).should.eventually.be.fulfilled; + }); + + it('should be rejected given a valid signed (non-emulator) token', () => { + const mockIdToken = mocks.generateIdToken(); + + return emulatorVerifier.verify(mockIdToken).should.eventually.be.rejected; + }); + }); +}); + +describe('UrlKeyFetcher', () => { + const agent = new https.Agent(); + let keyFetcher: jwtUtil.UrlKeyFetcher; + let clock: sinon.SinonFakeTimers | undefined; + let httpsSpy: sinon.SinonSpy; + + beforeEach(() => { + keyFetcher = new jwtUtil.UrlKeyFetcher( + 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', + agent); + httpsSpy = sinon.spy(https, 'request'); + }); + + afterEach(() => { + if (clock) { + clock.restore(); + clock = undefined; + } + httpsSpy.restore(); + }); + + after(() => { + nock.cleanAll(); + }); + + describe('Constructor', () => { + it('should not throw when valid key parameters are provided', () => { + expect(() => { + new jwtUtil.UrlKeyFetcher('https://www.example.com/publicKeys', agent); + }).not.to.throw(); + }); + + const invalidCertURLs = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop, 'file://invalid']; + invalidCertURLs.forEach((invalidCertUrl) => { + it('should throw given a non-URL public cert: ' + JSON.stringify(invalidCertUrl), () => { + expect(() => { + new jwtUtil.UrlKeyFetcher(invalidCertUrl as any, agent); + }).to.throw('The provided public client certificate URL is not a valid URL.'); + }); + }); + }); + + describe('fetchPublicKeys', () => { + let mockedRequests: nock.Scope[] = []; + + afterEach(() => { + _.forEach(mockedRequests, (mockedRequest) => mockedRequest.done()); + mockedRequests = []; + }); + + it('should use the given HTTP Agent', () => { + const agent = new https.Agent(); + const urlKeyFetcher = new jwtUtil.UrlKeyFetcher('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', agent); + mockedRequests.push(mockFetchPublicKeys()); + + return urlKeyFetcher.fetchPublicKeys() + .then(() => { + expect(https.request).to.have.been.calledOnce; + expect(httpsSpy.args[0][0].agent).to.equal(agent); + }); + }); + + it('should not fetch the public keys until the first time fetchPublicKeys() is called', () => { + mockedRequests.push(mockFetchPublicKeys()); + + const urlKeyFetcher = new jwtUtil.UrlKeyFetcher('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', agent); + expect(https.request).not.to.have.been.called; + + return urlKeyFetcher.fetchPublicKeys() + .then(() => expect(https.request).to.have.been.calledOnce); + }); + + it('should not re-fetch the public keys every time fetchPublicKeys() is called', () => { + mockedRequests.push(mockFetchPublicKeys()); + + return keyFetcher.fetchPublicKeys().then(() => { + expect(https.request).to.have.been.calledOnce; + return keyFetcher.fetchPublicKeys(); + }).then(() => expect(https.request).to.have.been.calledOnce); + }); + + it('should refresh the public keys after the "max-age" on the request expires', () => { + mockedRequests.push(mockFetchPublicKeys()); + mockedRequests.push(mockFetchPublicKeys()); + mockedRequests.push(mockFetchPublicKeys()); + + clock = sinon.useFakeTimers(1000); + + return keyFetcher.fetchPublicKeys().then(() => { + expect(https.request).to.have.been.calledOnce; + clock!.tick(999); + return keyFetcher.fetchPublicKeys(); + }).then(() => { + expect(https.request).to.have.been.calledOnce; + clock!.tick(1); + return keyFetcher.fetchPublicKeys(); + }).then(() => { + // One second has passed + expect(https.request).to.have.been.calledTwice; + clock!.tick(999); + return keyFetcher.fetchPublicKeys(); + }).then(() => { + expect(https.request).to.have.been.calledTwice; + clock!.tick(1); + return keyFetcher.fetchPublicKeys(); + }).then(() => { + // Two seconds have passed + expect(https.request).to.have.been.calledThrice; + }); + }); + + it('should be rejected if fetching the public keys fails', () => { + mockedRequests.push(mockFailedFetchPublicKeys()); + + return keyFetcher.fetchPublicKeys() + .should.eventually.be.rejectedWith('message'); + }); + + it('should be rejected if fetching the public keys returns a response with an error message', () => { + mockedRequests.push(mockFetchPublicKeysWithErrorResponse()); + + return keyFetcher.fetchPublicKeys() + .should.eventually.be.rejectedWith('Error fetching public keys for Google certs: message (description)'); + }); + }); +}); From 02e2ea253c2259439f4de3ce8d033d2ac044e7d2 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Fri, 30 Apr 2021 12:56:45 -0700 Subject: [PATCH 27/41] chore: Updated API reference comments to tsdoc.org standards (#1242) * chore: Updated API reference comments to tsdoc.org standards * chore: Removed more old jsdoc syntax --- etc/firebase-admin.messaging.api.md | 1 - src/app/core.ts | 28 +-- src/app/credential-factory.ts | 44 ++-- src/app/credential.ts | 7 +- src/app/firebase-app.ts | 24 +- src/app/firebase-namespace.ts | 8 +- src/auth/action-code-settings-builder.ts | 2 +- src/auth/auth-api-request.ts | 206 +++++++++--------- src/auth/auth-config.ts | 46 ++-- src/auth/auth.ts | 6 +- src/auth/base-auth.ts | 128 ++++++----- src/auth/identifier.ts | 8 +- src/auth/tenant-manager.ts | 20 +- src/auth/tenant.ts | 12 +- src/auth/token-generator.ts | 10 +- src/auth/token-verifier.ts | 6 +- src/auth/user-import-builder.ts | 14 +- src/auth/user-record.ts | 38 ++-- src/credential/index.ts | 44 ++-- src/database/database-namespace.ts | 12 +- src/database/database.ts | 14 +- src/database/index.ts | 8 +- src/firestore/firestore-internal.ts | 2 +- src/instance-id/index.ts | 11 +- src/instance-id/instance-id-namespace.ts | 10 +- .../instance-id-request-internal.ts | 4 +- src/instance-id/instance-id.ts | 7 +- src/machine-learning/index.ts | 49 ++--- .../machine-learning-namespace.ts | 10 +- src/machine-learning/machine-learning.ts | 16 +- src/messaging/batch-request-internal.ts | 16 +- src/messaging/index.ts | 11 +- .../messaging-api-request-internal.ts | 12 +- src/messaging/messaging-api.ts | 150 ++++++------- src/messaging/messaging-errors-internal.ts | 12 +- src/messaging/messaging-internal.ts | 6 +- src/messaging/messaging-namespace.ts | 10 +- src/messaging/messaging.ts | 118 +++++----- src/project-management/android-app.ts | 15 +- src/project-management/index.ts | 11 +- src/project-management/ios-app.ts | 9 +- ...project-management-api-request-internal.ts | 24 +- .../project-management-namespace.ts | 12 +- src/project-management/project-management.ts | 21 +- src/remote-config/index.ts | 11 +- .../remote-config-api-client-internal.ts | 2 +- src/remote-config/remote-config-api.ts | 2 +- src/remote-config/remote-config-namespace.ts | 10 +- src/remote-config/remote-config.ts | 32 +-- src/security-rules/index.ts | 11 +- .../security-rules-namespace.ts | 2 +- src/security-rules/security-rules.ts | 51 ++--- src/storage/index.ts | 9 +- src/storage/storage-namespace.ts | 8 +- src/storage/storage.ts | 4 +- src/utils/api-request.ts | 46 ++-- src/utils/deep-copy.ts | 10 +- src/utils/error.ts | 62 +++--- src/utils/index.ts | 26 +-- src/utils/jwt.ts | 10 +- src/utils/validator.ts | 68 +++--- 61 files changed, 750 insertions(+), 836 deletions(-) diff --git a/etc/firebase-admin.messaging.api.md b/etc/firebase-admin.messaging.api.md index 361a44b70e..9b6416aa0d 100644 --- a/etc/firebase-admin.messaging.api.md +++ b/etc/firebase-admin.messaging.api.md @@ -180,7 +180,6 @@ export type Message = TokenMessage | TopicMessage | ConditionMessage; // @public export class Messaging { - constructor(app: App); get app(): App; send(message: Message, dryRun?: boolean): Promise; sendAll(messages: Message[], dryRun?: boolean): Promise; diff --git a/src/app/core.ts b/src/app/core.ts index a946e8ab41..9434ab6244 100644 --- a/src/app/core.ts +++ b/src/app/core.ts @@ -20,7 +20,7 @@ import { Agent } from 'http'; import { Credential } from './credential'; /** - * Available options to pass to [`initializeApp()`](admin#.initializeApp). + * Available options to pass to {@link firebase-admin.app#initializeApp}. */ export interface AppOptions { @@ -28,13 +28,13 @@ export interface AppOptions { * A {@link Credential `Credential`} object used to * authenticate the Admin SDK. * - * See [Initialize the SDK](/docs/admin/setup#initialize_the_sdk) for detailed - * documentation and code samples. + * See {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} + * for detailed documentation and code samples. */ credential?: Credential; /** - * The object to use as the [`auth`](/docs/reference/security/database/#auth) + * The object to use as the {@link https://firebase.google.com/docs/reference/security/database/#auth | auth} * variable in your Realtime Database Rules when the Admin SDK reads from or * writes to the Realtime Database. This allows you to downscope the Admin SDK * from its default full read and write privileges. @@ -42,7 +42,8 @@ export interface AppOptions { * You can pass `null` to act as an unauthenticated client. * * See - * [Authenticate with limited privileges](/docs/database/admin/start#authenticate-with-limited-privileges) + * {@link https://firebase.google.com/docs/database/admin/start#authenticate-with-limited-privileges | + * Authenticate with limited privileges} * for detailed documentation and code samples. */ databaseAuthVariableOverride?: object | null; @@ -71,7 +72,7 @@ export interface AppOptions { projectId?: string; /** - * An [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * An {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} * to be used when making outgoing HTTP calls. This Agent instance is used * by all services that make REST calls (e.g. `auth`, `messaging`, * `projectManagement`). @@ -87,12 +88,6 @@ export interface AppOptions { /** * A Firebase app holds the initialization information for a collection of * services. - * - * Do not call this constructor directly. Instead, use - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`} - * to create an app. */ export interface App { @@ -119,10 +114,7 @@ export interface App { /** * The (read-only) configuration options for this app. These are the original - * parameters given in - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. + * parameters given in {@link firebase-admin.app#initializeApp}. * * @example * ```javascript @@ -170,7 +162,9 @@ export interface FirebaseError { stack?: string; /** - * @return A JSON-serializable representation of this object. + * Returns a JSON-serializable object representation of this error. + * + * @returns A JSON-serializable representation of this object. */ toJSON(): object; } diff --git a/src/app/credential-factory.ts b/src/app/credential-factory.ts index 96f92821f8..4ad5ea5cf9 100644 --- a/src/app/credential-factory.ts +++ b/src/app/credential-factory.ts @@ -28,22 +28,16 @@ const globalRefreshTokenCreds: { [key: string]: RefreshTokenCredential } = {}; /** * Returns a credential created from the - * {@link - * https://developers.google.com/identity/protocols/application-default-credentials - * Google Application Default Credentials} + * {@link https://developers.google.com/identity/protocols/application-default-credentials | + * Google Application Default Credentials} * that grants admin access to Firebase services. This credential can be used - * in the call to - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. + * in the call to {@link firebase-admin.app#initializeApp}. * * Google Application Default Credentials are available on any Google * infrastructure, such as Google App Engine and Google Compute Engine. * * See - * {@link - * https://firebase.google.com/docs/admin/setup#initialize_the_sdk - * Initialize the SDK} + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} * for more details. * * @example @@ -54,10 +48,10 @@ const globalRefreshTokenCreds: { [key: string]: RefreshTokenCredential } = {}; * }); * ``` * - * @param httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} * to be used when retrieving access tokens from Google token servers. * - * @return A credential authenticated via Google + * @returns A credential authenticated via Google * Application Default Credentials that can be used to initialize an app. */ export function applicationDefault(httpAgent?: Agent): Credential { @@ -70,15 +64,10 @@ export function applicationDefault(httpAgent?: Agent): Credential { /** * Returns a credential created from the provided service account that grants * admin access to Firebase services. This credential can be used in the call - * to - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. + * to {@link firebase-admin.app#initializeApp}. * * See - * {@link - * https://firebase.google.com/docs/admin/setup#initialize_the_sdk - * Initialize the SDK} + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} * for more details. * * @example @@ -106,10 +95,10 @@ export function applicationDefault(httpAgent?: Agent): Credential { * * @param serviceAccountPathOrObject The path to a service * account key JSON file or an object representing a service account key. - * @param httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} * to be used when retrieving access tokens from Google token servers. * - * @return A credential authenticated via the + * @returns A credential authenticated via the * provided service account that can be used to initialize an app. */ export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAgent?: Agent): Credential { @@ -124,15 +113,10 @@ export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAg /** * Returns a credential created from the provided refresh token that grants * admin access to Firebase services. This credential can be used in the call - * to - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. + * to {@link firebase-admin.app#initializeApp}. * * See - * {@link - * https://firebase.google.com/docs/admin/setup#initialize_the_sdk - * Initialize the SDK} + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} * for more details. * * @example @@ -148,10 +132,10 @@ export function cert(serviceAccountPathOrObject: string | ServiceAccount, httpAg * @param refreshTokenPathOrObject The path to a Google * OAuth2 refresh token JSON file or an object representing a Google OAuth2 * refresh token. - * @param httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} * to be used when retrieving access tokens from Google token servers. * - * @return A credential authenticated via the + * @returns A credential authenticated via the * provided service account that can be used to initialize an app. */ export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential { diff --git a/src/app/credential.ts b/src/app/credential.ts index 82e3e1ccc1..2453a97242 100644 --- a/src/app/credential.ts +++ b/src/app/credential.ts @@ -42,12 +42,7 @@ export interface Credential { * Returns a Google OAuth2 access token object used to authenticate with * Firebase services. * - * This object contains the following properties: - * * `access_token` (`string`): The actual Google OAuth2 access token. - * * `expires_in` (`number`): The number of seconds from when the token was - * issued that it expires. - * - * @return A Google OAuth2 access token object. + * @returns A Google OAuth2 access token object. */ getAccessToken(): Promise; } \ No newline at end of file diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 2ccf7cd04a..62519fe9fe 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -192,7 +192,7 @@ export class FirebaseApp implements app.App { /** * Returns the Auth service instance associated with this app. * - * @return The Auth service instance of this app. + * @returns The Auth service instance of this app. */ public auth(): Auth { const fn = require('../auth/index').getAuth; @@ -202,7 +202,7 @@ export class FirebaseApp implements app.App { /** * Returns the Database service for the specified URL, and the current app. * - * @return The Database service instance of this app. + * @returns The Database service instance of this app. */ public database(url?: string): Database { const fn = require('../database/index').getDatabaseWithUrl; @@ -212,7 +212,7 @@ export class FirebaseApp implements app.App { /** * Returns the Messaging service instance associated with this app. * - * @return The Messaging service instance of this app. + * @returns The Messaging service instance of this app. */ public messaging(): Messaging { const fn = require('../messaging/index').getMessaging; @@ -222,7 +222,7 @@ export class FirebaseApp implements app.App { /** * Returns the Storage service instance associated with this app. * - * @return The Storage service instance of this app. + * @returns The Storage service instance of this app. */ public storage(): Storage { const fn = require('../storage/index').getStorage; @@ -237,7 +237,7 @@ export class FirebaseApp implements app.App { /** * Returns the InstanceId service instance associated with this app. * - * @return The InstanceId service instance of this app. + * @returns The InstanceId service instance of this app. */ public instanceId(): InstanceId { const fn = require('../instance-id/index').getInstanceId; @@ -247,7 +247,7 @@ export class FirebaseApp implements app.App { /** * Returns the MachineLearning service instance associated with this app. * - * @return The Machine Learning service instance of this app + * @returns The Machine Learning service instance of this app */ public machineLearning(): MachineLearning { const fn = require('../machine-learning/index').getMachineLearning; @@ -257,7 +257,7 @@ export class FirebaseApp implements app.App { /** * Returns the ProjectManagement service instance associated with this app. * - * @return The ProjectManagement service instance of this app. + * @returns The ProjectManagement service instance of this app. */ public projectManagement(): ProjectManagement { const fn = require('../project-management/index').getProjectManagement; @@ -267,7 +267,7 @@ export class FirebaseApp implements app.App { /** * Returns the SecurityRules service instance associated with this app. * - * @return The SecurityRules service instance of this app. + * @returns The SecurityRules service instance of this app. */ public securityRules(): SecurityRules { const fn = require('../security-rules/index').getSecurityRules; @@ -277,7 +277,7 @@ export class FirebaseApp implements app.App { /** * Returns the RemoteConfig service instance associated with this app. * - * @return The RemoteConfig service instance of this app. + * @returns The RemoteConfig service instance of this app. */ public remoteConfig(): RemoteConfig { const fn = require('../remote-config/index').getRemoteConfig; @@ -287,7 +287,7 @@ export class FirebaseApp implements app.App { /** * Returns the name of the FirebaseApp instance. * - * @return The name of the FirebaseApp instance. + * @returns The name of the FirebaseApp instance. */ get name(): string { this.checkDestroyed_(); @@ -297,7 +297,7 @@ export class FirebaseApp implements app.App { /** * Returns the options for the FirebaseApp instance. * - * @return The options for the FirebaseApp instance. + * @returns The options for the FirebaseApp instance. */ get options(): AppOptions { this.checkDestroyed_(); @@ -314,7 +314,7 @@ export class FirebaseApp implements app.App { /** * Deletes the FirebaseApp instance. * - * @return An empty Promise fulfilled once the FirebaseApp instance is deleted. + * @returns An empty Promise fulfilled once the FirebaseApp instance is deleted. */ public delete(): Promise { this.checkDestroyed_(); diff --git a/src/app/firebase-namespace.ts b/src/app/firebase-namespace.ts index f7c0280d53..c8af35247d 100644 --- a/src/app/firebase-namespace.ts +++ b/src/app/firebase-namespace.ts @@ -72,7 +72,7 @@ export class FirebaseNamespaceInternals { * to a file. * @param appName Optional name of the FirebaseApp instance. * - * @return A new App instance. + * @returns A new App instance. */ public initializeApp(options?: AppOptions, appName = DEFAULT_APP_NAME): App { if (typeof options === 'undefined') { @@ -114,7 +114,7 @@ export class FirebaseNamespaceInternals { * if no name is provided). * * @param appName Optional name of the FirebaseApp instance to return. - * @return The App instance which has the provided name. + * @returns The App instance which has the provided name. */ public app(appName = DEFAULT_APP_NAME): App { if (typeof appName !== 'string' || appName === '') { @@ -360,7 +360,7 @@ export class FirebaseNamespace { * otherwise it will be assumed to be pointing to a file. * @param appName Optional name of the FirebaseApp instance. * - * @return A new FirebaseApp instance. + * @returns A new FirebaseApp instance. */ public initializeApp(options?: AppOptions, appName?: string): App { return this.INTERNAL.initializeApp(options, appName); @@ -371,7 +371,7 @@ export class FirebaseNamespace { * if no name is provided). * * @param appName Optional name of the FirebaseApp instance to return. - * @return The FirebaseApp instance which has the provided name. + * @returns The FirebaseApp instance which has the provided name. */ public app(appName?: string): App { return this.INTERNAL.app(appName); diff --git a/src/auth/action-code-settings-builder.ts b/src/auth/action-code-settings-builder.ts index ec63e4ea63..4ebc4df19c 100644 --- a/src/auth/action-code-settings-builder.ts +++ b/src/auth/action-code-settings-builder.ts @@ -223,7 +223,7 @@ export class ActionCodeSettingsBuilder { * Returns the corresponding constructed server request corresponding to the * current ActionCodeSettings. * - * @return {EmailActionCodeRequest} The constructed EmailActionCodeRequest request. + * @returns The constructed EmailActionCodeRequest request. */ public buildRequest(): EmailActionCodeRequest { const request: { [key: string]: any } = { diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 86abb109e3..69ac909b4e 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -126,8 +126,8 @@ class AuthResourceUrlBuilder { /** * The resource URL builder constructor. * - * @param {string} projectId The resource project ID. - * @param {string} version The endpoint API version. + * @param projectId The resource project ID. + * @param version The endpoint API version. * @constructor */ constructor(protected app: App, protected version: string = 'v1') { @@ -143,10 +143,10 @@ class AuthResourceUrlBuilder { /** * Returns the resource URL corresponding to the provided parameters. * - * @param {string=} api The backend API name. - * @param {object=} params The optional additional parameters to substitute in the + * @param api The backend API name. + * @param params The optional additional parameters to substitute in the * URL path. - * @return {Promise} The corresponding resource URL. + * @returns The corresponding resource URL. */ public getUrl(api?: string, params?: object): Promise { return this.getProjectId() @@ -190,9 +190,9 @@ class TenantAwareAuthResourceUrlBuilder extends AuthResourceUrlBuilder { /** * The tenant aware resource URL builder constructor. * - * @param {string} projectId The resource project ID. - * @param {string} version The endpoint API version. - * @param {string} tenantId The tenant ID. + * @param projectId The resource project ID. + * @param version The endpoint API version. + * @param tenantId The tenant ID. * @constructor */ constructor(protected app: App, protected version: string, protected tenantId: string) { @@ -209,10 +209,10 @@ class TenantAwareAuthResourceUrlBuilder extends AuthResourceUrlBuilder { /** * Returns the resource URL corresponding to the provided parameters. * - * @param {string=} api The backend API name. - * @param {object=} params The optional additional parameters to substitute in the + * @param api The backend API name. + * @param params The optional additional parameters to substitute in the * URL path. - * @return {Promise} The corresponding resource URL. + * @returns The corresponding resource URL. */ public getUrl(api?: string, params?: object): Promise { return super.getUrl(api, params) @@ -309,7 +309,7 @@ function validateAuthFactorInfo(request: AuthFactorInfo, writeOperationType: Wri * are removed from the original request. If an invalid field is passed * an error is thrown. * - * @param {any} request The providerUserInfo request object. + * @param request The providerUserInfo request object. */ function validateProviderUserInfo(request: any): void { const validKeys = { @@ -1013,8 +1013,8 @@ export abstract class AbstractAuthRequestHandler { private projectConfigUrlBuilder: AuthResourceUrlBuilder; /** - * @param {any} response The response to check for errors. - * @return {string|null} The error code if present; null otherwise. + * @param response The response to check for errors. + * @returns The error code if present; null otherwise. */ private static getErrorCode(response: any): string | null { return (validator.isNonNullObject(response) && response.error && response.error.message) || null; @@ -1062,7 +1062,7 @@ export abstract class AbstractAuthRequestHandler { } /** - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param app The app used to fetch access tokens to sign API requests. * @constructor */ constructor(protected readonly app: App) { @@ -1081,10 +1081,10 @@ export abstract class AbstractAuthRequestHandler { * session management (set as a server side session cookie with custom cookie policy). * The session cookie JWT will have the same payload claims as the provided ID token. * - * @param {string} idToken The Firebase ID token to exchange for a session cookie. - * @param {number} expiresIn The session cookie duration in milliseconds. + * @param idToken The Firebase ID token to exchange for a session cookie. + * @param expiresIn The session cookie duration in milliseconds. * - * @return {Promise} A promise that resolves on success with the created session cookie. + * @returns A promise that resolves on success with the created session cookie. */ public createSessionCookie(idToken: string, expiresIn: number): Promise { const request = { @@ -1099,8 +1099,8 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up a user by uid. * - * @param {string} uid The uid of the user to lookup. - * @return {Promise} A promise that resolves with the user information. + * @param uid The uid of the user to lookup. + * @returns A promise that resolves with the user information. */ public getAccountInfoByUid(uid: string): Promise { if (!validator.isUid(uid)) { @@ -1116,8 +1116,8 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up a user by email. * - * @param {string} email The email of the user to lookup. - * @return {Promise} A promise that resolves with the user information. + * @param email The email of the user to lookup. + * @returns A promise that resolves with the user information. */ public getAccountInfoByEmail(email: string): Promise { if (!validator.isEmail(email)) { @@ -1133,8 +1133,8 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up a user by phone number. * - * @param {string} phoneNumber The phone number of the user to lookup. - * @return {Promise} A promise that resolves with the user information. + * @param phoneNumber The phone number of the user to lookup. + * @returns A promise that resolves with the user information. */ public getAccountInfoByPhoneNumber(phoneNumber: string): Promise { if (!validator.isPhoneNumber(phoneNumber)) { @@ -1165,9 +1165,9 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up multiple users by their identifiers (uid, email, etc). * - * @param {UserIdentifier[]} identifiers The identifiers indicating the users + * @param identifiers The identifiers indicating the users * to be looked up. Must have <= 100 entries. - * @param {Promise} A promise that resolves with the set of successfully + * @param A promise that resolves with the set of successfully * looked up users. Possibly empty if no users were looked up. */ public getAccountInfoByIdentifiers(identifiers: UserIdentifier[]): Promise { @@ -1204,12 +1204,12 @@ export abstract class AbstractAuthRequestHandler { * Exports the users (single batch only) with a size of maxResults and starting from * the offset as specified by pageToken. * - * @param {number=} maxResults The page size, 1000 if undefined. This is also the maximum + * @param maxResults The page size, 1000 if undefined. This is also the maximum * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns users starting + * @param pageToken The next page token. If not specified, returns users starting * without any offset. Users are returned in the order they were created from oldest to * newest, relative to the page token offset. - * @return {Promise} A promise that resolves with the current batch of downloaded + * @returns A promise that resolves with the current batch of downloaded * users and the next page token if available. For the last page, an empty list of users * and no page token are returned. */ @@ -1241,10 +1241,10 @@ export abstract class AbstractAuthRequestHandler { * At most, 1000 users are allowed to be imported one at a time. * When importing a list of password users, UserImportOptions are required to be specified. * - * @param {UserImportRecord[]} users The list of user records to import to Firebase Auth. - * @param {UserImportOptions=} options The user import options, required when the users provided + * @param users The list of user records to import to Firebase Auth. + * @param options The user import options, required when the users provided * include password credentials. - * @return {Promise} A promise that resolves when the operation completes + * @returns A promise that resolves when the operation completes * with the result of the import. This includes the number of successful imports, the number * of failed uploads and their corresponding errors. */ @@ -1283,8 +1283,8 @@ export abstract class AbstractAuthRequestHandler { /** * Deletes an account identified by a uid. * - * @param {string} uid The uid of the user to delete. - * @return {Promise} A promise that resolves when the user is deleted. + * @param uid The uid of the user to delete. + * @returns A promise that resolves when the user is deleted. */ public deleteAccount(uid: string): Promise { if (!validator.isUid(uid)) { @@ -1324,9 +1324,9 @@ export abstract class AbstractAuthRequestHandler { /** * Sets additional developer claims on an existing user identified by provided UID. * - * @param {string} uid The user to edit. - * @param {object} customUserClaims The developer claims to set. - * @return {Promise} A promise that resolves when the operation completes + * @param uid The user to edit. + * @param customUserClaims The developer claims to set. + * @returns A promise that resolves when the operation completes * with the user id that was edited. */ public setCustomUserClaims(uid: string, customUserClaims: object | null): Promise { @@ -1359,9 +1359,9 @@ export abstract class AbstractAuthRequestHandler { /** * Edits an existing user. * - * @param {string} uid The user to edit. - * @param {object} properties The properties to set on the user. - * @return {Promise} A promise that resolves when the operation completes + * @param uid The user to edit. + * @param properties The properties to set on the user. + * @returns A promise that resolves when the operation completes * with the user id that was edited. */ public updateExistingAccount(uid: string, properties: UpdateRequest): Promise { @@ -1503,8 +1503,8 @@ export abstract class AbstractAuthRequestHandler { * the same second as the revocation will still be valid. If there is a chance that a token * was minted in the last second, delay for 1 second before revoking. * - * @param {string} uid The user whose tokens are to be revoked. - * @return {Promise} A promise that resolves when the operation completes + * @param uid The user whose tokens are to be revoked. + * @returns A promise that resolves when the operation completes * successfully with the user id of the corresponding user. */ public revokeRefreshTokens(uid: string): Promise { @@ -1526,8 +1526,8 @@ export abstract class AbstractAuthRequestHandler { /** * Create a new user with the properties supplied. * - * @param {object} properties The properties to set on the user. - * @return {Promise} A promise that resolves when the operation completes + * @param properties The properties to set on the user. + * @returns A promise that resolves when the operation completes * with the user id that was created. */ public createNewAccount(properties: CreateRequest): Promise { @@ -1589,13 +1589,13 @@ export abstract class AbstractAuthRequestHandler { * Generates the out of band email action link for the email specified using the action code settings provided. * Returns a promise that resolves with the generated link. * - * @param {string} requestType The request type. This could be either used for password reset, + * @param requestType The request type. This could be either used for password reset, * email verification, email link sign-in. - * @param {string} email The email of the user the link is being sent to. - * @param {ActionCodeSettings=} actionCodeSettings The optional action code setings which defines whether + * @param email The email of the user the link is being sent to. + * @param actionCodeSettings The optional action code setings which defines whether * the link is to be handled by a mobile app and the additional state information to be passed in the * deep link, etc. Required when requestType == 'EMAIL_SIGNIN' - * @return {Promise} A promise that resolves with the email action link. + * @returns A promise that resolves with the email action link. */ public getEmailActionLink( requestType: string, email: string, @@ -1629,8 +1629,8 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up an OIDC provider configuration by provider ID. * - * @param {string} providerId The provider identifier of the configuration to lookup. - * @return {Promise} A promise that resolves with the provider configuration information. + * @param providerId The provider identifier of the configuration to lookup. + * @returns A promise that resolves with the provider configuration information. */ public getOAuthIdpConfig(providerId: string): Promise { if (!OIDCConfig.isProviderId(providerId)) { @@ -1643,12 +1643,12 @@ export abstract class AbstractAuthRequestHandler { * Lists the OIDC configurations (single batch only) with a size of maxResults and starting from * the offset as specified by pageToken. * - * @param {number=} maxResults The page size, 100 if undefined. This is also the maximum + * @param maxResults The page size, 100 if undefined. This is also the maximum * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns OIDC configurations + * @param pageToken The next page token. If not specified, returns OIDC configurations * without any offset. Configurations are returned in the order they were created from oldest to * newest, relative to the page token offset. - * @return {Promise} A promise that resolves with the current batch of downloaded + * @returns A promise that resolves with the current batch of downloaded * OIDC configurations and the next page token if available. For the last page, an empty list of provider * configuration and no page token are returned. */ @@ -1675,8 +1675,8 @@ export abstract class AbstractAuthRequestHandler { /** * Deletes an OIDC configuration identified by a providerId. * - * @param {string} providerId The identifier of the OIDC configuration to delete. - * @return {Promise} A promise that resolves when the OIDC provider is deleted. + * @param providerId The identifier of the OIDC configuration to delete. + * @returns A promise that resolves when the OIDC provider is deleted. */ public deleteOAuthIdpConfig(providerId: string): Promise { if (!OIDCConfig.isProviderId(providerId)) { @@ -1691,8 +1691,8 @@ export abstract class AbstractAuthRequestHandler { /** * Creates a new OIDC provider configuration with the properties provided. * - * @param {AuthProviderConfig} options The properties to set on the new OIDC provider configuration to be created. - * @return {Promise} A promise that resolves with the newly created OIDC + * @param options The properties to set on the new OIDC provider configuration to be created. + * @returns A promise that resolves with the newly created OIDC * configuration. */ public createOAuthIdpConfig(options: OIDCAuthProviderConfig): Promise { @@ -1719,9 +1719,9 @@ export abstract class AbstractAuthRequestHandler { /** * Updates an existing OIDC provider configuration with the properties provided. * - * @param {string} providerId The provider identifier of the OIDC configuration to update. - * @param {OIDCUpdateAuthProviderRequest} options The properties to update on the existing configuration. - * @return {Promise} A promise that resolves with the modified provider + * @param providerId The provider identifier of the OIDC configuration to update. + * @param options The properties to update on the existing configuration. + * @returns A promise that resolves with the modified provider * configuration. */ public updateOAuthIdpConfig( @@ -1752,8 +1752,8 @@ export abstract class AbstractAuthRequestHandler { /** * Looks up an SAML provider configuration by provider ID. * - * @param {string} providerId The provider identifier of the configuration to lookup. - * @return {Promise} A promise that resolves with the provider configuration information. + * @param providerId The provider identifier of the configuration to lookup. + * @returns A promise that resolves with the provider configuration information. */ public getInboundSamlConfig(providerId: string): Promise { if (!SAMLConfig.isProviderId(providerId)) { @@ -1766,12 +1766,12 @@ export abstract class AbstractAuthRequestHandler { * Lists the SAML configurations (single batch only) with a size of maxResults and starting from * the offset as specified by pageToken. * - * @param {number=} maxResults The page size, 100 if undefined. This is also the maximum + * @param maxResults The page size, 100 if undefined. This is also the maximum * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns SAML configurations starting + * @param pageToken The next page token. If not specified, returns SAML configurations starting * without any offset. Configurations are returned in the order they were created from oldest to * newest, relative to the page token offset. - * @return {Promise} A promise that resolves with the current batch of downloaded + * @returns A promise that resolves with the current batch of downloaded * SAML configurations and the next page token if available. For the last page, an empty list of provider * configuration and no page token are returned. */ @@ -1798,8 +1798,8 @@ export abstract class AbstractAuthRequestHandler { /** * Deletes a SAML configuration identified by a providerId. * - * @param {string} providerId The identifier of the SAML configuration to delete. - * @return {Promise} A promise that resolves when the SAML provider is deleted. + * @param providerId The identifier of the SAML configuration to delete. + * @returns A promise that resolves when the SAML provider is deleted. */ public deleteInboundSamlConfig(providerId: string): Promise { if (!SAMLConfig.isProviderId(providerId)) { @@ -1814,8 +1814,8 @@ export abstract class AbstractAuthRequestHandler { /** * Creates a new SAML provider configuration with the properties provided. * - * @param {AuthProviderConfig} options The properties to set on the new SAML provider configuration to be created. - * @return {Promise} A promise that resolves with the newly created SAML + * @param options The properties to set on the new SAML provider configuration to be created. + * @returns A promise that resolves with the newly created SAML * configuration. */ public createInboundSamlConfig(options: SAMLAuthProviderConfig): Promise { @@ -1842,9 +1842,9 @@ export abstract class AbstractAuthRequestHandler { /** * Updates an existing SAML provider configuration with the properties provided. * - * @param {string} providerId The provider identifier of the SAML configuration to update. - * @param {SAMLUpdateAuthProviderRequest} options The properties to update on the existing configuration. - * @return {Promise} A promise that resolves with the modified provider + * @param providerId The provider identifier of the SAML configuration to update. + * @param options The properties to update on the existing configuration. + * @returns A promise that resolves with the modified provider * configuration. */ public updateInboundSamlConfig( @@ -1875,11 +1875,11 @@ export abstract class AbstractAuthRequestHandler { /** * Invokes the request handler based on the API settings object passed. * - * @param {AuthResourceUrlBuilder} urlBuilder The URL builder for Auth endpoints. - * @param {ApiSettings} apiSettings The API endpoint settings to apply to request and response. - * @param {object} requestData The request data. - * @param {object=} additionalResourceParams Additional resource related params if needed. - * @return {Promise} A promise that resolves with the response. + * @param urlBuilder The URL builder for Auth endpoints. + * @param apiSettings The API endpoint settings to apply to request and response. + * @param requestData The request data. + * @param additionalResourceParams Additional resource related params if needed. + * @returns A promise that resolves with the response. */ protected invokeRequestHandler( urlBuilder: AuthResourceUrlBuilder, apiSettings: ApiSettings, @@ -1925,17 +1925,17 @@ export abstract class AbstractAuthRequestHandler { } /** - * @return {AuthResourceUrlBuilder} A new Auth user management resource URL builder instance. + * @returns A new Auth user management resource URL builder instance. */ protected abstract newAuthUrlBuilder(): AuthResourceUrlBuilder; /** - * @return {AuthResourceUrlBuilder} A new project config resource URL builder instance. + * @returns A new project config resource URL builder instance. */ protected abstract newProjectConfigUrlBuilder(): AuthResourceUrlBuilder; /** - * @return {AuthResourceUrlBuilder} The current Auth user management resource URL builder. + * @returns The current Auth user management resource URL builder. */ private getAuthUrlBuilder(): AuthResourceUrlBuilder { if (!this.authUrlBuilder) { @@ -1945,7 +1945,7 @@ export abstract class AbstractAuthRequestHandler { } /** - * @return {AuthResourceUrlBuilder} The current project config resource URL builder. + * @returns The current project config resource URL builder. */ private getProjectConfigUrlBuilder(): AuthResourceUrlBuilder { if (!this.projectConfigUrlBuilder) { @@ -2034,7 +2034,7 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { /** * The FirebaseAuthRequestHandler constructor used to initialize an instance using a FirebaseApp. * - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. + * @param app The app used to fetch access tokens to sign API requests. * @constructor. */ constructor(app: App) { @@ -2043,14 +2043,14 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { } /** - * @return {AuthResourceUrlBuilder} A new Auth user management resource URL builder instance. + * @returns A new Auth user management resource URL builder instance. */ protected newAuthUrlBuilder(): AuthResourceUrlBuilder { return new AuthResourceUrlBuilder(this.app, 'v1'); } /** - * @return {AuthResourceUrlBuilder} A new project config resource URL builder instance. + * @returns A new project config resource URL builder instance. */ protected newProjectConfigUrlBuilder(): AuthResourceUrlBuilder { return new AuthResourceUrlBuilder(this.app, 'v2'); @@ -2059,8 +2059,8 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { /** * Looks up a tenant by tenant ID. * - * @param {string} tenantId The tenant identifier of the tenant to lookup. - * @return {Promise} A promise that resolves with the tenant information. + * @param tenantId The tenant identifier of the tenant to lookup. + * @returns A promise that resolves with the tenant information. */ public getTenant(tenantId: string): Promise { if (!validator.isNonEmptyString(tenantId)) { @@ -2076,12 +2076,12 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { * Exports the tenants (single batch only) with a size of maxResults and starting from * the offset as specified by pageToken. * - * @param {number=} maxResults The page size, 1000 if undefined. This is also the maximum + * @param maxResults The page size, 1000 if undefined. This is also the maximum * allowed limit. - * @param {string=} pageToken The next page token. If not specified, returns tenants starting + * @param pageToken The next page token. If not specified, returns tenants starting * without any offset. Tenants are returned in the order they were created from oldest to * newest, relative to the page token offset. - * @return {Promise} A promise that resolves with the current batch of downloaded + * @returns A promise that resolves with the current batch of downloaded * tenants and the next page token if available. For the last page, an empty list of tenants * and no page token are returned. */ @@ -2109,8 +2109,8 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { /** * Deletes a tenant identified by a tenantId. * - * @param {string} tenantId The identifier of the tenant to delete. - * @return {Promise} A promise that resolves when the tenant is deleted. + * @param tenantId The identifier of the tenant to delete. + * @returns A promise that resolves when the tenant is deleted. */ public deleteTenant(tenantId: string): Promise { if (!validator.isNonEmptyString(tenantId)) { @@ -2125,8 +2125,8 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { /** * Creates a new tenant with the properties provided. * - * @param {TenantOptions} tenantOptions The properties to set on the new tenant to be created. - * @return {Promise} A promise that resolves with the newly created tenant object. + * @param tenantOptions The properties to set on the new tenant to be created. + * @returns A promise that resolves with the newly created tenant object. */ public createTenant(tenantOptions: CreateTenantRequest): Promise { try { @@ -2144,9 +2144,9 @@ export class AuthRequestHandler extends AbstractAuthRequestHandler { /** * Updates an existing tenant with the properties provided. * - * @param {string} tenantId The tenant identifier of the tenant to update. - * @param {TenantOptions} tenantOptions The properties to update on the existing tenant. - * @return {Promise} A promise that resolves with the modified tenant object. + * @param tenantId The tenant identifier of the tenant to update. + * @param tenantOptions The properties to update on the existing tenant. + * @returns A promise that resolves with the modified tenant object. */ public updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise { if (!validator.isNonEmptyString(tenantId)) { @@ -2179,8 +2179,8 @@ export class TenantAwareAuthRequestHandler extends AbstractAuthRequestHandler { * The FirebaseTenantRequestHandler constructor used to initialize an instance using a * FirebaseApp and a tenant ID. * - * @param {FirebaseApp} app The app used to fetch access tokens to sign API requests. - * @param {string} tenantId The request handler's tenant ID. + * @param app The app used to fetch access tokens to sign API requests. + * @param tenantId The request handler's tenant ID. * @constructor */ constructor(app: App, private readonly tenantId: string) { @@ -2188,14 +2188,14 @@ export class TenantAwareAuthRequestHandler extends AbstractAuthRequestHandler { } /** - * @return {AuthResourceUrlBuilder} A new Auth user management resource URL builder instance. + * @returns A new Auth user management resource URL builder instance. */ protected newAuthUrlBuilder(): AuthResourceUrlBuilder { return new TenantAwareAuthResourceUrlBuilder(this.app, 'v1', this.tenantId); } /** - * @return {AuthResourceUrlBuilder} A new project config resource URL builder instance. + * @returns A new project config resource URL builder instance. */ protected newProjectConfigUrlBuilder(): AuthResourceUrlBuilder { return new TenantAwareAuthResourceUrlBuilder(this.app, 'v2', this.tenantId); @@ -2210,10 +2210,10 @@ export class TenantAwareAuthRequestHandler extends AbstractAuthRequestHandler { * Overrides the superclass methods by adding an additional check to match tenant IDs of * imported user records if present. * - * @param {UserImportRecord[]} users The list of user records to import to Firebase Auth. - * @param {UserImportOptions=} options The user import options, required when the users provided + * @param users The list of user records to import to Firebase Auth. + * @param options The user import options, required when the users provided * include password credentials. - * @return {Promise} A promise that resolves when the operation completes + * @returns A promise that resolves when the operation completes * with the result of the import. This includes the number of successful imports, the number * of failed uploads and their corresponding errors. */ diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 793eb3addc..0c1fc2db36 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -495,7 +495,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { * Throws an error if validation fails. * * @param options The options object to convert to a server request. - * @return The resulting server request. + * @returns The resulting server request. * @internal */ public static buildServerRequest(options: MultiFactorConfig): MultiFactorAuthServerConfig { @@ -599,7 +599,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { }) } - /** @return The plain object representation of the multi-factor config instance. */ + /** @returns The plain object representation of the multi-factor config instance. */ public toJSON(): object { return { state: this.state, @@ -676,8 +676,8 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { * Static method to convert a client side request to a EmailSignInConfigServerRequest. * Throws an error if validation fails. * - * @param {any} options The options object to convert to a server request. - * @return {EmailSignInConfigServerRequest} The resulting server request. + * @param options The options object to convert to a server request. + * @returns The resulting server request. * @internal */ public static buildServerRequest(options: EmailSignInProviderConfig): EmailSignInConfigServerRequest { @@ -695,7 +695,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { /** * Validates the EmailSignInConfig options object. Throws an error on failure. * - * @param {any} options The options object to validate. + * @param options The options object to validate. */ private static validate(options: EmailSignInProviderConfig): void { // TODO: Validate the request. @@ -738,7 +738,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { /** * The EmailSignInConfig constructor. * - * @param {any} response The server side response used to initialize the + * @param response The server side response used to initialize the * EmailSignInConfig object. * @constructor */ @@ -752,7 +752,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { this.passwordRequired = !response.enableEmailLinkSignin; } - /** @return {object} The plain object representation of the email sign-in config. */ + /** @returns The plain object representation of the email sign-in config. */ public toJSON(): object { return { enabled: this.enabled, @@ -890,9 +890,9 @@ export class SAMLConfig implements SAMLAuthProviderConfig { * Throws an error if validation fails. If the request is not a SAMLConfig request, * returns null. * - * @param {SAMLAuthProviderRequest} options The options object to convert to a server request. - * @param {boolean=} ignoreMissingFields Whether to ignore missing fields. - * @return {?SAMLConfigServerRequest} The resulting server request or null if not valid. + * @param options The options object to convert to a server request. + * @param ignoreMissingFields Whether to ignore missing fields. + * @returns The resulting server request or null if not valid. */ public static buildServerRequest( options: Partial, @@ -934,8 +934,8 @@ export class SAMLConfig implements SAMLAuthProviderConfig { /** * Returns the provider ID corresponding to the resource name if available. * - * @param {string} resourceName The server side resource name. - * @return {?string} The provider ID corresponding to the resource, null otherwise. + * @param resourceName The server side resource name. + * @returns The provider ID corresponding to the resource, null otherwise. */ public static getProviderIdFromResourceName(resourceName: string): string | null { // name is of form projects/project1/inboundSamlConfigs/providerId1 @@ -947,8 +947,8 @@ export class SAMLConfig implements SAMLAuthProviderConfig { } /** - * @param {any} providerId The provider ID to check. - * @return {boolean} Whether the provider ID corresponds to a SAML provider. + * @param providerId The provider ID to check. + * @returns Whether the provider ID corresponds to a SAML provider. */ public static isProviderId(providerId: any): providerId is string { return validator.isNonEmptyString(providerId) && providerId.indexOf('saml.') === 0; @@ -957,8 +957,8 @@ export class SAMLConfig implements SAMLAuthProviderConfig { /** * Validates the SAMLConfig options object. Throws an error on failure. * - * @param {SAMLAuthProviderRequest} options The options object to validate. - * @param {boolean=} ignoreMissingFields Whether to ignore missing fields. + * @param options The options object to validate. + * @param ignoreMissingFields Whether to ignore missing fields. */ public static validate(options: Partial, ignoreMissingFields = false): void { const validKeys = { @@ -1117,7 +1117,7 @@ export class SAMLConfig implements SAMLAuthProviderConfig { this.displayName = response.displayName; } - /** @return The plain object representation of the SAMLConfig. */ + /** @returns The plain object representation of the SAMLConfig. */ public toJSON(): object { return { enabled: this.enabled, @@ -1154,7 +1154,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { * * @param options The options object to convert to a server request. * @param ignoreMissingFields Whether to ignore missing fields. - * @return The resulting server request or null if not valid. + * @returns The resulting server request or null if not valid. */ public static buildServerRequest( options: Partial, @@ -1177,8 +1177,8 @@ export class OIDCConfig implements OIDCAuthProviderConfig { /** * Returns the provider ID corresponding to the resource name if available. * - * @param {string} resourceName The server side resource name - * @return {?string} The provider ID corresponding to the resource, null otherwise. + * @param resourceName The server side resource name + * @returns The provider ID corresponding to the resource, null otherwise. */ public static getProviderIdFromResourceName(resourceName: string): string | null { // name is of form projects/project1/oauthIdpConfigs/providerId1 @@ -1190,8 +1190,8 @@ export class OIDCConfig implements OIDCAuthProviderConfig { } /** - * @param {any} providerId The provider ID to check. - * @return {boolean} Whether the provider ID corresponds to an OIDC provider. + * @param providerId The provider ID to check. + * @returns Whether the provider ID corresponds to an OIDC provider. */ public static isProviderId(providerId: any): providerId is string { return validator.isNonEmptyString(providerId) && providerId.indexOf('oidc.') === 0; @@ -1303,7 +1303,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { this.displayName = response.displayName; } - /** @return {OIDCAuthProviderConfig} The plain object representation of the OIDCConfig. */ + /** @returns The plain object representation of the OIDCConfig. */ public toJSON(): OIDCAuthProviderConfig { return { enabled: this.enabled, diff --git a/src/auth/auth.ts b/src/auth/auth.ts index 132631970f..4ad29f6d20 100644 --- a/src/auth/auth.ts +++ b/src/auth/auth.ts @@ -43,14 +43,16 @@ export class Auth extends BaseAuth { /** * Returns the app associated with this Auth instance. * - * @return The app associated with this Auth instance. + * @returns The app associated with this Auth instance. */ get app(): App { return this.app_; } /** - * @return The tenant manager instance associated with the current project. + * Returns the tenant manager instance associated with the current project. + * + * @returns The tenant manager instance associated with the current project. */ public tenantManager(): TenantManager { return this.tenantManager_; diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts index a68299237a..9f155b607c 100644 --- a/src/auth/base-auth.ts +++ b/src/auth/base-auth.ts @@ -37,7 +37,7 @@ import { import { UserImportOptions, UserImportRecord, UserImportResult } from './user-import-builder'; import { ActionCodeSettings } from './action-code-settings-builder'; -/** Represents the result of the {@link auth.Auth.getUsers} API. */ +/** Represents the result of the {@link BaseAuth.getUsers} API. */ export interface GetUsersResult { /** * Set of user records, corresponding to the set of users that were @@ -70,8 +70,7 @@ export interface ListUsersResult { } /** - * Represents the result of the - * {@link auth.Auth.deleteUsers `deleteUsers()`} + * Represents the result of the {@link BaseAuth.deleteUsers}. * API. */ export interface DeleteUsersResult { @@ -90,14 +89,14 @@ export interface DeleteUsersResult { /** * A list of `FirebaseArrayIndexError` instances describing the errors that * were encountered during the deletion. Length of this list is equal to - * the return value of [`failureCount`](#failureCount). + * the return value of {@link DeleteUsersResult.failureCount}. */ errors: FirebaseArrayIndexError[]; } /** * Interface representing the session cookie options needed for the - * {@link auth.Auth.createSessionCookie `createSessionCookie()`} method. + * {@link BaseAuth.createSessionCookie} method. */ export interface SessionCookieOptions { @@ -152,14 +151,14 @@ export abstract class BaseAuth { * methods. (Tenant-aware instances will also embed the tenant ID in the * token.) * - * See [Create Custom Tokens](/docs/auth/admin/create-custom-tokens) for code - * samples and detailed documentation. + * See {@link https://firebase.google.com/docs/auth/admin/create-custom-tokens | Create Custom Tokens} + * for code samples and detailed documentation. * * @param uid The `uid` to use as the custom token's subject. * @param developerClaims Optional additional claims to include * in the custom token's payload. * - * @return A promise fulfilled with a custom token for the + * @returns A promise fulfilled with a custom token for the * provided `uid` and payload. */ public createCustomToken(uid: string, developerClaims?: object): Promise { @@ -173,8 +172,8 @@ export abstract class BaseAuth { * An optional flag can be passed to additionally check whether the ID token * was revoked. * - * See [Verify ID Tokens](/docs/auth/admin/verify-id-tokens) for code samples - * and detailed documentation. + * See {@link https://firebase.google.com/docs/auth/admin/verify-id-tokens | Verify ID Tokens} + * for code samples and detailed documentation. * * @param idToken The ID token to verify. * @param checkRevoked Whether to check if the ID token was revoked. @@ -182,7 +181,7 @@ export abstract class BaseAuth { * the `tokensValidAfterTime` time for the corresponding user. * When not specified, this additional check is not applied. * - * @return A promise fulfilled with the + * @returns A promise fulfilled with the * token's decoded claims if the ID token is valid; otherwise, a rejected * promise. */ @@ -203,12 +202,12 @@ export abstract class BaseAuth { /** * Gets the user data for the user corresponding to a given `uid`. * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data | Retrieve user data} * for code samples and detailed documentation. * * @param uid The `uid` corresponding to the user whose data to fetch. * - * @return A promise fulfilled with the user + * @returns A promise fulfilled with the user * data corresponding to the provided `uid`. */ public getUser(uid: string): Promise { @@ -222,13 +221,13 @@ export abstract class BaseAuth { /** * Gets the user data for the user corresponding to a given email. * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data | Retrieve user data} * for code samples and detailed documentation. * * @param email The email corresponding to the user whose data to * fetch. * - * @return A promise fulfilled with the user + * @returns A promise fulfilled with the user * data corresponding to the provided email. */ public getUserByEmail(email: string): Promise { @@ -243,13 +242,13 @@ export abstract class BaseAuth { * Gets the user data for the user corresponding to a given phone number. The * phone number has to conform to the E.164 specification. * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data | Retrieve user data} * for code samples and detailed documentation. * * @param phoneNumber The phone number corresponding to the user whose * data to fetch. * - * @return A promise fulfilled with the user + * @returns A promise fulfilled with the user * data corresponding to the provided phone number. */ public getUserByPhoneNumber(phoneNumber: string): Promise { @@ -263,14 +262,14 @@ export abstract class BaseAuth { /** * Gets the user data for the user corresponding to a given provider id. * - * See [Retrieve user data](/docs/auth/admin/manage-users#retrieve_user_data) + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#retrieve_user_data | Retrieve user data} * for code samples and detailed documentation. * * @param providerId The provider ID, for example, "google.com" for the * Google provider. * @param uid The user identifier for the given provider. * - * @return A promise fulfilled with the user data corresponding to the + * @returns A promise fulfilled with the user data corresponding to the * given provider id. */ public getUserByProviderUid(providerId: string, uid: string): Promise { @@ -301,7 +300,7 @@ export abstract class BaseAuth { * * @param identifiers The identifiers used to indicate which user records should be returned. * Must have <= 100 entries. - * @return {Promise} A promise that resolves to the corresponding user records. + * @returns A promise that resolves to the corresponding user records. * @throws FirebaseAuthError If any of the identifiers are invalid or if more than 100 * identifiers are specified. */ @@ -350,14 +349,14 @@ export abstract class BaseAuth { * starting from the offset as specified by `pageToken`. This is used to * retrieve all the users of a specified project in batches. * - * See [List all users](/docs/auth/admin/manage-users#list_all_users) + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#list_all_users | List all users} * for code samples and detailed documentation. * * @param maxResults The page size, 1000 if undefined. This is also * the maximum allowed limit. * @param pageToken The next page token. If not specified, returns * users starting without any offset. - * @return A promise that resolves with + * @returns A promise that resolves with * the current batch of downloaded users and the next page token. */ public listUsers(maxResults?: number, pageToken?: string): Promise { @@ -385,13 +384,13 @@ export abstract class BaseAuth { /** * Creates a new user. * - * See [Create a user](/docs/auth/admin/manage-users#create_a_user) for code - * samples and detailed documentation. + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#create_a_user | Create a user} + * for code samples and detailed documentation. * * @param properties The properties to set on the * new user record to be created. * - * @return A promise fulfilled with the user + * @returns A promise fulfilled with the user * data corresponding to the newly created user. */ public createUser(properties: CreateRequest): Promise { @@ -414,12 +413,12 @@ export abstract class BaseAuth { /** * Deletes an existing user. * - * See [Delete a user](/docs/auth/admin/manage-users#delete_a_user) for code - * samples and detailed documentation. + * See {@link https://firebase.google.com/docs/auth/admin/manage-users#delete_a_user | Delete a user} + * for code samples and detailed documentation. * * @param uid The `uid` corresponding to the user to delete. * - * @return An empty promise fulfilled once the user has been + * @returns An empty promise fulfilled once the user has been * deleted. */ public deleteUser(uid: string): Promise { @@ -447,7 +446,7 @@ export abstract class BaseAuth { * * @param uids The `uids` corresponding to the users to delete. * - * @return A Promise that resolves to the total number of successful/failed + * @returns A Promise that resolves to the total number of successful/failed * deletions, as well as the array of errors that corresponds to the * failed deletions. */ @@ -498,14 +497,14 @@ export abstract class BaseAuth { /** * Updates an existing user. * - * See [Update a user](/docs/auth/admin/manage-users#update_a_user) for code - * samples and detailed documentation. + * See {@link https://firebsae.google.com/docs/auth/admin/manage-users#update_a_user | Update a user} + * for code samples and detailed documentation. * * @param uid The `uid` corresponding to the user to update. * @param properties The properties to update on * the provided user. * - * @return A promise fulfilled with the + * @returns A promise fulfilled with the * updated user data. */ public updateUser(uid: string, properties: UpdateRequest): Promise { @@ -569,8 +568,8 @@ export abstract class BaseAuth { * is used (sub, iat, iss, etc), an error is thrown. They are set on the * authenticated user's ID token JWT. * - * See - * [Defining user roles and access levels](/docs/auth/admin/custom-claims) + * See {@link https://firebase.google.com/docs/auth/admin/custom-claims | + * Defining user roles and access levels} * for code samples and detailed documentation. * * @param uid The `uid` of the user to edit. @@ -580,7 +579,7 @@ export abstract class BaseAuth { * user's ID token which is transmitted on every authenticated request. * For profile non-access related user attributes, use database or other * separate storage systems. - * @return A promise that resolves when the operation completes + * @returns A promise that resolves when the operation completes * successfully. */ public setCustomUserClaims(uid: string, customUserClaims: object | null): Promise { @@ -593,22 +592,20 @@ export abstract class BaseAuth { /** * Revokes all refresh tokens for an existing user. * - * This API will update the user's - * {@link auth.UserRecord.tokensValidAfterTime `tokensValidAfterTime`} to + * This API will update the user's {@link UserRecord.tokensValidAfterTime} to * the current UTC. It is important that the server on which this is called has * its clock set correctly and synchronized. * * While this will revoke all sessions for a specified user and disable any * new ID tokens for existing sessions from getting minted, existing ID tokens * may remain active until their natural expiration (one hour). To verify that - * ID tokens are revoked, use - * {@link auth.Auth.verifyIdToken `verifyIdToken(idToken, true)`} + * ID tokens are revoked, use {@link BaseAuth.verifyIdToken} * where `checkRevoked` is set to true. * * @param uid The `uid` corresponding to the user whose refresh tokens * are to be revoked. * - * @return An empty promise fulfilled once the user's refresh + * @returns An empty promise fulfilled once the user's refresh * tokens have been revoked. */ public revokeRefreshTokens(uid: string): Promise { @@ -622,7 +619,7 @@ export abstract class BaseAuth { * Imports the provided list of users into Firebase Auth. * A maximum of 1000 users are allowed to be imported one at a time. * When importing users with passwords, - * {@link auth.UserImportOptions `UserImportOptions`} are required to be + * {@link UserImportOptions} are required to be * specified. * This operation is optimized for bulk imports and will ignore checks on `uid`, * `email` and other identifier uniqueness which could result in duplications. @@ -630,7 +627,7 @@ export abstract class BaseAuth { * @param users The list of user records to import to Firebase Auth. * @param options The user import options, required when the users provided include * password credentials. - * @return A promise that resolves when + * @returns A promise that resolves when * the operation completes with the result of the import. This includes the * number of successful imports, the number of failed imports and their * corresponding errors. @@ -646,15 +643,15 @@ export abstract class BaseAuth { * policy, and be used for session management. The session cookie JWT will have * the same payload claims as the provided ID token. * - * See [Manage Session Cookies](/docs/auth/admin/manage-cookies) for code - * samples and detailed documentation. + * See {@link https://firebase.google.com/docs/auth/admin/manage-cookies | Manage Session Cookies} + * for code samples and detailed documentation. * * @param idToken The Firebase ID token to exchange for a session * cookie. * @param sessionCookieOptions The session * cookie options which includes custom session duration. * - * @return A promise that resolves on success with the + * @returns A promise that resolves on success with the * created session cookie. */ public createSessionCookie( @@ -676,7 +673,8 @@ export abstract class BaseAuth { * `auth/session-cookie-revoked` error is thrown. If not specified the check is * not performed. * - * See [Verify Session Cookies](/docs/auth/admin/manage-cookies#verify_session_cookie_and_check_permissions) + * See {@link https://firebase.google.com/docs/auth/admin/manage-cookies#verify_session_cookie_and_check_permissions | + * Verify Session Cookies} * for code samples and detailed documentation * * @param sessionCookie The session cookie to verify. @@ -685,7 +683,7 @@ export abstract class BaseAuth { * check the `tokensValidAfterTime` time for the corresponding user. * When not specified, this additional check is not performed. * - * @return A promise fulfilled with the + * @returns A promise fulfilled with the * session cookie's decoded claims if the session cookie is valid; otherwise, * a rejected promise. */ @@ -707,7 +705,7 @@ export abstract class BaseAuth { /** * Generates the out of band email action link to reset a user's password. * The link is generated for the user with the specified email address. The - * optional {@link auth.ActionCodeSettings `ActionCodeSettings`} object + * optional {@link ActionCodeSettings} object * defines whether the link is to be handled by a mobile app or browser and the * additional state information to be passed in the deep link, etc. * @@ -751,7 +749,7 @@ export abstract class BaseAuth { * and accepts the Firebase Dynamic Links terms of service. * The Android package name and iOS bundle ID are respected only if they * are configured in the same Firebase Auth project. - * @return A promise that resolves with the generated link. + * @returns A promise that resolves with the generated link. */ public generatePasswordResetLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { return this.authRequestHandler.getEmailActionLink('PASSWORD_RESET', email, actionCodeSettings); @@ -759,8 +757,7 @@ export abstract class BaseAuth { /** * Generates the out of band email action link to verify the user's ownership - * of the specified email. The - * {@link auth.ActionCodeSettings `ActionCodeSettings`} object provided + * of the specified email. The {@link ActionCodeSettings} object provided * as an argument to this method defines whether the link is to be handled by a * mobile app or browser along with additional state information to be passed in * the deep link, etc. @@ -804,7 +801,7 @@ export abstract class BaseAuth { * and accepts the Firebase Dynamic Links terms of service. * The Android package name and iOS bundle ID are respected only if they * are configured in the same Firebase Auth project. - * @return A promise that resolves with the generated link. + * @returns A promise that resolves with the generated link. */ public generateEmailVerificationLink(email: string, actionCodeSettings?: ActionCodeSettings): Promise { return this.authRequestHandler.getEmailActionLink('VERIFY_EMAIL', email, actionCodeSettings); @@ -812,8 +809,7 @@ export abstract class BaseAuth { /** * Generates the out of band email action link to verify the user's ownership - * of the specified email. The - * {@link auth.ActionCodeSettings `ActionCodeSettings`} object provided + * of the specified email. The {@link ActionCodeSettings} object provided * as an argument to this method defines whether the link is to be handled by a * mobile app or browser along with additional state information to be passed in * the deep link, etc. @@ -857,7 +853,7 @@ export abstract class BaseAuth { * and accepts the Firebase Dynamic Links terms of service. * The Android package name and iOS bundle ID are respected only if they * are configured in the same Firebase Auth project. - * @return A promise that resolves with the generated link. + * @returns A promise that resolves with the generated link. */ public generateSignInWithEmailLink(email: string, actionCodeSettings: ActionCodeSettings): Promise { return this.authRequestHandler.getEmailActionLink('EMAIL_SIGNIN', email, actionCodeSettings); @@ -869,10 +865,10 @@ export abstract class BaseAuth { * * SAML and OIDC provider support requires Google Cloud's Identity Platform * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. * * @param options The provider config filter to apply. - * @return A promise that resolves with the list of provider configs meeting the + * @returns A promise that resolves with the list of provider configs meeting the * filter requirements. */ public listProviderConfigs(options: AuthProviderConfigFilter): Promise { @@ -926,11 +922,11 @@ export abstract class BaseAuth { * * SAML and OIDC provider support requires Google Cloud's Identity Platform * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. * * @param providerId The provider ID corresponding to the provider * config to return. - * @return A promise that resolves + * @returns A promise that resolves * with the configuration corresponding to the provided ID. */ public getProviderConfig(providerId: string): Promise { @@ -955,11 +951,11 @@ export abstract class BaseAuth { * * SAML and OIDC provider support requires Google Cloud's Identity Platform * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. * * @param providerId The provider ID corresponding to the provider * config to delete. - * @return A promise that resolves on completion. + * @returns A promise that resolves on completion. */ public deleteProviderConfig(providerId: string): Promise { if (OIDCConfig.isProviderId(providerId)) { @@ -978,12 +974,12 @@ export abstract class BaseAuth { * * SAML and OIDC provider support requires Google Cloud's Identity Platform * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. * * @param providerId The provider ID corresponding to the provider * config to update. * @param updatedConfig The updated configuration. - * @return A promise that resolves with the updated provider configuration. + * @returns A promise that resolves with the updated provider configuration. */ public updateProviderConfig( providerId: string, updatedConfig: UpdateAuthProviderRequest): Promise { @@ -1013,10 +1009,10 @@ export abstract class BaseAuth { * * SAML and OIDC provider support requires Google Cloud's Identity Platform * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform). + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. * * @param config The provider configuration to create. - * @return A promise that resolves with the created provider configuration. + * @returns A promise that resolves with the created provider configuration. */ public createProviderConfig(config: AuthProviderConfig): Promise { if (!validator.isNonNullObject(config)) { @@ -1046,7 +1042,7 @@ export abstract class BaseAuth { * @param decodedIdToken The JWT's decoded claims. * @param revocationErrorInfo The revocation error info to throw on revocation * detection. - * @return A Promise that will be fulfilled after a successful verification. + * @returns A Promise that will be fulfilled after a successful verification. */ private verifyDecodedJWTNotRevoked( decodedIdToken: DecodedIdToken, revocationErrorInfo: ErrorInfo): Promise { diff --git a/src/auth/identifier.ts b/src/auth/identifier.ts index 8db37c36b4..8bae483d1d 100644 --- a/src/auth/identifier.ts +++ b/src/auth/identifier.ts @@ -17,7 +17,7 @@ /** * Used for looking up an account by uid. * - * See `auth.getUsers()` + * See {@link BaseAuth.getUsers}. */ export interface UidIdentifier { uid: string; @@ -26,7 +26,7 @@ export interface UidIdentifier { /** * Used for looking up an account by email. * - * See `auth.getUsers()` + * See {@link BaseAuth.getUsers}. */ export interface EmailIdentifier { email: string; @@ -35,7 +35,7 @@ export interface EmailIdentifier { /** * Used for looking up an account by phone number. * - * See `auth.getUsers()` + * See {@link BaseAuth.getUsers}. */ export interface PhoneIdentifier { phoneNumber: string; @@ -44,7 +44,7 @@ export interface PhoneIdentifier { /** * Used for looking up an account by federated provider. * - * See `auth.getUsers()` + * See {@link BaseAuth.getUsers}. */ export interface ProviderIdentifier { providerId: string; diff --git a/src/auth/tenant-manager.ts b/src/auth/tenant-manager.ts index 2878ed907c..d3992fc2a1 100644 --- a/src/auth/tenant-manager.ts +++ b/src/auth/tenant-manager.ts @@ -29,14 +29,14 @@ import { DecodedIdToken } from './token-verifier'; /** * Interface representing the object returned from a - * {@link auth.TenantManager.listTenants `listTenants()`} + * {@link TenantManager.listTenants} * operation. * Contains the list of tenants for the current batch and the next page token if available. */ export interface ListTenantsResult { /** - * The list of {@link auth.Tenant `Tenant`} objects for the downloaded batch. + * The list of {@link Tenant} objects for the downloaded batch. */ tenants: Tenant[]; @@ -52,7 +52,7 @@ export interface ListTenantsResult { * * Multi-tenancy support requires Google Cloud's Identity Platform * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform) + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. * * Each tenant contains its own identity providers, settings and sets of users. * Using `TenantAwareAuth`, users for a specific tenant and corresponding OIDC/SAML @@ -61,7 +61,7 @@ export interface ListTenantsResult { * tenant. * * `TenantAwareAuth` instances for a specific `tenantId` can be instantiated by calling - * `auth.tenantManager().authForTenant(tenantId)`. + * {@link TenantManager.authForTenant}. */ export class TenantAwareAuth extends BaseAuth { @@ -169,7 +169,7 @@ export class TenantManager { * * @param tenantId The tenant ID whose `TenantAwareAuth` instance is to be returned. * - * @return The `TenantAwareAuth` instance corresponding to this tenant identifier. + * @returns The `TenantAwareAuth` instance corresponding to this tenant identifier. */ public authForTenant(tenantId: string): TenantAwareAuth { if (!validator.isNonEmptyString(tenantId)) { @@ -186,7 +186,7 @@ export class TenantManager { * * @param tenantId The tenant identifier corresponding to the tenant whose data to fetch. * - * @return A promise fulfilled with the tenant configuration to the provided `tenantId`. + * @returns A promise fulfilled with the tenant configuration to the provided `tenantId`. */ public getTenant(tenantId: string): Promise { return this.authRequestHandler.getTenant(tenantId) @@ -205,7 +205,7 @@ export class TenantManager { * @param pageToken The next page token. If not specified, returns * tenants starting without any offset. * - * @return A promise that resolves with + * @returns A promise that resolves with * a batch of downloaded tenants and the next page token. */ public listTenants( @@ -237,7 +237,7 @@ export class TenantManager { * * @param tenantId The `tenantId` corresponding to the tenant to delete. * - * @return An empty promise fulfilled once the tenant has been deleted. + * @returns An empty promise fulfilled once the tenant has been deleted. */ public deleteTenant(tenantId: string): Promise { return this.authRequestHandler.deleteTenant(tenantId); @@ -250,7 +250,7 @@ export class TenantManager { * * @param tenantOptions The properties to set on the new tenant configuration to be created. * - * @return A promise fulfilled with the tenant configuration corresponding to the newly + * @returns A promise fulfilled with the tenant configuration corresponding to the newly * created tenant. */ public createTenant(tenantOptions: CreateTenantRequest): Promise { @@ -266,7 +266,7 @@ export class TenantManager { * @param tenantId The `tenantId` corresponding to the tenant to delete. * @param tenantOptions The properties to update on the provided tenant. * - * @return A promise fulfilled with the update tenant data. + * @returns A promise fulfilled with the update tenant data. */ public updateTenant(tenantId: string, tenantOptions: UpdateTenantRequest): Promise { return this.authRequestHandler.updateTenant(tenantId, tenantOptions) diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index c6359a1e85..6c5577bda2 100644 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -86,7 +86,7 @@ export interface TenantServerResponse { * * Multi-tenancy support requires Google Cloud's Identity Platform * (GCIP). To learn more about GCIP, including pricing and features, - * see the [GCIP documentation](https://cloud.google.com/identity-platform) + * see the {@link https://cloud.google.com/identity-platform | GCIP documentation}. * * Before multi-tenancy can be used on a Google Cloud Identity Platform project, * tenants must be allowed on that project via the Cloud Console UI. @@ -128,7 +128,7 @@ export class Tenant { * * @param tenantOptions The properties to convert to a server request. * @param createRequest Whether this is a create request. - * @return The equivalent server request. + * @returns The equivalent server request. * * @internal */ @@ -158,8 +158,8 @@ export class Tenant { /** * Returns the tenant ID corresponding to the resource name if available. * - * @param {string} resourceName The server side resource name - * @return {?string} The tenant ID corresponding to the resource, null otherwise. + * @param resourceName The server side resource name + * @returns The tenant ID corresponding to the resource, null otherwise. * * @internal */ @@ -282,7 +282,9 @@ export class Tenant { } /** - * @return A JSON-serializable representation of this object. + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. */ public toJSON(): object { const json = { diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index a9c03130c6..d8e6e1962d 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -54,14 +54,14 @@ export interface CryptoSigner { * Cryptographically signs a buffer of data. * * @param {Buffer} buffer The data to be signed. - * @return {Promise} A promise that resolves with the raw bytes of a signature. + * @returns {Promise} A promise that resolves with the raw bytes of a signature. */ sign(buffer: Buffer): Promise; /** * Returns the ID of the service account used to sign tokens. * - * @return {Promise} A promise that resolves with a service account ID. + * @returns {Promise} A promise that resolves with a service account ID. */ getAccountId(): Promise; } @@ -259,7 +259,7 @@ export class EmulatedSigner implements CryptoSigner { * account credential, creates a ServiceAccountSigner. Otherwise creates an IAMSigner. * * @param {FirebaseApp} app A FirebaseApp instance. - * @return {CryptoSigner} A CryptoSigner instance. + * @returns {CryptoSigner} A CryptoSigner instance. */ export function cryptoSignerFromApp(app: App): CryptoSigner { const credential = app.options.credential; @@ -303,7 +303,7 @@ export class FirebaseTokenGenerator { * @param uid The user ID to use for the generated Firebase Auth Custom token. * @param developerClaims Optional developer claims to include in the generated Firebase * Auth Custom token. - * @return A Promise fulfilled with a Firebase Auth Custom token signed with a + * @returns A Promise fulfilled with a Firebase Auth Custom token signed with a * service account key and containing the provided payload. */ public createCustomToken(uid: string, developerClaims?: {[key: string]: any}): Promise { @@ -374,7 +374,7 @@ export class FirebaseTokenGenerator { * Returns whether or not the provided developer claims are valid. * * @param {object} [developerClaims] Optional developer claims to validate. - * @return {boolean} True if the provided claims are valid; otherwise, false. + * @returns {boolean} True if the provided claims are valid; otherwise, false. */ private isDeveloperClaimsValid_(developerClaims?: object): boolean { if (typeof developerClaims === 'undefined') { diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index b07ad29c14..4a88420f5a 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -299,7 +299,7 @@ export class FirebaseTokenVerifier { * * @param jwtToken The Firebase Auth JWT token to verify. * @param isEmulator Whether to accept Auth Emulator tokens. - * @return A promise fulfilled with the decoded claims of the Firebase Auth ID token. + * @returns A promise fulfilled with the decoded claims of the Firebase Auth ID token. */ public verifyJWT(jwtToken: string, isEmulator = false): Promise { if (!validator.isString(jwtToken)) { @@ -460,7 +460,7 @@ export class FirebaseTokenVerifier { * * @internal * @param app Firebase app instance. - * @return FirebaseTokenVerifier + * @returns FirebaseTokenVerifier */ export function createIdTokenVerifier(app: App): FirebaseTokenVerifier { return new FirebaseTokenVerifier( @@ -476,7 +476,7 @@ export function createIdTokenVerifier(app: App): FirebaseTokenVerifier { * * @internal * @param app Firebase app instance. - * @return FirebaseTokenVerifier + * @returns FirebaseTokenVerifier */ export function createSessionCookieVerifier(app: App): FirebaseTokenVerifier { return new FirebaseTokenVerifier( diff --git a/src/auth/user-import-builder.ts b/src/auth/user-import-builder.ts index c0e151abaa..811e7c9963 100644 --- a/src/auth/user-import-builder.ts +++ b/src/auth/user-import-builder.ts @@ -319,7 +319,7 @@ export type ValidatorFunction = (data: UploadAccountUser) => void; /** * Converts a client format second factor object to server format. * @param multiFactorInfo The client format second factor. - * @return The corresponding AuthFactorInfo server request format. + * @returns The corresponding AuthFactorInfo server request format. */ export function convertMultiFactorInfoToServerFormat(multiFactorInfo: UpdateMultiFactorInfoRequest): AuthFactorInfo { let enrolledAt; @@ -366,7 +366,7 @@ function isPhoneFactor(multiFactorInfo: UpdateMultiFactorInfoRequest): /** * @param {any} obj The object to check for number field within. * @param {string} key The entry key. - * @return {number} The corresponding number if available. Otherwise, NaN. + * @returns {number} The corresponding number if available. Otherwise, NaN. */ function getNumberField(obj: any, key: string): number { if (typeof obj[key] !== 'undefined' && obj[key] !== null) { @@ -381,7 +381,7 @@ function getNumberField(obj: any, key: string): number { * fields are provided. * @param {UserImportRecord} user The UserImportRecord to conver to UploadAccountUser. * @param {ValidatorFunction=} userValidator The user validator function. - * @return {UploadAccountUser} The corresponding UploadAccountUser to return. + * @returns {UploadAccountUser} The corresponding UploadAccountUser to return. */ function populateUploadAccountUser( user: UserImportRecord, userValidator?: ValidatorFunction): UploadAccountUser { @@ -497,7 +497,7 @@ export class UserImportBuilder { /** * Returns the corresponding constructed uploadAccount request. - * @return {UploadAccountRequest} The constructed uploadAccount request. + * @returns {UploadAccountRequest} The constructed uploadAccount request. */ public buildRequest(): UploadAccountRequest { const users = this.validatedUsers.map((user) => { @@ -509,7 +509,7 @@ export class UserImportBuilder { /** * Populates the UserImportResult using the client side detected errors and the server * side returned errors. - * @return {UserImportResult} The user import result based on the returned failed + * @returns {UserImportResult} The user import result based on the returned failed * uploadAccount response. */ public buildResponse( @@ -545,7 +545,7 @@ export class UserImportBuilder { * Throws an error whenever an invalid or missing options is detected. * @param {UserImportOptions} options The UserImportOptions. * @param {boolean} requiresHashOptions Whether to require hash options. - * @return {UploadAccountOptions} The populated UploadAccount options. + * @returns {UploadAccountOptions} The populated UploadAccount options. */ private populateOptions( options: UserImportOptions | undefined, requiresHashOptions: boolean): UploadAccountOptions { @@ -733,7 +733,7 @@ export class UserImportBuilder { * @param {UserImportRecord[]} users The UserImportRecords to convert to UnploadAccountUser * objects. * @param {ValidatorFunction=} userValidator The user validator function. - * @return {UploadAccountUser[]} The populated uploadAccount users. + * @returns {UploadAccountUser[]} The populated uploadAccount users. */ private populateUsers( users: UserImportRecord[], userValidator?: ValidatorFunction): UploadAccountUser[] { diff --git a/src/auth/user-record.ts b/src/auth/user-record.ts index da55540b56..49c21b4768 100644 --- a/src/auth/user-record.ts +++ b/src/auth/user-record.ts @@ -28,8 +28,8 @@ const B64_REDACTED = Buffer.from('REDACTED').toString('base64'); /** * Parses a time stamp string or number and returns the corresponding date if valid. * - * @param {any} time The unix timestamp string or number in milliseconds. - * @return {string} The corresponding date as a UTC string, if valid. Otherwise, null. + * @param time The unix timestamp string or number in milliseconds. + * @returns The corresponding date as a UTC string, if valid. Otherwise, null. */ function parseDate(time: any): string | null { try { @@ -141,7 +141,9 @@ export abstract class MultiFactorInfo { } /** - * @return A JSON-serializable representation of this object. + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. */ public toJSON(): object { return { @@ -156,7 +158,7 @@ export abstract class MultiFactorInfo { * Returns the factor ID based on the response provided. * * @param response The server side response. - * @return The multi-factor ID associated with the provided response. If the response is + * @returns The multi-factor ID associated with the provided response. If the response is * not associated with any known multi-factor ID, null is returned. * * @internal @@ -228,7 +230,7 @@ export class PhoneMultiFactorInfo extends MultiFactorInfo { * Returns the factor ID based on the response provided. * * @param response The server side response. - * @return The multi-factor ID associated with the provided response. If the response is + * @returns The multi-factor ID associated with the provided response. If the response is * not associated with any known multi-factor ID, null is returned. * * @internal @@ -276,7 +278,9 @@ export class MultiFactorSettings { } /** - * @return A JSON-serializable representation of this multi-factor object. + * Returns a JSON-serializable representation of this multi-factor object. + * + * @returns A JSON-serializable representation of this multi-factor object. */ public toJSON(): object { return { @@ -325,7 +329,9 @@ export class UserMetadata { } /** - * @return A JSON-serializable representation of this object. + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. */ public toJSON(): object { return { @@ -395,7 +401,9 @@ export class UserInfo { } /** - * @return A JSON-serializable representation of this object. + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. */ public toJSON(): object { return { @@ -466,7 +474,7 @@ export class UserRecord { * when uploading this user, as is typical when migrating from another Auth * system, this will be an empty string. If no password is set, this is * null. This is only available when the user is obtained from - * {@link auth.Auth.listUsers `listUsers()`}. + * {@link BaseAuth.listUsers}. */ public readonly passwordHash?: string; @@ -475,16 +483,14 @@ export class UserRecord { * algorithm (SCRYPT) is used. If a different hashing algorithm had been used to * upload this user, typical when migrating from another Auth system, this will * be an empty string. If no password is set, this is null. This is only - * available when the user is obtained from - * {@link auth.Auth.listUsers `listUsers()`}. + * available when the user is obtained from {@link BaseAuth.listUsers}. */ public readonly passwordSalt?: string; /** * The user's custom claims object if available, typically used to define * user roles and propagated to an authenticated user's ID token. - * This is set via - * {@link auth.Auth.setCustomUserClaims `setCustomUserClaims()`} + * This is set via {@link BaseAuth.setCustomUserClaims} */ public readonly customClaims?: {[key: string]: any}; @@ -496,7 +502,7 @@ export class UserRecord { /** * The date the user's tokens are valid after, formatted as a UTC string. * This is updated every time the user's refresh token are revoked either - * from the {@link auth.Auth.revokeRefreshTokens `revokeRefreshTokens()`} + * from the {@link BaseAuth.revokeRefreshTokens} * API or from the Firebase Auth backend on big account changes (password * resets, password or email updates, etc). */ @@ -565,7 +571,9 @@ export class UserRecord { } /** - * @return A JSON-serializable representation of this object. + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. */ public toJSON(): object { const json: any = { diff --git a/src/credential/index.ts b/src/credential/index.ts index fd20d0ab9f..7cc49758e8 100644 --- a/src/credential/index.ts +++ b/src/credential/index.ts @@ -37,22 +37,16 @@ export namespace credential { /** * Returns a credential created from the - * {@link - * https://developers.google.com/identity/protocols/application-default-credentials - * Google Application Default Credentials} + * {@link https://developers.google.com/identity/protocols/application-default-credentials | + * Google Application Default Credentials} * that grants admin access to Firebase services. This credential can be used - * in the call to - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. + * in the call to {@link firebase-admin.app#initializeApp}. * * Google Application Default Credentials are available on any Google * infrastructure, such as Google App Engine and Google Compute Engine. * * See - * {@link - * https://firebase.google.com/docs/admin/setup#initialize_the_sdk - * Initialize the SDK} + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} * for more details. * * @example @@ -63,10 +57,10 @@ export namespace credential { * }); * ``` * - * @param {!Object=} httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} * to be used when retrieving access tokens from Google token servers. * - * @return {!admin.credential.Credential} A credential authenticated via Google + * @returns A credential authenticated via Google * Application Default Credentials that can be used to initialize an app. */ export const applicationDefault = applicationDefaultFn; @@ -74,15 +68,10 @@ export namespace credential { /** * Returns a credential created from the provided service account that grants * admin access to Firebase services. This credential can be used in the call - * to - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. + * to {@link firebase-admin.app#initializeApp}. * * See - * {@link - * https://firebase.google.com/docs/admin/setup#initialize_the_sdk - * Initialize the SDK} + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} * for more details. * * @example @@ -110,10 +99,10 @@ export namespace credential { * * @param serviceAccountPathOrObject The path to a service * account key JSON file or an object representing a service account key. - * @param httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} * to be used when retrieving access tokens from Google token servers. * - * @return A credential authenticated via the + * @returns A credential authenticated via the * provided service account that can be used to initialize an app. */ export const cert = certFn; @@ -121,15 +110,10 @@ export namespace credential { /** * Returns a credential created from the provided refresh token that grants * admin access to Firebase services. This credential can be used in the call - * to - * {@link - * https://firebase.google.com/docs/reference/admin/node/admin#.initializeApp - * `admin.initializeApp()`}. + * to {@link firebase-admin.app#initializeApp}. * * See - * {@link - * https://firebase.google.com/docs/admin/setup#initialize_the_sdk - * Initialize the SDK} + * {@link https://firebase.google.com/docs/admin/setup#initialize_the_sdk | Initialize the SDK} * for more details. * * @example @@ -145,10 +129,10 @@ export namespace credential { * @param refreshTokenPathOrObject The path to a Google * OAuth2 refresh token JSON file or an object representing a Google OAuth2 * refresh token. - * @param httpAgent Optional [HTTP Agent](https://nodejs.org/api/http.html#http_class_http_agent) + * @param httpAgent Optional {@link https://nodejs.org/api/http.html#http_class_http_agent | HTTP Agent} * to be used when retrieving access tokens from Google token servers. * - * @return A credential authenticated via the + * @returns A credential authenticated via the * provided service account that can be used to initialize an app. */ export const refreshToken = refreshTokenFn; diff --git a/src/database/database-namespace.ts b/src/database/database-namespace.ts index b8882f8997..a6c1f4716e 100644 --- a/src/database/database-namespace.ts +++ b/src/database/database-namespace.ts @@ -19,14 +19,12 @@ import { App } from '../app'; import { Database as TDatabase } from './database'; /** - * Gets the {@link database.Database `Database`} service for the default + * Gets the {@link firebase-admin.database#Database} service for the default * app or a given app. * * `admin.database()` can be called with no arguments to access the default - * app's {@link database.Database `Database`} service or as - * `admin.database(app)` to access the - * {@link database.Database `Database`} service associated with a specific - * app. + * app's `Database` service or as `admin.database(app)` to access the + * `Database` service associated with a specific app. * * `admin.database` is also a namespace that can be used to access global * constants and methods associated with the `Database` service. @@ -46,7 +44,7 @@ import { Database as TDatabase } from './database'; * @param App whose `Database` service to * return. If not provided, the default `Database` service will be returned. * - * @return The default `Database` service if no app + * @returns The default `Database` service if no app * is provided or the `Database` service associated with the provided app. */ export declare function database(app?: App): database.Database; @@ -64,7 +62,7 @@ export namespace database { export declare const enableLogging: typeof rtdb.enableLogging; /** - * [`ServerValue`](https://firebase.google.com/docs/reference/js/firebase.database.ServerValue) + * {@link https://firebase.google.com/docs/reference/js/firebase.database.ServerValue | ServerValue} * module from the `@firebase/database` package. */ export declare const ServerValue: rtdb.ServerValue; diff --git a/src/database/database.ts b/src/database/database.ts index e3ff53b010..4d5275f405 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -32,7 +32,7 @@ export interface Database extends FirebaseDatabase { * Gets the currently applied security rules as a string. The return value consists of * the rules source including comments. * - * @return A promise fulfilled with the rules as a raw string. + * @returns A promise fulfilled with the rules as a raw string. */ getRules(): Promise; @@ -40,7 +40,7 @@ export interface Database extends FirebaseDatabase { * Gets the currently applied security rules as a parsed JSON object. Any comments in * the original source are stripped away. * - * @return A promise fulfilled with the parsed rules object. + * @returns A promise fulfilled with the parsed rules object. */ getRulesJSON(): Promise; @@ -49,7 +49,7 @@ export interface Database extends FirebaseDatabase { * specified as a string or a Buffer, it may include comments. * * @param source Source of the rules to apply. Must not be `null` or empty. - * @return Resolves when the rules are set on the Realtime Database. + * @returns Resolves when the rules are set on the Realtime Database. */ setRules(source: string | Buffer | object): Promise; } @@ -102,7 +102,7 @@ export class DatabaseService { /** * Returns the app associated with this DatabaseService instance. * - * @return The app associated with this DatabaseService instance. + * @returns The app associated with this DatabaseService instance. */ get app(): App { return this.appInternal; @@ -212,7 +212,7 @@ class DatabaseRulesClient { * Gets the currently applied security rules as a string. The return value consists of * the rules source including comments. * - * @return A promise fulfilled with the rules as a raw string. + * @returns A promise fulfilled with the rules as a raw string. */ public getRules(): Promise { const req: HttpRequestConfig = { @@ -235,7 +235,7 @@ class DatabaseRulesClient { * Gets the currently applied security rules as a parsed JSON object. Any comments in * the original source are stripped away. * - * @return {Promise} A promise fulfilled with the parsed rules source. + * @returns {Promise} A promise fulfilled with the parsed rules source. */ public getRulesJSON(): Promise { const req: HttpRequestConfig = { @@ -258,7 +258,7 @@ class DatabaseRulesClient { * * @param {string|Buffer|object} source Source of the rules to apply. Must not be `null` * or empty. - * @return {Promise} Resolves when the rules are set on the Database. + * @returns {Promise} Resolves when the rules are set on the Database. */ public setRules(source: string | Buffer | object): Promise { if (!validator.isNonEmptyString(source) && diff --git a/src/database/index.ts b/src/database/index.ts index 060a68c679..e81ba68e60 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -35,13 +35,13 @@ export { } from '@firebase/database-types'; /** - * [`enableLogging`](https://firebase.google.com/docs/reference/js/firebase.database#enablelogging) + * {@link https://firebase.google.com/docs/reference/js/firebase.database#enablelogging | enableLogging} * function from the `@firebase/database` package. */ export const enableLogging: typeof rtdb.enableLogging = enableLoggingFunc; /** - * [`ServerValue`](https://firebase.google.com/docs/reference/js/firebase.database.ServerValue) + * {@link https://firebase.google.com/docs/reference/js/firebase.database.ServerValue | ServerValue} * module from the `@firebase/database` package. */ export const ServerValue: rtdb.ServerValue = serverValueConst; @@ -71,7 +71,7 @@ export const ServerValue: rtdb.ServerValue = serverValueConst; * @param App whose `Database` service to * return. If not provided, the default `Database` service will be returned. * - * @return The default `Database` service if no app + * @returns The default `Database` service if no app * is provided or the `Database` service associated with the provided app. */ export function getDatabase(app?: App): Database { @@ -103,7 +103,7 @@ export function getDatabase(app?: App): Database { * @param App whose `Database` service to * return. If not provided, the default `Database` service will be returned. * - * @return The default `Database` service if no app + * @returns The default `Database` service if no app * is provided or the `Database` service associated with the provided app. */ export function getDatabaseWithUrl(url: string, app?: App): Database { diff --git a/src/firestore/firestore-internal.ts b/src/firestore/firestore-internal.ts index 7c4df98242..b883251d41 100644 --- a/src/firestore/firestore-internal.ts +++ b/src/firestore/firestore-internal.ts @@ -36,7 +36,7 @@ export class FirestoreService { /** * Returns the app associated with this Storage instance. * - * @return The app associated with this Storage instance. + * @returns The app associated with this Storage instance. */ get app(): App { return this.appInternal; diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index c6ce147f80..fdc44e8341 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -21,14 +21,11 @@ import { InstanceId } from './instance-id'; export { InstanceId }; /** - * Gets the {@link InstanceId `InstanceId`} service for the - * default app or a given app. + * Gets the {@link InstanceId} service for the default app or a given app. * * `getInstanceId()` can be called with no arguments to access the default - * app's {@link InstanceId `InstanceId`} service or as - * `getInstanceId(app)` to access the - * {@link InstanceId `InstanceId`} service associated with a - * specific app. + * app's `InstanceId` service or as `getInstanceId(app)` to access the + * `InstanceId` service associated with a specific app. * * @example * ```javascript @@ -46,7 +43,7 @@ export { InstanceId }; * return. If not provided, the default `InstanceId` service will be * returned. * - * @return The default `InstanceId` service if + * @returns The default `InstanceId` service if * no app is provided or the `InstanceId` service associated with the * provided app. */ diff --git a/src/instance-id/instance-id-namespace.ts b/src/instance-id/instance-id-namespace.ts index 74b367a595..f814cd874e 100644 --- a/src/instance-id/instance-id-namespace.ts +++ b/src/instance-id/instance-id-namespace.ts @@ -2,14 +2,12 @@ import { App } from '../app/index'; import { InstanceId as TInstanceId } from './instance-id'; /** - * Gets the {@link instanceId.InstanceId `InstanceId`} service for the + * Gets the {@link firebase-admin.instance-id#InstanceId} service for the * default app or a given app. * * `admin.instanceId()` can be called with no arguments to access the default - * app's {@link instanceId.InstanceId `InstanceId`} service or as - * `admin.instanceId(app)` to access the - * {@link instanceId.InstanceId `InstanceId`} service associated with a - * specific app. + * app's `InstanceId` service or as `admin.instanceId(app)` to access the + * `InstanceId` service associated with a specific app. * * @example * ```javascript @@ -27,7 +25,7 @@ import { InstanceId as TInstanceId } from './instance-id'; * return. If not provided, the default `InstanceId` service will be * returned. * - * @return The default `InstanceId` service if + * @returns The default `InstanceId` service if * no app is provided or the `InstanceId` service associated with the * provided app. */ diff --git a/src/instance-id/instance-id-request-internal.ts b/src/instance-id/instance-id-request-internal.ts index 1ef9125744..52441b84ee 100644 --- a/src/instance-id/instance-id-request-internal.ts +++ b/src/instance-id/instance-id-request-internal.ts @@ -76,8 +76,8 @@ export class FirebaseInstanceIdRequestHandler { /** * Invokes the request handler based on the API settings object passed. * - * @param {ApiSettings} apiSettings The API endpoint settings to apply to request and response. - * @return {Promise} A promise that resolves when the request is complete. + * @param apiSettings The API endpoint settings to apply to request and response. + * @returns A promise that resolves when the request is complete. */ private invokeRequestHandler(apiSettings: ApiSettings): Promise { return this.getPathPrefix() diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index 0e7bd0a66a..eb4492b2a1 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -51,12 +51,13 @@ export class InstanceId { * Note that Google Analytics for Firebase uses its own form of Instance ID to * keep track of analytics data. Therefore deleting a Firebase Instance ID does * not delete Analytics data. See - * [Delete an Instance ID](/support/privacy/manage-iids#delete_an_instance_id) + * {@link https://firebase.google.com/support/privacy/manage-iids#delete_an_instance_id | + * Delete an Instance ID} * for more information. * * @param instanceId The instance ID to be deleted. * - * @return A promise fulfilled when the instance ID is deleted. + * @returns A promise fulfilled when the instance ID is deleted. */ public deleteInstanceId(instanceId: string): Promise { return this.requestHandler.deleteInstanceId(instanceId) @@ -68,7 +69,7 @@ export class InstanceId { /** * Returns the app associated with this InstanceId instance. * - * @return The app associated with this InstanceId instance. + * @returns The app associated with this InstanceId instance. */ get app(): App { return this.app_; diff --git a/src/machine-learning/index.ts b/src/machine-learning/index.ts index 279273d301..e10fb222ac 100644 --- a/src/machine-learning/index.ts +++ b/src/machine-learning/index.ts @@ -33,34 +33,31 @@ export { } from './machine-learning-api-client'; /** - * Gets the {@link machineLearning.MachineLearning `MachineLearning`} service for the - * default app or a given app. + * Gets the {@link MachineLearning} service for the default app or a given app. * * `getMachineLearning()` can be called with no arguments to access the - * default app's {@link machineLearning.MachineLearning - * `MachineLearning`} service or as `getMachineLearning(app)` to access - * the {@link machineLearning.MachineLearning `MachineLearning`} - * service associated with a specific app. - * - * @example - * ```javascript - * // Get the MachineLearning service for the default app - * const defaultMachineLearning = getMachineLearning(); - * ``` - * - * @example - * ```javascript - * // Get the MachineLearning service for a given app - * const otherMachineLearning = getMachineLearning(otherApp); - * ``` - * - * @param app Optional app whose `MachineLearning` service to - * return. If not provided, the default `MachineLearning` service - * will be returned. - * - * @return The default `MachineLearning` service if no app is provided or the - * `MachineLearning` service associated with the provided app. - */ + * default app's {`MachineLearning` service or as `getMachineLearning(app)` to access + * the `MachineLearning` service associated with a specific app. + * + * @example + * ```javascript + * // Get the MachineLearning service for the default app + * const defaultMachineLearning = getMachineLearning(); + * ``` + * + * @example + * ```javascript + * // Get the MachineLearning service for a given app + * const otherMachineLearning = getMachineLearning(otherApp); + * ``` + * + * @param app Optional app whose `MachineLearning` service to + * return. If not provided, the default `MachineLearning` service + * will be returned. + * + * @returns The default `MachineLearning` service if no app is provided or the + * `MachineLearning` service associated with the provided app. + */ export function getMachineLearning(app?: App): MachineLearning { if (typeof app === 'undefined') { app = getApp(); diff --git a/src/machine-learning/machine-learning-namespace.ts b/src/machine-learning/machine-learning-namespace.ts index 021b6ca6aa..d16b4d1a0c 100644 --- a/src/machine-learning/machine-learning-namespace.ts +++ b/src/machine-learning/machine-learning-namespace.ts @@ -30,14 +30,12 @@ import { } from './machine-learning-api-client'; /** - * Gets the {@link machineLearning.MachineLearning `MachineLearning`} service for the + * Gets the {@link firebase-admin.machine-learning#MachineLearning} service for the * default app or a given app. * * `admin.machineLearning()` can be called with no arguments to access the - * default app's {@link machineLearning.MachineLearning - * `MachineLearning`} service or as `admin.machineLearning(app)` to access - * the {@link machineLearning.MachineLearning `MachineLearning`} - * service associated with a specific app. + * default app's `MachineLearning` service or as `admin.machineLearning(app)` to access + * the `MachineLearning` service associated with a specific app. * * @example * ```javascript @@ -55,7 +53,7 @@ import { * return. If not provided, the default `MachineLearning` service * will be returned. * - * @return The default `MachineLearning` service if no app is provided or the + * @returns The default `MachineLearning` service if no app is provided or the * `MachineLearning` service associated with the provided app. */ export declare function machineLearning(app?: App): machineLearning.MachineLearning; diff --git a/src/machine-learning/machine-learning.ts b/src/machine-learning/machine-learning.ts index 5c60438adf..849aad7546 100644 --- a/src/machine-learning/machine-learning.ts +++ b/src/machine-learning/machine-learning.ts @@ -84,7 +84,7 @@ export class MachineLearning { } /** - * The {@link app.App} associated with the current `MachineLearning` + * The {@link firebase-admin.app#App} associated with the current `MachineLearning` * service instance. */ public get app(): App { @@ -96,7 +96,7 @@ export class MachineLearning { * * @param model The model to create. * - * @return A Promise fulfilled with the created model. + * @returns A Promise fulfilled with the created model. */ public createModel(model: ModelOptions): Promise { return this.signUrlIfPresent(model) @@ -111,7 +111,7 @@ export class MachineLearning { * @param modelId The ID of the model to update. * @param model The model fields to update. * - * @return A Promise fulfilled with the updated model. + * @returns A Promise fulfilled with the updated model. */ public updateModel(modelId: string, model: ModelOptions): Promise { const updateMask = utils.generateUpdateMask(model); @@ -128,7 +128,7 @@ export class MachineLearning { * * @param modelId The ID of the model to publish. * - * @return A Promise fulfilled with the published model. + * @returns A Promise fulfilled with the published model. */ public publishModel(modelId: string): Promise { return this.setPublishStatus(modelId, true); @@ -139,7 +139,7 @@ export class MachineLearning { * * @param modelId The ID of the model to unpublish. * - * @return A Promise fulfilled with the unpublished model. + * @returns A Promise fulfilled with the unpublished model. */ public unpublishModel(modelId: string): Promise { return this.setPublishStatus(modelId, false); @@ -150,7 +150,7 @@ export class MachineLearning { * * @param modelId The ID of the model to get. * - * @return A Promise fulfilled with the model object. + * @returns A Promise fulfilled with the model object. */ public getModel(modelId: string): Promise { return this.client.getModel(modelId) @@ -162,7 +162,7 @@ export class MachineLearning { * * @param options The listing options. * - * @return A promise that + * @returns A promise that * resolves with the current (filtered) list of models and the next page * token. For the last page, an empty list of models and no page token * are returned. @@ -369,7 +369,7 @@ export class Model { * @param maxTimeMillis The maximum time in milliseconds to wait. * If not specified, a default maximum of 2 minutes is used. * - * @return A promise that resolves when the model is unlocked + * @returns A promise that resolves when the model is unlocked * or the maximum wait time has passed. */ public waitForUnlocked(maxTimeMillis?: number): Promise { diff --git a/src/messaging/batch-request-internal.ts b/src/messaging/batch-request-internal.ts index 0c6995a874..0e79af4c79 100644 --- a/src/messaging/batch-request-internal.ts +++ b/src/messaging/batch-request-internal.ts @@ -56,8 +56,8 @@ export class BatchRequestClient { * Sends the given array of sub requests as a single batch, and parses the results into an array * of HttpResponse objects. * - * @param {SubRequest[]} requests An array of sub requests to send. - * @return {Promise} A promise that resolves when the send operation is complete. + * @param requests An array of sub requests to send. + * @returns A promise that resolves when the send operation is complete. */ public send(requests: SubRequest[]): Promise { requests = requests.map((req) => { @@ -100,10 +100,10 @@ export class BatchRequestClient { * API, sets the content-type header to application/http, and the content-transfer-encoding to * binary. * - * @param {SubRequest} request A sub request that will be used to populate the part. - * @param {string} boundary Multipart boundary string. - * @param {number} idx An index number that is used to set the content-id header. - * @return {string} The part as a string that can be included in the HTTP body. + * @param request A sub request that will be used to populate the part. + * @param boundary Multipart boundary string. + * @param idx An index number that is used to set the content-id header. + * @returns The part as a string that can be included in the HTTP body. */ function createPart(request: SubRequest, boundary: string, idx: number): string { const serializedRequest: string = serializeSubRequest(request); @@ -122,8 +122,8 @@ function createPart(request: SubRequest, boundary: string, idx: number): string * format of the string is the wire format of a typical HTTP request, consisting of a header and a * body. * - * @param request {SubRequest} The sub request to be serialized. - * @return {string} String representation of the SubRequest. + * @param request The sub request to be serialized. + * @returns String representation of the SubRequest. */ function serializeSubRequest(request: SubRequest): string { const requestBody: string = JSON.stringify(request.body); diff --git a/src/messaging/index.ts b/src/messaging/index.ts index b64559e9f7..9d4cb990ae 100644 --- a/src/messaging/index.ts +++ b/src/messaging/index.ts @@ -61,14 +61,11 @@ export { } from './messaging-api'; /** - * Gets the {@link messaging.Messaging `Messaging`} service for the - * default app or a given app. + * Gets the {@link Messaging} service for the default app or a given app. * * `admin.messaging()` can be called with no arguments to access the default - * app's {@link messaging.Messaging `Messaging`} service or as - * `admin.messaging(app)` to access the - * {@link messaging.Messaging `Messaging`} service associated with a - * specific app. + * app's `Messaging` service or as `admin.messaging(app)` to access the + * `Messaging` service associated with aspecific app. * * @example * ```javascript @@ -85,7 +82,7 @@ export { * @param app Optional app whose `Messaging` service to * return. If not provided, the default `Messaging` service will be returned. * - * @return The default `Messaging` service if no + * @returns The default `Messaging` service if no * app is provided or the `Messaging` service associated with the provided * app. */ diff --git a/src/messaging/messaging-api-request-internal.ts b/src/messaging/messaging-api-request-internal.ts index 31122a3df4..59f67ba3c1 100644 --- a/src/messaging/messaging-api-request-internal.ts +++ b/src/messaging/messaging-api-request-internal.ts @@ -59,10 +59,10 @@ export class FirebaseMessagingRequestHandler { /** * Invokes the request handler with the provided request data. * - * @param {string} host The host to which to send the request. - * @param {string} path The path to which to send the request. - * @param {object} requestData The request data. - * @return {Promise} A promise that resolves with the response. + * @param host The host to which to send the request. + * @param path The path to which to send the request. + * @param requestData The request data. + * @returns A promise that resolves with the response. */ public invokeRequestHandler(host: string, path: string, requestData: object): Promise { const request: HttpRequestConfig = { @@ -100,8 +100,8 @@ export class FirebaseMessagingRequestHandler { * Sends the given array of sub requests as a single batch to FCM, and parses the result into * a BatchResponse object. * - * @param {SubRequest[]} requests An array of sub requests to send. - * @return {Promise} A promise that resolves when the send operation is complete. + * @param requests An array of sub requests to send. + * @returns A promise that resolves when the send operation is complete. */ public sendBatchRequest(requests: SubRequest[]): Promise { return this.batchClient.send(requests) diff --git a/src/messaging/messaging-api.ts b/src/messaging/messaging-api.ts index e586ba039d..cdde578c2b 100644 --- a/src/messaging/messaging-api.ts +++ b/src/messaging/messaging-api.ts @@ -39,13 +39,13 @@ export interface ConditionMessage extends BaseMessage { } /** - * Payload for the admin.messaging.send() operation. The payload contains all the fields + * Payload for the {@link Messaging.send} operation. The payload contains all the fields * in the BaseMessage type, and exactly one of token, topic or condition. */ export type Message = TokenMessage | TopicMessage | ConditionMessage; /** - * Payload for the admin.messaing.sendMulticast() method. The payload contains all the fields + * Payload for the {@link Messaging.sendMulticast} method. The payload contains all the fields * in the BaseMessage type, and a list of tokens. */ export interface MulticastMessage extends BaseMessage { @@ -53,7 +53,7 @@ export interface MulticastMessage extends BaseMessage { } /** - * A notification that can be included in {@link messaging.Message}. + * A notification that can be included in {@link Message}. */ export interface Notification { /** @@ -82,14 +82,14 @@ export interface FcmOptions { /** * Represents the WebPush protocol options that can be included in an - * {@link messaging.Message}. + * {@link Message}. */ export interface WebpushConfig { /** * A collection of WebPush headers. Header values must be strings. * - * See [WebPush specification](https://tools.ietf.org/html/rfc8030#section-5) + * See {@link https://tools.ietf.org/html/rfc8030#section-5 | WebPush specification} * for supported headers. */ headers?: { [key: string]: string }; @@ -124,9 +124,9 @@ export interface WebpushFcmOptions { /** * Represents the WebPush-specific notification options that can be included in - * {@link messaging.WebpushConfig}. This supports most of the standard + * {@link WebpushConfig}. This supports most of the standard * options as defined in the Web Notification - * [specification](https://developer.mozilla.org/en-US/docs/Web/API/notification/Notification). + * {@link https://developer.mozilla.org/en-US/docs/Web/API/notification/Notification | specification}. */ export interface WebpushNotification { @@ -236,9 +236,9 @@ export interface WebpushNotification { /** * Represents the APNs-specific options that can be included in an - * {@link messaging.Message}. Refer to - * [Apple documentation](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html) - * for various headers and payload fields supported by APNs. + * {@link Message}. Refer to + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html | + * Apple documentation} for various headers and payload fields supported by APNs. */ export interface ApnsConfig { /** @@ -271,8 +271,8 @@ export interface ApnsPayload { } /** - * Represents the [aps dictionary](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * that is part of APNs messages. + * Represents the {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html | + * aps dictionary} that is part of APNs messages. */ export interface Aps { @@ -374,7 +374,7 @@ export interface ApnsFcmOptions { /** * Represents the Android-specific options that can be included in an - * {@link messaging.Message}. + * {@link Message}. */ export interface AndroidConfig { @@ -422,7 +422,7 @@ export interface AndroidConfig { /** * Represents the Android-specific notification options that can be included in - * {@link messaging.AndroidConfig}. + * {@link AndroidConfig}. */ export interface AndroidNotification { /** @@ -531,7 +531,8 @@ export interface AndroidNotification { * Sets whether or not this notification is relevant only to the current device. * Some notifications can be bridged to other devices for remote display, such as * a Wear OS watch. This hint can be set to recommend this notification not be bridged. - * See [Wear OS guides](https://developer.android.com/training/wearables/notifications/bridger#existing-method-of-preventing-bridging) + * See {@link https://developer.android.com/training/wearables/notifications/bridger#existing-method-of-preventing-bridging | + * Wear OS guides}. */ localOnly?: boolean; @@ -556,15 +557,16 @@ export interface AndroidNotification { /** * If set to `true`, use the Android framework's default vibrate pattern for the - * notification. Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). - * If `default_vibrate_timings` is set to `true` and `vibrate_timings` is also set, + * notification. Default values are specified in {@link https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml | + * config.xml}. If `default_vibrate_timings` is set to `true` and `vibrate_timings` is also set, * the default value is used instead of the user-specified `vibrate_timings`. */ defaultVibrateTimings?: boolean; /** * If set to `true`, use the Android framework's default sound for the notification. - * Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). + * Default values are specified in {@link https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml | + * config.xml}. */ defaultSound?: boolean; @@ -576,7 +578,8 @@ export interface AndroidNotification { /** * If set to `true`, use the Android framework's default LED light settings - * for the notification. Default values are specified in [`config.xml`](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml). + * for the notification. Default values are specified in {@link https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml | + * config.xml}. * If `default_light_settings` is set to `true` and `light_settings` is also set, * the user-specified `light_settings` is used instead of the default value. */ @@ -590,7 +593,8 @@ export interface AndroidNotification { /** * Sets the number of items this notification represents. May be displayed as a - * badge count for Launchers that support badging. See [`NotificationBadge`(https://developer.android.com/training/notify-user/badges). + * badge count for Launchers that support badging. See {@link https://developer.android.com/training/notify-user/badges | + * NotificationBadge}. * For example, this might be useful if you're using just one notification to * represent multiple new messages but you want the count here to represent * the number of total new messages. If zero or unspecified, systems @@ -602,7 +606,7 @@ export interface AndroidNotification { /** * Represents settings to control notification LED that can be included in - * {@link messaging.AndroidNotification}. + * {@link AndroidNotification}. */ export interface LightSettings { /** @@ -638,10 +642,12 @@ export interface AndroidFcmOptions { * keys and values must both be strings. Keys can be any custom string, * except for the following reserved strings: * - * * `"from"` - * * Anything starting with `"google."`. + *
    + *
  • from
  • + *
  • Anything starting with google.
  • + *
* - * See [Build send requests](/docs/cloud-messaging/send-message) + * See {@link https://firebase.google.com/docs/cloud-messaging/send-message | Build send requests} * for code samples and detailed documentation. */ export interface DataMessagePayload { @@ -653,7 +659,7 @@ export interface DataMessagePayload { * Notification messages let developers send up to 4KB of predefined * key-value pairs. Accepted keys are outlined below. * - * See [Build send requests](/docs/cloud-messaging/send-message) + * See {@link https://firebase.google.com/docs/cloud-messaging/send-message | Build send requests} * for code samples and detailed documentation. */ export interface NotificationMessagePayload { @@ -730,13 +736,14 @@ export interface NotificationMessagePayload { * the body text to the user's current localization. * * **iOS:** Corresponds to `loc-key` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html | + * Payload Key Reference} and + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9 | + * Localizing the Content of Your Remote Notifications} for more information. * * **Android:** See - * [String Resources](http://developer.android.com/guide/topics/resources/string-resource.html) * for more information. + * {@link http://developer.android.com/guide/topics/resources/string-resource.html | String Resources} + * for more information. * * **Platforms:** iOS, Android */ @@ -750,14 +757,14 @@ export interface NotificationMessagePayload { * The value should be a stringified JSON array. * * **iOS:** Corresponds to `loc-args` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html | + * Payload Key Reference} and + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9 | + * Localizing the Content of Your Remote Notifications} for more information. * * **Android:** See - * [Formatting and Styling](http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling) - * for more information. + * {@link http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling | + * Formatting and Styling} for more information. * * **Platforms:** iOS, Android */ @@ -777,13 +784,13 @@ export interface NotificationMessagePayload { * the title text to the user's current localization. * * **iOS:** Corresponds to `title-loc-key` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html | + * Payload Key Reference} and + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9 | + * Localizing the Content of Your Remote Notifications} for more information. * * **Android:** See - * [String Resources](http://developer.android.com/guide/topics/resources/string-resource.html) + * {@link http://developer.android.com/guide/topics/resources/string-resource.html | String Resources} * for more information. * * **Platforms:** iOS, Android @@ -798,14 +805,14 @@ export interface NotificationMessagePayload { * The value should be a stringified JSON array. * * **iOS:** Corresponds to `title-loc-args` in the APNs payload. See - * [Payload Key Reference](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html) - * and - * [Localizing the Content of Your Remote Notifications](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9) - * for more information. + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html | + * Payload Key Reference} and + * {@link https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html#//apple_ref/doc/uid/TP40008194-CH10-SW9 | + * Localizing the Content of Your Remote Notifications} for more information. * * **Android:** See - * [Formatting and Styling](http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling) - * for more information. + * {@link http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling | + * Formatting and Styling} for more information. * * **Platforms:** iOS, Android */ @@ -817,8 +824,7 @@ export interface NotificationMessagePayload { * Interface representing a Firebase Cloud Messaging message payload. One or * both of the `data` and `notification` keys are required. * - * See - * [Build send requests](/docs/cloud-messaging/send-message) + * See {@link https://firebase.google.com/docs/cloud-messaging/send-message | Build send requests} * for code samples and detailed documentation. */ export interface MessagingPayload { @@ -838,7 +844,7 @@ export interface MessagingPayload { * Interface representing the options that can be provided when sending a * message via the FCM legacy APIs. * - * See [Build send requests](/docs/cloud-messaging/send-message) + * See {@link https://firebase.google.com/docs/cloud-messaging/send-message | Build send requests} * for code samples and detailed documentation. */ export interface MessagingOptions { @@ -866,7 +872,8 @@ export interface MessagingOptions { * app can wake a sleeping device and open a network connection to your server. * * For more information, see - * [Setting the priority of a message](/docs/cloud-messaging/concept-options#setting-the-priority-of-a-message). + * {@link https://firebase.google.com/docs/cloud-messaging/concept-options#setting-the-priority-of-a-message | + * Setting the priority of a message}. * * **Default value:** `"high"` for notification messages, `"normal"` for data * messages @@ -877,7 +884,7 @@ export interface MessagingOptions { * How long (in seconds) the message should be kept in FCM storage if the device * is offline. The maximum time to live supported is four weeks, and the default * value is also four weeks. For more information, see - * [Setting the lifespan of a message](/docs/cloud-messaging/concept-options#ttl). + * {@link https://firebase.google.com/docs/cloud-messaging/concept-options#ttl | Setting the lifespan of a message}. * * **Default value:** `2419200` (representing four weeks, in seconds) */ @@ -904,7 +911,8 @@ export interface MessagingOptions { * On iOS, use this field to represent `mutable-content` in the APNs payload. * When a notification is sent and this is set to `true`, the content of the * notification can be modified before it is displayed, using a - * [Notification Service app extension](https://developer.apple.com/reference/usernotifications/unnotificationserviceextension) + * {@link https://developer.apple.com/reference/usernotifications/unnotificationserviceextension | + * Notification Service app extension}. * * On Android and Web, this parameter will be ignored. * @@ -957,8 +965,8 @@ export interface MessagingDeviceResult { * via the FCM legacy APIs. * * See - * [Send to individual devices](/docs/cloud-messaging/admin/send-messages#send_to_individual_devices) - * for code samples and detailed documentation. + * {@link https://firebase.google.com/docs/cloud-messaging/admin/send-messages#send_to_individual_devices | + * Send to individual devices} for code samples and detailed documentation. */ export interface MessagingDevicesResponse { canonicalRegistrationTokenCount: number; @@ -969,13 +977,12 @@ export interface MessagingDevicesResponse { } /** - * Interface representing the server response from the - * {@link messaging.Messaging.sendToDeviceGroup `sendToDeviceGroup()`} + * Interface representing the server response from the {@link Messaging.sendToDeviceGroup} * method. * * See - * [Send messages to device groups](/docs/cloud-messaging/send-message?authuser=0#send_messages_to_device_groups) - * for code samples and detailed documentation. + * {@link https://firebase.google.com/docs/cloud-messaging/send-message?authuser=0#send_messages_to_device_groups | + * Send messages to device groups} for code samples and detailed documentation. */ export interface MessagingDeviceGroupResponse { @@ -996,12 +1003,11 @@ export interface MessagingDeviceGroupResponse { } /** - * Interface representing the server response from the legacy - * {@link messaging.Messaging.sendToTopic `sendToTopic()`} method. + * Interface representing the server response from the legacy {@link Messaging.sendToTopic} method. * * See - * [Send to a topic](/docs/cloud-messaging/admin/send-messages#send_to_a_topic) - * for code samples and detailed documentation. + * {@link https://firebase.google.com/docs/cloud-messaging/admin/send-messages#send_to_a_topic | + * Send to a topic} for code samples and detailed documentation. */ export interface MessagingTopicResponse { /** @@ -1013,11 +1019,11 @@ export interface MessagingTopicResponse { /** * Interface representing the server response from the legacy - * {@link messaging.Messaging.sendToCondition `sendToCondition()`} method. + * {@link Messaging.sendToCondition} method. * * See - * [Send to a condition](/docs/cloud-messaging/admin/send-messages#send_to_a_condition) - * for code samples and detailed documentation. + * {@link https://firebase.google.com/docs/cloud-messaging/admin/send-messages#send_to_a_condition | + * Send to a condition} for code samples and detailed documentation. */ export interface MessagingConditionResponse { /** @@ -1029,13 +1035,12 @@ export interface MessagingConditionResponse { /** * Interface representing the server response from the - * {@link messaging.Messaging.subscribeToTopic `subscribeToTopic()`} and - * {@link messaging.Messaging.unsubscribeFromTopic `unsubscribeFromTopic()`} + * {@link Messaging.subscribeToTopic} and {@link Messaging.unsubscribeFromTopic} * methods. * * See - * [Manage topics from the server](/docs/cloud-messaging/manage-topics) - * for code samples and detailed documentation. + * {@link https://firebase.google.com/docs/cloud-messaging/manage-topics | + * Manage topics from the server} for code samples and detailed documentation. */ export interface MessagingTopicManagementResponse { /** @@ -1052,15 +1057,14 @@ export interface MessagingTopicManagementResponse { /** * An array of errors corresponding to the provided registration token(s). The - * length of this array will be equal to [`failureCount`](#failureCount). + * length of this array will be equal to {@link MessagingTopicManagementResponse.failureCount}. */ errors: FirebaseArrayIndexError[]; } /** * Interface representing the server response from the - * {@link messaging.Messaging.sendAll `sendAll()`} and - * {@link messaging.Messaging.sendMulticast `sendMulticast()`} methods. + * {@link Messaging.sendAll} and {@link Messaging.sendMulticast} methods. */ export interface BatchResponse { diff --git a/src/messaging/messaging-errors-internal.ts b/src/messaging/messaging-errors-internal.ts index ecd3155bae..e87f635d55 100644 --- a/src/messaging/messaging-errors-internal.ts +++ b/src/messaging/messaging-errors-internal.ts @@ -22,8 +22,8 @@ import * as validator from '../utils/validator'; * Creates a new FirebaseMessagingError by extracting the error code, message and other relevant * details from an HTTP error response. * - * @param {HttpError} err The HttpError to convert into a Firebase error - * @return {FirebaseMessagingError} A Firebase error that can be returned to the user. + * @param err The HttpError to convert into a Firebase error + * @returns A Firebase error that can be returned to the user. */ export function createFirebaseError(err: HttpError): FirebaseMessagingError { if (err.response.isJson()) { @@ -62,8 +62,8 @@ export function createFirebaseError(err: HttpError): FirebaseMessagingError { } /** - * @param {object} response The response to check for errors. - * @return {string|null} The error code if present; null otherwise. + * @param response The response to check for errors. + * @returns The error code if present; null otherwise. */ export function getErrorCode(response: any): string | null { if (validator.isNonNullObject(response) && 'error' in response) { @@ -92,8 +92,8 @@ export function getErrorCode(response: any): string | null { /** * Extracts error message from the given response object. * - * @param {object} response The response to check for errors. - * @return {string|null} The error message if present; null otherwise. + * @param response The response to check for errors. + * @returns The error message if present; null otherwise. */ function getErrorMessage(response: any): string | null { if (validator.isNonNullObject(response) && diff --git a/src/messaging/messaging-internal.ts b/src/messaging/messaging-internal.ts index 4d68965774..3e832a2a8c 100644 --- a/src/messaging/messaging-internal.ts +++ b/src/messaging/messaging-internal.ts @@ -372,7 +372,7 @@ function validateApsAlert(alert: string | ApsAlert | undefined): void { * and notification fields. If successful, transforms the input object by renaming keys to valid * Android keys. Also transforms the ttl value to the format expected by FCM service. * - * @param {AndroidConfig} config An object to be validated. + * @param config An object to be validated. */ function validateAndroidConfig(config: AndroidConfig | undefined): void { if (typeof config === 'undefined') { @@ -585,8 +585,8 @@ function validateAndroidFcmOptions(fcmOptions: AndroidFcmOptions | undefined): v * Returns the duration in seconds with up to nine fractional * digits, terminated by 's'. Example: "3.5s". * - * @param {number} milliseconds The duration in milliseconds. - * @return {string} The resulting formatted string in seconds with up to nine fractional + * @param milliseconds The duration in milliseconds. + * @returns The resulting formatted string in seconds with up to nine fractional * digits, terminated by 's'. */ function transformMillisecondsToSecondsString(milliseconds: number): string { diff --git a/src/messaging/messaging-namespace.ts b/src/messaging/messaging-namespace.ts index b49fdb564a..2e34b87760 100644 --- a/src/messaging/messaging-namespace.ts +++ b/src/messaging/messaging-namespace.ts @@ -54,14 +54,12 @@ import { } from './messaging-api'; /** - * Gets the {@link messaging.Messaging `Messaging`} service for the + * Gets the {@link firebase-admin.messaging#Messaging} service for the * default app or a given app. * * `admin.messaging()` can be called with no arguments to access the default - * app's {@link messaging.Messaging `Messaging`} service or as - * `admin.messaging(app)` to access the - * {@link messaging.Messaging `Messaging`} service associated with a - * specific app. + * app's `Messaging` service or as `admin.messaging(app)` to access the + * `Messaging` service associated with a specific app. * * @example * ```javascript @@ -78,7 +76,7 @@ import { * @param app Optional app whose `Messaging` service to * return. If not provided, the default `Messaging` service will be returned. * - * @return The default `Messaging` service if no + * @returns The default `Messaging` service if no * app is provided or the `Messaging` service associated with the provided * app. */ diff --git a/src/messaging/messaging.ts b/src/messaging/messaging.ts index b1803a513e..688ec08fdb 100644 --- a/src/messaging/messaging.ts +++ b/src/messaging/messaging.ts @@ -107,9 +107,9 @@ const MESSAGING_CONDITION_RESPONSE_KEYS_MAP = { /** * Maps a raw FCM server response to a MessagingDevicesResponse object. * - * @param {object} response The raw FCM server response to map. + * @param response The raw FCM server response to map. * - * @return {MessagingDeviceGroupResponse} The mapped MessagingDevicesResponse object. + * @returns The mapped MessagingDevicesResponse object. */ function mapRawResponseToDevicesResponse(response: object): MessagingDevicesResponse { // Rename properties on the server response @@ -134,9 +134,9 @@ function mapRawResponseToDevicesResponse(response: object): MessagingDevicesResp /** * Maps a raw FCM server response to a MessagingDeviceGroupResponse object. * - * @param {object} response The raw FCM server response to map. + * @param response The raw FCM server response to map. * - * @return {MessagingDeviceGroupResponse} The mapped MessagingDeviceGroupResponse object. + * @returns The mapped MessagingDeviceGroupResponse object. */ function mapRawResponseToDeviceGroupResponse(response: object): MessagingDeviceGroupResponse { // Rename properties on the server response @@ -154,7 +154,7 @@ function mapRawResponseToDeviceGroupResponse(response: object): MessagingDeviceG * * @param {object} response The raw FCM server response to map. * - * @return {MessagingTopicManagementResponse} The mapped MessagingTopicManagementResponse object. + * @returns {MessagingTopicManagementResponse} The mapped MessagingTopicManagementResponse object. */ function mapRawResponseToTopicManagementResponse(response: object): MessagingTopicManagementResponse { // Add the success and failure counts. @@ -196,17 +196,7 @@ export class Messaging { private readonly messagingRequestHandler: FirebaseMessagingRequestHandler; /** - * Gets the {@link messaging.Messaging `Messaging`} service for the - * current app. - * - * @example - * ```javascript - * var messaging = app.messaging(); - * // The above is shorthand for: - * // var messaging = admin.messaging(app); - * ``` - * - * @return The `Messaging` service for the current app. + * @internal */ constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { @@ -221,7 +211,7 @@ export class Messaging { } /** - * The {@link app.App app} associated with the current `Messaging` service + * The {@link firebase-admin.app#App} associated with the current `Messaging` service * instance. * * @example @@ -239,7 +229,7 @@ export class Messaging { * @param message The message payload. * @param dryRun Whether to send the message in the dry-run * (validation only) mode. - * @return A promise fulfilled with a unique message ID + * @returns A promise fulfilled with a unique message ID * string after the message has been successfully handed off to the FCM * service for delivery. */ @@ -279,7 +269,7 @@ export class Messaging { * containing up to 500 messages. * @param dryRun Whether to send the messages in the dry-run * (validation only) mode. - * @return A Promise fulfilled with an object representing the result of the + * @returns A Promise fulfilled with an object representing the result of the * send operation. */ public sendAll(messages: Message[], dryRun?: boolean): Promise { @@ -337,7 +327,7 @@ export class Messaging { * containing up to 500 tokens. * @param dryRun Whether to send the message in the dry-run * (validation only) mode. - * @return A Promise fulfilled with an object representing the result of the + * @returns A Promise fulfilled with an object representing the result of the * send operation. */ public sendMulticast(message: MulticastMessage, dryRun?: boolean): Promise { @@ -374,8 +364,8 @@ export class Messaging { * Sends an FCM message to a single device corresponding to the provided * registration token. * - * See - * [Send to individual devices](/docs/cloud-messaging/admin/legacy-fcm#send_to_individual_devices) + * See {@link https://firebase.google.com/docs/cloud-messaging/admin/legacy-fcm#send_to_individual_devices | + * Send to individual devices} * for code samples and detailed documentation. Takes either a * `registrationToken` to send to a single device or a * `registrationTokens` parameter containing an array of tokens to send @@ -387,7 +377,7 @@ export class Messaging { * @param options Optional options to * alter the message. * - * @return A promise fulfilled with the server's response after the message + * @returns A promise fulfilled with the server's response after the message * has been sent. */ public sendToDevice( @@ -447,9 +437,8 @@ export class Messaging { * Sends an FCM message to a device group corresponding to the provided * notification key. * - * See - * [Send to a device group](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_device_group) - * for code samples and detailed documentation. + * See {@link https://firebase.google.com/docs/cloud-messaging/admin/legacy-fcm#send_to_a_device_group | + * Send to a device group} for code samples and detailed documentation. * * @param notificationKey The notification key for the device group to * which to send the message. @@ -457,7 +446,7 @@ export class Messaging { * @param options Optional options to * alter the message. * - * @return A promise fulfilled with the server's response after the message + * @returns A promise fulfilled with the server's response after the message * has been sent. */ public sendToDeviceGroup( @@ -531,16 +520,15 @@ export class Messaging { /** * Sends an FCM message to a topic. * - * See - * [Send to a topic](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_topic) - * for code samples and detailed documentation. + * See {@link https://firebase.google.com/docs/cloud-messaging/admin/legacy-fcm#send_to_a_topic | + * Send to a topic} for code samples and detailed documentation. * * @param topic The topic to which to send the message. * @param payload The message payload. * @param options Optional options to * alter the message. * - * @return A promise fulfilled with the server's response after the message + * @returns A promise fulfilled with the server's response after the message * has been sent. */ public sendToTopic( @@ -581,8 +569,8 @@ export class Messaging { /** * Sends an FCM message to a condition. * - * See - * [Send to a condition](/docs/cloud-messaging/admin/legacy-fcm#send_to_a_condition) + * See {@link https://firebase.google.com/docs/cloud-messaging/admin/legacy-fcm#send_to_a_condition | + * Send to a condition} * for code samples and detailed documentation. * * @param condition The condition determining to which topics to send @@ -591,7 +579,7 @@ export class Messaging { * @param options Optional options to * alter the message. * - * @return A promise fulfilled with the server's response after the message + * @returns A promise fulfilled with the server's response after the message * has been sent. */ public sendToCondition( @@ -639,8 +627,8 @@ export class Messaging { /** * Subscribes a device to an FCM topic. * - * See [Subscribe to a - * topic](/docs/cloud-messaging/manage-topics#suscribe_and_unsubscribe_using_the) + * See {@link https://firebase.google.com/docs/cloud-messaging/manage-topics#suscribe_and_unsubscribe_using_the | + * Subscribe to a topic} * for code samples and detailed documentation. Optionally, you can provide an * array of tokens to subscribe multiple devices. * @@ -648,7 +636,7 @@ export class Messaging { * for the devices to subscribe to the topic. * @param topic The topic to which to subscribe. * - * @return A promise fulfilled with the server's response after the device has been + * @returns A promise fulfilled with the server's response after the device has been * subscribed to the topic. */ public subscribeToTopic( @@ -666,8 +654,8 @@ export class Messaging { /** * Unsubscribes a device from an FCM topic. * - * See [Unsubscribe from a - * topic](/docs/cloud-messaging/admin/manage-topic-subscriptions#unsubscribe_from_a_topic) + * See {@link https://firebase.google.com/docs/cloud-messaging/admin/manage-topic-subscriptions#unsubscribe_from_a_topic | + * Unsubscribe from a topic} * for code samples and detailed documentation. Optionally, you can provide an * array of tokens to unsubscribe multiple devices. * @@ -675,7 +663,7 @@ export class Messaging { * device registration tokens to unsubscribe from the topic. * @param topic The topic from which to unsubscribe. * - * @return A promise fulfilled with the server's response after the device has been + * @returns A promise fulfilled with the server's response after the device has been * unsubscribed from the topic. */ public unsubscribeFromTopic( @@ -715,13 +703,13 @@ export class Messaging { /** * Helper method which sends and handles topic subscription management requests. * - * @param {string|string[]} registrationTokenOrTokens The registration token or an array of + * @param registrationTokenOrTokens The registration token or an array of * registration tokens to unsubscribe from the topic. - * @param {string} topic The topic to which to subscribe. - * @param {string} methodName The name of the original method called. - * @param {string} path The endpoint path to use for the request. + * @param topic The topic to which to subscribe. + * @param methodName The name of the original method called. + * @param path The endpoint path to use for the request. * - * @return {Promise} A Promise fulfilled with the parsed server + * @returns A Promise fulfilled with the parsed server * response. */ private sendTopicManagementRequest( @@ -766,8 +754,8 @@ export class Messaging { /** * Validates the types of the messaging payload and options. If invalid, an error will be thrown. * - * @param {MessagingPayload} payload The messaging payload to validate. - * @param {MessagingOptions} options The messaging options to validate. + * @param payload The messaging payload to validate. + * @param options The messaging options to validate. */ private validateMessagingPayloadAndOptionsTypes( payload: MessagingPayload, @@ -793,9 +781,9 @@ export class Messaging { /** * Validates the messaging payload. If invalid, an error will be thrown. * - * @param {MessagingPayload} payload The messaging payload to validate. + * @param payload The messaging payload to validate. * - * @return {MessagingPayload} A copy of the provided payload with whitelisted properties switched + * @returns A copy of the provided payload with whitelisted properties switched * from camelCase to underscore_case. */ private validateMessagingPayload(payload: MessagingPayload): MessagingPayload { @@ -884,9 +872,9 @@ export class Messaging { /** * Validates the messaging options. If invalid, an error will be thrown. * - * @param {MessagingOptions} options The messaging options to validate. + * @param options The messaging options to validate. * - * @return {MessagingOptions} A copy of the provided options with whitelisted properties switched + * @returns A copy of the provided options with whitelisted properties switched * from camelCase to underscore_case. */ private validateMessagingOptions(options: MessagingOptions): MessagingOptions { @@ -963,9 +951,9 @@ export class Messaging { /** * Validates the type of the provided registration token(s). If invalid, an error will be thrown. * - * @param {string|string[]} registrationTokenOrTokens The registration token(s) to validate. - * @param {string} method The method name to use in error messages. - * @param {ErrorInfo?} [errorInfo] The error info to use if the registration tokens are invalid. + * @param registrationTokenOrTokens The registration token(s) to validate. + * @param method The method name to use in error messages. + * @param errorInfo The error info to use if the registration tokens are invalid. */ private validateRegistrationTokensType( registrationTokenOrTokens: string | string[], @@ -985,10 +973,10 @@ export class Messaging { /** * Validates the provided registration tokens. If invalid, an error will be thrown. * - * @param {string|string[]} registrationTokenOrTokens The registration token or an array of + * @param registrationTokenOrTokens The registration token or an array of * registration tokens to validate. - * @param {string} method The method name to use in error messages. - * @param {errorInfo?} [ErrorInfo] The error info to use if the registration tokens are invalid. + * @param method The method name to use in error messages. + * @param errorInfo The error info to use if the registration tokens are invalid. */ private validateRegistrationTokens( registrationTokenOrTokens: string | string[], @@ -1021,9 +1009,9 @@ export class Messaging { /** * Validates the type of the provided topic. If invalid, an error will be thrown. * - * @param {string} topic The topic to validate. - * @param {string} method The method name to use in error messages. - * @param {ErrorInfo?} [errorInfo] The error info to use if the topic is invalid. + * @param topic The topic to validate. + * @param method The method name to use in error messages. + * @param errorInfo The error info to use if the topic is invalid. */ private validateTopicType( topic: string | string[], @@ -1042,9 +1030,9 @@ export class Messaging { /** * Validates the provided topic. If invalid, an error will be thrown. * - * @param {string} topic The topic to validate. - * @param {string} method The method name to use in error messages. - * @param {ErrorInfo?} [errorInfo] The error info to use if the topic is invalid. + * @param topic The topic to validate. + * @param method The method name to use in error messages. + * @param errorInfo The error info to use if the topic is invalid. */ private validateTopic( topic: string, @@ -1063,9 +1051,9 @@ export class Messaging { /** * Normalizes the provided topic name by prepending it with '/topics/', if necessary. * - * @param {string} topic The topic name to normalize. + * @param topic The topic name to normalize. * - * @return {string} The normalized topic name. + * @returns The normalized topic name. */ private normalizeTopic(topic: string): string { if (!/^\/topics\//.test(topic)) { diff --git a/src/project-management/android-app.ts b/src/project-management/android-app.ts index 5756051c28..dc63b47da9 100644 --- a/src/project-management/android-app.ts +++ b/src/project-management/android-app.ts @@ -42,8 +42,7 @@ export interface AndroidAppMetadata extends AppMetadata { /** * A reference to a Firebase Android app. * - * Do not call this constructor directly. Instead, use - * [`projectManagement.androidApp()`](projectManagement.ProjectManagement#androidApp). + * Do not call this constructor directly. Instead, use {@link ProjectManagement.androidApp}. */ export class AndroidApp { @@ -66,7 +65,7 @@ export class AndroidApp { /** * Retrieves metadata about this Android app. * - * @return A promise that resolves to the retrieved metadata about this Android app. + * @returns A promise that resolves to the retrieved metadata about this Android app. */ public getMetadata(): Promise { return this.requestHandler.getResource(this.resourceName) @@ -101,7 +100,7 @@ export class AndroidApp { * * @param newDisplayName The new display name to set. * - * @return A promise that resolves when the display name has been set. + * @returns A promise that resolves when the display name has been set. */ public setDisplayName(newDisplayName: string): Promise { return this.requestHandler.setDisplayName(this.resourceName, newDisplayName); @@ -110,7 +109,7 @@ export class AndroidApp { /** * Gets the list of SHA certificates associated with this Android app in Firebase. * - * @return The list of SHA-1 and SHA-256 certificates associated with this Android app in + * @returns The list of SHA-1 and SHA-256 certificates associated with this Android app in * Firebase. */ public getShaCertificates(): Promise { @@ -151,7 +150,7 @@ export class AndroidApp { * * @param certificateToAdd The SHA certificate to add. * - * @return A promise that resolves when the given certificate + * @returns A promise that resolves when the given certificate * has been added to the Android app. */ public addShaCertificate(certificateToAdd: ShaCertificate): Promise { @@ -163,7 +162,7 @@ export class AndroidApp { * * @param certificateToDelete The SHA certificate to delete. * - * @return A promise that resolves when the specified + * @returns A promise that resolves when the specified * certificate has been removed from the Android app. */ public deleteShaCertificate(certificateToDelete: ShaCertificate): Promise { @@ -179,7 +178,7 @@ export class AndroidApp { /** * Gets the configuration artifact associated with this app. * - * @return A promise that resolves to the Android app's + * @returns A promise that resolves to the Android app's * Firebase config file, in UTF-8 string format. This string is typically * intended to be written to a JSON file that gets shipped with your Android * app. diff --git a/src/project-management/index.ts b/src/project-management/index.ts index 97ff5c203c..92dd34f449 100644 --- a/src/project-management/index.ts +++ b/src/project-management/index.ts @@ -24,14 +24,11 @@ export { AndroidApp, AndroidAppMetadata, ShaCertificate } from './android-app'; export { IosApp, IosAppMetadata } from './ios-app'; /** - * Gets the {@link projectManagement.ProjectManagement - * `ProjectManagement`} service for the default app or a given app. + * Gets the {@link ProjectManagement} service for the default app or a given app. * * `getProjectManagement()` can be called with no arguments to access the - * default app's {@link projectManagement.ProjectManagement - * `ProjectManagement`} service, or as `getProjectManagement(app)` to access - * the {@link projectManagement.ProjectManagement `ProjectManagement`} - * service associated with a specific app. + * default app's `ProjectManagement` service, or as `getProjectManagement(app)` to access + * the `ProjectManagement` service associated with a specific app. * * @example * ```javascript @@ -48,7 +45,7 @@ export { IosApp, IosAppMetadata } from './ios-app'; * @param app Optional app whose `ProjectManagement` service * to return. If not provided, the default `ProjectManagement` service will * be returned. * - * @return The default `ProjectManagement` service if no app is provided or the + * @returns The default `ProjectManagement` service if no app is provided or the * `ProjectManagement` service associated with the provided app. */ export function getProjectManagement(app?: App): ProjectManagement { diff --git a/src/project-management/ios-app.ts b/src/project-management/ios-app.ts index 0becebbb02..dc1ff3a23c 100644 --- a/src/project-management/ios-app.ts +++ b/src/project-management/ios-app.ts @@ -39,8 +39,7 @@ export interface IosAppMetadata extends AppMetadata { /** * A reference to a Firebase iOS app. * - * Do not call this constructor directly. Instead, use - * [`projectManagement.iosApp()`](projectManagement.ProjectManagement#iosApp). + * Do not call this constructor directly. Instead, use {@link ProjectManagement.iosApp}. */ export class IosApp { @@ -63,7 +62,7 @@ export class IosApp { /** * Retrieves metadata about this iOS app. * - * @return A promise that + * @returns A promise that * resolves to the retrieved metadata about this iOS app. */ public getMetadata(): Promise { @@ -99,7 +98,7 @@ export class IosApp { * * @param newDisplayName The new display name to set. * - * @return A promise that resolves when the display name has + * @returns A promise that resolves when the display name has * been set. */ public setDisplayName(newDisplayName: string): Promise { @@ -109,7 +108,7 @@ export class IosApp { /** * Gets the configuration artifact associated with this app. * - * @return A promise that resolves to the iOS app's Firebase + * @returns A promise that resolves to the iOS app's Firebase * config file, in UTF-8 string format. This string is typically intended to * be written to a plist file that gets shipped with your iOS app. */ diff --git a/src/project-management/project-management-api-request-internal.ts b/src/project-management/project-management-api-request-internal.ts index 4f5b876435..7c9651e50a 100644 --- a/src/project-management/project-management-api-request-internal.ts +++ b/src/project-management/project-management-api-request-internal.ts @@ -58,7 +58,7 @@ export function assertServerResponse( * Class that provides mechanism to send requests to the Firebase project management backend * endpoints. * - * @private + * @internal */ export class ProjectManagementRequestHandler { private readonly baseUrl: string = @@ -122,7 +122,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the project whose Android + * @param parentResourceName Fully-qualified resource name of the project whose Android * apps you want to list. */ public listAndroidApps(parentResourceName: string): Promise { @@ -134,7 +134,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the project whose iOS apps + * @param parentResourceName Fully-qualified resource name of the project whose iOS apps * you want to list. */ public listIosApps(parentResourceName: string): Promise { @@ -146,7 +146,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the project whose iOS apps + * @param parentResourceName Fully-qualified resource name of the project whose iOS apps * you want to list. */ public listAppMetadata(parentResourceName: string): Promise { @@ -158,7 +158,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the project that you want + * @param parentResourceName Fully-qualified resource name of the project that you want * to create the Android app within. */ public createAndroidApp( @@ -185,7 +185,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the project that you want + * @param parentResourceName Fully-qualified resource name of the project that you want * to create the iOS app within. */ public createIosApp( @@ -212,7 +212,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} resourceName Fully-qualified resource name of the entity whose display name you + * @param resourceName Fully-qualified resource name of the entity whose display name you * want to set. */ public setDisplayName(resourceName: string, newDisplayName: string): Promise { @@ -226,7 +226,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the Android app whose SHA + * @param parentResourceName Fully-qualified resource name of the Android app whose SHA * certificates you want to get. */ public getAndroidShaCertificates(parentResourceName: string): Promise { @@ -235,7 +235,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the Android app that you + * @param parentResourceName Fully-qualified resource name of the Android app that you * want to add the given SHA certificate to. */ public addAndroidShaCertificate( @@ -250,7 +250,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the app whose config you + * @param parentResourceName Fully-qualified resource name of the app whose config you * want to get. */ public getConfig(parentResourceName: string): Promise { @@ -259,7 +259,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} parentResourceName Fully-qualified resource name of the entity that you want to + * @param parentResourceName Fully-qualified resource name of the entity that you want to * get. */ public getResource(parentResourceName: string): Promise { @@ -267,7 +267,7 @@ export class ProjectManagementRequestHandler { } /** - * @param {string} resourceName Fully-qualified resource name of the entity that you want to + * @param resourceName Fully-qualified resource name of the entity that you want to * delete. */ public deleteResource(resourceName: string): Promise { diff --git a/src/project-management/project-management-namespace.ts b/src/project-management/project-management-namespace.ts index e37903cda8..ae9e4cf0b3 100644 --- a/src/project-management/project-management-namespace.ts +++ b/src/project-management/project-management-namespace.ts @@ -31,14 +31,12 @@ import { } from './ios-app'; /** - * Gets the {@link projectManagement.ProjectManagement - * `ProjectManagement`} service for the default app or a given app. + * Gets the {@link firebase-admin.project-management#ProjectManagement} service for the + * default app or a given app. * * `admin.projectManagement()` can be called with no arguments to access the - * default app's {@link projectManagement.ProjectManagement - * `ProjectManagement`} service, or as `admin.projectManagement(app)` to access - * the {@link projectManagement.ProjectManagement `ProjectManagement`} - * service associated with a specific app. + * default app's `ProjectManagement` service, or as `admin.projectManagement(app)` to access + * the `ProjectManagement` service associated with a specific app. * * @example * ```javascript @@ -55,7 +53,7 @@ import { * @param app Optional app whose `ProjectManagement` service * to return. If not provided, the default `ProjectManagement` service will * be returned. * - * @return The default `ProjectManagement` service if no app is provided or the + * @returns The default `ProjectManagement` service if no app is provided or the * `ProjectManagement` service associated with the provided app. */ export declare function projectManagement(app?: App): projectManagement.ProjectManagement; diff --git a/src/project-management/project-management.ts b/src/project-management/project-management.ts index 5b0d691823..ff7a5089bb 100644 --- a/src/project-management/project-management.ts +++ b/src/project-management/project-management.ts @@ -25,9 +25,6 @@ import { AppMetadata, AppPlatform } from './app-metadata'; /** * The Firebase ProjectManagement service interface. - * - * Do not call this constructor directly. Instead, use - * [`admin.projectManagement()`](projectManagement#projectManagement). */ export class ProjectManagement { @@ -53,7 +50,7 @@ export class ProjectManagement { /** * Lists up to 100 Firebase Android apps associated with this Firebase project. * - * @return The list of Android apps. + * @returns The list of Android apps. */ public listAndroidApps(): Promise { return this.listPlatformApps('android', 'listAndroidApps()'); @@ -62,7 +59,7 @@ export class ProjectManagement { /** * Lists up to 100 Firebase iOS apps associated with this Firebase project. * - * @return The list of iOS apps. + * @returns The list of iOS apps. */ public listIosApps(): Promise { return this.listPlatformApps('ios', 'listIosApps()'); @@ -76,7 +73,7 @@ export class ProjectManagement { * * @param appId The `appId` of the Android app to reference. * - * @return An `AndroidApp` object that references the specified Firebase Android app. + * @returns An `AndroidApp` object that references the specified Firebase Android app. */ public androidApp(appId: string): AndroidApp { return new AndroidApp(appId, this.requestHandler); @@ -90,7 +87,7 @@ export class ProjectManagement { * * @param appId The `appId` of the iOS app to reference. * - * @return An `iOSApp` object that references the specified Firebase iOS app. + * @returns An `iOSApp` object that references the specified Firebase iOS app. */ public iosApp(appId: string): IosApp { return new IosApp(appId, this.requestHandler); @@ -103,7 +100,7 @@ export class ProjectManagement { * * @param shaHash The SHA-1 or SHA-256 hash for this certificate. * - * @return A `ShaCertificate` object contains the specified SHA hash. + * @returns A `ShaCertificate` object contains the specified SHA hash. */ public shaCertificate(shaHash: string): ShaCertificate { return new ShaCertificate(shaHash); @@ -117,7 +114,7 @@ export class ProjectManagement { * @param displayName An optional user-assigned display name for this * new app. * - * @return A promise that resolves to the newly created Android app. + * @returns A promise that resolves to the newly created Android app. */ public createAndroidApp(packageName: string, displayName?: string): Promise { return this.getResourceName() @@ -145,7 +142,7 @@ export class ProjectManagement { * @param displayName An optional user-assigned display name for this * new app. * - * @return A promise that resolves to the newly created iOS app. + * @returns A promise that resolves to the newly created iOS app. */ public createIosApp(bundleId: string, displayName?: string): Promise { return this.getResourceName() @@ -169,7 +166,7 @@ export class ProjectManagement { /** * Lists up to 100 Firebase apps associated with this Firebase project. * - * @return A promise that resolves to the metadata list of the apps. + * @returns A promise that resolves to the metadata list of the apps. */ public listAppMetadata(): Promise { return this.getResourceName() @@ -189,7 +186,7 @@ export class ProjectManagement { * * @param newDisplayName The new display name to be updated. * - * @return A promise that resolves when the project display name has been updated. + * @returns A promise that resolves when the project display name has been updated. */ public setDisplayName(newDisplayName: string): Promise { return this.getResourceName() diff --git a/src/remote-config/index.ts b/src/remote-config/index.ts index 0d0d6e6612..24c8a1fa4e 100644 --- a/src/remote-config/index.ts +++ b/src/remote-config/index.ts @@ -35,14 +35,11 @@ export { export { RemoteConfig } from './remote-config'; /** - * Gets the {@link remoteConfig.RemoteConfig `RemoteConfig`} service for the - * default app or a given app. + * Gets the {@link RemoteConfig} service for the default app or a given app. * * `getRemoteConfig()` can be called with no arguments to access the default - * app's {@link remoteConfig.RemoteConfig `RemoteConfig`} service or as - * `getRemoteConfig(app)` to access the - * {@link remoteConfig.RemoteConfig `RemoteConfig`} service associated with a - * specific app. + * app's `RemoteConfig` service or as `getRemoteConfig(app)` to access the + * `RemoteConfig` service associated with a specific app. * * @example * ```javascript @@ -59,7 +56,7 @@ export { RemoteConfig } from './remote-config'; * @param app Optional app for which to return the `RemoteConfig` service. * If not provided, the default `RemoteConfig` service is returned. * - * @return The default `RemoteConfig` service if no + * @returns The default `RemoteConfig` service if no * app is provided, or the `RemoteConfig` service associated with the provided * app. */ diff --git a/src/remote-config/remote-config-api-client-internal.ts b/src/remote-config/remote-config-api-client-internal.ts index 1e91b497f3..9ea77bb532 100644 --- a/src/remote-config/remote-config-api-client-internal.ts +++ b/src/remote-config/remote-config-api-client-internal.ts @@ -336,7 +336,7 @@ export class RemoteConfigApiClient { * * @param {ListVersionsOptions} options An options object to be validated. * - * @return {ListVersionsOptions} A copy of the provided options object with timestamps converted + * @returns {ListVersionsOptions} A copy of the provided options object with timestamps converted * to UTC Zulu format. */ private validateListVersionsOptions(options: ListVersionsOptions): ListVersionsOptions { diff --git a/src/remote-config/remote-config-api.ts b/src/remote-config/remote-config-api.ts index 9493e855d8..b8d49640c7 100644 --- a/src/remote-config/remote-config-api.ts +++ b/src/remote-config/remote-config-api.ts @@ -35,7 +35,7 @@ export interface RemoteConfigCondition { /** * The logic of this condition. * See the documentation on - * {@link https://firebase.google.com/docs/remote-config/condition-reference condition expressions} + * {@link https://firebase.google.com/docs/remote-config/condition-reference | condition expressions} * for the expected syntax of this field. */ expression: string; diff --git a/src/remote-config/remote-config-namespace.ts b/src/remote-config/remote-config-namespace.ts index d50fcb0aee..39839c6f07 100644 --- a/src/remote-config/remote-config-namespace.ts +++ b/src/remote-config/remote-config-namespace.ts @@ -32,14 +32,12 @@ import { import { RemoteConfig as TRemoteConfig } from './remote-config'; /** - * Gets the {@link remoteConfig.RemoteConfig `RemoteConfig`} service for the + * Gets the {@link firebase-admin.remote-config#RemoteConfig} service for the * default app or a given app. * * `admin.remoteConfig()` can be called with no arguments to access the default - * app's {@link remoteConfig.RemoteConfig `RemoteConfig`} service or as - * `admin.remoteConfig(app)` to access the - * {@link remoteConfig.RemoteConfig `RemoteConfig`} service associated with a - * specific app. + * app's `RemoteConfig` service or as `admin.remoteConfig(app)` to access the + * `RemoteConfig` service associated with a specific app. * * @example * ```javascript @@ -56,7 +54,7 @@ import { RemoteConfig as TRemoteConfig } from './remote-config'; * @param app Optional app for which to return the `RemoteConfig` service. * If not provided, the default `RemoteConfig` service is returned. * - * @return The default `RemoteConfig` service if no + * @returns The default `RemoteConfig` service if no * app is provided, or the `RemoteConfig` service associated with the provided * app. */ diff --git a/src/remote-config/remote-config.ts b/src/remote-config/remote-config.ts index c6b23517ef..2b35a216a2 100644 --- a/src/remote-config/remote-config.ts +++ b/src/remote-config/remote-config.ts @@ -45,10 +45,9 @@ export class RemoteConfig { } /** - * Gets the current active version of the {@link remoteConfig.RemoteConfigTemplate - * `RemoteConfigTemplate`} of the project. + * Gets the current active version of the {@link RemoteConfigTemplate} of the project. * - * @return A promise that fulfills with a `RemoteConfigTemplate`. + * @returns A promise that fulfills with a `RemoteConfigTemplate`. */ public getTemplate(): Promise { return this.client.getTemplate() @@ -58,12 +57,11 @@ export class RemoteConfig { } /** - * Gets the requested version of the {@link remoteConfig.RemoteConfigTemplate - * `RemoteConfigTemplate`} of the project. + * Gets the requested version of the {@link RemoteConfigTemplate} of the project. * * @param versionNumber Version number of the Remote Config template to look up. * - * @return A promise that fulfills with a `RemoteConfigTemplate`. + * @returns A promise that fulfills with a `RemoteConfigTemplate`. */ public getTemplateAtVersion(versionNumber: number | string): Promise { return this.client.getTemplateAtVersion(versionNumber) @@ -73,7 +71,7 @@ export class RemoteConfig { } /** - * Validates a {@link remoteConfig.RemoteConfigTemplate `RemoteConfigTemplate`}. + * Validates a {@link RemoteConfigTemplate}. * * @param template The Remote Config template to be validated. * @returns A promise that fulfills with the validated `RemoteConfigTemplate`. @@ -90,14 +88,14 @@ export class RemoteConfig { * * @param template The Remote Config template to be published. * @param options Optional options object when publishing a Remote Config template: - * - {boolean} `force` Setting this to `true` forces the Remote Config template to + * - `force`: Setting this to `true` forces the Remote Config template to * be updated and circumvent the ETag. This approach is not recommended * because it risks causing the loss of updates to your Remote Config * template if multiple clients are updating the Remote Config template. - * See {@link https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates + * See {@link https://firebase.google.com/docs/remote-config/use-config-rest#etag_usage_and_forced_updates | * ETag usage and forced updates}. * - * @return A Promise that fulfills with the published `RemoteConfigTemplate`. + * @returns A Promise that fulfills with the published `RemoteConfigTemplate`. */ public publishTemplate(template: RemoteConfigTemplate, options?: { force: boolean }): Promise { return this.client.publishTemplate(template, options) @@ -116,7 +114,7 @@ export class RemoteConfig { * been deleted due to staleness. Only the last 300 versions are stored. * All versions that correspond to non-active Remote Config templates (that is, all except the * template that is being fetched by clients) are also deleted if they are more than 90 days old. - * @return A promise that fulfills with the published `RemoteConfigTemplate`. + * @returns A promise that fulfills with the published `RemoteConfigTemplate`. */ public rollback(versionNumber: number | string): Promise { return this.client.rollback(versionNumber) @@ -132,7 +130,7 @@ export class RemoteConfig { * template that is being fetched by clients) are also deleted if they are older than 90 days. * * @param options Optional options object for getting a list of versions. - * @return A promise that fulfills with a `ListVersionsResult`. + * @returns A promise that fulfills with a `ListVersionsResult`. */ public listVersions(options?: ListVersionsOptions): Promise { return this.client.listVersions(options) @@ -149,7 +147,7 @@ export class RemoteConfig { * * @param json The JSON string to populate a Remote Config template. * - * @return A new template instance. + * @returns A new template instance. */ public createTemplateFromJSON(json: string): RemoteConfigTemplate { if (!validator.isNonEmptyString(json)) { @@ -234,14 +232,16 @@ class RemoteConfigTemplateImpl implements RemoteConfigTemplate { /** * Gets the ETag of the template. * - * @return The ETag of the Remote Config template. + * @returns The ETag of the Remote Config template. */ get etag(): string { return this.etagInternal; } /** - * @return A JSON-serializable representation of this object. + * Returns a JSON-serializable representation of this object. + * + * @returns A JSON-serializable representation of this object. */ public toJSON(): object { return { @@ -359,7 +359,7 @@ class VersionImpl implements Version { } /** - * @return A JSON-serializable representation of this object. + * @returns A JSON-serializable representation of this object. */ public toJSON(): object { return { diff --git a/src/security-rules/index.ts b/src/security-rules/index.ts index 3537f6bf6a..11770d381d 100644 --- a/src/security-rules/index.ts +++ b/src/security-rules/index.ts @@ -27,14 +27,11 @@ export { } from './security-rules'; /** - * Gets the {@link securityRules.SecurityRules - * `SecurityRules`} service for the default app or a given app. + * Gets the {@link SecurityRules} service for the default app or a given app. * * `admin.securityRules()` can be called with no arguments to access the - * default app's {@link securityRules.SecurityRules - * `SecurityRules`} service, or as `admin.securityRules(app)` to access - * the {@link securityRules.SecurityRules `SecurityRules`} - * service associated with a specific app. + * default app's `SecurityRules` service, or as `admin.securityRules(app)` to access + * the `SecurityRules` service associated with a specific app. * * @example * ```javascript @@ -51,7 +48,7 @@ export { * @param app Optional app to return the `SecurityRules` service * for. If not provided, the default `SecurityRules` service * is returned. - * @return The default `SecurityRules` service if no app is provided, or the + * @returns The default `SecurityRules` service if no app is provided, or the * `SecurityRules` service associated with the provided app. */ export function getSecurityRules(app?: App): SecurityRules { diff --git a/src/security-rules/security-rules-namespace.ts b/src/security-rules/security-rules-namespace.ts index 6556440f54..dfcd1fffe1 100644 --- a/src/security-rules/security-rules-namespace.ts +++ b/src/security-rules/security-rules-namespace.ts @@ -48,7 +48,7 @@ import { * @param app Optional app to return the `SecurityRules` service * for. If not provided, the default `SecurityRules` service * is returned. - * @return The default `SecurityRules` service if no app is provided, or the + * @returns The default `SecurityRules` service if no app is provided, or the * `SecurityRules` service associated with the provided app. */ export declare function securityRules(app?: App): securityRules.SecurityRules; diff --git a/src/security-rules/security-rules.ts b/src/security-rules/security-rules.ts index 83c49b9e86..6a5eafa3d9 100644 --- a/src/security-rules/security-rules.ts +++ b/src/security-rules/security-rules.ts @@ -24,8 +24,7 @@ import { FirebaseSecurityRulesError } from './security-rules-internal'; /** * A source file containing some Firebase security rules. The content includes raw * source code including text formatting, indentation and comments. Use the - * [`securityRules.createRulesFileFromSource()`](securityRules.SecurityRules#createRulesFileFromSource) - * method to create new instances of this type. + * {@link SecurityRules.createRulesFileFromSource} method to create new instances of this type. */ export interface RulesFile { readonly name: string; @@ -38,8 +37,7 @@ export interface RulesFile { export interface RulesetMetadata { /** * Name of the `Ruleset` as a short string. This can be directly passed into APIs - * like {@link securityRules.SecurityRules.getRuleset `securityRules.getRuleset()`} - * and {@link securityRules.SecurityRules.deleteRuleset `securityRules.deleteRuleset()`}. + * like {@link SecurityRules.getRuleset} and {@link SecurityRules.deleteRuleset}. */ readonly name: string; /** @@ -142,14 +140,14 @@ export class SecurityRules { } /** - * Gets the {@link securityRules.Ruleset `Ruleset`} identified by the given + * Gets the {@link Ruleset} identified by the given * name. The input name should be the short name string without the project ID * prefix. For example, to retrieve the `projects/project-id/rulesets/my-ruleset`, * pass the short name "my-ruleset". Rejects with a `not-found` error if the * specified `Ruleset` cannot be found. * * @param name Name of the `Ruleset` to retrieve. - * @return A promise that fulfills with the specified `Ruleset`. + * @returns A promise that fulfills with the specified `Ruleset`. */ public getRuleset(name: string): Promise { return this.client.getRuleset(name) @@ -159,22 +157,22 @@ export class SecurityRules { } /** - * Gets the {@link securityRules.Ruleset `Ruleset`} currently applied to + * Gets the {@link Ruleset} currently applied to * Cloud Firestore. Rejects with a `not-found` error if no ruleset is applied * on Firestore. * - * @return A promise that fulfills with the Firestore ruleset. + * @returns A promise that fulfills with the Firestore ruleset. */ public getFirestoreRuleset(): Promise { return this.getRulesetForRelease(SecurityRules.CLOUD_FIRESTORE); } /** - * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given + * Creates a new {@link Ruleset} from the given * source, and applies it to Cloud Firestore. * * @param source Rules source to apply. - * @return A promise that fulfills when the ruleset is created and released. + * @returns A promise that fulfills when the ruleset is created and released. */ public releaseFirestoreRulesetFromSource(source: string | Buffer): Promise { return Promise.resolve() @@ -191,26 +189,26 @@ export class SecurityRules { } /** - * Applies the specified {@link securityRules.Ruleset `Ruleset`} ruleset + * Applies the specified {@link Ruleset} ruleset * to Cloud Firestore. * * @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object * containing the name. - * @return A promise that fulfills when the ruleset is released. + * @returns A promise that fulfills when the ruleset is released. */ public releaseFirestoreRuleset(ruleset: string | RulesetMetadata): Promise { return this.releaseRuleset(ruleset, SecurityRules.CLOUD_FIRESTORE); } /** - * Gets the {@link securityRules.Ruleset `Ruleset`} currently applied to a + * Gets the {@link Ruleset} currently applied to a * Cloud Storage bucket. Rejects with a `not-found` error if no ruleset is applied * on the bucket. * * @param bucket Optional name of the Cloud Storage bucket to be retrieved. If not * specified, retrieves the ruleset applied on the default bucket configured via * `AppOptions`. - * @return A promise that fulfills with the Cloud Storage ruleset. + * @returns A promise that fulfills with the Cloud Storage ruleset. */ public getStorageRuleset(bucket?: string): Promise { return Promise.resolve() @@ -223,14 +221,14 @@ export class SecurityRules { } /** - * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given + * Creates a new {@link Ruleset} from the given * source, and applies it to a Cloud Storage bucket. * * @param source Rules source to apply. * @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If * not specified, applies the ruleset on the default bucket configured via - * {@link AppOptions `AppOptions`}. - * @return A promise that fulfills when the ruleset is created and released. + * {@link firebase-admin.app#AppOptions}. + * @returns A promise that fulfills when the ruleset is created and released. */ public releaseStorageRulesetFromSource(source: string | Buffer, bucket?: string): Promise { return Promise.resolve() @@ -250,15 +248,15 @@ export class SecurityRules { } /** - * Applies the specified {@link securityRules.Ruleset `Ruleset`} ruleset + * Applies the specified {@link Ruleset} ruleset * to a Cloud Storage bucket. * * @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object * containing the name. * @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If * not specified, applies the ruleset on the default bucket configured via - * {@link AppOptions `AppOptions`}. - * @return A promise that fulfills when the ruleset is released. + * {@link firebase-admin.app#AppOptions}. + * @returns A promise that fulfills when the ruleset is released. */ public releaseStorageRuleset(ruleset: string | RulesetMetadata, bucket?: string): Promise { return Promise.resolve() @@ -271,7 +269,7 @@ export class SecurityRules { } /** - * Creates a {@link securityRules.RulesFile `RuleFile`} with the given name + * Creates a {@link RulesFile} with the given name * and source. Throws an error if any of the arguments are invalid. This is a local * operation, and does not involve any network API calls. * @@ -285,7 +283,7 @@ export class SecurityRules { * @param name Name to assign to the rules file. This is usually a short file name that * helps identify the file in a ruleset. * @param source Contents of the rules file. - * @return A new rules file instance. + * @returns A new rules file instance. */ public createRulesFileFromSource(name: string, source: string | Buffer): RulesFile { if (!validator.isNonEmptyString(name)) { @@ -310,8 +308,7 @@ export class SecurityRules { } /** - * Creates a new {@link securityRules.Ruleset `Ruleset`} from the given - * {@link securityRules.RulesFile `RuleFile`}. + * Creates a new {@link Ruleset} from the given {@link RulesFile}. * * @param file Rules file to include in the new `Ruleset`. * @returns A promise that fulfills with the newly created `Ruleset`. @@ -330,14 +327,14 @@ export class SecurityRules { } /** - * Deletes the {@link securityRules.Ruleset `Ruleset`} identified by the given + * Deletes the {@link Ruleset} identified by the given * name. The input name should be the short name string without the project ID * prefix. For example, to delete the `projects/project-id/rulesets/my-ruleset`, * pass the short name "my-ruleset". Rejects with a `not-found` error if the * specified `Ruleset` cannot be found. * * @param name Name of the `Ruleset` to delete. - * @return A promise that fulfills when the `Ruleset` is deleted. + * @returns A promise that fulfills when the `Ruleset` is deleted. */ public deleteRuleset(name: string): Promise { return this.client.deleteRuleset(name); @@ -350,7 +347,7 @@ export class SecurityRules { * limit. * @param nextPageToken The next page token. If not specified, returns rulesets * starting without any offset. - * @return A promise that fulfills with a page of rulesets. + * @returns A promise that fulfills with a page of rulesets. */ public listRulesetMetadata(pageSize = 100, nextPageToken?: string): Promise { return this.client.listRulesets(pageSize, nextPageToken) diff --git a/src/storage/index.ts b/src/storage/index.ts index 6eeca2a107..878174afa2 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -21,14 +21,11 @@ import { Storage } from './storage'; export { Storage } from './storage'; /** - * Gets the {@link storage.Storage `Storage`} service for the - * default app or a given app. + * Gets the {@link Storage} service for the default app or a given app. * * `getStorage()` can be called with no arguments to access the default - * app's {@link storage.Storage `Storage`} service or as - * `getStorage(app)` to access the - * {@link storage.Storage `Storage`} service associated with a - * specific app. + * app's `Storage` service or as `getStorage(app)` to access the + * `Storage` service associated with a specific app. * * @example * ```javascript diff --git a/src/storage/storage-namespace.ts b/src/storage/storage-namespace.ts index 1e78d076d6..26ebddf72c 100644 --- a/src/storage/storage-namespace.ts +++ b/src/storage/storage-namespace.ts @@ -18,14 +18,12 @@ import { App } from '../app'; import { Storage as TStorage } from './storage'; /** - * Gets the {@link storage.Storage `Storage`} service for the + * Gets the {@link firebase-admin.storage#Storage} service for the * default app or a given app. * * `admin.storage()` can be called with no arguments to access the default - * app's {@link storage.Storage `Storage`} service or as - * `admin.storage(app)` to access the - * {@link storage.Storage `Storage`} service associated with a - * specific app. + * app's `Storage` service or as `admin.storage(app)` to access the + * `Storage` service associated with a specific app. * * @example * ```javascript diff --git a/src/storage/storage.ts b/src/storage/storage.ts index dc36b72f52..5aebb7c4b4 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -48,7 +48,7 @@ export class Storage { if (!process.env.STORAGE_EMULATOR_HOST && process.env.FIREBASE_STORAGE_EMULATOR_HOST) { process.env.STORAGE_EMULATOR_HOST = process.env.FIREBASE_STORAGE_EMULATOR_HOST; } - + let storage: typeof StorageClient; try { storage = require('@google-cloud/storage').Storage; @@ -92,7 +92,7 @@ export class Storage { * * @param name Optional name of the bucket to be retrieved. If name is not specified, * retrieves a reference to the default bucket. - * @returns A [Bucket](https://cloud.google.com/nodejs/docs/reference/storage/latest/Bucket) + * @returns A {@link https://cloud.google.com/nodejs/docs/reference/storage/latest/Bucket | Bucket} * instance as defined in the `@google-cloud/storage` package. */ public bucket(name?: string): Bucket { diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index 221a5221f2..dfad60b937 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -260,8 +260,8 @@ export class HttpClient { * header should be explicitly set by the caller. To send a JSON leaf value (e.g. "foo", 5), parse it into JSON, * and pass as a string or a Buffer along with the appropriate content-type header. * - * @param {HttpRequest} config HTTP request to be sent. - * @return {Promise} A promise that resolves with the response details. + * @param config HTTP request to be sent. + * @returns A promise that resolves with the response details. */ public send(config: HttpRequestConfig): Promise { return this.sendWithRetry(config); @@ -271,9 +271,9 @@ export class HttpClient { * Sends an HTTP request. In the event of an error, retries the HTTP request according to the * RetryConfig set on the HttpClient. * - * @param {HttpRequestConfig} config HTTP request to be sent. - * @param {number} retryAttempts Number of retries performed up to now. - * @return {Promise} A promise that resolves with the response details. + * @param config HTTP request to be sent. + * @param retryAttempts Number of retries performed up to now. + * @returns A promise that resolves with the response details. */ private sendWithRetry(config: HttpRequestConfig, retryAttempts = 0): Promise { return AsyncHttpCall.invoke(config) @@ -323,9 +323,9 @@ export class HttpClient { * Checks if a failed request is eligible for a retry, and if so returns the duration to wait before initiating * the retry. * - * @param {number} retryAttempts Number of retries completed up to now. - * @param {LowLevelError} err The last encountered error. - * @returns {[number, boolean]} A 2-tuple where the 1st element is the duration to wait before another retry, and the + * @param retryAttempts Number of retries completed up to now. + * @param err The last encountered error. + * @returns A 2-tuple where the 1st element is the duration to wait before another retry, and the * 2nd element is a boolean indicating whether the request is eligible for a retry or not. */ private getRetryDelayMillis(retryAttempts: number, err: LowLevelError): [number, boolean] { @@ -401,9 +401,9 @@ export class HttpClient { /** * Parses a full HTTP response message containing both a header and a body. * - * @param {string|Buffer} response The HTTP response to be parsed. - * @param {HttpRequestConfig} config The request configuration that resulted in the HTTP response. - * @return {HttpResponse} An object containing the parsed HTTP status, headers and the body. + * @param response The HTTP response to be parsed. + * @param config The request configuration that resulted in the HTTP response. + * @returns An object containing the parsed HTTP status, headers and the body. */ export function parseHttpResponse( response: string | Buffer, config: HttpRequestConfig): HttpResponse { @@ -839,8 +839,8 @@ export class AuthorizedHttpClient extends HttpClient { /** * Class that defines all the settings for the backend API endpoint. * - * @param {string} endpoint The Firebase Auth backend endpoint. - * @param {HttpMethod} httpMethod The http method for that endpoint. + * @param endpoint The Firebase Auth backend endpoint. + * @param httpMethod The http method for that endpoint. * @constructor */ export class ApiSettings { @@ -852,19 +852,19 @@ export class ApiSettings { .setResponseValidator(null); } - /** @return {string} The backend API endpoint. */ + /** @returns The backend API endpoint. */ public getEndpoint(): string { return this.endpoint; } - /** @return {HttpMethod} The request HTTP method. */ + /** @returns The request HTTP method. */ public getHttpMethod(): HttpMethod { return this.httpMethod; } /** - * @param {ApiCallbackFunction} requestValidator The request validator. - * @return {ApiSettings} The current API settings instance. + * @param requestValidator The request validator. + * @returns The current API settings instance. */ public setRequestValidator(requestValidator: ApiCallbackFunction | null): ApiSettings { const nullFunction: ApiCallbackFunction = () => undefined; @@ -872,14 +872,14 @@ export class ApiSettings { return this; } - /** @return {ApiCallbackFunction} The request validator. */ + /** @returns The request validator. */ public getRequestValidator(): ApiCallbackFunction { return this.requestValidator; } /** - * @param {ApiCallbackFunction} responseValidator The response validator. - * @return {ApiSettings} The current API settings instance. + * @param responseValidator The response validator. + * @returns The current API settings instance. */ public setResponseValidator(responseValidator: ApiCallbackFunction | null): ApiSettings { const nullFunction: ApiCallbackFunction = () => undefined; @@ -887,7 +887,7 @@ export class ApiSettings { return this; } - /** @return {ApiCallbackFunction} The response validator. */ + /** @returns The response validator. */ public getResponseValidator(): ApiCallbackFunction { return this.responseValidator; } @@ -938,10 +938,10 @@ export class ExponentialBackoffPoller extends EventEmitter { /** * Poll the provided callback with exponential backoff. * - * @param {() => Promise} callback The callback to be called for each poll. If the + * @param callback The callback to be called for each poll. If the * callback resolves to a falsey value, polling will continue. Otherwise, the truthy * resolution will be used to resolve the promise returned by this method. - * @return {Promise} A Promise which resolves to the truthy value returned by the provided + * @returns A Promise which resolves to the truthy value returned by the provided * callback when polling is complete. */ public poll(callback: () => Promise): Promise { diff --git a/src/utils/deep-copy.ts b/src/utils/deep-copy.ts index 8e5bee9d8b..4b73e7da8f 100644 --- a/src/utils/deep-copy.ts +++ b/src/utils/deep-copy.ts @@ -18,8 +18,8 @@ /** * Returns a deep copy of an object or array. * - * @param {object|array} value The object or array to deep copy. - * @return {object|array} A deep copy of the provided object or array. + * @param value The object or array to deep copy. + * @returns A deep copy of the provided object or array. */ export function deepCopy(value: T): T { return deepExtend(undefined, value); @@ -37,9 +37,9 @@ export function deepCopy(value: T): T { * Note that the target can be a function, in which case the properties in the source object are * copied onto it as static properties of the function. * - * @param {any} target The value which is being extended. - * @param {any} source The value whose properties are extending the target. - * @return {any} The target value. + * @param target The value which is being extended. + * @param source The value whose properties are extending the target. + * @returns The target value. */ export function deepExtend(target: any, source: any): any { if (!(source instanceof Object)) { diff --git a/src/utils/error.ts b/src/utils/error.ts index 91fe79debb..2953d9148e 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -36,7 +36,7 @@ interface ServerToClientCode { /** * Firebase error code structure. This extends Error. * - * @param {ErrorInfo} errorInfo The error information (code and message). + * @param errorInfo The error information (code and message). * @constructor */ export class FirebaseError extends Error implements FirebaseErrorInterface { @@ -50,17 +50,17 @@ export class FirebaseError extends Error implements FirebaseErrorInterface { (this as any).__proto__ = FirebaseError.prototype; } - /** @return {string} The error code. */ + /** @returns The error code. */ public get code(): string { return this.errorInfo.code; } - /** @return {string} The error message. */ + /** @returns The error message. */ public get message(): string { return this.errorInfo.message; } - /** @return {object} The object representation of the error. */ + /** @returns The object representation of the error. */ public toJSON(): object { return { code: this.code, @@ -72,9 +72,9 @@ export class FirebaseError extends Error implements FirebaseErrorInterface { /** * A FirebaseError with a prefix in front of the error code. * - * @param {string} codePrefix The prefix to apply to the error code. - * @param {string} code The error code. - * @param {string} message The error message. + * @param codePrefix The prefix to apply to the error code. + * @param code The error code. + * @param message The error message. * @constructor */ export class PrefixedFirebaseError extends FirebaseError { @@ -95,8 +95,8 @@ export class PrefixedFirebaseError extends FirebaseError { * Allows the error type to be checked without needing to know implementation details * of the code prefixing. * - * @param {string} code The non-prefixed error code to test against. - * @return {boolean} True if the code matches, false otherwise. + * @param code The non-prefixed error code to test against. + * @returns True if the code matches, false otherwise. */ public hasCode(code: string): boolean { return `${this.codePrefix}/${code}` === this.code; @@ -106,8 +106,8 @@ export class PrefixedFirebaseError extends FirebaseError { /** * Firebase App error code structure. This extends PrefixedFirebaseError. * - * @param {string} code The error code. - * @param {string} message The error message. + * @param code The error code. + * @param message The error message. * @constructor */ export class FirebaseAppError extends PrefixedFirebaseError { @@ -125,8 +125,8 @@ export class FirebaseAppError extends PrefixedFirebaseError { /** * Firebase Auth error code structure. This extends PrefixedFirebaseError. * - * @param {ErrorInfo} info The error code info. - * @param {string} [message] The error message. This will override the default + * @param info The error code info. + * @param [message] The error message. This will override the default * message if provided. * @constructor */ @@ -134,11 +134,11 @@ export class FirebaseAuthError extends PrefixedFirebaseError { /** * Creates the developer-facing error corresponding to the backend error code. * - * @param {string} serverErrorCode The server error code. - * @param {string} [message] The error message. The default message is used + * @param serverErrorCode The server error code. + * @param [message] The error message. The default message is used * if not provided. - * @param {object} [rawServerResponse] The error's raw server response. - * @return {FirebaseAuthError} The corresponding developer-facing error. + * @param [rawServerResponse] The error's raw server response. + * @returns The corresponding developer-facing error. */ public static fromServerError( serverErrorCode: string, @@ -185,8 +185,8 @@ export class FirebaseAuthError extends PrefixedFirebaseError { /** * Firebase Database error code structure. This extends FirebaseError. * - * @param {ErrorInfo} info The error code info. - * @param {string} [message] The error message. This will override the default + * @param info The error code info. + * @param [message] The error message. This will override the default * message if provided. * @constructor */ @@ -200,8 +200,8 @@ export class FirebaseDatabaseError extends FirebaseError { /** * Firebase Firestore error code structure. This extends FirebaseError. * - * @param {ErrorInfo} info The error code info. - * @param {string} [message] The error message. This will override the default + * @param info The error code info. + * @param [message] The error message. This will override the default * message if provided. * @constructor */ @@ -215,8 +215,8 @@ export class FirebaseFirestoreError extends FirebaseError { /** * Firebase instance ID error code structure. This extends FirebaseError. * - * @param {ErrorInfo} info The error code info. - * @param {string} [message] The error message. This will override the default + * @param info The error code info. + * @param [message] The error message. This will override the default * message if provided. * @constructor */ @@ -231,19 +231,19 @@ export class FirebaseInstanceIdError extends FirebaseError { /** * Firebase Messaging error code structure. This extends PrefixedFirebaseError. * - * @param {ErrorInfo} info The error code info. - * @param {string} [message] The error message. This will override the default message if provided. + * @param info The error code info. + * @param [message] The error message. This will override the default message if provided. * @constructor */ export class FirebaseMessagingError extends PrefixedFirebaseError { /** * Creates the developer-facing error corresponding to the backend error code. * - * @param {string} serverErrorCode The server error code. - * @param {string} [message] The error message. The default message is used + * @param serverErrorCode The server error code. + * @param [message] The error message. The default message is used * if not provided. - * @param {object} [rawServerResponse] The error's raw server response. - * @return {FirebaseMessagingError} The corresponding developer-facing error. + * @param [rawServerResponse] The error's raw server response. + * @returns The corresponding developer-facing error. */ public static fromServerError( serverErrorCode: string | null, @@ -305,8 +305,8 @@ export class FirebaseMessagingError extends PrefixedFirebaseError { /** * Firebase project management error code structure. This extends PrefixedFirebaseError. * - * @param {ProjectManagementErrorCode} code The error code. - * @param {string} message The error message. + * @param code The error code. + * @param message The error message. * @constructor */ export class FirebaseProjectManagementError extends PrefixedFirebaseError { diff --git a/src/utils/index.ts b/src/utils/index.ts index 559574fb47..372dc6a5a6 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -37,8 +37,8 @@ export function getSdkVersion(): string { * * For example, this can be used to map underscore_cased properties to camelCase. * - * @param {object} obj The object whose properties to rename. - * @param {object} keyMap The mapping from old to new property names. + * @param obj The object whose properties to rename. + * @param keyMap The mapping from old to new property names. */ export function renameProperties(obj: {[key: string]: any}, keyMap: { [key: string]: string }): void { Object.keys(keyMap).forEach((oldKey) => { @@ -54,9 +54,9 @@ export function renameProperties(obj: {[key: string]: any}, keyMap: { [key: stri /** * Defines a new read-only property directly on an object and returns the object. * - * @param {object} obj The object on which to define the property. - * @param {string} prop The name of the property to be defined or modified. - * @param {any} value The value associated with the property. + * @param obj The object on which to define the property. + * @param prop The name of the property to be defined or modified. + * @param value The value associated with the property. */ export function addReadonlyGetter(obj: object, prop: string, value: any): void { Object.defineProperty(obj, prop, { @@ -75,7 +75,7 @@ export function addReadonlyGetter(obj: object, prop: string, value: any): void { * * @param app A Firebase app to get the project ID from. * - * @return A project ID string or null. + * @returns A project ID string or null. */ export function getExplicitProjectId(app: App): string | null { const options = app.options; @@ -104,7 +104,7 @@ export function getExplicitProjectId(app: App): string | null { * * @param app A Firebase app to get the project ID from. * - * @return A project ID string or null. + * @returns A project ID string or null. */ export function findProjectId(app: App): Promise { const projectId = getExplicitProjectId(app); @@ -123,8 +123,8 @@ export function findProjectId(app: App): Promise { /** * Encodes data using web-safe-base64. * - * @param {Buffer} data The raw data byte input. - * @return {string} The base64-encoded result. + * @param data The raw data byte input. + * @returns The base64-encoded result. */ export function toWebSafeBase64(data: Buffer): string { return data.toString('base64').replace(/\//g, '_').replace(/\+/g, '-'); @@ -135,11 +135,11 @@ export function toWebSafeBase64(data: Buffer): string { * with corresponding arguments {projectId: '1234', api: 'resource'} * and returns output: 'project/1234/resource'. * - * @param {string} str The original string where the param need to be + * @param str The original string where the param need to be * replaced. - * @param {object=} params The optional parameters to replace in the + * @param params The optional parameters to replace in the * string. - * @return {string} The resulting formatted string. + * @returns The resulting formatted string. */ export function formatString(str: string, params?: object): string { let formatted = str; @@ -160,7 +160,7 @@ export function formatString(str: string, params?: object): string { * Nested objects beyond that path will be ignored. This is useful for * keys with variable object values. * @param root The path so far. - * @return The computed update mask list. + * @returns The computed update mask list. */ export function generateUpdateMask( obj: any, terminalPaths: string[] = [], root = '' diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts index d048567061..6ca56dabc6 100644 --- a/src/utils/jwt.ts +++ b/src/utils/jwt.ts @@ -62,7 +62,7 @@ export class UrlKeyFetcher implements KeyFetcher { /** * Fetches the public keys for the Google certs. * - * @return A promise fulfilled with public keys for the Google certs. + * @returns A promise fulfilled with public keys for the Google certs. */ public fetchPublicKeys(): Promise<{ [key: string]: string }> { if (this.shouldRefresh()) { @@ -73,7 +73,7 @@ export class UrlKeyFetcher implements KeyFetcher { /** * Checks if the cached public keys need to be refreshed. - * + * * @returns Whether the keys should be fetched from the client certs url or not. */ private shouldRefresh(): boolean { @@ -163,7 +163,7 @@ export class EmulatorSignatureVerifier implements SignatureVerifier { /** * Provides a callback to fetch public keys. - * + * * @param fetcher KeyFetcher to fetch the keys from. * @returns A callback function that can be used to get keys in `jsonwebtoken`. */ @@ -186,7 +186,7 @@ function getKeyCallback(fetcher: KeyFetcher): jwt.GetPublicKeyOrSecret { /** * Verifies the signature of a JWT using the provided secret or a function to fetch * the secret or public key. - * + * * @param token The JWT to be verfied. * @param secretOrPublicKey The secret or a function to fetch the secret or public key. * @param options JWT verification options. @@ -224,7 +224,7 @@ export function verifyJwtSignature(token: string, secretOrPublicKey: jwt.Secret /** * Decodes general purpose Firebase JWTs. - * + * * @param jwtToken JWT token to be decoded. * @returns Decoded token containing the header and payload. */ diff --git a/src/utils/validator.ts b/src/utils/validator.ts index bca0c660ab..523f91f33c 100644 --- a/src/utils/validator.ts +++ b/src/utils/validator.ts @@ -20,8 +20,8 @@ import url = require('url'); /** * Validates that a value is a byte buffer. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is byte buffer or not. + * @param value The value to validate. + * @returns Whether the value is byte buffer or not. */ export function isBuffer(value: any): value is Buffer { return value instanceof Buffer; @@ -30,8 +30,8 @@ export function isBuffer(value: any): value is Buffer { /** * Validates that a value is an array. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is an array or not. + * @param value The value to validate. + * @returns Whether the value is an array or not. */ export function isArray(value: any): value is T[] { return Array.isArray(value); @@ -40,8 +40,8 @@ export function isArray(value: any): value is T[] { /** * Validates that a value is a non-empty array. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a non-empty array or not. + * @param value The value to validate. + * @returns Whether the value is a non-empty array or not. */ export function isNonEmptyArray(value: any): value is T[] { return isArray(value) && value.length !== 0; @@ -51,8 +51,8 @@ export function isNonEmptyArray(value: any): value is T[] { /** * Validates that a value is a boolean. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a boolean or not. + * @param value The value to validate. + * @returns Whether the value is a boolean or not. */ export function isBoolean(value: any): boolean { return typeof value === 'boolean'; @@ -62,8 +62,8 @@ export function isBoolean(value: any): boolean { /** * Validates that a value is a number. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a number or not. + * @param value The value to validate. + * @returns Whether the value is a number or not. */ export function isNumber(value: any): boolean { return typeof value === 'number' && !isNaN(value); @@ -73,8 +73,8 @@ export function isNumber(value: any): boolean { /** * Validates that a value is a string. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a string or not. + * @param value The value to validate. + * @returns Whether the value is a string or not. */ export function isString(value: any): value is string { return typeof value === 'string'; @@ -84,8 +84,8 @@ export function isString(value: any): value is string { /** * Validates that a value is a base64 string. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a base64 string or not. + * @param value The value to validate. + * @returns Whether the value is a base64 string or not. */ export function isBase64String(value: any): boolean { if (!isString(value)) { @@ -98,8 +98,8 @@ export function isBase64String(value: any): boolean { /** * Validates that a value is a non-empty string. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a non-empty string or not. + * @param value The value to validate. + * @returns Whether the value is a non-empty string or not. */ export function isNonEmptyString(value: any): value is string { return isString(value) && value !== ''; @@ -109,8 +109,8 @@ export function isNonEmptyString(value: any): value is string { /** * Validates that a value is a nullable object. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is an object or not. + * @param value The value to validate. + * @returns Whether the value is an object or not. */ export function isObject(value: any): boolean { return typeof value === 'object' && !isArray(value); @@ -120,8 +120,8 @@ export function isObject(value: any): boolean { /** * Validates that a value is a non-null object. * - * @param {any} value The value to validate. - * @return {boolean} Whether the value is a non-null object or not. + * @param value The value to validate. + * @returns Whether the value is a non-null object or not. */ export function isNonNullObject(value: T | null | undefined): value is T { return isObject(value) && value !== null; @@ -131,8 +131,8 @@ export function isNonNullObject(value: T | null | undefined): value is T { /** * Validates that a string is a valid Firebase Auth uid. * - * @param {any} uid The string to validate. - * @return {boolean} Whether the string is a valid Firebase Auth uid. + * @param uid The string to validate. + * @returns Whether the string is a valid Firebase Auth uid. */ export function isUid(uid: any): boolean { return typeof uid === 'string' && uid.length > 0 && uid.length <= 128; @@ -142,8 +142,8 @@ export function isUid(uid: any): boolean { /** * Validates that a string is a valid Firebase Auth password. * - * @param {any} password The password string to validate. - * @return {boolean} Whether the string is a valid Firebase Auth password. + * @param password The password string to validate. + * @returns Whether the string is a valid Firebase Auth password. */ export function isPassword(password: any): boolean { // A password must be a string of at least 6 characters. @@ -154,8 +154,8 @@ export function isPassword(password: any): boolean { /** * Validates that a string is a valid email. * - * @param {any} email The string to validate. - * @return {boolean} Whether the string is valid email or not. + * @param email The string to validate. + * @returns Whether the string is valid email or not. */ export function isEmail(email: any): boolean { if (typeof email !== 'string') { @@ -170,8 +170,8 @@ export function isEmail(email: any): boolean { /** * Validates that a string is a valid phone number. * - * @param {any} phoneNumber The string to validate. - * @return {boolean} Whether the string is a valid phone number or not. + * @param phoneNumber The string to validate. + * @returns Whether the string is a valid phone number or not. */ export function isPhoneNumber(phoneNumber: any): boolean { if (typeof phoneNumber !== 'string') { @@ -190,7 +190,7 @@ export function isPhoneNumber(phoneNumber: any): boolean { * Validates that a string is a valid ISO date string. * * @param dateString The string to validate. - * @return Whether the string is a valid ISO date string. + * @returns Whether the string is a valid ISO date string. */ export function isISODateString(dateString: any): boolean { try { @@ -206,7 +206,7 @@ export function isISODateString(dateString: any): boolean { * Validates that a string is a valid UTC date string. * * @param dateString The string to validate. - * @return Whether the string is a valid UTC date string. + * @returns Whether the string is a valid UTC date string. */ export function isUTCDateString(dateString: any): boolean { try { @@ -221,8 +221,8 @@ export function isUTCDateString(dateString: any): boolean { /** * Validates that a string is a valid web URL. * - * @param {any} urlStr The string to validate. - * @return {boolean} Whether the string is valid web URL or not. + * @param urlStr The string to validate. + * @returns Whether the string is valid web URL or not. */ export function isURL(urlStr: any): boolean { if (typeof urlStr !== 'string') { @@ -267,8 +267,8 @@ export function isURL(urlStr: any): boolean { /** * Validates that the provided topic is a valid FCM topic name. * - * @param {any} topic The topic to validate. - * @return {boolean} Whether the provided topic is a valid FCM topic name. + * @param topic The topic to validate. + * @returns Whether the provided topic is a valid FCM topic name. */ export function isTopic(topic: any): boolean { if (typeof topic !== 'string') { From ec08bfc11f682afcbe8dc74413da740c46918627 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 5 May 2021 10:31:00 -0700 Subject: [PATCH 28/41] chore: Added docstrings to type aliases in namespaces (#1247) * chore: Adding documentation for type aliases * chore: Adding more type alias doc strings * chore: Added documentation to type aliases --- etc/firebase-admin.api.md | 253 +----------------- etc/firebase-admin.database.api.md | 2 +- etc/firebase-admin.firestore.api.md | 3 +- package-lock.json | 2 +- package.json | 44 ++- src/auth/auth-namespace.ts | 203 ++++++++++++++ src/database/database-namespace.ts | 39 ++- src/database/database.ts | 5 + src/database/index.ts | 2 +- src/firestore/index.ts | 4 +- src/instance-id/instance-id-namespace.ts | 3 + .../machine-learning-namespace.ts | 34 +++ src/messaging/messaging-namespace.ts | 130 +++++++++ .../project-management-namespace.ts | 31 +++ src/remote-config/remote-config-namespace.ts | 51 ++++ .../security-rules-namespace.ts | 19 ++ src/storage/storage-namespace.ts | 3 + 17 files changed, 557 insertions(+), 271 deletions(-) diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index c465c6e45b..df6558951e 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -62,208 +62,106 @@ export function auth(app?: App): auth.Auth; // @public (undocumented) export namespace auth { // Warning: (ae-forgotten-export) The symbol "ActionCodeSettings" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ActionCodeSettings = ActionCodeSettings; // Warning: (ae-forgotten-export) The symbol "Auth" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Auth = Auth; // Warning: (ae-forgotten-export) The symbol "AuthFactorType" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AuthFactorType = AuthFactorType; // Warning: (ae-forgotten-export) The symbol "AuthProviderConfig" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AuthProviderConfig = AuthProviderConfig; // Warning: (ae-forgotten-export) The symbol "AuthProviderConfigFilter" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AuthProviderConfigFilter = AuthProviderConfigFilter; // Warning: (ae-forgotten-export) The symbol "BaseAuth" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type BaseAuth = BaseAuth; // Warning: (ae-forgotten-export) The symbol "CreateMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type CreateMultiFactorInfoRequest = CreateMultiFactorInfoRequest; // Warning: (ae-forgotten-export) The symbol "CreatePhoneMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type CreatePhoneMultiFactorInfoRequest = CreatePhoneMultiFactorInfoRequest; // Warning: (ae-forgotten-export) The symbol "CreateRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type CreateRequest = CreateRequest; // Warning: (ae-forgotten-export) The symbol "CreateTenantRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type CreateTenantRequest = CreateTenantRequest; // Warning: (ae-forgotten-export) The symbol "DecodedIdToken" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type DecodedIdToken = DecodedIdToken; // Warning: (ae-forgotten-export) The symbol "DeleteUsersResult" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type DeleteUsersResult = DeleteUsersResult; // Warning: (ae-forgotten-export) The symbol "EmailIdentifier" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type EmailIdentifier = EmailIdentifier; // Warning: (ae-forgotten-export) The symbol "EmailSignInProviderConfig" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type EmailSignInProviderConfig = EmailSignInProviderConfig; // Warning: (ae-forgotten-export) The symbol "GetUsersResult" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type GetUsersResult = GetUsersResult; // Warning: (ae-forgotten-export) The symbol "HashAlgorithmType" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type HashAlgorithmType = HashAlgorithmType; // Warning: (ae-forgotten-export) The symbol "ListProviderConfigResults" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ListProviderConfigResults = ListProviderConfigResults; // Warning: (ae-forgotten-export) The symbol "ListTenantsResult" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ListTenantsResult = ListTenantsResult; // Warning: (ae-forgotten-export) The symbol "ListUsersResult" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ListUsersResult = ListUsersResult; // Warning: (ae-forgotten-export) The symbol "MultiFactorConfig" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MultiFactorConfig = MultiFactorConfig; // Warning: (ae-forgotten-export) The symbol "MultiFactorConfigState" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MultiFactorConfigState = MultiFactorConfigState; // Warning: (ae-forgotten-export) The symbol "MultiFactorCreateSettings" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MultiFactorCreateSettings = MultiFactorCreateSettings; // Warning: (ae-forgotten-export) The symbol "MultiFactorInfo" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MultiFactorInfo = MultiFactorInfo; // Warning: (ae-forgotten-export) The symbol "MultiFactorSettings" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MultiFactorSettings = MultiFactorSettings; // Warning: (ae-forgotten-export) The symbol "MultiFactorUpdateSettings" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MultiFactorUpdateSettings = MultiFactorUpdateSettings; // Warning: (ae-forgotten-export) The symbol "OIDCAuthProviderConfig" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type OIDCAuthProviderConfig = OIDCAuthProviderConfig; // Warning: (ae-forgotten-export) The symbol "OIDCUpdateAuthProviderRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type OIDCUpdateAuthProviderRequest = OIDCUpdateAuthProviderRequest; // Warning: (ae-forgotten-export) The symbol "PhoneIdentifier" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type PhoneIdentifier = PhoneIdentifier; // Warning: (ae-forgotten-export) The symbol "PhoneMultiFactorInfo" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type PhoneMultiFactorInfo = PhoneMultiFactorInfo; // Warning: (ae-forgotten-export) The symbol "ProviderIdentifier" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ProviderIdentifier = ProviderIdentifier; // Warning: (ae-forgotten-export) The symbol "SAMLAuthProviderConfig" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type SAMLAuthProviderConfig = SAMLAuthProviderConfig; // Warning: (ae-forgotten-export) The symbol "SAMLUpdateAuthProviderRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type SAMLUpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest; // Warning: (ae-forgotten-export) The symbol "SessionCookieOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type SessionCookieOptions = SessionCookieOptions; // Warning: (ae-forgotten-export) The symbol "Tenant" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Tenant = Tenant; // Warning: (ae-forgotten-export) The symbol "TenantAwareAuth" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type TenantAwareAuth = TenantAwareAuth; // Warning: (ae-forgotten-export) The symbol "TenantManager" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type TenantManager = TenantManager; // Warning: (ae-forgotten-export) The symbol "UidIdentifier" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UidIdentifier = UidIdentifier; // Warning: (ae-forgotten-export) The symbol "UpdateAuthProviderRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UpdateAuthProviderRequest = UpdateAuthProviderRequest; // Warning: (ae-forgotten-export) The symbol "UpdateMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UpdateMultiFactorInfoRequest = UpdateMultiFactorInfoRequest; // Warning: (ae-forgotten-export) The symbol "UpdatePhoneMultiFactorInfoRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UpdatePhoneMultiFactorInfoRequest = UpdatePhoneMultiFactorInfoRequest; // Warning: (ae-forgotten-export) The symbol "UpdateRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UpdateRequest = UpdateRequest; // Warning: (ae-forgotten-export) The symbol "UpdateTenantRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UpdateTenantRequest = UpdateTenantRequest; // Warning: (ae-forgotten-export) The symbol "UserIdentifier" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UserIdentifier = UserIdentifier; // Warning: (ae-forgotten-export) The symbol "UserImportOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UserImportOptions = UserImportOptions; // Warning: (ae-forgotten-export) The symbol "UserImportRecord" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UserImportRecord = UserImportRecord; // Warning: (ae-forgotten-export) The symbol "UserImportResult" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UserImportResult = UserImportResult; // Warning: (ae-forgotten-export) The symbol "UserInfo" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UserInfo = UserInfo; // Warning: (ae-forgotten-export) The symbol "UserMetadata" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UserMetadata = UserMetadata; // Warning: (ae-forgotten-export) The symbol "UserMetadataRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UserMetadataRequest = UserMetadataRequest; // Warning: (ae-forgotten-export) The symbol "UserProviderRequest" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UserProviderRequest = UserProviderRequest; // Warning: (ae-forgotten-export) The symbol "UserRecord" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type UserRecord = UserRecord; } @@ -284,23 +182,14 @@ export function database(app?: App): database.Database; // @public (undocumented) export namespace database { // Warning: (ae-forgotten-export) The symbol "Database" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Database = Database; - // (undocumented) export type DataSnapshot = rtdb.DataSnapshot; - // (undocumented) export type EventType = rtdb.EventType; - // (undocumented) export type OnDisconnect = rtdb.OnDisconnect; - // (undocumented) export type Query = rtdb.Query; - // (undocumented) export type Reference = rtdb.Reference; - // (undocumented) export type ThenableReference = rtdb.ThenableReference; - const // (undocumented) - enableLogging: typeof rtdb.enableLogging; + const enableLogging: typeof rtdb.enableLogging; const ServerValue: rtdb.ServerValue; } @@ -371,8 +260,6 @@ export function instanceId(app?: App): instanceId.InstanceId; // @public (undocumented) export namespace instanceId { // Warning: (ae-forgotten-export) The symbol "InstanceId" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type InstanceId = InstanceId; } @@ -382,40 +269,22 @@ export function machineLearning(app?: App): machineLearning.MachineLearning; // @public (undocumented) export namespace machineLearning { // Warning: (ae-forgotten-export) The symbol "AutoMLTfliteModelOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AutoMLTfliteModelOptions = AutoMLTfliteModelOptions; // Warning: (ae-forgotten-export) The symbol "GcsTfliteModelOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type GcsTfliteModelOptions = GcsTfliteModelOptions; // Warning: (ae-forgotten-export) The symbol "ListModelsOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ListModelsOptions = ListModelsOptions; // Warning: (ae-forgotten-export) The symbol "ListModelsResult" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ListModelsResult = ListModelsResult; // Warning: (ae-forgotten-export) The symbol "MachineLearning" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MachineLearning = MachineLearning; // Warning: (ae-forgotten-export) The symbol "Model" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Model = Model; // Warning: (ae-forgotten-export) The symbol "ModelOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ModelOptions = ModelOptions; // Warning: (ae-forgotten-export) The symbol "ModelOptionsBase" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ModelOptionsBase = ModelOptionsBase; // Warning: (ae-forgotten-export) The symbol "TFLiteModel" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type TFLiteModel = TFLiteModel; } @@ -425,136 +294,70 @@ export function messaging(app?: App): messaging.Messaging; // @public (undocumented) export namespace messaging { // Warning: (ae-forgotten-export) The symbol "AndroidConfig" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AndroidConfig = AndroidConfig; // Warning: (ae-forgotten-export) The symbol "AndroidFcmOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AndroidFcmOptions = AndroidFcmOptions; // Warning: (ae-forgotten-export) The symbol "AndroidNotification" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AndroidNotification = AndroidNotification; // Warning: (ae-forgotten-export) The symbol "ApnsConfig" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ApnsConfig = ApnsConfig; // Warning: (ae-forgotten-export) The symbol "ApnsFcmOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ApnsFcmOptions = ApnsFcmOptions; // Warning: (ae-forgotten-export) The symbol "ApnsPayload" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ApnsPayload = ApnsPayload; // Warning: (ae-forgotten-export) The symbol "Aps" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Aps = Aps; // Warning: (ae-forgotten-export) The symbol "ApsAlert" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ApsAlert = ApsAlert; // Warning: (ae-forgotten-export) The symbol "BatchResponse" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type BatchResponse = BatchResponse; // Warning: (ae-forgotten-export) The symbol "ConditionMessage" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ConditionMessage = ConditionMessage; // Warning: (ae-forgotten-export) The symbol "CriticalSound" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type CriticalSound = CriticalSound; // Warning: (ae-forgotten-export) The symbol "DataMessagePayload" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type DataMessagePayload = DataMessagePayload; // Warning: (ae-forgotten-export) The symbol "FcmOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type FcmOptions = FcmOptions; // Warning: (ae-forgotten-export) The symbol "LightSettings" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type LightSettings = LightSettings; // Warning: (ae-forgotten-export) The symbol "Message" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Message = Message; // Warning: (ae-forgotten-export) The symbol "Messaging" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Messaging = Messaging; // Warning: (ae-forgotten-export) The symbol "MessagingConditionResponse" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MessagingConditionResponse = MessagingConditionResponse; // Warning: (ae-forgotten-export) The symbol "MessagingDeviceGroupResponse" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MessagingDeviceGroupResponse = MessagingDeviceGroupResponse; // Warning: (ae-forgotten-export) The symbol "MessagingDeviceResult" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MessagingDeviceResult = MessagingDeviceResult; // Warning: (ae-forgotten-export) The symbol "MessagingDevicesResponse" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MessagingDevicesResponse = MessagingDevicesResponse; // Warning: (ae-forgotten-export) The symbol "MessagingOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MessagingOptions = MessagingOptions; // Warning: (ae-forgotten-export) The symbol "MessagingPayload" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MessagingPayload = MessagingPayload; // Warning: (ae-forgotten-export) The symbol "MessagingTopicManagementResponse" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MessagingTopicManagementResponse = MessagingTopicManagementResponse; // Warning: (ae-forgotten-export) The symbol "MessagingTopicResponse" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MessagingTopicResponse = MessagingTopicResponse; // Warning: (ae-forgotten-export) The symbol "MulticastMessage" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type MulticastMessage = MulticastMessage; // Warning: (ae-forgotten-export) The symbol "Notification" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Notification = Notification; // Warning: (ae-forgotten-export) The symbol "NotificationMessagePayload" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type NotificationMessagePayload = NotificationMessagePayload; // Warning: (ae-forgotten-export) The symbol "SendResponse" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type SendResponse = SendResponse; // Warning: (ae-forgotten-export) The symbol "TokenMessage" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type TokenMessage = TokenMessage; // Warning: (ae-forgotten-export) The symbol "TopicMessage" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type TopicMessage = TopicMessage; // Warning: (ae-forgotten-export) The symbol "WebpushConfig" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type WebpushConfig = WebpushConfig; // Warning: (ae-forgotten-export) The symbol "WebpushFcmOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type WebpushFcmOptions = WebpushFcmOptions; // Warning: (ae-forgotten-export) The symbol "WebpushNotification" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type WebpushNotification = WebpushNotification; } @@ -564,36 +367,20 @@ export function projectManagement(app?: App): projectManagement.ProjectManagemen // @public (undocumented) export namespace projectManagement { // Warning: (ae-forgotten-export) The symbol "AndroidApp" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AndroidApp = AndroidApp; // Warning: (ae-forgotten-export) The symbol "AndroidAppMetadata" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AndroidAppMetadata = AndroidAppMetadata; // Warning: (ae-forgotten-export) The symbol "AppMetadata" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AppMetadata = AppMetadata; // Warning: (ae-forgotten-export) The symbol "AppPlatform" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type AppPlatform = AppPlatform; // Warning: (ae-forgotten-export) The symbol "IosApp" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type IosApp = IosApp; // Warning: (ae-forgotten-export) The symbol "IosAppMetadata" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type IosAppMetadata = IosAppMetadata; // Warning: (ae-forgotten-export) The symbol "ProjectManagement" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ProjectManagement = ProjectManagement; // Warning: (ae-forgotten-export) The symbol "ShaCertificate" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ShaCertificate = ShaCertificate; } @@ -603,56 +390,30 @@ export function remoteConfig(app?: App): remoteConfig.RemoteConfig; // @public (undocumented) export namespace remoteConfig { // Warning: (ae-forgotten-export) The symbol "ExplicitParameterValue" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ExplicitParameterValue = ExplicitParameterValue; // Warning: (ae-forgotten-export) The symbol "InAppDefaultValue" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type InAppDefaultValue = InAppDefaultValue; // Warning: (ae-forgotten-export) The symbol "ListVersionsOptions" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ListVersionsOptions = ListVersionsOptions; // Warning: (ae-forgotten-export) The symbol "ListVersionsResult" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type ListVersionsResult = ListVersionsResult; // Warning: (ae-forgotten-export) The symbol "RemoteConfig" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type RemoteConfig = RemoteConfig; // Warning: (ae-forgotten-export) The symbol "RemoteConfigCondition" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type RemoteConfigCondition = RemoteConfigCondition; // Warning: (ae-forgotten-export) The symbol "RemoteConfigParameter" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type RemoteConfigParameter = RemoteConfigParameter; // Warning: (ae-forgotten-export) The symbol "RemoteConfigParameterGroup" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type RemoteConfigParameterGroup = RemoteConfigParameterGroup; // Warning: (ae-forgotten-export) The symbol "RemoteConfigParameterValue" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type RemoteConfigParameterValue = RemoteConfigParameterValue; // Warning: (ae-forgotten-export) The symbol "RemoteConfigTemplate" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type RemoteConfigTemplate = RemoteConfigTemplate; // Warning: (ae-forgotten-export) The symbol "RemoteConfigUser" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type RemoteConfigUser = RemoteConfigUser; // Warning: (ae-forgotten-export) The symbol "TagColor" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type TagColor = TagColor; // Warning: (ae-forgotten-export) The symbol "Version" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Version = Version; } @@ -665,24 +426,14 @@ export function securityRules(app?: App): securityRules.SecurityRules; // @public (undocumented) export namespace securityRules { // Warning: (ae-forgotten-export) The symbol "Ruleset" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Ruleset = Ruleset; // Warning: (ae-forgotten-export) The symbol "RulesetMetadata" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type RulesetMetadata = RulesetMetadata; // Warning: (ae-forgotten-export) The symbol "RulesetMetadataList" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type RulesetMetadataList = RulesetMetadataList; // Warning: (ae-forgotten-export) The symbol "RulesFile" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type RulesFile = RulesFile; // Warning: (ae-forgotten-export) The symbol "SecurityRules" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type SecurityRules = SecurityRules; } @@ -702,8 +453,6 @@ export function storage(app?: App): storage.Storage; // @public (undocumented) export namespace storage { // Warning: (ae-forgotten-export) The symbol "Storage" needs to be exported by the entry point default-namespace.d.ts - // - // (undocumented) export type Storage = Storage; } diff --git a/etc/firebase-admin.database.api.md b/etc/firebase-admin.database.api.md index f76fd77fe4..12ecdd6008 100644 --- a/etc/firebase-admin.database.api.md +++ b/etc/firebase-admin.database.api.md @@ -14,7 +14,7 @@ import { Reference } from '@firebase/database-types'; import * as rtdb from '@firebase/database-types'; import { ThenableReference } from '@firebase/database-types'; -// @public (undocumented) +// @public export interface Database extends FirebaseDatabase { getRules(): Promise; getRulesJSON(): Promise; diff --git a/etc/firebase-admin.firestore.api.md b/etc/firebase-admin.firestore.api.md index e1c0fbe0fc..4d300b5397 100644 --- a/etc/firebase-admin.firestore.api.md +++ b/etc/firebase-admin.firestore.api.md @@ -16,7 +16,6 @@ import { DocumentSnapshot } from '@google-cloud/firestore'; import { FieldPath } from '@google-cloud/firestore'; import { FieldValue } from '@google-cloud/firestore'; import { Firestore } from '@google-cloud/firestore'; -import * as _firestore from '@google-cloud/firestore'; import { FirestoreDataConverter } from '@google-cloud/firestore'; import { GeoPoint } from '@google-cloud/firestore'; import { GrpcStatus } from '@google-cloud/firestore'; @@ -64,7 +63,7 @@ export { GeoPoint } // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export function getFirestore(app?: App): _firestore.Firestore; +export function getFirestore(app?: App): Firestore; export { GrpcStatus } diff --git a/package-lock.json b/package-lock.json index f4875def9a..fb0b99043a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "firebase-admin", - "version": "9.4.2", + "version": "9.100.0-alpha.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e50e67dc71..107f5d0382 100644 --- a/package.json +++ b/package.json @@ -57,17 +57,39 @@ "types": "./lib/index.d.ts", "typesVersions": { "*": { - "app": ["lib/app"], - "auth": ["lib/auth"], - "database": ["lib/database"], - "firestore": ["lib/firestore"], - "instance-id": ["lib/instance-id"], - "machine-learning": ["lib/machine-learning"], - "messaging": ["lib/messaging"], - "project-management": ["lib/project-management"], - "remote-config": ["lib/remote-config"], - "security-rules": ["lib/security-rules"], - "storage": ["lib/storage"] + "app": [ + "lib/app" + ], + "auth": [ + "lib/auth" + ], + "database": [ + "lib/database" + ], + "firestore": [ + "lib/firestore" + ], + "instance-id": [ + "lib/instance-id" + ], + "machine-learning": [ + "lib/machine-learning" + ], + "messaging": [ + "lib/messaging" + ], + "project-management": [ + "lib/project-management" + ], + "remote-config": [ + "lib/remote-config" + ], + "security-rules": [ + "lib/security-rules" + ], + "storage": [ + "lib/storage" + ] } }, "exports": { diff --git a/src/auth/auth-namespace.ts b/src/auth/auth-namespace.ts index 59c7e31211..adf3834a56 100644 --- a/src/auth/auth-namespace.ts +++ b/src/auth/auth-namespace.ts @@ -118,55 +118,258 @@ export declare function auth(app?: App): auth.Auth; /* eslint-disable @typescript-eslint/no-namespace */ export namespace auth { + /** + * Type alias to {@link firebase-admin.auth#ActionCodeSettings}. + */ export type ActionCodeSettings = TActionCodeSettings; + + /** + * Type alias to {@link firebase-admin.auth#Auth}. + */ export type Auth = TAuth; + + /** + * Type alias to {@link firebase-admin.auth#AuthFactorType}. + */ export type AuthFactorType = TAuthFactorType; + + /** + * Type alias to {@link firebase-admin.auth#AuthProviderConfig}. + */ export type AuthProviderConfig = TAuthProviderConfig; + + /** + * Type alias to {@link firebase-admin.auth#AuthProviderConfigFilter}. + */ export type AuthProviderConfigFilter = TAuthProviderConfigFilter; + + /** + * Type alias to {@link firebase-admin.auth#BaseAuth}. + */ export type BaseAuth = TBaseAuth; + + /** + * Type alias to {@link firebase-admin.auth#CreateMultiFactorInfoRequest}. + */ export type CreateMultiFactorInfoRequest = TCreateMultiFactorInfoRequest; + + /** + * Type alias to {@link firebase-admin.auth#CreatePhoneMultiFactorInfoRequest}. + */ export type CreatePhoneMultiFactorInfoRequest = TCreatePhoneMultiFactorInfoRequest; + + /** + * Type alias to {@link firebase-admin.auth#CreateRequest}. + */ export type CreateRequest = TCreateRequest; + + /** + * Type alias to {@link firebase-admin.auth#CreateTenantRequest}. + */ export type CreateTenantRequest = TCreateTenantRequest; + + /** + * Type alias to {@link firebase-admin.auth#DecodedIdToken}. + */ export type DecodedIdToken = TDecodedIdToken; + + /** + * Type alias to {@link firebase-admin.auth#DeleteUsersResult}. + */ export type DeleteUsersResult = TDeleteUsersResult; + + /** + * Type alias to {@link firebase-admin.auth#EmailIdentifier}. + */ export type EmailIdentifier = TEmailIdentifier; + + /** + * Type alias to {@link firebase-admin.auth#EmailSignInProviderConfig}. + */ export type EmailSignInProviderConfig = TEmailSignInProviderConfig; + + /** + * Type alias to {@link firebase-admin.auth#GetUsersResult}. + */ export type GetUsersResult = TGetUsersResult; + + /** + * Type alias to {@link firebase-admin.auth#HashAlgorithmType}. + */ export type HashAlgorithmType = THashAlgorithmType; + + /** + * Type alias to {@link firebase-admin.auth#ListProviderConfigResults}. + */ export type ListProviderConfigResults = TListProviderConfigResults; + + /** + * Type alias to {@link firebase-admin.auth#ListTenantsResult}. + */ export type ListTenantsResult = TListTenantsResult; + + /** + * Type alias to {@link firebase-admin.auth#ListUsersResult}. + */ export type ListUsersResult = TListUsersResult; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorCreateSettings}. + */ export type MultiFactorCreateSettings = TMultiFactorCreateSettings; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorConfig}. + */ export type MultiFactorConfig = TMultiFactorConfig; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorConfigState}. + */ export type MultiFactorConfigState = TMultiFactorConfigState; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorInfo}. + */ export type MultiFactorInfo = TMultiFactorInfo; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorUpdateSettings}. + */ export type MultiFactorUpdateSettings = TMultiFactorUpdateSettings; + + /** + * Type alias to {@link firebase-admin.auth#MultiFactorSettings}. + */ export type MultiFactorSettings = TMultiFactorSettings; + + /** + * Type alias to {@link firebase-admin.auth#OIDCAuthProviderConfig}. + */ export type OIDCAuthProviderConfig = TOIDCAuthProviderConfig; + + /** + * Type alias to {@link firebase-admin.auth#OIDCUpdateAuthProviderRequest}. + */ export type OIDCUpdateAuthProviderRequest = TOIDCUpdateAuthProviderRequest; + + /** + * Type alias to {@link firebase-admin.auth#PhoneIdentifier}. + */ export type PhoneIdentifier = TPhoneIdentifier; + + /** + * Type alias to {@link firebase-admin.auth#PhoneMultiFactorInfo}. + */ export type PhoneMultiFactorInfo = TPhoneMultiFactorInfo; + + /** + * Type alias to {@link firebase-admin.auth#ProviderIdentifier}. + */ export type ProviderIdentifier = TProviderIdentifier; + + /** + * Type alias to {@link firebase-admin.auth#SAMLAuthProviderConfig}. + */ export type SAMLAuthProviderConfig = TSAMLAuthProviderConfig; + + /** + * Type alias to {@link firebase-admin.auth#SAMLUpdateAuthProviderRequest}. + */ export type SAMLUpdateAuthProviderRequest = TSAMLUpdateAuthProviderRequest; + + /** + * Type alias to {@link firebase-admin.auth#SessionCookieOptions}. + */ export type SessionCookieOptions = TSessionCookieOptions; + + /** + * Type alias to {@link firebase-admin.auth#Tenant}. + */ export type Tenant = TTenant; + + /** + * Type alias to {@link firebase-admin.auth#TenantAwareAuth}. + */ export type TenantAwareAuth = TTenantAwareAuth; + + /** + * Type alias to {@link firebase-admin.auth#TenantManager}. + */ export type TenantManager = TTenantManager; + + /** + * Type alias to {@link firebase-admin.auth#UidIdentifier}. + */ export type UidIdentifier = TUidIdentifier; + + /** + * Type alias to {@link firebase-admin.auth#UpdateAuthProviderRequest}. + */ export type UpdateAuthProviderRequest = TUpdateAuthProviderRequest; + + /** + * Type alias to {@link firebase-admin.auth#UpdateMultiFactorInfoRequest}. + */ export type UpdateMultiFactorInfoRequest = TUpdateMultiFactorInfoRequest; + + /** + * Type alias to {@link firebase-admin.auth#UpdatePhoneMultiFactorInfoRequest}. + */ export type UpdatePhoneMultiFactorInfoRequest = TUpdatePhoneMultiFactorInfoRequest; + + /** + * Type alias to {@link firebase-admin.auth#UpdateRequest}. + */ export type UpdateRequest = TUpdateRequest; + + /** + * Type alias to {@link firebase-admin.auth#UpdateTenantRequest}. + */ export type UpdateTenantRequest = TUpdateTenantRequest; + + /** + * Type alias to {@link firebase-admin.auth#UserIdentifier}. + */ export type UserIdentifier = TUserIdentifier; + + /** + * Type alias to {@link firebase-admin.auth#UserImportOptions}. + */ export type UserImportOptions = TUserImportOptions; + + /** + * Type alias to {@link firebase-admin.auth#UserImportRecord}. + */ export type UserImportRecord = TUserImportRecord; + + /** + * Type alias to {@link firebase-admin.auth#UserImportResult}. + */ export type UserImportResult = TUserImportResult; + + /** + * Type alias to {@link firebase-admin.auth#UserInfo}. + */ export type UserInfo = TUserInfo; + + /** + * Type alias to {@link firebase-admin.auth#UserMetadata}. + */ export type UserMetadata = TUserMetadata; + + /** + * Type alias to {@link firebase-admin.auth#UserMetadataRequest}. + */ export type UserMetadataRequest = TUserMetadataRequest; + + /** + * Type alias to {@link firebase-admin.auth#UserProviderRequest}. + */ export type UserProviderRequest = TUserProviderRequest; + + /** + * Type alias to {@link firebase-admin.auth#UserRecord}. + */ export type UserRecord = TUserRecord; } diff --git a/src/database/database-namespace.ts b/src/database/database-namespace.ts index a6c1f4716e..cc2a1111fa 100644 --- a/src/database/database-namespace.ts +++ b/src/database/database-namespace.ts @@ -51,19 +51,56 @@ export declare function database(app?: App): database.Database; /* eslint-disable @typescript-eslint/no-namespace */ export namespace database { + /** + * Type alias to {@link firebase-admin.database#Database}. + */ export type Database = TDatabase; + + /** + * Type alias to {@link https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot | DataSnapshot} + * type from the `@firebase/database` package. + */ export type DataSnapshot = rtdb.DataSnapshot; + + /** + * Type alias to the {@link https://firebase.google.com/docs/reference/js/firebase.database#eventtype | EventType} + * type from the `@firebase/database` package. + */ export type EventType = rtdb.EventType; + + /** + * Type alias to {@link https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect | OnDisconnect} + * type from the `@firebase/database` package. + */ export type OnDisconnect = rtdb.OnDisconnect; + + /** + * Type alias to {@link https://firebase.google.com/docs/reference/js/firebase.database.Query | Query} + * type from the `@firebase/database` package. + */ export type Query = rtdb.Query; + + /** + * Type alias to {@link https://firebase.google.com/docs/reference/js/firebase.database.Reference | Reference} + * type from the `@firebase/database` package. + */ export type Reference = rtdb.Reference; + + /** + * Type alias to {@link https://firebase.google.com/docs/reference/js/firebase.database.ThenableReference | + * ThenableReference} type from the `@firebase/database` package. + */ export type ThenableReference = rtdb.ThenableReference; + /** + * {@link https://firebase.google.com/docs/reference/js/firebase.database#enablelogging | enableLogging} + * function from the `@firebase/database` package. + */ export declare const enableLogging: typeof rtdb.enableLogging; /** * {@link https://firebase.google.com/docs/reference/js/firebase.database.ServerValue | ServerValue} - * module from the `@firebase/database` package. + * constant from the `@firebase/database` package. */ export declare const ServerValue: rtdb.ServerValue; } diff --git a/src/database/database.ts b/src/database/database.ts index 4d5275f405..5cd93827a9 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -27,6 +27,11 @@ import * as validator from '../utils/validator'; import { AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; import { getSdkVersion } from '../utils/index'; +/** + * The Firebase Database service interface. Extends the + * {@link https://firebase.google.com/docs/reference/js/firebase.database.Database | Database} + * interface provided by the `@firebase/database` package. + */ export interface Database extends FirebaseDatabase { /** * Gets the currently applied security rules as a string. The return value consists of diff --git a/src/database/index.ts b/src/database/index.ts index e81ba68e60..e3c1058671 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -42,7 +42,7 @@ export const enableLogging: typeof rtdb.enableLogging = enableLoggingFunc; /** * {@link https://firebase.google.com/docs/reference/js/firebase.database.ServerValue | ServerValue} - * module from the `@firebase/database` package. + * constant from the `@firebase/database` package. */ export const ServerValue: rtdb.ServerValue = serverValueConst; diff --git a/src/firestore/index.ts b/src/firestore/index.ts index 50f2562c18..efb4047ac1 100644 --- a/src/firestore/index.ts +++ b/src/firestore/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import * as _firestore from '@google-cloud/firestore'; +import { Firestore } from '@google-cloud/firestore'; import { App, getApp } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { FirestoreService } from './firestore-internal'; @@ -50,7 +50,7 @@ export { setLogFunction, } from '@google-cloud/firestore'; -export function getFirestore(app?: App): _firestore.Firestore { +export function getFirestore(app?: App): Firestore { if (typeof app === 'undefined') { app = getApp(); } diff --git a/src/instance-id/instance-id-namespace.ts b/src/instance-id/instance-id-namespace.ts index f814cd874e..9d05874a7b 100644 --- a/src/instance-id/instance-id-namespace.ts +++ b/src/instance-id/instance-id-namespace.ts @@ -33,5 +33,8 @@ export declare function instanceId(app?: App): instanceId.InstanceId; /* eslint-disable @typescript-eslint/no-namespace */ export namespace instanceId { + /** + * Type alias to {@link firebase-admin.instance-id#InstanceId}. + */ export type InstanceId = TInstanceId; } diff --git a/src/machine-learning/machine-learning-namespace.ts b/src/machine-learning/machine-learning-namespace.ts index d16b4d1a0c..96a867fed2 100644 --- a/src/machine-learning/machine-learning-namespace.ts +++ b/src/machine-learning/machine-learning-namespace.ts @@ -60,14 +60,48 @@ export declare function machineLearning(app?: App): machineLearning.MachineLearn /* eslint-disable @typescript-eslint/no-namespace */ export namespace machineLearning { + /** + * Type alias to {@link firebase-admin.machine-learning#ListModelsResult}. + */ export type ListModelsResult = TListModelsResult; + + /** + * Type alias to {@link firebase-admin.machine-learning#MachineLearning}. + */ export type MachineLearning = TMachineLearning; + + /** + * Type alias to {@link firebase-admin.machine-learning#Model}. + */ export type Model = TModel; + + /** + * Type alias to {@link firebase-admin.machine-learning#TFLiteModel}. + */ export type TFLiteModel = TTFLiteModel; + /** + * Type alias to {@link firebase-admin.machine-learning#AutoMLTfliteModelOptions}. + */ export type AutoMLTfliteModelOptions = TAutoMLTfliteModelOptions; + + /** + * Type alias to {@link firebase-admin.machine-learning#GcsTfliteModelOptions}. + */ export type GcsTfliteModelOptions = TGcsTfliteModelOptions; + + /** + * Type alias to {@link firebase-admin.machine-learning#ListModelsOptions}. + */ export type ListModelsOptions = TListModelsOptions; + + /** + * Type alias to {@link firebase-admin.machine-learning#ModelOptions}. + */ export type ModelOptions = TModelOptions; + + /** + * Type alias to {@link firebase-admin.machine-learning#ModelOptionsBase}. + */ export type ModelOptionsBase = TModelOptionsBase; } diff --git a/src/messaging/messaging-namespace.ts b/src/messaging/messaging-namespace.ts index 2e34b87760..abfd432bfd 100644 --- a/src/messaging/messaging-namespace.ts +++ b/src/messaging/messaging-namespace.ts @@ -84,40 +84,170 @@ export declare function messaging(app?: App): messaging.Messaging; /* eslint-disable @typescript-eslint/no-namespace */ export namespace messaging { + /** + * Type alias to {@link firebase-admin.messaging#Messaging}. + */ export type Messaging = TMessaging; + /** + * Type alias to {@link firebase-admin.messaging#AndroidConfig}. + */ export type AndroidConfig = TAndroidConfig; + + /** + * Type alias to {@link firebase-admin.messaging#AndroidFcmOptions}. + */ export type AndroidFcmOptions = TAndroidFcmOptions; + + /** + * Type alias to {@link firebase-admin.messaging#AndroidNotification}. + */ export type AndroidNotification = TAndroidNotification; + + /** + * Type alias to {@link firebase-admin.messaging#ApnsConfig}. + */ export type ApnsConfig = TApnsConfig; + + /** + * Type alias to {@link firebase-admin.messaging#ApnsFcmOptions}. + */ export type ApnsFcmOptions = TApnsFcmOptions; + + /** + * Type alias to {@link firebase-admin.messaging#ApnsPayload}. + */ export type ApnsPayload = TApnsPayload; + + /** + * Type alias to {@link firebase-admin.messaging#Aps}. + */ export type Aps = TAps; + + /** + * Type alias to {@link firebase-admin.messaging#ApsAlert}. + */ export type ApsAlert = TApsAlert; + + /** + * Type alias to {@link firebase-admin.messaging#BatchResponse}. + */ export type BatchResponse = TBatchResponse; + + /** + * Type alias to {@link firebase-admin.messaging#CriticalSound}. + */ export type CriticalSound = TCriticalSound; + + /** + * Type alias to {@link firebase-admin.messaging#ConditionMessage}. + */ export type ConditionMessage = TConditionMessage; + + /** + * Type alias to {@link firebase-admin.messaging#FcmOptions}. + */ export type FcmOptions = TFcmOptions; + + /** + * Type alias to {@link firebase-admin.messaging#LightSettings}. + */ export type LightSettings = TLightSettings; + + /** + * Type alias to {@link firebase-admin.messaging#Message}. + */ export type Message = TMessage; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingTopicManagementResponse}. + */ export type MessagingTopicManagementResponse = TMessagingTopicManagementResponse; + + /** + * Type alias to {@link firebase-admin.messaging#MulticastMessage}. + */ export type MulticastMessage = TMulticastMessage; + + /** + * Type alias to {@link firebase-admin.messaging#Notification}. + */ export type Notification = TNotification; + + /** + * Type alias to {@link firebase-admin.messaging#SendResponse}. + */ export type SendResponse = TSendResponse; + + /** + * Type alias to {@link firebase-admin.messaging#TokenMessage}. + */ export type TokenMessage = TTokenMessage; + + /** + * Type alias to {@link firebase-admin.messaging#TopicMessage}. + */ export type TopicMessage = TTopicMessage; + + /** + * Type alias to {@link firebase-admin.messaging#WebpushConfig}. + */ export type WebpushConfig = TWebpushConfig; + + /** + * Type alias to {@link firebase-admin.messaging#WebpushFcmOptions}. + */ export type WebpushFcmOptions = TWebpushFcmOptions; + + /** + * Type alias to {@link firebase-admin.messaging#WebpushNotification}. + */ export type WebpushNotification = TWebpushNotification; // Legacy APIs + + /** + * Type alias to {@link firebase-admin.messaging#DataMessagePayload}. + */ export type DataMessagePayload = TDataMessagePayload; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingConditionResponse}. + */ export type MessagingConditionResponse = TMessagingConditionResponse; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingDeviceGroupResponse}. + */ export type MessagingDeviceGroupResponse = TMessagingDeviceGroupResponse; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingDeviceResult}. + */ export type MessagingDeviceResult = TMessagingDeviceResult; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingDevicesResponse}. + */ export type MessagingDevicesResponse = TMessagingDevicesResponse; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingOptions}. + */ export type MessagingOptions = TMessagingOptions; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingPayload}. + */ export type MessagingPayload = TMessagingPayload; + + /** + * Type alias to {@link firebase-admin.messaging#MessagingTopicResponse}. + */ export type MessagingTopicResponse = TMessagingTopicResponse; + + /** + * Type alias to {@link firebase-admin.messaging#NotificationMessagePayload}. + */ export type NotificationMessagePayload = TNotificationMessagePayload; } diff --git a/src/project-management/project-management-namespace.ts b/src/project-management/project-management-namespace.ts index ae9e4cf0b3..60bfd34f8f 100644 --- a/src/project-management/project-management-namespace.ts +++ b/src/project-management/project-management-namespace.ts @@ -60,12 +60,43 @@ export declare function projectManagement(app?: App): projectManagement.ProjectM /* eslint-disable @typescript-eslint/no-namespace */ export namespace projectManagement { + /** + * Type alias to {@link firebase-admin.project-management#AppMetadata}. + */ export type AppMetadata = TAppMetadata; + + /** + * Type alias to {@link firebase-admin.project-management#AppPlatform}. + */ export type AppPlatform = TAppPlatform; + + /** + * Type alias to {@link firebase-admin.project-management#ProjectManagement}. + */ export type ProjectManagement = TProjectManagement; + + /** + * Type alias to {@link firebase-admin.project-management#IosApp}. + */ export type IosApp = TIosApp; + + /** + * Type alias to {@link firebase-admin.project-management#IosAppMetadata}. + */ export type IosAppMetadata = TIosAppMetadata; + + /** + * Type alias to {@link firebase-admin.project-management#AndroidApp}. + */ export type AndroidApp = TAndroidApp; + + /** + * Type alias to {@link firebase-admin.project-management#AndroidAppMetadata}. + */ export type AndroidAppMetadata = TAndroidAppMetadata; + + /** + * Type alias to {@link firebase-admin.project-management#ShaCertificate}. + */ export type ShaCertificate = TShaCertificate; } diff --git a/src/remote-config/remote-config-namespace.ts b/src/remote-config/remote-config-namespace.ts index 39839c6f07..b2d6426e50 100644 --- a/src/remote-config/remote-config-namespace.ts +++ b/src/remote-config/remote-config-namespace.ts @@ -62,17 +62,68 @@ export declare function remoteConfig(app?: App): remoteConfig.RemoteConfig; /* eslint-disable @typescript-eslint/no-namespace */ export namespace remoteConfig { + /** + * Type alias to {@link firebase-admin.remote-config#ExplicitParameterValue}. + */ export type ExplicitParameterValue = TExplicitParameterValue; + + /** + * Type alias to {@link firebase-admin.remote-config#InAppDefaultValue}. + */ export type InAppDefaultValue = TInAppDefaultValue; + + /** + * Type alias to {@link firebase-admin.remote-config#ListVersionsOptions}. + */ export type ListVersionsOptions = TListVersionsOptions; + + /** + * Type alias to {@link firebase-admin.remote-config#ListVersionsResult}. + */ export type ListVersionsResult = TListVersionsResult; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfig}. + */ export type RemoteConfig = TRemoteConfig; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigCondition}. + */ export type RemoteConfigCondition = TRemoteConfigCondition; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigParameter}. + */ export type RemoteConfigParameter = TRemoteConfigParameter; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigParameterGroup}. + */ export type RemoteConfigParameterGroup = TRemoteConfigParameterGroup; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigParameterValue}. + */ export type RemoteConfigParameterValue = TRemoteConfigParameterValue; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigTemplate}. + */ export type RemoteConfigTemplate = TRemoteConfigTemplate; + + /** + * Type alias to {@link firebase-admin.remote-config#RemoteConfigUser}. + */ export type RemoteConfigUser = TRemoteConfigUser; + + /** + * Type alias to {@link firebase-admin.remote-config#TagColor}. + */ export type TagColor = TTagColor; + + /** + * Type alias to {@link firebase-admin.remote-config#Version}. + */ export type Version = TVersion; } diff --git a/src/security-rules/security-rules-namespace.ts b/src/security-rules/security-rules-namespace.ts index dfcd1fffe1..f418fcc883 100644 --- a/src/security-rules/security-rules-namespace.ts +++ b/src/security-rules/security-rules-namespace.ts @@ -55,9 +55,28 @@ export declare function securityRules(app?: App): securityRules.SecurityRules; /* eslint-disable @typescript-eslint/no-namespace */ export namespace securityRules { + /** + * Type alias to {@link firebase-admin.security-rules#RulesFile}. + */ export type RulesFile = TRulesFile; + + /** + * Type alias to {@link firebase-admin.security-rules#Ruleset}. + */ export type Ruleset = TRuleset; + + /** + * Type alias to {@link firebase-admin.security-rules#RulesetMetadata}. + */ export type RulesetMetadata = TRulesetMetadata; + + /** + * Type alias to {@link firebase-admin.security-rules#RulesetMetadataList}. + */ export type RulesetMetadataList = TRulesetMetadataList; + + /** + * Type alias to {@link firebase-admin.security-rules#SecurityRules}. + */ export type SecurityRules = TSecurityRules; } diff --git a/src/storage/storage-namespace.ts b/src/storage/storage-namespace.ts index 26ebddf72c..f0dd80725d 100644 --- a/src/storage/storage-namespace.ts +++ b/src/storage/storage-namespace.ts @@ -41,5 +41,8 @@ export declare function storage(app?: App): storage.Storage; /* eslint-disable @typescript-eslint/no-namespace */ export namespace storage { + /** + * Type alias to {@link firebase-admin.storage#Storage}. + */ export type Storage = TStorage; } From 8e6554f51137ba246d0bd0991dc6ac036e4aef11 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 6 May 2021 12:12:52 -0700 Subject: [PATCH 29/41] feat: Added ESM entry points to the package (#1249) * feat: Added ESM entry points to the package * fix: Removed postcheck ts files from compilation * fix: Version change revert --- .github/scripts/verify_package.sh | 12 +- entrypoints.json | 51 ++++++++ generate-esm-wrapper.js | 82 ++++++++++++ generate-reports.js | 30 ++--- gulpfile.js | 2 +- package.json | 60 +++++++-- .../integration/postcheck/esm/example.test.js | 121 ++++++++++++++++++ test/integration/postcheck/esm/package.json | 3 + .../{typescript => postcheck}/package.json | 12 +- .../{typescript => postcheck}/tsconfig.json | 2 +- .../typescript}/example-modular.test.ts | 0 .../typescript}/example.test.ts | 0 .../src => postcheck/typescript}/example.ts | 0 13 files changed, 328 insertions(+), 47 deletions(-) create mode 100644 entrypoints.json create mode 100644 generate-esm-wrapper.js create mode 100644 test/integration/postcheck/esm/example.test.js create mode 100644 test/integration/postcheck/esm/package.json rename test/integration/{typescript => postcheck}/package.json (63%) rename test/integration/{typescript => postcheck}/tsconfig.json (89%) rename test/integration/{typescript/src => postcheck/typescript}/example-modular.test.ts (100%) rename test/integration/{typescript/src => postcheck/typescript}/example.test.ts (100%) rename test/integration/{typescript/src => postcheck/typescript}/example.ts (100%) diff --git a/.github/scripts/verify_package.sh b/.github/scripts/verify_package.sh index f8c37b4db0..2b295a63b9 100755 --- a/.github/scripts/verify_package.sh +++ b/.github/scripts/verify_package.sh @@ -53,7 +53,7 @@ trap cleanup EXIT # Copy package and test sources into working directory cp "${PKG_NAME}" "${WORK_DIR}" -cp -r test/integration/typescript/* "${WORK_DIR}" +cp -r test/integration/postcheck/* "${WORK_DIR}" cp test/resources/mock.key.json "${WORK_DIR}" # Enter work dir @@ -68,6 +68,10 @@ npm install -S "${PKG_NAME}" echo "> tsc -p tsconfig.json" ./node_modules/.bin/tsc -p tsconfig.json -MOCHA_CLI="./node_modules/.bin/mocha -r ts-node/register" -echo "> $MOCHA_CLI src/*.test.ts" -$MOCHA_CLI src/*.test.ts +MOCHA_CLI="./node_modules/.bin/mocha" +TS_MOCHA_CLI="${MOCHA_CLI} -r ts-node/register" +echo "> $TS_MOCHA_CLI typescript/*.test.ts" +$TS_MOCHA_CLI typescript/*.test.ts + +echo "> $MOCHA_CLI esm/*.js" +$MOCHA_CLI esm/*.js diff --git a/entrypoints.json b/entrypoints.json new file mode 100644 index 0000000000..3c2d6c1e0c --- /dev/null +++ b/entrypoints.json @@ -0,0 +1,51 @@ +{ + "firebase-admin": { + "legacy": true, + "typings": "./lib/default-namespace.d.ts", + "dist": "./lib/index.js" + }, + "firebase-admin/app": { + "typings": "./lib/app/index.d.ts", + "dist": "./lib/app/index.js" + }, + "firebase-admin/auth": { + "typings": "./lib/auth/index.d.ts", + "dist": "./lib/auth/index.js" + }, + "firebase-admin/database": { + "typings": "./lib/database/index.d.ts", + "dist": "./lib/database/index.js" + }, + "firebase-admin/firestore": { + "typings": "./lib/firestore/index.d.ts", + "dist": "./lib/firestore/index.js" + }, + "firebase-admin/instance-id": { + "typings": "./lib/instance-id/index.d.ts", + "dist": "./lib/instance-id/index.js" + }, + "firebase-admin/messaging": { + "typings": "./lib/messaging/index.d.ts", + "dist": "./lib/messaging/index.js" + }, + "firebase-admin/machine-learning": { + "typings": "./lib/machine-learning/index.d.ts", + "dist": "./lib/machine-learning/index.js" + }, + "firebase-admin/project-management": { + "typings": "./lib/project-management/index.d.ts", + "dist": "./lib/project-management/index.js" + }, + "firebase-admin/security-rules": { + "typings": "./lib/security-rules/index.d.ts", + "dist": "./lib/security-rules/index.js" + }, + "firebase-admin/storage": { + "typings": "./lib/storage/index.d.ts", + "dist": "./lib/storage/index.js" + }, + "firebase-admin/remote-config": { + "typings": "./lib/remote-config/index.d.ts", + "dist": "./lib/remote-config/index.js" + } +} diff --git a/generate-esm-wrapper.js b/generate-esm-wrapper.js new file mode 100644 index 0000000000..3d47c709d1 --- /dev/null +++ b/generate-esm-wrapper.js @@ -0,0 +1,82 @@ +/** + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require('path'); +const fs = require('mz/fs'); + +async function main() { + const entryPoints = require('./entrypoints.json'); + for (const entryPoint in entryPoints) { + const info = entryPoints[entryPoint]; + if (info.legacy) { + continue; + } + + await generateEsmWrapper(entryPoint, info.dist); + } +} + +async function generateEsmWrapper(entryPoint, source) { + console.log(`Generating ESM wrapper for ${entryPoint}`); + const target = getTarget(entryPoint); + const output = getEsmOutput(source, target); + await fs.mkdir(path.dirname(target), { recursive: true }); + await fs.writeFile(target, output); + await fs.writeFile('./lib/esm/package.json', JSON.stringify({type: 'module'})); +} + +function getTarget(entryPoint) { + const child = entryPoint.replace('firebase-admin/', ''); + return `./lib/esm/${child}/index.js`; +} + +function getEsmOutput(source, target) { + const sourcePath = path.resolve(source); + const cjsSource = require.resolve(sourcePath); + const keys = getExports(cjsSource); + const targetPath = path.resolve(target); + const importPath = getImportPath(targetPath, cjsSource); + + let output = `import mod from ${JSON.stringify(importPath)};`; + output += '\n\n'; + for (const key of keys) { + output += `export const ${key} = mod.${key};\n`; + } + + return output; +} + +function getImportPath(from, to) { + const fromDir = path.dirname(from); + return path.relative(fromDir, to).replace(/\\/g, '/'); +} + +function getExports(cjsSource) { + const mod = require(cjsSource); + const keys = new Set(Object.getOwnPropertyNames(mod)); + keys.delete('__esModule'); + return [...keys].sort(); +} + +(async () => { + try { + await main(); + } catch (err) { + console.log(err); + process.exit(1); + } +})(); diff --git a/generate-reports.js b/generate-reports.js index 4d92f64563..129c06e542 100644 --- a/generate-reports.js +++ b/generate-reports.js @@ -31,38 +31,24 @@ const { local: localMode } = yargs // API Extractor configuration file. const config = require('./api-extractor.json'); -// List of module entry points. We generate a separate report for each entry point. -const entryPoints = { - 'firebase-admin': './lib/default-namespace.d.ts', - 'firebase-admin/app': './lib/app/index.d.ts', - 'firebase-admin/auth': './lib/auth/index.d.ts', - 'firebase-admin/database': './lib/database/index.d.ts', - 'firebase-admin/firestore': './lib/firestore/index.d.ts', - 'firebase-admin/instance-id': './lib/instance-id/index.d.ts', - 'firebase-admin/messaging': './lib/messaging/index.d.ts', - 'firebase-admin/machine-learning': './lib/machine-learning/index.d.ts', - 'firebase-admin/project-management': './lib/project-management/index.d.ts', - 'firebase-admin/security-rules': './lib/security-rules/index.d.ts', - 'firebase-admin/storage': './lib/storage/index.d.ts', - 'firebase-admin/remote-config': './lib/remote-config/index.d.ts', -}; - const tempConfigFile = 'api-extractor.tmp'; async function generateReports() { - for (const key in entryPoints) { - await generateReportForEntryPoint(key); + const entryPoints = require('./entrypoints.json'); + for (const entryPoint in entryPoints) { + const filePath = entryPoints[entryPoint].typings; + await generateReportForEntryPoint(entryPoint, filePath); } } -async function generateReportForEntryPoint(key) { - console.log(`\nGenerating API report for ${key}`) +async function generateReportForEntryPoint(entryPoint, filePath) { + console.log(`\nGenerating API report for ${entryPoint}`) console.log('========================================================\n'); - const safeName = key.replace('/', '.'); + const safeName = entryPoint.replace('/', '.'); console.log('Updating configuration for entry point...'); config.apiReport.reportFileName = `${safeName}.api.md`; - config.mainEntryPointFilePath = entryPoints[key]; + config.mainEntryPointFilePath = filePath; console.log(`Report file name: ${config.apiReport.reportFileName}`); console.log(`Entry point declaration: ${config.mainEntryPointFilePath}`); await fs.writeFile(tempConfigFile, JSON.stringify(config)); diff --git a/gulpfile.js b/gulpfile.js index 07184a93f9..a056ca2bc9 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -41,7 +41,7 @@ var paths = { test: [ 'test/**/*.ts', - '!test/integration/typescript/src/example*.ts', + '!test/integration/postcheck/typescript/*.ts', ], build: 'lib/', diff --git a/package.json b/package.json index 107f5d0382..d75168d7ff 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "scripts": { "build": "gulp build", "build:tests": "gulp compile_test", - "prepare": "npm run build", + "prepare": "npm run build && npm run esm-wrap", "lint": "run-p lint:src lint:test", "test": "run-s lint test:unit", "integration": "run-s build test:integration", @@ -22,7 +22,8 @@ "lint:test": "eslint test/ --ext .ts", "apidocs": "node docgen/generate-docs.js --api node", "api-extractor": "node generate-reports.js", - "api-extractor:local": "npm run build && node generate-reports.js --local" + "api-extractor:local": "npm run build && node generate-reports.js --local", + "esm-wrap": "node generate-esm-wrapper.js" }, "nyc": { "extension": [ @@ -94,17 +95,50 @@ }, "exports": { ".": "./lib/index.js", - "./app": "./lib/app/index.js", - "./auth": "./lib/auth/index.js", - "./database": "./lib/database/index.js", - "./firestore": "./lib/firestore/index.js", - "./instance-id": "./lib/instance-id/index.js", - "./machine-learning": "./lib/machine-learning/index.js", - "./messaging": "./lib/messaging/index.js", - "./project-management": "./lib/project-management/index.js", - "./remote-config": "./lib/remote-config/index.js", - "./security-rules": "./lib/security-rules/index.js", - "./storage": "./lib/storage/index.js" + "./app": { + "require": "./lib/app/index.js", + "import": "./lib/esm/app/index.js" + }, + "./auth": { + "require": "./lib/auth/index.js", + "import": "./lib/esm/auth/index.js" + }, + "./database": { + "require": "./lib/database/index.js", + "import": "./lib/esm/database/index.js" + }, + "./firestore": { + "require": "./lib/firestore/index.js", + "import": "./lib/esm/firestore/index.js" + }, + "./instance-id": { + "require": "./lib/instance-id/index.js", + "import": "./lib/esm/instance-id/index.js" + }, + "./machine-learning": { + "require": "./lib/machine-learning/index.js", + "import": "./lib/esm/machine-learning/index.js" + }, + "./messaging": { + "require": "./lib/messaging/index.js", + "import": "./lib/esm/messaging/index.js" + }, + "./project-management": { + "require": "./lib/project-management/index.js", + "import": "./lib/esm/project-management/index.js" + }, + "./remote-config": { + "require": "./lib/remote-config/index.js", + "import": "./lib/esm/remote-config/index.js" + }, + "./security-rules": { + "require": "./lib/security-rules/index.js", + "import": "./lib/esm/security-rules/index.js" + }, + "./storage": { + "require": "./lib/storage/index.js", + "import": "./lib/esm/storage/index.js" + } }, "dependencies": { "@firebase/database": "^0.8.1", diff --git a/test/integration/postcheck/esm/example.test.js b/test/integration/postcheck/esm/example.test.js new file mode 100644 index 0000000000..323b226982 --- /dev/null +++ b/test/integration/postcheck/esm/example.test.js @@ -0,0 +1,121 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expect } from 'chai'; + +import { cert, deleteApp, initializeApp } from 'firebase-admin/app'; +import { getAuth, Auth } from 'firebase-admin/auth'; +import { getDatabase, getDatabaseWithUrl, ServerValue } from 'firebase-admin/database'; +import { getFirestore, DocumentReference, Firestore, FieldValue } from 'firebase-admin/firestore'; +import { getInstanceId, InstanceId } from 'firebase-admin/instance-id'; +import { getMachineLearning, MachineLearning } from 'firebase-admin/machine-learning'; +import { getMessaging, Messaging } from 'firebase-admin/messaging'; +import { getProjectManagement, ProjectManagement } from 'firebase-admin/project-management'; +import { getRemoteConfig, RemoteConfig } from 'firebase-admin/remote-config'; +import { getSecurityRules, SecurityRules } from 'firebase-admin/security-rules'; +import { getStorage, Storage } from 'firebase-admin/storage'; + +describe('ESM entry points', () => { + let app; + + before(() => { + app = initializeApp({ + credential: cert('mock.key.json'), + databaseURL: 'https://mock.firebaseio.com' + }, 'TestApp'); + }); + + after(() => { + return deleteApp(app); + }); + + it('Should return an initialized App', () => { + expect(app.name).to.equal('TestApp'); + }); + + it('Should return an Auth client', () => { + const client = getAuth(app); + expect(client).to.be.instanceOf(Auth); + }); + + it('Should return a Messaging client', () => { + const client = getMessaging(app); + expect(client).to.be.instanceOf(Messaging); + }); + + it('Should return a ProjectManagement client', () => { + const client = getProjectManagement(app); + expect(client).to.be.instanceOf(ProjectManagement); + }); + + it('Should return a SecurityRules client', () => { + const client = getSecurityRules(app); + expect(client).to.be.instanceOf(SecurityRules); + }); + + it('Should return a Database client', () => { + const db = getDatabase(app); + expect(db).to.be.not.undefined; + expect(typeof db.getRules).to.equal('function'); + }); + + it('Should return a Database client for URL', () => { + const db = getDatabaseWithUrl('https://other-mock.firebaseio.com', app); + expect(db).to.be.not.undefined; + expect(typeof db.getRules).to.equal('function'); + }); + + it('Should return a Database ServerValue', () => { + expect(ServerValue.increment(1)).to.be.not.undefined; + }); + + it('Should return a Cloud Storage client', () => { + const storage = getStorage(app); + expect(storage).to.be.instanceOf(Storage) + const bucket = storage.bucket('TestBucket'); + expect(bucket.name).to.equal('TestBucket'); + }); + + it('Should return a Firestore client', () => { + const firestore = getFirestore(app); + expect(firestore).to.be.instanceOf(Firestore); + }); + + it('Should return a Firestore FieldValue', () => { + expect(FieldValue.increment(1)).to.be.not.undefined; + }); + + it('Should return a DocumentReference', () => { + const ref = getFirestore(app).collection('test').doc(); + expect(ref).to.be.instanceOf(DocumentReference); + }); + + it('Should return an InstanceId client', () => { + const client = getInstanceId(app); + expect(client).to.be.instanceOf(InstanceId); + }); + + it('Should return a MachineLearning client', () => { + const client = getMachineLearning(app); + expect(client).to.be.instanceOf(MachineLearning); + }); + + it('Should return a RemoteConfig client', () => { + const client = getRemoteConfig(app); + expect(client).to.be.instanceOf(RemoteConfig); + }); +}); diff --git a/test/integration/postcheck/esm/package.json b/test/integration/postcheck/esm/package.json new file mode 100644 index 0000000000..3dbc1ca591 --- /dev/null +++ b/test/integration/postcheck/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/test/integration/typescript/package.json b/test/integration/postcheck/package.json similarity index 63% rename from test/integration/typescript/package.json rename to test/integration/postcheck/package.json index 8f467c2a41..6345381702 100644 --- a/test/integration/typescript/package.json +++ b/test/integration/postcheck/package.json @@ -1,5 +1,5 @@ { - "name": "firebase-admin-typescript-test", + "name": "firebase-admin-postcheck", "version": "1.0.0", "description": "Firebase Admin SDK post package test cases", "license": "Apache-2.0", @@ -8,12 +8,12 @@ "url": "https://github.com/firebase/firebase-admin-node" }, "devDependencies": { - "@types/chai": "^3.4.35", + "@types/chai": "^4.0.0", "@types/mocha": "^2.2.48", - "@types/node": "^8.10.59", - "chai": "^3.5.0", - "mocha": "^5.2.0", - "ts-node": "^3.3.0", + "@types/node": "^10.10.0", + "chai": "^4.2.0", + "mocha": "^8.0.0", + "ts-node": "^9.0.0", "typescript": "^3.7.3" } } diff --git a/test/integration/typescript/tsconfig.json b/test/integration/postcheck/tsconfig.json similarity index 89% rename from test/integration/typescript/tsconfig.json rename to test/integration/postcheck/tsconfig.json index 6820c83387..85001669ec 100644 --- a/test/integration/typescript/tsconfig.json +++ b/test/integration/postcheck/tsconfig.json @@ -11,6 +11,6 @@ ] }, "files": [ - "src/example.ts" + "./typescript/example.ts" ] } diff --git a/test/integration/typescript/src/example-modular.test.ts b/test/integration/postcheck/typescript/example-modular.test.ts similarity index 100% rename from test/integration/typescript/src/example-modular.test.ts rename to test/integration/postcheck/typescript/example-modular.test.ts diff --git a/test/integration/typescript/src/example.test.ts b/test/integration/postcheck/typescript/example.test.ts similarity index 100% rename from test/integration/typescript/src/example.test.ts rename to test/integration/postcheck/typescript/example.test.ts diff --git a/test/integration/typescript/src/example.ts b/test/integration/postcheck/typescript/example.ts similarity index 100% rename from test/integration/typescript/src/example.ts rename to test/integration/postcheck/typescript/example.ts From 17157bcbab4e5c680ad0ec9c532619901a0a8e6e Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Fri, 14 May 2021 14:30:26 -0700 Subject: [PATCH 30/41] chore: Implementing API Documenter workflow for generating API references (#1272) * chore: Implementing API Documenter based docs pipeline * chore: Cleaned up the post processing script * fix: Updated RTDB and Firestore docs * fix: Improved text * fix: Enabling -s suffix mode for namespace files --- .gitignore | 2 +- docgen/content-sources/node/HOME.md | 9 - docgen/content-sources/node/toc.yaml | 293 --------- docgen/extras/firebase-admin.database.md | 12 + docgen/extras/firebase-admin.firestore.md | 27 + docgen/generate-docs.js | 466 --------------- docgen/post-process.js | 84 +++ docgen/theme/assets/css/firebase.css | 40 -- docgen/theme/assets/css/main.css | 552 ----------------- docgen/theme/assets/images/lockup.png | Bin 2646 -> 0 bytes docgen/theme/layouts/default.hbs | 35 -- docgen/theme/partials/breadcrumb.hbs | 11 - docgen/theme/partials/comment.hbs | 22 - docgen/theme/partials/header.hbs | 23 - docgen/theme/partials/member.sources.hbs | 15 - docgen/theme/partials/navigation.hbs | 22 - docgen/theme/templates/reflection.hbs | 72 --- docgen/tsconfig.json | 3 - docgen/typedoc.js | 27 - etc/firebase-admin.firestore.api.md | 2 +- package-lock.json | 685 ++++++---------------- package.json | 11 +- src/database/index.ts | 16 +- src/firestore/index.ts | 27 + 24 files changed, 355 insertions(+), 2101 deletions(-) delete mode 100644 docgen/content-sources/node/HOME.md delete mode 100644 docgen/content-sources/node/toc.yaml create mode 100644 docgen/extras/firebase-admin.database.md create mode 100644 docgen/extras/firebase-admin.firestore.md delete mode 100644 docgen/generate-docs.js create mode 100644 docgen/post-process.js delete mode 100644 docgen/theme/assets/css/firebase.css delete mode 100644 docgen/theme/assets/css/main.css delete mode 100644 docgen/theme/assets/images/lockup.png delete mode 100644 docgen/theme/layouts/default.hbs delete mode 100644 docgen/theme/partials/breadcrumb.hbs delete mode 100644 docgen/theme/partials/comment.hbs delete mode 100644 docgen/theme/partials/header.hbs delete mode 100644 docgen/theme/partials/member.sources.hbs delete mode 100644 docgen/theme/partials/navigation.hbs delete mode 100644 docgen/theme/templates/reflection.hbs delete mode 100644 docgen/tsconfig.json delete mode 100644 docgen/typedoc.js diff --git a/.gitignore b/.gitignore index 672f8c23a9..8490a7d18b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,4 @@ test/resources/apikey.txt # Release tarballs should not be checked in firebase-admin-*.tgz -docgen/html/ +docgen/markdown/ diff --git a/docgen/content-sources/node/HOME.md b/docgen/content-sources/node/HOME.md deleted file mode 100644 index 4e253f7733..0000000000 --- a/docgen/content-sources/node/HOME.md +++ /dev/null @@ -1,9 +0,0 @@ -# Firebase Admin Node.js SDK Reference - -The Admin SDK is a set of server libraries that lets you interact with Firebase from privileged environments. -You can install it via our [npm package](https://www.npmjs.com/package/firebase-admin). - -To get started using the Firebase Admin Node.js SDK, see -[Add the Firebase Admin SDK to your server](https://firebase.google.com/docs/admin/setup). - -For source code, see the [Firebase Admin Node.js SDK GitHub repo](https://github.com/firebase/firebase-admin-node). diff --git a/docgen/content-sources/node/toc.yaml b/docgen/content-sources/node/toc.yaml deleted file mode 100644 index 487d3fcc39..0000000000 --- a/docgen/content-sources/node/toc.yaml +++ /dev/null @@ -1,293 +0,0 @@ -toc: -- title: "admin" - path: /docs/reference/admin/node/admin - section: - - title: "AppOptions" - path: /docs/reference/admin/node/admin.AppOptions - - title: "FirebaseArrayIndexError" - path: /docs/reference/admin/node/admin.FirebaseArrayIndexError - - title: "FirebaseError" - path: /docs/reference/admin/node/admin.FirebaseError - - title: "GoogleOAuthAccessToken" - path: /docs/reference/admin/node/admin.GoogleOAuthAccessToken - - title: "ServiceAccount" - path: /docs/reference/admin/node/admin.ServiceAccount - -- title: "admin.app" - path: /docs/reference/admin/node/admin.app - section: - - title: "App" - path: /docs/reference/admin/node/admin.app.App-1 - -- title: "admin.auth" - path: /docs/reference/admin/node/admin.auth - section: - - title: "Auth" - path: /docs/reference/admin/node/admin.auth.Auth-1 - - title: "ActionCodeSettings" - path: /docs/reference/admin/node/admin.auth.ActionCodeSettings - - title: "AuthProviderConfig" - path: /docs/reference/admin/node/admin.auth.AuthProviderConfig - - title: "AuthProviderConfigFilter" - path: /docs/reference/admin/node/admin.auth.AuthProviderConfigFilter - - title: "CreateMultiFactorInfoRequest" - path: /docs/reference/admin/node/admin.auth.CreateMultiFactorInfoRequest - - title: "CreatePhoneMultiFactorInfoRequest" - path: /docs/reference/admin/node/admin.auth.CreatePhoneMultiFactorInfoRequest - - title: "CreateRequest" - path: /docs/reference/admin/node/admin.auth.CreateRequest - - title: "EmailSignInProviderConfig" - path: /docs/reference/admin/node/admin.auth.EmailSignInProviderConfig - - title: "ListProviderConfigResults" - path: /docs/reference/admin/node/admin.auth.ListProviderConfigResults - - title: "ListTenantsResult" - path: /docs/reference/admin/node/admin.auth.ListTenantsResult - - title: "MultiFactorConfig" - path: /docs/reference/admin/node/admin.auth.MultiFactorConfig - - title: "MultiFactorCreateSettings" - path: /docs/reference/admin/node/admin.auth.MultiFactorCreateSettings - - title: "MultiFactorInfo" - path: /docs/reference/admin/node/admin.auth.MultiFactorInfo - - title: "MultiFactorSettings" - path: /docs/reference/admin/node/admin.auth.MultiFactorSettings - - title: "MultiFactorUpdateSettings" - path: /docs/reference/admin/node/admin.auth.MultiFactorUpdateSettings - - title: "OIDCAuthProviderConfig" - path: /docs/reference/admin/node/admin.auth.OIDCAuthProviderConfig - - title: "OIDCUpdateAuthProviderRequest" - path: /docs/reference/admin/node/admin.auth.OIDCUpdateAuthProviderRequest - - title: "PhoneMultiFactorInfo" - path: /docs/reference/admin/node/admin.auth.PhoneMultiFactorInfo - - title: "SAMLAuthProviderConfig" - path: /docs/reference/admin/node/admin.auth.SAMLAuthProviderConfig - - title: "SAMLUpdateAuthProviderRequest" - path: /docs/reference/admin/node/admin.auth.SAMLUpdateAuthProviderRequest - - title: "Tenant" - path: /docs/reference/admin/node/admin.auth.Tenant - - title: "TenantAwareAuth" - path: /docs/reference/admin/node/admin.auth.TenantAwareAuth - - title: "TenantManager" - path: /docs/reference/admin/node/admin.auth.TenantManager - - title: "UpdateMultiFactorInfoRequest" - path: /docs/reference/admin/node/admin.auth.UpdateMultiFactorInfoRequest - - title: "UpdatePhoneMultiFactorInfoRequest" - path: /docs/reference/admin/node/admin.auth.UpdatePhoneMultiFactorInfoRequest - - title: "UpdateRequest" - path: /docs/reference/admin/node/admin.auth.UpdateRequest - - title: "UpdateTenantRequest" - path: /docs/reference/admin/node/admin.auth.UpdateTenantRequest - - title: "UserImportOptions" - path: /docs/reference/admin/node/admin.auth.UserImportOptions - - title: "UserImportRecord" - path: /docs/reference/admin/node/admin.auth.UserImportRecord - - title: "UserImportResult" - path: /docs/reference/admin/node/admin.auth.UserImportResult - - title: "DecodedIdToken" - path: /docs/reference/admin/node/admin.auth.DecodedIdToken - - title: "UserInfo" - path: /docs/reference/admin/node/admin.auth.UserInfo - - title: "UserMetadata" - path: /docs/reference/admin/node/admin.auth.UserMetadata - - title: "UserMetadataRequest" - path: /docs/reference/admin/node/admin.auth.UserMetadataRequest - - title: "UserProviderRequest" - path: /docs/reference/admin/node/admin.auth.UserProviderRequest - - title: "UserRecord" - path: /docs/reference/admin/node/admin.auth.UserRecord - - title: "UserProvider" - path: /docs/reference/admin/node/admin.auth.UserProvider - - title: "SessionCookieOptions" - path: /docs/reference/admin/node/admin.auth.SessionCookieOptions - - title: "BaseAuth" - path: /docs/reference/admin/node/admin.auth.BaseAuth - - title: "ListUsersResult" - path: /docs/reference/admin/node/admin.auth.ListUsersResult - - title: "GetUsersResult" - path: /docs/reference/admin/node/admin.auth.GetUsersResult - - title: "DeleteUsersResult" - path: /docs/reference/admin/node/admin.auth.DeleteUsersResult - - title: "UidIdentifier" - path: /docs/reference/admin/node/admin.auth.UidIdentifier - - title: "EmailIdentifier" - path: /docs/reference/admin/node/admin.auth.EmailIdentifier - - title: "PhoneIdentifier" - path: /docs/reference/admin/node/admin.auth.PhoneIdentifier - - title: "ProviderIdentifier" - path: /docs/reference/admin/node/admin.auth.ProviderIdentifier - -- title: "admin.credential" - path: /docs/reference/admin/node/admin.credential - section: - - title: "Credential" - path: /docs/reference/admin/node/admin.credential.Credential-1 - -- title: "admin.database" - path: /docs/reference/admin/node/admin.database - section: - - title: "Database" - path: /docs/reference/admin/node/admin.database.Database-1 - -- title: "admin.firestore" - path: /docs/reference/admin/node/admin.firestore - -- title: "admin.instanceId" - path: /docs/reference/admin/node/admin.instanceId - section: - - title: "InstanceId" - path: /docs/reference/admin/node/admin.instanceId.InstanceId-1 - -- title: "admin.machineLearning" - path: /docs/reference/admin/node/admin.machineLearning - section: - - title: "ListModelsOptions" - path: /docs/reference/admin/node/admin.machineLearning.ListModelsOptions - - title: "ListModelsResult" - path: /docs/reference/admin/node/admin.machineLearning.ListModelsResult - - title: "MachineLearning" - path: /docs/reference/admin/node/admin.machineLearning.MachineLearning-1 - - title: "Model" - path: /docs/reference/admin/node/admin.machineLearning.Model - - title: "ModelOptionsBase" - path: /docs/reference/admin/node/admin.machineLearning.ModelOptionsBase - - title: "GcsTfliteModelOptions" - path: /docs/reference/admin/node/admin.machineLearning.GcsTfliteModelOptions - - title: "AutoMLTfliteModelOptions" - path: /docs/reference/admin/node/admin.machineLearning.AutoMLTfliteModelOptions - - title: "TFLiteModel" - path: /docs/reference/admin/node/admin.machineLearning.TFLiteModel - -- title: "admin.messaging" - path: /docs/reference/admin/node/admin.messaging - section: - - title: "BaseMessage" - path: /docs/reference/admin/node/admin.messaging.BaseMessage - - title: "TopicMessage" - path: /docs/reference/admin/node/admin.messaging.TopicMessage - - title: "TokenMessage" - path: /docs/reference/admin/node/admin.messaging.TokenMessage - - title: "ConditionMessage" - path: /docs/reference/admin/node/admin.messaging.ConditionMessage - - title: "AndroidConfig" - path: /docs/reference/admin/node/admin.messaging.AndroidConfig - - title: "AndroidFcmOptions" - path: /docs/reference/admin/node/admin.messaging.AndroidFcmOptions - - title: "AndroidNotification" - path: /docs/reference/admin/node/admin.messaging.AndroidNotification - - title: "FcmOptions" - path: /docs/reference/admin/node/admin.messaging.FcmOptions - - title: "LightSettings" - path: /docs/reference/admin/node/admin.messaging.LightSettings - - title: "Messaging" - path: /docs/reference/admin/node/admin.messaging.Messaging-1 - - title: "MessagingConditionResponse" - path: /docs/reference/admin/node/admin.messaging.MessagingConditionResponse - - title: "MessagingDeviceGroupResponse" - path: /docs/reference/admin/node/admin.messaging.MessagingDeviceGroupResponse - - title: "MessagingDeviceResult" - path: /docs/reference/admin/node/admin.messaging.MessagingDeviceResult - - title: "MessagingDevicesResponse" - path: /docs/reference/admin/node/admin.messaging.MessagingDevicesResponse - - title: "MessagingOptions" - path: /docs/reference/admin/node/admin.messaging.MessagingOptions - - title: "MessagingPayload" - path: /docs/reference/admin/node/admin.messaging.MessagingPayload - - title: "MessagingTopicResponse" - path: /docs/reference/admin/node/admin.messaging.MessagingTopicResponse - - title: "MessagingTopicManagementResponse" - path: /docs/reference/admin/node/admin.messaging.MessagingTopicManagementResponse - - title: "NotificationMessagePayload" - path: /docs/reference/admin/node/admin.messaging.NotificationMessagePayload - - title: "MulticastMessage" - path: /docs/reference/admin/node/admin.messaging.MulticastMessage - - title: "WebpushNotification" - path: /docs/reference/admin/node/admin.messaging.WebpushNotification - - title: "WebpushFcmOptions" - path: /docs/reference/admin/node/admin.messaging.WebpushFcmOptions - - title: "DataMessagePayload" - path: /docs/reference/admin/node/admin.messaging.DataMessagePayload - - title: "BatchResponse" - path: /docs/reference/admin/node/admin.messaging.BatchResponse - - title: "SendResponse" - path: /docs/reference/admin/node/admin.messaging.SendResponse - - title: "ApnsConfig" - path: /docs/reference/admin/node/admin.messaging.ApnsConfig - - title: "ApnsFcmOptions" - path: /docs/reference/admin/node/admin.messaging.ApnsFcmOptions - - title: "ApnsPayload" - path: /docs/reference/admin/node/admin.messaging.ApnsPayload - - title: "Aps" - path: /docs/reference/admin/node/admin.messaging.Aps - - title: "ApsAlert" - path: /docs/reference/admin/node/admin.messaging.ApsAlert - - title: "CriticalSound" - path: /docs/reference/admin/node/admin.messaging.CriticalSound - - title: "Notification" - path: /docs/reference/admin/node/admin.messaging.Notification - - title: "WebpushConfig" - path: /docs/reference/admin/node/admin.messaging.WebpushConfig - -- title: "admin.projectManagement" - path: /docs/reference/admin/node/admin.projectManagement - section: - - title: "AndroidApp" - path: /docs/reference/admin/node/admin.projectManagement.AndroidApp - - title: "AndroidAppMetadata" - path: /docs/reference/admin/node/admin.projectManagement.AndroidAppMetadata - - title: "AppMetadata" - path: /docs/reference/admin/node/admin.projectManagement.AppMetadata - - title: "AppPlatform" - path: /docs/reference/admin/node/admin.projectManagement.AppPlatform - - title: "IosApp" - path: /docs/reference/admin/node/admin.projectManagement.IosApp - - title: "IosAppMetadata" - path: /docs/reference/admin/node/admin.projectManagement.IosAppMetadata - - title: "ProjectManagement" - path: /docs/reference/admin/node/admin.projectManagement.ProjectManagement-1 - - title: "ShaCertificate" - path: /docs/reference/admin/node/admin.projectManagement.ShaCertificate - -- title: "admin.securityRules" - path: /docs/reference/admin/node/admin.securityRules - section: - - title: "RulesFile" - path: /docs/reference/admin/node/admin.securityRules.RulesFile - - title: "Ruleset" - path: /docs/reference/admin/node/admin.securityRules.Ruleset - - title: "RulesetMetadata" - path: /docs/reference/admin/node/admin.securityRules.RulesetMetadata - - title: "RulesetMetadataList" - path: /docs/reference/admin/node/admin.securityRules.RulesetMetadataList - - title: "SecurityRules" - path: /docs/reference/admin/node/admin.securityRules.SecurityRules-1 - -- title: "admin.storage" - path: /docs/reference/admin/node/admin.storage - section: - - title: "Storage" - path: /docs/reference/admin/node/admin.storage.Storage-1 - -- title: "admin.remoteConfig" - path: /docs/reference/admin/node/admin.remoteConfig - section: - - title: "RemoteConfig" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfig-1 - - title: "RemoteConfigTemplate" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfigTemplate - - title: "RemoteConfigParameter" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfigParameter - - title: "RemoteConfigParameterGroup" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfigParameterGroup - - title: "RemoteConfigCondition" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfigCondition - - title: "ExplicitParameterValue" - path: /docs/reference/admin/node/admin.remoteConfig.ExplicitParameterValue - - title: "InAppDefaultValue" - path: /docs/reference/admin/node/admin.remoteConfig.InAppDefaultValue - - title: "Version" - path: /docs/reference/admin/node/admin.remoteConfig.Version - - title: "ListVersionsResult" - path: /docs/reference/admin/node/admin.remoteConfig.ListVersionsResult - - title: "ListVersionsOptions" - path: /docs/reference/admin/node/admin.remoteConfig.ListVersionsOptions - - title: "RemoteConfigUser" - path: /docs/reference/admin/node/admin.remoteConfig.RemoteConfigUser diff --git a/docgen/extras/firebase-admin.database.md b/docgen/extras/firebase-admin.database.md new file mode 100644 index 0000000000..0f33392cf0 --- /dev/null +++ b/docgen/extras/firebase-admin.database.md @@ -0,0 +1,12 @@ +## External API Re-exports + +The following externally defined APIs are re-exported from this module entry point for convenience. + +| Symbol | Description | +| --- | --- | +| [DataSnapshot](https://firebase.google.com/docs/reference/js/firebase.database.DataSnapshot) | `DataSnapshot` type from the `@firebase/database` package. | +| [EventType](https://firebase.google.com/docs/reference/js/firebase.database#eventtype) | `EventType` type from the `@firebase/database` package. | +| [OnDisconnect](https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect) | `OnDisconnect` type from the `@firebase/database` package. | +| [Query](https://firebase.google.com/docs/reference/js/firebase.database.Query) | `Query` type from the `@firebase/database` package. | +| [Reference](https://firebase.google.com/docs/reference/js/firebase.database.Reference) | `Reference` type from the `@firebase/database` package. | +| [ThenableReference](https://firebase.google.com/docs/reference/js/firebase.database.ThenableReference) | `ThenableReference` type from the `@firebase/database` package. | diff --git a/docgen/extras/firebase-admin.firestore.md b/docgen/extras/firebase-admin.firestore.md new file mode 100644 index 0000000000..9281e20c2b --- /dev/null +++ b/docgen/extras/firebase-admin.firestore.md @@ -0,0 +1,27 @@ +## External API Re-exports + +The following externally defined APIs are re-exported from this module entry point for convenience. + +| Symbol | Description | +| --- | --- | +| [BulkWriter](https://googleapis.dev/nodejs/firestore/latest/BulkWriter.html) | `BulkWriter` type from the `@google-cloud/firestore` package. | +| [BulkWriterOptions](https://googleapis.dev/nodejs/firestore/latest/global.html#BulkWriterOptions) | `BulkWriterOptions` type from the `@google-cloud/firestore` package. | +| [CollectionGroup](https://googleapis.dev/nodejs/firestore/latest/CollectionGroup.html) | `CollectionGroup` type from the `@google-cloud/firestore` package. | +| [CollectionReference](https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html) | `CollectionReference` type from the `@google-cloud/firestore` package. | +| [DocumentData](https://googleapis.dev/nodejs/firestore/latest/global.html#DocumentData) | `DocumentData` type from the `@google-cloud/firestore` package. | +| [DocumentReference](https://googleapis.dev/nodejs/firestore/latest/DocumentReference.html) | `DocumentReference` type from the `@google-cloud/firestore` package. | +| [DocumentSnapshot](https://googleapis.dev/nodejs/firestore/latest/DocumentSnapshot.html) | `DocumentSnapshot` type from the `@google-cloud/firestore` package. | +| [FieldPath](https://googleapis.dev/nodejs/firestore/latest/FieldPath.html) | `FieldPath` type from the `@google-cloud/firestore` package. | +| [FieldValue](https://googleapis.dev/nodejs/firestore/latest/FieldValue.html) | `FieldValue` type from the `@google-cloud/firestore` package. | +| [Firestore](https://googleapis.dev/nodejs/firestore/latest/Firestore.html) | `Firestore` type from the `@google-cloud/firestore` package. | +| [FirestoreDataConverter](https://googleapis.dev/nodejs/firestore/latest/global.html#FirestoreDataConverter) | `FirestoreDataConverter` type from the `@google-cloud/firestore` package. | +| [GeoPoint](https://googleapis.dev/nodejs/firestore/latest/GeoPoint.html) | `GeoPoint` type from the `@google-cloud/firestore` package. | +| [Query](https://googleapis.dev/nodejs/firestore/latest/Query.html) | `Query` type from the `@google-cloud/firestore` package. | +| [QueryDocumentSnapshot](https://googleapis.dev/nodejs/firestore/latest/QueryDocumentSnapshot.html) | `QueryDocumentSnapshot` type from the `@google-cloud/firestore` package. | +| [QueryPartition](https://googleapis.dev/nodejs/firestore/latest/QueryPartition.html) | `QueryPartition` type from the `@google-cloud/firestore` package. | +| [QuerySnapshot](https://googleapis.dev/nodejs/firestore/latest/QuerySnapshot.html) | `QuerySnapshot` type from the `@google-cloud/firestore` package. | +| [Timestamp](https://googleapis.dev/nodejs/firestore/latest/Timestamp.html) | `Timestamp` type from the `@google-cloud/firestore` package. | +| [Transaction](https://googleapis.dev/nodejs/firestore/latest/Transaction.html) | `Transaction` type from the `@google-cloud/firestore` package. | +| [WriteBatch](https://googleapis.dev/nodejs/firestore/latest/WriteBatch.html) | `WriteBatch` type from the `@google-cloud/firestore` package. | +| [WriteResult](https://googleapis.dev/nodejs/firestore/latest/WriteResult.html) | `WriteResult` type from the `@google-cloud/firestore` package. | +| [setLogFunction](https://googleapis.dev/nodejs/firestore/latest/global.html#setLogFunction) | `setLogFunction` function from the `@google-cloud/firestore` package. | diff --git a/docgen/generate-docs.js b/docgen/generate-docs.js deleted file mode 100644 index af35956e57..0000000000 --- a/docgen/generate-docs.js +++ /dev/null @@ -1,466 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const { exec } = require('child-process-promise'); -const fs = require('mz/fs'); -const jsdom = require('jsdom'); -const path = require('path'); -const readline = require('readline'); -const yargs = require('yargs'); -const yaml = require('js-yaml'); - -const repoPath = path.resolve(`${__dirname}/..`); - -const defaultSources = [ - `${repoPath}/lib/firebase-namespace.d.ts`, - `${repoPath}/lib/firebase-namespace-api.d.ts`, - `${repoPath}/lib/core.d.ts`, - `${repoPath}/lib/**/*.d.ts`, -]; - -// Command-line options. -const { source: sourceFile } = yargs - .option('source', { - default: defaultSources.join(' '), - describe: 'Typescript source file(s)', - type: 'string' - }) - .version(false) - .help().argv; - -const docPath = path.resolve(`${__dirname}/html/node`); -const contentPath = path.resolve(`${__dirname}/content-sources/node`); -const tempHomePath = path.resolve(`${contentPath}/HOME_TEMP.md`); -const devsitePath = `/docs/reference/admin/node/`; - -const firestoreExcludes = [ - 'v1', 'v1beta1', 'setLogFunction','DocumentData', - 'BulkWriterOptions', 'DocumentChangeType', 'FirestoreDataConverter', - 'GrpcStatus', 'Precondition', 'ReadOptions', 'UpdateData', 'Settings', -]; -const firestoreHtmlPath = `${docPath}/admin.firestore.html`; -const firestoreHeader = `
-

Type aliases

-
-

Following types are defined in the @google-cloud/firestore package - and re-exported from this namespace for convenience.

-
-
    `; -const firestoreFooter = '\n
\n
\n'; - -const databaseExcludes = ['enableLogging']; -const databaseHtmlPath = `${docPath}/admin.database.html`; -const databaseHeader = `
-

Type aliases

-
-

Following types are defined in the @firebase/database package - and re-exported from this namespace for convenience.

-
-
    `; -const databaseFooter = '\n
\n
\n'; - -/** - * Strips path prefix and returns only filename. - * @param {string} path - */ -function stripPath(path) { - const parts = path.split('/'); - return parts[parts.length - 1]; -} - -/** - * Runs Typedoc command. - * - * Additional config options come from ./typedoc.js - */ -function runTypedoc() { - const command = `${repoPath}/node_modules/.bin/typedoc ${sourceFile} \ - --out ${docPath} \ - --readme ${tempHomePath} \ - --options ${__dirname}/typedoc.js \ - --theme ${__dirname}/theme`; - - console.log('Running command:\n', command); - return exec(command); -} - -/** - * Moves files from subdir to root. - * @param {string} subdir Subdir to move files out of. - */ -function moveFilesToRoot(subdir) { - return exec(`mv ${docPath}/${subdir}/* ${docPath}`) - .then(() => { - exec(`rmdir ${docPath}/${subdir}`); - }) - .catch(e => console.error(e)); -} - -/** - * Reformat links to match flat structure. - * @param {string} file File to fix links in. - */ -function fixLinks(file) { - return fs.readFile(file, 'utf8').then(data => { - const flattenedLinks = data - .replace(/\.\.\//g, '') - .replace(/(modules|interfaces|classes|enums)\//g, ''); - let caseFixedLinks = flattenedLinks; - for (const lower in lowerToUpperLookup) { - const re = new RegExp('\\b' + lower, 'g'); - caseFixedLinks = caseFixedLinks.replace(re, lowerToUpperLookup[lower]); - } - return fs.writeFile(file, caseFixedLinks); - }); -} - -let tocText = ''; - -/** - * Generates temporary markdown file that will be sourced by Typedoc to - * create index.html. - * - * @param {string} tocRaw - * @param {string} homeRaw - */ -function generateTempHomeMdFile(tocRaw, homeRaw) { - const { toc } = yaml.safeLoad(tocRaw); - let tocPageLines = [homeRaw, '# API Reference']; - toc.forEach(group => { - tocPageLines.push(`\n## [${group.title}](${stripPath(group.path)}.html)`); - const section = group.section || []; - section.forEach(item => { - tocPageLines.push(`- [${item.title}](${stripPath(item.path)}.html)`); - }); - }); - return fs.writeFile(tempHomePath, tocPageLines.join('\n')); -} - -/** - * Mapping between lowercase file name and correctly cased name. - * Used to update links when filenames are capitalized. - */ -const lowerToUpperLookup = {}; - -/** - * Checks to see if any files listed in toc.yaml were not generated. - * If files exist, fixes filename case to match toc.yaml version. - */ -function checkForMissingFilesAndFixFilenameCase() { - // Get filenames from toc.yaml. - const filenames = tocText - .split('\n') - .filter(line => line.includes('path:')) - .map(line => line.split(devsitePath)[1]); - // Logs warning to console if a file from TOC is not found. - const fileCheckPromises = filenames.map(filename => { - // Warns if file does not exist, fixes filename case if it does. - // Preferred filename for devsite should be capitalized and taken from - // toc.yaml. - const tocFilePath = `${docPath}/${filename}.html`; - // Generated filename from Typedoc will be lowercase and won't have the admin prefix. - const generatedFilePath = `${docPath}/${filename.toLowerCase().replace('admin.', '')}.html`; - return fs.exists(generatedFilePath).then(exists => { - if (exists) { - // Store in a lookup table for link fixing. - lowerToUpperLookup[ - `${filename.toLowerCase().replace('admin.', '')}.html` - ] = `${filename}.html`; - return fs.rename(generatedFilePath, tocFilePath); - } else { - console.warn( - `Missing file: ${filename}.html requested ` + - `in toc.yaml but not found in ${docPath}` - ); - } - }); - }); - - return Promise.all(fileCheckPromises).then(() => filenames); -} - -/** - * Gets a list of html files in generated dir and checks if any are not - * found in toc.yaml. - * Option to remove the file if not found (used for node docs). - * - * @param {Array} filenamesFromToc Filenames pulled from toc.yaml - * @param {boolean} shouldRemove Should just remove the file - */ -function checkForUnlistedFiles(filenamesFromToc, shouldRemove) { - return fs.readdir(docPath).then(files => { - const htmlFiles = files - .filter(filename => filename.slice(-4) === 'html') - .map(filename => filename.slice(0, -5)); - const removePromises = []; - htmlFiles.forEach(filename => { - if ( - !filenamesFromToc.includes(filename) && - filename !== 'index' && - filename !== 'globals' - ) { - if (shouldRemove) { - console.log( - `REMOVING ${docPath}/${filename}.html - not listed in toc.yaml.` - ); - removePromises.push(fs.unlink(`${docPath}/${filename}.html`)); - } else { - // This is just a warning, it doesn't need to finish before - // the process continues. - console.warn( - `Unlisted file: ${filename} generated ` + - `but not listed in toc.yaml.` - ); - } - } - }); - if (shouldRemove) { - return Promise.all(removePromises).then(() => - htmlFiles.filter(filename => filenamesFromToc.includes(filename)) - ); - } else { - return htmlFiles; - } - }); -} - -/** - * Writes a _toc_autogenerated.yaml as a record of all files that were - * autogenerated. Helpful to tech writers. - * - * @param {Array} htmlFiles List of html files found in generated dir. - */ -function writeGeneratedFileList(htmlFiles) { - const fileList = htmlFiles.map(filename => { - return { - title: filename, - path: `${devsitePath}${filename}` - }; - }); - const generatedTocYAML = yaml.safeDump({ toc: fileList }); - return fs - .writeFile(`${docPath}/_toc_autogenerated.yaml`, generatedTocYAML) - .then(() => htmlFiles); -} - -/** - * Fix all links in generated files to other generated files to point to top - * level of generated docs dir. - * - * @param {Array} htmlFiles List of html files found in generated dir. - */ -function fixAllLinks(htmlFiles) { - const writePromises = []; - htmlFiles.forEach(file => { - // Update links in each html file to match flattened file structure. - writePromises.push(fixLinks(`${docPath}/${file}.html`)); - }); - return Promise.all(writePromises); -} - -/** - * Updates the auto-generated Firestore API references page, by appending - * the specified HTML content block. - * - * @param {string} htmlPath Path of the HTML file to update. - * @param {string} contentBlock The HTML content block to be added to the Firestore docs. - */ -function updateHtml(htmlPath, contentBlock) { - const dom = new jsdom.JSDOM(fs.readFileSync(htmlPath)); - const contentNode = dom.window.document.body.querySelector('.col-12'); - - // Recent versions of Typedoc generates an additional index section and a variables - // section for namespaces with re-exports. We iterate through these nodes and remove - // them from the output. - const sections = []; - contentNode.childNodes.forEach((child) => { - if (child.nodeName === 'SECTION') { - sections.push(child); - } - }); - contentNode.removeChild(sections[1]); - contentNode.removeChild(sections[2]); - - const newSection = new jsdom.JSDOM(contentBlock); - contentNode.appendChild(newSection.window.document.body.firstChild); - fs.writeFileSync(htmlPath, dom.window.document.documentElement.outerHTML); -} - -/** - * Adds Firestore type aliases to the auto-generated API docs. These are the - * types that are imported from the @google-cloud/firestore package, and - * then re-exported from the admin.firestore namespace. Typedoc currently - * does not handle these correctly, so we need this solution instead. - */ -function addFirestoreTypeAliases() { - return new Promise((resolve, reject) => { - const fileStream = fs.createReadStream(`${repoPath}/lib/firestore/index.d.ts`); - fileStream.on('error', (err) => { - reject(err); - }); - const lineReader = readline.createInterface({ - input: fileStream, - }); - - let contentBlock = firestoreHeader; - lineReader.on('line', (line) => { - line = line.trim(); - if (line.startsWith('export import') && line.indexOf('_firestore.') >= 0) { - const typeName = line.split(' ')[2]; - if (firestoreExcludes.indexOf(typeName) === -1) { - contentBlock += ` -
  • - ${typeName} -
  • `; - } - } - }); - - lineReader.on('close', () => { - try { - contentBlock += firestoreFooter; - updateHtml(firestoreHtmlPath, contentBlock); - resolve(); - } catch (err) { - reject(err); - } - }); - }); -} - -/** - * Adds RTDB type aliases to the auto-generated API docs. These are the - * types that are imported from the @firebase/database package, and - * then re-exported from the admin.database namespace. Typedoc currently - * does not handle these correctly, so we need this solution instead. - */ -function addDatabaseTypeAliases() { - return new Promise((resolve, reject) => { - const fileStream = fs.createReadStream(`${repoPath}/lib/database/index.d.ts`); - fileStream.on('error', (err) => { - reject(err); - }); - const lineReader = readline.createInterface({ - input: fileStream, - }); - - let contentBlock = databaseHeader; - lineReader.on('line', (line) => { - line = line.trim(); - if (line.startsWith('export import') && line.indexOf('rtdb.') >= 0) { - const typeName = line.split(' ')[2]; - if (databaseExcludes.indexOf(typeName) === -1) { - contentBlock += ` -
  • - ${typeName} -
  • `; - } - } - }); - - lineReader.on('close', () => { - try { - contentBlock += databaseFooter; - updateHtml(databaseHtmlPath, contentBlock); - resolve(); - } catch (err) { - reject(err); - } - }); - }); -} - -/** - * Main document generation process. - * - * Steps for generating documentation: - * 1) Create temporary md file as source of homepage. - * 2) Run Typedoc, sourcing index.d.ts for API content and temporary md file - * for index.html content. - * 3) Write table of contents file. - * 4) Flatten file structure by moving all items up to root dir and fixing - * links as needed. - * 5) Check for mismatches between TOC list and generated file list. - */ -Promise.all([ - fs.readFile(`${contentPath}/toc.yaml`, 'utf8'), - fs.readFile(`${contentPath}/HOME.md`, 'utf8') -]) - // Read TOC and homepage text and assemble a homepage markdown file. - // This file will be sourced by Typedoc to generate index.html. - .then(([tocRaw, homeRaw]) => { - tocText = tocRaw; - return generateTempHomeMdFile(tocRaw, homeRaw); - }) - // Run main Typedoc process (uses index.d.ts and generated temp file above). - .then(runTypedoc) - .then(output => { - // Typedoc output. - console.log(output.stdout); - // Clean up temp home markdown file. (Nothing needs to wait for this.) - return fs.unlink(tempHomePath); - }) - // Write out TOC file. Do this after Typedoc step to prevent Typedoc - // erroring when it finds an unexpected file in the target dir. - .then(() => fs.writeFile(`${docPath}/_toc.yaml`, tocText)) - // Flatten file structure. These categories don't matter to us and it makes - // it easier to manage the docs directory. - .then(() => { - return Promise.all([ - // moveFilesToRoot('classes'), - moveFilesToRoot('modules'), - moveFilesToRoot('interfaces'), - moveFilesToRoot('enums'), - ]); - }) - // Rename the globals file to be the top-level admin doc. - .then(() => fs.rename(`${docPath}/globals.html`, `${docPath}/admin.html`)) - // Check for files listed in TOC that are missing and warn if so. - // Not blocking. - .then(checkForMissingFilesAndFixFilenameCase) - // Check for files that exist but aren't listed in the TOC and warn. - // (If API is node, actually remove the file.) - // Removal is blocking, warnings aren't. - .then(filenamesFromToc => - checkForUnlistedFiles(filenamesFromToc, true) - ) - // Write a _toc_autogenerated.yaml to record what files were created. - .then(htmlFiles => writeGeneratedFileList(htmlFiles)) - // Correct the links in all the generated html files now that files have - // all been moved to top level. - .then(fixAllLinks) - // Add local variable include line to index.html (to access current SDK - // version number). - .then(addFirestoreTypeAliases) - .then(addDatabaseTypeAliases) - .then(() => { - fs.readFile(`${docPath}/index.html`, 'utf8').then(data => { - // String to include devsite local variables. - const localVariablesIncludeString = `{% include "docs/web/_local_variables.html" %}\n`; - return fs.writeFile( - `${docPath}/index.html`, - localVariablesIncludeString + data - ); - }); - }) - .catch(e => { - if (e.stdout) { - console.error(e.stdout); - } else { - console.error(e); - } - }); diff --git a/docgen/post-process.js b/docgen/post-process.js new file mode 100644 index 0000000000..8c66cf5772 --- /dev/null +++ b/docgen/post-process.js @@ -0,0 +1,84 @@ +/** + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const fs = require('mz/fs'); +const path = require('path'); +const readline = require('readline'); + +async function main() { + const extras = await getExtraFiles(); + for (const source of extras) { + await applyExtraContentFrom(source); + } +} + +async function getExtraFiles() { + const extrasPath = path.join(__dirname, 'extras'); + const files = await fs.readdir(extrasPath); + return files + .filter((name) => name.endsWith('.md')) + .map((name) => path.join(__dirname, 'extras', name)); +} + +async function applyExtraContentFrom(source) { + const target = path.join(__dirname, 'markdown', path.basename(source)); + if (!await fs.exists(target)) { + console.log(`Target path not found: ${target}`); + return; + } + + const extra = await readExtraContentFrom(source); + await writeExtraContentTo(target, extra); +} + +async function writeExtraContentTo(target, extra) { + const output = []; + const reader = readline.createInterface({ + input: fs.createReadStream(target), + }); + for await (const line of reader) { + output.push(line); + if (line.startsWith('{% block body %}')) { + output.push(...extra); + } + } + + const outputBuffer = Buffer.from(output.join('\r\n')); + console.log(`Writing extra content to ${target}`); + await fs.writeFile(target, outputBuffer); +} + +async function readExtraContentFrom(source) { + const reader = readline.createInterface({ + input: fs.createReadStream(source), + }); + const content = ['']; + for await (const line of reader) { + content.push(line); + } + + return content; +} + +(async () => { + try { + await main(); + } catch (err) { + console.log(err); + process.exit(1); + } +})(); diff --git a/docgen/theme/assets/css/firebase.css b/docgen/theme/assets/css/firebase.css deleted file mode 100644 index 86c5928201..0000000000 --- a/docgen/theme/assets/css/firebase.css +++ /dev/null @@ -1,40 +0,0 @@ -.firebase-docs .project-name { - color: #333; - display: inline-block; - font-size: 20px; - font-weight: normal; - margin-left: 130px; -} - -.firebase-docs aside.tsd-sources { - padding: 8px; -} - -.firebase-docs .tsd-panel li { - margin: 0; -} - -.firebase-docs aside.tsd-sources:before { - content: unset; -} - -.firebase-docs dl.tsd-comment-tags dt.tag-example { - float: none; - text-transform: capitalize; - color: #000; - font-size: 1.1em; - padding: 5px; - border: none; -} - -.firebase-docs dl.tsd-comment-tags dd.tag-body-example { - padding-left: 0; -} - -.firebase-docs .tsd-breadcrumb .breadcrumb-name a { - color: #039be5; -} - -.firebase-docs .tsd-breadcrumb .model-name { - color: #333; -} \ No newline at end of file diff --git a/docgen/theme/assets/css/main.css b/docgen/theme/assets/css/main.css deleted file mode 100644 index 12f3d05d96..0000000000 --- a/docgen/theme/assets/css/main.css +++ /dev/null @@ -1,552 +0,0 @@ -/*! normalize.css v1.1.3 | MIT License | git.io/normalize */ -/* ========================================================================== HTML5 display definitions ========================================================================== */ -/** Correct `block` display not defined in IE 6/7/8/9 and Firefox 3. */ -article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; } - -/** Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3. */ -audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } - -/** Prevent modern browsers from displaying `audio` without controls. Remove excess height in iOS 5 devices. */ -audio:not([controls]) { display: none; height: 0; } - -/** Address styling not present in IE 7/8/9, Firefox 3, and Safari 4. Known issue: no IE 6 support. */ -[hidden] { display: none; } - -/* ========================================================================== Base ========================================================================== */ -/** 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using `em` units. 2. Prevent iOS text size adjust after orientation change, without disabling user zoom. */ -html { font-size: 100%; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ font-family: sans-serif; } - -/** Address `font-family` inconsistency between `textarea` and other form elements. */ -button, input, select, textarea { font-family: sans-serif; } - -/** Address margins handled incorrectly in IE 6/7. */ -body { margin: 0; } - -/* ========================================================================== Links ========================================================================== */ -/** Address `outline` inconsistency between Chrome and other browsers. */ -a:focus { outline: thin dotted; } -a:active, a:hover { outline: 0; } - -/** Improve readability when focused and also mouse hovered in all browsers. */ -/* ========================================================================== Typography ========================================================================== */ -/** Address font sizes and margins set differently in IE 6/7. Address font sizes within `section` and `article` in Firefox 4+, Safari 5, and Chrome. */ -h1 { font-size: 2em; margin: 0.67em 0; } - -h2 { font-size: 1.5em; margin: 0.83em 0; } - -h3 { font-size: 1.17em; margin: 1em 0; } - -h4, .tsd-index-panel h3 { font-size: 1em; margin: 1.33em 0; } - -h5 { font-size: 0.83em; margin: 1.67em 0; } - -h6 { font-size: 0.67em; margin: 2.33em 0; } - -/** Address styling not present in IE 7/8/9, Safari 5, and Chrome. */ -abbr[title] { border-bottom: 1px dotted; } - -/** Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome. */ -b, strong { font-weight: bold; } - -blockquote { margin: 1em 40px; } - -/** Address styling not present in Safari 5 and Chrome. */ -dfn { font-style: italic; } - -/** Address differences between Firefox and other browsers. Known issue: no IE 6/7 normalization. */ -hr { box-sizing: content-box; height: 0; } - -/** Address styling not present in IE 6/7/8/9. */ -mark { background: #ff0; color: #000; } - -/** Address margins set differently in IE 6/7. */ -p, pre { margin: 1em 0; } - -/** Improve readability of pre-formatted text in all browsers. */ -pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } - -/** Address CSS quotes not supported in IE 6/7. */ -q { quotes: none; } -q:before, q:after { content: ""; content: none; } - -/** Address `quotes` property not supported in Safari 4. */ -/** Address inconsistent and variable font size in all browsers. */ -small { font-size: 80%; } - -/** Prevent `sub` and `sup` affecting `line-height` in all browsers. */ -sub { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } - -sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; top: -0.5em; } - -sub { bottom: -0.25em; } - -/* ========================================================================== Lists ========================================================================== */ -dd { margin: 0 0 0 40px; } - -/** Address paddings set differently in IE 6/7. */ -menu, ol, ul { padding: 0 0 0 40px; } - -/** Correct list images handled incorrectly in IE 7. */ -nav ul, nav ol { list-style: none; list-style-image: none; } - -/* ========================================================================== Embedded content ========================================================================== */ -/** 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3. 2. Improve image quality when scaled in IE 7. */ -img { border: 0; /* 1 */ -ms-interpolation-mode: bicubic; } - -/* 2 */ -/** Correct overflow displayed oddly in IE 9. */ -svg:not(:root) { overflow: hidden; } - -/* ========================================================================== Figures ========================================================================== */ -/** Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11. */ -figure, form { margin: 0; } - -/* ========================================================================== Forms ========================================================================== */ -/** Correct margin displayed oddly in IE 6/7. */ -/** Define consistent border, margin, and padding. */ -fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } - -/** 1. Correct color not being inherited in IE 6/7/8/9. 2. Correct text not wrapping in Firefox 3. 3. Correct alignment displayed oddly in IE 6/7. */ -legend { border: 0; /* 1 */ padding: 0; white-space: normal; /* 2 */ *margin-left: -7px; } - -/* 3 */ -/** 1. Correct font size not being inherited in all browsers. 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5, and Chrome. 3. Improve appearance and consistency in all browsers. */ -button, input, select, textarea { font-size: 100%; /* 1 */ margin: 0; /* 2 */ vertical-align: baseline; /* 3 */ *vertical-align: middle; } - -/* 3 */ -/** Address Firefox 3+ setting `line-height` on `input` using `!important` in the UA stylesheet. */ -button, input { line-height: normal; } - -/** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+. Correct `select` style inheritance in Firefox 4+ and Opera. */ -button, select { text-transform: none; } - -/** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. 4. Remove inner spacing in IE 7 without affecting normal text inputs. Known issue: inner spacing remains in IE 6. */ -button, html input[type="button"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ *overflow: visible; } - -/* 4 */ -input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ *overflow: visible; } - -/* 4 */ -/** Re-set default cursor for disabled elements. */ -button[disabled], html input[disabled] { cursor: default; } - -/** 1. Address box sizing set to content-box in IE 8/9. 2. Remove excess padding in IE 8/9. 3. Remove excess padding in IE 7. Known issue: excess padding remains in IE 6. */ -input { /* 3 */ } -input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ *height: 13px; /* 3 */ *width: 13px; } -input[type="search"] { -webkit-appearance: textfield; /* 1 */ /* 2 */ box-sizing: content-box; } -input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } - -/** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */ -/** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */ -/** Remove inner padding and border in Firefox 3+. */ -button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } - -/** 1. Remove default vertical scrollbar in IE 6/7/8/9. 2. Improve readability and alignment in all browsers. */ -textarea { overflow: auto; /* 1 */ vertical-align: top; } - -/* 2 */ -/* ========================================================================== Tables ========================================================================== */ -/** Remove most spacing between table cells. */ -table { border-collapse: collapse; border-spacing: 0; } - -.hljs { display: inline-block; padding: 0.5em; background: white; color: #37474f; } - -.hljs-comment, -.hljs-annotation, -.hljs-template_comment, -.diff .hljs-header, -.hljs-chunk, -.apache .hljs-cbracket { color: #d81b60; } - -.hljs-keyword, -.hljs-id, -.hljs-built_in, -.css .smalltalk .hljs-class, -.hljs-winutils, -.bash .hljs-variable, -.tex .hljs-command, -.hljs-request, -.hljs-status, -.hljs-meta, -.nginx .hljs-title { color: #3b78e7; } - -.xml .hljs-tag { color: #3b78e7; } -.xml .hljs-tag .hljs-value { color: #3b78e7; } - -.hljs-string, -.hljs-title, -.hljs-parent, -.hljs-tag .hljs-value, -.hljs-rules .hljs-value { color: #0d904f; } - -.devsite-dark-code .hljs { display: inline-block; padding: 0.5em; background: white; color: #eceff1; } - -.devsite-dark-code .hljs-comment, -.devsite-dark-code .hljs-annotation, -.devsite-dark-code .hljs-template_comment, -.devsite-dark-code .diff .hljs-header, -.devsite-dark-code .hljs-chunk { color: #f06292; } - -.devsite-dark-code .hljs-keyword, -.devsite-dark-code .hljs-id, -.devsite-dark-code .hljs-built_in, -.devsite-dark-code .hljs-winutils, -.devsite-dark-code .hljs-request, -.devsite-dark-code .hljs-status, -.devsite-dark-code .hljs-meta { color: #4dd0e1; } - -.devsite-dark-code .hljs-string, -.devsite-dark-code .hljs-title, -.devsite-dark-code .hljs-parent, -.devsite-dark-code .hljs-tag .hljs-value, -.devsite-dark-code .hljs-rules .hljs-value { color: #9ccc65; } - -.col > :first-child, .col-1 > :first-child, .col-2 > :first-child, .col-3 > :first-child, .col-4 > :first-child, .col-5 > :first-child, .col-6 > :first-child, .col-7 > :first-child, .col-8 > :first-child, .col-9 > :first-child, .col-10 > :first-child, .col-11 > :first-child, .tsd-panel > :first-child, ul.tsd-descriptions > li > :first-child, .col > :first-child > :first-child, .col-1 > :first-child > :first-child, .col-2 > :first-child > :first-child, .col-3 > :first-child > :first-child, .col-4 > :first-child > :first-child, .col-5 > :first-child > :first-child, .col-6 > :first-child > :first-child, .col-7 > :first-child > :first-child, .col-8 > :first-child > :first-child, .col-9 > :first-child > :first-child, .col-10 > :first-child > :first-child, .col-11 > :first-child > :first-child, .tsd-panel > :first-child > :first-child, ul.tsd-descriptions > li > :first-child > :first-child, .col > :first-child > :first-child > :first-child, .col-1 > :first-child > :first-child > :first-child, .col-2 > :first-child > :first-child > :first-child, .col-3 > :first-child > :first-child > :first-child, .col-4 > :first-child > :first-child > :first-child, .col-5 > :first-child > :first-child > :first-child, .col-6 > :first-child > :first-child > :first-child, .col-7 > :first-child > :first-child > :first-child, .col-8 > :first-child > :first-child > :first-child, .col-9 > :first-child > :first-child > :first-child, .col-10 > :first-child > :first-child > :first-child, .col-11 > :first-child > :first-child > :first-child, .tsd-panel > :first-child > :first-child > :first-child, ul.tsd-descriptions > li > :first-child > :first-child > :first-child { margin-top: 0; } -.col > :last-child, .col-1 > :last-child, .col-2 > :last-child, .col-3 > :last-child, .col-4 > :last-child, .col-5 > :last-child, .col-6 > :last-child, .col-7 > :last-child, .col-8 > :last-child, .col-9 > :last-child, .col-10 > :last-child, .col-11 > :last-child, .tsd-panel > :last-child, ul.tsd-descriptions > li > :last-child, .col > :last-child > :last-child, .col-1 > :last-child > :last-child, .col-2 > :last-child > :last-child, .col-3 > :last-child > :last-child, .col-4 > :last-child > :last-child, .col-5 > :last-child > :last-child, .col-6 > :last-child > :last-child, .col-7 > :last-child > :last-child, .col-8 > :last-child > :last-child, .col-9 > :last-child > :last-child, .col-10 > :last-child > :last-child, .col-11 > :last-child > :last-child, .tsd-panel > :last-child > :last-child, ul.tsd-descriptions > li > :last-child > :last-child, .col > :last-child > :last-child > :last-child, .col-1 > :last-child > :last-child > :last-child, .col-2 > :last-child > :last-child > :last-child, .col-3 > :last-child > :last-child > :last-child, .col-4 > :last-child > :last-child > :last-child, .col-5 > :last-child > :last-child > :last-child, .col-6 > :last-child > :last-child > :last-child, .col-7 > :last-child > :last-child > :last-child, .col-8 > :last-child > :last-child > :last-child, .col-9 > :last-child > :last-child > :last-child, .col-10 > :last-child > :last-child > :last-child, .col-11 > :last-child > :last-child > :last-child, .tsd-panel > :last-child > :last-child > :last-child, ul.tsd-descriptions > li > :last-child > :last-child > :last-child { margin-bottom: 0; } - -.container { max-width: 1200px; margin: 0 auto; padding: 0 40px; } -@media (max-width: 640px) { .container { padding: 0 20px; } } - -.container-main { padding-bottom: 200px; } - -.row { position: relative; margin: 0 -10px; } -.row:after { visibility: hidden; display: block; content: ""; clear: both; height: 0; } - -.col, .col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11 { box-sizing: border-box; float: left; padding: 0 10px; } - -.col-1 { width: 8.33333%; } - -.offset-1 { margin-left: 8.33333%; } - -.col-2 { width: 16.66667%; } - -.offset-2 { margin-left: 16.66667%; } - -.col-3 { width: 25%; } - -.offset-3 { margin-left: 25%; } - -.col-4 { width: 33.33333%; } - -.offset-4 { margin-left: 33.33333%; } - -.col-5 { width: 41.66667%; } - -.offset-5 { margin-left: 41.66667%; } - -.col-6 { width: 50%; } - -.offset-6 { margin-left: 50%; } - -.col-7 { width: 58.33333%; } - -.offset-7 { margin-left: 58.33333%; } - -.col-8 { width: 66.66667%; } - -.offset-8 { margin-left: 66.66667%; } - -.col-9 { width: 75%; } - -.offset-9 { margin-left: 75%; } - -.col-10 { width: 83.33333%; } - -.offset-10 { margin-left: 83.33333%; } - -.col-11 { width: 91.66667%; } - -.offset-11 { margin-left: 91.66667%; } - -.tsd-kind-icon { display: block; position: relative; padding-left: 20px; text-indent: -20px; } - -.no-transition { transition: none !important; } - -@-webkit-keyframes fade-in { from { opacity: 0; } - to { opacity: 1; } } - -@keyframes fade-in { from { opacity: 0; } - to { opacity: 1; } } -@-webkit-keyframes fade-out { from { opacity: 1; visibility: visible; } - to { opacity: 0; } } -@keyframes fade-out { from { opacity: 1; visibility: visible; } - to { opacity: 0; } } -@-webkit-keyframes fade-in-delayed { 0% { opacity: 0; } - 33% { opacity: 0; } - 100% { opacity: 1; } } -@keyframes fade-in-delayed { 0% { opacity: 0; } - 33% { opacity: 0; } - 100% { opacity: 1; } } -@-webkit-keyframes fade-out-delayed { 0% { opacity: 1; visibility: visible; } - 66% { opacity: 0; } - 100% { opacity: 0; } } -@keyframes fade-out-delayed { 0% { opacity: 1; visibility: visible; } - 66% { opacity: 0; } - 100% { opacity: 0; } } -@-webkit-keyframes shift-to-left { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); } - to { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } } -@keyframes shift-to-left { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); } - to { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } } -@-webkit-keyframes unshift-to-left { from { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } - to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } -@keyframes unshift-to-left { from { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } - to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } -@-webkit-keyframes pop-in-from-right { from { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } - to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } -@keyframes pop-in-from-right { from { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } - to { -webkit-transform: translate(0, 0); transform: translate(0, 0); } } -@-webkit-keyframes pop-out-to-right { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); visibility: visible; } - to { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } } -@keyframes pop-out-to-right { from { -webkit-transform: translate(0, 0); transform: translate(0, 0); visibility: visible; } - to { -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } } - -a { color: #4da6ff; text-decoration: none; } -a:hover { text-decoration: underline; } - -pre { padding: 10px; } -pre code { padding: 0; font-size: 100%; background-color: transparent; } - -.tsd-typography ul { list-style: square; padding: 0 0 0 20px; margin: 0; } -.tsd-typography h4, .tsd-typography .tsd-index-panel h3, .tsd-index-panel .tsd-typography h3, .tsd-typography h5, .tsd-typography h6 { font-size: 1em; margin: 0; } -.tsd-typography h5, .tsd-typography h6 { font-weight: normal; } -.tsd-typography p, .tsd-typography ul, .tsd-typography ol { margin: 1em 0; } - -@media (min-width: 901px) and (max-width: 1024px) { html.default .col-content { width: 72%; } - html.default .col-menu { width: 28%; } - html.default .tsd-navigation { padding-left: 10px; } } -@media (max-width: 900px) { html.default .col-content { float: none; width: 100%; } - html.default .col-menu { position: fixed !important; overflow: auto; -webkit-overflow-scrolling: touch; overflow-scrolling: touch; z-index: 1024; top: 0 !important; bottom: 0 !important; left: auto !important; right: 0 !important; width: 100%; padding: 20px 20px 0 0; max-width: 450px; visibility: hidden; background-color: #fff; -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } - html.default .col-menu > *:last-child { padding-bottom: 20px; } - html.default .overlay { content: ""; display: block; position: fixed; z-index: 1023; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.75); visibility: hidden; } - html.default.to-has-menu .overlay { -webkit-animation: fade-in 0.4s; animation: fade-in 0.4s; } - html.default.to-has-menu header, html.default.to-has-menu footer, html.default.to-has-menu .col-content { -webkit-animation: shift-to-left 0.4s; animation: shift-to-left 0.4s; } - html.default.to-has-menu .col-menu { -webkit-animation: pop-in-from-right 0.4s; animation: pop-in-from-right 0.4s; } - html.default.from-has-menu .overlay { -webkit-animation: fade-out 0.4s; animation: fade-out 0.4s; } - html.default.from-has-menu header, html.default.from-has-menu footer, html.default.from-has-menu .col-content { -webkit-animation: unshift-to-left 0.4s; animation: unshift-to-left 0.4s; } - html.default.from-has-menu .col-menu { -webkit-animation: pop-out-to-right 0.4s; animation: pop-out-to-right 0.4s; } - html.default.has-menu body { overflow: hidden; } - html.default.has-menu .overlay { visibility: visible; } - html.default.has-menu header, html.default.has-menu footer, html.default.has-menu .col-content { -webkit-transform: translate(-25%, 0); transform: translate(-25%, 0); } - html.default.has-menu .col-menu { visibility: visible; -webkit-transform: translate(0, 0); transform: translate(0, 0); } } - -.tsd-page-title { padding: 0; margin: 0; background: #fff; } -.tsd-page-title h1 { font-weight: normal; margin: 0; } - -.tsd-breadcrumb { margin: 0; padding: 0; color: #808080; } -.tsd-breadcrumb a { color: #808080; text-decoration: none; } -.tsd-breadcrumb a:hover { text-decoration: underline; } -.tsd-breadcrumb li { display: inline; margin-right: -0.25em; } - -html.minimal .container { margin: 0; } -html.minimal .container-main { padding-top: 50px; padding-bottom: 0; } -html.minimal .content-wrap { padding-left: 300px; } -html.minimal .tsd-navigation { position: fixed !important; overflow: auto; -webkit-overflow-scrolling: touch; overflow-scrolling: touch; box-sizing: border-box; z-index: 1; left: 0; top: 40px; bottom: 0; width: 300px; padding: 20px; margin: 0; } -html.minimal .tsd-member .tsd-member { margin-left: 0; } -html.minimal .tsd-page-toolbar { position: fixed; z-index: 2; } -html.minimal #tsd-filter .tsd-filter-group { right: 0; -webkit-transform: none; transform: none; } -html.minimal footer { background-color: transparent; } -html.minimal footer .container { padding: 0; } -html.minimal .tsd-generator { padding: 0; } -@media (max-width: 900px) { html.minimal .tsd-navigation { display: none; } - html.minimal .content-wrap { padding-left: 0; } } - -dl.tsd-comment-tags { overflow: hidden; } -dl.tsd-comment-tags dt { clear: both; float: left; padding: 1px 5px; margin: 0 10px 0 0; border-radius: 4px; border: 1px solid #808080; color: #808080; font-size: 0.8em; font-weight: normal; } -dl.tsd-comment-tags dd { margin: 0 0 10px 0; } -dl.tsd-comment-tags p { margin: 0; } - -.tsd-panel.tsd-comment .lead { font-size: 1.1em; line-height: 1.333em; margin-bottom: 2em; } -.tsd-panel.tsd-comment .lead:last-child { margin-bottom: 0; } - -.toggle-protected .tsd-is-private { display: none; } - -.toggle-public .tsd-is-private, .toggle-public .tsd-is-protected, .toggle-public .tsd-is-private-protected { display: none; } - -.toggle-inherited .tsd-is-inherited { display: none; } - -.toggle-only-exported .tsd-is-not-exported { display: none; } - -.toggle-externals .tsd-is-external { display: none; } - -#tsd-filter { position: relative; display: inline-block; height: 40px; vertical-align: bottom; } -.no-filter #tsd-filter { display: none; } -#tsd-filter .tsd-filter-group { display: inline-block; height: 40px; vertical-align: bottom; white-space: nowrap; } -#tsd-filter input { display: none; } -@media (max-width: 900px) { #tsd-filter .tsd-filter-group { display: block; position: absolute; top: 40px; right: 20px; height: auto; background-color: #fff; visibility: hidden; -webkit-transform: translate(50%, 0); transform: translate(50%, 0); box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); } - .has-options #tsd-filter .tsd-filter-group { visibility: visible; } - .to-has-options #tsd-filter .tsd-filter-group { -webkit-animation: fade-in 0.2s; animation: fade-in 0.2s; } - .from-has-options #tsd-filter .tsd-filter-group { -webkit-animation: fade-out 0.2s; animation: fade-out 0.2s; } - #tsd-filter label, #tsd-filter .tsd-select { display: block; padding-right: 20px; } } - -footer { border-top: 1px solid #eee; background-color: #fff; } -footer.with-border-bottom { border-bottom: 1px solid #eee; } -footer .tsd-legend-group { font-size: 0; } -footer .tsd-legend { display: inline-block; width: 25%; padding: 0; font-size: 16px; list-style: none; line-height: 1.333em; vertical-align: top; } -@media (max-width: 900px) { footer .tsd-legend { width: 50%; } } - -.tsd-hierarchy { list-style: square; padding: 0 0 0 20px; margin: 0; } -.tsd-hierarchy .target { font-weight: bold; } - -.tsd-index-panel .tsd-index-content { margin-bottom: -30px !important; } -.tsd-index-panel .tsd-index-section { margin-bottom: 30px !important; } -.tsd-index-panel h3 { margin: 0 -20px 10px -20px; padding: 0 20px 10px 20px; border-bottom: 1px solid #eee; } -.tsd-index-panel ul.tsd-index-list { -webkit-column-count: 3; -moz-column-count: 3; -ms-column-count: 3; -o-column-count: 3; column-count: 3; -webkit-column-gap: 20px; -moz-column-gap: 20px; -ms-column-gap: 20px; -o-column-gap: 20px; column-gap: 20px; padding: 0; list-style: none; line-height: 1.333em; } -@media (max-width: 900px) { .tsd-index-panel ul.tsd-index-list { -webkit-column-count: 1; -moz-column-count: 1; -ms-column-count: 1; -o-column-count: 1; column-count: 1; } } -@media (min-width: 901px) and (max-width: 1024px) { .tsd-index-panel ul.tsd-index-list { -webkit-column-count: 2; -moz-column-count: 2; -ms-column-count: 2; -o-column-count: 2; column-count: 2; } } -.tsd-index-panel ul.tsd-index-list li { -webkit-column-break-inside: avoid; -moz-column-break-inside: avoid; -ms-column-break-inside: avoid; -o-column-break-inside: avoid; column-break-inside: avoid; -webkit-page-break-inside: avoid; -moz-page-break-inside: avoid; -ms-page-break-inside: avoid; -o-page-break-inside: avoid; page-break-inside: avoid; } - -.tsd-flag { display: inline-block; padding: 1px 5px; border-radius: 4px; color: #fff; background-color: #808080; text-indent: 0; font-size: 14px; font-weight: normal; line-height: 1.5em; } - -.tsd-anchor { position: absolute; top: -100px; } - -.tsd-member { position: relative; } -.tsd-member .tsd-anchor + h3 { margin-top: 0; margin-bottom: 0; border-bottom: none; } - -.tsd-navigation { padding: 0 0 0 40px; } -.tsd-navigation a { display: block; padding-top: 2px; padding-bottom: 2px; border-left: 2px solid transparent; color: #222; text-decoration: none; transition: border-left-color 0.1s; } -.tsd-navigation a:hover { text-decoration: underline; } -.tsd-navigation ul { margin: 0; padding: 0; list-style: none; } -.tsd-navigation li { padding: 0; } - -.tsd-navigation.primary { padding-bottom: 40px; } -.tsd-navigation.primary a { display: block; padding-top: 6px; padding-bottom: 6px; } -.tsd-navigation.primary ul li a { padding-left: 5px; } -.tsd-navigation.primary ul li li a { padding-left: 25px; } -.tsd-navigation.primary ul li li li a { padding-left: 45px; } -.tsd-navigation.primary ul li li li li a { padding-left: 65px; } -.tsd-navigation.primary ul li li li li li a { padding-left: 85px; } -.tsd-navigation.primary ul li li li li li li a { padding-left: 105px; } -.tsd-navigation.primary > ul { border-bottom: 1px solid #eee; } -.tsd-navigation.primary li { border-top: 1px solid #eee; } -.tsd-navigation.primary li.current > a { font-weight: bold; } -.tsd-navigation.primary li.label span { display: block; padding: 20px 0 6px 5px; color: #808080; } -.tsd-navigation.primary li.globals + li > span, .tsd-navigation.primary li.globals + li > a { padding-top: 20px; } - -.tsd-navigation.secondary ul { transition: opacity 0.2s; } -.tsd-navigation.secondary ul li a { padding-left: 25px; } -.tsd-navigation.secondary ul li li a { padding-left: 45px; } -.tsd-navigation.secondary ul li li li a { padding-left: 65px; } -.tsd-navigation.secondary ul li li li li a { padding-left: 85px; } -.tsd-navigation.secondary ul li li li li li a { padding-left: 105px; } -.tsd-navigation.secondary ul li li li li li li a { padding-left: 125px; } -.tsd-navigation.secondary ul.current a { border-left-color: #eee; } -.tsd-navigation.secondary li.focus > a, .tsd-navigation.secondary ul.current li.focus > a { border-left-color: #000; } -.tsd-navigation.secondary li.current { margin-top: 20px; margin-bottom: 20px; border-left-color: #eee; } -.tsd-navigation.secondary li.current > a { font-weight: bold; } - -@media (min-width: 901px) { .menu-sticky-wrap { position: static; } - .no-csspositionsticky .menu-sticky-wrap.sticky { position: fixed; } - .no-csspositionsticky .menu-sticky-wrap.sticky-current { position: fixed; } - .no-csspositionsticky .menu-sticky-wrap.sticky-current ul.before-current, .no-csspositionsticky .menu-sticky-wrap.sticky-current ul.after-current { opacity: 0; } - .no-csspositionsticky .menu-sticky-wrap.sticky-bottom { position: absolute; top: auto !important; left: auto !important; bottom: 0; right: 0; } - .csspositionsticky .menu-sticky-wrap.sticky { position: -webkit-sticky; position: sticky; } - .csspositionsticky .menu-sticky-wrap.sticky-current { position: -webkit-sticky; position: sticky; } } - -.tsd-panel { margin: 20px 0; padding: 20px; background-color: #fff; } -.tsd-panel:empty { display: none; } -.tsd-panel > h1, .tsd-panel > h2, .tsd-panel > h3 { margin: 1.5em -20px 10px -20px; padding: 0 20px 10px 20px; border-bottom: 1px solid #ebebeb; } -.tsd-panel > h1.tsd-before-signature, .tsd-panel > h2.tsd-before-signature, .tsd-panel > h3.tsd-before-signature { margin-bottom: 0; border-bottom: 0; } -.tsd-panel table { display: block; width: 100%; overflow: auto; margin-top: 10px; word-break: normal; word-break: keep-all; } -.tsd-panel table th { font-weight: bold; } -.tsd-panel table th, .tsd-panel table td { padding: 6px 13px; border: 1px solid #ddd; } -.tsd-panel table tr { background-color: #fff; border-top: 1px solid #ccc; } -.tsd-panel table tr:nth-child(2n) { background-color: #f8f8f8; } - -.tsd-panel-group { margin: 60px 0; } -.tsd-panel-group > h1, .tsd-panel-group > h2, .tsd-panel-group > h3 { padding-left: 20px; padding-right: 20px; } - -#tsd-search { transition: background-color 0.2s; } -#tsd-search .title { position: relative; z-index: 2; } -#tsd-search .field { position: absolute; left: 0; top: 0; right: 40px; height: 40px; } -#tsd-search .field input { box-sizing: border-box; position: relative; top: -50px; z-index: 1; width: 100%; padding: 0 10px; opacity: 0; outline: 0; border: 0; background: transparent; color: #222; } -#tsd-search .field label { position: absolute; overflow: hidden; right: -40px; } -#tsd-search .field input, #tsd-search .title { transition: opacity 0.2s; } -#tsd-search .results { position: absolute; visibility: hidden; top: 40px; width: 100%; margin: 0; padding: 0; list-style: none; box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); } -#tsd-search .results li { padding: 0 10px; background-color: #fdfdfd; } -#tsd-search .results li:nth-child(even) { background-color: #fff; } -#tsd-search .results li.state { display: none; } -#tsd-search .results li.current, #tsd-search .results li:hover { background-color: #eee; } -#tsd-search .results a { display: block; } -#tsd-search .results a:before { top: 10px; } -#tsd-search .results span.parent { color: #808080; font-weight: normal; } -#tsd-search.has-focus { background-color: #eee; } -#tsd-search.has-focus .field input { top: 0; opacity: 1; } -#tsd-search.has-focus .title { z-index: 0; opacity: 0; } -#tsd-search.has-focus .results { visibility: visible; } -#tsd-search.loading .results li.state.loading { display: block; } -#tsd-search.failure .results li.state.failure { display: block; } - -.tsd-signature { margin: 0 0 1em 0; padding: 10px; border: 1px solid #eee; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; } -.tsd-signature.tsd-kind-icon { padding-left: 30px; } -.tsd-panel > .tsd-signature { margin-left: -20px; margin-right: -20px; border-width: 1px 0; } -.tsd-panel > .tsd-signature.tsd-kind-icon { padding-left: 40px; } - -.tsd-signature-symbol { color: #808080; font-weight: normal; } - -.tsd-signature-type { font-style: italic; font-weight: normal; } - -.tsd-signatures { padding: 0; margin: 0 0 1em 0; border: 1px solid #eee; } -.tsd-signatures .tsd-signature { margin: 0; border-width: 1px 0 0 0; transition: background-color 0.1s; } -.tsd-signatures .tsd-signature:first-child { border-top-width: 0; } -.tsd-signatures .tsd-signature.current { background-color: #eee; } -.tsd-signatures.active > .tsd-signature { cursor: pointer; } -.tsd-panel > .tsd-signatures { margin-left: -20px; margin-right: -20px; border-width: 1px 0; } -.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon { padding-left: 40px; } -.tsd-panel > a.anchor + .tsd-signatures { border-top-width: 0; margin-top: -20px; } - -ul.tsd-descriptions { position: relative; overflow: hidden; transition: height 0.3s; padding: 0; list-style: none; } -ul.tsd-descriptions.active > .tsd-description { display: none; } -ul.tsd-descriptions.active > .tsd-description.current { display: block; } -ul.tsd-descriptions.active > .tsd-description.fade-in { -webkit-animation: fade-in-delayed 0.3s; animation: fade-in-delayed 0.3s; } -ul.tsd-descriptions.active > .tsd-description.fade-out { -webkit-animation: fade-out-delayed 0.3s; animation: fade-out-delayed 0.3s; position: absolute; display: block; top: 0; left: 0; right: 0; opacity: 0; visibility: hidden; } -ul.tsd-descriptions h4, ul.tsd-descriptions .tsd-index-panel h3, .tsd-index-panel ul.tsd-descriptions h3 { font-size: 16px; margin: 1em 0 0.5em 0; } - -ul.tsd-parameters, ul.tsd-type-parameters { list-style: square; margin: 0; padding-left: 20px; } -ul.tsd-parameters > li.tsd-parameter-siganture, ul.tsd-type-parameters > li.tsd-parameter-siganture { list-style: none; margin-left: -20px; } -ul.tsd-parameters h5, ul.tsd-type-parameters h5 { font-size: 16px; margin: 1em 0 0.5em 0; } -ul.tsd-parameters .tsd-comment, ul.tsd-type-parameters .tsd-comment { margin-top: -0.5em; } - -.tsd-sources { font-size: 14px; color: #808080; } -.tsd-sources a { color: #808080; text-decoration: underline; } -.tsd-sources ul, .tsd-sources p { margin: 0 !important; } -.tsd-sources ul { list-style: none; padding: 0; } - -.tsd-page-toolbar { position: absolute; z-index: 1; top: 0; left: 0; width: 100%; height: 40px; color: #333; background: #fff; border-bottom: 1px solid #eee; } -.tsd-page-toolbar a { color: #333; text-decoration: none; } -.tsd-page-toolbar a.title { font-weight: bold; } -.tsd-page-toolbar a.title:hover { text-decoration: underline; } -.tsd-page-toolbar .table-wrap { display: table; width: 100%; height: 40px; } -.tsd-page-toolbar .table-cell { display: table-cell; position: relative; white-space: nowrap; line-height: 40px; } -.tsd-page-toolbar .table-cell:first-child { width: 100%; } - -.tsd-widget:before, .tsd-select .tsd-select-label:before, .tsd-select .tsd-select-list li:before { content: ""; display: inline-block; width: 40px; height: 40px; margin: 0 -8px 0 0; background-image: url(../images/widgets.png); background-repeat: no-repeat; text-indent: -1024px; vertical-align: bottom; } -@media (-webkit-min-device-pixel-ratio: 1.5), (min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { .tsd-widget:before, .tsd-select .tsd-select-label:before, .tsd-select .tsd-select-list li:before { background-image: url(../images/widgets@2x.png); background-size: 320px 40px; } } - -.tsd-widget { display: inline-block; overflow: hidden; opacity: 0.6; height: 40px; transition: opacity 0.1s, background-color 0.2s; vertical-align: bottom; cursor: pointer; } -.tsd-widget:hover { opacity: 0.8; } -.tsd-widget.active { opacity: 1; background-color: #eee; } -.tsd-widget.no-caption { width: 40px; } -.tsd-widget.no-caption:before { margin: 0; } -.tsd-widget.search:before { background-position: 0 0; } -.tsd-widget.menu:before { background-position: -40px 0; } -.tsd-widget.options:before { background-position: -80px 0; } -.tsd-widget.options, .tsd-widget.menu { display: none; } -@media (max-width: 900px) { .tsd-widget.options, .tsd-widget.menu { display: inline-block; } } -input[type=checkbox] + .tsd-widget:before { background-position: -120px 0; } -input[type=checkbox]:checked + .tsd-widget:before { background-position: -160px 0; } - -.tsd-select { position: relative; display: inline-block; height: 40px; transition: opacity 0.1s, background-color 0.2s; vertical-align: bottom; cursor: pointer; } -.tsd-select .tsd-select-label { opacity: 0.6; transition: opacity 0.2s; } -.tsd-select .tsd-select-label:before { background-position: -240px 0; } -.tsd-select.active .tsd-select-label { opacity: 0.8; } -.tsd-select.active .tsd-select-list { visibility: visible; opacity: 1; transition-delay: 0s; } -.tsd-select .tsd-select-list { position: absolute; visibility: hidden; top: 40px; left: 0; margin: 0; padding: 0; opacity: 0; list-style: none; box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); transition: visibility 0s 0.2s, opacity 0.2s; } -.tsd-select .tsd-select-list li { padding: 0 20px 0 0; background-color: #fdfdfd; } -.tsd-select .tsd-select-list li:before { background-position: 40px 0; } -.tsd-select .tsd-select-list li:nth-child(even) { background-color: #fff; } -.tsd-select .tsd-select-list li:hover { background-color: #eee; } -.tsd-select .tsd-select-list li.selected:before { background-position: -200px 0; } -@media (max-width: 900px) { .tsd-select .tsd-select-list { top: 0; left: auto; right: 100%; margin-right: -5px; } - .tsd-select .tsd-select-label:before { background-position: -280px 0; } } - -img { max-width: 100%; } diff --git a/docgen/theme/assets/images/lockup.png b/docgen/theme/assets/images/lockup.png deleted file mode 100644 index f4cdcf24a42787815f45a0fbd6b2211b69cf87a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2646 zcmV-c3aRypP)qsBRaHO8(yazJJDcK~r7o->-O8-CmCY zr#`z4j)yXEeD5C+QGXbQp&O3{6EN^efRRlACm;I?A{d6LFLdL<9qEFg1D^mmd|wt3 z48znHx^b-gd_m5Ej{^+djaf9@j|hfg>IdC8+I~|`$@3orIJi9vC-!?0!7xmHsN8V! zv28Hov*eTv-U-;2So2OZ3=0|#S#nA~dXus49GuwaMTGk>3{$z`=tGHBaz6JF{69*I z)m=oa;&>=lL@*3f8!9&(+L0=gv&e@5(p@9==0-nJs{ngs~{)x zp#rjiWM$D%LcX>j=QGVWUw`b;1R|7N`%VaHLxhP)foe!;#45B0#QKRaeSRFSMqw#s z_*1l;&$dcr7ER}m?)K(!%3GAvBHfXLW4pYFAi4U@6rA4$aQRDFM3{(Mf)edf7Sv9# zpdncdqUDr$AwRqk7RG|5btEGoq;p#flcV-Ai!(uUA- zzV&HY!Zg%^);U;E4oG)otI3(i%sBV^f}BN~VDfkPXa8x&ENaOqkr{OE>chA}1mC5O%rG5Hw&F!B+JzqA`xGNCb%!$EN$Q4uA33 z(XwMkkzV8ugbeq`90Lw)O;B>?pTZ3J>xyzB4Fx8CiK70k$%&j6*CPFhFbOR|^)rx< zc#V&^K&j;#57H~!91unBaM1rGBI6Du=HN401reI(5O?si3RvT*q>t*eyJKz%nyw_L zWZ_@c<(x`xfQcVA!0d@foXUyx09@RYK!iz{CMfY|<)V1XE)-fA^|9`-+5adKsPSj$ zpMyFxMO^5!OJj}!!)=*zIj!uekn;x(Fn{RbTavRVXodw<3aa{>K7v?LoMvi}L|Se1 zKaM0_xhVC|!A~5w*M)I$am+DbWaDZ%pDtuiLQbTZIkn`JTzE8#2$L{LP(s#T#E%4! zNZExRq^4;Vgt(*11sf&%Pm?Sj&sRbKiPGm{WqzCI;4eFV7)jbZc59V>1j>#ZM3U-p zae2()c&H64xR#tnmYs7u0NE@2~W%HNyu5NX>BV>XJDvF6>Ps!W0Z$W>G}|`LzoKHKvGmvqrMY z^fGL7y)sT@qgEp0QST30!W%H=FtRCEdOs&-Pq#8B&G|>pBf=C66!Z?e$993Ry8o_> z164$EiqGM3%Xbq6k=8XEa~S;OikwT2qnJM>mQvi3)4)oufr5&4FR1z)J^FI01*LaB zDGAqo%;DIskh}*}$|-LdoBNYXODWZOv-{?r+^gT%h6n?=odgZn0AF1MwZcPAL#>@l zzA^$xgcOvHfJ0GZbscjQv|q|8SxU+5Ni3zfDQ6KB<0~v^%?0%!`cdC0Zotv@J@rNe zpUw9LkX{?Y_SS98VRUoC%ARCF$bC5_mp-3BgaNEqL4BG_2z0n#B#JAZ+Z1%Lxn942 z>g?KDmobOYjVt!IC`W|`CC-3Ky=FaZ+<)wr1MaG2TRNRXJSxwKKin!aj0mX!ML>Z6x! zP%w4)6n%=hHs&yJ2Q2-su9=gd3qSWEi7k(u7e9;m}+`GAdCKv3g8 zOdlb?-5U_1Hm3FQnvXdg35FK_)3ElL(?SXwVnH~LV?Ou4&Xf_E(?YM3%T-)@3Z*Zn>X9eU8T|*y%r$VZYzD1yw$T zjd*IJ$Bi+sc-;4fd%QQ&J*nB#TOj}D+R2%Jy$fbecEI(4O)12Sh}yywFPb0{EfXn* z^wAr_)j>SYBU+M4;>4zNonu;$#nkV&zsm6=8XFJDF?TW^D5}3Lm-c7oGF$2{=j`bY z$PI7K;6>ENA~B4`>OUOn4&4~@XBS`Uab3>&bDeNwwEg_`fz2UAFbwlS=*FS0{)xlg zx#ib-YA)x}%bhTBusw^P)35F%!?2)~q~7a8or%e#-IMt@^yOTBwF{<>cI5Eb3B;@J zAj7brN>I|a+-PTN=2Vw+IjsVa{GOR&F&RBrP@2IG{C?Vrg`{rVa?YLYEEa$^xAB0x z3B!U~qJCxKP$w3W2r-ex7r6lBIt&Y{5H&G*q$9WdswL*q%UxUmVvK+Vl?=9fk;KHo zj*Ns0K<>k^pbW#XpbW#XpbW!;G7Q6lG7JmKFpLSj52E7_O`Jf{)c^nh07*qoM6N<$ Eg8U-`K>z>% diff --git a/docgen/theme/layouts/default.hbs b/docgen/theme/layouts/default.hbs deleted file mode 100644 index 485d831d71..0000000000 --- a/docgen/theme/layouts/default.hbs +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - {{#ifCond model.name '==' project.name}}{{project.name}}{{else}}{{model.name}} | {{project.name}}{{/ifCond}} - - - - - - - -{{> header}} - -
    -
    -
    - {{{contents}}} -
    -
    -
    - -
    - -{{> analytics}} - - - diff --git a/docgen/theme/partials/breadcrumb.hbs b/docgen/theme/partials/breadcrumb.hbs deleted file mode 100644 index db115163f7..0000000000 --- a/docgen/theme/partials/breadcrumb.hbs +++ /dev/null @@ -1,11 +0,0 @@ - -{{#if parent}} - {{#with parent}}{{> breadcrumb}}{{/with}} - -{{/if}} \ No newline at end of file diff --git a/docgen/theme/partials/comment.hbs b/docgen/theme/partials/comment.hbs deleted file mode 100644 index f92e54301a..0000000000 --- a/docgen/theme/partials/comment.hbs +++ /dev/null @@ -1,22 +0,0 @@ -{{#with comment}} - {{#if hasVisibleComponent}} -
    - {{#if shortText}} -
    - {{#markdown}}{{{shortText}}}{{/markdown}} -
    - {{/if}} - {{#if text}} - {{#markdown}}{{{text}}}{{/markdown}} - {{/if}} - {{#if tags}} -
    - {{#each tags}} -
    {{tagName}}
    -
    {{#markdown}}{{{text}}}{{/markdown}}
    - {{/each}} -
    - {{/if}} -
    - {{/if}} -{{/with}} \ No newline at end of file diff --git a/docgen/theme/partials/header.hbs b/docgen/theme/partials/header.hbs deleted file mode 100644 index 4aee65a6b3..0000000000 --- a/docgen/theme/partials/header.hbs +++ /dev/null @@ -1,23 +0,0 @@ -
    -
    -
    -

    - {{#ifCond model.name '==' project.name}} - {{else}} -
      - {{#with model.parent}}{{> breadcrumb}}{{/with}} -
    • {{model.name}}
    • - {{#if model.typeParameters}} - < - {{#each model.typeParameters}} - {{#if @index}}, {{/if}} - {{name}} - {{/each}} - > - {{/if}} -
    - {{/ifCond}} -

    -
    -
    -
    \ No newline at end of file diff --git a/docgen/theme/partials/member.sources.hbs b/docgen/theme/partials/member.sources.hbs deleted file mode 100644 index 5a0e186f01..0000000000 --- a/docgen/theme/partials/member.sources.hbs +++ /dev/null @@ -1,15 +0,0 @@ -{{#if implementationOf}} - -{{/if}} -{{#if inheritedFrom}} - -{{/if}} -{{#if overwrites}} - -{{/if}} \ No newline at end of file diff --git a/docgen/theme/partials/navigation.hbs b/docgen/theme/partials/navigation.hbs deleted file mode 100644 index 54704739a8..0000000000 --- a/docgen/theme/partials/navigation.hbs +++ /dev/null @@ -1,22 +0,0 @@ -{{#if isVisible}} - {{#if isLabel}} -
  • - {{{wbr title}}} -
  • - {{else}} - {{#unless isGlobals}} -
  • - {{{wbr title}}} - {{#if isInPath}} - {{#if children}} -
      - {{#each children}} - {{> navigation}} - {{/each}} -
    - {{/if}} - {{/if}} -
  • - {{/unless}} - {{/if}} -{{/if}} diff --git a/docgen/theme/templates/reflection.hbs b/docgen/theme/templates/reflection.hbs deleted file mode 100644 index 53cd2879a4..0000000000 --- a/docgen/theme/templates/reflection.hbs +++ /dev/null @@ -1,72 +0,0 @@ -{{#with model}} - {{#if hasComment}} -
    - {{> comment}} -
    - {{/if}} -{{/with}} - -{{#if model.typeParameters}} -
    -

    Type parameters

    - {{#with model}}{{> typeParameters}}{{/with}} -
    -{{/if}} - -{{#if model.implementedTypes}} -
    -

    Implements

    -
      - {{#each model.implementedTypes}} -
    • {{> type}}
    • - {{/each}} -
    -
    -{{/if}} - -{{#if model.implementedBy}} -
    -

    Implemented by

    -
      - {{#each model.implementedBy}} -
    • {{> type}}
    • - {{/each}} -
    -
    -{{/if}} - -{{#if model.signatures}} -
    -

    Callable

    - {{#with model}}{{> member.signatures}}{{/with}} -
    -{{/if}} - -{{#if model.indexSignature}} -
    -

    Indexable

    -
    {{#compact}} - [ - {{#each model.indexSignature.parameters}} - {{name}}: {{#with type}}{{>type}}{{/with}} - {{/each}} - ]:  - {{#with model.indexSignature.type}}{{>type}}{{/with}} - {{/compact}}
    - - {{#with model.indexSignature}} - {{> comment}} - {{/with}} - - {{#if model.indexSignature.type.declaration}} - {{#with model.indexSignature.type.declaration}} - {{> parameter}} - {{/with}} - {{/if}} -
    -{{/if}} - -{{#with model}} - {{> index}} - {{> members}} -{{/with}} \ No newline at end of file diff --git a/docgen/tsconfig.json b/docgen/tsconfig.json deleted file mode 100644 index 3c43903cfd..0000000000 --- a/docgen/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../tsconfig.json" -} diff --git a/docgen/typedoc.js b/docgen/typedoc.js deleted file mode 100644 index 788c9cc071..0000000000 --- a/docgen/typedoc.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @license - * Copyright 2019 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const options = { - includeDeclarations: true, - excludeExternals: true, - ignoreCompilerErrors: true, - name: 'Admin Node.js SDK', - mode: 'file', - hideGenerator: true -}; - -module.exports = options; diff --git a/etc/firebase-admin.firestore.api.md b/etc/firebase-admin.firestore.api.md index 4d300b5397..7d83ee108a 100644 --- a/etc/firebase-admin.firestore.api.md +++ b/etc/firebase-admin.firestore.api.md @@ -62,7 +62,7 @@ export { GeoPoint } // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @public export function getFirestore(app?: App): Firestore; export { GrpcStatus } diff --git a/package-lock.json b/package-lock.json index fb0b99043a..cd49174385 100644 --- a/package-lock.json +++ b/package-lock.json @@ -155,6 +155,105 @@ "to-fast-properties": "^2.0.0" } }, + "@firebase/api-documenter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@firebase/api-documenter/-/api-documenter-0.1.1.tgz", + "integrity": "sha512-/8EtiyrWquuv6Byy9JYlYrclxAfPIwzUPCAzhOb14shGZW/YWANm8WRHbNSVOFXbZMGd89s3WZX3gVw2zWjZxA==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "0.12.24", + "@rushstack/node-core-library": "3.36.0", + "@rushstack/ts-command-line": "4.7.8", + "api-extractor-model-me": "0.1.1", + "colors": "~1.2.1", + "resolve": "~1.17.0", + "tslib": "^2.1.0" + }, + "dependencies": { + "@microsoft/tsdoc": { + "version": "0.12.24", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", + "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", + "dev": true + }, + "@rushstack/node-core-library": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", + "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", + "dev": true, + "requires": { + "@types/node": "10.17.13", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~3.18.3" + } + }, + "@rushstack/ts-command-line": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.8.tgz", + "integrity": "sha512-8ghIWhkph7NnLCMDJtthpsb7TMOsVGXVDvmxjE/CeklTqjbbUFBjGXizJfpbEkRQTELuZQ2+vGn7sGwIWKN2uA==", + "dev": true, + "requires": { + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "colors": "~1.2.1", + "string-argv": "~0.3.1" + } + }, + "@types/node": { + "version": "10.17.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", + "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", + "dev": true + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "@firebase/app": { "version": "0.6.13", "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.13.tgz", @@ -1069,12 +1168,6 @@ "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, - "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", - "dev": true - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1096,36 +1189,12 @@ "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", "dev": true }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - } - } - }, "acorn-jsx": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", "dev": true }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", - "dev": true - }, "agent-base": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", @@ -1216,6 +1285,82 @@ "normalize-path": "^2.1.1" } }, + "api-extractor-model-me": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/api-extractor-model-me/-/api-extractor-model-me-0.1.1.tgz", + "integrity": "sha512-Ez801ZMADfkseOWNRFquvyQYDm3D9McpxfkKMWL6JFCGcpub0miJ+TFNphIR1nSZbrsxz3kIeOovNMY4VlL6Bw==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "0.12.24", + "@rushstack/node-core-library": "3.36.0" + }, + "dependencies": { + "@microsoft/tsdoc": { + "version": "0.12.24", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", + "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", + "dev": true + }, + "@rushstack/node-core-library": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", + "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", + "dev": true, + "requires": { + "@types/node": "10.17.13", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~3.18.3" + } + }, + "@types/node": { + "version": "10.17.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", + "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", + "dev": true + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "append-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", @@ -1319,12 +1464,6 @@ "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true - }, "array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", @@ -1477,12 +1616,6 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true - }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -1682,12 +1815,6 @@ } } }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, "browser-stdout": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", @@ -2263,29 +2390,6 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "optional": true }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -2305,17 +2409,6 @@ "assert-plus": "^1.0.0" } }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - } - }, "date-and-time": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.2.tgz", @@ -2526,15 +2619,6 @@ "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==", "dev": true }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - } - }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -2738,28 +2822,6 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, "eslint": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", @@ -3431,44 +3493,6 @@ "map-cache": "^0.2.2" } }, - "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", - "dev": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^1.0.0" - }, - "dependencies": { - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } - } - }, - "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", - "dev": true - } - } - }, "fs-minipass": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", @@ -4131,27 +4155,6 @@ "glogg": "^1.0.0" } }, - "handlebars": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -4274,12 +4277,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "highlight.js": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.5.0.tgz", - "integrity": "sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw==", - "dev": true - }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -4295,15 +4292,6 @@ "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4563,12 +4551,6 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", @@ -5030,40 +5012,6 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, - "jsdom": { - "version": "15.2.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", - "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^7.1.0", - "acorn-globals": "^4.3.2", - "array-equal": "^1.0.0", - "cssom": "^0.4.1", - "cssstyle": "^2.0.0", - "data-urls": "^1.1.0", - "domexception": "^1.0.1", - "escodegen": "^1.11.1", - "html-encoding-sniffer": "^1.0.2", - "nwsapi": "^2.2.0", - "parse5": "5.1.0", - "pn": "^1.1.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.7", - "saxes": "^3.1.9", - "symbol-tree": "^3.2.2", - "tough-cookie": "^3.0.1", - "w3c-hr-time": "^1.0.1", - "w3c-xmlserializer": "^1.1.2", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^7.0.0", - "ws": "^7.0.0", - "xml-name-validator": "^3.0.0" - } - }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -5469,12 +5417,6 @@ "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", "dev": true }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, "lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", @@ -5569,12 +5511,6 @@ "yallist": "^3.0.2" } }, - "lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -5614,12 +5550,6 @@ "object-visit": "^1.0.0" } }, - "marked": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.5.tgz", - "integrity": "sha512-2AlqgYnVPOc9WDyWu7S5DJaEZsfk6dNh/neatQ3IHUW4QLutM/VPSH9lG7bif+XjFWc9K9XR3QvR+fXuECmfdA==", - "dev": true - }, "matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", @@ -6305,12 +6235,6 @@ } } }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, "nested-error-stacks": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", @@ -6616,12 +6540,6 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, "nyc": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", @@ -7094,12 +7012,6 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, - "parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", - "dev": true - }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -7265,12 +7177,6 @@ "extend-shallow": "^3.0.2" } }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -7688,29 +7594,6 @@ "lodash": "^4.17.15" } }, - "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", - "dev": true, - "requires": { - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7848,15 +7731,6 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, - "saxes": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", - "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", - "dev": true, - "requires": { - "xmlchars": "^2.1.1" - } - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -7929,17 +7803,6 @@ "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", "dev": true }, - "shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -8466,12 +8329,6 @@ "es6-symbol": "^3.1.1" } }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -8812,26 +8669,6 @@ } } }, - "tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "dev": true, - "requires": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, "ts-node": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", @@ -8915,76 +8752,12 @@ "is-typedarray": "^1.0.0" } }, - "typedoc": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.19.2.tgz", - "integrity": "sha512-oDEg1BLEzi1qvgdQXc658EYgJ5qJLVSeZ0hQ57Eq4JXy6Vj2VX4RVo18qYxRWz75ifAaYuYNBUCnbhjd37TfOg==", - "dev": true, - "requires": { - "fs-extra": "^9.0.1", - "handlebars": "^4.7.6", - "highlight.js": "^10.2.0", - "lodash": "^4.17.20", - "lunr": "^2.3.9", - "marked": "^1.1.1", - "minimatch": "^3.0.0", - "progress": "^2.0.3", - "semver": "^7.3.2", - "shelljs": "^0.8.4", - "typedoc-default-themes": "^0.11.4" - }, - "dependencies": { - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } - } - }, - "typedoc-default-themes": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.11.4.tgz", - "integrity": "sha512-Y4Lf+qIb9NTydrexlazAM46SSLrmrQRqWiD52593g53SsmUFioAsMWt8m834J6qsp+7wHRjxCXSZeiiW5cMUdw==", - "dev": true - }, "typescript": { "version": "3.9.6", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz", "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==", "dev": true }, - "uglify-js": { - "version": "3.12.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.1.tgz", - "integrity": "sha512-o8lHP20KjIiQe5b/67Rh68xEGRrc2SRsCuuoYclXXoC74AfSRGblU1HKzJWH3HxPZ+Ort85fWHpSX7KwBUC9CQ==", - "dev": true, - "optional": true - }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -9264,32 +9037,6 @@ "vinyl": "^2.0.0" } }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", - "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", - "dev": true, - "requires": { - "domexception": "^1.0.1", - "webidl-conversions": "^4.0.2", - "xml-name-validator": "^3.0.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, "websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -9305,32 +9052,6 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -9361,12 +9082,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, "workerpool": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", @@ -9409,30 +9124,12 @@ "typedarray-to-buffer": "^3.1.5" } }, - "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==", - "dev": true - }, "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "optional": true }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, "xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", @@ -9457,9 +9154,9 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yargs": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.1.0.tgz", - "integrity": "sha512-upWFJOmDdHN0syLuESuvXDmrRcWd1QafJolHskzaw79uZa7/x53gxQKiR07W59GWY1tFhhU/Th9DrtSfpS782g==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz", + "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==", "dev": true, "requires": { "cliui": "^7.0.2", @@ -9467,7 +9164,7 @@ "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", - "y18n": "^5.0.2", + "y18n": "^5.0.5", "yargs-parser": "^20.2.2" }, "dependencies": { @@ -9487,9 +9184,9 @@ } }, "cliui": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.3.tgz", - "integrity": "sha512-Gj3QHTkVMPKqwP3f7B4KPkBZRMR9r4rfi5bXFpg1a+Svvj8l7q5CnkBkVQzfxT5DFSsGk2+PascOgL0JYkL2kw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { "string-width": "^4.2.0", @@ -9525,9 +9222,9 @@ "dev": true }, "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", "dev": true, "requires": { "emoji-regex": "^8.0.0", @@ -9556,15 +9253,15 @@ } }, "y18n": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.4.tgz", - "integrity": "sha512-deLOfD+RvFgrpAmSZgfGdWYE+OKyHcVHaRQ7NphG/63scpRvTHHeQMAxGGvaLVGJ+HYVcCXlzcTK0ZehFf+eHQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yargs-parser": { - "version": "20.2.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.3.tgz", - "integrity": "sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww==", + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", "dev": true } } diff --git a/package.json b/package.json index d75168d7ff..e581c9cd57 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,12 @@ "test:coverage": "nyc npm run test:unit", "lint:src": "eslint src/ --ext .ts", "lint:test": "eslint test/ --ext .ts", - "apidocs": "node docgen/generate-docs.js --api node", + "apidocs": "run-s api-extractor:local api-documenter api-documenter:post", "api-extractor": "node generate-reports.js", "api-extractor:local": "npm run build && node generate-reports.js --local", - "esm-wrap": "node generate-esm-wrapper.js" + "esm-wrap": "node generate-esm-wrapper.js", + "api-documenter": "api-documenter-fire markdown --input temp --output docgen/markdown -s", + "api-documenter:post": "node docgen/post-process.js" }, "nyc": { "extension": [ @@ -153,6 +155,7 @@ "@google-cloud/storage": "^5.3.0" }, "devDependencies": { + "@firebase/api-documenter": "^0.1.1", "@firebase/app": "^0.6.13", "@firebase/auth": "^0.16.2", "@firebase/auth-types": "^0.10.1", @@ -186,7 +189,6 @@ "gulp-replace": "^0.5.4", "gulp-typescript": "^5.0.1", "http-message-parser": "^0.0.34", - "jsdom": "^15.0.0", "lodash": "^4.17.15", "minimist": "^1.2.0", "mocha": "^8.0.0", @@ -200,8 +202,7 @@ "sinon": "^9.0.0", "sinon-chai": "^3.0.0", "ts-node": "^9.0.0", - "typedoc": "^0.19.2", "typescript": "^3.7.3", - "yargs": "^16.0.0" + "yargs": "^17.0.1" } } diff --git a/src/database/index.ts b/src/database/index.ts index e3c1058671..f5b71fb7c3 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -47,14 +47,12 @@ export const enableLogging: typeof rtdb.enableLogging = enableLoggingFunc; export const ServerValue: rtdb.ServerValue = serverValueConst; /** - * Gets the {@link database.Database `Database`} service for the default + * Gets the {@link Database} service for the default * app or a given app. * * `getDatabase()` can be called with no arguments to access the default - * app's {@link database.Database `Database`} service or as - * `getDatabase(app)` to access the - * {@link database.Database `Database`} service associated with a specific - * app. + * app's `Database` service or as `getDatabase(app)` to access the + * `Database` service associated with a specific app. * * @example * ```javascript @@ -79,14 +77,12 @@ export function getDatabase(app?: App): Database { } /** - * Gets the {@link database.Database `Database`} service for the default + * Gets the {@link Database} service for the default * app or a given app. * * `getDatabaseWithUrl()` can be called with no arguments to access the default - * app's {@link database.Database `Database`} service or as - * `getDatabaseWithUrl(app)` to access the - * {@link database.Database `Database`} service associated with a specific - * app. + * app's {@link Database} service or as `getDatabaseWithUrl(app)` to access the + * {@link Database} service associated with a specific app. * * @example * ```javascript diff --git a/src/firestore/index.ts b/src/firestore/index.ts index efb4047ac1..3d91bf9486 100644 --- a/src/firestore/index.ts +++ b/src/firestore/index.ts @@ -50,6 +50,33 @@ export { setLogFunction, } from '@google-cloud/firestore'; +/** + * Gets the {@link https://googleapis.dev/nodejs/firestore/latest/Firestore.html | Firestore} + * service for the default app or a given app. + * + * `getFirestore()` can be called with no arguments to access the default + * app's `Firestore` service or as `getFirestore(app)` to access the + * `Firestore` service associated with a specific app. + * + * @example + * ```javascript + * // Get the Firestore service for the default app + * const defaultFirestore = getFirestore(); + * ``` + * + * @example + * ```javascript + * // Get the Firestore service for a specific app + * const otherFirestore = getFirestore(app); + * ``` + * + * @param App whose `Firestore` service to + * return. If not provided, the default `Firestore` service will be returned. + * + * @returns The default {@link https://googleapis.dev/nodejs/firestore/latest/Firestore.html | Firestore} + * service if no app is provided or the `Firestore` service associated with the + * provided app. + */ export function getFirestore(app?: App): Firestore { if (typeof app === 'undefined') { app = getApp(); From 6cf83f8ff45c36ef9fc2e5f7ed7ac6989a5c55dd Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 18 May 2021 10:56:08 -0700 Subject: [PATCH 31/41] chore: Added post processing script to fix doc titles and links (#1273) --- docgen/post-process.js | 90 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 12 deletions(-) diff --git a/docgen/post-process.js b/docgen/post-process.js index 8c66cf5772..ae6993a915 100644 --- a/docgen/post-process.js +++ b/docgen/post-process.js @@ -20,12 +20,78 @@ const path = require('path'); const readline = require('readline'); async function main() { + await applyExtras(); + await fixHomePage(); + await fixTitles(); +} + +/** + * Adds extra content to the generated markdown files. Content in each file in the `extras/` + * directory is added/merged to the top of the corresponding markdown file in the `markdown/` + * directory. + */ +async function applyExtras() { const extras = await getExtraFiles(); for (const source of extras) { await applyExtraContentFrom(source); } } +/** + * Replace dotted module names in the home page with the correct slash-separated names. For + * example, `firebase-admin.foo` becomes `firebase-admin/foo`. Also replaces the term "Package" + * with "Module" for accuracy. + */ +async function fixHomePage() { + const homePage = path.join(__dirname, 'markdown', 'index.md'); + const content = await fs.readFile(homePage); + const updatedText = content.toString() + .replace(/\[firebase-admin\./g, '[firebase-admin/') + .replace(/_package/g, '_module') + .replace(/Package/g, 'Module'); + console.log(`Updating module listings in ${homePage}`); + await fs.writeFile(homePage, updatedText); +} + +/** + * Replaces dotted module names and the term "package" in page titles. For example, the title text + * `firebase-admin.foo package` becomes `firebase-admin/foo module`. + */ +async function fixTitles() { + const markdownDir = path.join(__dirname, 'markdown'); + const files = await fs.readdir(markdownDir); + for (const file of files) { + await fixTitleOf(path.join(markdownDir, file)); + } +} + +async function fixTitleOf(file) { + const reader = readline.createInterface({ + input: fs.createReadStream(file), + }); + + const buffer = []; + let updated = false; + for await (let line of reader) { + if (line.startsWith('{% block title %}')) { + if (line.match(/firebase-admin\./)) { + line = line.replace(/firebase-admin\./, 'firebase-admin/').replace('package', 'module'); + updated = true; + } else { + break; + } + } + + buffer.push(line); + } + + if (updated) { + console.log(`Updating title in ${file}`); + const content = Buffer.from(buffer.join('\r\n')); + await fs.writeFile(file, content); + } +} + async function getExtraFiles() { const extrasPath = path.join(__dirname, 'extras'); const files = await fs.readdir(extrasPath); @@ -45,6 +111,18 @@ async function applyExtraContentFrom(source) { await writeExtraContentTo(target, extra); } +async function readExtraContentFrom(source) { + const reader = readline.createInterface({ + input: fs.createReadStream(source), + }); + const content = ['']; + for await (const line of reader) { + content.push(line); + } + + return content; +} + async function writeExtraContentTo(target, extra) { const output = []; const reader = readline.createInterface({ @@ -62,18 +140,6 @@ async function writeExtraContentTo(target, extra) { await fs.writeFile(target, outputBuffer); } -async function readExtraContentFrom(source) { - const reader = readline.createInterface({ - input: fs.createReadStream(source), - }); - const content = ['']; - for await (const line of reader) { - content.push(line); - } - - return content; -} - (async () => { try { await main(); From 70d830bf46c4c7f526fc70f7291d2a5f506b8a96 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 25 May 2021 09:48:45 -0700 Subject: [PATCH 32/41] chore: Added package docs for module entry points (#1275) --- etc/firebase-admin.api.md | 2 -- etc/firebase-admin.app.api.md | 2 -- etc/firebase-admin.auth.api.md | 2 -- etc/firebase-admin.database.api.md | 2 -- etc/firebase-admin.firestore.api.md | 2 -- etc/firebase-admin.instance-id.api.md | 2 -- etc/firebase-admin.machine-learning.api.md | 2 -- etc/firebase-admin.messaging.api.md | 2 -- etc/firebase-admin.project-management.api.md | 2 -- etc/firebase-admin.remote-config.api.md | 2 -- etc/firebase-admin.security-rules.api.md | 2 -- etc/firebase-admin.storage.api.md | 2 -- src/app/index.ts | 6 ++++++ src/auth/index.ts | 6 ++++++ src/database/index.ts | 6 ++++++ src/default-namespace.d.ts | 6 ++++++ src/firestore/index.ts | 6 ++++++ src/instance-id/index.ts | 8 +++++++- src/machine-learning/index.ts | 6 ++++++ src/messaging/index.ts | 6 ++++++ src/project-management/index.ts | 6 ++++++ src/remote-config/index.ts | 6 ++++++ src/security-rules/index.ts | 6 ++++++ src/storage/index.ts | 6 ++++++ 24 files changed, 73 insertions(+), 25 deletions(-) diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index df6558951e..7ec4aaff22 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -457,6 +457,4 @@ export namespace storage { } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.app.api.md b/etc/firebase-admin.app.api.md index 1614e2acb3..794a74e9e4 100644 --- a/etc/firebase-admin.app.api.md +++ b/etc/firebase-admin.app.api.md @@ -85,6 +85,4 @@ export interface ServiceAccount { } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 8e45cf81f9..4241e2ecf7 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -454,6 +454,4 @@ export class UserRecord { } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.database.api.md b/etc/firebase-admin.database.api.md index 12ecdd6008..abc6b75a0c 100644 --- a/etc/firebase-admin.database.api.md +++ b/etc/firebase-admin.database.api.md @@ -48,6 +48,4 @@ export const ServerValue: rtdb.ServerValue; export { ThenableReference } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.firestore.api.md b/etc/firebase-admin.firestore.api.md index 7d83ee108a..392d55a71c 100644 --- a/etc/firebase-admin.firestore.api.md +++ b/etc/firebase-admin.firestore.api.md @@ -96,6 +96,4 @@ export { WriteBatch } export { WriteResult } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.instance-id.api.md b/etc/firebase-admin.instance-id.api.md index 81528b204c..0754a74e69 100644 --- a/etc/firebase-admin.instance-id.api.md +++ b/etc/firebase-admin.instance-id.api.md @@ -18,6 +18,4 @@ export class InstanceId { } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.machine-learning.api.md b/etc/firebase-admin.machine-learning.api.md index 70b0b778ab..149a81c61a 100644 --- a/etc/firebase-admin.machine-learning.api.md +++ b/etc/firebase-admin.machine-learning.api.md @@ -90,6 +90,4 @@ export interface TFLiteModel { } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.messaging.api.md b/etc/firebase-admin.messaging.api.md index 9b6416aa0d..a7ac9f1182 100644 --- a/etc/firebase-admin.messaging.api.md +++ b/etc/firebase-admin.messaging.api.md @@ -351,6 +351,4 @@ export interface WebpushNotification { } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.project-management.api.md b/etc/firebase-admin.project-management.api.md index 4b69129073..a2d20dc539 100644 --- a/etc/firebase-admin.project-management.api.md +++ b/etc/firebase-admin.project-management.api.md @@ -87,6 +87,4 @@ export class ShaCertificate { } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md index d85e53d66f..6e0e2eb142 100644 --- a/etc/firebase-admin.remote-config.api.md +++ b/etc/firebase-admin.remote-config.api.md @@ -114,6 +114,4 @@ export interface Version { } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.security-rules.api.md b/etc/firebase-admin.security-rules.api.md index 9fb95e3493..c3a75dd97e 100644 --- a/etc/firebase-admin.security-rules.api.md +++ b/etc/firebase-admin.security-rules.api.md @@ -57,6 +57,4 @@ export class SecurityRules { } -// (No @packageDocumentation comment for this package) - ``` diff --git a/etc/firebase-admin.storage.api.md b/etc/firebase-admin.storage.api.md index b0b7199eb7..278148d2b2 100644 --- a/etc/firebase-admin.storage.api.md +++ b/etc/firebase-admin.storage.api.md @@ -19,6 +19,4 @@ export class Storage { } -// (No @packageDocumentation comment for this package) - ``` diff --git a/src/app/index.ts b/src/app/index.ts index e343e4fac2..02a8509653 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -17,6 +17,12 @@ import { getSdkVersion } from '../utils'; +/** + * Firebase App and SDK initialization. + * + * @packageDocumentation + */ + export { App, AppOptions, FirebaseArrayIndexError, FirebaseError } from './core' export { initializeApp, getApp, getApps, deleteApp } from './lifecycle'; diff --git a/src/auth/index.ts b/src/auth/index.ts index 272762f0b5..afbbe780ca 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * Firebase Authentication. + * + * @packageDocumentation + */ + import { App, getApp } from '../app/index'; import { FirebaseApp } from '../app/firebase-app'; import { Auth } from './auth'; diff --git a/src/database/index.ts b/src/database/index.ts index f5b71fb7c3..57e9264f4e 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * Firebase Realtime Database. + * + * @packageDocumentation + */ + import * as rtdb from '@firebase/database-types'; import { enableLogging as enableLoggingFunc, diff --git a/src/default-namespace.d.ts b/src/default-namespace.d.ts index 22ea79d419..5405cc67c6 100644 --- a/src/default-namespace.d.ts +++ b/src/default-namespace.d.ts @@ -14,4 +14,10 @@ * limitations under the License. */ +/** + * Firebase namespaced API (legacy). + * + * @packageDocumentation + */ + export * from './firebase-namespace-api'; diff --git a/src/firestore/index.ts b/src/firestore/index.ts index 3d91bf9486..bc52480a44 100644 --- a/src/firestore/index.ts +++ b/src/firestore/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * Cloud Firestore. + * + * @packageDocumentation + */ + import { Firestore } from '@google-cloud/firestore'; import { App, getApp } from '../app'; import { FirebaseApp } from '../app/firebase-app'; diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index fdc44e8341..40043b9467 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -14,9 +14,15 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; +/** + * Firebase Instance ID service. + * + * @packageDocumentation + */ + import { App, getApp } from '../app/index'; import { InstanceId } from './instance-id'; +import { FirebaseApp } from '../app/firebase-app'; export { InstanceId }; diff --git a/src/machine-learning/index.ts b/src/machine-learning/index.ts index e10fb222ac..a3bf1d8f8a 100644 --- a/src/machine-learning/index.ts +++ b/src/machine-learning/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * Firebase Machine Learning. + * + * @packageDocumentation + */ + import { App, getApp } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { MachineLearning } from './machine-learning'; diff --git a/src/messaging/index.ts b/src/messaging/index.ts index 9d4cb990ae..233bf98988 100644 --- a/src/messaging/index.ts +++ b/src/messaging/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * Firebase Cloud Messaging (FCM). + * + * @packageDocumentation + */ + import { App, getApp } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { Messaging } from './messaging'; diff --git a/src/project-management/index.ts b/src/project-management/index.ts index 92dd34f449..1bdff385f9 100644 --- a/src/project-management/index.ts +++ b/src/project-management/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * Firebase project management. + * + * @packageDocumentation + */ + import { App, getApp } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { ProjectManagement } from './project-management'; diff --git a/src/remote-config/index.ts b/src/remote-config/index.ts index 24c8a1fa4e..ae7eaaad45 100644 --- a/src/remote-config/index.ts +++ b/src/remote-config/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * Firebase Remote Config. + * + * @packageDocumentation + */ + import { App, getApp } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { RemoteConfig } from './remote-config'; diff --git a/src/security-rules/index.ts b/src/security-rules/index.ts index 11770d381d..0f8c846b7e 100644 --- a/src/security-rules/index.ts +++ b/src/security-rules/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * Security Rules for Cloud Firestore and Cloud Storage. + * + * @packageDocumentation + */ + import { App, getApp } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { SecurityRules } from './security-rules'; diff --git a/src/storage/index.ts b/src/storage/index.ts index 878174afa2..bbab751584 100644 --- a/src/storage/index.ts +++ b/src/storage/index.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +/** + * Cloud Storage for Firebase. + * + * @packageDocumentation + */ + import { App, getApp } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { Storage } from './storage'; From 5d919533a6805ced9bbf02fda91410d10d087a8e Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 27 May 2021 14:21:59 -0700 Subject: [PATCH 33/41] chore: Added support for generating a toc file for docs (#1304) --- docgen/post-process.js | 22 +++++++++++++++++++ package-lock.json | 49 ++++++++++++++++++++++++------------------ package.json | 5 +++-- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/docgen/post-process.js b/docgen/post-process.js index ae6993a915..7366b2a86d 100644 --- a/docgen/post-process.js +++ b/docgen/post-process.js @@ -63,6 +63,9 @@ async function fixTitles() { for (const file of files) { await fixTitleOf(path.join(markdownDir, file)); } + + const tocFile = path.join(markdownDir, 'toc.yaml'); + await fixTocTitles(tocFile); } async function fixTitleOf(file) { @@ -92,6 +95,25 @@ async function fixTitleOf(file) { } } +async function fixTocTitles(file) { + const reader = readline.createInterface({ + input: fs.createReadStream(file), + }); + + const buffer = []; + for await (let line of reader) { + if (line.includes('- title: firebase-admin.')) { + line = line.replace(/firebase-admin\./, 'firebase-admin/'); + } + + buffer.push(line); + } + + console.log(`Updating titles in ${file}`); + const content = Buffer.from(buffer.join('\r\n')); + await fs.writeFile(file, content); +} + async function getExtraFiles() { const extrasPath = path.join(__dirname, 'extras'); const files = await fs.readdir(extrasPath); diff --git a/package-lock.json b/package-lock.json index cd49174385..339b6c80e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -156,9 +156,9 @@ } }, "@firebase/api-documenter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@firebase/api-documenter/-/api-documenter-0.1.1.tgz", - "integrity": "sha512-/8EtiyrWquuv6Byy9JYlYrclxAfPIwzUPCAzhOb14shGZW/YWANm8WRHbNSVOFXbZMGd89s3WZX3gVw2zWjZxA==", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@firebase/api-documenter/-/api-documenter-0.1.2.tgz", + "integrity": "sha512-aDofRZebqbMzrbo5WAi9f21qUTzhIub7yOszirik3AwujqOzcUr1F7lIFrI41686JD1Zw56lLL/B5EWZTwvVjA==", "dev": true, "requires": { "@microsoft/tsdoc": "0.12.24", @@ -166,6 +166,7 @@ "@rushstack/ts-command-line": "4.7.8", "api-extractor-model-me": "0.1.1", "colors": "~1.2.1", + "js-yaml": "4.0.0", "resolve": "~1.17.0", "tslib": "^2.1.0" }, @@ -211,15 +212,21 @@ "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", "dev": true }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "argparse": "^2.0.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + } } }, "lru-cache": { @@ -1324,17 +1331,6 @@ "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", "dev": true }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3493,6 +3489,17 @@ "map-cache": "^0.2.2" } }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs-minipass": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", diff --git a/package.json b/package.json index e581c9cd57..2b78d495b5 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,12 @@ "test:coverage": "nyc npm run test:unit", "lint:src": "eslint src/ --ext .ts", "lint:test": "eslint test/ --ext .ts", - "apidocs": "run-s api-extractor:local api-documenter api-documenter:post", + "apidocs": "run-s api-extractor:local api-documenter api-documenter:toc api-documenter:post", "api-extractor": "node generate-reports.js", "api-extractor:local": "npm run build && node generate-reports.js --local", "esm-wrap": "node generate-esm-wrapper.js", "api-documenter": "api-documenter-fire markdown --input temp --output docgen/markdown -s", + "api-documenter:toc": "api-documenter-fire toc --input temp --output docgen/markdown -p /docs/reference/admin/node -s", "api-documenter:post": "node docgen/post-process.js" }, "nyc": { @@ -155,7 +156,7 @@ "@google-cloud/storage": "^5.3.0" }, "devDependencies": { - "@firebase/api-documenter": "^0.1.1", + "@firebase/api-documenter": "^0.1.2", "@firebase/app": "^0.6.13", "@firebase/auth": "^0.16.2", "@firebase/auth-types": "^0.10.1", From f65deae4faacc2bb7817ad4a92585a16c6b54279 Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Wed, 2 Jun 2021 14:13:48 -0400 Subject: [PATCH 34/41] chore: Merge main branch into modular-sdk (#1312) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(core): Automate Daily Integration Tests (#1130) * Automate daily integration tests * Rename to nightly * Change to 6am and 8pm PT & remove tar verification * Fix schedule comment * Updating Google Cloud naming (#1122) * Reinstating tag that devsite needs present to supress machine translation. * Updating a couple of references to GCP/Google Cloud Platform per new branding guidelines. * update typo in interface name (#1138) FireabseErrorInterface -> FirebaseErrorInterface * Improve token verification logic with Auth Emulator. (#1148) * Improve token verification logic with Auth Emulator. * Clean up comments. * Fix linting issues. * Address review comments. * Use mock for auth emulator unit test. * Implement session cookies. * Call useEmulator() only once. * Update tests. * Delete unused test helper. * Add unit tests for checking revocation. * Fix typo in test comments. * feat: Exporting all types of Messages so they can be used by consumers (#1147) * feat: Exporting all types of Messages so they can be used by consumers Fixes https://github.com/firebase/firebase-admin-node/issues/1146 * feat(exportMessageTypes): Testing TokenMessage * feat(exportMessageTypes): Added tests for all Message types * feat(exportMessageTypes): Fixed build * feat(exportMessageTypes): Better unit tests * feat(exportMessageTypes): Deleted unneeded separate TS test * feat(exportMessageTypes): Fixed build * feat(exportMessageTypes): Fixed linting * feat(auth): Implement getUserByProviderId (#769) RELEASE NOTE: Added a new getUserByProviderId() to lookup user accounts by their providers. * Allow enabling of anonymous provider via tenant configuration (#802) RELEASE NOTES: Allow enabling of anonymous provider via tenant configuration. * feat(auth): Add ability to link a federated ID with the `updateUser()` method. (#770) * (chore): Export UserProvider type and add it to toc.yaml (#1165) - Export UserProvider type - Add UserProvider to toc.yaml * [chore] Release 9.5.0 (#1167) Release 9.5.0 * chore: Updated doc generator for typedoc 0.19.0 (#1166) * Update HOME.md (#1181) Quick addition of a little bit of clarifying verbiage per an internal bug report. Thanks! * feat(rtdb): Support emulator mode for rules management operations (#1190) * feat(rtdb): Support emulator mode for rules management operations * fix: Adding namespace to emulated URL string * fix: Consolidated unit testing * fix: Removed extra whitespace * fix: Decoupled proactive token refresh from FirebaseApp (#1194) * fix: Decoupled proactive token refresh from FirebaseApp * fix: Defined constants for duration values * fix: Logging errors encountered while scheduling a refresh * fix: Renamed some variables for clarity * fix(rtdb): Fixing the RTDB token listener callback (#1203) * Add emulator-based integration tests. (#1155) * Add emulator-based integration tests. * Move emulator stuff out of package.json. * Update CONTRIBUTING.md too. * Add npx. * Skip new unsupported tests. * Inline commands in ci.yml. * Disable one flaky tests in emulator. (#1205) * [chore] Release 9.6.0 (#1209) * (chore): Add JWT Decoder and Signature Verifier (#1204) * (chore): Add JWT Decoder * Add signature verifier and key fetcher abstractions * Add unit tests for utils/jwt * chore: Add Mailgun send email action (#1210) * Add Mailgun send email github action * Add send email action in nightly workflow * chore: Fix bug in send-email action code (#1214) * chore: Fix bug in send-email action code * Add run id and trigger on repo dispatch event * Change dispatch event name in nightly workflow (#1216) - Change dispatch event name to `firebase_nightly_build` * chore: Clean up nightly workflow trigger tests (#1212) - Remove failing integration test added to trigger the send email workflow in nightly builds. * Add support for FIREBASE_STORAGE_EMULATOR_HOST env var (#1175) * Add support for FIREBASE_STORAGE_EMULATOR_HOST env var * Fixes lint error * Add test for FIREBASE_STORAGE_EMULATOR_HOST support * Lint fix * Minor fixes to storage tests * Address review comments * Address review suggestion Co-authored-by: Samuel Bushi * Revert "Disable one flaky tests in emulator. (#1205)" (#1227) This reverts commit 19660d921d20732857bf54393a09e8b5bce15d63. * fix(rtdb): Fixing a token refresh livelock in Cloud Functions (#1234) * [chore] Release 9.7.0 (#1240) * fix: adds missing EMAIL_NOT_FOUND error code (#1246) Catch `EMAIL_NOT_FOUND` and translate to `auth/email-not-found` when `/accounts:sendOobCode` is called for password reset on a user that does not exist. Fixes https://github.com/firebase/firebase-admin-node/issues/1202 * build(deps-dev): bump lodash from 4.17.19 to 4.17.21 (#1255) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Upgraded RTDB and other @firebase dependencies (#1250) * build(deps): bump y18n from 3.2.1 to 3.2.2 (#1208) Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix storage emulator env formatting (#1257) * Fix storage emulator env formatting * Repair test * Rename test * Dang tests 2 good 4 me * Fix test * Fix tests again * build(deps): bump hosted-git-info from 2.8.8 to 2.8.9 (#1260) Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9. - [Release notes](https://github.com/npm/hosted-git-info/releases) - [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md) - [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hiranya Jayathilaka * feat: Add abuse reduction support (#1264) - Add abuse reduction support APIs * Fix @types/node conflict with grpc and port type (#1258) Upgraded the @types/node dependency to v12.12.47 * [chore] Release 9.8.0 (#1266) * build(deps): bump handlebars from 4.7.6 to 4.7.7 (#1253) Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.7.6 to 4.7.7. - [Release notes](https://github.com/wycats/handlebars.js/releases) - [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) - [Commits](https://github.com/wycats/handlebars.js/compare/v4.7.6...v4.7.7) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump jose from 2.0.4 to 2.0.5 (#1265) Bumps [jose](https://github.com/panva/jose) from 2.0.4 to 2.0.5. - [Release notes](https://github.com/panva/jose/releases) - [Changelog](https://github.com/panva/jose/blob/v2.0.5/CHANGELOG.md) - [Commits](https://github.com/panva/jose/compare/v2.0.4...v2.0.5) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: Revert regression introduced in #1257 (#1277) * fix(auth): make MFA uid optional for updateUser operations (#1278) * fix(auth): make MFA uid optional for updateUser operations MFA `uid` should be optional for `updateUser` operations. When not specified, the backend will provision a `uid` for the enrolled second factor. Fixes https://github.com/firebase/firebase-admin-node/issues/1276 * chore: Enabled dependabot (#1279) * chore: Remove gulp-replace dependency (#1285) * build(deps-dev): bump gulp-header from 1.8.12 to 2.0.9 (#1283) Bumps [gulp-header](https://github.com/tracker1/gulp-header) from 1.8.12 to 2.0.9. - [Release notes](https://github.com/tracker1/gulp-header/releases) - [Changelog](https://github.com/gulp-community/gulp-header/blob/master/changelog.md) - [Commits](https://github.com/tracker1/gulp-header/compare/v1.8.12...v2.0.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump run-sequence from 1.2.2 to 2.2.1 (#1282) Bumps [run-sequence](https://github.com/OverZealous/run-sequence) from 1.2.2 to 2.2.1. - [Release notes](https://github.com/OverZealous/run-sequence/releases) - [Changelog](https://github.com/OverZealous/run-sequence/blob/master/CHANGELOG.md) - [Commits](https://github.com/OverZealous/run-sequence/compare/v1.2.2...v2.2.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump sinon from 9.0.2 to 9.2.4 (#1289) Bumps [sinon](https://github.com/sinonjs/sinon) from 9.0.2 to 9.2.4. - [Release notes](https://github.com/sinonjs/sinon/releases) - [Changelog](https://github.com/sinonjs/sinon/blob/master/CHANGELOG.md) - [Commits](https://github.com/sinonjs/sinon/compare/v9.0.2...v9.2.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump nyc from 14.1.1 to 15.1.0 (#1290) Bumps [nyc](https://github.com/istanbuljs/nyc) from 14.1.1 to 15.1.0. - [Release notes](https://github.com/istanbuljs/nyc/releases) - [Changelog](https://github.com/istanbuljs/nyc/blob/master/CHANGELOG.md) - [Commits](https://github.com/istanbuljs/nyc/compare/v14.1.1...v15.1.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump chalk from 1.1.3 to 4.1.1 (#1288) Bumps [chalk](https://github.com/chalk/chalk) from 1.1.3 to 4.1.1. - [Release notes](https://github.com/chalk/chalk/releases) - [Commits](https://github.com/chalk/chalk/compare/v1.1.3...v4.1.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @microsoft/api-extractor from 7.11.2 to 7.15.2 (#1291) Bumps [@microsoft/api-extractor](https://github.com/microsoft/rushstack) from 7.11.2 to 7.15.2. - [Release notes](https://github.com/microsoft/rushstack/releases) - [Commits](https://github.com/microsoft/rushstack/compare/@microsoft/api-extractor_v7.11.2...@microsoft/api-extractor_v7.15.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Teporarily disabling sendToDeviceGroup integration test (#1292) * feat(auth): Added code flow support for OIDC flow. (#1220) * OIDC codeflow support * improve configs to simulate the real cases * update for changes in signiture * resolve comments * improve validator logic * remove unnecessary logic * add tests and fix errors * add auth-api-request rests * Update supported Node version to 10.13.0v (#1300) * Fixed integration test failure of skipped tests (#1299) * Fix integration test failure of skipped testss * Trigger integration tests * [chore] Release 9.9.0 (#1302) * Update OIDC reference docs (#1305) * Add OAuthResponseType to ToC (#1303) * fix(auth): Better type hierarchies for Auth API (#1294) * fix(auth): Better type heirarchies for Auth API * fix: Moved factorId back to the base types * fix: Updated API report * fix: Fixed a grammar error in comment * fix: Update to comment text * Fix build issues * Add new auth type hierarchies from the main branch * Update import paths * Update package-lock * Export new auth types from the entry point Co-authored-by: egilmorez Co-authored-by: batuxd <9674241+suchcodemuchwow@users.noreply.github.com> Co-authored-by: Yuchen Shi Co-authored-by: Marc Bornträger Co-authored-by: rsgowman Co-authored-by: Hiranya Jayathilaka Co-authored-by: Abe Haskins Co-authored-by: Samuel Bushi Co-authored-by: bojeil-google Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nikhil Agarwal <54072321+nikhilag@users.noreply.github.com> Co-authored-by: Xin Li --- .github/dependabot.yml | 6 + .github/scripts/run_integration_tests.sh | 2 + .github/workflows/nightly.yml | 1 + .github/workflows/release.yml | 1 + .gitignore | 1 + CONTRIBUTING.md | 2 +- README.md | 2 +- etc/firebase-admin.api.md | 33 + etc/firebase-admin.auth.api.md | 51 +- package-lock.json | 5515 ++++++++--------- package.json | 16 +- .../app-check-api-client-internal.ts | 228 + src/app-check/app-check.ts | 86 + src/app-check/index.ts | 164 + src/app-check/token-generator.ts | 145 + src/app-check/token-verifier.ts | 165 + src/app/firebase-app.ts | 19 + src/app/firebase-namespace.ts | 15 +- src/auth/auth-api-request.ts | 22 +- src/auth/auth-config.ts | 161 +- src/auth/base-auth.ts | 6 +- src/auth/index.ts | 4 + src/auth/tenant-manager.ts | 6 +- src/auth/token-generator.ts | 237 +- src/auth/user-record.ts | 4 +- src/database/database.ts | 29 +- src/firebase-namespace-api.ts | 3 + src/storage/storage.ts | 13 +- src/utils/api-request.ts | 2 +- src/utils/crypto-signer.ts | 250 + src/utils/error.ts | 14 + src/utils/jwt.ts | 107 +- test/integration/app-check.spec.ts | 103 + test/integration/auth.spec.ts | 97 +- test/integration/messaging.spec.ts | 2 +- test/resources/mock.jwks.json | 12 + test/resources/mocks.ts | 35 + .../app-check-api-client-internal.spec.ts | 238 + test/unit/app-check/app-check.spec.ts | 195 + test/unit/app-check/token-generator.spec.ts | 261 + test/unit/app-check/token-verifier.spec.ts | 245 + test/unit/app/firebase-app.spec.ts | 29 +- test/unit/app/firebase-namespace.spec.ts | 42 +- test/unit/auth/auth-api-request.spec.ts | 85 +- test/unit/auth/auth-config.spec.ts | 129 + test/unit/auth/auth.spec.ts | 103 + test/unit/auth/token-generator.spec.ts | 245 +- test/unit/auth/token-verifier.spec.ts | 7 +- test/unit/database/database.spec.ts | 4 +- test/unit/firebase.spec.ts | 15 + test/unit/index.spec.ts | 7 + test/unit/storage/storage.spec.ts | 23 +- test/unit/utils/crypto-signer.spec.ts | 224 + test/unit/utils/jwt.spec.ts | 329 +- 54 files changed, 6346 insertions(+), 3394 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 src/app-check/app-check-api-client-internal.ts create mode 100644 src/app-check/app-check.ts create mode 100644 src/app-check/index.ts create mode 100644 src/app-check/token-generator.ts create mode 100644 src/app-check/token-verifier.ts create mode 100644 src/utils/crypto-signer.ts create mode 100644 test/integration/app-check.spec.ts create mode 100644 test/resources/mock.jwks.json create mode 100644 test/unit/app-check/app-check-api-client-internal.spec.ts create mode 100644 test/unit/app-check/app-check.spec.ts create mode 100644 test/unit/app-check/token-generator.spec.ts create mode 100644 test/unit/app-check/token-verifier.spec.ts create mode 100644 test/unit/utils/crypto-signer.spec.ts diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..aff82a102d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/scripts/run_integration_tests.sh b/.github/scripts/run_integration_tests.sh index fd479df552..37dc7d1216 100755 --- a/.github/scripts/run_integration_tests.sh +++ b/.github/scripts/run_integration_tests.sh @@ -22,4 +22,6 @@ gpg --quiet --batch --yes --decrypt --passphrase="${FIREBASE_SERVICE_ACCT_KEY}" echo "${FIREBASE_API_KEY}" > test/resources/apikey.txt +echo "${FIREBASE_APP_ID}" > test/resources/appid.txt + npm run test:integration -- --updateRules --testMultiTenancy diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a36144816b..536827b1e5 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -55,6 +55,7 @@ jobs: env: FIREBASE_SERVICE_ACCT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCT_KEY }} FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }} + FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }} - name: Package release artifacts run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7171414edc..29681b50dc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,6 +66,7 @@ jobs: env: FIREBASE_SERVICE_ACCT_KEY: ${{ secrets.FIREBASE_SERVICE_ACCT_KEY }} FIREBASE_API_KEY: ${{ secrets.FIREBASE_API_KEY }} + FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }} - name: Package release artifacts run: | diff --git a/.gitignore b/.gitignore index 8490a7d18b..9331c650de 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ node_modules/ # Real key file should not be checked in test/resources/key.json test/resources/apikey.txt +test/resources/appid.txt # Release tarballs should not be checked in firebase-admin-*.tgz diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ac6a71cb1..a3ad29f277 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,7 +87,7 @@ information on using pull requests. ### Prerequisites -1. Node.js 10.10.0 or higher. +1. Node.js 10.13.0 or higher. 2. NPM 5 or higher (NPM 6 recommended). 3. Google Cloud SDK ([`gcloud`](https://cloud.google.com/sdk/downloads) utility) diff --git a/README.md b/README.md index 4e9b7df5e0..59a22770ff 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ requests, code review feedback, and also pull requests. ## Supported Environments -We support Node.js 10.10.0 and higher. +We support Node.js 10.13.0 and higher. Please also note that the Admin SDK should only be used in server-side/back-end environments controlled by the app developer. diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 7ec4aaff22..a434fbc62a 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -17,6 +17,8 @@ export function app(name?: string): app.App; export namespace app { // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point default-namespace.d.ts export interface App extends App { + // (undocumented) + appCheck(): appCheck.AppCheck; // (undocumented) auth(): auth.Auth; // (undocumented) @@ -41,6 +43,37 @@ export namespace app { } } +// @public +export function appCheck(app?: app.App): appCheck.AppCheck; + +// @public (undocumented) +export namespace appCheck { + export interface AppCheck { + // (undocumented) + app: app.App; + createToken(appId: string): Promise; + verifyToken(appCheckToken: string): Promise; + } + export interface AppCheckToken { + token: string; + ttlMillis: number; + } + export interface DecodedAppCheckToken { + // (undocumented) + [key: string]: any; + app_id: string; + aud: string[]; + exp: number; + iat: number; + iss: string; + sub: string; + } + export interface VerifyAppCheckTokenResponse { + appId: string; + token: appCheck.DecodedAppCheckToken; + } +} + // @public export interface AppOptions { // Warning: (ae-forgotten-export) The symbol "Credential" needs to be exported by the entry point default-namespace.d.ts diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 4241e2ecf7..563e2204bb 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -32,11 +32,7 @@ export class Auth extends BaseAuth { export type AuthFactorType = 'phone'; // @public -export interface AuthProviderConfig { - displayName?: string; - enabled: boolean; - providerId: string; -} +export type AuthProviderConfig = SAMLAuthProviderConfig | OIDCAuthProviderConfig; // @public export interface AuthProviderConfigFilter { @@ -75,13 +71,31 @@ export abstract class BaseAuth { } // @public -export interface CreateMultiFactorInfoRequest { +export interface BaseAuthProviderConfig { + displayName?: string; + enabled: boolean; + providerId: string; +} + +// @public +export interface BaseCreateMultiFactorInfoRequest { displayName?: string; factorId: string; } // @public -export interface CreatePhoneMultiFactorInfoRequest extends CreateMultiFactorInfoRequest { +export interface BaseUpdateMultiFactorInfoRequest { + displayName?: string; + enrollmentTime?: string; + factorId: string; + uid?: string; +} + +// @public +export type CreateMultiFactorInfoRequest = CreatePhoneMultiFactorInfoRequest; + +// @public +export interface CreatePhoneMultiFactorInfoRequest extends BaseCreateMultiFactorInfoRequest { phoneNumber: string; } @@ -205,17 +219,27 @@ export interface MultiFactorUpdateSettings { } // @public -export interface OIDCAuthProviderConfig extends AuthProviderConfig { +export interface OAuthResponseType { + code?: boolean; + idToken?: boolean; +} + +// @public +export interface OIDCAuthProviderConfig extends BaseAuthProviderConfig { clientId: string; + clientSecret?: string; issuer: string; + responseType?: OAuthResponseType; } // @public export interface OIDCUpdateAuthProviderRequest { clientId?: string; + clientSecret?: string; displayName?: string; enabled?: boolean; issuer?: string; + responseType?: OAuthResponseType; } // @public @@ -239,7 +263,7 @@ export interface ProviderIdentifier { } // @public -export interface SAMLAuthProviderConfig extends AuthProviderConfig { +export interface SAMLAuthProviderConfig extends BaseAuthProviderConfig { callbackURL?: string; idpEntityId: string; rpEntityId: string; @@ -305,15 +329,10 @@ export interface UidIdentifier { export type UpdateAuthProviderRequest = SAMLUpdateAuthProviderRequest | OIDCUpdateAuthProviderRequest; // @public -export interface UpdateMultiFactorInfoRequest { - displayName?: string; - enrollmentTime?: string; - factorId: string; - uid?: string; -} +export type UpdateMultiFactorInfoRequest = UpdatePhoneMultiFactorInfoRequest; // @public -export interface UpdatePhoneMultiFactorInfoRequest extends UpdateMultiFactorInfoRequest { +export interface UpdatePhoneMultiFactorInfoRequest extends BaseUpdateMultiFactorInfoRequest { phoneNumber: string; } diff --git a/package-lock.json b/package-lock.json index 339b6c80e6..4fcc7c284c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,68 +5,205 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", "dev": true, "requires": { - "@babel/highlight": "^7.10.4" + "@babel/highlight": "^7.12.13" + } + }, + "@babel/compat-data": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz", + "integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ==", + "dev": true + }, + "@babel/core": { + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz", + "integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.14.3", + "@babel/helper-compilation-targets": "^7.13.16", + "@babel/helper-module-transforms": "^7.14.2", + "@babel/helpers": "^7.14.0", + "@babel/parser": "^7.14.3", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.14.2", + "@babel/types": "^7.14.2", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "@babel/generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz", - "integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==", + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", + "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", "dev": true, "requires": { - "@babel/types": "^7.10.4", + "@babel/types": "^7.14.2", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz", + "integrity": "sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.14.4", + "@babel/helper-validator-option": "^7.12.17", + "browserslist": "^4.16.6", + "semver": "^6.3.0" } }, "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", + "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.14.2" } }, "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", + "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-module-imports": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", + "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-module-transforms": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", + "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-replace-supers": "^7.13.12", + "@babel/helper-simple-access": "^7.13.12", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-validator-identifier": "^7.14.0", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.14.2", + "@babel/types": "^7.14.2" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", + "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-replace-supers": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz", + "integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.13.12", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.14.2", + "@babel/types": "^7.14.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", + "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "dev": true, + "requires": { + "@babel/types": "^7.13.12" } }, "@babel/helper-split-export-declaration": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz", - "integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.12.13" } }, "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", + "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", "dev": true }, + "@babel/helpers": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", + "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", + "dev": true, + "requires": { + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.14.0", + "@babel/types": "^7.14.0" + } + }, "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", + "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-validator-identifier": "^7.14.0", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -91,6 +228,27 @@ "supports-color": "^5.3.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -103,37 +261,36 @@ } }, "@babel/parser": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.4.tgz", - "integrity": "sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==", + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", + "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", "dev": true }, "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/traverse": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.4.tgz", - "integrity": "sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q==", + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", + "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4", + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.14.2", + "@babel/helper-function-name": "^7.14.2", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.14.2", + "@babel/types": "^7.14.2", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" + "globals": "^11.1.0" }, "dependencies": { "globals": { @@ -145,13 +302,12 @@ } }, "@babel/types": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz", - "integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==", + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", + "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.13", + "@babel/helper-validator-identifier": "^7.14.0", "to-fast-properties": "^2.0.0" } }, @@ -169,187 +325,77 @@ "js-yaml": "4.0.0", "resolve": "~1.17.0", "tslib": "^2.1.0" - }, - "dependencies": { - "@microsoft/tsdoc": { - "version": "0.12.24", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", - "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", - "dev": true - }, - "@rushstack/node-core-library": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", - "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", - "dev": true, - "requires": { - "@types/node": "10.17.13", - "colors": "~1.2.1", - "fs-extra": "~7.0.1", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.17.0", - "semver": "~7.3.0", - "timsort": "~0.3.0", - "z-schema": "~3.18.3" - } - }, - "@rushstack/ts-command-line": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.8.tgz", - "integrity": "sha512-8ghIWhkph7NnLCMDJtthpsb7TMOsVGXVDvmxjE/CeklTqjbbUFBjGXizJfpbEkRQTELuZQ2+vGn7sGwIWKN2uA==", - "dev": true, - "requires": { - "@types/argparse": "1.0.38", - "argparse": "~1.0.9", - "colors": "~1.2.1", - "string-argv": "~0.3.1" - } - }, - "@types/node": { - "version": "10.17.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", - "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", - "dev": true - }, - "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - } - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } } }, "@firebase/app": { - "version": "0.6.13", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.13.tgz", - "integrity": "sha512-xGrJETzvCb89VYbGSHFHCW7O/y067HRxT7MGehUE1xMxdPVBDNayHnxEuKwzfGvXAjVmajXBKFlKxaCWpgSjCQ==", + "version": "0.6.22", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.22.tgz", + "integrity": "sha512-9E0KP7Z+LpBOx/oQauLYvf3XleYpbfoi058wStADUtP+eOX5GIImAFNDTOO4ZNuJfLgyrHpKi7Cct6mDdxrz+g==", "dev": true, "requires": { - "@firebase/app-types": "0.6.1", - "@firebase/component": "0.1.21", + "@firebase/app-types": "0.6.2", + "@firebase/component": "0.5.0", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.4", + "@firebase/util": "1.1.0", "dom-storage": "2.1.0", - "tslib": "^1.11.1", + "tslib": "^2.1.0", "xmlhttprequest": "1.8.0" } }, "@firebase/app-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.1.tgz", - "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg==" + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.2.tgz", + "integrity": "sha512-2VXvq/K+n8XMdM4L2xy5bYp2ZXMawJXluUIDzUBvMthVR+lhxK4pfFiqr1mmDbv9ydXvEAuFsD+6DpcZuJcSSw==" }, "@firebase/auth": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.2.tgz", - "integrity": "sha512-68TlDL0yh3kF8PiCzI8m8RWd/bf/xCLUsdz1NZ2Dwea0sp6e2WAhu0sem1GfhwuEwL+Ns4jCdX7qbe/OQlkVEA==", + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.6.tgz", + "integrity": "sha512-1Lj3AY40Z2weCK6FuJqUEkeVJpRaaCo1LT6P5s3VIR99PDYLHeMm2m02rBaskE7ralJA975Vkv7sHrpykRfDrA==", "dev": true, "requires": { - "@firebase/auth-types": "0.10.1" + "@firebase/auth-types": "0.10.3" } }, "@firebase/auth-interop-types": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.5.tgz", - "integrity": "sha512-88h74TMQ6wXChPA6h9Q3E1Jg6TkTHep2+k63OWg3s0ozyGVMeY+TTOti7PFPzq5RhszQPQOoCi59es4MaRvgCw==" + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.1.6.tgz", + "integrity": "sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==" }, "@firebase/auth-types": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.1.tgz", - "integrity": "sha512-/+gBHb1O9x/YlG7inXfxff/6X3BPZt4zgBv4kql6HEmdzNQCodIRlEYnI+/da+lN+dha7PjaFH7C7ewMmfV7rw==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.10.3.tgz", + "integrity": "sha512-zExrThRqyqGUbXOFrH/sowuh2rRtfKHp9SBVY2vOqKWdCX1Ztn682n9WLtlUDsiYVIbBcwautYWk2HyCGFv0OA==", "dev": true }, "@firebase/component": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.21.tgz", - "integrity": "sha512-kd5sVmCLB95EK81Pj+yDTea8pzN2qo/1yr0ua9yVi6UgMzm6zAeih73iVUkaat96MAHy26yosMufkvd3zC4IKg==", - "dev": true, + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.0.tgz", + "integrity": "sha512-v18csWtXb0ri+3m7wuGLY/UDgcb89vuMlZGQ//+7jEPLIQeLbylvZhol1uzW9WzoOpxMxOS2W5qyVGX36wZvEA==", "requires": { - "@firebase/util": "0.3.4", - "tslib": "^1.11.1" + "@firebase/util": "1.1.0", + "tslib": "^2.1.0" } }, "@firebase/database": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.1.tgz", - "integrity": "sha512-/1HhR4ejpqUaM9Cn3KSeNdQvdlehWIhdfTVWFxS73ZlLYf7ayk9jITwH10H3ZOIm5yNzxF67p/U7Z/0IPhgWaQ==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.10.2.tgz", + "integrity": "sha512-jMGtl5eBES9k0rOIZd6/EAuVB6m3LzRei1lvEiqWWBje2Xoz//7sjZcxOYtAKCCLldEI1EUrzW8Tv5yEAoPPpg==", "requires": { - "@firebase/auth-interop-types": "0.1.5", - "@firebase/component": "0.1.21", - "@firebase/database-types": "0.6.1", + "@firebase/auth-interop-types": "0.1.6", + "@firebase/component": "0.5.0", + "@firebase/database-types": "0.7.2", "@firebase/logger": "0.2.6", - "@firebase/util": "0.3.4", + "@firebase/util": "1.1.0", "faye-websocket": "0.11.3", - "tslib": "^1.11.1" - }, - "dependencies": { - "@firebase/component": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.1.21.tgz", - "integrity": "sha512-kd5sVmCLB95EK81Pj+yDTea8pzN2qo/1yr0ua9yVi6UgMzm6zAeih73iVUkaat96MAHy26yosMufkvd3zC4IKg==", - "requires": { - "@firebase/util": "0.3.4", - "tslib": "^1.11.1" - } - }, - "@firebase/util": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", - "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", - "requires": { - "tslib": "^1.11.1" - } - } + "tslib": "^2.1.0" } }, "@firebase/database-types": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.6.1.tgz", - "integrity": "sha512-JtL3FUbWG+bM59iYuphfx9WOu2Mzf0OZNaqWiQ7lJR8wBe7bS9rIm9jlBFtksB7xcya1lZSQPA/GAy2jIlMIkA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.2.tgz", + "integrity": "sha512-cdAd/dgwvC0r3oLEDUR+ULs1vBsEvy0b27nlzKhU6LQgm9fCDzgaH9nFGv8x+S9dly4B0egAXkONkVoWcOAisg==", "requires": { - "@firebase/app-types": "0.6.1" + "@firebase/app-types": "0.6.2" } }, "@firebase/logger": { @@ -358,18 +404,17 @@ "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==" }, "@firebase/util": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-0.3.4.tgz", - "integrity": "sha512-VwjJUE2Vgr2UMfH63ZtIX9Hd7x+6gayi6RUXaTqEYxSbf/JmehLmAEYSuxS/NckfzAXWeGnKclvnXVibDgpjQQ==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.1.0.tgz", + "integrity": "sha512-lfuSASuPKNdfebuFR8rjFamMQUPH9iiZHcKS755Rkm/5gRT0qC7BMhCh3ZkHf7NVbplzIc/GhmX2jM+igDRCag==", "requires": { - "tslib": "^1.11.1" + "tslib": "^2.1.0" } }, "@google-cloud/common": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.3.3.tgz", - "integrity": "sha512-2PwPDE47N4WiWQK/F35vE5aWVoCjKQ2NW8r8OFAg6QslkLMjX6WNcmUO8suYlSkavc58qOvzA4jG6eVkC90i8Q==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.6.0.tgz", + "integrity": "sha512-aHIFTqJZmeTNO9md8XxV+ywuvXF3xBm5WNmgWeeCK+XN5X+kGW0WEX94wGwj+/MdOnrVf4dL2RvSIt9J5yJG6Q==", "optional": true, "requires": { "@google-cloud/projectify": "^2.0.0", @@ -378,45 +423,21 @@ "duplexify": "^4.1.1", "ent": "^2.2.0", "extend": "^3.0.2", - "google-auth-library": "^6.0.0", + "google-auth-library": "^7.0.2", "retry-request": "^4.1.1", "teeny-request": "^7.0.0" - }, - "dependencies": { - "duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", - "optional": true, - "requires": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "optional": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "@google-cloud/firestore": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.5.0.tgz", - "integrity": "sha512-sExt4E+TlBqyv4l/av6kBZ4YGS99Cc3P5UvLRNj9z41mT9ekPGhIzptbj4K6O7h0KCyDIDOiJdi4gUPH9lip4g==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.12.2.tgz", + "integrity": "sha512-5rurTAJXQ0SANEf8K9eA2JAB5zAh+pu4tGRnkZx5gBWQLZXdBFdtepS+irvKuSXw1KbeAQOuRANSc/nguys6SQ==", "optional": true, "requires": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.2.0" + "google-gax": "^2.12.0", + "protobufjs": "^6.8.6" } }, "@google-cloud/paginator": { @@ -442,22 +463,23 @@ "optional": true }, "@google-cloud/storage": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.3.0.tgz", - "integrity": "sha512-3t5UF3SZ14Bw2kcBHubCai6EIugU2GnQOstYWVSFuoO8IJ94RAaIOPq/dtexvQbUTpBTAGpd5smVR9WPL1mJVw==", + "version": "5.8.5", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.8.5.tgz", + "integrity": "sha512-i0gB9CRwQeOBYP7xuvn14M40LhHCwMjceBjxE4CTvsqL519sVY5yVKxLiAedHWGwUZHJNRa7Q2CmNfkdRwVNPg==", "optional": true, "requires": { - "@google-cloud/common": "^3.3.0", + "@google-cloud/common": "^3.6.0", "@google-cloud/paginator": "^3.0.0", "@google-cloud/promisify": "^2.0.0", "arrify": "^2.0.0", + "async-retry": "^1.3.1", "compressible": "^2.0.12", - "concat-stream": "^2.0.0", - "date-and-time": "^0.14.0", - "duplexify": "^3.5.0", + "date-and-time": "^1.0.0", + "duplexify": "^4.0.0", "extend": "^3.0.2", - "gaxios": "^3.0.0", - "gcs-resumable-upload": "^3.1.0", + "gaxios": "^4.0.0", + "gcs-resumable-upload": "^3.1.4", + "get-stream": "^6.0.0", "hash-stream-validation": "^0.2.2", "mime": "^2.2.0", "mime-types": "^2.0.8", @@ -470,110 +492,88 @@ } }, "@grpc/grpc-js": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.1.8.tgz", - "integrity": "sha512-64hg5rmEm6F/NvlWERhHmmgxbWU8nD2TMWE+9TvG7/WcOrFT3fzg/Uu631pXRFwmJ4aWO/kp9vVSlr8FUjBDLA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.2.tgz", + "integrity": "sha512-UXepkOKCATJrhHGsxt+CGfpZy9zUn1q9mop5kfcXq1fBkTePxVNPOdnISlCbJFlCtld+pSLGyZCzr9/zVprFKA==", "optional": true, "requires": { - "@grpc/proto-loader": "^0.6.0-pre14", - "@types/node": "^12.12.47", - "google-auth-library": "^6.0.0", - "semver": "^6.2.0" + "@types/node": ">=12.12.47" + } + }, + "@grpc/proto-loader": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.2.tgz", + "integrity": "sha512-q2Qle60Ht2OQBCp9S5hv1JbI4uBBq6/mqSevFNK3ZEgRDBCAkWqZPUhD/K9gXOHrHKluliHiVq2L9sw1mVyAIg==", + "optional": true, + "requires": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^6.10.0", + "yargs": "^16.1.1" }, "dependencies": { - "@grpc/proto-loader": { - "version": "0.6.0-pre9", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.0-pre9.tgz", - "integrity": "sha512-oM+LjpEjNzW5pNJjt4/hq1HYayNeQT+eGrOPABJnYHv7TyNPDNzkQ76rDYZF86X5swJOa4EujEMzQ9iiTdPgww==", - "optional": true, - "requires": { - "@types/long": "^4.0.1", - "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^6.9.0", - "yargs": "^15.3.1" - } - }, - "@types/node": { - "version": "12.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.3.tgz", - "integrity": "sha512-8Jduo8wvvwDzEVJCOvS/G6sgilOLvvhn1eMmK3TW8/T217O7u1jdrK6ImKLv80tVryaPSVeKu6sjDEiFjd4/eg==", - "optional": true - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "optional": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "optional": true, "requires": { - "color-convert": "^2.0.1" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } - }, + } + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "optional": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "optional": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "optional": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "optional": true + "dev": true }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "optional": true, + "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "optional": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "optional": true + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "optional": true, + "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -582,7 +582,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "optional": true, + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -591,7 +591,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "optional": true, + "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -600,153 +600,220 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "optional": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "optional": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "optional": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "optional": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "optional": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "optional": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "optional": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "optional": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } + "dev": true }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "optional": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true } } }, - "@grpc/proto-loader": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.5.tgz", - "integrity": "sha512-WwN9jVNdHRQoOBo9FDH7qU+mgfjPc8GygPYms3M+y3fbQLfnCe/Kv/E01t7JRgnrsOHH8euvSbed3mIalXhwqQ==", - "optional": true, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@mapbox/node-pre-gyp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz", + "integrity": "sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==", + "dev": true, "requires": { - "lodash.camelcase": "^4.3.0", - "protobufjs": "^6.8.6" + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.1", + "nopt": "^5.0.0", + "npmlog": "^4.1.2", + "rimraf": "^3.0.2", + "semver": "^7.3.4", + "tar": "^6.1.0" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } } }, "@microsoft/api-extractor": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.11.2.tgz", - "integrity": "sha512-iZPv22j9K02cbwIDblOgF1MxZG+KWovp3CQpWCD6UC/+YYO4DfLxX5uZYVNzfgT4vU8fN0rugJmGm85rHX6Ouw==", + "version": "7.15.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.15.2.tgz", + "integrity": "sha512-/Y/n+QOc1vM6Vg3OAUByT/wXdZciE7jV3ay33+vxl3aKva5cNsuOauL14T7XQWUiLko3ilPwrcnFcEjzXpLsuA==", "dev": true, "requires": { - "@microsoft/api-extractor-model": "7.10.8", - "@microsoft/tsdoc": "0.12.19", - "@rushstack/node-core-library": "3.34.7", - "@rushstack/rig-package": "0.2.7", - "@rushstack/ts-command-line": "4.7.6", + "@microsoft/api-extractor-model": "7.13.2", + "@microsoft/tsdoc": "0.13.2", + "@microsoft/tsdoc-config": "~0.15.2", + "@rushstack/node-core-library": "3.38.0", + "@rushstack/rig-package": "0.2.12", + "@rushstack/ts-command-line": "4.7.10", "colors": "~1.2.1", "lodash": "~4.17.15", "resolve": "~1.17.0", "semver": "~7.3.0", "source-map": "~0.6.1", - "typescript": "~4.0.5" + "typescript": "~4.2.4" }, "dependencies": { - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "@microsoft/tsdoc": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", + "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", "dev": true }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "@rushstack/node-core-library": { + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.38.0.tgz", + "integrity": "sha512-cmvl0yQx8sSmbuXwiRYJi8TO+jpTtrLJQ8UmFHhKvgPVJAW8cV8dnpD1Xx/BvTGrJZ2XtRAIkAhBS9okBnap4w==", + "dev": true, + "requires": { + "@types/node": "10.17.13", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~3.18.3" + } + }, + "@rushstack/ts-command-line": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.10.tgz", + "integrity": "sha512-8t042g8eerypNOEcdpxwRA3uCmz0duMo21rG4Z2mdz7JxJeylDmzjlU3wDdef2t3P1Z61JCdZB6fbm1Mh0zi7w==", + "dev": true, + "requires": { + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "colors": "~1.2.1", + "string-argv": "~0.3.1" + } + }, + "@types/node": { + "version": "10.17.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", + "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", "dev": true }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, "typescript": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz", - "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", + "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", "dev": true } } }, "@microsoft/api-extractor-model": { - "version": "7.10.8", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.10.8.tgz", - "integrity": "sha512-9TfiCTPnkUeLaYywZeg9rYbVPX9Tj6AAkO6ThnjSE0tTPLjMcL3RiHkqn0BJ4+aGcl56APwo32zj5+kG+NqxYA==", + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.13.2.tgz", + "integrity": "sha512-gA9Q8q5TPM2YYk7rLinAv9KqcodrmRC13BVmNzLswjtFxpz13lRh0BmrqD01/sddGpGMIuWFYlfUM4VSWxnggA==", "dev": true, "requires": { - "@microsoft/tsdoc": "0.12.19", - "@rushstack/node-core-library": "3.34.7" + "@microsoft/tsdoc": "0.13.2", + "@microsoft/tsdoc-config": "~0.15.2", + "@rushstack/node-core-library": "3.38.0" + }, + "dependencies": { + "@microsoft/tsdoc": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", + "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", + "dev": true + }, + "@rushstack/node-core-library": { + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.38.0.tgz", + "integrity": "sha512-cmvl0yQx8sSmbuXwiRYJi8TO+jpTtrLJQ8UmFHhKvgPVJAW8cV8dnpD1Xx/BvTGrJZ2XtRAIkAhBS9okBnap4w==", + "dev": true, + "requires": { + "@types/node": "10.17.13", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~3.18.3" + } + }, + "@types/node": { + "version": "10.17.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", + "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } } }, "@microsoft/tsdoc": { - "version": "0.12.19", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.19.tgz", - "integrity": "sha512-IpgPxHrNxZiMNUSXqR1l/gePKPkfAmIKoDRP9hp7OwjU29ZR8WCJsOJ8iBKgw0Qk+pFwR+8Y1cy8ImLY6e9m4A==", + "version": "0.12.24", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", + "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", "dev": true }, + "@microsoft/tsdoc-config": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.15.2.tgz", + "integrity": "sha512-mK19b2wJHSdNf8znXSMYVShAHktVr/ib0Ck2FA3lsVBSEhSI/TfXT7DJQkAYgcztTuwazGcg58ZjYdk0hTCVrA==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "0.13.2", + "ajv": "~6.12.6", + "jju": "~1.4.0", + "resolve": "~1.19.0" + }, + "dependencies": { + "@microsoft/tsdoc": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", + "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", + "dev": true + }, + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + } + } + }, + "@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==" + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -812,9 +879,9 @@ "optional": true }, "@rushstack/node-core-library": { - "version": "3.34.7", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.34.7.tgz", - "integrity": "sha512-7FwJ0jmZsh7bDIZ1IqDNphY9Kc6aAi1D2K8jiq+da4flMyL84HNeq2KxvwFLzjLwu3eMr88X+oBpgxCTD5Y57Q==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", + "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", "dev": true, "requires": { "@types/node": "10.17.13", @@ -834,54 +901,31 @@ "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", "dev": true }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "lru-cache": "^6.0.0" } - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true } } }, "@rushstack/rig-package": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.2.7.tgz", - "integrity": "sha512-hI1L0IIzCHqH/uW64mKqEQ0/MANA/IklVId3jGpj1kt9RJcBdeNUIlzDtHl437LZRAuEA8CyotRHzG6YDgWlTw==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.2.12.tgz", + "integrity": "sha512-nbePcvF8hQwv0ql9aeQxcaMPK/h1OLAC00W7fWCRWIvD2MchZOE8jumIIr66HGrfG2X1sw++m/ZYI4D+BM5ovQ==", "dev": true, "requires": { - "@types/node": "10.17.13", "resolve": "~1.17.0", "strip-json-comments": "~3.1.1" - }, - "dependencies": { - "@types/node": { - "version": "10.17.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", - "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - } } }, "@rushstack/ts-command-line": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.6.tgz", - "integrity": "sha512-falJVNfpJtsL3gJaY77JXXycfzhzB9VkKhqEfjRWD69/f6ezMUorPR6Nc90MnIaWgePTcdTJPZibxOQrNpu1Uw==", + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.8.tgz", + "integrity": "sha512-8ghIWhkph7NnLCMDJtthpsb7TMOsVGXVDvmxjE/CeklTqjbbUFBjGXizJfpbEkRQTELuZQ2+vGn7sGwIWKN2uA==", "dev": true, "requires": { "@types/argparse": "1.0.38", @@ -891,9 +935,9 @@ } }, "@sinonjs/commons": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", - "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -908,20 +952,10 @@ "@sinonjs/commons": "^1.7.0" } }, - "@sinonjs/formatio": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", - "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^5.0.2" - } - }, "@sinonjs/samsam": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.0.3.tgz", - "integrity": "sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", + "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", "dev": true, "requires": { "@sinonjs/commons": "^1.6.0", @@ -954,11 +988,20 @@ "dev": true }, "@types/bluebird": { - "version": "3.5.32", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.32.tgz", - "integrity": "sha512-dIOxFfI0C+jz89g6lQ+TqhGgPQ0MxSnh/E4xuC0blhFtyW269+mPG5QeLgbdwst/LvdP8o1y0o/Gz5EHXLec/g==", + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.35.tgz", + "integrity": "sha512-2WeeXK7BuQo7yPI4WGOBum90SzF/f8rqlvpaXx4rjeTmNssGRDHWf7fgDUH90xMB3sUOu716fUK5d+OVx0+ncQ==", "dev": true }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", @@ -966,25 +1009,27 @@ "dev": true }, "@types/chai": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.11.tgz", - "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==", + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.18.tgz", + "integrity": "sha512-rS27+EkB/RE1Iz3u0XtVL5q36MGDWbgYe7zWiodyKNUnthxY0rukK5V36eiUCtCisB7NN8zKYH6DO2M37qxFEQ==", "dev": true }, "@types/chai-as-promised": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.3.tgz", - "integrity": "sha512-FQnh1ohPXJELpKhzjuDkPLR2BZCAqed+a6xV4MI/T3XzHfd2FlarfUGUdZYgqYe8oxkYn0fchHEeHfHqdZ96sg==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.4.tgz", + "integrity": "sha512-1y3L1cHePcIm5vXkh1DSGf/zQq5n5xDKG1fpCvf18+uOkpce0Z1ozNFPkyWsVswK7ntN1sZBw3oU6gmN+pDUcA==", "dev": true, "requires": { "@types/chai": "*" } }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true + "@types/connect": { + "version": "3.4.34", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", + "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "requires": { + "@types/node": "*" + } }, "@types/eslint-visitor-keys": { "version": "1.0.0", @@ -992,31 +1037,69 @@ "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", "dev": true }, + "@types/express": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz", + "integrity": "sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "requires": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.20.tgz", + "integrity": "sha512-8qqFN4W53IEWa9bdmuVrUcVkFemQWnt5DKPQ/oa8xKDYgtjCr2OO6NX5TIK49NLFr3mPYU2cLh92DQquC3oWWQ==", + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/express-unless": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz", + "integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==", + "requires": { + "@types/express": "*" + } + }, "@types/firebase-token-generator": { "version": "2.0.28", - "resolved": "http://registry.npmjs.org/@types/firebase-token-generator/-/firebase-token-generator-2.0.28.tgz", + "resolved": "https://registry.npmjs.org/@types/firebase-token-generator/-/firebase-token-generator-2.0.28.tgz", "integrity": "sha1-Z1VIHZMk4mt6XItFXWgUg3aCw5Y=", "dev": true }, "@types/json-schema": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", - "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, "@types/jsonwebtoken": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz", - "integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-rNAPdomlIUX0i0cg2+I+Q1wOUr531zHBQ+cV/28PJ39bSPKjahatZZ2LMuhiguETkCgLVzfruw/ZvNMNkKoSzw==", "dev": true, "requires": { "@types/node": "*" } }, "@types/lodash": { - "version": "4.14.157", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.157.tgz", - "integrity": "sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ==", + "version": "4.14.170", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz", + "integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==", "dev": true }, "@types/long": { @@ -1025,16 +1108,21 @@ "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==", "optional": true }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", "dev": true }, "@types/minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", + "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", "dev": true }, "@types/mocha": { @@ -1053,9 +1141,19 @@ } }, "@types/node": { - "version": "10.17.26", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.26.tgz", - "integrity": "sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw==" + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz", + "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA==" + }, + "@types/qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" }, "@types/request": { "version": "2.48.5", @@ -1070,28 +1168,37 @@ } }, "@types/request-promise": { - "version": "4.1.46", - "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.46.tgz", - "integrity": "sha512-3Thpj2Va5m0ji3spaCk8YKrjkZyZc6RqUVOphA0n/Xet66AW/AiOAs5vfXhQIL5NmkaO7Jnun7Nl9NEjJ2zBaw==", + "version": "4.1.47", + "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.47.tgz", + "integrity": "sha512-eRSZhAS8SMsrWOM8vbhxFGVZhTbWSJvaRKyufJTdIf4gscUouQvOBlfotPSPHbMR3S7kfkyKbhb1SWPmQdy3KQ==", "dev": true, "requires": { "@types/bluebird": "*", "@types/request": "*" } }, + "@types/serve-static": { + "version": "1.13.9", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", + "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "@types/sinon": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.4.tgz", - "integrity": "sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw==", + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.11.tgz", + "integrity": "sha512-PwP4UY33SeeVKodNE37ZlOsR9cReypbMJOhZ7BVE0lB+Hix3efCOxiJWiE5Ia+yL9Cn2Ch72EjFTRze8RZsNtg==", "dev": true, "requires": { "@types/sinonjs__fake-timers": "*" } }, "@types/sinon-chai": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.4.tgz", - "integrity": "sha512-xq5KOWNg70PRC7dnR2VOxgYQ6paumW+4pTZP+6uTSdhpYsAUEeeT5bw6rRHHQrZ4KyR+M5ojOR+lje6TGSpUxA==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.5.tgz", + "integrity": "sha512-bKQqIpew7mmIGNRlxW6Zli/QVyc3zikpGzCa797B/tRnD9OtHvZ/ts8sYXV+Ilj9u3QRaUEM8xrjgd1gwm1BpQ==", "dev": true, "requires": { "@types/chai": "*", @@ -1099,9 +1206,9 @@ } }, "@types/sinonjs__fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz", - "integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", + "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", "dev": true }, "@types/tough-cookie": { @@ -1162,10 +1269,13 @@ }, "dependencies": { "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -1191,30 +1301,39 @@ } }, "acorn": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz", - "integrity": "sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-jsx": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", - "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, "agent-base": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz", - "integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==", - "optional": true, + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "requires": { "debug": "4" } }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -1232,19 +1351,28 @@ "ansi-wrap": "^0.1.0" } }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "requires": { - "type-fest": "^0.11.0" + "type-fest": "^0.21.3" }, "dependencies": { "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true } } @@ -1258,17 +1386,27 @@ "ansi-wrap": "0.1.0" } }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } }, "ansi-wrap": { "version": "0.1.0", @@ -1290,6 +1428,17 @@ "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "api-extractor-model-me": { @@ -1300,61 +1449,6 @@ "requires": { "@microsoft/tsdoc": "0.12.24", "@rushstack/node-core-library": "3.36.0" - }, - "dependencies": { - "@microsoft/tsdoc": { - "version": "0.12.24", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", - "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", - "dev": true - }, - "@rushstack/node-core-library": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", - "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", - "dev": true, - "requires": { - "@types/node": "10.17.13", - "colors": "~1.2.1", - "fs-extra": "~7.0.1", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.17.0", - "semver": "~7.3.0", - "timsort": "~0.3.0", - "z-schema": "~3.18.3" - } - }, - "@types/node": { - "version": "10.17.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", - "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } } }, "append-buffer": { @@ -1367,12 +1461,12 @@ } }, "append-transform": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "requires": { - "default-require-extensions": "^2.0.0" + "default-require-extensions": "^3.0.0" } }, "aproba": { @@ -1395,6 +1489,38 @@ "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "arg": { @@ -1449,9 +1575,9 @@ "dev": true }, "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", "dev": true }, "array-each": { @@ -1597,6 +1723,15 @@ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, + "async-retry": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz", + "integrity": "sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==", + "optional": true, + "requires": { + "retry": "0.12.0" + } + }, "async-settle": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", @@ -1625,9 +1760,9 @@ "dev": true }, "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, "bach": { @@ -1648,9 +1783,9 @@ } }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, "base": { @@ -1709,18 +1844,18 @@ } }, "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bcrypt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.0.tgz", - "integrity": "sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", + "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==", "dev": true, "requires": { - "node-addon-api": "^3.0.0", - "node-pre-gyp": "0.15.0" + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^3.1.0" } }, "bcrypt-pbkdf": { @@ -1732,16 +1867,10 @@ "tweetnacl": "^0.14.3" } }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, "bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.1.tgz", + "integrity": "sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==", "optional": true }, "binary-extensions": { @@ -1750,12 +1879,6 @@ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, - "binaryextensions": { - "version": "1.0.1", - "resolved": "http://registry.npmjs.org/binaryextensions/-/binaryextensions-1.0.1.tgz", - "integrity": "sha1-HmN0iLNbWL2l9HdL+WpSEqjJB1U=", - "dev": true - }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -1817,6 +1940,19 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + } + }, "buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -1842,7 +1978,8 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, "cache-base": { "version": "1.0.1", @@ -1862,50 +1999,25 @@ } }, "caching-transform": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", - "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, "requires": { - "hasha": "^3.0.0", - "make-dir": "^2.0.0", - "package-hash": "^3.0.0", - "write-file-atomic": "^2.4.2" - }, - "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - } + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" } }, "callsites": { @@ -1920,6 +2032,12 @@ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true }, + "caniuse-lite": { + "version": "1.0.30001232", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001232.tgz", + "integrity": "sha512-e4Gyp7P8vqC2qV2iHA+cJNf/yqUKOShXQOJHQt81OHxlIZl/j/j3soEA0adAQi8CPUQgvOdDENyQ5kd6a6mNSg==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -1927,16 +2045,16 @@ "dev": true }, "chai": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", - "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", "deep-eql": "^3.0.1", "get-func-name": "^2.0.0", - "pathval": "^1.1.0", + "pathval": "^1.1.1", "type-detect": "^4.0.5" } }, @@ -1950,16 +2068,13 @@ } }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "chardet": { @@ -2025,19 +2140,13 @@ } } } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true } } }, "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true }, "class-utils": { @@ -2063,6 +2172,12 @@ } } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -2079,14 +2194,13 @@ "dev": true }, "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, "clone": { @@ -2116,6 +2230,38 @@ "inherits": "^2.0.1", "process-nextick-args": "^2.0.0", "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "code-point-at": { @@ -2146,19 +2292,17 @@ } }, "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "requires": { - "color-name": "1.1.3" + "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "color-support": { "version": "1.1.3", @@ -2166,6 +2310,12 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, "colors": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz", @@ -2182,9 +2332,9 @@ } }, "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "optional": true }, @@ -2216,26 +2366,45 @@ "dev": true }, "concat-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", - "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", - "optional": true, + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", - "readable-stream": "^3.0.2", + "readable-stream": "^2.2.2", "typedarray": "^0.0.6" }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "optional": true, + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" } } } @@ -2247,14 +2416,6 @@ "dev": true, "requires": { "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "configstore": { @@ -2301,57 +2462,35 @@ "dev": true }, "copy-props": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", - "dev": true, - "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cp-file": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", - "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "make-dir": "^2.0.0", - "nested-error-stacks": "^2.0.0", - "pify": "^4.0.1", - "safe-buffer": "^5.0.1" - }, - "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", + "dev": true, + "requires": { + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true } } }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", @@ -2406,29 +2545,24 @@ } }, "date-and-time": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.14.2.tgz", - "integrity": "sha512-EFTCh9zRSEpGPmJaexg7HTuzZHh6cnJj1ui7IGCFNXzd2QdpsNh05Db5TF3xzJm30YN+A8/6xHSuRcQqoc3kFA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-1.0.1.tgz", + "integrity": "sha512-7u+uNfnjWkX+YFQfivvW24TjaJG6ahvTrfw1auq7KlC7osuGcZBIWGBvB9UcENjH6JnLVhMqlRripk1dSHjAUA==", "optional": true }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decode-uri-component": { "version": "0.2.0", @@ -2445,12 +2579,6 @@ "type-detect": "^4.0.0" } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -2475,18 +2603,18 @@ } }, "default-require-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", "dev": true, "requires": { - "strip-bom": "^3.0.0" + "strip-bom": "^4.0.0" }, "dependencies": { "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true } } @@ -2560,6 +2688,17 @@ "pify": "^2.0.0", "pinkie-promise": "^2.0.0", "rimraf": "^2.2.8" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "delayed-stream": { @@ -2595,9 +2734,9 @@ } }, "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, "doctrine": { @@ -2624,49 +2763,15 @@ "is-obj": "^2.0.0" } }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "optional": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", "stream-shift": "^1.0.0" } }, @@ -2698,6 +2803,12 @@ "safe-buffer": "^5.0.1" } }, + "electron-to-chromium": { + "version": "1.3.743", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.743.tgz", + "integrity": "sha512-K2wXfo9iZQzNJNx67+Pld0DRF+9bYinj62gXCdgPhcu1vidwVuLPHQPPFnCdO55njWigXXpfBiT90jGUPbw8Zg==", + "dev": true + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2727,22 +2838,27 @@ } }, "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", "dev": true, "requires": { + "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.10.3", "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" } }, "es-to-primitive": { @@ -2809,8 +2925,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-string-regexp": { "version": "1.0.5", @@ -2889,6 +3004,21 @@ "supports-color": "^5.3.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -2919,6 +3049,31 @@ "eslint-visitor-keys": "^1.1.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "regexpp": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", @@ -2934,12 +3089,6 @@ "ansi-regex": "^4.1.0" } }, - "strip-json-comments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz", - "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==", - "dev": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2952,12 +3101,12 @@ } }, "eslint-scope": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", - "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, @@ -2994,29 +3143,37 @@ "dev": true }, "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" }, "dependencies": { "estraverse": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", - "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "dev": true } } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { @@ -3106,9 +3263,9 @@ }, "dependencies": { "type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", - "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz", + "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==", "dev": true } } @@ -3313,38 +3470,14 @@ } }, "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" } }, "find-up": { @@ -3384,7 +3517,7 @@ }, "firebase-token-generator": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/firebase-token-generator/-/firebase-token-generator-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/firebase-token-generator/-/firebase-token-generator-2.0.0.tgz", "integrity": "sha1-l2fXWewTq9yZuhFf1eqZ2Lk9EgY=", "dev": true }, @@ -3436,6 +3569,38 @@ "requires": { "inherits": "^2.0.3", "readable-stream": "^2.3.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "for-in": { @@ -3454,13 +3619,56 @@ } }, "foreground-child": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", - "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "requires": { - "cross-spawn": "^4", - "signal-exit": "^3.0.0" + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "forever-agent": { @@ -3489,6 +3697,12 @@ "map-cache": "^0.2.2" } }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, "fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -3501,12 +3715,12 @@ } }, "fs-minipass": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "requires": { - "minipass": "^2.6.0" + "minipass": "^3.0.0" } }, "fs-mkdirp-stream": { @@ -3517,18 +3731,6 @@ "requires": { "graceful-fs": "^4.1.11", "through2": "^2.0.3" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } } }, "fs.realpath": { @@ -3573,12 +3775,49 @@ "string-width": "^1.0.1", "strip-ansi": "^3.0.1", "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } } }, "gaxios": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.3.tgz", - "integrity": "sha512-PkzQludeIFhd535/yucALT/Wxyj/y2zLyrMwPcJmnLHDugmV49NvAi/vb+VUq/eWztATZCNcb8ue+ywPG+oLuw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.0.tgz", + "integrity": "sha512-pHplNbslpwCLMyII/lHPWFQbJWOX0B3R1hwBEOvzYi1GmdKZruuEHK4N9V6f7tf1EaPYyF80mui1+344p6SmLg==", "optional": true, "requires": { "abort-controller": "^3.0.0", @@ -3589,48 +3828,76 @@ } }, "gcp-metadata": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.0.tgz", - "integrity": "sha512-r57SV28+olVsflPlKyVig3Muo/VDlcsObMtvDGOEtEJXj+DDE8bEl0coIkXh//hbkSDTvo+f5lbihZOndYXQQQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", + "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", "optional": true, "requires": { - "gaxios": "^3.0.0", - "json-bigint": "^0.3.0" + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" } }, "gcs-resumable-upload": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.1.tgz", - "integrity": "sha512-RS1osvAicj9+MjCc6jAcVL1Pt3tg7NK2C2gXM5nqD1Gs0klF2kj5nnAFSBy97JrtslMIQzpb7iSuxaG8rFWd2A==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.4.tgz", + "integrity": "sha512-5dyDfHrrVcIskiw/cPssVD4HRiwoHjhk1Nd6h5W3pQ/qffDvhfy4oNCr1f3ZXFPwTnxkCbibsB+73oOM+NvmJQ==", "optional": true, "requires": { "abort-controller": "^3.0.0", "configstore": "^5.0.0", "extend": "^3.0.2", - "gaxios": "^3.0.0", - "google-auth-library": "^6.0.0", + "gaxios": "^4.0.0", + "google-auth-library": "^7.0.0", "pumpify": "^2.0.0", "stream-events": "^1.0.4" } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, "get-prop": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/get-prop/-/get-prop-0.0.10.tgz", "integrity": "sha1-p2tANdFw3XKKZkVpVbyBkjZzCNI=", "dev": true }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "optional": true + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -3647,9 +3914,9 @@ } }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3661,9 +3928,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -3687,6 +3954,18 @@ "unique-stream": "^2.0.2" }, "dependencies": { + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -3726,13 +4005,43 @@ "inherits": "^2.0.3", "pump": "^2.0.0" } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, "glob-watcher": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", - "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -3740,6 +4049,7 @@ "chokidar": "^2.0.0", "is-negated-glob": "^1.0.0", "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", "object.defaults": "^1.1.0" } }, @@ -3778,7 +4088,7 @@ }, "globby": { "version": "5.0.0", - "resolved": "http://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { @@ -3808,86 +4118,55 @@ } }, "google-auth-library": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.3.tgz", - "integrity": "sha512-2Np6ojPmaJGXHSMsBhtTQEKfSMdLc8hefoihv7N2cwEr8E5bq39fhoat6TcXHwa0XoGO5Guh9sp3nxHFPmibMw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.1.0.tgz", + "integrity": "sha512-X+gbkGjnLN3HUZP2W3KBREuA603BXd80ITvL0PeS0QpyDNYz/u0pIZ7aRuGnrSuUc0grk/qxEgtVTFt1ogbP+A==", "optional": true, "requires": { "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "fast-text-encoding": "^1.0.0", - "gaxios": "^3.0.0", - "gcp-metadata": "^4.1.0", - "gtoken": "^5.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", "jws": "^4.0.0", - "lru-cache": "^5.0.0" + "lru-cache": "^6.0.0" } }, "google-gax": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.9.1.tgz", - "integrity": "sha512-KQ7HiMTB/PAzKv3OU00x6tC1H7MHvSxQfon5BSyW5o+lkMgRA8xoqvlxZCBC1dlW1azOPGF8vScy8QgFmhaQ9Q==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.14.1.tgz", + "integrity": "sha512-I5RDEN7MEptrCxeHX3ht7nKFGfyjgYX4hQKI9eVMBohMzVbFSwWUndo0CcKXu8es7NhB4gt2XYLm1AHkXhtHpA==", "optional": true, "requires": { - "@grpc/grpc-js": "~1.1.1", - "@grpc/proto-loader": "^0.5.1", + "@grpc/grpc-js": "~1.3.0", + "@grpc/proto-loader": "^0.6.1", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", - "google-auth-library": "^6.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^7.0.2", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", - "protobufjs": "^6.9.0", + "object-hash": "^2.1.1", + "protobufjs": "^6.10.2", "retry-request": "^4.0.0" - }, - "dependencies": { - "duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", - "optional": true, - "requires": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "optional": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "google-p12-pem": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.1.tgz", - "integrity": "sha512-VlQgtozgNVVVcYTXS36eQz4PXPt9gIPqLOhHN0QiV6W6h4qSCNVKPtKC5INtJsaHHF2r7+nOIa26MJeJMTaZEQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", + "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", "optional": true, "requires": { - "node-forge": "^0.9.0" - }, - "dependencies": { - "node-forge": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.2.tgz", - "integrity": "sha512-naKSScof4Wn+aoHU6HBsifh92Zeicm1GDQKd1vp3Y/kOi8ub0DozCa9KpvYNCXslFHYRmLNiqRopGdTGwNLpNw==", - "optional": true - } + "node-forge": "^0.10.0" } }, "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" }, "growl": { "version": "1.10.5", @@ -3896,15 +4175,14 @@ "dev": true }, "gtoken": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.1.tgz", - "integrity": "sha512-33w4FNDkUcyIOq/TqyC+drnKdI4PdXmWp9lZzssyEQKuvu9ZFN3KttaSnDKo52U3E51oujVGop93mKxmqO8HHg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz", + "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==", "optional": true, "requires": { - "gaxios": "^3.0.0", - "google-p12-pem": "^3.0.0", - "jws": "^4.0.0", - "mime": "^2.2.0" + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" } }, "gulp": { @@ -3919,18 +4197,29 @@ "vinyl-fs": "^3.0.0" }, "dependencies": { - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" } }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, "gulp-cli": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", @@ -3957,10 +4246,55 @@ "yargs": "^7.1.0" } }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "y18n": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", + "dev": true + }, "yargs": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.1.tgz", - "integrity": "sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", + "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", "dev": true, "requires": { "camelcase": "^3.0.0", @@ -3975,7 +4309,17 @@ "string-width": "^1.0.2", "which-module": "^1.0.0", "y18n": "^3.2.1", - "yargs-parser": "5.0.0-security.0" + "yargs-parser": "^5.0.1" + } + }, + "yargs-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", + "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", + "dev": true, + "requires": { + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" } } } @@ -3992,37 +4336,15 @@ } }, "gulp-header": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz", - "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-2.0.9.tgz", + "integrity": "sha512-LMGiBx+qH8giwrOuuZXSGvswcIUh0OiioNkUpLhNyvaC6/Ga8X6cfAeme2L5PqsbXMhL8o8b/OmVqIQdxprhcQ==", "dev": true, "requires": { - "concat-with-sourcemaps": "*", - "lodash.template": "^4.4.0", + "concat-with-sourcemaps": "^1.1.0", + "lodash.template": "^4.5.0", + "map-stream": "0.0.7", "through2": "^2.0.0" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } - } - }, - "gulp-replace": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-0.5.4.tgz", - "integrity": "sha1-aaZ5FLvRPFYr/xT1BKQDeWqg2qk=", - "dev": true, - "requires": { - "istextorbinary": "1.0.2", - "readable-stream": "^2.0.1", - "replacestream": "^4.0.0" } }, "gulp-typescript": { @@ -4050,105 +4372,15 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true - } - } - }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true }, "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" + "inherits": "^2.0.4", + "readable-stream": "2 || 3" } } } @@ -4169,12 +4401,12 @@ "dev": true }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "dev": true, "requires": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, @@ -4194,27 +4426,32 @@ "dev": true, "requires": { "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", "dev": true }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, "has-unicode": { @@ -4262,20 +4499,13 @@ "optional": true }, "hasha": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", - "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", "dev": true, "requires": { - "is-stream": "^1.0.1" - }, - "dependencies": { - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - } + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" } }, "he": { @@ -4294,9 +4524,9 @@ } }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "html-escaper": { @@ -4316,26 +4546,12 @@ "get-prop": "0.0.10", "minimist": "^1.2.0", "stream-buffers": "^3.0.0" - }, - "dependencies": { - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - } } }, "http-parser-js": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz", - "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==" + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", + "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" }, "http-proxy-agent": { "version": "4.0.1", @@ -4363,7 +4579,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "optional": true, "requires": { "agent-base": "6", "debug": "4" @@ -4379,9 +4594,9 @@ } }, "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true }, "ignore": { @@ -4390,19 +4605,10 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, - "ignore-walk": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", - "dev": true, - "requires": { - "minimatch": "^3.0.4" - } - }, "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -4420,6 +4626,12 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -4442,9 +4654,9 @@ "dev": true }, "inquirer": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.0.tgz", - "integrity": "sha512-K+LZp6L/6eE5swqIcVXrxl21aGDU4S50gKH0/d96OMQnSBCyGyZl/oZhbkVmdp5sBoINHd4xZvFSARh2dk6DWA==", + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -4453,97 +4665,13 @@ "cli-width": "^3.0.0", "external-editor": "^3.0.3", "figures": "^3.0.0", - "lodash": "^4.17.15", + "lodash": "^4.17.19", "mute-stream": "0.0.8", "run-async": "^2.4.0", "rxjs": "^6.6.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "interpret": { @@ -4594,6 +4722,12 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-bigint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", + "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", + "dev": true + }, "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", @@ -4603,6 +4737,15 @@ "binary-extensions": "^1.0.0" } }, + "is-boolean-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", + "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -4610,11 +4753,20 @@ "dev": true }, "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", "dev": true }, + "is-core-module": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -4636,9 +4788,9 @@ } }, "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", + "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", "dev": true }, "is-descriptor": { @@ -4673,13 +4825,9 @@ "dev": true }, "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { "version": "4.0.1", @@ -4696,6 +4844,12 @@ "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", "dev": true }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -4716,6 +4870,12 @@ } } }, + "is-number-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", + "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", + "dev": true + }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -4762,12 +4922,13 @@ } }, "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", + "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", "dev": true, "requires": { - "has-symbols": "^1.0.1" + "call-bind": "^1.0.2", + "has-symbols": "^1.0.2" } }, "is-relative": { @@ -4782,8 +4943,7 @@ "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "optional": true + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" }, "is-stream-ended": { "version": "0.1.4", @@ -4791,13 +4951,19 @@ "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", "optional": true }, + "is-string": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", + "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", + "dev": true + }, "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", "dev": true, "requires": { - "has-symbols": "^1.0.1" + "has-symbols": "^1.0.2" } }, "is-typedarray": { @@ -4835,7 +5001,8 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "2.0.0", @@ -4856,139 +5023,126 @@ "dev": true }, "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", "dev": true }, "istanbul-lib-hook": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", - "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, "requires": { - "append-transform": "^1.0.0" + "append-transform": "^2.0.0" } }, "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, "requires": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" } }, - "istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", "dev": true, "requires": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" }, "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "isexe": "^2.0.0" } } } }, - "istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" } }, - "istanbul-reports": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", - "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", "dev": true, "requires": { - "html-escaper": "^2.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" } }, - "istextorbinary": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/istextorbinary/-/istextorbinary-1.0.2.tgz", - "integrity": "sha1-rOGTVNGpoBc+/rEITOD4ewrX3s8=", + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", "dev": true, "requires": { - "binaryextensions": "~1.0.0", - "textextensions": "~1.0.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" } }, "jju": { @@ -4997,6 +5151,14 @@ "integrity": "sha1-o6vicYryQaKykE+EpiWXDzia4yo=", "dev": true }, + "jose": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz", + "integrity": "sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA==", + "requires": { + "@panva/asn1.js": "^1.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5004,13 +5166,20 @@ "dev": true }, "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + } } }, "jsbn": { @@ -5026,9 +5195,9 @@ "dev": true }, "json-bigint": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.1.tgz", - "integrity": "sha512-DGWnSzmusIreWlEupsUelHrhwmPPE+FiQvg+drKfk2p+bdEYa5mp4PJ8JsCWqae0M2jQNb0HPvnwvf1qOTThzQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "optional": true, "requires": { "bignumber.js": "^9.0.0" @@ -5064,6 +5233,15 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -5129,15 +5307,15 @@ } }, "just-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", - "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", + "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==", "dev": true }, "just-extend": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", - "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, "jwa": { @@ -5151,6 +5329,18 @@ "safe-buffer": "^5.0.1" } }, + "jwks-rsa": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.3.tgz", + "integrity": "sha512-/rkjXRWAp0cS00tunsHResw68P5iTQru8+jHufLNv3JHc4nObFEndfEUSuPugh09N+V9XYxKUqi7QrkmCHSSSg==", + "requires": { + "@types/express-jwt": "0.0.42", + "debug": "^4.1.0", + "jose": "^2.0.5", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.2" + } + }, "jws": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", @@ -5184,6 +5374,38 @@ "dev": true, "requires": { "readable-stream": "^2.0.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "lcid": { @@ -5230,6 +5452,11 @@ "resolve": "^1.1.7" } }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -5244,69 +5471,18 @@ } }, "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "dependencies": { - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } + "p-locate": "^5.0.0" } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash._reinterpolate": { @@ -5315,26 +5491,16 @@ "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", "optional": true }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" - } + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, "lodash.flattendeep": { "version": "4.4.0", @@ -5353,18 +5519,6 @@ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, "lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -5396,28 +5550,11 @@ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -5450,57 +5587,6 @@ "dev": true, "requires": { "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "long": { @@ -5510,19 +5596,42 @@ "optional": true }, "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "optional": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "lru-memoizer": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.4.tgz", + "integrity": "sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==", "requires": { - "yallist": "^3.0.2" + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } } }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "optional": true, "requires": { "semver": "^6.0.0" } @@ -5548,6 +5657,12 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -5598,23 +5713,6 @@ "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", "dev": true }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -5637,22 +5735,22 @@ } }, "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", "optional": true }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.48.0" } }, "mimic-fn": { @@ -5676,22 +5774,22 @@ "dev": true }, "minipass": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", "dev": true, "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" + "yallist": "^4.0.0" } }, "minizlib": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "requires": { - "minipass": "^2.9.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" } }, "mixin-deep": { @@ -5716,72 +5814,54 @@ } }, "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true }, "mocha": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.2.1.tgz", - "integrity": "sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.4.3", - "debug": "4.2.0", - "diff": "4.0.2", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.1.6", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "3.14.0", + "js-yaml": "4.0.0", "log-symbols": "4.0.0", "minimatch": "3.0.4", - "ms": "2.1.2", - "nanoid": "3.1.12", + "ms": "2.1.3", + "nanoid": "3.1.20", "serialize-javascript": "5.0.1", "strip-json-comments": "3.1.1", - "supports-color": "7.2.0", + "supports-color": "8.1.1", "which": "2.0.2", "wide-align": "1.1.3", - "workerpool": "6.0.2", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "dependencies": { "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -5789,9 +5869,9 @@ } }, "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, "braces": { @@ -5803,21 +5883,15 @@ "fill-range": "^7.0.1" } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", + "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -5825,38 +5899,6 @@ "readdirp": "~3.5.0" } }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -5883,23 +5925,25 @@ } }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } }, "is-binary-path": { "version": "2.1.0", @@ -5910,42 +5954,18 @@ "binary-extensions": "^2.0.0" } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5961,42 +5981,10 @@ "picomatch": "^2.2.1" } }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -6020,101 +6008,26 @@ "isexe": "^2.0.0" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", - "dev": true - }, "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - } + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } }, "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true } } }, @@ -6136,12 +6049,6 @@ "minimatch": "^3.0.4" }, "dependencies": { - "array-differ": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", - "dev": true - }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -6150,15 +6057,6 @@ } } }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "requires": { - "duplexer2": "0.0.2" - } - }, "mute-stdout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", @@ -6183,16 +6081,16 @@ } }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", "dev": true, "optional": true }, "nanoid": { - "version": "3.1.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", - "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==", + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", "dev": true }, "nanomatch": { @@ -6220,34 +6118,6 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "needle": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.5.0.tgz", - "integrity": "sha512-o/qITSDR0JCyCKEQ1/1bnUXMmznxabbwi/Y4WwJElf+evwJNFNwIDMCCt5IigFVxgeGBJESLohGtIS9gEzo1fA==", - "dev": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "nested-error-stacks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", - "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", - "dev": true - }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", @@ -6261,9 +6131,9 @@ "dev": true }, "nise": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", - "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", + "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0", @@ -6274,9 +6144,9 @@ } }, "nock": { - "version": "13.0.5", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.0.5.tgz", - "integrity": "sha512-1ILZl0zfFm2G4TIeJFW0iHknxr2NyA+aGCMTjDVUsBY4CkMRispF1pfIYkTRdAR/3Bg+UzdEuK0B6HczMQZcCg==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.1.0.tgz", + "integrity": "sha512-3N3DUY8XYrxxzWazQ+nSBpiaJ3q6gcpNh4gXovC/QBxrsvNp4tq+wsLHF6mJ3nrn3lPLn7KCJqKxy/9aD+0fdw==", "dev": true, "requires": { "debug": "^4.1.0", @@ -6286,48 +6156,36 @@ } }, "node-addon-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz", - "integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true }, "node-fetch": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "optional": true + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, "node-forge": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" }, - "node-pre-gyp": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", - "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", - "dev": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.3", - "needle": "^2.5.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" } }, + "node-releases": { + "version": "1.1.72", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", + "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "dev": true + }, "node-version": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/node-version/-/node-version-1.2.0.tgz", @@ -6335,13 +6193,12 @@ "dev": true }, "nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", "dev": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1" } }, "normalize-package-data": { @@ -6365,13 +6222,10 @@ } }, "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "now-and-later": { "version": "2.0.1", @@ -6382,32 +6236,6 @@ "once": "^1.3.2" } }, - "npm-bundled": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", - "dev": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true - }, - "npm-packlist": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", - "dev": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, "npm-run-all": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", @@ -6445,6 +6273,21 @@ "supports-color": "^5.3.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -6458,6 +6301,12 @@ "which": "^1.2.9" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -6548,53 +6397,40 @@ "dev": true }, "nyc": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", - "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "caching-transform": "^3.0.2", - "convert-source-map": "^1.6.0", - "cp-file": "^6.2.0", - "find-cache-dir": "^2.1.0", - "find-up": "^3.0.0", - "foreground-child": "^1.5.6", - "glob": "^7.1.3", - "istanbul-lib-coverage": "^2.0.5", - "istanbul-lib-hook": "^2.0.7", - "istanbul-lib-instrument": "^3.3.0", - "istanbul-lib-report": "^2.0.8", - "istanbul-lib-source-maps": "^3.0.6", - "istanbul-reports": "^2.2.4", - "js-yaml": "^3.13.1", - "make-dir": "^2.1.0", - "merge-source-map": "^1.1.0", - "resolve-from": "^4.0.0", - "rimraf": "^2.6.3", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", "signal-exit": "^3.0.2", - "spawn-wrap": "^1.4.2", - "test-exclude": "^5.2.3", - "uuid": "^3.3.2", - "yargs": "^13.2.2", - "yargs-parser": "^13.0.0" + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -6602,57 +6438,57 @@ "dev": true }, "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "p-locate": "^4.1.0" } }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "p-try": "^2.0.0" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "p-limit": "^2.2.0" } }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "require-main-filename": { @@ -6661,36 +6497,10 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, "which-module": { @@ -6700,44 +6510,45 @@ "dev": true }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^3.0.0", + "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "yargs-parser": "^18.1.2" } }, "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "requires": { "camelcase": "^5.0.0", @@ -6789,10 +6600,16 @@ } } }, + "object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "optional": true + }, "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", + "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", "dev": true }, "object-keys": { @@ -6811,15 +6628,15 @@ } }, "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" } }, "object.defaults": { @@ -6872,9 +6689,9 @@ } }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "requires": { "mimic-fn": "^2.1.0" } @@ -6900,14 +6717,40 @@ "dev": true, "requires": { "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -6923,57 +6766,46 @@ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "yocto-queue": "^0.1.0" } }, - "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "requires": { - "p-try": "^2.0.0" + "p-limit": "^3.0.2" } }, - "p-locate": { + "p-map": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "requires": { - "p-limit": "^2.0.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } + "aggregate-error": "^3.0.0" } }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "package-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", - "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", "dev": true, "requires": { "graceful-fs": "^4.1.15", - "hasha": "^3.0.0", + "hasha": "^5.0.0", "lodash.flattendeep": "^4.4.0", "release-zalgo": "^1.0.0" } @@ -7042,7 +6874,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, @@ -7059,9 +6891,9 @@ "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "path-root": { @@ -7108,9 +6940,9 @@ } }, "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, "performance-now": { @@ -7120,9 +6952,9 @@ "dev": true }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "dev": true }, "pidtree": { @@ -7153,22 +6985,56 @@ } }, "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "find-up": "^3.0.0" + "find-up": "^4.0.0" }, "dependencies": { "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true } } }, @@ -7198,14 +7064,24 @@ }, "pretty-hrtime": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } }, "progress": { "version": "2.0.3", @@ -7226,9 +7102,9 @@ "dev": true }, "protobufjs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz", - "integrity": "sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", + "integrity": "sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==", "optional": true, "requires": { "@protobufjs/aspromise": "^1.1.2", @@ -7242,23 +7118,14 @@ "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/long": "^4.0.1", - "@types/node": "^13.7.0", + "@types/node": ">=13.7.0", "long": "^4.0.0" - }, - "dependencies": { - "@types/node": { - "version": "13.13.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.30.tgz", - "integrity": "sha512-HmqFpNzp3TSELxU/bUuRK+xzarVOAsR00hzcvM0TXrMlt/+wcSLa5q6YhTb6/cA6wqDCZLDcfd8fSL95x5h7AA==", - "optional": true - } } }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.8.0", @@ -7285,31 +7152,6 @@ "duplexify": "^4.1.1", "inherits": "^2.0.3", "pump": "^3.0.0" - }, - "dependencies": { - "duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", - "optional": true, - "requires": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "optional": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "punycode": { @@ -7333,18 +7175,6 @@ "safe-buffer": "^5.1.0" } }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -7367,24 +7197,13 @@ } }, "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "readdirp": { @@ -7396,6 +7215,38 @@ "graceful-fs": "^4.1.11", "micromatch": "^3.1.10", "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "rechoir": { @@ -7447,22 +7298,10 @@ "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", "dev": true, - "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" } }, "remove-trailing-separator": { @@ -7472,9 +7311,9 @@ "dev": true }, "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", "dev": true }, "repeat-string": { @@ -7500,17 +7339,6 @@ "remove-trailing-separator": "^1.1.0" } }, - "replacestream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", - "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.3", - "object-assign": "^4.0.1", - "readable-stream": "^2.0.2" - } - }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -7550,16 +7378,6 @@ "mime-types": "^2.1.12" } }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", @@ -7569,36 +7387,24 @@ } }, "request-promise": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", - "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz", + "integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==", "dev": true, "requires": { "bluebird": "^3.5.0", - "request-promise-core": "1.1.3", + "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } } }, "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", "dev": true, "requires": { - "lodash": "^4.17.15" + "lodash": "^4.17.19" } }, "require-directory": { @@ -7668,20 +7474,25 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "optional": true + }, "retry-request": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", - "integrity": "sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", + "integrity": "sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==", "optional": true, "requires": { - "debug": "^4.1.1", - "through2": "^3.0.1" + "debug": "^4.1.1" } }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -7694,22 +7505,123 @@ "dev": true }, "run-sequence": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-1.2.2.tgz", - "integrity": "sha1-UJWgvr6YczsBQL0I3YDsAw3azes=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz", + "integrity": "sha512-qkzZnQWMZjcKbh3CNly2srtrkaO/2H/SI5f2eliMCapdRD3UhMrwjfOAZJAnZ2H8Ju4aBzFZkBGXUqFs9V0yxw==", "dev": true, "requires": { - "chalk": "*", - "gulp-util": "*" + "chalk": "^1.1.3", + "fancy-log": "^1.3.2", + "plugin-error": "^0.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } } }, "rxjs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz", - "integrity": "sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg==", + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, "requires": { "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "safe-buffer": { @@ -7719,7 +7631,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -7732,12 +7644,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -7764,7 +7670,8 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "set-value": { "version": "2.0.1", @@ -7816,17 +7723,16 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "sinon": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.2.tgz", - "integrity": "sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==", + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.2", + "@sinonjs/commons": "^1.8.1", "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/formatio": "^5.0.1", - "@sinonjs/samsam": "^5.0.3", + "@sinonjs/samsam": "^5.3.1", "diff": "^4.0.2", - "nise": "^4.0.1", + "nise": "^4.0.4", "supports-color": "^7.1.0" }, "dependencies": { @@ -7835,28 +7741,13 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, "sinon-chai": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.5.0.tgz", - "integrity": "sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.7.0.tgz", + "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", "dev": true }, "slice-ansi": { @@ -7879,6 +7770,21 @@ "color-convert": "^1.9.0" } }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -7941,6 +7847,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, @@ -8016,9 +7928,9 @@ } }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "source-map-resolve": { @@ -8042,20 +7954,12 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, "sparkles": { @@ -8065,17 +7969,28 @@ "dev": true }, "spawn-wrap": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", - "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, "requires": { - "foreground-child": "^1.5.6", - "mkdirp": "^0.5.0", - "os-homedir": "^1.0.1", - "rimraf": "^2.6.2", + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", "signal-exit": "^3.0.2", - "which": "^1.3.0" + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "spdx-correct": { @@ -8105,9 +8020,9 @@ } }, "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", + "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", "dev": true }, "split-string": { @@ -8208,19 +8123,6 @@ "dev": true, "requires": { "readable-stream": "^3.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "streamsearch": { @@ -8235,68 +8137,60 @@ "dev": true }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "string.prototype.padend": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz", - "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz", + "integrity": "sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ==", "dev": true, "requires": { + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" + "es-abstract": "^1.18.0-next.2" } }, "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" } }, "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } + "safe-buffer": "~5.2.0" } }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^5.0.0" } }, "strip-bom": { @@ -8309,9 +8203,9 @@ } }, "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "stubs": { @@ -8321,10 +8215,13 @@ "optional": true }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } }, "sver-compat": { "version": "1.5.0", @@ -8380,147 +8277,58 @@ "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", - "dev": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "teeny-request": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.0.tgz", - "integrity": "sha512-kWD3sdGmIix6w7c8ZdVKxWq+3YwVPGWz+Mq0wRZXayEKY/YHb63b8uphfBzcFDmyq8frD9+UTc3wLyOhltRbtg==", - "optional": true, - "requires": { - "http-proxy-agent": "^4.0.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.2.0", - "stream-events": "^1.0.5", - "uuid": "^8.0.0" - } - }, - "test-exclude": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", - "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", - "dev": true, - "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^2.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" + "ansi-regex": "^4.1.0" } - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true } } }, + "tar": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", + "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "teeny-request": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.1.0.tgz", + "integrity": "sha512-hPfSc05a7Mf3syqVhSkrVMb844sMiP60MrfGMts3ft6V6UlSkEIGQzgwf0dy1KjdE3FV2lJ5s7QCBFcaoQLA6g==", + "optional": true, + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "textextensions": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/textextensions/-/textextensions-1.0.2.tgz", - "integrity": "sha1-ZUhjk+4fK7A5pgy7oFsLaL2VAdI=", - "dev": true - }, "thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -8546,12 +8354,45 @@ "dev": true }, "through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, "requires": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "through2-filter": { @@ -8562,18 +8403,6 @@ "requires": { "through2": "~2.0.0", "xtend": "~4.0.0" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } } }, "time-stamp": { @@ -8662,45 +8491,60 @@ "dev": true, "requires": { "through2": "^2.0.3" - }, - "dependencies": { - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "ts-node": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", - "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", "dev": true, "requires": { "arg": "^4.1.0", + "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "source-map-support": "^0.5.17", "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } } }, "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" }, "tsutils": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", - "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "requires": { "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "tunnel-agent": { @@ -8748,23 +8592,35 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "optional": true, "requires": { "is-typedarray": "^1.0.0" } }, "typescript": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz", - "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==", + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", "dev": true }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -8772,9 +8628,9 @@ "dev": true }, "undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", "dev": true, "requires": { "arr-flatten": "^1.0.1", @@ -8782,10 +8638,19 @@ "bach": "^1.0.0", "collection-map": "^1.0.0", "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", "last-run": "^1.1.0", "object.defaults": "^1.0.0", "object.reduce": "^1.0.0", "undertaker-registry": "^1.0.0" + }, + "dependencies": { + "fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=", + "dev": true + } } }, "undertaker-registry": { @@ -8878,9 +8743,9 @@ "dev": true }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" @@ -8904,15 +8769,15 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", - "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "optional": true }, "v8-compile-cache": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", - "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, "v8flags": { @@ -8958,9 +8823,9 @@ } }, "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", "dev": true, "requires": { "clone": "^2.1.1", @@ -8996,6 +8861,18 @@ "vinyl-sourcemap": "^1.1.0" }, "dependencies": { + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", @@ -9017,14 +8894,34 @@ "pump": "^2.0.0" } }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "safe-buffer": "~5.1.0" } } } @@ -9042,6 +8939,17 @@ "now-and-later": "^2.0.0", "remove-bom-buffer": "^3.0.0", "vinyl": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "websocket-driver": { @@ -9068,6 +8976,19 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, "which-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", @@ -9081,6 +9002,39 @@ "dev": true, "requires": { "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "word-wrap": { @@ -9090,19 +9044,19 @@ "dev": true }, "workerpool": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.2.tgz", - "integrity": "sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", "dev": true }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, "wrappy": { @@ -9117,13 +9071,23 @@ "dev": true, "requires": { "mkdirp": "^0.5.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } } }, "write-file-atomic": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "optional": true, "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -9150,15 +9114,14 @@ "dev": true }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { "version": "17.0.1", @@ -9173,115 +9136,12 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "dev": true - } } }, "yargs-parser": { - "version": "5.0.0-security.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz", - "integrity": "sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ==", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "object.assign": "^4.1.0" - } + "version": "20.2.7", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==" }, "yargs-unparser": { "version": "2.0.0", @@ -9315,6 +9175,11 @@ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, "z-schema": { "version": "3.18.4", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.18.4.tgz", diff --git a/package.json b/package.json index 2b78d495b5..f82170e2ae 100644 --- a/package.json +++ b/package.json @@ -144,11 +144,12 @@ } }, "dependencies": { - "@firebase/database": "^0.8.1", - "@firebase/database-types": "^0.6.1", - "@types/node": "^10.10.0", + "@firebase/database": "^0.10.0", + "@firebase/database-types": "^0.7.2", + "@types/node": ">=12.12.47", "dicer": "^0.3.0", "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^2.0.2", "node-forge": "^0.10.0" }, "optionalDependencies": { @@ -179,15 +180,14 @@ "bcrypt": "^5.0.0", "chai": "^4.2.0", "chai-as-promised": "^7.0.0", - "chalk": "^1.1.3", + "chalk": "^4.1.1", "child-process-promise": "^2.2.1", "del": "^2.2.1", "eslint": "^6.8.0", "firebase-token-generator": "^2.0.0", "gulp": "^4.0.2", "gulp-filter": "^6.0.0", - "gulp-header": "^1.8.8", - "gulp-replace": "^0.5.4", + "gulp-header": "^2.0.9", "gulp-typescript": "^5.0.1", "http-message-parser": "^0.0.34", "lodash": "^4.17.15", @@ -196,10 +196,10 @@ "mz": "^2.7.0", "nock": "^13.0.0", "npm-run-all": "^4.1.5", - "nyc": "^14.1.0", + "nyc": "^15.1.0", "request": "^2.75.0", "request-promise": "^4.1.1", - "run-sequence": "^1.1.5", + "run-sequence": "^2.2.1", "sinon": "^9.0.0", "sinon-chai": "^3.0.0", "ts-node": "^9.0.0", diff --git a/src/app-check/app-check-api-client-internal.ts b/src/app-check/app-check-api-client-internal.ts new file mode 100644 index 0000000000..640acb72aa --- /dev/null +++ b/src/app-check/app-check-api-client-internal.ts @@ -0,0 +1,228 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { appCheck } from './index'; +import { + HttpRequestConfig, HttpClient, HttpError, AuthorizedHttpClient, HttpResponse +} from '../utils/api-request'; +import { FirebaseApp } from '../app/firebase-app'; +import { PrefixedFirebaseError } from '../utils/error'; + +import * as utils from '../utils/index'; +import * as validator from '../utils/validator'; + +import AppCheckToken = appCheck.AppCheckToken; + +// App Check backend constants +const FIREBASE_APP_CHECK_V1_API_URL_FORMAT = 'https://firebaseappcheck.googleapis.com/v1beta/projects/{projectId}/apps/{appId}:exchangeCustomToken'; + +const FIREBASE_APP_CHECK_CONFIG_HEADERS = { + 'X-Firebase-Client': `fire-admin-node/${utils.getSdkVersion()}` +}; + +/** + * Class that facilitates sending requests to the Firebase App Check backend API. + * + * @internal + */ +export class AppCheckApiClient { + private readonly httpClient: HttpClient; + private projectId?: string; + + constructor(private readonly app: FirebaseApp) { + if (!validator.isNonNullObject(app) || !('options' in app)) { + throw new FirebaseAppCheckError( + 'invalid-argument', + 'First argument passed to admin.appCheck() must be a valid Firebase app instance.'); + } + this.httpClient = new AuthorizedHttpClient(app); + } + + /** + * Exchange a signed custom token to App Check token + * + * @param customToken The custom token to be exchanged. + * @param appId The mobile App ID. + * @return A promise that fulfills with a `AppCheckToken`. + */ + public exchangeToken(customToken: string, appId: string): Promise { + if (!validator.isNonEmptyString(appId)) { + throw new FirebaseAppCheckError( + 'invalid-argument', + '`appId` must be a non-empty string.'); + } + if (!validator.isNonEmptyString(customToken)) { + throw new FirebaseAppCheckError( + 'invalid-argument', + '`customToken` must be a non-empty string.'); + } + return this.getUrl(appId) + .then((url) => { + const request: HttpRequestConfig = { + method: 'POST', + url, + headers: FIREBASE_APP_CHECK_CONFIG_HEADERS, + data: { customToken } + }; + return this.httpClient.send(request); + }) + .then((resp) => { + return this.toAppCheckToken(resp); + }) + .catch((err) => { + throw this.toFirebaseError(err); + }); + } + + private getUrl(appId: string): Promise { + return this.getProjectId() + .then((projectId) => { + const urlParams = { + projectId, + appId, + }; + const baseUrl = utils.formatString(FIREBASE_APP_CHECK_V1_API_URL_FORMAT, urlParams); + return utils.formatString(baseUrl); + }); + } + + private getProjectId(): Promise { + if (this.projectId) { + return Promise.resolve(this.projectId); + } + return utils.findProjectId(this.app) + .then((projectId) => { + if (!validator.isNonEmptyString(projectId)) { + throw new FirebaseAppCheckError( + 'unknown-error', + 'Failed to determine project ID. Initialize the ' + + 'SDK with service account credentials or set project ID as an app option. ' + + 'Alternatively, set the GOOGLE_CLOUD_PROJECT environment variable.'); + } + this.projectId = projectId; + return projectId; + }); + } + + private toFirebaseError(err: HttpError): PrefixedFirebaseError { + if (err instanceof PrefixedFirebaseError) { + return err; + } + + const response = err.response; + if (!response.isJson()) { + return new FirebaseAppCheckError( + 'unknown-error', + `Unexpected response with status: ${response.status} and body: ${response.text}`); + } + + const error: Error = (response.data as ErrorResponse).error || {}; + let code: AppCheckErrorCode = 'unknown-error'; + if (error.status && error.status in APP_CHECK_ERROR_CODE_MAPPING) { + code = APP_CHECK_ERROR_CODE_MAPPING[error.status]; + } + const message = error.message || `Unknown server error: ${response.text}`; + return new FirebaseAppCheckError(code, message); + } + + /** + * Creates an AppCheckToken from the API response. + * + * @param resp API response object. + * @return An AppCheckToken instance. + */ + private toAppCheckToken(resp: HttpResponse): AppCheckToken { + const token = resp.data.attestationToken; + // `ttl` is a string with the suffix "s" preceded by the number of seconds, + // with nanoseconds expressed as fractional seconds. + const ttlMillis = this.stringToMilliseconds(resp.data.ttl); + return { + token, + ttlMillis + } + } + + /** + * Converts a duration string with the suffix `s` to milliseconds. + * + * @param duration The duration as a string with the suffix "s" preceded by the + * number of seconds, with fractional seconds. For example, 3 seconds with 0 nanoseconds + * is expressed as "3s", while 3 seconds and 1 nanosecond is expressed as "3.000000001s", + * and 3 seconds and 1 microsecond is expressed as "3.000001s". + * + * @return The duration in milliseconds. + */ + private stringToMilliseconds(duration: string): number { + if (!validator.isNonEmptyString(duration) || !duration.endsWith('s')) { + throw new FirebaseAppCheckError( + 'invalid-argument', '`ttl` must be a valid duration string with the suffix `s`.'); + } + const seconds = duration.slice(0, -1); + return Math.floor(Number(seconds) * 1000); + } +} + +interface ErrorResponse { + error?: Error; +} + +interface Error { + code?: number; + message?: string; + status?: string; +} + +export const APP_CHECK_ERROR_CODE_MAPPING: { [key: string]: AppCheckErrorCode } = { + ABORTED: 'aborted', + INVALID_ARGUMENT: 'invalid-argument', + INVALID_CREDENTIAL: 'invalid-credential', + INTERNAL: 'internal-error', + PERMISSION_DENIED: 'permission-denied', + UNAUTHENTICATED: 'unauthenticated', + NOT_FOUND: 'not-found', + UNKNOWN: 'unknown-error', +}; + +export type AppCheckErrorCode = + 'aborted' + | 'invalid-argument' + | 'invalid-credential' + | 'internal-error' + | 'permission-denied' + | 'unauthenticated' + | 'not-found' + | 'app-check-token-expired' + | 'unknown-error'; + +/** + * Firebase App Check error code structure. This extends PrefixedFirebaseError. + * + * @param {AppCheckErrorCode} code The error code. + * @param {string} message The error message. + * @constructor + */ +export class FirebaseAppCheckError extends PrefixedFirebaseError { + constructor(code: AppCheckErrorCode, message: string) { + super('app-check', code, message); + + /* tslint:disable:max-line-length */ + // Set the prototype explicitly. See the following link for more details: + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + /* tslint:enable:max-line-length */ + (this as any).__proto__ = FirebaseAppCheckError.prototype; + } +} diff --git a/src/app-check/app-check.ts b/src/app-check/app-check.ts new file mode 100644 index 0000000000..1e79980820 --- /dev/null +++ b/src/app-check/app-check.ts @@ -0,0 +1,86 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { appCheck } from './index'; +import { AppCheckApiClient } from './app-check-api-client-internal'; +import { + appCheckErrorFromCryptoSignerError, AppCheckTokenGenerator +} from './token-generator'; +import { AppCheckTokenVerifier } from './token-verifier'; +import { cryptoSignerFromApp } from '../utils/crypto-signer'; + +import AppCheckInterface = appCheck.AppCheck; +import AppCheckToken = appCheck.AppCheckToken; +import VerifyAppCheckTokenResponse = appCheck.VerifyAppCheckTokenResponse; +import { FirebaseApp } from '../app/firebase-app'; + +/** + * AppCheck service bound to the provided app. + */ +export class AppCheck implements AppCheckInterface { + + private readonly client: AppCheckApiClient; + private readonly tokenGenerator: AppCheckTokenGenerator; + private readonly appCheckTokenVerifier: AppCheckTokenVerifier; + + /** + * @param app The app for this AppCheck service. + * @constructor + */ + constructor(readonly app: FirebaseApp) { + this.client = new AppCheckApiClient(app); + try { + this.tokenGenerator = new AppCheckTokenGenerator(cryptoSignerFromApp(app)); + } catch (err) { + throw appCheckErrorFromCryptoSignerError(err); + } + this.appCheckTokenVerifier = new AppCheckTokenVerifier(app); + } + + /** + * Creates a new {@link appCheck.AppCheckToken `AppCheckToken`} that can be sent + * back to a client. + * + * @param appId The app ID to use as the JWT app_id. + * + * @returns A promise that fulfills with a `AppCheckToken`. + */ + public createToken(appId: string): Promise { + return this.tokenGenerator.createCustomToken(appId) + .then((customToken) => { + return this.client.exchangeToken(customToken, appId); + }); + } + + /** + * Verifies an App Check token. + * + * @param appCheckToken The App Check token to verify. + * + * @returns A promise that fulfills with a `VerifyAppCheckTokenResponse` on successful + * verification. + */ + public verifyToken(appCheckToken: string): Promise { + return this.appCheckTokenVerifier.verifyToken(appCheckToken) + .then((decodedToken) => { + return { + appId: decodedToken.app_id, + token: decodedToken, + }; + }); + } +} diff --git a/src/app-check/index.ts b/src/app-check/index.ts new file mode 100644 index 0000000000..6552d9208d --- /dev/null +++ b/src/app-check/index.ts @@ -0,0 +1,164 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { app } from '../firebase-namespace-api'; + +/** + * Gets the {@link appCheck.AppCheck `AppCheck`} service for the + * default app or a given app. + * + * You can call `admin.appCheck()` with no arguments to access the default + * app's {@link appCheck.AppCheck `AppCheck`} service or as + * `admin.appCheck(app)` to access the + * {@link appCheck.AppCheck `AppCheck`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the `AppCheck` service for the default app + * var defaultAppCheck = admin.appCheck(); + * ``` + * + * @example + * ```javascript + * // Get the `AppCheck` service for a given app + * var otherAppCheck = admin.appCheck(otherApp); + * ``` + * + * @param app Optional app for which to return the `AppCheck` service. + * If not provided, the default `AppCheck` service is returned. + * + * @return The default `AppCheck` service if no + * app is provided, or the `AppCheck` service associated with the provided + * app. + */ +export declare function appCheck(app?: app.App): appCheck.AppCheck; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace appCheck { + /** + * The Firebase `AppCheck` service interface. + */ + export interface AppCheck { + app: app.App; + + /** + * Creates a new {@link appCheck.AppCheckToken `AppCheckToken`} that can be sent + * back to a client. + * + * @param appId The App ID of the Firebase App the token belongs to. + * + * @return A promise that fulfills with a `AppCheckToken`. + */ + createToken(appId: string): Promise; + + /** + * Verifies a Firebase App Check token (JWT). If the token is valid, the promise is + * fulfilled with the token's decoded claims; otherwise, the promise is + * rejected. + * + * @param appCheckToken The App Check token to verify. + * + * @return A promise fulfilled with the + * token's decoded claims if the App Check token is valid; otherwise, a rejected + * promise. + */ + verifyToken(appCheckToken: string): Promise; + } + + /** + * Interface representing an App Check token. + */ + export interface AppCheckToken { + /** + * The Firebase App Check token. + */ + token: string; + + /** + * The time-to-live duration of the token in milliseconds. + */ + ttlMillis: number; + } + + /** + * Interface representing a decoded Firebase App Check token, returned from the + * {@link appCheck.AppCheck.verifyToken `verifyToken()`} method. + */ + export interface DecodedAppCheckToken { + /** + * The issuer identifier for the issuer of the response. + * + * This value is a URL with the format + * `https://firebaseappcheck.googleapis.com/`, where `` is the + * same project number specified in the [`aud`](#aud) property. + */ + iss: string; + + /** + * The Firebase App ID corresponding to the app the token belonged to. + * + * As a convenience, this value is copied over to the [`app_id`](#app_id) property. + */ + sub: string; + + /** + * The audience for which this token is intended. + * + * This value is a JSON array of two strings, the first is the project number of your + * Firebase project, and the second is the project ID of the same project. + */ + aud: string[]; + + /** + * The App Check token's expiration time, in seconds since the Unix epoch. That is, the + * time at which this App Check token expires and should no longer be considered valid. + */ + exp: number; + + /** + * The App Check token's issued-at time, in seconds since the Unix epoch. That is, the + * time at which this App Check token was issued and should start to be considered + * valid. + */ + iat: number; + + /** + * The App ID corresponding to the App the App Check token belonged to. + * + * This value is not actually one of the JWT token claims. It is added as a + * convenience, and is set as the value of the [`sub`](#sub) property. + */ + app_id: string; + [key: string]: any; + } + + /** + * Interface representing a verified App Check token response. + */ + export interface VerifyAppCheckTokenResponse { + /** + * The App ID corresponding to the App the App Check token belonged to. + */ + appId: string; + + /** + * The decoded Firebase App Check token. + */ + token: appCheck.DecodedAppCheckToken; + } +} diff --git a/src/app-check/token-generator.ts b/src/app-check/token-generator.ts new file mode 100644 index 0000000000..1b557438bb --- /dev/null +++ b/src/app-check/token-generator.ts @@ -0,0 +1,145 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as validator from '../utils/validator'; +import { toWebSafeBase64 } from '../utils'; + +import { CryptoSigner, CryptoSignerError, CryptoSignerErrorCode } from '../utils/crypto-signer'; +import { + FirebaseAppCheckError, + AppCheckErrorCode, + APP_CHECK_ERROR_CODE_MAPPING, +} from './app-check-api-client-internal'; +import { HttpError } from '../utils/api-request'; + +const ONE_HOUR_IN_SECONDS = 60 * 60; + +// Audience to use for Firebase App Check Custom tokens +const FIREBASE_APP_CHECK_AUDIENCE = 'https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1beta.TokenExchangeService'; + +/** + * Class for generating Firebase App Check tokens. + * + * @internal + */ +export class AppCheckTokenGenerator { + + private readonly signer: CryptoSigner; + + /** + * The AppCheckTokenGenerator class constructor. + * + * @param signer The CryptoSigner instance for this token generator. + * @constructor + */ + constructor(signer: CryptoSigner) { + if (!validator.isNonNullObject(signer)) { + throw new FirebaseAppCheckError( + 'invalid-argument', + 'INTERNAL ASSERT: Must provide a CryptoSigner to use AppCheckTokenGenerator.'); + } + this.signer = signer; + } + + /** + * Creates a new custom token that can be exchanged to an App Check token. + * + * @param appId The Application ID to use for the generated token. + * + * @return A Promise fulfilled with a custom token signed with a service account key + * that can be exchanged to an App Check token. + */ + public createCustomToken(appId: string): Promise { + if (!validator.isNonEmptyString(appId)) { + throw new FirebaseAppCheckError( + 'invalid-argument', + '`appId` must be a non-empty string.'); + } + return this.signer.getAccountId().then((account) => { + const header = { + alg: this.signer.algorithm, + typ: 'JWT', + }; + const iat = Math.floor(Date.now() / 1000); + const body = { + iss: account, + sub: account, + // eslint-disable-next-line @typescript-eslint/camelcase + app_id: appId, + aud: FIREBASE_APP_CHECK_AUDIENCE, + exp: iat + ONE_HOUR_IN_SECONDS, + iat, + }; + const token = `${this.encodeSegment(header)}.${this.encodeSegment(body)}`; + return this.signer.sign(Buffer.from(token)) + .then((signature) => { + return `${token}.${this.encodeSegment(signature)}`; + }); + }).catch((err) => { + throw appCheckErrorFromCryptoSignerError(err); + }); + } + + private encodeSegment(segment: object | Buffer): string { + const buffer: Buffer = (segment instanceof Buffer) ? segment : Buffer.from(JSON.stringify(segment)); + return toWebSafeBase64(buffer).replace(/=+$/, ''); + } +} + +/** + * Creates a new FirebaseAppCheckError by extracting the error code, message and other relevant + * details from a CryptoSignerError. + * + * @param err The Error to convert into a FirebaseAppCheckError error + * @return A Firebase App Check error that can be returned to the user. + */ +export function appCheckErrorFromCryptoSignerError(err: Error): Error { + if (!(err instanceof CryptoSignerError)) { + return err; + } + if (err.code === CryptoSignerErrorCode.SERVER_ERROR && validator.isNonNullObject(err.cause)) { + const httpError = err.cause as HttpError + const errorResponse = httpError.response.data; + if (errorResponse?.error) { + const status = errorResponse.error.status; + const description = errorResponse.error.message || JSON.stringify(httpError.response); + + let code: AppCheckErrorCode = 'unknown-error'; + if (status && status in APP_CHECK_ERROR_CODE_MAPPING) { + code = APP_CHECK_ERROR_CODE_MAPPING[status]; + } + return new FirebaseAppCheckError(code, + `Error returned from server while siging a custom token: ${description}` + ); + } + return new FirebaseAppCheckError('internal-error', + 'Error returned from server: ' + JSON.stringify(errorResponse) + '.' + ); + } + return new FirebaseAppCheckError(mapToAppCheckErrorCode(err.code), err.message); +} + +function mapToAppCheckErrorCode(code: string): AppCheckErrorCode { + switch (code) { + case CryptoSignerErrorCode.INVALID_CREDENTIAL: + return 'invalid-credential'; + case CryptoSignerErrorCode.INVALID_ARGUMENT: + return 'invalid-argument'; + default: + return 'internal-error'; + } +} diff --git a/src/app-check/token-verifier.ts b/src/app-check/token-verifier.ts new file mode 100644 index 0000000000..cf37b46ce6 --- /dev/null +++ b/src/app-check/token-verifier.ts @@ -0,0 +1,165 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { appCheck } from '.'; +import * as validator from '../utils/validator'; +import * as util from '../utils/index'; +import { FirebaseAppCheckError } from './app-check-api-client-internal'; +import { FirebaseApp } from '../app/firebase-app'; +import { + ALGORITHM_RS256, DecodedToken, decodeJwt, JwtError, + JwtErrorCode, PublicKeySignatureVerifier, SignatureVerifier +} from '../utils/jwt'; + +import DecodedAppCheckToken = appCheck.DecodedAppCheckToken; + +const APP_CHECK_ISSUER = 'https://firebaseappcheck.googleapis.com/'; +const JWKS_URL = 'https://firebaseappcheck.googleapis.com/v1beta/jwks'; + +/** + * Class for verifying Firebase App Check tokens. + * + * @internal + */ +export class AppCheckTokenVerifier { + private readonly signatureVerifier: SignatureVerifier; + + constructor(private readonly app: FirebaseApp) { + this.signatureVerifier = PublicKeySignatureVerifier.withJwksUrl(JWKS_URL); + } + + /** + * Verifies the format and signature of a Firebase App Check token. + * + * @param token The Firebase Auth JWT token to verify. + * @return A promise fulfilled with the decoded claims of the Firebase App Check token. + */ + public verifyToken(token: string): Promise { + if (!validator.isString(token)) { + throw new FirebaseAppCheckError( + 'invalid-argument', + 'App check token must be a non-null string.', + ); + } + + return this.ensureProjectId() + .then((projectId) => { + return this.decodeAndVerify(token, projectId); + }) + .then((decoded) => { + const decodedAppCheckToken = decoded.payload as DecodedAppCheckToken; + // eslint-disable-next-line @typescript-eslint/camelcase + decodedAppCheckToken.app_id = decodedAppCheckToken.sub; + return decodedAppCheckToken; + }); + } + + private ensureProjectId(): Promise { + return util.findProjectId(this.app) + .then((projectId) => { + if (!validator.isNonEmptyString(projectId)) { + throw new FirebaseAppCheckError( + 'invalid-credential', + 'Must initialize app with a cert credential or set your Firebase project ID as the ' + + 'GOOGLE_CLOUD_PROJECT environment variable to verify an App Check token.' + ); + } + return projectId; + }) + } + + private decodeAndVerify(token: string, projectId: string): Promise { + return this.safeDecode(token) + .then((decodedToken) => { + this.verifyContent(decodedToken, projectId); + return this.verifySignature(token) + .then(() => decodedToken); + }); + } + + private safeDecode(jwtToken: string): Promise { + return decodeJwt(jwtToken) + .catch(() => { + const errorMessage = 'Decoding App Check token failed. Make sure you passed ' + + 'the entire string JWT which represents the Firebase App Check token.'; + throw new FirebaseAppCheckError('invalid-argument', errorMessage); + }); + } + + /** + * Verifies the content of a Firebase App Check JWT. + * + * @param fullDecodedToken The decoded JWT. + * @param projectId The Firebase Project Id. + */ + private verifyContent(fullDecodedToken: DecodedToken, projectId: string | null): void { + const header = fullDecodedToken.header; + const payload = fullDecodedToken.payload; + + const projectIdMatchMessage = ' Make sure the App Check token comes from the same ' + + 'Firebase project as the service account used to authenticate this SDK.'; + const scopedProjectId = `projects/${projectId}`; + + let errorMessage: string | undefined; + if (header.alg !== ALGORITHM_RS256) { + errorMessage = 'The provided App Check token has incorrect algorithm. Expected "' + + ALGORITHM_RS256 + '" but got ' + '"' + header.alg + '".'; + } else if (!validator.isNonEmptyArray(payload.aud) || !payload.aud.includes(scopedProjectId)) { + errorMessage = 'The provided App Check token has incorrect "aud" (audience) claim. Expected "' + + scopedProjectId + '" but got "' + payload.aud + '".' + projectIdMatchMessage; + } else if (typeof payload.iss !== 'string' || !payload.iss.startsWith(APP_CHECK_ISSUER)) { + errorMessage = 'The provided App Check token has incorrect "iss" (issuer) claim.'; + } else if (typeof payload.sub !== 'string') { + errorMessage = 'The provided App Check token has no "sub" (subject) claim.'; + } else if (payload.sub === '') { + errorMessage = 'The provided App Check token has an empty string "sub" (subject) claim.'; + } + if (errorMessage) { + throw new FirebaseAppCheckError('invalid-argument', errorMessage); + } + } + + private verifySignature(jwtToken: string): + Promise { + return this.signatureVerifier.verify(jwtToken) + .catch((error: JwtError) => { + throw this.mapJwtErrorToAppCheckError(error); + }); + } + + /** + * Maps JwtError to FirebaseAppCheckError + * + * @param error JwtError to be mapped. + * @returns FirebaseAppCheckError instance. + */ + private mapJwtErrorToAppCheckError(error: JwtError): FirebaseAppCheckError { + if (error.code === JwtErrorCode.TOKEN_EXPIRED) { + const errorMessage = 'The provided App Check token has expired. Get a fresh App Check token' + + ' from your client app and try again.' + return new FirebaseAppCheckError('app-check-token-expired', errorMessage); + } else if (error.code === JwtErrorCode.INVALID_SIGNATURE) { + const errorMessage = 'The provided App Check token has invalid signature.'; + return new FirebaseAppCheckError('invalid-argument', errorMessage); + } else if (error.code === JwtErrorCode.NO_MATCHING_KID) { + const errorMessage = 'The provided App Check token has "kid" claim which does not ' + + 'correspond to a known public key. Most likely the provided App Check token ' + + 'is expired, so get a fresh token from your client app and try again.'; + return new FirebaseAppCheckError('invalid-argument', errorMessage); + } + return new FirebaseAppCheckError('invalid-argument', error.message); + } +} diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 62519fe9fe..68cfe47adb 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -24,6 +24,7 @@ import { FirebaseNamespaceInternals } from './firebase-namespace'; import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { Auth } from '../auth/index'; +import { AppCheck } from '../app-check/app-check'; import { MachineLearning } from '../machine-learning/index'; import { Messaging } from '../messaging/index'; import { Storage } from '../storage/index'; @@ -69,6 +70,10 @@ export class FirebaseAppInternals { return Promise.resolve(this.cachedToken_); } + public getCachedToken(): FirebaseAccessToken | null { + return this.cachedToken_ || null; + } + private refreshToken(): Promise { return Promise.resolve(this.credential_.getAccessToken()) .then((result) => { @@ -92,6 +97,8 @@ export class FirebaseAppInternals { if (!this.cachedToken_ || this.cachedToken_.accessToken !== token.accessToken || this.cachedToken_.expirationTime !== token.expirationTime) { + // Update the cache before firing listeners. Listeners may directly query the + // cached token state. this.cachedToken_ = token; this.tokenListeners_.forEach((listener) => { listener(token.accessToken); @@ -189,6 +196,18 @@ export class FirebaseApp implements app.App { this.INTERNAL = new FirebaseAppInternals(credential); } + /** + * Returns the AppCheck service instance associated with this app. + * + * @returns The AppCheck service instance of this app. + */ + public appCheck(): AppCheck { + return this.ensureService_('appCheck', () => { + const appCheckService: typeof AppCheck = require('../app-check/app-check').AppCheck; + return new appCheckService(this); + }); + } + /** * Returns the Auth service instance associated with this app. * diff --git a/src/app/firebase-namespace.ts b/src/app/firebase-namespace.ts index c8af35247d..18b870dd4e 100644 --- a/src/app/firebase-namespace.ts +++ b/src/app/firebase-namespace.ts @@ -19,7 +19,7 @@ import fs = require('fs'); import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { - app, auth, messaging, machineLearning, storage, firestore, database, + app, appCheck, auth, messaging, machineLearning, storage, firestore, database, instanceId, projectManagement, securityRules , remoteConfig, AppOptions, } from '../firebase-namespace-api'; import { FirebaseApp } from './firebase-app'; @@ -30,6 +30,7 @@ import * as validator from '../utils/validator'; import { getSdkVersion } from '../utils/index'; import App = app.App; +import AppCheck = appCheck.AppCheck; import Auth = auth.Auth; import Database = database.Database; import Firestore = firestore.Firestore; @@ -349,6 +350,18 @@ export class FirebaseNamespace { return Object.assign(fn, { RemoteConfig: remoteConfig }); } + /** + * Gets the `AppCheck` service namespace. The returned namespace can be used to get the + * `AppCheck` service for the default app or an explicitly specified app. + */ + get appCheck(): FirebaseServiceNamespace { + const fn: FirebaseServiceNamespace = (app?: App) => { + return this.ensureApp(app).appCheck(); + }; + const appCheck = require('../app-check/app-check').AppCheck; + return Object.assign(fn, { AppCheck: appCheck }); + } + // TODO: Change the return types to app.App in the following methods. /** diff --git a/src/auth/auth-api-request.ts b/src/auth/auth-api-request.ts index 69ac909b4e..e331b648cc 100644 --- a/src/auth/auth-api-request.ts +++ b/src/auth/auth-api-request.ts @@ -244,9 +244,8 @@ class AuthHttpClient extends AuthorizedHttpClient { * an error is thrown. * * @param request The AuthFactorInfo request object. - * @param writeOperationType The write operation type. */ -function validateAuthFactorInfo(request: AuthFactorInfo, writeOperationType: WriteOperationType): void { +function validateAuthFactorInfo(request: AuthFactorInfo): void { const validKeys = { mfaEnrollmentId: true, displayName: true, @@ -262,8 +261,8 @@ function validateAuthFactorInfo(request: AuthFactorInfo, writeOperationType: Wri // No enrollment ID is available for signupNewUser. Use another identifier. const authFactorInfoIdentifier = request.mfaEnrollmentId || request.phoneInfo || JSON.stringify(request); - const uidRequired = writeOperationType !== WriteOperationType.Create; - if ((typeof request.mfaEnrollmentId !== 'undefined' || uidRequired) && + // Enrollment uid may or may not be specified for update operations. + if (typeof request.mfaEnrollmentId !== 'undefined' && !validator.isNonEmptyString(request.mfaEnrollmentId)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_UID, @@ -560,7 +559,7 @@ function validateCreateEditRequest(request: any, writeOperationType: WriteOperat throw new FirebaseAuthError(AuthClientErrorCode.INVALID_ENROLLED_FACTORS); } enrollments.forEach((authFactorInfoEntry: AuthFactorInfo) => { - validateAuthFactorInfo(authFactorInfoEntry, writeOperationType); + validateAuthFactorInfo(authFactorInfoEntry); }); } } @@ -1541,7 +1540,12 @@ export abstract class AbstractAuthRequestHandler { } // Build the signupNewUser request. - const request: any = deepCopy(properties); + type SignUpNewUserRequest = CreateRequest & { + photoUrl?: string | null; + localId?: string; + mfaInfo?: AuthFactorInfo[]; + }; + const request: SignUpNewUserRequest = deepCopy(properties); // Rewrite photoURL to photoUrl. if (typeof request.photoURL !== 'undefined') { request.photoUrl = request.photoURL; @@ -1557,14 +1561,14 @@ export abstract class AbstractAuthRequestHandler { if (validator.isNonEmptyArray(request.multiFactor.enrolledFactors)) { const mfaInfo: AuthFactorInfo[] = []; try { - request.multiFactor.enrolledFactors.forEach((multiFactorInfo: any) => { + request.multiFactor.enrolledFactors.forEach((multiFactorInfo) => { // Enrollment time and uid are not allowed for signupNewUser endpoint. // They will automatically be provisioned server side. - if (multiFactorInfo.enrollmentTime) { + if ('enrollmentTime' in multiFactorInfo) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, '"enrollmentTime" is not supported when adding second factors via "createUser()"'); - } else if (multiFactorInfo.uid) { + } else if ('uid' in multiFactorInfo) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, '"uid" is not supported when adding second factors via "createUser()"'); diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 0c1fc2db36..c833449532 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -19,10 +19,10 @@ import { deepCopy } from '../utils/deep-copy'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; /** - * Interface representing base properties of a user enrolled second factor for a + * Interface representing base properties of a user-enrolled second factor for a * `CreateRequest`. */ -export interface CreateMultiFactorInfoRequest { +export interface BaseCreateMultiFactorInfoRequest { /** * The optional display name for an enrolled second factor. @@ -36,10 +36,10 @@ export interface CreateMultiFactorInfoRequest { } /** - * Interface representing a phone specific user enrolled second factor for a + * Interface representing a phone specific user-enrolled second factor for a * `CreateRequest`. */ -export interface CreatePhoneMultiFactorInfoRequest extends CreateMultiFactorInfoRequest { +export interface CreatePhoneMultiFactorInfoRequest extends BaseCreateMultiFactorInfoRequest { /** * The phone number associated with a phone second factor. @@ -48,10 +48,16 @@ export interface CreatePhoneMultiFactorInfoRequest extends CreateMultiFactorInfo } /** - * Interface representing common properties of a user enrolled second factor + * Type representing the properties of a user-enrolled second factor + * for a `CreateRequest`. + */ +export type CreateMultiFactorInfoRequest = | CreatePhoneMultiFactorInfoRequest; + +/** + * Interface representing common properties of a user-enrolled second factor * for an `UpdateRequest`. */ -export interface UpdateMultiFactorInfoRequest { +export interface BaseUpdateMultiFactorInfoRequest { /** * The ID of the enrolled second factor. This ID is unique to the user. When not provided, @@ -76,10 +82,10 @@ export interface UpdateMultiFactorInfoRequest { } /** - * Interface representing a phone specific user enrolled second factor + * Interface representing a phone specific user-enrolled second factor * for an `UpdateRequest`. */ -export interface UpdatePhoneMultiFactorInfoRequest extends UpdateMultiFactorInfoRequest { +export interface UpdatePhoneMultiFactorInfoRequest extends BaseUpdateMultiFactorInfoRequest { /** * The phone number associated with a phone second factor. @@ -87,6 +93,12 @@ export interface UpdatePhoneMultiFactorInfoRequest extends UpdateMultiFactorInfo phoneNumber: string; } +/** + * Type representing the properties of a user-enrolled second factor + * for an `UpdateRequest`. + */ +export type UpdateMultiFactorInfoRequest = | UpdatePhoneMultiFactorInfoRequest; + /** * The multi-factor related user settings for create operations. */ @@ -357,6 +369,17 @@ export interface OIDCUpdateAuthProviderRequest { * configuration's value is not modified. */ issuer?: string; + + /** + * The OIDC provider's client secret to enable OIDC code flow. + * If not provided, the existing configuration's value is not modified. + */ + clientSecret?: string; + + /** + * The OIDC provider's response object for OAuth authorization flow. + */ + responseType?: OAuthResponseType; } export type UpdateAuthProviderRequest = @@ -411,6 +434,8 @@ export interface OIDCConfigServerRequest { issuer?: string; displayName?: string; enabled?: boolean; + clientSecret?: string; + responseType?: OAuthResponseType; [key: string]: any; } @@ -423,6 +448,8 @@ export interface OIDCConfigServerResponse { issuer?: string; displayName?: string; enabled?: boolean; + clientSecret?: string; + responseType?: OAuthResponseType; } /** The server side email configuration request interface. */ @@ -764,7 +791,7 @@ export class EmailSignInConfig implements EmailSignInProviderConfig { /** * The base Auth provider configuration interface. */ -export interface AuthProviderConfig { +export interface BaseAuthProviderConfig { /** * The provider ID defined by the developer. @@ -792,7 +819,7 @@ export interface AuthProviderConfig { * Auth provider configuration interface. A SAML provider can be created via * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. */ -export interface SAMLAuthProviderConfig extends AuthProviderConfig { +export interface SAMLAuthProviderConfig extends BaseAuthProviderConfig { /** * The SAML IdP entity identifier. @@ -832,12 +859,33 @@ export interface SAMLAuthProviderConfig extends AuthProviderConfig { callbackURL?: string; } +/** + * The interface representing OIDC provider's response object for OAuth + * authorization flow. + * One of the following settings is required: + *
      + *
    • Set code to true for the code flow.
    • + *
    • Set idToken to true for the ID token flow.
    • + *
    + */ +export interface OAuthResponseType { + /** + * Whether ID token is returned from IdP's authorization endpoint. + */ + idToken?: boolean; + + /** + * Whether authorization code is returned from IdP's authorization endpoint. + */ + code?: boolean; +} + /** * The [OIDC](https://openid.net/specs/openid-connect-core-1_0-final.html) Auth * provider configuration interface. An OIDC provider can be created via * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. */ -export interface OIDCAuthProviderConfig extends AuthProviderConfig { +export interface OIDCAuthProviderConfig extends BaseAuthProviderConfig { /** * This is the required client ID used to confirm the audience of an OIDC @@ -864,8 +912,23 @@ export interface OIDCAuthProviderConfig extends AuthProviderConfig { * [spec](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation). */ issuer: string; + + /** + * The OIDC provider's client secret to enable OIDC code flow. + */ + clientSecret?: string; + + /** + * The OIDC provider's response object for OAuth authorization flow. + */ + responseType?: OAuthResponseType; } +/** + * The Auth provider configuration type. + * {@link auth.Auth.createProviderConfig `createProviderConfig()`}. + */ +export type AuthProviderConfig = SAMLAuthProviderConfig | OIDCAuthProviderConfig; /** * Defines the SAMLConfig class used to convert a client side configuration to its @@ -1145,6 +1208,8 @@ export class OIDCConfig implements OIDCAuthProviderConfig { public readonly providerId: string; public readonly issuer: string; public readonly clientId: string; + public readonly clientSecret?: string; + public readonly responseType: OAuthResponseType; /** * Converts a client side request to a OIDCConfigServerRequest which is the format @@ -1171,6 +1236,12 @@ export class OIDCConfig implements OIDCAuthProviderConfig { request.displayName = options.displayName; request.issuer = options.issuer; request.clientId = options.clientId; + if (typeof options.clientSecret !== 'undefined') { + request.clientSecret = options.clientSecret; + } + if (typeof options.responseType !== 'undefined') { + request.responseType = options.responseType; + } return request; } @@ -1210,6 +1281,12 @@ export class OIDCConfig implements OIDCAuthProviderConfig { providerId: true, clientId: true, issuer: true, + clientSecret: true, + responseType: true, + }; + const validResponseTypes = { + idToken: true, + code: true, }; if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( @@ -1268,6 +1345,59 @@ export class OIDCConfig implements OIDCAuthProviderConfig { '"OIDCAuthProviderConfig.displayName" must be a valid string.', ); } + if (typeof options.clientSecret !== 'undefined' && + !validator.isNonEmptyString(options.clientSecret)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + '"OIDCAuthProviderConfig.clientSecret" must be a valid string.', + ); + } + if (validator.isNonNullObject(options.responseType) && typeof options.responseType !== 'undefined') { + Object.keys(options.responseType).forEach((key) => { + if (!(key in validResponseTypes)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + `"${key}" is not a valid OAuthResponseType parameter.`, + ); + } + }); + + const idToken = options.responseType.idToken; + if (typeof idToken !== 'undefined' && !validator.isBoolean(idToken)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + '"OIDCAuthProviderConfig.responseType.idToken" must be a boolean.', + ); + } + + const code = options.responseType.code; + if (typeof code !== 'undefined') { + if (!validator.isBoolean(code)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + '"OIDCAuthProviderConfig.responseType.code" must be a boolean.', + ); + } + + // If code flow is enabled, client secret must be provided. + if (code && typeof options.clientSecret === 'undefined') { + throw new FirebaseAuthError( + AuthClientErrorCode.MISSING_OAUTH_CLIENT_SECRET, + 'The OAuth configuration client secret is required to enable OIDC code flow.', + ); + } + } + + const allKeys = Object.keys(options.responseType).length; + const enabledCount = Object.values(options.responseType).filter(Boolean).length; + // Only one of OAuth response types can be set to true. + if (allKeys > 1 && enabledCount != 1) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_OAUTH_RESPONSETYPE, + 'Only exactly one OAuth responseType should be set to true.', + ); + } + } } /** @@ -1301,6 +1431,13 @@ export class OIDCConfig implements OIDCAuthProviderConfig { // When enabled is undefined, it takes its default value of false. this.enabled = !!response.enabled; this.displayName = response.displayName; + + if (typeof response.clientSecret !== 'undefined') { + this.clientSecret = response.clientSecret; + } + if (typeof response.responseType !== 'undefined') { + this.responseType = response.responseType; + } } /** @returns The plain object representation of the OIDCConfig. */ @@ -1311,6 +1448,8 @@ export class OIDCConfig implements OIDCAuthProviderConfig { providerId: this.providerId, issuer: this.issuer, clientId: this.clientId, + clientSecret: deepCopy(this.clientSecret), + responseType: deepCopy(this.responseType), }; } } diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts index 9f155b607c..da3ed253c8 100644 --- a/src/auth/base-auth.ts +++ b/src/auth/base-auth.ts @@ -20,7 +20,7 @@ import { deepCopy } from '../utils/deep-copy'; import * as validator from '../utils/validator'; import { AbstractAuthRequestHandler, useEmulator } from './auth-api-request'; -import { FirebaseTokenGenerator, EmulatedSigner, cryptoSignerFromApp } from './token-generator'; +import { FirebaseTokenGenerator, EmulatedSigner } from './token-generator'; import { FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier, DecodedIdToken, @@ -36,6 +36,8 @@ import { } from './identifier'; import { UserImportOptions, UserImportRecord, UserImportResult } from './user-import-builder'; import { ActionCodeSettings } from './action-code-settings-builder'; +import { cryptoSignerFromApp } from '../utils/crypto-signer'; +import { FirebaseApp } from '../app/firebase-app'; /** Represents the result of the {@link BaseAuth.getUsers} API. */ export interface GetUsersResult { @@ -137,7 +139,7 @@ export abstract class BaseAuth { if (tokenGenerator) { this.tokenGenerator = tokenGenerator; } else { - const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); + const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app as FirebaseApp); this.tokenGenerator = new FirebaseTokenGenerator(cryptoSigner); } diff --git a/src/auth/index.ts b/src/auth/index.ts index afbbe780ca..b189e1ac56 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -64,6 +64,9 @@ export { AuthFactorType, AuthProviderConfig, AuthProviderConfigFilter, + BaseAuthProviderConfig, + BaseCreateMultiFactorInfoRequest, + BaseUpdateMultiFactorInfoRequest, CreateMultiFactorInfoRequest, CreatePhoneMultiFactorInfoRequest, CreateRequest, @@ -73,6 +76,7 @@ export { MultiFactorConfigState, MultiFactorCreateSettings, MultiFactorUpdateSettings, + OAuthResponseType, OIDCAuthProviderConfig, OIDCUpdateAuthProviderRequest, SAMLAuthProviderConfig, diff --git a/src/auth/tenant-manager.ts b/src/auth/tenant-manager.ts index d3992fc2a1..37d5f34b31 100644 --- a/src/auth/tenant-manager.ts +++ b/src/auth/tenant-manager.ts @@ -21,11 +21,13 @@ import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; import { BaseAuth, SessionCookieOptions } from './base-auth'; import { Tenant, TenantServerResponse, CreateTenantRequest, UpdateTenantRequest } from './tenant'; -import { FirebaseTokenGenerator, EmulatedSigner, cryptoSignerFromApp } from './token-generator'; +import { FirebaseTokenGenerator, EmulatedSigner } from './token-generator'; import { AuthRequestHandler, TenantAwareAuthRequestHandler, useEmulator, } from './auth-api-request'; import { DecodedIdToken } from './token-verifier'; +import { FirebaseApp } from '../app/firebase-app'; +import { cryptoSignerFromApp } from '../utils/crypto-signer'; /** * Interface representing the object returned from a @@ -81,7 +83,7 @@ export class TenantAwareAuth extends BaseAuth { * @internal */ constructor(app: App, tenantId: string) { - const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); + const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app as FirebaseApp); const tokenGenerator = new FirebaseTokenGenerator(cryptoSigner, tenantId); super(app, new TenantAwareAuthRequestHandler(app, tenantId), tokenGenerator); utils.addReadonlyGetter(this, 'tenantId', tenantId); diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index d8e6e1962d..c2e5b8ab90 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -15,18 +15,14 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; -import { ServiceAccountCredential } from '../app/credential-internal'; -import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; -import { AuthorizedHttpClient, HttpError, HttpRequestConfig, HttpClient } from '../utils/api-request'; +import { AuthClientErrorCode, ErrorInfo, FirebaseAuthError } from '../utils/error'; +import { HttpError } from '../utils/api-request'; +import { CryptoSigner, CryptoSignerError, CryptoSignerErrorCode } from '../utils/crypto-signer'; import * as validator from '../utils/validator'; import { toWebSafeBase64 } from '../utils'; import { Algorithm } from 'jsonwebtoken'; -import { App } from '../app'; - -const ALGORITHM_RS256: Algorithm = 'RS256' as const; const ALGORITHM_NONE: Algorithm = 'none' as const; const ONE_HOUR_IN_SECONDS = 60 * 60; @@ -40,32 +36,6 @@ export const BLACKLISTED_CLAIMS = [ // Audience to use for Firebase Auth Custom tokens const FIREBASE_AUDIENCE = 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit'; -/** - * CryptoSigner interface represents an object that can be used to sign JWTs. - */ -export interface CryptoSigner { - - /** - * The name of the signing algorithm. - */ - readonly algorithm: Algorithm; - - /** - * Cryptographically signs a buffer of data. - * - * @param {Buffer} buffer The data to be signed. - * @returns {Promise} A promise that resolves with the raw bytes of a signature. - */ - sign(buffer: Buffer): Promise; - - /** - * Returns the ID of the service account used to sign tokens. - * - * @returns {Promise} A promise that resolves with a service account ID. - */ - getAccountId(): Promise; -} - /** * Represents the header of a JWT. */ @@ -88,148 +58,6 @@ interface JWTBody { tenant_id?: string; } -/** - * A CryptoSigner implementation that uses an explicitly specified service account private key to - * sign data. Performs all operations locally, and does not make any RPC calls. - */ -export class ServiceAccountSigner implements CryptoSigner { - - algorithm = ALGORITHM_RS256; - - /** - * Creates a new CryptoSigner instance from the given service account credential. - * - * @param {ServiceAccountCredential} credential A service account credential. - */ - constructor(private readonly credential: ServiceAccountCredential) { - if (!credential) { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CREDENTIAL, - 'INTERNAL ASSERT: Must provide a service account credential to initialize ServiceAccountSigner.', - ); - } - } - - /** - * @inheritDoc - */ - public sign(buffer: Buffer): Promise { - const crypto = require('crypto'); // eslint-disable-line @typescript-eslint/no-var-requires - const sign = crypto.createSign('RSA-SHA256'); - sign.update(buffer); - return Promise.resolve(sign.sign(this.credential.privateKey)); - } - - /** - * @inheritDoc - */ - public getAccountId(): Promise { - return Promise.resolve(this.credential.clientEmail); - } -} - -/** - * A CryptoSigner implementation that uses the remote IAM service to sign data. If initialized without - * a service account ID, attempts to discover a service account ID by consulting the local Metadata - * service. This will succeed in managed environments like Google Cloud Functions and App Engine. - * - * @see https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signBlob - * @see https://cloud.google.com/compute/docs/storing-retrieving-metadata - */ -export class IAMSigner implements CryptoSigner { - algorithm = ALGORITHM_RS256; - - private readonly httpClient: AuthorizedHttpClient; - private serviceAccountId?: string; - - constructor(httpClient: AuthorizedHttpClient, serviceAccountId?: string) { - if (!httpClient) { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - 'INTERNAL ASSERT: Must provide a HTTP client to initialize IAMSigner.', - ); - } - if (typeof serviceAccountId !== 'undefined' && !validator.isNonEmptyString(serviceAccountId)) { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_ARGUMENT, - 'INTERNAL ASSERT: Service account ID must be undefined or a non-empty string.', - ); - } - this.httpClient = httpClient; - this.serviceAccountId = serviceAccountId; - } - - /** - * @inheritDoc - */ - public sign(buffer: Buffer): Promise { - return this.getAccountId().then((serviceAccount) => { - const request: HttpRequestConfig = { - method: 'POST', - url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccount}:signBlob`, - data: { payload: buffer.toString('base64') }, - }; - return this.httpClient.send(request); - }).then((response: any) => { - // Response from IAM is base64 encoded. Decode it into a buffer and return. - return Buffer.from(response.data.signedBlob, 'base64'); - }).catch((err) => { - if (err instanceof HttpError) { - const error = err.response.data; - if (validator.isNonNullObject(error) && error.error) { - const errorCode = error.error.status; - const description = 'Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens ' + - 'for more details on how to use and troubleshoot this feature.'; - const errorMsg = `${error.error.message}; ${description}`; - - throw FirebaseAuthError.fromServerError(errorCode, errorMsg, error); - } - throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, - 'Error returned from server: ' + error + '. Additionally, an ' + - 'internal error occurred while attempting to extract the ' + - 'errorcode from the error.', - ); - } - throw err; - }); - } - - /** - * @inheritDoc - */ - public getAccountId(): Promise { - if (validator.isNonEmptyString(this.serviceAccountId)) { - return Promise.resolve(this.serviceAccountId); - } - const request: HttpRequestConfig = { - method: 'GET', - url: 'http://metadata/computeMetadata/v1/instance/service-accounts/default/email', - headers: { - 'Metadata-Flavor': 'Google', - }, - }; - const client = new HttpClient(); - return client.send(request).then((response) => { - if (!response.text) { - throw new FirebaseAuthError( - AuthClientErrorCode.INTERNAL_ERROR, - 'HTTP Response missing payload', - ); - } - this.serviceAccountId = response.text; - return response.text; - }).catch((err) => { - throw new FirebaseAuthError( - AuthClientErrorCode.INVALID_CREDENTIAL, - 'Failed to determine service account. Make sure to initialize ' + - 'the SDK with a service account credential. Alternatively specify a service ' + - `account with iam.serviceAccounts.signBlob permission. Original error: ${err}`, - ); - }); - } -} - /** * A CryptoSigner implementation that is used when communicating with the Auth emulator. * It produces unsigned tokens. @@ -254,22 +82,6 @@ export class EmulatedSigner implements CryptoSigner { } } -/** - * Create a new CryptoSigner instance for the given app. If the app has been initialized with a service - * account credential, creates a ServiceAccountSigner. Otherwise creates an IAMSigner. - * - * @param {FirebaseApp} app A FirebaseApp instance. - * @returns {CryptoSigner} A CryptoSigner instance. - */ -export function cryptoSignerFromApp(app: App): CryptoSigner { - const credential = app.options.credential; - if (credential instanceof ServiceAccountCredential) { - return new ServiceAccountSigner(credential); - } - - return new IAMSigner(new AuthorizedHttpClient(app as FirebaseApp), app.options.serviceAccountId); -} - /** * Class for generating different types of Firebase Auth tokens (JWTs). */ @@ -362,6 +174,8 @@ export class FirebaseTokenGenerator { return Promise.all([token, signPromise]); }).then(([token, signature]) => { return `${token}.${this.encodeSegment(signature)}`; + }).catch((err) => { + throw handleCryptoSignerError(err); }); } @@ -384,3 +198,44 @@ export class FirebaseTokenGenerator { } } +/** + * Creates a new FirebaseAuthError by extracting the error code, message and other relevant + * details from a CryptoSignerError. + * + * @param {Error} err The Error to convert into a FirebaseAuthError error + * @return {FirebaseAuthError} A Firebase Auth error that can be returned to the user. + */ +export function handleCryptoSignerError(err: Error): Error { + if (!(err instanceof CryptoSignerError)) { + return err; + } + if (err.code === CryptoSignerErrorCode.SERVER_ERROR && validator.isNonNullObject(err.cause)) { + const httpError = err.cause; + const errorResponse = (httpError as HttpError).response.data; + if (validator.isNonNullObject(errorResponse) && errorResponse.error) { + const errorCode = errorResponse.error.status; + const description = 'Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens ' + + 'for more details on how to use and troubleshoot this feature.'; + const errorMsg = `${errorResponse.error.message}; ${description}`; + + return FirebaseAuthError.fromServerError(errorCode, errorMsg, errorResponse); + } + return new FirebaseAuthError(AuthClientErrorCode.INTERNAL_ERROR, + 'Error returned from server: ' + errorResponse + '. Additionally, an ' + + 'internal error occurred while attempting to extract the ' + + 'errorcode from the error.' + ); + } + return new FirebaseAuthError(mapToAuthClientErrorCode(err.code), err.message); +} + +function mapToAuthClientErrorCode(code: string): ErrorInfo { + switch (code) { + case CryptoSignerErrorCode.INVALID_CREDENTIAL: + return AuthClientErrorCode.INVALID_CREDENTIAL; + case CryptoSignerErrorCode.INVALID_ARGUMENT: + return AuthClientErrorCode.INVALID_ARGUMENT; + default: + return AuthClientErrorCode.INTERNAL_ERROR; + } +} diff --git a/src/auth/user-record.ts b/src/auth/user-record.ts index 49c21b4768..7b49618be5 100644 --- a/src/auth/user-record.ts +++ b/src/auth/user-record.ts @@ -87,7 +87,7 @@ enum MultiFactorId { } /** - * Interface representing the common properties of a user enrolled second factor. + * Interface representing the common properties of a user-enrolled second factor. */ export abstract class MultiFactorInfo { @@ -194,7 +194,7 @@ export abstract class MultiFactorInfo { } /** - * Interface representing a phone specific user enrolled second factor. + * Interface representing a phone specific user-enrolled second factor. */ export class PhoneMultiFactorInfo extends MultiFactorInfo { diff --git a/src/database/database.ts b/src/database/database.ts index 5cd93827a9..c7b731c134 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -81,7 +81,7 @@ export class DatabaseService { this.appInternal = app; } - private get firebsaeApp(): FirebaseApp { + private get firebaseApp(): FirebaseApp { return this.app as FirebaseApp; } @@ -90,7 +90,7 @@ export class DatabaseService { */ public delete(): Promise { if (this.tokenListener) { - this.firebsaeApp.INTERNAL.removeAuthTokenListener(this.tokenListener); + this.firebaseApp.INTERNAL.removeAuthTokenListener(this.tokenListener); clearTimeout(this.tokenRefreshTimeout); } @@ -143,7 +143,7 @@ export class DatabaseService { if (!this.tokenListener) { this.tokenListener = this.onTokenChange.bind(this); - this.firebsaeApp.INTERNAL.addAuthTokenListener(this.tokenListener); + this.firebaseApp.INTERNAL.addAuthTokenListener(this.tokenListener); } return db; @@ -151,24 +151,21 @@ export class DatabaseService { // eslint-disable-next-line @typescript-eslint/no-unused-vars private onTokenChange(_: string): void { - this.firebsaeApp.INTERNAL.getToken() - .then((token) => { - const delayMillis = token.expirationTime - TOKEN_REFRESH_THRESHOLD_MILLIS - Date.now(); - // If the new token is set to expire soon (unlikely), do nothing. Somebody will eventually - // notice and refresh the token, at which point this callback will fire again. - if (delayMillis > 0) { - this.scheduleTokenRefresh(delayMillis); - } - }) - .catch((err) => { - console.error('Unexpected error while attempting to schedule a token refresh:', err); - }); + const token = this.firebaseApp.INTERNAL.getCachedToken(); + if (token) { + const delayMillis = token.expirationTime - TOKEN_REFRESH_THRESHOLD_MILLIS - Date.now(); + // If the new token is set to expire soon (unlikely), do nothing. Somebody will eventually + // notice and refresh the token, at which point this callback will fire again. + if (delayMillis > 0) { + this.scheduleTokenRefresh(delayMillis); + } + } } private scheduleTokenRefresh(delayMillis: number): void { clearTimeout(this.tokenRefreshTimeout); this.tokenRefreshTimeout = setTimeout(() => { - this.firebsaeApp.INTERNAL.getToken(/*forceRefresh=*/ true) + this.firebaseApp.INTERNAL.getToken(/*forceRefresh=*/ true) .catch(() => { // Ignore the error since this might just be an intermittent failure. If we really cannot // refresh the token, an error will be logged once the existing token expires and we try diff --git a/src/firebase-namespace-api.ts b/src/firebase-namespace-api.ts index cb685e3893..d75fb9a17e 100644 --- a/src/firebase-namespace-api.ts +++ b/src/firebase-namespace-api.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { appCheck } from './app-check/index'; import { auth } from './auth/auth-namespace'; import { database } from './database/database-namespace'; import { firestore } from './firestore/firestore-namespace'; @@ -42,6 +43,7 @@ export namespace app { * to create an app. */ export interface App extends AppCore { + appCheck(): appCheck.AppCheck; auth(): auth.Auth; database(url?: string): database.Database; firestore(): firestore.Firestore; @@ -76,6 +78,7 @@ export namespace app { export * from './credential/index'; export { auth } from './auth/auth-namespace'; +export { appCheck } from './app-check/index'; export { database } from './database/database-namespace'; export { firestore } from './firestore/firestore-namespace'; export { instanceId } from './instance-id/instance-id-namespace'; diff --git a/src/storage/storage.ts b/src/storage/storage.ts index 5aebb7c4b4..3c7994fb27 100644 --- a/src/storage/storage.ts +++ b/src/storage/storage.ts @@ -46,9 +46,18 @@ export class Storage { } if (!process.env.STORAGE_EMULATOR_HOST && process.env.FIREBASE_STORAGE_EMULATOR_HOST) { - process.env.STORAGE_EMULATOR_HOST = process.env.FIREBASE_STORAGE_EMULATOR_HOST; - } + const firebaseStorageEmulatorHost = process.env.FIREBASE_STORAGE_EMULATOR_HOST; + + if (firebaseStorageEmulatorHost.match(/https?:\/\//)) { + throw new FirebaseError({ + code: 'storage/invalid-emulator-host', + message: 'FIREBASE_STORAGE_EMULATOR_HOST should not contain a protocol (http or https).', + }); + } + process.env.STORAGE_EMULATOR_HOST = `http://${process.env.FIREBASE_STORAGE_EMULATOR_HOST}`; + } + let storage: typeof StorageClient; try { storage = require('@google-cloud/storage').Storage; diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts index dfad60b937..129f2f5c83 100644 --- a/src/utils/api-request.ts +++ b/src/utils/api-request.ts @@ -728,7 +728,7 @@ class HttpRequestConfigImpl implements HttpRequestConfig { public buildRequestOptions(): https.RequestOptions { const parsed = this.buildUrl(); const protocol = parsed.protocol; - let port: string | undefined = parsed.port; + let port: string | null = parsed.port; if (!port) { const isHttps = protocol === 'https:'; port = isHttps ? '443' : '80'; diff --git a/src/utils/crypto-signer.ts b/src/utils/crypto-signer.ts new file mode 100644 index 0000000000..569d5212f7 --- /dev/null +++ b/src/utils/crypto-signer.ts @@ -0,0 +1,250 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { FirebaseApp } from '../app/firebase-app'; +import { ServiceAccountCredential } from '../app/credential-internal'; +import { AuthorizedHttpClient, HttpRequestConfig, HttpClient, HttpError } from './api-request'; + +import { Algorithm } from 'jsonwebtoken'; +import { ErrorInfo } from '../utils/error'; +import * as validator from '../utils/validator'; + +const ALGORITHM_RS256: Algorithm = 'RS256' as const; + +/** + * CryptoSigner interface represents an object that can be used to sign JWTs. + */ +export interface CryptoSigner { + + /** + * The name of the signing algorithm. + */ + readonly algorithm: Algorithm; + + /** + * Cryptographically signs a buffer of data. + * + * @param buffer The data to be signed. + * @returns A promise that resolves with the raw bytes of a signature. + */ + sign(buffer: Buffer): Promise; + + /** + * Returns the ID of the service account used to sign tokens. + * + * @return {Promise} A promise that resolves with a service account ID. + */ + getAccountId(): Promise; +} + +/** + * A CryptoSigner implementation that uses an explicitly specified service account private key to + * sign data. Performs all operations locally, and does not make any RPC calls. + */ +export class ServiceAccountSigner implements CryptoSigner { + + algorithm = ALGORITHM_RS256; + + /** + * Creates a new CryptoSigner instance from the given service account credential. + * + * @param credential A service account credential. + */ + constructor(private readonly credential: ServiceAccountCredential) { + if (!credential) { + throw new CryptoSignerError({ + code: CryptoSignerErrorCode.INVALID_CREDENTIAL, + message: 'INTERNAL ASSERT: Must provide a service account credential to initialize ServiceAccountSigner.', + }); + } + } + + /** + * @inheritDoc + */ + public sign(buffer: Buffer): Promise { + const crypto = require('crypto'); // eslint-disable-line @typescript-eslint/no-var-requires + const sign = crypto.createSign('RSA-SHA256'); + sign.update(buffer); + return Promise.resolve(sign.sign(this.credential.privateKey)); + } + + /** + * @inheritDoc + */ + public getAccountId(): Promise { + return Promise.resolve(this.credential.clientEmail); + } +} + +/** + * A CryptoSigner implementation that uses the remote IAM service to sign data. If initialized without + * a service account ID, attempts to discover a service account ID by consulting the local Metadata + * service. This will succeed in managed environments like Google Cloud Functions and App Engine. + * + * @see https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts/signBlob + * @see https://cloud.google.com/compute/docs/storing-retrieving-metadata + */ +export class IAMSigner implements CryptoSigner { + algorithm = ALGORITHM_RS256; + + private readonly httpClient: AuthorizedHttpClient; + private serviceAccountId?: string; + + constructor(httpClient: AuthorizedHttpClient, serviceAccountId?: string) { + if (!httpClient) { + throw new CryptoSignerError({ + code: CryptoSignerErrorCode.INVALID_ARGUMENT, + message: 'INTERNAL ASSERT: Must provide a HTTP client to initialize IAMSigner.', + }); + } + if (typeof serviceAccountId !== 'undefined' && !validator.isNonEmptyString(serviceAccountId)) { + throw new CryptoSignerError({ + code: CryptoSignerErrorCode.INVALID_ARGUMENT, + message: 'INTERNAL ASSERT: Service account ID must be undefined or a non-empty string.', + }); + } + this.httpClient = httpClient; + this.serviceAccountId = serviceAccountId; + } + + /** + * @inheritDoc + */ + public sign(buffer: Buffer): Promise { + return this.getAccountId().then((serviceAccount) => { + const request: HttpRequestConfig = { + method: 'POST', + url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${serviceAccount}:signBlob`, + data: { payload: buffer.toString('base64') }, + }; + return this.httpClient.send(request); + }).then((response: any) => { + // Response from IAM is base64 encoded. Decode it into a buffer and return. + return Buffer.from(response.data.signedBlob, 'base64'); + }).catch((err) => { + if (err instanceof HttpError) { + throw new CryptoSignerError({ + code: CryptoSignerErrorCode.SERVER_ERROR, + message: err.message, + cause: err + }); + } + throw err + }); + } + + /** + * @inheritDoc + */ + public getAccountId(): Promise { + if (validator.isNonEmptyString(this.serviceAccountId)) { + return Promise.resolve(this.serviceAccountId); + } + const request: HttpRequestConfig = { + method: 'GET', + url: 'http://metadata/computeMetadata/v1/instance/service-accounts/default/email', + headers: { + 'Metadata-Flavor': 'Google', + }, + }; + const client = new HttpClient(); + return client.send(request).then((response) => { + if (!response.text) { + throw new CryptoSignerError({ + code: CryptoSignerErrorCode.INTERNAL_ERROR, + message: 'HTTP Response missing payload', + }); + } + this.serviceAccountId = response.text; + return response.text; + }).catch((err) => { + throw new CryptoSignerError({ + code: CryptoSignerErrorCode.INVALID_CREDENTIAL, + message: 'Failed to determine service account. Make sure to initialize ' + + 'the SDK with a service account credential. Alternatively specify a service ' + + `account with iam.serviceAccounts.signBlob permission. Original error: ${err}`, + }); + }); + } +} + +/** + * Creates a new CryptoSigner instance for the given app. If the app has been initialized with a + * service account credential, creates a ServiceAccountSigner. + * + * @param app A FirebaseApp instance. + * @returns A CryptoSigner instance. + */ +export function cryptoSignerFromApp(app: FirebaseApp): CryptoSigner { + const credential = app.options.credential; + if (credential instanceof ServiceAccountCredential) { + return new ServiceAccountSigner(credential); + } + + return new IAMSigner(new AuthorizedHttpClient(app), app.options.serviceAccountId); +} + +/** + * Defines extended error info type. This includes a code, message string, and error data. + */ +export interface ExtendedErrorInfo extends ErrorInfo { + cause?: Error; +} + +/** + * CryptoSigner error code structure. + * + * @param errorInfo The error information (code and message). + * @constructor + */ +export class CryptoSignerError extends Error { + constructor(private errorInfo: ExtendedErrorInfo) { + super(errorInfo.message); + + /* tslint:disable:max-line-length */ + // Set the prototype explicitly. See the following link for more details: + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + /* tslint:enable:max-line-length */ + (this as any).__proto__ = CryptoSignerError.prototype; + } + + /** @return {string} The error code. */ + public get code(): string { + return this.errorInfo.code; + } + + /** @return {string} The error message. */ + public get message(): string { + return this.errorInfo.message; + } + + /** @return {object} The error data. */ + public get cause(): Error | undefined { + return this.errorInfo.cause; + } +} + +/** + * Crypto Signer error codes and their default messages. + */ +export class CryptoSignerErrorCode { + public static INVALID_ARGUMENT = 'invalid-argument'; + public static INTERNAL_ERROR = 'internal-error'; + public static INVALID_CREDENTIAL = 'invalid-credential'; + public static SERVER_ERROR = 'server-error'; +} diff --git a/src/utils/error.ts b/src/utils/error.ts index 2953d9148e..551203a128 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -374,6 +374,10 @@ export class AuthClientErrorCode { code: 'email-already-exists', message: 'The email address is already in use by another account.', }; + public static EMAIL_NOT_FOUND = { + code: 'email-not-found', + message: 'There is no user record corresponding to the provided email.', + }; public static FORBIDDEN_CLAIM = { code: 'reserved-claim', message: 'The specified developer claim is reserved and cannot be specified.', @@ -521,6 +525,10 @@ export class AuthClientErrorCode { code: 'invalid-provider-uid', message: 'The providerUid must be a valid provider uid string.', }; + public static INVALID_OAUTH_RESPONSETYPE = { + code: 'invalid-oauth-responsetype', + message: 'Only exactly one OAuth responseType should be set to true.', + }; public static INVALID_SESSION_COOKIE_DURATION = { code: 'invalid-session-cookie-duration', message: 'The session cookie duration must be a valid number in milliseconds ' + @@ -593,6 +601,10 @@ export class AuthClientErrorCode { code: 'missing-oauth-client-id', message: 'The OAuth/OIDC configuration client ID must not be empty.', }; + public static MISSING_OAUTH_CLIENT_SECRET = { + code: 'missing-oauth-client-secret', + message: 'The OAuth configuration client secret is required to enable OIDC code flow.', + }; public static MISSING_PROVIDER_ID = { code: 'missing-provider-id', message: 'A valid provider ID must be provided in the request.', @@ -854,6 +866,8 @@ const AUTH_SERVER_TO_CLIENT_CODE: ServerToClientCode = { DUPLICATE_MFA_ENROLLMENT_ID: 'SECOND_FACTOR_UID_ALREADY_EXISTS', // setAccountInfo email already exists. EMAIL_EXISTS: 'EMAIL_ALREADY_EXISTS', + // /accounts:sendOobCode for password reset when user is not found. + EMAIL_NOT_FOUND: 'EMAIL_NOT_FOUND', // Reserved claim name. FORBIDDEN_CLAIM: 'FORBIDDEN_CLAIM', // Invalid claims provided. diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts index 6ca56dabc6..b5ca551f7f 100644 --- a/src/utils/jwt.ts +++ b/src/utils/jwt.ts @@ -16,6 +16,7 @@ import * as validator from './validator'; import * as jwt from 'jsonwebtoken'; +import * as jwks from 'jwks-rsa'; import { HttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; import { Agent } from 'http'; @@ -28,6 +29,9 @@ export const ALGORITHM_RS256: jwt.Algorithm = 'RS256' as const; const JWT_CALLBACK_ERROR_PREFIX = 'error in secret or public key callback: '; const NO_MATCHING_KID_ERROR_MESSAGE = 'no-matching-kid-error'; +const NO_KID_IN_HEADER_ERROR_MESSAGE = 'no-kid-in-header-error'; + +const ONE_DAY_IN_SECONDS = 24 * 3600; export type Dictionary = { [key: string]: any } @@ -44,6 +48,51 @@ interface KeyFetcher { fetchPublicKeys(): Promise<{ [key: string]: string }>; } +export class JwksFetcher implements KeyFetcher { + private publicKeys: { [key: string]: string }; + private publicKeysExpireAt = 0; + private client: jwks.JwksClient; + + constructor(jwksUrl: string) { + if (!validator.isURL(jwksUrl)) { + throw new Error('The provided JWKS URL is not a valid URL.'); + } + + this.client = jwks({ + jwksUri: jwksUrl, + cache: false, // disable jwks-rsa LRU cache as the keys are always cached for 24 hours. + }); + } + + public fetchPublicKeys(): Promise<{ [key: string]: string }> { + if (this.shouldRefresh()) { + return this.refresh(); + } + return Promise.resolve(this.publicKeys); + } + + private shouldRefresh(): boolean { + return !this.publicKeys || this.publicKeysExpireAt <= Date.now(); + } + + private refresh(): Promise<{ [key: string]: string }> { + return this.client.getSigningKeys() + .then((signingKeys) => { + // reset expire at from previous set of keys. + this.publicKeysExpireAt = 0; + const newKeys = signingKeys.reduce((map: { [key: string]: string }, signingKey: jwks.SigningKey) => { + map[signingKey.kid] = signingKey.getPublicKey(); + return map; + }, {}); + this.publicKeysExpireAt = Date.now() + (ONE_DAY_IN_SECONDS * 1000); + this.publicKeys = newKeys; + return newKeys; + }).catch((err) => { + throw new Error(`Error fetching Json Web Keys: ${err.message}`); + }); + } +} + /** * Class to fetch public keys from a client certificates URL. */ @@ -128,7 +177,7 @@ export class UrlKeyFetcher implements KeyFetcher { } /** - * Class for verifing JWT signature with a public key. + * Class for verifying JWT signature with a public key. */ export class PublicKeySignatureVerifier implements SignatureVerifier { constructor(private keyFetcher: KeyFetcher) { @@ -141,18 +190,56 @@ export class PublicKeySignatureVerifier implements SignatureVerifier { return new PublicKeySignatureVerifier(new UrlKeyFetcher(clientCertUrl, httpAgent)); } + public static withJwksUrl(jwksUrl: string): PublicKeySignatureVerifier { + return new PublicKeySignatureVerifier(new JwksFetcher(jwksUrl)); + } + public verify(token: string): Promise { if (!validator.isString(token)) { return Promise.reject(new JwtError(JwtErrorCode.INVALID_ARGUMENT, 'The provided token must be a string.')); } - return verifyJwtSignature(token, getKeyCallback(this.keyFetcher), { algorithms: [ALGORITHM_RS256] }); + return verifyJwtSignature(token, getKeyCallback(this.keyFetcher), { algorithms: [ALGORITHM_RS256] }) + .catch((error: JwtError) => { + if (error.code === JwtErrorCode.NO_KID_IN_HEADER) { + // No kid in JWT header. Try with all the public keys. + return this.verifyWithoutKid(token); + } + throw error; + }); + } + + private verifyWithoutKid(token: string): Promise { + return this.keyFetcher.fetchPublicKeys() + .then(publicKeys => this.verifyWithAllKeys(token, publicKeys)); + } + + private verifyWithAllKeys(token: string, keys: { [key: string]: string }): Promise { + const promises: Promise[] = []; + Object.values(keys).forEach((key) => { + const result = verifyJwtSignature(token, key) + .then(() => true) + .catch((error) => { + if (error.code === JwtErrorCode.TOKEN_EXPIRED) { + throw error; + } + return false; + }) + promises.push(result); + }); + + return Promise.all(promises) + .then((result) => { + if (result.every((r) => r === false)) { + throw new JwtError(JwtErrorCode.INVALID_SIGNATURE, 'Invalid token signature.'); + } + }); } } /** - * Class for verifing unsigned (emulator) JWTs. + * Class for verifying unsigned (emulator) JWTs. */ export class EmulatorSignatureVerifier implements SignatureVerifier { public verify(token: string): Promise { @@ -169,6 +256,9 @@ export class EmulatorSignatureVerifier implements SignatureVerifier { */ function getKeyCallback(fetcher: KeyFetcher): jwt.GetPublicKeyOrSecret { return (header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) => { + if (!header.kid) { + callback(new Error(NO_KID_IN_HEADER_ERROR_MESSAGE)); + } const kid = header.kid || ''; fetcher.fetchPublicKeys().then((publicKeys) => { if (!Object.prototype.hasOwnProperty.call(publicKeys, kid)) { @@ -187,7 +277,7 @@ function getKeyCallback(fetcher: KeyFetcher): jwt.GetPublicKeyOrSecret { * Verifies the signature of a JWT using the provided secret or a function to fetch * the secret or public key. * - * @param token The JWT to be verfied. + * @param token The JWT to be verified. * @param secretOrPublicKey The secret or a function to fetch the secret or public key. * @param options JWT verification options. * @returns A Promise resolving for a token with a valid signature. @@ -212,8 +302,12 @@ export function verifyJwtSignature(token: string, secretOrPublicKey: jwt.Secret } else if (error.name === 'JsonWebTokenError') { if (error.message && error.message.includes(JWT_CALLBACK_ERROR_PREFIX)) { const message = error.message.split(JWT_CALLBACK_ERROR_PREFIX).pop() || 'Error fetching public keys.'; - const code = (message === NO_MATCHING_KID_ERROR_MESSAGE) ? JwtErrorCode.NO_MATCHING_KID : - JwtErrorCode.KEY_FETCH_ERROR; + let code = JwtErrorCode.KEY_FETCH_ERROR; + if (message === NO_MATCHING_KID_ERROR_MESSAGE) { + code = JwtErrorCode.NO_MATCHING_KID; + } else if (message === NO_KID_IN_HEADER_ERROR_MESSAGE) { + code = JwtErrorCode.NO_KID_IN_HEADER; + } return reject(new JwtError(code, message)); } } @@ -271,5 +365,6 @@ export enum JwtErrorCode { TOKEN_EXPIRED = 'token-expired', INVALID_SIGNATURE = 'invalid-token', NO_MATCHING_KID = 'no-matching-kid-error', + NO_KID_IN_HEADER = 'no-kid-error', KEY_FETCH_ERROR = 'key-fetch-error', } diff --git a/test/integration/app-check.spec.ts b/test/integration/app-check.spec.ts new file mode 100644 index 0000000000..32386f32bc --- /dev/null +++ b/test/integration/app-check.spec.ts @@ -0,0 +1,103 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as _ from 'lodash'; +import * as admin from '../../lib/index'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import fs = require('fs'); +import path = require('path'); + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const chalk = require('chalk'); + +chai.should(); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +let appId: string; + +describe('admin.appCheck', () => { + before(async () => { + try { + appId = fs.readFileSync(path.join(__dirname, '../resources/appid.txt')).toString().trim(); + } catch (error) { + console.log(chalk.yellow( + 'Unable to find an an App ID. Skipping tests that require a valid App ID.', + error, + )); + } + }); + + describe('createToken', () => { + it('should succeed with a vaild token', function() { + if (!appId) { + this.skip(); + } + return admin.appCheck().createToken(appId as string) + .then((token) => { + expect(token).to.have.keys(['token', 'ttlMillis']); + expect(token.token).to.be.a('string').and.to.not.be.empty; + expect(token.ttlMillis).to.be.a('number'); + }); + }); + + it('should propagate API errors', () => { + // rejects with invalid-argument when appId is incorrect + return admin.appCheck().createToken('incorrect-app-id') + .should.eventually.be.rejected.and.have.property('code', 'app-check/invalid-argument'); + }); + + const invalidAppIds = ['', null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop]; + invalidAppIds.forEach((invalidAppId) => { + it(`should throw given an invalid appId: ${JSON.stringify(invalidAppId)}`, () => { + expect(() => admin.appCheck().createToken(invalidAppId as any)) + .to.throw('appId` must be a non-empty string.'); + }); + }); + }); + + describe('verifyToken', () => { + let validToken: admin.appCheck.AppCheckToken; + + before(async () => { + if (!appId) { + return; + } + // obtain a valid app check token + validToken = await admin.appCheck().createToken(appId as string); + }); + + it('should succeed with a decoded verifed token response', function() { + if (!appId) { + this.skip(); + } + return admin.appCheck().verifyToken(validToken.token) + .then((verifedToken) => { + expect(verifedToken).to.have.keys(['token', 'appId']); + expect(verifedToken.token).to.have.keys(['iss', 'sub', 'aud', 'exp', 'iat', 'app_id']); + expect(verifedToken.token.app_id).to.be.a('string').and.equals(appId); + }); + }); + + it('should propagate API errors', () => { + // rejects with invalid-argument when the token is invalid + return admin.appCheck().verifyToken('invalid-token') + .should.eventually.be.rejected.and.have.property('code', 'app-check/invalid-argument'); + }); + }); +}); diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 07499ec3b8..84ab010b93 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -786,6 +786,11 @@ describe('admin.auth', () => { .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); + it('getUserByProviderUid() fails when called with a non-existing provider id', () => { + return getAuth().getUserByProviderUid('google.com', nonexistentUid) + .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); + }); + it('updateUser() fails when called with a non-existing UID', () => { return getAuth().updateUser(nonexistentUid, { emailVerified: true, @@ -1338,12 +1343,31 @@ describe('admin.auth', () => { enabled: true, issuer: 'https://oidc.com/issuer1', clientId: 'CLIENT_ID1', + responseType: { + idToken: true, + }, + }; + const deltaChanges = { + displayName: 'OIDC_DISPLAY_NAME3', + enabled: false, + issuer: 'https://oidc.com/issuer3', + clientId: 'CLIENT_ID3', + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + }, }; const modifiedConfigOptions = { + providerId: authProviderConfig.providerId, displayName: 'OIDC_DISPLAY_NAME3', enabled: false, issuer: 'https://oidc.com/issuer3', clientId: 'CLIENT_ID3', + clientSecret: 'CLIENT_SECRET', + responseType: { + code: true, + }, }; before(function() { @@ -1373,12 +1397,10 @@ describe('admin.auth', () => { .then((config) => { assertDeepEqualUnordered(authProviderConfig, config); return tenantAwareAuth.updateProviderConfig( - authProviderConfig.providerId, modifiedConfigOptions); + authProviderConfig.providerId, deltaChanges); }) .then((config) => { - const modifiedConfig = deepExtend( - { providerId: authProviderConfig.providerId }, modifiedConfigOptions); - assertDeepEqualUnordered(modifiedConfig, config); + assertDeepEqualUnordered(modifiedConfigOptions, config); return tenantAwareAuth.deleteProviderConfig(authProviderConfig.providerId); }) .then(() => { @@ -1637,6 +1659,9 @@ describe('admin.auth', () => { enabled: true, issuer: 'https://oidc.com/issuer1', clientId: 'CLIENT_ID1', + responseType: { + idToken: true, + }, }; const authProviderConfig2 = { providerId: randomOidcProviderId(), @@ -1644,6 +1669,10 @@ describe('admin.auth', () => { enabled: true, issuer: 'https://oidc.com/issuer2', clientId: 'CLIENT_ID2', + clientSecret: 'CLIENT_SECRET', + responseType: { + code: true, + }, }; const removeTempConfigs = (): Promise => { @@ -1710,39 +1739,65 @@ describe('admin.auth', () => { }); }); - it('updateProviderConfig() successfully overwrites an OIDC config', () => { + it('updateProviderConfig() successfully partially modifies an OIDC config', () => { + const deltaChanges = { + displayName: 'OIDC_DISPLAY_NAME3', + enabled: false, + issuer: 'https://oidc.com/issuer3', + clientId: 'CLIENT_ID3', + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + }, + }; + // Only above fields should be modified. const modifiedConfigOptions = { + providerId: authProviderConfig1.providerId, displayName: 'OIDC_DISPLAY_NAME3', enabled: false, issuer: 'https://oidc.com/issuer3', clientId: 'CLIENT_ID3', + clientSecret: 'CLIENT_SECRET', + responseType: { + code: true, + }, }; - return getAuth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) .then((config) => { - const modifiedConfig = deepExtend( - { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); - assertDeepEqualUnordered(modifiedConfig, config); + assertDeepEqualUnordered(modifiedConfigOptions, config); }); }); - it('updateProviderConfig() successfully partially modifies an OIDC config', () => { + it('updateProviderConfig() with invalid oauth response type should be rejected', () => { const deltaChanges = { displayName: 'OIDC_DISPLAY_NAME4', + enabled: false, issuer: 'https://oidc.com/issuer4', + clientId: 'CLIENT_ID4', + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: false, + }, }; - // Only above fields should be modified. - const modifiedConfigOptions = { - displayName: 'OIDC_DISPLAY_NAME4', + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges). + should.eventually.be.rejected.and.have.property('code', 'auth/invalid-oauth-responsetype'); + }); + + it('updateProviderConfig() code flow with no client secret should be rejected', () => { + const deltaChanges = { + displayName: 'OIDC_DISPLAY_NAME5', enabled: false, - issuer: 'https://oidc.com/issuer4', - clientId: 'CLIENT_ID3', + issuer: 'https://oidc.com/issuer5', + clientId: 'CLIENT_ID5', + responseType: { + idToken: false, + code: true, + }, }; - return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) - .then((config) => { - const modifiedConfig = deepExtend( - { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); - assertDeepEqualUnordered(modifiedConfig, config); - }); + return getAuth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges). + should.eventually.be.rejected.and.have.property('code', 'auth/missing-oauth-client-secret'); }); it('deleteProviderConfig() successfully deletes an existing OIDC config', () => { diff --git a/test/integration/messaging.spec.ts b/test/integration/messaging.spec.ts index f1c027a935..bc3533893e 100644 --- a/test/integration/messaging.spec.ts +++ b/test/integration/messaging.spec.ts @@ -172,7 +172,7 @@ describe('admin.messaging', () => { }); }); - it('sendToDeviceGroup() returns a response with success count', () => { + xit('sendToDeviceGroup() returns a response with success count', () => { return getMessaging().sendToDeviceGroup(notificationKey, payload, options) .then((response) => { expect(typeof response.successCount).to.equal('number'); diff --git a/test/resources/mock.jwks.json b/test/resources/mock.jwks.json new file mode 100644 index 0000000000..08695991c3 --- /dev/null +++ b/test/resources/mock.jwks.json @@ -0,0 +1,12 @@ +{ + "keys": [ + { + "kty": "RSA", + "e": "AQAB", + "use": "sig", + "kid": "FGQdnRlzAmKyKr6-Hg_kMQrBkj_H6i6ADnBQz4OI6BU", + "alg": "RS256", + "n": "rFYQyEdjj43mnpXwj-3WgAE01TSYe1-XFE9mxUDShysFwtVZOHFSMm6kl-B3Y_O8NcPt5osntLlH6KHvygExAE0tDmFYq8aKt7LQQF8rTv0rI6MP92ezyCEp4MPmAPFD_tY160XGrkqApuY2_-L8eEXdkRyH2H7lCYypFC0u3DIY25Vlq-ZDkxB2kGykGgb1zVazCDDViqV1p9hSltmm4el9AyF08FsMCpk_NvwKOY4pJ_sm99CDKxMhQBaT9lrIQt0B1VqTpEwlOoiFiyXASRXp9ZTeL4mrLPqSeozwPvspD81wbgecd62F640scKBr3ko73L8M8UWcwgd-moKCJw" + } + ] +} diff --git a/test/resources/mocks.ts b/test/resources/mocks.ts index d63677f96c..b6b4b6bc97 100644 --- a/test/resources/mocks.ts +++ b/test/resources/mocks.ts @@ -35,6 +35,8 @@ const ONE_HOUR_IN_SECONDS = 60 * 60; export const uid = 'someUid'; export const projectId = 'project_id'; +export const projectNumber = '12345678'; +export const appId = '12345678:app:ID'; export const developerClaims = { one: 'uno', two: 'dos', @@ -145,6 +147,10 @@ export const refreshToken = { type: 'refreshToken', }; +// Randomly generated JSON Web Key Sets that do not correspond to anything related to Firebase. +// eslint-disable-next-line @typescript-eslint/no-var-requires +export const jwksResponse = require('./mock.jwks.json'); + // eslint-disable-next-line @typescript-eslint/no-var-requires export const certificateObject = require('./mock.key.json'); @@ -177,6 +183,14 @@ export const x509CertPairs = [ /* eslint-enable max-len */ ]; +// Randomly generated key pairs that don't correspond to anything related to Firebase or GCP +export const jwksKeyPair = { + /* eslint-disable max-len */ + // The private key for this key pair is identical to the one used in ./mock.jwks.json + private: '-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEArFYQyEdjj43mnpXwj+3WgAE01TSYe1+XFE9mxUDShysFwtVZ\nOHFSMm6kl+B3Y/O8NcPt5osntLlH6KHvygExAE0tDmFYq8aKt7LQQF8rTv0rI6MP\n92ezyCEp4MPmAPFD/tY160XGrkqApuY2/+L8eEXdkRyH2H7lCYypFC0u3DIY25Vl\nq+ZDkxB2kGykGgb1zVazCDDViqV1p9hSltmm4el9AyF08FsMCpk/NvwKOY4pJ/sm\n99CDKxMhQBaT9lrIQt0B1VqTpEwlOoiFiyXASRXp9ZTeL4mrLPqSeozwPvspD81w\nbgecd62F640scKBr3ko73L8M8UWcwgd+moKCJwIDAQABAoIBAEDPJQSMhE6KKL5e\n2NbntJDy4zGC1A0hh6llqtpnZETc0w/QN/tX8ndw0IklKwD1ukPl6OOYVVhLjVVZ\nANpQ1GKuo1ETHsuKoMQwhMyQfbL41m5SdkCuSRfsENmsEiUslkuRtzlBRlRpRDR/\nwxM8A4IflBFsT1IFdpC+yx8BVuwLc35iVnaGQpo/jhSDibt07j+FdOKEWkMGj+rL\nsHC6cpB2NMTBl9CIDLW/eq1amBOAGtsSKqoGJvaQY/mZf7SPkRjYIfIl2PWSaduT\nfmMrsYYFtHUKVOMYAD7P5RWNkS8oERucnXT3ouAECvip3Ew2JqlQc0FP7FS5CxH3\nWdfvLuECgYEA8Q7rJrDOdO867s7P/lXMklbAGnuNnAZJdAEXUMIaPJi7al97F119\n4DKBuF7c/dDf8CdiOvMzP8r/F8+FFx2D61xxkQNeuxo5Xjlt23OzW5EI2S6ABesZ\n/3sQWqvKCGuqN7WENYF3EiKyByQ22MYXk8CE7KZuO57Aj88t6TsaNhkCgYEAtwSs\nhbqKSCneC1bQ3wfSAF2kPYRrQEEa2VCLlX1Mz7zHufxksUWAnAbU8O3hIGnXjz6T\nqzivyJJhFSgNGeYpwV67GfXnibpr3OZ/yx2YXIQfp0daivj++kvEU7aNfM9rHZA9\nS3Gh7hKELdB9b0DkrX5GpLiZWA6NnJdrIRYbAj8CgYBCZSyJvJsxBA+EZTxOvk0Z\nZYGGCc/oUKb8p6xHVx8o35yHYQMjXWHlVaP7J03RLy3vFLnuqLvN71ixszviMQP7\n2LuDCJ2YBVIVzNWgY07cgqcgQrmKZ8YCY2AOyVBdX2JD8+AVaLJmMV49r1DYBj/K\nN3WlRPYJv+Ej+xmXKus+SQKBgHh/Zkthxxu+HQigL0M4teYxwSoTnj2e39uGsXBK\nICGCLIniiDVDCmswAFFkfV3G8frI+5a26t2Gqs6wIPgVVxaOlWeBROGkUNIPHMKR\niLgY8XJEg3OOfuoyql9niP5M3jyHtCOQ/Elv/YDgjUWLl0Q3KLHZLHUSl+AqvYj6\nMewnAoGBANgYzPZgP+wreI55BFR470blKh1mFz+YGa+53DCd7JdMH2pdp4hoh303\nXxpOSVlAuyv9SgTsZ7WjGO5UdhaBzVPKgN0OO6JQmQ5ZrOR8ZJ7VB73FiVHCEerj\n1m2zyFv6OT7vqdg+V1/SzxMEmXXFQv1g69k6nWGazne3IJlzrSpj\n-----END RSA PRIVATE KEY-----\n', + public: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArFYQyEdjj43mnpXwj+3W\ngAE01TSYe1+XFE9mxUDShysFwtVZOHFSMm6kl+B3Y/O8NcPt5osntLlH6KHvygEx\nAE0tDmFYq8aKt7LQQF8rTv0rI6MP92ezyCEp4MPmAPFD/tY160XGrkqApuY2/+L8\neEXdkRyH2H7lCYypFC0u3DIY25Vlq+ZDkxB2kGykGgb1zVazCDDViqV1p9hSltmm\n4el9AyF08FsMCpk/NvwKOY4pJ/sm99CDKxMhQBaT9lrIQt0B1VqTpEwlOoiFiyXA\nSRXp9ZTeL4mrLPqSeozwPvspD81wbgecd62F640scKBr3ko73L8M8UWcwgd+moKC\nJwIDAQAB\n-----END PUBLIC KEY-----\n', +}; + /** * Generates a mocked Firebase ID token. * @@ -226,6 +240,27 @@ export function generateSessionCookie(overrides?: object, expiresIn?: number): s return jwt.sign(developerClaims, certificateObject.private_key, options); } +/** + * Generates a mocked App Check token. + * + * @param {object} overrides Overrides for the generated token's attributes. + * @return {string} A mocked App Check token with any provided overrides included. + */ +export function generateAppCheckToken(overrides?: object): string { + const options = _.assign({ + audience: ['projects/' + projectNumber, 'projects/' + projectId], + expiresIn: ONE_HOUR_IN_SECONDS, + issuer: 'https://firebaseappcheck.googleapis.com/' + projectNumber, + subject: appId, + algorithm: ALGORITHM, + header: { + kid: jwksResponse.keys[0].kid, + }, + }, overrides); + + return jwt.sign(developerClaims, jwksKeyPair.private, options); +} + /** Mock socket emitter class. */ export class MockSocketEmitter extends events.EventEmitter { public setTimeout: (_: number) => void = () => undefined; diff --git a/test/unit/app-check/app-check-api-client-internal.spec.ts b/test/unit/app-check/app-check-api-client-internal.spec.ts new file mode 100644 index 0000000000..b846fd1c68 --- /dev/null +++ b/test/unit/app-check/app-check-api-client-internal.spec.ts @@ -0,0 +1,238 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as _ from 'lodash'; +import * as chai from 'chai'; +import * as sinon from 'sinon'; +import { HttpClient } from '../../../src/utils/api-request'; +import * as utils from '../utils'; +import * as mocks from '../../resources/mocks'; +import { getSdkVersion } from '../../../src/utils'; + +import { FirebaseApp } from '../../../src/app/firebase-app'; +import { AppCheckApiClient, FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; +import { FirebaseAppError } from '../../../src/utils/error'; +import { deepCopy } from '../../../src/utils/deep-copy'; + +const expect = chai.expect; + +describe('AppCheckApiClient', () => { + + const ERROR_RESPONSE = { + error: { + code: 404, + message: 'Requested entity not found', + status: 'NOT_FOUND', + }, + }; + + const EXPECTED_HEADERS = { + 'Authorization': 'Bearer mock-token', + 'X-Firebase-Client': `fire-admin-node/${getSdkVersion()}`, + }; + + const noProjectId = 'Failed to determine project ID. Initialize the SDK with service ' + + 'account credentials or set project ID as an app option. Alternatively, set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + const APP_ID = '1:1234:android:1234'; + + const TEST_TOKEN_TO_EXCHANGE = 'signed-custom-token'; + + const TEST_RESPONSE = { + attestationToken: 'token', + ttl: '3s' + }; + + const mockOptions = { + credential: new mocks.MockCredential(), + projectId: 'test-project', + }; + + const clientWithoutProjectId = new AppCheckApiClient( + mocks.mockCredentialApp()); + + // Stubs used to simulate underlying api calls. + let stubs: sinon.SinonStub[] = []; + let app: FirebaseApp; + let apiClient: AppCheckApiClient; + + beforeEach(() => { + app = mocks.appWithOptions(mockOptions); + apiClient = new AppCheckApiClient(app); + }); + + afterEach(() => { + _.forEach(stubs, (stub) => stub.restore()); + stubs = []; + return app.delete(); + }); + + describe('Constructor', () => { + it('should reject when the app is null', () => { + expect(() => new AppCheckApiClient(null as unknown as FirebaseApp)) + .to.throw('First argument passed to admin.appCheck() must be a valid Firebase app instance.'); + }); + }); + + describe('exchangeToken', () => { + it('should reject when project id is not available', () => { + return clientWithoutProjectId.exchangeToken(TEST_TOKEN_TO_EXCHANGE, APP_ID) + .should.eventually.be.rejectedWith(noProjectId); + }); + + it('should throw given no appId', () => { + expect(() => { + (apiClient as any).exchangeToken(TEST_TOKEN_TO_EXCHANGE); + }).to.throw('appId` must be a non-empty string.'); + }); + + const invalidAppIds = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop]; + invalidAppIds.forEach((invalidAppId) => { + it('should throw given a non-string appId: ' + JSON.stringify(invalidAppId), () => { + expect(() => { + apiClient.exchangeToken(TEST_TOKEN_TO_EXCHANGE, invalidAppId as any); + }).to.throw('appId` must be a non-empty string.'); + }); + }); + + it('should throw given an empty string appId', () => { + expect(() => { + apiClient.exchangeToken(TEST_TOKEN_TO_EXCHANGE, ''); + }).to.throw('appId` must be a non-empty string.'); + }); + + it('should throw given no customToken', () => { + expect(() => { + (apiClient as any).exchangeToken(undefined, APP_ID); + }).to.throw('customToken` must be a non-empty string.'); + }); + + const invalidCustomTokens = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop]; + invalidCustomTokens.forEach((invalidCustomToken) => { + it('should throw given a non-string customToken: ' + JSON.stringify(invalidCustomToken), () => { + expect(() => { + apiClient.exchangeToken(invalidCustomToken as any, APP_ID); + }).to.throw('customToken` must be a non-empty string.'); + }); + }); + + it('should throw given an empty string customToken', () => { + expect(() => { + apiClient.exchangeToken('', APP_ID); + }).to.throw('customToken` must be a non-empty string.'); + }); + + it('should reject when a full platform error response is received', () => { + const stub = sinon + .stub(HttpClient.prototype, 'send') + .rejects(utils.errorFrom(ERROR_RESPONSE, 404)); + stubs.push(stub); + const expected = new FirebaseAppCheckError('not-found', 'Requested entity not found'); + return apiClient.exchangeToken(TEST_TOKEN_TO_EXCHANGE, APP_ID) + .should.eventually.be.rejected.and.deep.include(expected); + }); + + it('should reject with unknown-error when error code is not present', () => { + const stub = sinon + .stub(HttpClient.prototype, 'send') + .rejects(utils.errorFrom({}, 404)); + stubs.push(stub); + const expected = new FirebaseAppCheckError('unknown-error', 'Unknown server error: {}'); + return apiClient.exchangeToken(TEST_TOKEN_TO_EXCHANGE, APP_ID) + .should.eventually.be.rejected.and.deep.include(expected); + }); + + it('should reject with unknown-error for non-json response', () => { + const stub = sinon + .stub(HttpClient.prototype, 'send') + .rejects(utils.errorFrom('not json', 404)); + stubs.push(stub); + const expected = new FirebaseAppCheckError( + 'unknown-error', 'Unexpected response with status: 404 and body: not json'); + return apiClient.exchangeToken(TEST_TOKEN_TO_EXCHANGE, APP_ID) + .should.eventually.be.rejected.and.deep.include(expected); + }); + + it('should reject when rejected with a FirebaseAppError', () => { + const expected = new FirebaseAppError('network-error', 'socket hang up'); + const stub = sinon + .stub(HttpClient.prototype, 'send') + .rejects(expected); + stubs.push(stub); + return apiClient.exchangeToken(TEST_TOKEN_TO_EXCHANGE, APP_ID) + .should.eventually.be.rejected.and.deep.include(expected); + }); + + ['', 'abc', '3s2', 'sssa', '3.000000001', '3.2', null, NaN, true, [], {}, 100, 1.2, -200, -2.4] + .forEach((invalidDuration) => { + it(`should throw if the returned ttl duration is: ${invalidDuration}`, () => { + const response = deepCopy(TEST_RESPONSE); + (response as any).ttl = invalidDuration; + const stub = sinon + .stub(HttpClient.prototype, 'send') + .resolves(utils.responseFrom(response, 200)); + stubs.push(stub); + const expected = new FirebaseAppCheckError( + 'invalid-argument', '`ttl` must be a valid duration string with the suffix `s`.'); + return apiClient.exchangeToken(TEST_TOKEN_TO_EXCHANGE, APP_ID) + .should.eventually.be.rejected.and.deep.include(expected); + }); + }); + + it('should resolve with the App Check token on success', () => { + const stub = sinon + .stub(HttpClient.prototype, 'send') + .resolves(utils.responseFrom(TEST_RESPONSE, 200)); + stubs.push(stub); + return apiClient.exchangeToken(TEST_TOKEN_TO_EXCHANGE, APP_ID) + .then((resp) => { + expect(resp.token).to.deep.equal(TEST_RESPONSE.attestationToken); + expect(resp.ttlMillis).to.deep.equal(3000); + expect(stub).to.have.been.calledOnce.and.calledWith({ + method: 'POST', + url: `https://firebaseappcheck.googleapis.com/v1beta/projects/test-project/apps/${APP_ID}:exchangeCustomToken`, + headers: EXPECTED_HEADERS, + data: { customToken: TEST_TOKEN_TO_EXCHANGE } + }); + }); + }); + + new Map([['3s', 3000], ['4.1s', 4100], ['3.000000001s', 3000], ['3.000001s', 3000]]) + .forEach((ttlMillis, ttlString) => { // value, key, map + // 3 seconds with 0 nanoseconds expressed as "3s" + // 3 seconds and 1 nanosecond expressed as "3.000000001s" + // 3 seconds and 1 microsecond expressed as "3.000001s" + it(`should resolve with ttlMillis as ${ttlMillis} when ttl + from server is: ${ttlString}`, () => { + const response = deepCopy(TEST_RESPONSE); + (response as any).ttl = ttlString; + const stub = sinon + .stub(HttpClient.prototype, 'send') + .resolves(utils.responseFrom(response, 200)); + stubs.push(stub); + return apiClient.exchangeToken(TEST_TOKEN_TO_EXCHANGE, APP_ID) + .then((resp) => { + expect(resp.token).to.deep.equal(response.attestationToken); + expect(resp.ttlMillis).to.deep.equal(ttlMillis); + }); + }); + }); + }); +}); diff --git a/test/unit/app-check/app-check.spec.ts b/test/unit/app-check/app-check.spec.ts new file mode 100644 index 0000000000..0818a27945 --- /dev/null +++ b/test/unit/app-check/app-check.spec.ts @@ -0,0 +1,195 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as _ from 'lodash'; +import * as chai from 'chai'; +import * as sinon from 'sinon'; +import * as mocks from '../../resources/mocks'; + +import { FirebaseApp } from '../../../src/app/firebase-app'; +import { AppCheck } from '../../../src/app-check/app-check'; +import { AppCheckApiClient, FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; +import { AppCheckTokenGenerator } from '../../../src/app-check/token-generator'; +import { HttpClient } from '../../../src/utils/api-request'; +import { ServiceAccountSigner } from '../../../src/utils/crypto-signer'; +import { AppCheckTokenVerifier } from '../../../src/app-check/token-verifier'; + +const expect = chai.expect; + +describe('AppCheck', () => { + + const INTERNAL_ERROR = new FirebaseAppCheckError('internal-error', 'message'); + const APP_ID = '1:1234:android:1234'; + const TEST_TOKEN_TO_EXCHANGE = 'signed-custom-token'; + + let appCheck: AppCheck; + + let mockApp: FirebaseApp; + let mockCredentialApp: FirebaseApp; + + // Stubs used to simulate underlying api calls. + let stubs: sinon.SinonStub[] = []; + + before(() => { + mockApp = mocks.app(); + mockCredentialApp = mocks.mockCredentialApp(); + appCheck = new AppCheck(mockApp); + }); + + after(() => { + return mockApp.delete(); + }); + + afterEach(() => { + _.forEach(stubs, (stub) => stub.restore()); + stubs = []; + }); + + describe('Constructor', () => { + const invalidApps = [null, NaN, 0, 1, true, false, '', 'a', [], [1, 'a'], {}, { a: 1 }, _.noop]; + invalidApps.forEach((invalidApp) => { + it('should throw given invalid app: ' + JSON.stringify(invalidApp), () => { + expect(() => { + const appCheckAny: any = AppCheck; + return new appCheckAny(invalidApp); + }).to.throw( + 'First argument passed to admin.appCheck() must be a valid Firebase app ' + + 'instance.'); + }); + }); + + it('should throw given no app', () => { + expect(() => { + const appCheckAny: any = AppCheck; + return new appCheckAny(); + }).to.throw( + 'First argument passed to admin.appCheck() must be a valid Firebase app ' + + 'instance.'); + }); + + it('should reject when initialized without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const noProjectId = 'Failed to determine project ID. Initialize the SDK with service ' + + 'account credentials or set project ID as an app option. Alternatively, set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + const appCheckWithoutProjectId = new AppCheck(mockCredentialApp); + const stub = sinon.stub(AppCheckTokenGenerator.prototype, 'createCustomToken') + .resolves(TEST_TOKEN_TO_EXCHANGE); + stubs.push(stub); + return appCheckWithoutProjectId.createToken(APP_ID) + .should.eventually.rejectedWith(noProjectId); + }); + + it('should reject when failed to contact the Metadata server', () => { + // Remove the Project ID to force a request to the Metadata server + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const appCheckWithoutProjectId = new AppCheck(mockCredentialApp); + const stub = sinon.stub(HttpClient.prototype, 'send') + .rejects(new Error('network error.')); + stubs.push(stub); + const expected = 'Failed to determine service account. Make sure to initialize the SDK ' + + 'with a service account credential. Alternatively specify a service account with ' + + 'iam.serviceAccounts.signBlob permission. Original error: ' + + 'Error: network error.'; + return appCheckWithoutProjectId.createToken(APP_ID) + .should.eventually.be.rejectedWith(expected); + }); + + it('should reject when failed to sign the token', () => { + const expected = 'sign error'; + const stub = sinon.stub(ServiceAccountSigner.prototype, 'sign') + .rejects(new Error(expected)); + stubs.push(stub); + return appCheck.createToken(APP_ID) + .should.eventually.be.rejectedWith(expected); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return new AppCheck(mockApp); + }).not.to.throw(); + }); + }); + + describe('app', () => { + it('returns the app from the constructor', () => { + // We expect referential equality here + expect(appCheck.app).to.equal(mockApp); + }); + }); + + describe('createToken', () => { + it('should propagate API errors', () => { + const stub = sinon + .stub(AppCheckApiClient.prototype, 'exchangeToken') + .rejects(INTERNAL_ERROR); + stubs.push(stub); + return appCheck.createToken(APP_ID) + .should.eventually.be.rejected.and.deep.equal(INTERNAL_ERROR); + }); + + it('should resolve with AppCheckToken on success', () => { + const response = { token: 'token', ttlMillis: 3000 }; + const stub = sinon + .stub(AppCheckApiClient.prototype, 'exchangeToken') + .resolves(response); + stubs.push(stub); + return appCheck.createToken(APP_ID) + .then((token) => { + expect(token.token).equals('token'); + expect(token.ttlMillis).equals(3000); + }); + }); + }); + + describe('verifyToken', () => { + it('should propagate API errors', () => { + const stub = sinon + .stub(AppCheckTokenVerifier.prototype, 'verifyToken') + .rejects(INTERNAL_ERROR); + stubs.push(stub); + return appCheck.verifyToken('token') + .should.eventually.be.rejected.and.deep.equal(INTERNAL_ERROR); + }); + + it('should resolve with VerifyAppCheckTokenResponse on success', () => { + const response = { + sub: 'app-id', + iss: 'https://firebaseappcheck.googleapis.com/123456', + // eslint-disable-next-line @typescript-eslint/camelcase + app_id: 'app-id', + aud: ['123456', 'project-id'], + exp: 1617741496, + iat: 1516239022, + }; + const stub = sinon + .stub(AppCheckTokenVerifier.prototype, 'verifyToken') + .resolves(response); + stubs.push(stub); + return appCheck.verifyToken('token') + .then((tokenResponse) => { + expect(tokenResponse.appId).equals('app-id'); + expect(tokenResponse.token).equals(response); + }); + }); + }); +}); diff --git a/test/unit/app-check/token-generator.spec.ts b/test/unit/app-check/token-generator.spec.ts new file mode 100644 index 0000000000..2a7431f9cd --- /dev/null +++ b/test/unit/app-check/token-generator.spec.ts @@ -0,0 +1,261 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as _ from 'lodash'; +import * as jwt from 'jsonwebtoken'; +import * as chai from 'chai'; +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as mocks from '../../resources/mocks'; + +import { + appCheckErrorFromCryptoSignerError, + AppCheckTokenGenerator +} from '../../../src/app-check/token-generator'; +import { + CryptoSignerError, CryptoSignerErrorCode, ServiceAccountSigner +} from '../../../src/utils/crypto-signer'; +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; +import { FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; +import * as utils from '../utils'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +const ALGORITHM = 'RS256'; +const ONE_HOUR_IN_SECONDS = 60 * 60; +const FIREBASE_APP_CHECK_AUDIENCE = 'https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1beta.TokenExchangeService'; + +/** + * Verifies a token is signed with the private key corresponding to the provided public key. + * + * @param {string} token The token to verify. + * @param {string} publicKey The public key to use to verify the token. + * @return {Promise} A promise fulfilled with the decoded token if it is valid; otherwise, a rejected promise. + */ +function verifyToken(token: string, publicKey: string): Promise { + return new Promise((resolve, reject) => { + jwt.verify(token, publicKey, { + algorithms: [ALGORITHM], + }, (err, res) => { + if (err) { + reject(err); + } else { + resolve(res as object); + } + }); + }); +} + +describe('AppCheckTokenGenerator', () => { + const cert = new ServiceAccountCredential(mocks.certificateObject); + const APP_ID = 'test-app-id'; + + let clock: sinon.SinonFakeTimers | undefined; + afterEach(() => { + if (clock) { + clock.restore(); + clock = undefined; + } + }); + + describe('Constructor', () => { + it('should throw given no arguments', () => { + expect(() => { + // Need to overcome the type system to allow a call with no parameter + const anyFirebaseAppCheckTokenGenerator: any = AppCheckTokenGenerator; + return new anyFirebaseAppCheckTokenGenerator(); + }).to.throw('Must provide a CryptoSigner to use AppCheckTokenGenerator'); + }); + }); + + const invalidSigners: any[] = [null, NaN, 0, 1, true, false, '', 'a', [], _.noop]; + invalidSigners.forEach((invalidSigner) => { + it('should throw given invalid signer: ' + JSON.stringify(invalidSigner), () => { + expect(() => { + return new AppCheckTokenGenerator(invalidSigner as any); + }).to.throw('Must provide a CryptoSigner to use AppCheckTokenGenerator'); + }); + }); + + describe('createCustomToken()', () => { + const tokenGenerator = new AppCheckTokenGenerator(new ServiceAccountSigner(cert)); + + it('should throw given no appId', () => { + expect(() => { + (tokenGenerator as any).createCustomToken(); + }).to.throw(FirebaseAppCheckError).with.property('code', 'app-check/invalid-argument'); + }); + + const invalidAppIds = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop]; + invalidAppIds.forEach((invalidAppId) => { + it('should throw given a non-string appId: ' + JSON.stringify(invalidAppId), () => { + expect(() => { + tokenGenerator.createCustomToken(invalidAppId as any); + }).to.throw(FirebaseAppCheckError).with.property('code', 'app-check/invalid-argument'); + }); + }); + + it('should throw given an empty string appId', () => { + expect(() => { + tokenGenerator.createCustomToken(''); + }).to.throw(FirebaseAppCheckError).with.property('code', 'app-check/invalid-argument'); + }); + + it('should be fulfilled with a Firebase Custom JWT', () => { + return tokenGenerator.createCustomToken(APP_ID) + .should.eventually.be.a('string').and.not.be.empty; + }); + + it('should be fulfilled with a JWT with the correct decoded payload', () => { + clock = sinon.useFakeTimers(1000); + + return tokenGenerator.createCustomToken(APP_ID) + .then((token) => { + const decoded = jwt.decode(token); + const expected: { [key: string]: any } = { + // eslint-disable-next-line @typescript-eslint/camelcase + app_id: APP_ID, + iat: 1, + exp: ONE_HOUR_IN_SECONDS + 1, + aud: FIREBASE_APP_CHECK_AUDIENCE, + iss: mocks.certificateObject.client_email, + sub: mocks.certificateObject.client_email, + }; + + expect(decoded).to.deep.equal(expected); + }); + }); + + it('should be fulfilled with a JWT with the correct header', () => { + clock = sinon.useFakeTimers(1000); + + return tokenGenerator.createCustomToken(APP_ID) + .then((token) => { + const decoded: any = jwt.decode(token, { + complete: true, + }); + expect(decoded.header).to.deep.equal({ + alg: ALGORITHM, + typ: 'JWT', + }); + }); + }); + + it('should be fulfilled with a JWT which can be verified by the service account public key', () => { + return tokenGenerator.createCustomToken(APP_ID) + .then((token) => { + return verifyToken(token, mocks.keyPairs[0].public); + }); + }); + + it('should be fulfilled with a JWT which cannot be verified by a random public key', () => { + return tokenGenerator.createCustomToken(APP_ID) + .then((token) => { + return verifyToken(token, mocks.keyPairs[1].public) + .should.eventually.be.rejectedWith('invalid signature'); + }); + }); + + it('should be fulfilled with a JWT which expires after one hour', () => { + clock = sinon.useFakeTimers(1000); + + let token: string; + return tokenGenerator.createCustomToken(APP_ID) + .then((result) => { + token = result; + + clock!.tick((ONE_HOUR_IN_SECONDS * 1000) - 1); + + // Token should still be valid + return verifyToken(token, mocks.keyPairs[0].public); + }) + .then(() => { + clock!.tick(1); + + // Token should now be invalid + return verifyToken(token, mocks.keyPairs[0].public) + .should.eventually.be.rejectedWith('jwt expired'); + }); + }); + + describe('appCheckErrorFromCryptoSignerError', () => { + it('should convert CryptoSignerError to FirebaseAppCheckError', () => { + const cryptoError = new CryptoSignerError({ + code: CryptoSignerErrorCode.INVALID_ARGUMENT, + message: 'test error.', + }); + const appCheckError = appCheckErrorFromCryptoSignerError(cryptoError); + expect(appCheckError).to.be.an.instanceof(FirebaseAppCheckError); + expect(appCheckError).to.have.property('code', 'app-check/invalid-argument'); + expect(appCheckError).to.have.property('message', 'test error.'); + }); + + it('should convert CryptoSignerError HttpError to FirebaseAppCheckError', () => { + const cryptoError = new CryptoSignerError({ + code: CryptoSignerErrorCode.SERVER_ERROR, + message: 'test error.', + cause: utils.errorFrom({ + error: { + message: 'server error.', + }, + }) + }); + const appCheckError = appCheckErrorFromCryptoSignerError(cryptoError); + expect(appCheckError).to.be.an.instanceof(FirebaseAppCheckError); + expect(appCheckError).to.have.property('code', 'app-check/unknown-error'); + expect(appCheckError).to.have.property('message', + 'Error returned from server while siging a custom token: server error.'); + }); + + it('should convert CryptoSignerError HttpError with no error.message to FirebaseAppCheckError', () => { + const cryptoError = new CryptoSignerError({ + code: CryptoSignerErrorCode.SERVER_ERROR, + message: 'test error.', + cause: utils.errorFrom({ + error: {}, + }) + }); + const appCheckError = appCheckErrorFromCryptoSignerError(cryptoError); + expect(appCheckError).to.be.an.instanceof(FirebaseAppCheckError); + expect(appCheckError).to.have.property('code', 'app-check/unknown-error'); + expect(appCheckError).to.have.property('message', + 'Error returned from server while siging a custom token: '+ + '{"status":500,"headers":{},"data":{"error":{}},"text":"{\\"error\\":{}}"}'); + }); + + it('should convert CryptoSignerError HttpError with no errorcode to FirebaseAppCheckError', () => { + const cryptoError = new CryptoSignerError({ + code: CryptoSignerErrorCode.SERVER_ERROR, + message: 'test error.', + cause: utils.errorFrom('server error.') + }); + const appCheckError = appCheckErrorFromCryptoSignerError(cryptoError); + expect(appCheckError).to.be.an.instanceof(FirebaseAppCheckError); + expect(appCheckError).to.have.property('code', 'app-check/internal-error'); + expect(appCheckError).to.have.property('message', + 'Error returned from server: null.'); + }); + }); + }); +}); diff --git a/test/unit/app-check/token-verifier.spec.ts b/test/unit/app-check/token-verifier.spec.ts new file mode 100644 index 0000000000..27d0be2fbf --- /dev/null +++ b/test/unit/app-check/token-verifier.spec.ts @@ -0,0 +1,245 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as _ from 'lodash'; +import * as chai from 'chai'; +import * as sinon from 'sinon'; +import * as mocks from '../../resources/mocks'; +import * as nock from 'nock'; + +import { AppCheckTokenVerifier } from '../../../src/app-check/token-verifier'; +import { JwtError, JwtErrorCode, PublicKeySignatureVerifier } from '../../../src/utils/jwt'; + +const expect = chai.expect; + +const ONE_HOUR_IN_SECONDS = 60 * 60; + +describe('AppCheckTokenVerifier', () => { + + // Stubs used to simulate underlying api calls. + let stubs: sinon.SinonStub[] = []; + let tokenVerifier: AppCheckTokenVerifier; + let clock: sinon.SinonFakeTimers | undefined; + + before(() => { + tokenVerifier = new AppCheckTokenVerifier(mocks.app()); + }); + + after(() => { + nock.cleanAll(); + }); + + afterEach(() => { + _.forEach(stubs, (stub) => stub.restore()); + stubs = []; + + if (clock) { + clock.restore(); + clock = undefined; + } + }); + + describe('verifyJWT()', () => { + let mockedRequests: nock.Scope[] = []; + let stubs: sinon.SinonStub[] = []; + + afterEach(() => { + _.forEach(mockedRequests, (mockedRequest) => mockedRequest.done()); + mockedRequests = []; + + _.forEach(stubs, (stub) => stub.restore()); + stubs = []; + }); + + it('should throw given no App Check token', () => { + expect(() => { + (tokenVerifier as any).verifyToken(); + }).to.throw('App check token must be a non-null string'); + }); + + const invalidTokens = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop]; + invalidTokens.forEach((invalidToken) => { + it('should throw given a non-string App Check token: ' + JSON.stringify(invalidToken), () => { + expect(() => { + tokenVerifier.verifyToken(invalidToken as any); + }).to.throw('App check token must be a non-null string'); + }); + }); + + it('should throw given an empty string App Check token', () => { + return tokenVerifier.verifyToken('') + .should.eventually.be.rejectedWith('Decoding App Check token failed'); + }); + + it('should be rejected given an invalid App Check token', () => { + return tokenVerifier.verifyToken('invalid-token') + .should.eventually.be.rejectedWith('Decoding App Check token failed'); + }); + + it('should throw if the token verifier was initialized with no "project_id"', () => { + const tokenVerifierWithNoProjectId = new AppCheckTokenVerifier(mocks.mockCredentialApp()); + const expected = 'Must initialize app with a cert credential or set your Firebase project ID as ' + + 'the GOOGLE_CLOUD_PROJECT environment variable to verify an App Check token.'; + return tokenVerifierWithNoProjectId.verifyToken('app.check.token') + .should.eventually.be.rejectedWith(expected); + }); + + it('should be rejected given an App Check token with an incorrect algorithm', () => { + const mockAppCheckToken = mocks.generateAppCheckToken({ + algorithm: 'HS256', + }); + return tokenVerifier.verifyToken(mockAppCheckToken) + .should.eventually.be.rejectedWith('The provided App Check token has incorrect algorithm'); + }); + + const invalidAudiences = [ + 'incorrectAudience', [], [mocks.projectNumber, mocks.projectId], + ['projects/' + mocks.projectNumber, mocks.projectId] + ]; + invalidAudiences.forEach((invalidAudience) => { + it('should be rejected given an App Check token with an incorrect audience:' + + JSON.stringify(invalidAudience), () => { + const mockAppCheckToken = mocks.generateAppCheckToken({ + audience: invalidAudience, + }); + + return tokenVerifier.verifyToken(mockAppCheckToken) + .should.eventually.be.rejectedWith('The provided App Check token has incorrect "aud" (audience) claim'); + }); + }); + + it('should be rejected given an App Check token with an incorrect issuer', () => { + const mockAppCheckToken = mocks.generateAppCheckToken({ + issuer: 'incorrectIssuer', + }); + + return tokenVerifier.verifyToken(mockAppCheckToken) + .should.eventually.be.rejectedWith('The provided App Check token has incorrect "iss" (issuer) claim'); + }); + + it('should be rejected given an App Check token with an empty subject', () => { + const mockAppCheckToken = mocks.generateAppCheckToken({ + subject: '', + }); + + return tokenVerifier.verifyToken(mockAppCheckToken) + .should.eventually.be.rejectedWith('The provided App Check token has an empty string "sub" (subject) claim'); + }); + + it('should be rejected when the verifier throws no maching kid error', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .rejects(new JwtError(JwtErrorCode.NO_MATCHING_KID, 'No matching key ID.')); + stubs.push(verifierStub); + + const mockAppCheckToken = mocks.generateAppCheckToken({ + header: { + kid: 'wrongkid', + }, + }); + + return tokenVerifier.verifyToken(mockAppCheckToken) + .should.eventually.be.rejectedWith('The provided App Check token has "kid" claim which does not ' + + 'correspond to a known public key'); + }); + + it('should be rejected when the verifier throws expired token error', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .rejects(new JwtError(JwtErrorCode.TOKEN_EXPIRED, 'Expired token.')); + stubs.push(verifierStub); + + const mockAppCheckToken = mocks.generateAppCheckToken(); + + return tokenVerifier.verifyToken(mockAppCheckToken) + .should.eventually.be.rejectedWith('The provided App Check token has expired. ' + + 'Get a fresh App Check token from your client app and try again.') + .and.have.property('code', 'app-check/app-check-token-expired'); + }); + + it('should be rejected when the verifier throws invalid signature error.', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .rejects(new JwtError(JwtErrorCode.INVALID_SIGNATURE, 'invalid signature.')); + stubs.push(verifierStub); + + const mockAppCheckToken = mocks.generateAppCheckToken(); + + return tokenVerifier.verifyToken(mockAppCheckToken) + .should.eventually.be.rejectedWith('The provided App Check token has invalid signature'); + }); + + it('should be rejected when the verifier throws key fetch error.', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .rejects(new JwtError(JwtErrorCode.KEY_FETCH_ERROR, 'Error fetching Json Web Keys.')); + stubs.push(verifierStub); + + const mockAppCheckToken = mocks.generateAppCheckToken(); + + return tokenVerifier.verifyToken(mockAppCheckToken) + .should.eventually.be.rejectedWith('Error fetching Json Web Keys.'); + }); + + it('should be fulfilled when the kid is not present in the header (should try all the keys)', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .resolves(); + stubs.push(verifierStub); + + clock = sinon.useFakeTimers(1000); + + const mockAppCheckToken = mocks.generateAppCheckToken({ + header: {}, + }); + + return tokenVerifier.verifyToken(mockAppCheckToken) + .should.eventually.be.fulfilled.and.deep.equal({ + one: 'uno', + two: 'dos', + iat: 1, + exp: ONE_HOUR_IN_SECONDS + 1, + aud: ['projects/' + mocks.projectNumber, 'projects/' + mocks.projectId], + iss: 'https://firebaseappcheck.googleapis.com/' + mocks.projectNumber, + sub: mocks.appId, + // eslint-disable-next-line @typescript-eslint/camelcase + app_id: mocks.appId, + }); + }); + + it('should be fulfilled with decoded claims given a valid App Check token', () => { + const verifierStub = sinon.stub(PublicKeySignatureVerifier.prototype, 'verify') + .resolves(); + stubs.push(verifierStub); + + clock = sinon.useFakeTimers(1000); + + const mockAppCheckToken = mocks.generateAppCheckToken(); + + return tokenVerifier.verifyToken(mockAppCheckToken) + .should.eventually.be.fulfilled.and.deep.equal({ + one: 'uno', + two: 'dos', + iat: 1, + exp: ONE_HOUR_IN_SECONDS + 1, + aud: ['projects/' + mocks.projectNumber, 'projects/' + mocks.projectId], + iss: 'https://firebaseappcheck.googleapis.com/' + mocks.projectNumber, + sub: mocks.appId, + // eslint-disable-next-line @typescript-eslint/camelcase + app_id: mocks.appId, + }); + }); + + }); +}); diff --git a/test/unit/app/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts index 5d34491ef4..8ef2a69d4a 100644 --- a/test/unit/app/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -34,7 +34,7 @@ import { } from '../../../src/app/firebase-namespace'; import { auth, messaging, machineLearning, storage, firestore, database, - instanceId, projectManagement, securityRules , remoteConfig, + instanceId, projectManagement, securityRules , remoteConfig, appCheck, } from '../../../src/firebase-namespace-api'; import { FirebaseAppError, AppErrorCodes } from '../../../src/utils/error'; @@ -48,6 +48,7 @@ import InstanceId = instanceId.InstanceId; import ProjectManagement = projectManagement.ProjectManagement; import SecurityRules = securityRules.SecurityRules; import RemoteConfig = remoteConfig.RemoteConfig; +import AppCheck = appCheck.AppCheck; chai.should(); chai.use(sinonChai); @@ -664,6 +665,32 @@ describe('FirebaseApp', () => { }); }); + describe('appCheck()', () => { + it('should throw if the app has already been deleted', () => { + const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); + + return app.delete().then(() => { + expect(() => { + return app.appCheck(); + }).to.throw(`Firebase app named "${mocks.appName}" has already been deleted.`); + }); + }); + + it('should return the AppCheck client', () => { + const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); + + const appCheck: AppCheck = app.appCheck(); + expect(appCheck).to.not.be.null; + }); + + it('should return a cached version of AppCheck on subsequent calls', () => { + const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); + const service1: AppCheck = app.appCheck(); + const service2: AppCheck = app.appCheck(); + expect(service1).to.equal(service2); + }); + }); + describe('INTERNAL.getToken()', () => { it('throws a custom credential implementation which returns invalid access tokens', () => { diff --git a/test/unit/app/firebase-namespace.spec.ts b/test/unit/app/firebase-namespace.spec.ts index e2daa62729..b1f3854f16 100644 --- a/test/unit/app/firebase-namespace.spec.ts +++ b/test/unit/app/firebase-namespace.spec.ts @@ -49,9 +49,10 @@ import { getSdkVersion } from '../../../src/utils/index'; import { app, auth, messaging, machineLearning, storage, firestore, database, - instanceId, projectManagement, securityRules , remoteConfig, + instanceId, projectManagement, securityRules , remoteConfig, appCheck, } from '../../../src/firebase-namespace-api'; import { Auth as AuthImpl } from '../../../src/auth/auth'; +import { AppCheck as AppCheckImpl } from '../../../src/app-check/app-check'; import { InstanceId as InstanceIdImpl } from '../../../src/instance-id/instance-id'; import { MachineLearning as MachineLearningImpl } from '../../../src/machine-learning/machine-learning'; import { Messaging as MessagingImpl } from '../../../src/messaging/messaging'; @@ -63,6 +64,7 @@ import { Storage as StorageImpl } from '../../../src/storage/storage'; import { clearGlobalAppDefaultCred } from '../../../src/app/credential-factory'; import App = app.App; +import AppCheck = appCheck.AppCheck; import Auth = auth.Auth; import Database = database.Database; import Firestore = firestore.Firestore; @@ -790,4 +792,42 @@ describe('FirebaseNamespace', () => { after(clearGlobalAppDefaultCred); }); + + describe('#appCheck()', () => { + it('should throw when called before initializing an app', () => { + expect(() => { + firebaseNamespace.appCheck(); + }).to.throw(DEFAULT_APP_NOT_FOUND); + }); + + it('should throw when default app is not initialized', () => { + firebaseNamespace.initializeApp(mocks.appOptions, 'testApp'); + expect(() => { + firebaseNamespace.appCheck(); + }).to.throw(DEFAULT_APP_NOT_FOUND); + }); + + it('should return a valid namespace when the default app is initialized', () => { + const app: App = firebaseNamespace.initializeApp(mocks.appOptions); + const fac: AppCheck = firebaseNamespace.appCheck(); + expect(fac.app).to.be.deep.equal(app); + }); + + it('should return a valid namespace when the named app is initialized', () => { + const app: App = firebaseNamespace.initializeApp(mocks.appOptions, 'testApp'); + const fac: AppCheck = firebaseNamespace.appCheck(app); + expect(fac.app).to.be.deep.equal(app); + }); + + it('should return a reference to AppCheck type', () => { + expect(firebaseNamespace.appCheck.AppCheck).to.be.deep.equal(AppCheckImpl); + }); + + it('should return a cached version of AppCheck on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const service1: AppCheck = firebaseNamespace.appCheck(); + const service2: AppCheck = firebaseNamespace.appCheck(); + expect(service1).to.equal(service2); + }); + }); }); diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index ffe921371d..75000300e7 100644 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -1418,12 +1418,6 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { factorId: 'phone', enrollmentTime: new Date().toUTCString(), }, - { - uid: 'mfaUid2', - phoneNumber: '+16505550002', - displayName: 'Personal phone number', - factorId: 'phone', - }, ], }, customClaims: { admin: true }, @@ -2064,6 +2058,11 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { phoneNumber: '+16505551000', factorId: 'phone', } as UpdateMultiFactorInfoRequest, + { + // No error should be thrown when no uid is specified. + phoneNumber: '+16505551234', + factorId: 'phone', + } as UpdateMultiFactorInfoRequest, ], }, }; @@ -2089,6 +2088,9 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { mfaEnrollmentId: 'enrolledSecondFactor2', phoneInfo: '+16505551000', }, + { + phoneInfo: '+16505551234', + }, ], }, }; @@ -3490,6 +3492,8 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const providerId = 'oidc.provider'; const path = handler.path('v2', `/oauthIdpConfigs?oauthIdpConfigId=${providerId}`, 'project_id'); const expectedHttpMethod = 'POST'; + const clientSecret = 'CLIENT_SECRET'; + const responseType = { code: true }; const configOptions = { providerId, displayName: 'OIDC_DISPLAY_NAME', @@ -3506,6 +3510,26 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const expectedResult = utils.responseFrom(deepExtend({ name: `projects/project1/oauthIdpConfigs/${providerId}`, }, expectedRequest)); + const expectedCodeFlowOptions = { + providerId, + displayName: 'OIDC_DISPLAY_NAME', + enabled: true, + clientId: 'CLIENT_ID', + issuer: 'https://oidc.com/issuer', + clientSecret, + responseType, + }; + const expectedCodeFlowRequest = { + displayName: 'OIDC_DISPLAY_NAME', + enabled: true, + clientId: 'CLIENT_ID', + issuer: 'https://oidc.com/issuer', + clientSecret, + responseType, + }; + const expectedCodeFlowResult = utils.responseFrom(deepExtend({ + name: `projects/project1/oauthIdpConfigs/${providerId}`, + }, expectedCodeFlowRequest)); it('should be fulfilled given valid parameters', () => { const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); @@ -3520,6 +3544,19 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); }); + it('should be fulfilled given valid parameters for OIDC code flow', () => { + const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedCodeFlowResult); + stubs.push(stub); + + const requestHandler = handler.init(mockApp); + return requestHandler.createOAuthIdpConfig(expectedCodeFlowOptions) + .then((response) => { + expect(response).to.deep.equal(expectedCodeFlowResult.data); + expect(stub).to.have.been.calledOnce.and.calledWith( + callParams(path, expectedHttpMethod, expectedCodeFlowRequest)); + }); + }); + it('should be rejected given invalid parameters', () => { const expectedError = new FirebaseAuthError( AuthClientErrorCode.INVALID_CONFIG, @@ -3582,6 +3619,8 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const providerId = 'oidc.provider'; const path = handler.path('v2', `/oauthIdpConfigs/${providerId}`, 'project_id'); const expectedHttpMethod = 'PATCH'; + const clientSecret = 'CLIENT_SECRET'; + const responseType = { code: true }; const configOptions = { displayName: 'OIDC_DISPLAY_NAME', enabled: true, @@ -3605,6 +3644,26 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { clientId: 'NEW_CLIENT_ID', issuer: 'https://oidc.com/issuer2', })); + const expectedCodeFlowOptions = { + providerId, + displayName: 'OIDC_DISPLAY_NAME', + enabled: true, + clientId: 'CLIENT_ID', + issuer: 'https://oidc.com/issuer', + clientSecret, + responseType, + }; + const expectedCodeFlowRequest = { + displayName: 'OIDC_DISPLAY_NAME', + enabled: true, + clientId: 'CLIENT_ID', + issuer: 'https://oidc.com/issuer', + clientSecret, + responseType, + }; + const expectedCodeFlowResult = utils.responseFrom(deepExtend({ + name: `projects/project1/oauthIdpConfigs/${providerId}`, + }, expectedCodeFlowRequest)); it('should be fulfilled given full parameters', () => { const expectedPath = path + '?updateMask=enabled,displayName,issuer,clientId'; @@ -3620,6 +3679,20 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); }); + it('should be fulfilled given full parameters for OIDC code flow', () => { + const expectedPath = path + '?updateMask=enabled,displayName,issuer,clientId,clientSecret,responseType.code'; + const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedCodeFlowResult); + stubs.push(stub); + + const requestHandler = handler.init(mockApp); + return requestHandler.updateOAuthIdpConfig(providerId, expectedCodeFlowOptions) + .then((response) => { + expect(response).to.deep.equal(expectedCodeFlowResult.data); + expect(stub).to.have.been.calledOnce.and.calledWith( + callParams(expectedPath, expectedHttpMethod, expectedCodeFlowRequest)); + }); + }); + it('should be fulfilled given partial parameters', () => { const expectedPath = path + '?updateMask=enabled,clientId'; const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedPartialResult); diff --git a/test/unit/auth/auth-config.spec.ts b/test/unit/auth/auth-config.spec.ts index 5e55bb6992..62e7d4e3f6 100644 --- a/test/unit/auth/auth-config.spec.ts +++ b/test/unit/auth/auth-config.spec.ts @@ -725,6 +725,11 @@ describe('OIDCConfig', () => { issuer: 'https://oidc.com/issuer', displayName: 'oidcProviderName', enabled: true, + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + }, }; const serverResponse: OIDCConfigServerResponse = { name: 'projects/project_id/oauthIdpConfigs/oidc.provider', @@ -732,6 +737,10 @@ describe('OIDCConfig', () => { issuer: 'https://oidc.com/issuer', displayName: 'oidcProviderName', enabled: true, + clientSecret: 'CLIENT_SECRET', + responseType: { + code: true, + }, }; const clientRequest: OIDCAuthProviderConfig = { providerId: 'oidc.provider', @@ -739,6 +748,11 @@ describe('OIDCConfig', () => { issuer: 'https://oidc.com/issuer', displayName: 'oidcProviderName', enabled: true, + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + }, }; const config = new OIDCConfig(serverResponse); @@ -767,6 +781,21 @@ describe('OIDCConfig', () => { expect(config.enabled).to.be.true; }); + it('should set readonly property clientSecret', () => { + expect(config.clientSecret).to.equal('CLIENT_SECRET'); + }); + + it('should set readonly property expected responseType', () => { + expect(config.responseType).to.deep.equal({ code: true }); + }); + + it('should not throw on no responseType and clientSecret', () => { + const testResponse = deepCopy(serverResponse); + delete testResponse.clientSecret; + delete testResponse.responseType; + expect(() => new OIDCConfig(testResponse)).not.to.throw(); + }); + it('should throw on missing issuer', () => { const invalidResponse = deepCopy(serverResponse); delete invalidResponse.issuer; @@ -829,6 +858,10 @@ describe('OIDCConfig', () => { providerId: 'oidc.provider', issuer: 'https://oidc.com/issuer', clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + responseType: { + code: true, + }, }); }); }); @@ -842,12 +875,22 @@ describe('OIDCConfig', () => { const updateRequest: OIDCUpdateAuthProviderRequest = { clientId: 'CLIENT_ID', displayName: 'OIDC_PROVIDER_DISPLAY_NAME', + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + } }; const updateServerRequest: OIDCConfigServerRequest = { clientId: 'CLIENT_ID', displayName: 'OIDC_PROVIDER_DISPLAY_NAME', issuer: undefined, enabled: undefined, + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + } }; expect(OIDCConfig.buildServerRequest(updateRequest, true)).to.deep.equal(updateServerRequest); }); @@ -890,6 +933,62 @@ describe('OIDCConfig', () => { expect(() => OIDCConfig.validate(partialRequest, true)).not.to.throw(); }); + it('should throw on OAuth responseType contains invalid parameters', () => { + const invalidRequest = deepCopy(clientRequest) as any; + invalidRequest.responseType.unknownField = true; + expect(() => OIDCConfig.validate(invalidRequest, true)) + .to.throw('"unknownField" is not a valid OAuthResponseType parameter.'); + }); + + it('should not throw when exactly one OAuth responseType is true', () => { + const validRequest = deepCopy(clientRequest) as any; + validRequest.responseType.code = false; + validRequest.responseType.idToken = true; + expect(() => OIDCConfig.validate(validRequest, true)).not.to.throw(); + }); + + it('should not throw when only idToken responseType is set to true', () => { + const validRequest = deepCopy(clientRequest) as any; + validRequest.responseType = { idToken: true }; + expect(() => OIDCConfig.validate(validRequest, true)).not.to.throw(); + }); + + it('should not throw when only code responseType is set to true', () => { + const validRequest = deepCopy(clientRequest) as any; + const validResponseType = { code: true }; + validRequest.responseType = validResponseType; + expect(() => OIDCConfig.validate(validRequest, true)).not.to.throw(); + }); + + it('should throw on two OAuth responseTypes set to true', () => { + const invalidRequest = deepCopy(clientRequest) as any; + invalidRequest.responseType.idToken = true; + invalidRequest.responseType.code = true; + expect(() => OIDCConfig.validate(invalidRequest, true)) + .to.throw('Only exactly one OAuth responseType should be set to true.'); + }); + + it('should throw on no OAuth responseType set to true', () => { + const invalidRequest = deepCopy(clientRequest) as any; + invalidRequest.responseType.idToken = false; + invalidRequest.responseType.code = false; + expect(() => OIDCConfig.validate(invalidRequest, true)) + .to.throw('Only exactly one OAuth responseType should be set to true.'); + }); + + it('should not throw when responseType is empty', () => { + const testRequest = deepCopy(clientRequest) as any; + testRequest.responseType = {}; + expect(() => OIDCConfig.validate(testRequest, true)).not.to.throw(); + }); + + it('should throw on no client secret when OAuth responseType code flow set to true', () => { + const invalidRequest = deepCopy(clientRequest) as any; + delete invalidRequest.clientSecret; + expect(() => OIDCConfig.validate(invalidRequest, true)) + .to.throw('The OAuth configuration client secret is required to enable OIDC code flow.'); + }); + const nonObjects = [null, NaN, 0, 1, true, false, '', 'a', [], [1, 'a'], _.noop]; nonObjects.forEach((request) => { it('should throw on non-null OIDCAuthProviderConfig object:' + JSON.stringify(request), () => { @@ -955,5 +1054,35 @@ describe('OIDCConfig', () => { .to.throw('"OIDCAuthProviderConfig.displayName" must be a valid string.'); }); }); + + const invalidClientSecrets = [null, NaN, 0, 1, true, false, '', [], [1, 'a'], {}, { a: 1 }, _.noop]; + invalidClientSecrets.forEach((invalidClientSecret) => { + it('should throw on invalid clientSecret:' + JSON.stringify(invalidClientSecret), () => { + const invalidClientRequest = deepCopy(clientRequest) as any; + invalidClientRequest.clientSecret = invalidClientSecret; + expect(() => OIDCConfig.validate(invalidClientRequest)) + .to.throw('"OIDCAuthProviderConfig.clientSecret" must be a valid string.'); + }); + }); + + const invalidOAuthResponseIdTokenBooleans = [null, NaN, 0, 1, 'invalid', '', [], [1, 'a'], {}, { a: 1 }, _.noop]; + invalidOAuthResponseIdTokenBooleans.forEach((invalidOAuthResponseIdTokenBoolean) => { + it('should throw on invalid responseType.idToken:' + JSON.stringify(invalidOAuthResponseIdTokenBoolean), () => { + const invalidClientRequest = deepCopy(clientRequest) as any; + invalidClientRequest.responseType.idToken = invalidOAuthResponseIdTokenBoolean; + expect(() => OIDCConfig.validate(invalidClientRequest)) + .to.throw('"OIDCAuthProviderConfig.responseType.idToken" must be a boolean.'); + }); + }); + + const invalidOAuthResponseCodeBooleans = [null, NaN, 0, 1, 'invalid', '', [], [1, 'a'], {}, { a: 1 }, _.noop]; + invalidOAuthResponseCodeBooleans.forEach((invalidOAuthResponseCodeBoolean) => { + it('should throw on invalid responseType.code:' + JSON.stringify(invalidOAuthResponseCodeBoolean), () => { + const invalidClientRequest = deepCopy(clientRequest) as any; + invalidClientRequest.responseType.code = invalidOAuthResponseCodeBoolean; + expect(() => OIDCConfig.validate(invalidClientRequest)) + .to.throw('"OIDCAuthProviderConfig.responseType.code" must be a boolean.'); + }); + }); }); }); diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index fb55544924..a488c7e453 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -1862,6 +1862,109 @@ AUTH_CONFIGS.forEach((testConfig) => { }); }); + describe('non-federated providers', () => { + let invokeRequestHandlerStub: sinon.SinonStub; + let getAccountInfoByUidStub: sinon.SinonStub; + beforeEach(() => { + invokeRequestHandlerStub = sinon.stub(testConfig.RequestHandler.prototype, 'invokeRequestHandler') + .resolves({ + // nothing here is checked; we just need enough to not crash. + users: [{ + localId: 1, + }], + }); + + getAccountInfoByUidStub = sinon.stub(testConfig.RequestHandler.prototype, 'getAccountInfoByUid') + .resolves({ + // nothing here is checked; we just need enough to not crash. + users: [{ + localId: 1, + }], + }); + }); + afterEach(() => { + invokeRequestHandlerStub.restore(); + getAccountInfoByUidStub.restore(); + }); + + it('specifying both email and providerId=email should be rejected', () => { + expect(() => { + auth.updateUser(uid, { + email: 'user@example.com', + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + + it('specifying both phoneNumber and providerId=phone should be rejected', () => { + expect(() => { + auth.updateUser(uid, { + phoneNumber: '+15555550001', + providerToLink: { + providerId: 'phone', + uid: '+15555550001', + }, + }); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + + it('email linking should use email field', async () => { + await auth.updateUser(uid, { + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }); + expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( + sinon.match.any, sinon.match.any, { + localId: uid, + email: 'user@example.com', + }); + }); + + it('phone linking should use phoneNumber field', async () => { + await auth.updateUser(uid, { + providerToLink: { + providerId: 'phone', + uid: '+15555550001', + }, + }); + expect(invokeRequestHandlerStub).to.have.been.calledOnce.and.calledWith( + sinon.match.any, sinon.match.any, { + localId: uid, + phoneNumber: '+15555550001', + }); + }); + + it('specifying both phoneNumber=null and providersToUnlink=phone should be rejected', () => { + expect(() => { + auth.updateUser(uid, { + phoneNumber: null, + providersToUnlink: ['phone'], + }); + }).to.throw(FirebaseAuthError).with.property('code', 'auth/argument-error'); + }); + + it('doesnt mutate the properties parameter', async () => { + const properties: UpdateRequest = { + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }; + await auth.updateUser(uid, properties); + expect(properties).to.deep.equal({ + providerToLink: { + providerId: 'email', + uid: 'user@example.com', + }, + }); + }); + }); + it('should be rejected given an app which returns null access tokens', () => { return nullAccessTokenAuth.updateUser(uid, propertiesToEdit) .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); diff --git a/test/unit/auth/token-generator.spec.ts b/test/unit/auth/token-generator.spec.ts index 963c75f6f5..70df21b4ef 100644 --- a/test/unit/auth/token-generator.spec.ts +++ b/test/unit/auth/token-generator.spec.ts @@ -26,14 +26,13 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../../resources/mocks'; import { - BLACKLISTED_CLAIMS, FirebaseTokenGenerator, ServiceAccountSigner, IAMSigner, EmulatedSigner + BLACKLISTED_CLAIMS, FirebaseTokenGenerator, EmulatedSigner, handleCryptoSignerError } from '../../../src/auth/token-generator'; +import { CryptoSignerError, CryptoSignerErrorCode, ServiceAccountSigner } from '../../../src/utils/crypto-signer'; import { ServiceAccountCredential } from '../../../src/app/credential-internal'; -import { AuthorizedHttpClient, HttpClient } from '../../../src/utils/api-request'; -import { FirebaseApp } from '../../../src/app/firebase-app'; -import * as utils from '../utils'; import { FirebaseAuthError } from '../../../src/utils/error'; +import * as utils from '../utils'; chai.should(); chai.use(sinonChai); @@ -66,195 +65,6 @@ function verifyToken(token: string, publicKey: string): Promise { }); } -describe('CryptoSigner', () => { - describe('ServiceAccountSigner', () => { - it('should throw given no arguments', () => { - expect(() => { - const anyServiceAccountSigner: any = ServiceAccountSigner; - return new anyServiceAccountSigner(); - }).to.throw('Must provide a service account credential to initialize ServiceAccountSigner'); - }); - - it('should not throw given a valid certificate', () => { - expect(() => { - return new ServiceAccountSigner(new ServiceAccountCredential(mocks.certificateObject)); - }).not.to.throw(); - }); - - it('should sign using the private_key in the certificate', () => { - const payload = Buffer.from('test'); - const cert = new ServiceAccountCredential(mocks.certificateObject); - - // eslint-disable-next-line @typescript-eslint/no-var-requires - const crypto = require('crypto'); - const rsa = crypto.createSign('RSA-SHA256'); - rsa.update(payload); - const result = rsa.sign(cert.privateKey, 'base64'); - - const signer = new ServiceAccountSigner(cert); - return signer.sign(payload).then((signature) => { - expect(signature.toString('base64')).to.equal(result); - }); - }); - - it('should return the client_email from the certificate', () => { - const cert = new ServiceAccountCredential(mocks.certificateObject); - const signer = new ServiceAccountSigner(cert); - return signer.getAccountId().should.eventually.equal(cert.clientEmail); - }); - }); - - describe('IAMSigner', () => { - let mockApp: FirebaseApp; - let getTokenStub: sinon.SinonStub; - const mockAccessToken: string = utils.generateRandomAccessToken(); - - beforeEach(() => { - mockApp = mocks.app(); - getTokenStub = utils.stubGetAccessToken(mockAccessToken, mockApp); - return mockApp.INTERNAL.getToken(); - }); - - afterEach(() => { - getTokenStub.restore(); - return mockApp.delete(); - }); - - it('should throw given no arguments', () => { - expect(() => { - const anyIAMSigner: any = IAMSigner; - return new anyIAMSigner(); - }).to.throw('Must provide a HTTP client to initialize IAMSigner'); - }); - - describe('explicit service account ID', () => { - const response = { signedBlob: Buffer.from('testsignature').toString('base64') }; - const input = Buffer.from('input'); - const signRequest = { - method: 'POST', - url: 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/test-service-account:signBlob', - headers: { Authorization: `Bearer ${mockAccessToken}` }, - data: { payload: input.toString('base64') }, - }; - let stub: sinon.SinonStub; - - afterEach(() => { - stub.restore(); - }); - - it('should sign using the IAM service', () => { - const expectedResult = utils.responseFrom(response); - stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); - const requestHandler = new AuthorizedHttpClient(mockApp); - const signer = new IAMSigner(requestHandler, 'test-service-account'); - return signer.sign(input).then((signature) => { - expect(signature.toString('base64')).to.equal(response.signedBlob); - expect(stub).to.have.been.calledOnce.and.calledWith(signRequest); - }); - }); - - it('should fail if the IAM service responds with an error', () => { - const expectedResult = utils.errorFrom({ - error: { - status: 'PROJECT_NOT_FOUND', - message: 'test reason', - }, - }); - stub = sinon.stub(HttpClient.prototype, 'send').rejects(expectedResult); - const requestHandler = new AuthorizedHttpClient(mockApp); - const signer = new IAMSigner(requestHandler, 'test-service-account'); - return signer.sign(input).catch((err) => { - const message = 'test reason; Please refer to ' + - 'https://firebase.google.com/docs/auth/admin/create-custom-tokens for more details on ' + - 'how to use and troubleshoot this feature.'; - expect(err.message).to.equal(message); - expect(stub).to.have.been.calledOnce.and.calledWith(signRequest); - }); - }); - - it('should return the explicitly specified service account', () => { - const signer = new IAMSigner(new AuthorizedHttpClient(mockApp), 'test-service-account'); - return signer.getAccountId().should.eventually.equal('test-service-account'); - }); - }); - - describe('auto discovered service account', () => { - const input = Buffer.from('input'); - const response = { signedBlob: Buffer.from('testsignature').toString('base64') }; - const metadataRequest = { - method: 'GET', - url: 'http://metadata/computeMetadata/v1/instance/service-accounts/default/email', - headers: { 'Metadata-Flavor': 'Google' }, - }; - const signRequest = { - method: 'POST', - url: 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/discovered-service-account:signBlob', - headers: { Authorization: `Bearer ${mockAccessToken}` }, - data: { payload: input.toString('base64') }, - }; - let stub: sinon.SinonStub; - - afterEach(() => { - stub.restore(); - }); - - it('should sign using the IAM service', () => { - stub = sinon.stub(HttpClient.prototype, 'send'); - stub.onCall(0).resolves(utils.responseFrom('discovered-service-account')); - stub.onCall(1).resolves(utils.responseFrom(response)); - const requestHandler = new AuthorizedHttpClient(mockApp); - const signer = new IAMSigner(requestHandler); - return signer.sign(input).then((signature) => { - expect(signature.toString('base64')).to.equal(response.signedBlob); - expect(stub).to.have.been.calledTwice; - expect(stub.getCall(0).args[0]).to.deep.equal(metadataRequest); - expect(stub.getCall(1).args[0]).to.deep.equal(signRequest); - }); - }); - - it('should fail if the IAM service responds with an error', () => { - const expectedResult = { - error: { - status: 'PROJECT_NOT_FOUND', - message: 'test reason', - }, - }; - stub = sinon.stub(HttpClient.prototype, 'send'); - stub.onCall(0).resolves(utils.responseFrom('discovered-service-account')); - stub.onCall(1).rejects(utils.errorFrom(expectedResult)); - const requestHandler = new AuthorizedHttpClient(mockApp); - const signer = new IAMSigner(requestHandler); - return signer.sign(input).catch((err) => { - const message = 'test reason; Please refer to ' + - 'https://firebase.google.com/docs/auth/admin/create-custom-tokens for more details on ' + - 'how to use and troubleshoot this feature.'; - expect(err.message).to.equal(message); - expect(stub).to.have.been.calledTwice; - expect(stub.getCall(0).args[0]).to.deep.equal(metadataRequest); - expect(stub.getCall(1).args[0]).to.deep.equal(signRequest); - }); - }); - - it('should return the discovered service account', () => { - stub = sinon.stub(HttpClient.prototype, 'send'); - stub.onCall(0).resolves(utils.responseFrom('discovered-service-account')); - const signer = new IAMSigner(new AuthorizedHttpClient(mockApp)); - return signer.getAccountId().should.eventually.equal('discovered-service-account'); - }); - - it('should return the expected error when failed to contact the Metadata server', () => { - stub = sinon.stub(HttpClient.prototype, 'send'); - stub.onCall(0).rejects(utils.errorFrom('test error')); - const signer = new IAMSigner(new AuthorizedHttpClient(mockApp)); - const expected = 'Failed to determine service account. Make sure to initialize the SDK with ' + - 'a service account credential. Alternatively specify a service account with ' + - 'iam.serviceAccounts.signBlob permission.'; - return signer.getAccountId().should.eventually.be.rejectedWith(expected); - }); - }); - }); -}); - describe('FirebaseTokenGenerator', () => { const tenantId = 'tenantId1'; const cert = new ServiceAccountCredential(mocks.certificateObject); @@ -384,7 +194,7 @@ describe('FirebaseTokenGenerator', () => { BLACKLISTED_CLAIMS.forEach((blacklistedClaim) => { it('should throw given a developer claims object with a blacklisted claim: ' + blacklistedClaim, () => { - const blacklistedDeveloperClaims: {[key: string]: any} = _.clone(mocks.developerClaims); + const blacklistedDeveloperClaims: { [key: string]: any } = _.clone(mocks.developerClaims); blacklistedDeveloperClaims[blacklistedClaim] = true; expect(() => { tokenGenerator.createCustomToken(mocks.uid, blacklistedDeveloperClaims); @@ -415,7 +225,7 @@ describe('FirebaseTokenGenerator', () => { return tokenGenerator.createCustomToken(mocks.uid) .then((token) => { const decoded = jwt.decode(token); - const expected: {[key: string]: any} = { + const expected: { [key: string]: any } = { uid: mocks.uid, iat: 1, exp: ONE_HOUR_IN_SECONDS + 1, @@ -440,7 +250,7 @@ describe('FirebaseTokenGenerator', () => { .then((token) => { const decoded = jwt.decode(token); - const expected: {[key: string]: any} = { + const expected: { [key: string]: any } = { uid: mocks.uid, iat: 1, exp: ONE_HOUR_IN_SECONDS + 1, @@ -526,4 +336,47 @@ describe('FirebaseTokenGenerator', () => { }); }); }); + + describe('handleCryptoSignerError', () => { + it('should convert CryptoSignerError to FirebaseAuthError', () => { + const cryptoError = new CryptoSignerError({ + code: CryptoSignerErrorCode.INVALID_ARGUMENT, + message: 'test error.', + }); + const authError = handleCryptoSignerError(cryptoError); + expect(authError).to.be.an.instanceof(FirebaseAuthError); + expect(authError).to.have.property('code', 'auth/argument-error'); + expect(authError).to.have.property('message', 'test error.'); + }); + + it('should convert CryptoSignerError HttpError to FirebaseAuthError', () => { + const cryptoError = new CryptoSignerError({ + code: CryptoSignerErrorCode.SERVER_ERROR, + message: 'test error.', + cause: utils.errorFrom({ + error: { + message: 'server error.', + }, + }) + }); + const authError = handleCryptoSignerError(cryptoError); + expect(authError).to.be.an.instanceof(FirebaseAuthError); + expect(authError).to.have.property('code', 'auth/internal-error'); + expect(authError).to.have.property('message', 'server error.; Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens for more details on how to use and troubleshoot this feature. Raw server response: "{"error":{"message":"server error."}}"'); + }); + + it('should convert CryptoSignerError HttpError with no errorcode to FirebaseAuthError', () => { + const cryptoError = new CryptoSignerError({ + code: CryptoSignerErrorCode.SERVER_ERROR, + message: 'test error.', + cause: utils.errorFrom('server error.') + }); + const authError = handleCryptoSignerError(cryptoError); + expect(authError).to.be.an.instanceof(FirebaseAuthError); + expect(authError).to.have.property('code', 'auth/internal-error'); + expect(authError).to.have.property('message', + 'Error returned from server: null. Additionally, an internal error occurred ' + + 'while attempting to extract the errorcode from the error.'); + }); + }); }); diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index 52cd6d3c25..ea3bd9ff90 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -27,7 +27,8 @@ import { Agent } from 'http'; import LegacyFirebaseTokenGenerator = require('firebase-token-generator'); import * as mocks from '../../resources/mocks'; -import { FirebaseTokenGenerator, ServiceAccountSigner } from '../../../src/auth/token-generator'; +import { FirebaseTokenGenerator } from '../../../src/auth/token-generator'; +import { ServiceAccountSigner } from '../../../src/utils/crypto-signer'; import * as verifier from '../../../src/auth/token-verifier'; import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { FirebaseApp } from '../../../src/app/firebase-app'; @@ -43,7 +44,7 @@ const expect = chai.expect; const ONE_HOUR_IN_SECONDS = 60 * 60; function createTokenVerifier( - app: FirebaseApp, + app: FirebaseApp ): verifier.FirebaseTokenVerifier { return new verifier.FirebaseTokenVerifier( 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', @@ -447,7 +448,7 @@ describe('FirebaseTokenVerifier', () => { createTokenVerifier(mockAppWithAgent); expect(verifierSpy.args[0][1]).to.equal(agentForApp); - + verifierSpy.restore(); }); diff --git a/test/unit/database/database.spec.ts b/test/unit/database/database.spec.ts index 23e9b54a07..e299ae0fe0 100644 --- a/test/unit/database/database.spec.ts +++ b/test/unit/database/database.spec.ts @@ -208,9 +208,7 @@ describe('Database', () => { }); }); - // Currently doesn't work as expected since onTokenChange() can force a token refresh - // by calling getToken(). Skipping for now. - xit('should not reschedule when the token is about to expire in 5 minutes', () => { + it('should not reschedule when the token is about to expire in 5 minutes', () => { database.getDatabase(); return mockApp.INTERNAL.getToken() .then((token1) => { diff --git a/test/unit/firebase.spec.ts b/test/unit/firebase.spec.ts index 793da4a2fc..0edb814ce0 100644 --- a/test/unit/firebase.spec.ts +++ b/test/unit/firebase.spec.ts @@ -235,6 +235,21 @@ describe('Firebase', () => { }); }); + describe('#appCheck', () => { + it('should throw if the app has not been initialized', () => { + expect(() => { + return firebaseAdmin.appCheck(); + }).to.throw('The default Firebase app does not exist.'); + }); + + it('should return the appCheck service', () => { + firebaseAdmin.initializeApp(mocks.appOptions); + expect(() => { + return firebaseAdmin.appCheck(); + }).not.to.throw(); + }); + }); + describe('#storage', () => { it('should throw if the app has not be initialized', () => { expect(() => { diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index b5eade74b1..7434f07366 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -28,6 +28,7 @@ import './utils/error.spec'; import './utils/validator.spec'; import './utils/api-request.spec'; import './utils/jwt.spec'; +import './utils/crypto-signer.spec'; // Auth import './auth/auth.spec'; @@ -85,3 +86,9 @@ import './security-rules/security-rules-api-client.spec'; import './remote-config/index.spec'; import './remote-config/remote-config.spec'; import './remote-config/remote-config-api-client.spec'; + +// AppCheck +import './app-check/app-check.spec'; +import './app-check/app-check-api-client-internal.spec'; +import './app-check/token-generator.spec'; +import './app-check/token-verifier.spec.ts'; diff --git a/test/unit/storage/storage.spec.ts b/test/unit/storage/storage.spec.ts index 985b9b29af..ea656a1e92 100644 --- a/test/unit/storage/storage.spec.ts +++ b/test/unit/storage/storage.spec.ts @@ -115,19 +115,28 @@ describe('Storage', () => { }); describe('Emulator mode', () => { - const EMULATOR_HOST = 'http://localhost:9199'; + const VALID_EMULATOR_HOST = 'localhost:9199'; + const INVALID_EMULATOR_HOST = 'https://localhost:9199'; - before(() => { + beforeEach(() => { delete process.env.STORAGE_EMULATOR_HOST; - process.env.FIREBASE_STORAGE_EMULATOR_HOST = EMULATOR_HOST; + delete process.env.FIREBASE_STORAGE_EMULATOR_HOST; }); it('sets STORAGE_EMULATOR_HOST if FIREBASE_STORAGE_EMULATOR_HOST is set', () => { - new Storage(mockApp); - - expect(process.env.STORAGE_EMULATOR_HOST).to.equal(EMULATOR_HOST); + process.env.FIREBASE_STORAGE_EMULATOR_HOST = VALID_EMULATOR_HOST; + + new Storage(mockApp) + expect(process.env.STORAGE_EMULATOR_HOST).to.equal(`http://${VALID_EMULATOR_HOST}`); + }); + + it('throws if FIREBASE_STORAGE_EMULATOR_HOST has a protocol', () => { + process.env.FIREBASE_STORAGE_EMULATOR_HOST = INVALID_EMULATOR_HOST; + + expect(() => new Storage(mockApp)).to.throw( + 'FIREBASE_STORAGE_EMULATOR_HOST should not contain a protocol'); }); - + after(() => { delete process.env.STORAGE_EMULATOR_HOST; delete process.env.FIREBASE_STORAGE_EMULATOR_HOST; diff --git a/test/unit/utils/crypto-signer.spec.ts b/test/unit/utils/crypto-signer.spec.ts new file mode 100644 index 0000000000..efda058c02 --- /dev/null +++ b/test/unit/utils/crypto-signer.spec.ts @@ -0,0 +1,224 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as chai from 'chai'; +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as mocks from '../../resources/mocks'; +import { ServiceAccountSigner, IAMSigner, CryptoSignerError } from '../../../src/utils/crypto-signer'; + +import { ServiceAccountCredential } from '../../../src/app/credential-internal'; +import { AuthorizedHttpClient, HttpClient } from '../../../src/utils/api-request'; +import { FirebaseApp } from '../../../src/app/firebase-app'; +import * as utils from '../utils'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('CryptoSigner', () => { + describe('ServiceAccountSigner', () => { + it('should throw given no arguments', () => { + expect(() => { + const anyServiceAccountSigner: any = ServiceAccountSigner; + return new anyServiceAccountSigner(); + }).to.throw('Must provide a service account credential to initialize ServiceAccountSigner'); + }); + + it('should not throw given a valid certificate', () => { + expect(() => { + return new ServiceAccountSigner(new ServiceAccountCredential(mocks.certificateObject)); + }).not.to.throw(); + }); + + it('should sign using the private_key in the certificate', () => { + const payload = Buffer.from('test'); + const cert = new ServiceAccountCredential(mocks.certificateObject); + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const crypto = require('crypto'); + const rsa = crypto.createSign('RSA-SHA256'); + rsa.update(payload); + const result = rsa.sign(cert.privateKey, 'base64'); + + const signer = new ServiceAccountSigner(cert); + return signer.sign(payload).then((signature) => { + expect(signature.toString('base64')).to.equal(result); + }); + }); + + it('should return the client_email from the certificate', () => { + const cert = new ServiceAccountCredential(mocks.certificateObject); + const signer = new ServiceAccountSigner(cert); + return signer.getAccountId().should.eventually.equal(cert.clientEmail); + }); + }); + + describe('IAMSigner', () => { + let mockApp: FirebaseApp; + let getTokenStub: sinon.SinonStub; + const mockAccessToken: string = utils.generateRandomAccessToken(); + + beforeEach(() => { + mockApp = mocks.app(); + getTokenStub = utils.stubGetAccessToken(mockAccessToken, mockApp); + return mockApp.INTERNAL.getToken(); + }); + + afterEach(() => { + getTokenStub.restore(); + return mockApp.delete(); + }); + + it('should throw given no arguments', () => { + expect(() => { + const anyIAMSigner: any = IAMSigner; + return new anyIAMSigner(); + }).to.throw('Must provide a HTTP client to initialize IAMSigner'); + }); + + describe('explicit service account ID', () => { + const response = { signedBlob: Buffer.from('testsignature').toString('base64') }; + const input = Buffer.from('input'); + const signRequest = { + method: 'POST', + url: 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/test-service-account:signBlob', + headers: { Authorization: `Bearer ${mockAccessToken}` }, + data: { payload: input.toString('base64') }, + }; + let stub: sinon.SinonStub; + + afterEach(() => { + stub.restore(); + }); + + it('should sign using the IAM service', () => { + const expectedResult = utils.responseFrom(response); + stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); + const requestHandler = new AuthorizedHttpClient(mockApp); + const signer = new IAMSigner(requestHandler, 'test-service-account'); + return signer.sign(input).then((signature) => { + expect(signature.toString('base64')).to.equal(response.signedBlob); + expect(stub).to.have.been.calledOnce.and.calledWith(signRequest); + }); + }); + + it('should fail if the IAM service responds with an error', () => { + const expectedResult = utils.errorFrom({ + error: { + status: 'PROJECT_NOT_FOUND', + message: 'test reason', + }, + }); + stub = sinon.stub(HttpClient.prototype, 'send').rejects(expectedResult); + const requestHandler = new AuthorizedHttpClient(mockApp); + const signer = new IAMSigner(requestHandler, 'test-service-account'); + return signer.sign(input).catch((err) => { + expect(err).to.be.instanceOf(CryptoSignerError); + expect(err.message).to.equal('Server responded with status 500.'); + expect(err.cause).to.deep.equal(expectedResult); + expect(stub).to.have.been.calledOnce.and.calledWith(signRequest); + }); + }); + + it('should return the explicitly specified service account', () => { + const signer = new IAMSigner(new AuthorizedHttpClient(mockApp), 'test-service-account'); + return signer.getAccountId().should.eventually.equal('test-service-account'); + }); + }); + + describe('auto discovered service account', () => { + const input = Buffer.from('input'); + const response = { signedBlob: Buffer.from('testsignature').toString('base64') }; + const metadataRequest = { + method: 'GET', + url: 'http://metadata/computeMetadata/v1/instance/service-accounts/default/email', + headers: { 'Metadata-Flavor': 'Google' }, + }; + const signRequest = { + method: 'POST', + url: 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/discovered-service-account:signBlob', + headers: { Authorization: `Bearer ${mockAccessToken}` }, + data: { payload: input.toString('base64') }, + }; + let stub: sinon.SinonStub; + + afterEach(() => { + stub.restore(); + }); + + it('should sign using the IAM service', () => { + stub = sinon.stub(HttpClient.prototype, 'send'); + stub.onCall(0).resolves(utils.responseFrom('discovered-service-account')); + stub.onCall(1).resolves(utils.responseFrom(response)); + const requestHandler = new AuthorizedHttpClient(mockApp); + const signer = new IAMSigner(requestHandler); + return signer.sign(input).then((signature) => { + expect(signature.toString('base64')).to.equal(response.signedBlob); + expect(stub).to.have.been.calledTwice; + expect(stub.getCall(0).args[0]).to.deep.equal(metadataRequest); + expect(stub.getCall(1).args[0]).to.deep.equal(signRequest); + }); + }); + + it('should fail if the IAM service responds with an error', () => { + const expectedResult = utils.errorFrom({ + error: { + status: 'PROJECT_NOT_FOUND', + message: 'test reason', + }, + }); + stub = sinon.stub(HttpClient.prototype, 'send'); + stub.onCall(0).resolves(utils.responseFrom('discovered-service-account')); + stub.onCall(1).rejects(expectedResult); + const requestHandler = new AuthorizedHttpClient(mockApp); + const signer = new IAMSigner(requestHandler); + return signer.sign(input).catch((err) => { + expect(err).to.be.instanceOf(CryptoSignerError); + expect(err.message).to.equal('Server responded with status 500.'); + expect(err.cause).to.deep.equal(expectedResult); + expect(stub).to.have.been.calledTwice; + expect(stub.getCall(0).args[0]).to.deep.equal(metadataRequest); + expect(stub.getCall(1).args[0]).to.deep.equal(signRequest); + }); + }); + + it('should return the discovered service account', () => { + stub = sinon.stub(HttpClient.prototype, 'send'); + stub.onCall(0).resolves(utils.responseFrom('discovered-service-account')); + const signer = new IAMSigner(new AuthorizedHttpClient(mockApp)); + return signer.getAccountId().should.eventually.equal('discovered-service-account'); + }); + + it('should return the expected error when failed to contact the Metadata server', () => { + stub = sinon.stub(HttpClient.prototype, 'send'); + stub.onCall(0).rejects(utils.errorFrom('test error')); + const signer = new IAMSigner(new AuthorizedHttpClient(mockApp)); + const expected = 'Failed to determine service account. Make sure to initialize the SDK with ' + + 'a service account credential. Alternatively specify a service account with ' + + 'iam.serviceAccounts.signBlob permission.'; + return signer.getAccountId().should.eventually.be.rejectedWith(expected); + }); + }); + }); +}); diff --git a/test/unit/utils/jwt.spec.ts b/test/unit/utils/jwt.spec.ts index 525608feef..3b7a02a4de 100644 --- a/test/unit/utils/jwt.spec.ts +++ b/test/unit/utils/jwt.spec.ts @@ -23,16 +23,19 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as nock from 'nock'; import * as sinon from 'sinon'; -//import * as sinonChai from 'sinon-chai'; -//import * as chaiAsPromised from 'chai-as-promised'; import * as mocks from '../../resources/mocks'; -import * as jwtUtil from '../../../src/utils/jwt'; +import { + ALGORITHM_RS256, DecodedToken, decodeJwt, EmulatorSignatureVerifier, JwksFetcher, + JwtErrorCode, PublicKeySignatureVerifier, UrlKeyFetcher, verifyJwtSignature +} from '../../../src/utils/jwt'; const expect = chai.expect; const ONE_HOUR_IN_SECONDS = 60 * 60; +const ONE_DAY_IN_SECONDS = 86400; const publicCertPath = '/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'; +const jwksPath = '/v1alpha/jwks'; /** * Returns a mocked out success response from the URL containing the public keys for the Google certs. @@ -80,6 +83,43 @@ function mockFailedFetchPublicKeys(): nock.Scope { .replyWithError('message'); } +/** + * Returns a mocked out success JWKS response. + * + * @returns A nock response object. + */ +function mockFetchJsonWebKeys(path: string = jwksPath): nock.Scope { + return nock('https://firebaseappcheck.googleapis.com') + .get(path) + .reply(200, mocks.jwksResponse); +} + +/** + * Returns a mocked out error response for JWKS. + * The status code is 200 but the response itself will contain an 'error' key. + * + * @returns A nock response object. + */ +function mockFetchJsonWebKeysWithErrorResponse(): nock.Scope { + return nock('https://firebaseappcheck.googleapis.com') + .get(jwksPath) + .reply(200, { + error: 'message', + error_description: 'description', // eslint-disable-line @typescript-eslint/camelcase + }); +} + +/** + * Returns a mocked out failed JSON Web Keys response. + * The status code is non-200 and the response itself will fail. + * + * @returns A nock response object. + */ +function mockFailedFetchJsonWebKeys(): nock.Scope { + return nock('https://firebaseappcheck.googleapis.com') + .get(jwksPath) + .replyWithError('message'); +} const TOKEN_PAYLOAD = { one: 'uno', @@ -91,7 +131,7 @@ const TOKEN_PAYLOAD = { sub: mocks.uid, }; -const DECODED_SIGNED_TOKEN: jwtUtil.DecodedToken = { +const DECODED_SIGNED_TOKEN: DecodedToken = { header: { alg: 'RS256', kid: 'aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd', @@ -100,7 +140,7 @@ const DECODED_SIGNED_TOKEN: jwtUtil.DecodedToken = { payload: TOKEN_PAYLOAD }; -const DECODED_UNSIGNED_TOKEN: jwtUtil.DecodedToken = { +const DECODED_UNSIGNED_TOKEN: DecodedToken = { header: { alg: 'none', typ: 'JWT', @@ -122,25 +162,25 @@ describe('decodeJwt', () => { }); it('should reject given no token', () => { - return (jwtUtil.decodeJwt as any)() + return (decodeJwt as any)() .should.eventually.be.rejectedWith('The provided token must be a string.'); }); const invalidIdTokens = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop]; invalidIdTokens.forEach((invalidIdToken) => { it('should reject given a non-string token: ' + JSON.stringify(invalidIdToken), () => { - return jwtUtil.decodeJwt(invalidIdToken as any) + return decodeJwt(invalidIdToken as any) .should.eventually.be.rejectedWith('The provided token must be a string.'); }); }); it('should reject given an empty string token', () => { - return jwtUtil.decodeJwt('') + return decodeJwt('') .should.eventually.be.rejectedWith('Decoding token failed.'); }); it('should reject given an invalid token', () => { - return jwtUtil.decodeJwt('invalid-token') + return decodeJwt('invalid-token') .should.eventually.be.rejectedWith('Decoding token failed.'); }); @@ -149,7 +189,7 @@ describe('decodeJwt', () => { const mockIdToken = mocks.generateIdToken(); - return jwtUtil.decodeJwt(mockIdToken) + return decodeJwt(mockIdToken) .should.eventually.be.fulfilled.and.deep.equal(DECODED_SIGNED_TOKEN); }); @@ -161,7 +201,7 @@ describe('decodeJwt', () => { header: {} }); - return jwtUtil.decodeJwt(mockIdToken) + return decodeJwt(mockIdToken) .should.eventually.be.fulfilled.and.deep.equal(DECODED_UNSIGNED_TOKEN); }); }); @@ -178,28 +218,28 @@ describe('verifyJwtSignature', () => { }); it('should throw given no token', () => { - return (jwtUtil.verifyJwtSignature as any)() + return (verifyJwtSignature as any)() .should.eventually.be.rejectedWith('The provided token must be a string.'); }); const invalidIdTokens = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop]; invalidIdTokens.forEach((invalidIdToken) => { it('should reject given a non-string token: ' + JSON.stringify(invalidIdToken), () => { - return jwtUtil.verifyJwtSignature(invalidIdToken as any, mocks.keyPairs[0].public) + return verifyJwtSignature(invalidIdToken as any, mocks.keyPairs[0].public) .should.eventually.be.rejectedWith('The provided token must be a string.'); }); }); it('should reject given an empty string token', () => { - return jwtUtil.verifyJwtSignature('', mocks.keyPairs[0].public) + return verifyJwtSignature('', mocks.keyPairs[0].public) .should.eventually.be.rejectedWith('jwt must be provided'); }); it('should be fulfilled given a valid signed token and public key', () => { const mockIdToken = mocks.generateIdToken(); - return jwtUtil.verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, - { algorithms: [jwtUtil.ALGORITHM_RS256] }) + return verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, + { algorithms: [ALGORITHM_RS256] }) .should.eventually.be.fulfilled; }); @@ -209,7 +249,7 @@ describe('verifyJwtSignature', () => { header: {} }); - return jwtUtil.verifyJwtSignature(mockIdToken, '') + return verifyJwtSignature(mockIdToken, '') .should.eventually.be.fulfilled; }); @@ -217,18 +257,18 @@ describe('verifyJwtSignature', () => { const mockIdToken = mocks.generateIdToken(); const getKeyCallback = (_: any, callback: any): void => callback(null, mocks.keyPairs[0].public); - return jwtUtil.verifyJwtSignature(mockIdToken, getKeyCallback, - { algorithms: [jwtUtil.ALGORITHM_RS256] }) + return verifyJwtSignature(mockIdToken, getKeyCallback, + { algorithms: [ALGORITHM_RS256] }) .should.eventually.be.fulfilled; }); it('should be rejected when the given algorithm does not match the token', () => { const mockIdToken = mocks.generateIdToken(); - return jwtUtil.verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, + return verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, { algorithms: ['RS384'] }) .should.eventually.be.rejectedWith('invalid algorithm') - .with.property('code', jwtUtil.JwtErrorCode.INVALID_SIGNATURE); + .with.property('code', JwtErrorCode.INVALID_SIGNATURE); }); it('should be rejected given an expired token', () => { @@ -237,18 +277,18 @@ describe('verifyJwtSignature', () => { clock.tick((ONE_HOUR_IN_SECONDS * 1000) - 1); // token should still be valid - return jwtUtil.verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, - { algorithms: [jwtUtil.ALGORITHM_RS256] }) + return verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, + { algorithms: [ALGORITHM_RS256] }) .then(() => { clock!.tick(1); // token should now be invalid - return jwtUtil.verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, - { algorithms: [jwtUtil.ALGORITHM_RS256] }) + return verifyJwtSignature(mockIdToken, mocks.keyPairs[0].public, + { algorithms: [ALGORITHM_RS256] }) .should.eventually.be.rejectedWith( 'The provided token has expired. Get a fresh token from your client app and try again.' ) - .with.property('code', jwtUtil.JwtErrorCode.TOKEN_EXPIRED); + .with.property('code', JwtErrorCode.TOKEN_EXPIRED); }); }); @@ -257,10 +297,10 @@ describe('verifyJwtSignature', () => { const getKeyCallback = (_: any, callback: any): void => callback(new Error('key fetch failed.')); - return jwtUtil.verifyJwtSignature(mockIdToken, getKeyCallback, - { algorithms: [jwtUtil.ALGORITHM_RS256] }) + return verifyJwtSignature(mockIdToken, getKeyCallback, + { algorithms: [ALGORITHM_RS256] }) .should.eventually.be.rejectedWith('key fetch failed.') - .with.property('code', jwtUtil.JwtErrorCode.KEY_FETCH_ERROR); + .with.property('code', JwtErrorCode.KEY_FETCH_ERROR); }); it('should be rejected with correct no matching key id found error.', () => { @@ -268,43 +308,49 @@ describe('verifyJwtSignature', () => { const getKeyCallback = (_: any, callback: any): void => callback(new Error('no-matching-kid-error')); - return jwtUtil.verifyJwtSignature(mockIdToken, getKeyCallback, - { algorithms: [jwtUtil.ALGORITHM_RS256] }) + return verifyJwtSignature(mockIdToken, getKeyCallback, + { algorithms: [ALGORITHM_RS256] }) .should.eventually.be.rejectedWith('no-matching-kid-error') - .with.property('code', jwtUtil.JwtErrorCode.NO_MATCHING_KID); + .with.property('code', JwtErrorCode.NO_MATCHING_KID); }); it('should be rejected given a public key that does not match the token.', () => { const mockIdToken = mocks.generateIdToken(); - return jwtUtil.verifyJwtSignature(mockIdToken, mocks.keyPairs[1].public, - { algorithms: [jwtUtil.ALGORITHM_RS256] }) + return verifyJwtSignature(mockIdToken, mocks.keyPairs[1].public, + { algorithms: [ALGORITHM_RS256] }) .should.eventually.be.rejectedWith('invalid signature') - .with.property('code', jwtUtil.JwtErrorCode.INVALID_SIGNATURE); + .with.property('code', JwtErrorCode.INVALID_SIGNATURE); }); it('should be rejected given an invalid JWT.', () => { - return jwtUtil.verifyJwtSignature('invalid-token', mocks.keyPairs[0].public) + return verifyJwtSignature('invalid-token', mocks.keyPairs[0].public) .should.eventually.be.rejectedWith('jwt malformed') - .with.property('code', jwtUtil.JwtErrorCode.INVALID_SIGNATURE); + .with.property('code', JwtErrorCode.INVALID_SIGNATURE); }); }); describe('PublicKeySignatureVerifier', () => { let stubs: sinon.SinonStub[] = []; - const verifier = new jwtUtil.PublicKeySignatureVerifier( - new jwtUtil.UrlKeyFetcher('https://www.example.com/publicKeys')); + let clock: sinon.SinonFakeTimers | undefined; + const verifier = new PublicKeySignatureVerifier( + new UrlKeyFetcher('https://www.example.com/publicKeys')); afterEach(() => { _.forEach(stubs, (stub) => stub.restore()); stubs = []; + + if (clock) { + clock.restore(); + clock = undefined; + } }); describe('Constructor', () => { it('should not throw when valid key fetcher is provided', () => { expect(() => { - new jwtUtil.PublicKeySignatureVerifier( - new jwtUtil.UrlKeyFetcher('https://www.example.com/publicKeys')); + new PublicKeySignatureVerifier( + new UrlKeyFetcher('https://www.example.com/publicKeys')); }).not.to.throw(); }); @@ -312,17 +358,27 @@ describe('PublicKeySignatureVerifier', () => { invalidKeyFetchers.forEach((invalidKeyFetcher) => { it('should throw given an invalid key fetcher: ' + JSON.stringify(invalidKeyFetcher), () => { expect(() => { - new jwtUtil.PublicKeySignatureVerifier(invalidKeyFetchers as any); + new PublicKeySignatureVerifier(invalidKeyFetchers as any); }).to.throw('The provided key fetcher is not an object or null.'); }); }); }); describe('withCertificateUrl', () => { - it('should return a PublicKeySignatureVerifier instance when a valid cert url is provided', () => { - expect( - jwtUtil.PublicKeySignatureVerifier.withCertificateUrl('https://www.example.com/publicKeys') - ).to.be.an.instanceOf(jwtUtil.PublicKeySignatureVerifier); + it('should return a PublicKeySignatureVerifier instance with a UrlKeyFetcher when a ' + + 'valid cert url is provided', () => { + const verifier = PublicKeySignatureVerifier.withCertificateUrl('https://www.example.com/publicKeys'); + expect(verifier).to.be.an.instanceOf(PublicKeySignatureVerifier); + expect((verifier as any).keyFetcher).to.be.an.instanceOf(UrlKeyFetcher); + }); + }); + + describe('withJwksUrl', () => { + it('should return a PublicKeySignatureVerifier instance with a JwksFetcher when a ' + + 'valid jwks url is provided', () => { + const verifier = PublicKeySignatureVerifier.withJwksUrl('https://www.example.com/publicKeys'); + expect(verifier).to.be.an.instanceOf(PublicKeySignatureVerifier); + expect((verifier as any).keyFetcher).to.be.an.instanceOf(JwksFetcher); }); }); @@ -345,8 +401,8 @@ describe('PublicKeySignatureVerifier', () => { .should.eventually.be.rejectedWith('jwt must be provided'); }); - it('should be fullfilled given a valid token', () => { - const keyFetcherStub = sinon.stub(jwtUtil.UrlKeyFetcher.prototype, 'fetchPublicKeys') + it('should be fulfilled given a valid token', () => { + const keyFetcherStub = sinon.stub(UrlKeyFetcher.prototype, 'fetchPublicKeys') .resolves(VALID_PUBLIC_KEYS_RESPONSE); stubs.push(keyFetcherStub); const mockIdToken = mocks.generateIdToken(); @@ -354,8 +410,41 @@ describe('PublicKeySignatureVerifier', () => { return verifier.verify(mockIdToken).should.eventually.be.fulfilled; }); + it('should be fulfilled given a valid token without a kid (should check against all the keys)', () => { + const keyFetcherStub = sinon.stub(UrlKeyFetcher.prototype, 'fetchPublicKeys') + .resolves({ 'kid-other': 'key-other', ...VALID_PUBLIC_KEYS_RESPONSE }); + stubs.push(keyFetcherStub); + const mockIdToken = mocks.generateIdToken({ + header: {} + }); + + return verifier.verify(mockIdToken).should.eventually.be.fulfilled; + }); + + it('should be rejected given an expired token without a kid (should check against all the keys)', () => { + const keyFetcherStub = sinon.stub(UrlKeyFetcher.prototype, 'fetchPublicKeys') + .resolves({ 'kid-other': 'key-other', ...VALID_PUBLIC_KEYS_RESPONSE }); + stubs.push(keyFetcherStub); + clock = sinon.useFakeTimers(1000); + const mockIdToken = mocks.generateIdToken({ + header: {} + }); + clock.tick((ONE_HOUR_IN_SECONDS * 1000) - 1); + + // token should still be valid + return verifier.verify(mockIdToken) + .then(() => { + clock!.tick(1); + + // token should now be invalid + return verifier.verify(mockIdToken).should.eventually.be.rejectedWith( + 'The provided token has expired. Get a fresh token from your client app and try again.') + .with.property('code', JwtErrorCode.TOKEN_EXPIRED); + }); + }); + it('should be rejected given a token with an incorrect algorithm', () => { - const keyFetcherStub = sinon.stub(jwtUtil.UrlKeyFetcher.prototype, 'fetchPublicKeys') + const keyFetcherStub = sinon.stub(UrlKeyFetcher.prototype, 'fetchPublicKeys') .resolves(VALID_PUBLIC_KEYS_RESPONSE); stubs.push(keyFetcherStub); const mockIdToken = mocks.generateIdToken({ @@ -364,36 +453,36 @@ describe('PublicKeySignatureVerifier', () => { return verifier.verify(mockIdToken).should.eventually.be .rejectedWith('invalid algorithm') - .with.property('code', jwtUtil.JwtErrorCode.INVALID_SIGNATURE); + .with.property('code', JwtErrorCode.INVALID_SIGNATURE); }); // tests to cover the private getKeyCallback function. it('should reject when no matching kid found', () => { - const keyFetcherStub = sinon.stub(jwtUtil.UrlKeyFetcher.prototype, 'fetchPublicKeys') + const keyFetcherStub = sinon.stub(UrlKeyFetcher.prototype, 'fetchPublicKeys') .resolves({ 'not-a-matching-key': 'public-key' }); stubs.push(keyFetcherStub); const mockIdToken = mocks.generateIdToken(); return verifier.verify(mockIdToken).should.eventually.be .rejectedWith('no-matching-kid-error') - .with.property('code', jwtUtil.JwtErrorCode.NO_MATCHING_KID); + .with.property('code', JwtErrorCode.NO_MATCHING_KID); }); it('should reject when an error occurs while fetching the keys', () => { - const keyFetcherStub = sinon.stub(jwtUtil.UrlKeyFetcher.prototype, 'fetchPublicKeys') + const keyFetcherStub = sinon.stub(UrlKeyFetcher.prototype, 'fetchPublicKeys') .rejects(new Error('Error fetching public keys.')); stubs.push(keyFetcherStub); const mockIdToken = mocks.generateIdToken(); return verifier.verify(mockIdToken).should.eventually.be .rejectedWith('Error fetching public keys.') - .with.property('code', jwtUtil.JwtErrorCode.KEY_FETCH_ERROR); + .with.property('code', JwtErrorCode.KEY_FETCH_ERROR); }); }); }); describe('EmulatorSignatureVerifier', () => { - const emulatorVerifier = new jwtUtil.EmulatorSignatureVerifier(); + const emulatorVerifier = new EmulatorSignatureVerifier(); describe('verify', () => { it('should be fullfilled given a valid unsigned (emulator) token', () => { @@ -415,12 +504,12 @@ describe('EmulatorSignatureVerifier', () => { describe('UrlKeyFetcher', () => { const agent = new https.Agent(); - let keyFetcher: jwtUtil.UrlKeyFetcher; + let keyFetcher: UrlKeyFetcher; let clock: sinon.SinonFakeTimers | undefined; let httpsSpy: sinon.SinonSpy; beforeEach(() => { - keyFetcher = new jwtUtil.UrlKeyFetcher( + keyFetcher = new UrlKeyFetcher( 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', agent); httpsSpy = sinon.spy(https, 'request'); @@ -441,7 +530,7 @@ describe('UrlKeyFetcher', () => { describe('Constructor', () => { it('should not throw when valid key parameters are provided', () => { expect(() => { - new jwtUtil.UrlKeyFetcher('https://www.example.com/publicKeys', agent); + new UrlKeyFetcher('https://www.example.com/publicKeys', agent); }).not.to.throw(); }); @@ -449,7 +538,7 @@ describe('UrlKeyFetcher', () => { invalidCertURLs.forEach((invalidCertUrl) => { it('should throw given a non-URL public cert: ' + JSON.stringify(invalidCertUrl), () => { expect(() => { - new jwtUtil.UrlKeyFetcher(invalidCertUrl as any, agent); + new UrlKeyFetcher(invalidCertUrl as any, agent); }).to.throw('The provided public client certificate URL is not a valid URL.'); }); }); @@ -465,7 +554,7 @@ describe('UrlKeyFetcher', () => { it('should use the given HTTP Agent', () => { const agent = new https.Agent(); - const urlKeyFetcher = new jwtUtil.UrlKeyFetcher('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', agent); + const urlKeyFetcher = new UrlKeyFetcher('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', agent); mockedRequests.push(mockFetchPublicKeys()); return urlKeyFetcher.fetchPublicKeys() @@ -478,7 +567,7 @@ describe('UrlKeyFetcher', () => { it('should not fetch the public keys until the first time fetchPublicKeys() is called', () => { mockedRequests.push(mockFetchPublicKeys()); - const urlKeyFetcher = new jwtUtil.UrlKeyFetcher('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', agent); + const urlKeyFetcher = new UrlKeyFetcher('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', agent); expect(https.request).not.to.have.been.called; return urlKeyFetcher.fetchPublicKeys() @@ -539,3 +628,121 @@ describe('UrlKeyFetcher', () => { }); }); }); + +describe('JwksFetcher', () => { + let keyFetcher: JwksFetcher; + let clock: sinon.SinonFakeTimers | undefined; + let httpsSpy: sinon.SinonSpy; + + beforeEach(() => { + keyFetcher = new JwksFetcher( + 'https://firebaseappcheck.googleapis.com/v1alpha/jwks' + ); + httpsSpy = sinon.spy(https, 'request'); + }); + + afterEach(() => { + if (clock) { + clock.restore(); + clock = undefined; + } + httpsSpy.restore(); + }); + + after(() => { + nock.cleanAll(); + }); + + describe('Constructor', () => { + it('should not throw when valid url is provided', () => { + expect(() => { + new JwksFetcher('https://www.example.com/publicKeys'); + }).not.to.throw(); + }); + + const invalidJwksURLs = [null, NaN, 0, 1, true, false, [], {}, { a: 1 }, _.noop, 'file://invalid']; + invalidJwksURLs.forEach((invalidJwksURL) => { + it('should throw given a non-URL jwks endpoint: ' + JSON.stringify(invalidJwksURL), () => { + expect(() => { + new JwksFetcher(invalidJwksURL as any); + }).to.throw('The provided JWKS URL is not a valid URL.'); + }); + }); + }); + + describe('fetchPublicKeys', () => { + let mockedRequests: nock.Scope[] = []; + + afterEach(() => { + _.forEach(mockedRequests, (mockedRequest) => mockedRequest.done()); + mockedRequests = []; + }); + + it('should not fetch the public keys until the first time fetchPublicKeys() is called', () => { + mockedRequests.push(mockFetchJsonWebKeys()); + + const jwksFetcher = new JwksFetcher('https://firebaseappcheck.googleapis.com/v1alpha/jwks'); + expect(https.request).not.to.have.been.called; + + return jwksFetcher.fetchPublicKeys() + .then((result) => { + expect(https.request).to.have.been.calledOnce; + expect(result).to.have.key(mocks.jwksResponse.keys[0].kid); + }); + }); + + it('should not re-fetch the public keys every time fetchPublicKeys() is called', () => { + mockedRequests.push(mockFetchJsonWebKeys()); + + return keyFetcher.fetchPublicKeys().then(() => { + expect(https.request).to.have.been.calledOnce; + return keyFetcher.fetchPublicKeys(); + }).then(() => expect(https.request).to.have.been.calledOnce); + }); + + it('should refresh the public keys after the previous set of keys expire', () => { + mockedRequests.push(mockFetchJsonWebKeys()); + mockedRequests.push(mockFetchJsonWebKeys()); + mockedRequests.push(mockFetchJsonWebKeys()); + + clock = sinon.useFakeTimers(1000); + + return keyFetcher.fetchPublicKeys().then(() => { + expect(https.request).to.have.been.calledOnce; + clock!.tick((ONE_DAY_IN_SECONDS - 1) * 1000); + return keyFetcher.fetchPublicKeys(); + }).then(() => { + expect(https.request).to.have.been.calledOnce; + clock!.tick(ONE_DAY_IN_SECONDS * 1000); // 24 hours in milliseconds + return keyFetcher.fetchPublicKeys(); + }).then(() => { + // App check keys do not contain cache headers so we cache the keys for 24 hours. + // 24 hours has passed + expect(https.request).to.have.been.calledTwice; + clock!.tick((ONE_DAY_IN_SECONDS - 1) * 1000); + return keyFetcher.fetchPublicKeys(); + }).then(() => { + expect(https.request).to.have.been.calledTwice; + clock!.tick(ONE_DAY_IN_SECONDS * 1000); + return keyFetcher.fetchPublicKeys(); + }).then(() => { + // 48 hours have passed + expect(https.request).to.have.been.calledThrice; + }); + }); + + it('should be rejected if fetching the public keys fails', () => { + mockedRequests.push(mockFailedFetchJsonWebKeys()); + + return keyFetcher.fetchPublicKeys() + .should.eventually.be.rejectedWith('message'); + }); + + it('should be rejected if fetching the public keys returns a response with an error message', () => { + mockedRequests.push(mockFetchJsonWebKeysWithErrorResponse()); + + return keyFetcher.fetchPublicKeys() + .should.eventually.be.rejectedWith('Error fetching Json Web Keys'); + }); + }); +}); From 244a9afb7668e412a28505a3eaf2345c91fe91ed Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 3 Jun 2021 10:33:34 -0700 Subject: [PATCH 35/41] fix: Applying extra markdown content before first header (#1317) * fix: Applying extra markdown content before first header * fix: Better reg-ex for detecting markdown heading --- docgen/post-process.js | 17 +++++++++++++---- package.json | 5 +++-- src/app-check/index.ts | 10 +++++----- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/docgen/post-process.js b/docgen/post-process.js index 7366b2a86d..4012797471 100644 --- a/docgen/post-process.js +++ b/docgen/post-process.js @@ -137,7 +137,7 @@ async function readExtraContentFrom(source) { const reader = readline.createInterface({ input: fs.createReadStream(source), }); - const content = ['']; + const content = []; for await (const line of reader) { content.push(line); } @@ -150,11 +150,20 @@ async function writeExtraContentTo(target, extra) { const reader = readline.createInterface({ input: fs.createReadStream(target), }); + + let firstHeadingSeen = false; for await (const line of reader) { - output.push(line); - if (line.startsWith('{% block body %}')) { - output.push(...extra); + // Insert extra content just before the first markdown heading. + if (line.match(/^\#+ /)) { + if (!firstHeadingSeen) { + output.push(...extra); + output.push(''); + } + + firstHeadingSeen = true; } + + output.push(line); } const outputBuffer = Buffer.from(output.join('\r\n')); diff --git a/package.json b/package.json index f82170e2ae..f26c11e9c6 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,12 @@ "test:coverage": "nyc npm run test:unit", "lint:src": "eslint src/ --ext .ts", "lint:test": "eslint test/ --ext .ts", - "apidocs": "run-s api-extractor:local api-documenter api-documenter:toc api-documenter:post", + "apidocs": "run-s api-extractor:local api-documenter", "api-extractor": "node generate-reports.js", "api-extractor:local": "npm run build && node generate-reports.js --local", "esm-wrap": "node generate-esm-wrapper.js", - "api-documenter": "api-documenter-fire markdown --input temp --output docgen/markdown -s", + "api-documenter": "run-s api-documenter:markdown api-documenter:toc api-documenter:post", + "api-documenter:markdown": "api-documenter-fire markdown --input temp --output docgen/markdown -s", "api-documenter:toc": "api-documenter-fire toc --input temp --output docgen/markdown -p /docs/reference/admin/node -s", "api-documenter:post": "node docgen/post-process.js" }, diff --git a/src/app-check/index.ts b/src/app-check/index.ts index 6552d9208d..ef347be57f 100644 --- a/src/app-check/index.ts +++ b/src/app-check/index.ts @@ -42,7 +42,7 @@ import { app } from '../firebase-namespace-api'; * @param app Optional app for which to return the `AppCheck` service. * If not provided, the default `AppCheck` service is returned. * - * @return The default `AppCheck` service if no + * @returns The default `AppCheck` service if no * app is provided, or the `AppCheck` service associated with the provided * app. */ @@ -59,10 +59,10 @@ export namespace appCheck { /** * Creates a new {@link appCheck.AppCheckToken `AppCheckToken`} that can be sent * back to a client. - * + * * @param appId The App ID of the Firebase App the token belongs to. * - * @return A promise that fulfills with a `AppCheckToken`. + * @returns A promise that fulfills with a `AppCheckToken`. */ createToken(appId: string): Promise; @@ -73,7 +73,7 @@ export namespace appCheck { * * @param appCheckToken The App Check token to verify. * - * @return A promise fulfilled with the + * @returns A promise fulfilled with the * token's decoded claims if the App Check token is valid; otherwise, a rejected * promise. */ @@ -146,7 +146,7 @@ export namespace appCheck { app_id: string; [key: string]: any; } - + /** * Interface representing a verified App Check token response. */ From e8be12e4b281359b40722c429a0f15e68ebf292b Mon Sep 17 00:00:00 2001 From: Lahiru Maramba Date: Fri, 4 Jun 2021 10:11:53 -0400 Subject: [PATCH 36/41] feat(fac): Expose FAC APIs from firebase-admin/app-check entry point (#1293) * feat(fac): Expose FAC APIs from firebase-admin/app-check entry point * Fix tsdoc syntax --- entrypoints.json | 4 + etc/firebase-admin.api.md | 34 ++-- etc/firebase-admin.app-check.api.md | 47 ++++++ package-lock.json | 30 ++-- package.json | 7 + .../app-check-api-client-internal.ts | 22 ++- src/app-check/app-check-api.ts | 94 +++++++++++ src/app-check/app-check-namespace.ts | 74 +++++++++ src/app-check/app-check.ts | 25 +-- src/app-check/index.ts | 150 ++++-------------- src/app-check/token-generator.ts | 2 +- src/app-check/token-verifier.ts | 7 +- src/app/firebase-app.ts | 8 +- src/auth/token-generator.ts | 4 +- src/firebase-namespace-api.ts | 4 +- src/utils/crypto-signer.ts | 5 +- .../integration/postcheck/esm/example.test.js | 6 + .../typescript/example-modular.test.ts | 6 + test/unit/app-check/token-generator.spec.ts | 4 +- test/unit/app/firebase-namespace.spec.ts | 2 +- 20 files changed, 330 insertions(+), 205 deletions(-) create mode 100644 etc/firebase-admin.app-check.api.md create mode 100644 src/app-check/app-check-api.ts create mode 100644 src/app-check/app-check-namespace.ts diff --git a/entrypoints.json b/entrypoints.json index 3c2d6c1e0c..d35b7b8edf 100644 --- a/entrypoints.json +++ b/entrypoints.json @@ -8,6 +8,10 @@ "typings": "./lib/app/index.d.ts", "dist": "./lib/app/index.js" }, + "firebase-admin/app-check": { + "typings": "./lib/app-check/index.d.ts", + "dist": "./lib/app-check/index.js" + }, "firebase-admin/auth": { "typings": "./lib/auth/index.d.ts", "dist": "./lib/auth/index.js" diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index a434fbc62a..c82698b5a3 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -44,34 +44,18 @@ export namespace app { } // @public -export function appCheck(app?: app.App): appCheck.AppCheck; +export function appCheck(app?: App): appCheck.AppCheck; // @public (undocumented) export namespace appCheck { - export interface AppCheck { - // (undocumented) - app: app.App; - createToken(appId: string): Promise; - verifyToken(appCheckToken: string): Promise; - } - export interface AppCheckToken { - token: string; - ttlMillis: number; - } - export interface DecodedAppCheckToken { - // (undocumented) - [key: string]: any; - app_id: string; - aud: string[]; - exp: number; - iat: number; - iss: string; - sub: string; - } - export interface VerifyAppCheckTokenResponse { - appId: string; - token: appCheck.DecodedAppCheckToken; - } + // Warning: (ae-forgotten-export) The symbol "AppCheck" needs to be exported by the entry point default-namespace.d.ts + export type AppCheck = AppCheck; + // Warning: (ae-forgotten-export) The symbol "AppCheckToken" needs to be exported by the entry point default-namespace.d.ts + export type AppCheckToken = AppCheckToken; + // Warning: (ae-forgotten-export) The symbol "DecodedAppCheckToken" needs to be exported by the entry point default-namespace.d.ts + export type DecodedAppCheckToken = DecodedAppCheckToken; + // Warning: (ae-forgotten-export) The symbol "VerifyAppCheckTokenResponse" needs to be exported by the entry point default-namespace.d.ts + export type VerifyAppCheckTokenResponse = VerifyAppCheckTokenResponse; } // @public diff --git a/etc/firebase-admin.app-check.api.md b/etc/firebase-admin.app-check.api.md new file mode 100644 index 0000000000..7455692bec --- /dev/null +++ b/etc/firebase-admin.app-check.api.md @@ -0,0 +1,47 @@ +## API Report File for "firebase-admin.app-check" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Agent } from 'http'; + +// @public +export class AppCheck { + // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts + // + // (undocumented) + readonly app: App; + createToken(appId: string): Promise; + verifyToken(appCheckToken: string): Promise; +} + +// @public +export interface AppCheckToken { + token: string; + ttlMillis: number; +} + +// @public +export interface DecodedAppCheckToken { + // (undocumented) + [key: string]: any; + app_id: string; + aud: string[]; + exp: number; + iat: number; + iss: string; + sub: string; +} + +// @public +export function getAppCheck(app?: App): AppCheck; + +// @public +export interface VerifyAppCheckTokenResponse { + appId: string; + token: DecodedAppCheckToken; +} + + +``` diff --git a/package-lock.json b/package-lock.json index 4fcc7c284c..d1097b9d83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1058,9 +1058,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.20.tgz", - "integrity": "sha512-8qqFN4W53IEWa9bdmuVrUcVkFemQWnt5DKPQ/oa8xKDYgtjCr2OO6NX5TIK49NLFr3mPYU2cLh92DQquC3oWWQ==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz", + "integrity": "sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA==", "requires": { "@types/node": "*", "@types/qs": "*", @@ -1141,9 +1141,9 @@ } }, "@types/node": { - "version": "15.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.6.1.tgz", - "integrity": "sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA==" + "version": "15.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.0.tgz", + "integrity": "sha512-+aHJvoCsVhO2ZCuT4o5JtcPrCPyDE3+1nvbDprYes+pPkEsbjH7AGUCNtjMOXS0fqH14t+B7yLzaqSz92FPWyw==" }, "@types/qs": { "version": "6.9.6", @@ -2033,9 +2033,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001232", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001232.tgz", - "integrity": "sha512-e4Gyp7P8vqC2qV2iHA+cJNf/yqUKOShXQOJHQt81OHxlIZl/j/j3soEA0adAQi8CPUQgvOdDENyQ5kd6a6mNSg==", + "version": "1.0.30001233", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001233.tgz", + "integrity": "sha512-BmkbxLfStqiPA7IEzQpIk0UFZFf3A4E6fzjPJ6OR+bFC2L8ES9J8zGA/asoi47p8XDVkev+WJo2I2Nc8c/34Yg==", "dev": true }, "caseless": { @@ -2804,9 +2804,9 @@ } }, "electron-to-chromium": { - "version": "1.3.743", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.743.tgz", - "integrity": "sha512-K2wXfo9iZQzNJNx67+Pld0DRF+9bYinj62gXCdgPhcu1vidwVuLPHQPPFnCdO55njWigXXpfBiT90jGUPbw8Zg==", + "version": "1.3.746", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.746.tgz", + "integrity": "sha512-3ffyGODL38apwSsIgXaWnAKNXChsjXhAmBTjbqCbrv1fBbVltuNLWh0zdrQbwK/oxPQ/Gss/kYfFAPPGu9mszQ==", "dev": true }, "emoji-regex": { @@ -4118,9 +4118,9 @@ } }, "google-auth-library": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.1.0.tgz", - "integrity": "sha512-X+gbkGjnLN3HUZP2W3KBREuA603BXd80ITvL0PeS0QpyDNYz/u0pIZ7aRuGnrSuUc0grk/qxEgtVTFt1ogbP+A==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.1.1.tgz", + "integrity": "sha512-+Q1linq/To3DYLyPz4UTEkQ0v5EOXadMM/S+taLV3W9611hq9zqg8kgGApqbTQnggtwdO9yU1y2YT7+83wdTRg==", "optional": true, "requires": { "arrify": "^2.0.0", diff --git a/package.json b/package.json index f26c11e9c6..a9e5f9a153 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,9 @@ "app": [ "lib/app" ], + "app-check": [ + "lib/app-check" + ], "auth": [ "lib/auth" ], @@ -103,6 +106,10 @@ "require": "./lib/app/index.js", "import": "./lib/esm/app/index.js" }, + "./app-check": { + "require": "./lib/app-check/index.js", + "import": "./lib/esm/app-check/index.js" + }, "./auth": { "require": "./lib/auth/index.js", "import": "./lib/esm/auth/index.js" diff --git a/src/app-check/app-check-api-client-internal.ts b/src/app-check/app-check-api-client-internal.ts index 640acb72aa..a57fa0a8e4 100644 --- a/src/app-check/app-check-api-client-internal.ts +++ b/src/app-check/app-check-api-client-internal.ts @@ -15,17 +15,15 @@ * limitations under the License. */ -import { appCheck } from './index'; +import { App } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; import { HttpRequestConfig, HttpClient, HttpError, AuthorizedHttpClient, HttpResponse } from '../utils/api-request'; -import { FirebaseApp } from '../app/firebase-app'; import { PrefixedFirebaseError } from '../utils/error'; - import * as utils from '../utils/index'; import * as validator from '../utils/validator'; - -import AppCheckToken = appCheck.AppCheckToken; +import { AppCheckToken } from './app-check-api' // App Check backend constants const FIREBASE_APP_CHECK_V1_API_URL_FORMAT = 'https://firebaseappcheck.googleapis.com/v1beta/projects/{projectId}/apps/{appId}:exchangeCustomToken'; @@ -43,13 +41,13 @@ export class AppCheckApiClient { private readonly httpClient: HttpClient; private projectId?: string; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { throw new FirebaseAppCheckError( 'invalid-argument', 'First argument passed to admin.appCheck() must be a valid Firebase app instance.'); } - this.httpClient = new AuthorizedHttpClient(app); + this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } /** @@ -57,7 +55,7 @@ export class AppCheckApiClient { * * @param customToken The custom token to be exchanged. * @param appId The mobile App ID. - * @return A promise that fulfills with a `AppCheckToken`. + * @returns A promise that fulfills with a `AppCheckToken`. */ public exchangeToken(customToken: string, appId: string): Promise { if (!validator.isNonEmptyString(appId)) { @@ -143,7 +141,7 @@ export class AppCheckApiClient { * Creates an AppCheckToken from the API response. * * @param resp API response object. - * @return An AppCheckToken instance. + * @returns An AppCheckToken instance. */ private toAppCheckToken(resp: HttpResponse): AppCheckToken { const token = resp.data.attestationToken; @@ -164,7 +162,7 @@ export class AppCheckApiClient { * is expressed as "3s", while 3 seconds and 1 nanosecond is expressed as "3.000000001s", * and 3 seconds and 1 microsecond is expressed as "3.000001s". * - * @return The duration in milliseconds. + * @returns The duration in milliseconds. */ private stringToMilliseconds(duration: string): number { if (!validator.isNonEmptyString(duration) || !duration.endsWith('s')) { @@ -211,8 +209,8 @@ export type AppCheckErrorCode = /** * Firebase App Check error code structure. This extends PrefixedFirebaseError. * - * @param {AppCheckErrorCode} code The error code. - * @param {string} message The error message. + * @param code The error code. + * @param message The error message. * @constructor */ export class FirebaseAppCheckError extends PrefixedFirebaseError { diff --git a/src/app-check/app-check-api.ts b/src/app-check/app-check-api.ts new file mode 100644 index 0000000000..4eeaa1bce5 --- /dev/null +++ b/src/app-check/app-check-api.ts @@ -0,0 +1,94 @@ +/*! + * @license + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Interface representing an App Check token. + */ +export interface AppCheckToken { + /** + * The Firebase App Check token. + */ + token: string; + + /** + * The time-to-live duration of the token in milliseconds. + */ + ttlMillis: number; +} + +/** + * Interface representing a decoded Firebase App Check token, returned from the + * {@link AppCheck.verifyToken} method. + */ +export interface DecodedAppCheckToken { + /** + * The issuer identifier for the issuer of the response. + * This value is a URL with the format + * `https://firebaseappcheck.googleapis.com/`, where `` is the + * same project number specified in the {@link DecodedAppCheckToken.aud | aud} property. + */ + iss: string; + + /** + * The Firebase App ID corresponding to the app the token belonged to. + * As a convenience, this value is copied over to the {@link DecodedAppCheckToken.app_id | app_id} property. + */ + sub: string; + + /** + * The audience for which this token is intended. + * This value is a JSON array of two strings, the first is the project number of your + * Firebase project, and the second is the project ID of the same project. + */ + aud: string[]; + + /** + * The App Check token's expiration time, in seconds since the Unix epoch. That is, the + * time at which this App Check token expires and should no longer be considered valid. + */ + exp: number; + + /** + * The App Check token's issued-at time, in seconds since the Unix epoch. That is, the + * time at which this App Check token was issued and should start to be considered + * valid. + */ + iat: number; + + /** + * The App ID corresponding to the App the App Check token belonged to. + * This value is not actually one of the JWT token claims. It is added as a + * convenience, and is set as the value of the {@link DecodedAppCheckToken.sub | sub} property. + */ + app_id: string; + [key: string]: any; +} + +/** + * Interface representing a verified App Check token response. + */ +export interface VerifyAppCheckTokenResponse { + /** + * The App ID corresponding to the App the App Check token belonged to. + */ + appId: string; + + /** + * The decoded Firebase App Check token. + */ + token: DecodedAppCheckToken; +} diff --git a/src/app-check/app-check-namespace.ts b/src/app-check/app-check-namespace.ts new file mode 100644 index 0000000000..4e5de379b2 --- /dev/null +++ b/src/app-check/app-check-namespace.ts @@ -0,0 +1,74 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App } from '../app'; +import { + AppCheckToken as TAppCheckToken, + DecodedAppCheckToken as TDecodedAppCheckToken, + VerifyAppCheckTokenResponse as TVerifyAppCheckTokenResponse, +} from './app-check-api'; +import { AppCheck as TAppCheck } from './app-check'; + +/** + * Gets the {@link firebase-admin.app-check#AppCheck} service for the default app or a given app. + * + * `admin.appCheck()` can be called with no arguments to access the default + * app's `AppCheck` service or as `admin.appCheck(app)` to access the + * `AppCheck` service associated with a specific app. + * + * @example + * ```javascript + * // Get the `AppCheck` service for the default app + * var defaultAppCheck = admin.appCheck(); + * ``` + * + * @example + * ```javascript + * // Get the `AppCheck` service for a given app + * var otherAppCheck = admin.appCheck(otherApp); + * ``` + * + * @param app Optional app for which to return the `AppCheck` service. + * If not provided, the default `AppCheck` service is returned. + * + * @returns The default `AppCheck` service if no + * app is provided, or the `AppCheck` service associated with the provided + * app. + */ +export declare function appCheck(app?: App): appCheck.AppCheck; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace appCheck { + /** + * Type alias to {@link firebase-admin.app-check#AppCheck}. + */ + export type AppCheck = TAppCheck; + + /** + * Type alias to {@link firebase-admin.app-check#AppCheckToken}. + */ + export type AppCheckToken = TAppCheckToken; + + /** + * Type alias to {@link firebase-admin.app-check#DecodedAppCheckToken}. + */ + export type DecodedAppCheckToken = TDecodedAppCheckToken; + + /** + * Type alias to {@link firebase-admin.app-check#VerifyAppCheckTokenResponse}. + */ + export type VerifyAppCheckTokenResponse = TVerifyAppCheckTokenResponse; +} diff --git a/src/app-check/app-check.ts b/src/app-check/app-check.ts index 1e79980820..815ab7bf8d 100644 --- a/src/app-check/app-check.ts +++ b/src/app-check/app-check.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { appCheck } from './index'; +import { App } from '../app'; import { AppCheckApiClient } from './app-check-api-client-internal'; import { appCheckErrorFromCryptoSignerError, AppCheckTokenGenerator @@ -23,15 +23,15 @@ import { import { AppCheckTokenVerifier } from './token-verifier'; import { cryptoSignerFromApp } from '../utils/crypto-signer'; -import AppCheckInterface = appCheck.AppCheck; -import AppCheckToken = appCheck.AppCheckToken; -import VerifyAppCheckTokenResponse = appCheck.VerifyAppCheckTokenResponse; -import { FirebaseApp } from '../app/firebase-app'; +import { + AppCheckToken, + VerifyAppCheckTokenResponse, +} from './app-check-api'; /** - * AppCheck service bound to the provided app. + * The Firebase `AppCheck` service interface. */ -export class AppCheck implements AppCheckInterface { +export class AppCheck { private readonly client: AppCheckApiClient; private readonly tokenGenerator: AppCheckTokenGenerator; @@ -40,8 +40,9 @@ export class AppCheck implements AppCheckInterface { /** * @param app The app for this AppCheck service. * @constructor + * @internal */ - constructor(readonly app: FirebaseApp) { + constructor(readonly app: App) { this.client = new AppCheckApiClient(app); try { this.tokenGenerator = new AppCheckTokenGenerator(cryptoSignerFromApp(app)); @@ -67,12 +68,14 @@ export class AppCheck implements AppCheckInterface { } /** - * Verifies an App Check token. + * Verifies a Firebase App Check token (JWT). If the token is valid, the promise is + * fulfilled with the token's decoded claims; otherwise, the promise is + * rejected. * * @param appCheckToken The App Check token to verify. * - * @returns A promise that fulfills with a `VerifyAppCheckTokenResponse` on successful - * verification. + * @returns A promise fulfilled with the token's decoded claims + * if the App Check token is valid; otherwise, a rejected promise. */ public verifyToken(appCheckToken: string): Promise { return this.appCheckTokenVerifier.verifyToken(appCheckToken) diff --git a/src/app-check/index.ts b/src/app-check/index.ts index ef347be57f..cf057ad7ab 100644 --- a/src/app-check/index.ts +++ b/src/app-check/index.ts @@ -15,28 +15,40 @@ * limitations under the License. */ -import { app } from '../firebase-namespace-api'; +/** + * Firebase App Check. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app'; +import { FirebaseApp } from '../app/firebase-app'; +import { AppCheck } from './app-check'; + +export { + AppCheckToken, + DecodedAppCheckToken, + VerifyAppCheckTokenResponse, +} from './app-check-api'; +export { AppCheck } from './app-check'; /** - * Gets the {@link appCheck.AppCheck `AppCheck`} service for the - * default app or a given app. + * Gets the {@link AppCheck} service for the default app or a given app. * - * You can call `admin.appCheck()` with no arguments to access the default - * app's {@link appCheck.AppCheck `AppCheck`} service or as - * `admin.appCheck(app)` to access the - * {@link appCheck.AppCheck `AppCheck`} service associated with a - * specific app. + * `getAppCheck()` can be called with no arguments to access the default + * app's `AppCheck` service or as `getAppCheck(app)` to access the + * `AppCheck` service associated with a specific app. * * @example * ```javascript * // Get the `AppCheck` service for the default app - * var defaultAppCheck = admin.appCheck(); + * const defaultAppCheck = getAppCheck(); * ``` * * @example * ```javascript * // Get the `AppCheck` service for a given app - * var otherAppCheck = admin.appCheck(otherApp); + * const otherAppCheck = getAppCheck(otherApp); * ``` * * @param app Optional app for which to return the `AppCheck` service. @@ -46,119 +58,11 @@ import { app } from '../firebase-namespace-api'; * app is provided, or the `AppCheck` service associated with the provided * app. */ -export declare function appCheck(app?: app.App): appCheck.AppCheck; - -/* eslint-disable @typescript-eslint/no-namespace */ -export namespace appCheck { - /** - * The Firebase `AppCheck` service interface. - */ - export interface AppCheck { - app: app.App; - - /** - * Creates a new {@link appCheck.AppCheckToken `AppCheckToken`} that can be sent - * back to a client. - * - * @param appId The App ID of the Firebase App the token belongs to. - * - * @returns A promise that fulfills with a `AppCheckToken`. - */ - createToken(appId: string): Promise; - - /** - * Verifies a Firebase App Check token (JWT). If the token is valid, the promise is - * fulfilled with the token's decoded claims; otherwise, the promise is - * rejected. - * - * @param appCheckToken The App Check token to verify. - * - * @returns A promise fulfilled with the - * token's decoded claims if the App Check token is valid; otherwise, a rejected - * promise. - */ - verifyToken(appCheckToken: string): Promise; - } - - /** - * Interface representing an App Check token. - */ - export interface AppCheckToken { - /** - * The Firebase App Check token. - */ - token: string; - - /** - * The time-to-live duration of the token in milliseconds. - */ - ttlMillis: number; - } - - /** - * Interface representing a decoded Firebase App Check token, returned from the - * {@link appCheck.AppCheck.verifyToken `verifyToken()`} method. - */ - export interface DecodedAppCheckToken { - /** - * The issuer identifier for the issuer of the response. - * - * This value is a URL with the format - * `https://firebaseappcheck.googleapis.com/`, where `` is the - * same project number specified in the [`aud`](#aud) property. - */ - iss: string; - - /** - * The Firebase App ID corresponding to the app the token belonged to. - * - * As a convenience, this value is copied over to the [`app_id`](#app_id) property. - */ - sub: string; - - /** - * The audience for which this token is intended. - * - * This value is a JSON array of two strings, the first is the project number of your - * Firebase project, and the second is the project ID of the same project. - */ - aud: string[]; - - /** - * The App Check token's expiration time, in seconds since the Unix epoch. That is, the - * time at which this App Check token expires and should no longer be considered valid. - */ - exp: number; - - /** - * The App Check token's issued-at time, in seconds since the Unix epoch. That is, the - * time at which this App Check token was issued and should start to be considered - * valid. - */ - iat: number; - - /** - * The App ID corresponding to the App the App Check token belonged to. - * - * This value is not actually one of the JWT token claims. It is added as a - * convenience, and is set as the value of the [`sub`](#sub) property. - */ - app_id: string; - [key: string]: any; +export function getAppCheck(app?: App): AppCheck { + if (typeof app === 'undefined') { + app = getApp(); } - /** - * Interface representing a verified App Check token response. - */ - export interface VerifyAppCheckTokenResponse { - /** - * The App ID corresponding to the App the App Check token belonged to. - */ - appId: string; - - /** - * The decoded Firebase App Check token. - */ - token: appCheck.DecodedAppCheckToken; - } + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('appCheck', (app) => new AppCheck(app)); } diff --git a/src/app-check/token-generator.ts b/src/app-check/token-generator.ts index 1b557438bb..0ebe9196eb 100644 --- a/src/app-check/token-generator.ts +++ b/src/app-check/token-generator.ts @@ -123,7 +123,7 @@ export function appCheckErrorFromCryptoSignerError(err: Error): Error { code = APP_CHECK_ERROR_CODE_MAPPING[status]; } return new FirebaseAppCheckError(code, - `Error returned from server while siging a custom token: ${description}` + `Error returned from server while signing a custom token: ${description}` ); } return new FirebaseAppCheckError('internal-error', diff --git a/src/app-check/token-verifier.ts b/src/app-check/token-verifier.ts index cf37b46ce6..9924f62b3f 100644 --- a/src/app-check/token-verifier.ts +++ b/src/app-check/token-verifier.ts @@ -14,17 +14,16 @@ * limitations under the License. */ -import { appCheck } from '.'; import * as validator from '../utils/validator'; import * as util from '../utils/index'; import { FirebaseAppCheckError } from './app-check-api-client-internal'; -import { FirebaseApp } from '../app/firebase-app'; import { ALGORITHM_RS256, DecodedToken, decodeJwt, JwtError, JwtErrorCode, PublicKeySignatureVerifier, SignatureVerifier } from '../utils/jwt'; -import DecodedAppCheckToken = appCheck.DecodedAppCheckToken; +import { DecodedAppCheckToken } from './app-check-api' +import { App } from '../app'; const APP_CHECK_ISSUER = 'https://firebaseappcheck.googleapis.com/'; const JWKS_URL = 'https://firebaseappcheck.googleapis.com/v1beta/jwks'; @@ -37,7 +36,7 @@ const JWKS_URL = 'https://firebaseappcheck.googleapis.com/v1beta/jwks'; export class AppCheckTokenVerifier { private readonly signatureVerifier: SignatureVerifier; - constructor(private readonly app: FirebaseApp) { + constructor(private readonly app: App) { this.signatureVerifier = PublicKeySignatureVerifier.withJwksUrl(JWKS_URL); } diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 68cfe47adb..0fdabacbbd 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -24,7 +24,7 @@ import { FirebaseNamespaceInternals } from './firebase-namespace'; import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { Auth } from '../auth/index'; -import { AppCheck } from '../app-check/app-check'; +import { AppCheck } from '../app-check/index'; import { MachineLearning } from '../machine-learning/index'; import { Messaging } from '../messaging/index'; import { Storage } from '../storage/index'; @@ -202,10 +202,8 @@ export class FirebaseApp implements app.App { * @returns The AppCheck service instance of this app. */ public appCheck(): AppCheck { - return this.ensureService_('appCheck', () => { - const appCheckService: typeof AppCheck = require('../app-check/app-check').AppCheck; - return new appCheckService(this); - }); + const fn = require('../app-check/index').getAppCheck; + return fn(this); } /** diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index c2e5b8ab90..71da2390b1 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -187,8 +187,8 @@ export class FirebaseTokenGenerator { /** * Returns whether or not the provided developer claims are valid. * - * @param {object} [developerClaims] Optional developer claims to validate. - * @returns {boolean} True if the provided claims are valid; otherwise, false. + * @param developerClaims Optional developer claims to validate. + * @returns True if the provided claims are valid; otherwise, false. */ private isDeveloperClaimsValid_(developerClaims?: object): boolean { if (typeof developerClaims === 'undefined') { diff --git a/src/firebase-namespace-api.ts b/src/firebase-namespace-api.ts index d75fb9a17e..d1bea3d509 100644 --- a/src/firebase-namespace-api.ts +++ b/src/firebase-namespace-api.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { appCheck } from './app-check/index'; +import { appCheck } from './app-check/app-check-namespace'; import { auth } from './auth/auth-namespace'; import { database } from './database/database-namespace'; import { firestore } from './firestore/firestore-namespace'; @@ -77,8 +77,8 @@ export namespace app { } export * from './credential/index'; +export { appCheck } from './app-check/app-check-namespace'; export { auth } from './auth/auth-namespace'; -export { appCheck } from './app-check/index'; export { database } from './database/database-namespace'; export { firestore } from './firestore/firestore-namespace'; export { instanceId } from './instance-id/instance-id-namespace'; diff --git a/src/utils/crypto-signer.ts b/src/utils/crypto-signer.ts index 569d5212f7..d34ace3feb 100644 --- a/src/utils/crypto-signer.ts +++ b/src/utils/crypto-signer.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { App } from '../app'; import { FirebaseApp } from '../app/firebase-app'; import { ServiceAccountCredential } from '../app/credential-internal'; import { AuthorizedHttpClient, HttpRequestConfig, HttpClient, HttpError } from './api-request'; @@ -190,13 +191,13 @@ export class IAMSigner implements CryptoSigner { * @param app A FirebaseApp instance. * @returns A CryptoSigner instance. */ -export function cryptoSignerFromApp(app: FirebaseApp): CryptoSigner { +export function cryptoSignerFromApp(app: App): CryptoSigner { const credential = app.options.credential; if (credential instanceof ServiceAccountCredential) { return new ServiceAccountSigner(credential); } - return new IAMSigner(new AuthorizedHttpClient(app), app.options.serviceAccountId); + return new IAMSigner(new AuthorizedHttpClient(app as FirebaseApp), app.options.serviceAccountId); } /** diff --git a/test/integration/postcheck/esm/example.test.js b/test/integration/postcheck/esm/example.test.js index 323b226982..c731577f66 100644 --- a/test/integration/postcheck/esm/example.test.js +++ b/test/integration/postcheck/esm/example.test.js @@ -18,6 +18,7 @@ import { expect } from 'chai'; import { cert, deleteApp, initializeApp } from 'firebase-admin/app'; +import { getAppCheck, AppCheck } from 'firebase-admin/app-check'; import { getAuth, Auth } from 'firebase-admin/auth'; import { getDatabase, getDatabaseWithUrl, ServerValue } from 'firebase-admin/database'; import { getFirestore, DocumentReference, Firestore, FieldValue } from 'firebase-admin/firestore'; @@ -47,6 +48,11 @@ describe('ESM entry points', () => { expect(app.name).to.equal('TestApp'); }); + it('Should return an AppCheck client', () => { + const client = getAppCheck(app); + expect(client).to.be.instanceOf(AppCheck); + }); + it('Should return an Auth client', () => { const client = getAuth(app); expect(client).to.be.instanceOf(Auth); diff --git a/test/integration/postcheck/typescript/example-modular.test.ts b/test/integration/postcheck/typescript/example-modular.test.ts index 8f607e448a..0d67abade4 100644 --- a/test/integration/postcheck/typescript/example-modular.test.ts +++ b/test/integration/postcheck/typescript/example-modular.test.ts @@ -18,6 +18,7 @@ import { expect } from 'chai'; import { cert, deleteApp, initializeApp, App } from 'firebase-admin/app'; +import { getAppCheck, AppCheck } from 'firebase-admin/app-check'; import { getAuth, Auth } from 'firebase-admin/auth'; import { getDatabase, getDatabaseWithUrl, Database, ServerValue } from 'firebase-admin/database'; import { getFirestore, DocumentReference, Firestore, FieldValue } from 'firebase-admin/firestore'; @@ -53,6 +54,11 @@ describe('Modular API', () => { expect(app.name).to.equal('TestApp'); }); + it('Should return an AppCheck client', () => { + const client = getAppCheck(app); + expect(client).to.be.instanceOf(AppCheck); + }); + it('Should return an Auth client', () => { const client = getAuth(app); expect(client).to.be.instanceOf(Auth); diff --git a/test/unit/app-check/token-generator.spec.ts b/test/unit/app-check/token-generator.spec.ts index 2a7431f9cd..ba47850f30 100644 --- a/test/unit/app-check/token-generator.spec.ts +++ b/test/unit/app-check/token-generator.spec.ts @@ -225,7 +225,7 @@ describe('AppCheckTokenGenerator', () => { expect(appCheckError).to.be.an.instanceof(FirebaseAppCheckError); expect(appCheckError).to.have.property('code', 'app-check/unknown-error'); expect(appCheckError).to.have.property('message', - 'Error returned from server while siging a custom token: server error.'); + 'Error returned from server while signing a custom token: server error.'); }); it('should convert CryptoSignerError HttpError with no error.message to FirebaseAppCheckError', () => { @@ -240,7 +240,7 @@ describe('AppCheckTokenGenerator', () => { expect(appCheckError).to.be.an.instanceof(FirebaseAppCheckError); expect(appCheckError).to.have.property('code', 'app-check/unknown-error'); expect(appCheckError).to.have.property('message', - 'Error returned from server while siging a custom token: '+ + 'Error returned from server while signing a custom token: '+ '{"status":500,"headers":{},"data":{"error":{}},"text":"{\\"error\\":{}}"}'); }); diff --git a/test/unit/app/firebase-namespace.spec.ts b/test/unit/app/firebase-namespace.spec.ts index b1f3854f16..220d76579b 100644 --- a/test/unit/app/firebase-namespace.spec.ts +++ b/test/unit/app/firebase-namespace.spec.ts @@ -51,8 +51,8 @@ import { app, auth, messaging, machineLearning, storage, firestore, database, instanceId, projectManagement, securityRules , remoteConfig, appCheck, } from '../../../src/firebase-namespace-api'; -import { Auth as AuthImpl } from '../../../src/auth/auth'; import { AppCheck as AppCheckImpl } from '../../../src/app-check/app-check'; +import { Auth as AuthImpl } from '../../../src/auth/auth'; import { InstanceId as InstanceIdImpl } from '../../../src/instance-id/instance-id'; import { MachineLearning as MachineLearningImpl } from '../../../src/machine-learning/machine-learning'; import { Messaging as MessagingImpl } from '../../../src/messaging/messaging'; From b4a2aeebee5ad10860a6db51537457638b0af025 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Fri, 25 Jun 2021 11:11:02 -0700 Subject: [PATCH 37/41] chore: Bumped version to alpha.1 (#1347) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9e5f9a153..14f0b0b771 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-admin", - "version": "9.100.0-alpha.0", + "version": "9.100.0-alpha.1", "description": "Firebase admin SDK for Node.js", "author": "Firebase (https://firebase.google.com/)", "license": "Apache-2.0", From bee6a2bd90e777adc73ba43622702cda8121c0b4 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 18 Aug 2021 11:45:29 -0700 Subject: [PATCH 38/41] chore: Merging main branch into modular-sdk (#1411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(core): Automate Daily Integration Tests (#1130) * Automate daily integration tests * Rename to nightly * Change to 6am and 8pm PT & remove tar verification * Fix schedule comment * Updating Google Cloud naming (#1122) * Reinstating tag that devsite needs present to supress machine translation. * Updating a couple of references to GCP/Google Cloud Platform per new branding guidelines. * update typo in interface name (#1138) FireabseErrorInterface -> FirebaseErrorInterface * Improve token verification logic with Auth Emulator. (#1148) * Improve token verification logic with Auth Emulator. * Clean up comments. * Fix linting issues. * Address review comments. * Use mock for auth emulator unit test. * Implement session cookies. * Call useEmulator() only once. * Update tests. * Delete unused test helper. * Add unit tests for checking revocation. * Fix typo in test comments. * feat: Exporting all types of Messages so they can be used by consumers (#1147) * feat: Exporting all types of Messages so they can be used by consumers Fixes https://github.com/firebase/firebase-admin-node/issues/1146 * feat(exportMessageTypes): Testing TokenMessage * feat(exportMessageTypes): Added tests for all Message types * feat(exportMessageTypes): Fixed build * feat(exportMessageTypes): Better unit tests * feat(exportMessageTypes): Deleted unneeded separate TS test * feat(exportMessageTypes): Fixed build * feat(exportMessageTypes): Fixed linting * feat(auth): Implement getUserByProviderId (#769) RELEASE NOTE: Added a new getUserByProviderId() to lookup user accounts by their providers. * Allow enabling of anonymous provider via tenant configuration (#802) RELEASE NOTES: Allow enabling of anonymous provider via tenant configuration. * feat(auth): Add ability to link a federated ID with the `updateUser()` method. (#770) * (chore): Export UserProvider type and add it to toc.yaml (#1165) - Export UserProvider type - Add UserProvider to toc.yaml * [chore] Release 9.5.0 (#1167) Release 9.5.0 * chore: Updated doc generator for typedoc 0.19.0 (#1166) * Update HOME.md (#1181) Quick addition of a little bit of clarifying verbiage per an internal bug report. Thanks! * feat(rtdb): Support emulator mode for rules management operations (#1190) * feat(rtdb): Support emulator mode for rules management operations * fix: Adding namespace to emulated URL string * fix: Consolidated unit testing * fix: Removed extra whitespace * fix: Decoupled proactive token refresh from FirebaseApp (#1194) * fix: Decoupled proactive token refresh from FirebaseApp * fix: Defined constants for duration values * fix: Logging errors encountered while scheduling a refresh * fix: Renamed some variables for clarity * fix(rtdb): Fixing the RTDB token listener callback (#1203) * Add emulator-based integration tests. (#1155) * Add emulator-based integration tests. * Move emulator stuff out of package.json. * Update CONTRIBUTING.md too. * Add npx. * Skip new unsupported tests. * Inline commands in ci.yml. * Disable one flaky tests in emulator. (#1205) * [chore] Release 9.6.0 (#1209) * (chore): Add JWT Decoder and Signature Verifier (#1204) * (chore): Add JWT Decoder * Add signature verifier and key fetcher abstractions * Add unit tests for utils/jwt * chore: Add Mailgun send email action (#1210) * Add Mailgun send email github action * Add send email action in nightly workflow * chore: Fix bug in send-email action code (#1214) * chore: Fix bug in send-email action code * Add run id and trigger on repo dispatch event * Change dispatch event name in nightly workflow (#1216) - Change dispatch event name to `firebase_nightly_build` * chore: Clean up nightly workflow trigger tests (#1212) - Remove failing integration test added to trigger the send email workflow in nightly builds. * Add support for FIREBASE_STORAGE_EMULATOR_HOST env var (#1175) * Add support for FIREBASE_STORAGE_EMULATOR_HOST env var * Fixes lint error * Add test for FIREBASE_STORAGE_EMULATOR_HOST support * Lint fix * Minor fixes to storage tests * Address review comments * Address review suggestion Co-authored-by: Samuel Bushi * Revert "Disable one flaky tests in emulator. (#1205)" (#1227) This reverts commit 19660d921d20732857bf54393a09e8b5bce15d63. * fix(rtdb): Fixing a token refresh livelock in Cloud Functions (#1234) * [chore] Release 9.7.0 (#1240) * fix: adds missing EMAIL_NOT_FOUND error code (#1246) Catch `EMAIL_NOT_FOUND` and translate to `auth/email-not-found` when `/accounts:sendOobCode` is called for password reset on a user that does not exist. Fixes https://github.com/firebase/firebase-admin-node/issues/1202 * build(deps-dev): bump lodash from 4.17.19 to 4.17.21 (#1255) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Upgraded RTDB and other @firebase dependencies (#1250) * build(deps): bump y18n from 3.2.1 to 3.2.2 (#1208) Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix storage emulator env formatting (#1257) * Fix storage emulator env formatting * Repair test * Rename test * Dang tests 2 good 4 me * Fix test * Fix tests again * build(deps): bump hosted-git-info from 2.8.8 to 2.8.9 (#1260) Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9. - [Release notes](https://github.com/npm/hosted-git-info/releases) - [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md) - [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hiranya Jayathilaka * feat: Add abuse reduction support (#1264) - Add abuse reduction support APIs * Fix @types/node conflict with grpc and port type (#1258) Upgraded the @types/node dependency to v12.12.47 * [chore] Release 9.8.0 (#1266) * build(deps): bump handlebars from 4.7.6 to 4.7.7 (#1253) Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.7.6 to 4.7.7. - [Release notes](https://github.com/wycats/handlebars.js/releases) - [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) - [Commits](https://github.com/wycats/handlebars.js/compare/v4.7.6...v4.7.7) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump jose from 2.0.4 to 2.0.5 (#1265) Bumps [jose](https://github.com/panva/jose) from 2.0.4 to 2.0.5. - [Release notes](https://github.com/panva/jose/releases) - [Changelog](https://github.com/panva/jose/blob/v2.0.5/CHANGELOG.md) - [Commits](https://github.com/panva/jose/compare/v2.0.4...v2.0.5) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: Revert regression introduced in #1257 (#1277) * fix(auth): make MFA uid optional for updateUser operations (#1278) * fix(auth): make MFA uid optional for updateUser operations MFA `uid` should be optional for `updateUser` operations. When not specified, the backend will provision a `uid` for the enrolled second factor. Fixes https://github.com/firebase/firebase-admin-node/issues/1276 * chore: Enabled dependabot (#1279) * chore: Remove gulp-replace dependency (#1285) * build(deps-dev): bump gulp-header from 1.8.12 to 2.0.9 (#1283) Bumps [gulp-header](https://github.com/tracker1/gulp-header) from 1.8.12 to 2.0.9. - [Release notes](https://github.com/tracker1/gulp-header/releases) - [Changelog](https://github.com/gulp-community/gulp-header/blob/master/changelog.md) - [Commits](https://github.com/tracker1/gulp-header/compare/v1.8.12...v2.0.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump run-sequence from 1.2.2 to 2.2.1 (#1282) Bumps [run-sequence](https://github.com/OverZealous/run-sequence) from 1.2.2 to 2.2.1. - [Release notes](https://github.com/OverZealous/run-sequence/releases) - [Changelog](https://github.com/OverZealous/run-sequence/blob/master/CHANGELOG.md) - [Commits](https://github.com/OverZealous/run-sequence/compare/v1.2.2...v2.2.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump sinon from 9.0.2 to 9.2.4 (#1289) Bumps [sinon](https://github.com/sinonjs/sinon) from 9.0.2 to 9.2.4. - [Release notes](https://github.com/sinonjs/sinon/releases) - [Changelog](https://github.com/sinonjs/sinon/blob/master/CHANGELOG.md) - [Commits](https://github.com/sinonjs/sinon/compare/v9.0.2...v9.2.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump nyc from 14.1.1 to 15.1.0 (#1290) Bumps [nyc](https://github.com/istanbuljs/nyc) from 14.1.1 to 15.1.0. - [Release notes](https://github.com/istanbuljs/nyc/releases) - [Changelog](https://github.com/istanbuljs/nyc/blob/master/CHANGELOG.md) - [Commits](https://github.com/istanbuljs/nyc/compare/v14.1.1...v15.1.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump chalk from 1.1.3 to 4.1.1 (#1288) Bumps [chalk](https://github.com/chalk/chalk) from 1.1.3 to 4.1.1. - [Release notes](https://github.com/chalk/chalk/releases) - [Commits](https://github.com/chalk/chalk/compare/v1.1.3...v4.1.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @microsoft/api-extractor from 7.11.2 to 7.15.2 (#1291) Bumps [@microsoft/api-extractor](https://github.com/microsoft/rushstack) from 7.11.2 to 7.15.2. - [Release notes](https://github.com/microsoft/rushstack/releases) - [Commits](https://github.com/microsoft/rushstack/compare/@microsoft/api-extractor_v7.11.2...@microsoft/api-extractor_v7.15.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Teporarily disabling sendToDeviceGroup integration test (#1292) * feat(auth): Added code flow support for OIDC flow. (#1220) * OIDC codeflow support * improve configs to simulate the real cases * update for changes in signiture * resolve comments * improve validator logic * remove unnecessary logic * add tests and fix errors * add auth-api-request rests * Update supported Node version to 10.13.0v (#1300) * Fixed integration test failure of skipped tests (#1299) * Fix integration test failure of skipped testss * Trigger integration tests * [chore] Release 9.9.0 (#1302) * Update OIDC reference docs (#1305) * Add OAuthResponseType to ToC (#1303) * fix(auth): Better type hierarchies for Auth API (#1294) * fix(auth): Better type heirarchies for Auth API * fix: Moved factorId back to the base types * fix: Updated API report * fix: Fixed a grammar error in comment * fix: Update to comment text * build(deps-dev): bump nock from 13.0.5 to 13.0.11 (#1311) Bumps [nock](https://github.com/nock/nock) from 13.0.5 to 13.0.11. - [Release notes](https://github.com/nock/nock/releases) - [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md) - [Commits](https://github.com/nock/nock/compare/v13.0.5...v13.0.11) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump ws from 7.3.1 to 7.4.6 (#1309) Bumps [ws](https://github.com/websockets/ws) from 7.3.1 to 7.4.6. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.3.1...7.4.6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump del from 2.2.2 to 6.0.0 (#1310) Bumps [del](https://github.com/sindresorhus/del) from 2.2.2 to 6.0.0. - [Release notes](https://github.com/sindresorhus/del/releases) - [Commits](https://github.com/sindresorhus/del/compare/v2.2.2...v6.0.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/jsonwebtoken from 8.5.0 to 8.5.1 (#1315) Bumps [@types/jsonwebtoken](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jsonwebtoken) from 8.5.0 to 8.5.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jsonwebtoken) --- updated-dependencies: - dependency-name: "@types/jsonwebtoken" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump nock from 13.0.11 to 13.1.0 (#1313) Bumps [nock](https://github.com/nock/nock) from 13.0.11 to 13.1.0. - [Release notes](https://github.com/nock/nock/releases) - [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md) - [Commits](https://github.com/nock/nock/compare/v13.0.11...v13.1.0) --- updated-dependencies: - dependency-name: nock dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/sinon-chai from 3.2.4 to 3.2.5 (#1316) Bumps [@types/sinon-chai](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/sinon-chai) from 3.2.4 to 3.2.5. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/sinon-chai) --- updated-dependencies: - dependency-name: "@types/sinon-chai" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump bcrypt from 5.0.0 to 5.0.1 (#1324) Bumps [bcrypt](https://github.com/kelektiv/node.bcrypt.js) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/kelektiv/node.bcrypt.js/releases) - [Changelog](https://github.com/kelektiv/node.bcrypt.js/blob/master/CHANGELOG.md) - [Commits](https://github.com/kelektiv/node.bcrypt.js/compare/v5.0.0...v5.0.1) --- updated-dependencies: - dependency-name: bcrypt dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @google-cloud/firestore from 4.5.0 to 4.12.2 (#1325) Bumps [@google-cloud/firestore](https://github.com/googleapis/nodejs-firestore) from 4.5.0 to 4.12.2. - [Release notes](https://github.com/googleapis/nodejs-firestore/releases) - [Changelog](https://github.com/googleapis/nodejs-firestore/blob/master/CHANGELOG.md) - [Commits](https://github.com/googleapis/nodejs-firestore/compare/v4.5.0...v4.12.2) --- updated-dependencies: - dependency-name: "@google-cloud/firestore" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/mocha from 2.2.48 to 8.2.2 (#1323) Bumps [@types/mocha](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/mocha) from 2.2.48 to 8.2.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/mocha) --- updated-dependencies: - dependency-name: "@types/mocha" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @firebase/app from 0.6.21 to 0.6.26 (#1329) Bumps [@firebase/app](https://github.com/firebase/firebase-js-sdk/tree/HEAD/packages/app) from 0.6.21 to 0.6.26. - [Release notes](https://github.com/firebase/firebase-js-sdk/releases) - [Changelog](https://github.com/firebase/firebase-js-sdk/blob/@firebase/app@0.6.26/packages/app/CHANGELOG.md) - [Commits](https://github.com/firebase/firebase-js-sdk/commits/@firebase/app@0.6.26/packages/app) --- updated-dependencies: - dependency-name: "@firebase/app" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @firebase/database from 0.10.0 to 0.10.4 (#1328) Bumps [@firebase/database](https://github.com/firebase/firebase-js-sdk/tree/HEAD/packages/database) from 0.10.0 to 0.10.4. - [Release notes](https://github.com/firebase/firebase-js-sdk/releases) - [Changelog](https://github.com/firebase/firebase-js-sdk/blob/master/packages/database/CHANGELOG.md) - [Commits](https://github.com/firebase/firebase-js-sdk/commits/@firebase/database@0.10.4/packages/database) --- updated-dependencies: - dependency-name: "@firebase/database" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump request-promise from 4.2.5 to 4.2.6 (#1331) Bumps [request-promise](https://github.com/request/request-promise) from 4.2.5 to 4.2.6. - [Release notes](https://github.com/request/request-promise/releases) - [Commits](https://github.com/request/request-promise/compare/v4.2.5...v4.2.6) --- updated-dependencies: - dependency-name: request-promise dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump gulp-filter from 6.0.0 to 7.0.0 (#1334) Bumps [gulp-filter](https://github.com/sindresorhus/gulp-filter) from 6.0.0 to 7.0.0. - [Release notes](https://github.com/sindresorhus/gulp-filter/releases) - [Commits](https://github.com/sindresorhus/gulp-filter/compare/v6.0.0...v7.0.0) --- updated-dependencies: - dependency-name: gulp-filter dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/minimist from 1.2.0 to 1.2.1 (#1336) Bumps [@types/minimist](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/minimist) from 1.2.0 to 1.2.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/minimist) --- updated-dependencies: - dependency-name: "@types/minimist" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(docs): replace all global.html -> admin.html (#1341) Co-authored-by: Hiranya Jayathilaka * feat(fis): Adding the admin.installations() API for deleting Firebase installation IDs (#1187) * feat(fis): Added admin.installations() API * fix: Marked IID APIs deprecated * fix: Deprecated App.instanceId() method; Added more unit and integration tests * fix: Throwing FirebaseInstallationsError from constructor * fix: Some docs updates * fix: Minor update to API doc comment * fix: Added Installations class to API ref toc * fix: Minor doc updates * fix: Updated TOC for new Auth type aliases (#1342) * [chore] Release 9.10.0 * [chore] Release 9.10.0 (#1345) * build(deps-dev): bump @types/request-promise from 4.1.46 to 4.1.47 (#1338) Bumps [@types/request-promise](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/request-promise) from 4.1.46 to 4.1.47. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/request-promise) --- updated-dependencies: - dependency-name: "@types/request-promise" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @firebase/database from 0.10.4 to 0.10.5 (#1350) Bumps [@firebase/database](https://github.com/firebase/firebase-js-sdk/tree/HEAD/packages/database) from 0.10.4 to 0.10.5. - [Release notes](https://github.com/firebase/firebase-js-sdk/releases) - [Changelog](https://github.com/firebase/firebase-js-sdk/blob/master/packages/database/CHANGELOG.md) - [Commits](https://github.com/firebase/firebase-js-sdk/commits/@firebase/database@0.10.5/packages/database) --- updated-dependencies: - dependency-name: "@firebase/database" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/nock from 9.3.1 to 11.1.0 (#1351) Bumps [@types/nock](https://github.com/nock/nock) from 9.3.1 to 11.1.0. - [Release notes](https://github.com/nock/nock/releases) - [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md) - [Commits](https://github.com/nock/nock/compare/v9.3.1...v11.1.0) --- updated-dependencies: - dependency-name: "@types/nock" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/sinon from 9.0.4 to 10.0.2 (#1326) Bumps [@types/sinon](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/sinon) from 9.0.4 to 10.0.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/sinon) --- updated-dependencies: - dependency-name: "@types/sinon" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @firebase/database from 0.10.5 to 0.10.6 (#1356) Bumps [@firebase/database](https://github.com/firebase/firebase-js-sdk/tree/HEAD/packages/database) from 0.10.5 to 0.10.6. - [Release notes](https://github.com/firebase/firebase-js-sdk/releases) - [Changelog](https://github.com/firebase/firebase-js-sdk/blob/master/packages/database/CHANGELOG.md) - [Commits](https://github.com/firebase/firebase-js-sdk/commits/@firebase/database@0.10.6/packages/database) --- updated-dependencies: - dependency-name: "@firebase/database" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump jwks-rsa from 2.0.2 to 2.0.3 (#1361) Bumps [jwks-rsa](https://github.com/auth0/node-jwks-rsa) from 2.0.2 to 2.0.3. - [Release notes](https://github.com/auth0/node-jwks-rsa/releases) - [Changelog](https://github.com/auth0/node-jwks-rsa/blob/master/CHANGELOG.md) - [Commits](https://github.com/auth0/node-jwks-rsa/compare/v2.0.2...v2.0.3) --- updated-dependencies: - dependency-name: jwks-rsa dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump yargs from 16.1.0 to 17.0.1 (#1357) Bumps [yargs](https://github.com/yargs/yargs) from 16.1.0 to 17.0.1. - [Release notes](https://github.com/yargs/yargs/releases) - [Changelog](https://github.com/yargs/yargs/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/yargs/compare/v16.1.0...v17.0.1) --- updated-dependencies: - dependency-name: yargs dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/chai from 4.2.11 to 4.2.21 (#1365) Bumps [@types/chai](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chai) from 4.2.11 to 4.2.21. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chai) --- updated-dependencies: - dependency-name: "@types/chai" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update index.ts (#1367) Fix typo in comments: `sendMulticase` -> `sendMulticast` * build(deps): bump @google-cloud/firestore from 4.12.2 to 4.13.1 (#1369) Bumps [@google-cloud/firestore](https://github.com/googleapis/nodejs-firestore) from 4.12.2 to 4.13.1. - [Release notes](https://github.com/googleapis/nodejs-firestore/releases) - [Changelog](https://github.com/googleapis/nodejs-firestore/blob/master/CHANGELOG.md) - [Commits](https://github.com/googleapis/nodejs-firestore/compare/v4.12.2...v4.13.1) --- updated-dependencies: - dependency-name: "@google-cloud/firestore" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(fac): Add custom TTL options for App Check (#1363) * Add custom ttl options for App Check * PR fixes * Add integration tests * PR fixes * Reduce App Check custom token exp to 5 mins (#1372) * Add AppCheckTokenOptions type to ToC (#1375) Add `AppCheckTokenOptions` to `ToC` * Fix typo and formatting in docs (#1378) - Fix typo and add back-ticks * [chore] Release 9.11.0 (#1376) - Release 9.11.0 * build(deps-dev): bump nock from 13.1.0 to 13.1.1 (#1370) Bumps [nock](https://github.com/nock/nock) from 13.1.0 to 13.1.1. - [Release notes](https://github.com/nock/nock/releases) - [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md) - [Commits](https://github.com/nock/nock/compare/v13.1.0...v13.1.1) --- updated-dependencies: - dependency-name: nock dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/bcrypt from 2.0.0 to 5.0.0 (#1384) Bumps [@types/bcrypt](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/bcrypt) from 2.0.0 to 5.0.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/bcrypt) --- updated-dependencies: - dependency-name: "@types/bcrypt" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @firebase/database from 0.10.6 to 0.10.7 (#1385) Bumps [@firebase/database](https://github.com/firebase/firebase-js-sdk/tree/HEAD/packages/database) from 0.10.6 to 0.10.7. - [Release notes](https://github.com/firebase/firebase-js-sdk/releases) - [Changelog](https://github.com/firebase/firebase-js-sdk/blob/master/packages/database/CHANGELOG.md) - [Commits](https://github.com/firebase/firebase-js-sdk/commits/@firebase/database@0.10.7/packages/database) --- updated-dependencies: - dependency-name: "@firebase/database" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/lodash from 4.14.157 to 4.14.171 (#1386) Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.14.157 to 4.14.171. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash) --- updated-dependencies: - dependency-name: "@types/lodash" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/request from 2.48.5 to 2.48.6 (#1387) Bumps [@types/request](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/request) from 2.48.5 to 2.48.6. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/request) --- updated-dependencies: - dependency-name: "@types/request" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/minimist from 1.2.1 to 1.2.2 (#1388) Bumps [@types/minimist](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/minimist) from 1.2.1 to 1.2.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/minimist) --- updated-dependencies: - dependency-name: "@types/minimist" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump jwks-rsa from 2.0.3 to 2.0.4 (#1393) Bumps [jwks-rsa](https://github.com/auth0/node-jwks-rsa) from 2.0.3 to 2.0.4. - [Release notes](https://github.com/auth0/node-jwks-rsa/releases) - [Changelog](https://github.com/auth0/node-jwks-rsa/blob/master/CHANGELOG.md) - [Commits](https://github.com/auth0/node-jwks-rsa/compare/v2.0.3...2.0.4) --- updated-dependencies: - dependency-name: jwks-rsa dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @microsoft/api-extractor from 7.15.2 to 7.18.4 (#1379) * build(deps-dev): bump @microsoft/api-extractor from 7.15.2 to 7.18.4 Bumps [@microsoft/api-extractor](https://github.com/microsoft/rushstack) from 7.15.2 to 7.18.4. - [Release notes](https://github.com/microsoft/rushstack/releases) - [Commits](https://github.com/microsoft/rushstack/compare/@microsoft/api-extractor_v7.15.2...@microsoft/api-extractor_v7.18.4) --- updated-dependencies: - dependency-name: "@microsoft/api-extractor" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * fix: Updated API report with the new API Extractor version Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hiranya Jayathilaka * build(deps): bump tar from 6.1.0 to 6.1.3 (#1399) Bumps [tar](https://github.com/npm/node-tar) from 6.1.0 to 6.1.3. - [Release notes](https://github.com/npm/node-tar/releases) - [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-tar/compare/v6.1.0...v6.1.3) --- updated-dependencies: - dependency-name: tar dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump ts-node from 9.0.0 to 10.2.0 (#1402) Bumps [ts-node](https://github.com/TypeStrong/ts-node) from 9.0.0 to 10.2.0. - [Release notes](https://github.com/TypeStrong/ts-node/releases) - [Commits](https://github.com/TypeStrong/ts-node/compare/v9.0.0...v10.2.0) --- updated-dependencies: - dependency-name: ts-node dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: Regenerated package-lock file * fix: Fixing a typo in doc comment Co-authored-by: Lahiru Maramba Co-authored-by: egilmorez Co-authored-by: batuxd <9674241+suchcodemuchwow@users.noreply.github.com> Co-authored-by: Yuchen Shi Co-authored-by: Marc Bornträger Co-authored-by: rsgowman Co-authored-by: Abe Haskins Co-authored-by: Samuel Bushi Co-authored-by: bojeil-google Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nikhil Agarwal <54072321+nikhilag@users.noreply.github.com> Co-authored-by: Xin Li Co-authored-by: NothingEverHappens Co-authored-by: Daniel Hritzkiv --- entrypoints.json | 4 + etc/firebase-admin.api.md | 18 +- etc/firebase-admin.app-check.api.md | 10 +- etc/firebase-admin.app.api.md | 3 +- etc/firebase-admin.auth.api.md | 7 +- etc/firebase-admin.database.api.md | 3 +- etc/firebase-admin.firestore.api.md | 3 +- etc/firebase-admin.installations.api.md | 23 + etc/firebase-admin.instance-id.api.md | 9 +- etc/firebase-admin.machine-learning.api.md | 3 +- etc/firebase-admin.messaging.api.md | 5 +- etc/firebase-admin.project-management.api.md | 5 +- etc/firebase-admin.remote-config.api.md | 3 +- etc/firebase-admin.security-rules.api.md | 3 +- etc/firebase-admin.storage.api.md | 5 +- package-lock.json | 1911 +++++++++++++---- package.json | 27 +- .../app-check-api-client-internal.ts | 6 +- src/app-check/app-check-api.ts | 11 + src/app-check/app-check-namespace.ts | 3 + src/app-check/app-check.ts | 8 +- src/app-check/index.ts | 1 + src/app-check/token-generator.ts | 54 +- src/app-check/token-verifier.ts | 4 +- src/app/firebase-app.ts | 11 + src/app/firebase-namespace.ts | 15 +- src/auth/auth-config.ts | 16 +- src/firebase-namespace-api.ts | 4 + src/installations/index.ts | 63 + src/installations/installations-namespace.ts | 58 + .../installations-request-handler.ts} | 36 +- src/installations/installations.ts | 65 + src/instance-id/index.ts | 6 + src/instance-id/instance-id.ts | 26 +- src/messaging/messaging-internal.ts | 27 +- src/utils/error.ts | 34 +- src/utils/index.ts | 26 + test/integration/app-check.spec.ts | 14 + test/integration/auth.spec.ts | 55 + test/integration/installations.spec.ts | 31 + test/integration/instance-id.spec.ts | 2 +- test/unit/app-check/app-check.spec.ts | 13 +- test/unit/app-check/token-generator.spec.ts | 12 +- test/unit/app/firebase-app.spec.ts | 29 +- test/unit/app/firebase-namespace.spec.ts | 46 +- test/unit/auth/token-verifier.spec.ts | 2 +- test/unit/index.spec.ts | 5 +- .../installations-request-handler.spec.ts} | 38 +- test/unit/installations/installations.spec.ts | 190 ++ test/unit/instance-id/index.spec.ts | 2 +- test/unit/instance-id/instance-id.spec.ts | 23 +- test/unit/utils/index.spec.ts | 15 +- test/unit/utils/jwt.spec.ts | 2 +- 53 files changed, 2361 insertions(+), 634 deletions(-) create mode 100644 etc/firebase-admin.installations.api.md create mode 100644 src/installations/index.ts create mode 100644 src/installations/installations-namespace.ts rename src/{instance-id/instance-id-request-internal.ts => installations/installations-request-handler.ts} (76%) create mode 100644 src/installations/installations.ts create mode 100644 test/integration/installations.spec.ts rename test/unit/{instance-id/instance-id-request.spec.ts => installations/installations-request-handler.spec.ts} (72%) create mode 100644 test/unit/installations/installations.spec.ts diff --git a/entrypoints.json b/entrypoints.json index d35b7b8edf..975db81888 100644 --- a/entrypoints.json +++ b/entrypoints.json @@ -24,6 +24,10 @@ "typings": "./lib/firestore/index.d.ts", "dist": "./lib/firestore/index.js" }, + "firebase-admin/installations": { + "typings": "./lib/installations/index.d.ts", + "dist": "./lib/installations/index.js" + }, "firebase-admin/instance-id": { "typings": "./lib/instance-id/index.d.ts", "dist": "./lib/instance-id/index.js" diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index c82698b5a3..29d2e783d8 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; import { Bucket } from '@google-cloud/storage'; import { FirebaseDatabase } from '@firebase/database-types'; @@ -27,6 +29,8 @@ export namespace app { // (undocumented) firestore(): firestore.Firestore; // (undocumented) + installations(): installations.Installations; + // @deprecated (undocumented) instanceId(): instanceId.InstanceId; // (undocumented) machineLearning(): machineLearning.MachineLearning; @@ -52,6 +56,10 @@ export namespace appCheck { export type AppCheck = AppCheck; // Warning: (ae-forgotten-export) The symbol "AppCheckToken" needs to be exported by the entry point default-namespace.d.ts export type AppCheckToken = AppCheckToken; + // Warning: (ae-forgotten-export) The symbol "AppCheckTokenOptions" needs to be exported by the entry point default-namespace.d.ts + // + // (undocumented) + export type AppCheckTokenOptions = AppCheckTokenOptions; // Warning: (ae-forgotten-export) The symbol "DecodedAppCheckToken" needs to be exported by the entry point default-namespace.d.ts export type DecodedAppCheckToken = DecodedAppCheckToken; // Warning: (ae-forgotten-export) The symbol "VerifyAppCheckTokenResponse" needs to be exported by the entry point default-namespace.d.ts @@ -271,6 +279,15 @@ export interface GoogleOAuthAccessToken { // @public (undocumented) export function initializeApp(options?: AppOptions, name?: string): app.App; +// @public +export function installations(app?: App): installations.Installations; + +// @public (undocumented) +export namespace installations { + // Warning: (ae-forgotten-export) The symbol "Installations" needs to be exported by the entry point default-namespace.d.ts + export type Installations = Installations; +} + // @public export function instanceId(app?: App): instanceId.InstanceId; @@ -473,5 +490,4 @@ export namespace storage { export type Storage = Storage; } - ``` diff --git a/etc/firebase-admin.app-check.api.md b/etc/firebase-admin.app-check.api.md index 7455692bec..fb4e10ff64 100644 --- a/etc/firebase-admin.app-check.api.md +++ b/etc/firebase-admin.app-check.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; // @public @@ -12,7 +14,7 @@ export class AppCheck { // // (undocumented) readonly app: App; - createToken(appId: string): Promise; + createToken(appId: string, options?: AppCheckTokenOptions): Promise; verifyToken(appCheckToken: string): Promise; } @@ -22,6 +24,11 @@ export interface AppCheckToken { ttlMillis: number; } +// @public +export interface AppCheckTokenOptions { + ttlMillis?: number; +} + // @public export interface DecodedAppCheckToken { // (undocumented) @@ -43,5 +50,4 @@ export interface VerifyAppCheckTokenResponse { token: DecodedAppCheckToken; } - ``` diff --git a/etc/firebase-admin.app.api.md b/etc/firebase-admin.app.api.md index 794a74e9e4..13b925d89e 100644 --- a/etc/firebase-admin.app.api.md +++ b/etc/firebase-admin.app.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; // @public @@ -84,5 +86,4 @@ export interface ServiceAccount { projectId?: string; } - ``` diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index 563e2204bb..36b2dcf686 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; // @public @@ -26,7 +28,7 @@ export class Auth extends BaseAuth { // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts get app(): App; tenantManager(): TenantManager; - } +} // @public export type AuthFactorType = 'phone'; @@ -299,7 +301,7 @@ export class Tenant { [phoneNumber: string]: string; }; toJSON(): object; - } +} // @public export class TenantAwareAuth extends BaseAuth { @@ -472,5 +474,4 @@ export class UserRecord { readonly uid: string; } - ``` diff --git a/etc/firebase-admin.database.api.md b/etc/firebase-admin.database.api.md index abc6b75a0c..e2411ce4be 100644 --- a/etc/firebase-admin.database.api.md +++ b/etc/firebase-admin.database.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; import { DataSnapshot } from '@firebase/database-types'; import { EventType } from '@firebase/database-types'; @@ -47,5 +49,4 @@ export const ServerValue: rtdb.ServerValue; export { ThenableReference } - ``` diff --git a/etc/firebase-admin.firestore.api.md b/etc/firebase-admin.firestore.api.md index 392d55a71c..48373f54b6 100644 --- a/etc/firebase-admin.firestore.api.md +++ b/etc/firebase-admin.firestore.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; import { BulkWriter } from '@google-cloud/firestore'; import { BulkWriterOptions } from '@google-cloud/firestore'; @@ -95,5 +97,4 @@ export { WriteBatch } export { WriteResult } - ``` diff --git a/etc/firebase-admin.installations.api.md b/etc/firebase-admin.installations.api.md new file mode 100644 index 0000000000..a3581a317f --- /dev/null +++ b/etc/firebase-admin.installations.api.md @@ -0,0 +1,23 @@ +## API Report File for "firebase-admin.installations" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +/// + +import { Agent } from 'http'; + +// Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts +// +// @public +export function getInstallations(app?: App): Installations; + +// @public +export class Installations { + constructor(app: App); + get app(): App; + deleteInstallation(fid: string): Promise; +} + +``` diff --git a/etc/firebase-admin.instance-id.api.md b/etc/firebase-admin.instance-id.api.md index 0754a74e69..2e2a43f642 100644 --- a/etc/firebase-admin.instance-id.api.md +++ b/etc/firebase-admin.instance-id.api.md @@ -4,18 +4,19 @@ ```ts +/// + import { Agent } from 'http'; // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts // -// @public +// @public @deprecated export function getInstanceId(app?: App): InstanceId; -// @public +// @public @deprecated export class InstanceId { get app(): App; deleteInstanceId(instanceId: string): Promise; - } - +} ``` diff --git a/etc/firebase-admin.machine-learning.api.md b/etc/firebase-admin.machine-learning.api.md index 149a81c61a..80fa32bcc5 100644 --- a/etc/firebase-admin.machine-learning.api.md +++ b/etc/firebase-admin.machine-learning.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; // @public (undocumented) @@ -89,5 +91,4 @@ export interface TFLiteModel { readonly sizeBytes: number; } - ``` diff --git a/etc/firebase-admin.messaging.api.md b/etc/firebase-admin.messaging.api.md index a7ac9f1182..c37466734c 100644 --- a/etc/firebase-admin.messaging.api.md +++ b/etc/firebase-admin.messaging.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; // @public @@ -190,7 +192,7 @@ export class Messaging { sendToTopic(topic: string, payload: MessagingPayload, options?: MessagingOptions): Promise; subscribeToTopic(registrationTokenOrTokens: string | string[], topic: string): Promise; unsubscribeFromTopic(registrationTokenOrTokens: string | string[], topic: string): Promise; - } +} // @public export interface MessagingConditionResponse { @@ -350,5 +352,4 @@ export interface WebpushNotification { vibrate?: number | number[]; } - ``` diff --git a/etc/firebase-admin.project-management.api.md b/etc/firebase-admin.project-management.api.md index a2d20dc539..2b8d28297c 100644 --- a/etc/firebase-admin.project-management.api.md +++ b/etc/firebase-admin.project-management.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; // @public @@ -75,7 +77,7 @@ export class ProjectManagement { listIosApps(): Promise; setDisplayName(newDisplayName: string): Promise; shaCertificate(shaHash: string): ShaCertificate; - } +} // @public export class ShaCertificate { @@ -86,5 +88,4 @@ export class ShaCertificate { readonly shaHash: string; } - ``` diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md index 6e0e2eb142..933f8536d7 100644 --- a/etc/firebase-admin.remote-config.api.md +++ b/etc/firebase-admin.remote-config.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; // @public @@ -113,5 +115,4 @@ export interface Version { versionNumber?: string; } - ``` diff --git a/etc/firebase-admin.security-rules.api.md b/etc/firebase-admin.security-rules.api.md index c3a75dd97e..890da538d1 100644 --- a/etc/firebase-admin.security-rules.api.md +++ b/etc/firebase-admin.security-rules.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; // Warning: (ae-forgotten-export) The symbol "App" needs to be exported by the entry point index.d.ts @@ -56,5 +58,4 @@ export class SecurityRules { releaseStorageRulesetFromSource(source: string | Buffer, bucket?: string): Promise; } - ``` diff --git a/etc/firebase-admin.storage.api.md b/etc/firebase-admin.storage.api.md index 278148d2b2..204c033a6b 100644 --- a/etc/firebase-admin.storage.api.md +++ b/etc/firebase-admin.storage.api.md @@ -4,6 +4,8 @@ ```ts +/// + import { Agent } from 'http'; import { Bucket } from '@google-cloud/storage'; @@ -16,7 +18,6 @@ export function getStorage(app?: App): Storage; export class Storage { get app(): App; bucket(name?: string): Bucket; - } - +} ``` diff --git a/package-lock.json b/package-lock.json index d1097b9d83..a32e9e19e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "firebase-admin", - "version": "9.100.0-alpha.0", + "version": "9.100.0-alpha.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -14,9 +14,9 @@ } }, "@babel/compat-data": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz", - "integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", + "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", "dev": true }, "@babel/core": { @@ -42,11 +42,87 @@ "source-map": "^0.5.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", + "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -70,14 +146,14 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz", - "integrity": "sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA==", + "version": "7.13.16", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", + "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", "dev": true, "requires": { - "@babel/compat-data": "^7.14.4", + "@babel/compat-data": "^7.13.15", "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.16.6", + "browserslist": "^4.14.5", "semver": "^6.3.0" } }, @@ -133,6 +209,14 @@ "@babel/template": "^7.12.13", "@babel/traverse": "^7.14.2", "@babel/types": "^7.14.2" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "dev": true + } } }, "@babel/helper-optimise-call-expression": { @@ -145,15 +229,15 @@ } }, "@babel/helper-replace-supers": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz", - "integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==", + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.3.tgz", + "integrity": "sha512-Rlh8qEWZSTfdz+tgNV/N4gz1a0TMNwCUcENhMjHTHKp3LseYH5Jha0NSlyTQWMnjbYcwFt+bqAMqSLHVXkQ6UA==", "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.13.12", "@babel/helper-optimise-call-expression": "^7.12.13", "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.4" + "@babel/types": "^7.14.2" } }, "@babel/helper-simple-access": { @@ -261,9 +345,9 @@ } }, "@babel/parser": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", - "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.3.tgz", + "integrity": "sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ==", "dev": true }, "@babel/template": { @@ -275,6 +359,84 @@ "@babel/code-frame": "^7.12.13", "@babel/parser": "^7.12.13", "@babel/types": "^7.12.13" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", + "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "@babel/traverse": { @@ -293,22 +455,121 @@ "globals": "^11.1.0" }, "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "dev": true + }, + "@babel/highlight": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", + "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, "@babel/types": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", - "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", + "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.0", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "dev": true + } + } + }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz", + "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" } }, "@firebase/api-documenter": { @@ -325,21 +586,87 @@ "js-yaml": "4.0.0", "resolve": "~1.17.0", "tslib": "^2.1.0" + }, + "dependencies": { + "@microsoft/tsdoc": { + "version": "0.12.24", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", + "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", + "dev": true + }, + "@rushstack/node-core-library": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", + "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", + "dev": true, + "requires": { + "@types/node": "10.17.13", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~3.18.3" + } + }, + "@rushstack/ts-command-line": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.8.tgz", + "integrity": "sha512-8ghIWhkph7NnLCMDJtthpsb7TMOsVGXVDvmxjE/CeklTqjbbUFBjGXizJfpbEkRQTELuZQ2+vGn7sGwIWKN2uA==", + "dev": true, + "requires": { + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "colors": "~1.2.1", + "string-argv": "~0.3.1" + } + }, + "@types/node": { + "version": "10.17.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", + "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } } }, "@firebase/app": { - "version": "0.6.22", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.22.tgz", - "integrity": "sha512-9E0KP7Z+LpBOx/oQauLYvf3XleYpbfoi058wStADUtP+eOX5GIImAFNDTOO4ZNuJfLgyrHpKi7Cct6mDdxrz+g==", + "version": "0.6.26", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.26.tgz", + "integrity": "sha512-y4tpb+uiYLQC5+/AHBtIGZMaTjJ2BHQEsXmPqxyhfVFDzWMcXFsc//RVxA/0OejajhJR6GeqDcIS3m47mUD+Aw==", "dev": true, "requires": { "@firebase/app-types": "0.6.2", - "@firebase/component": "0.5.0", + "@firebase/component": "0.5.2", "@firebase/logger": "0.2.6", "@firebase/util": "1.1.0", "dom-storage": "2.1.0", "tslib": "^2.1.0", "xmlhttprequest": "1.8.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "dev": true + } } }, "@firebase/app-types": { @@ -348,9 +675,9 @@ "integrity": "sha512-2VXvq/K+n8XMdM4L2xy5bYp2ZXMawJXluUIDzUBvMthVR+lhxK4pfFiqr1mmDbv9ydXvEAuFsD+6DpcZuJcSSw==" }, "@firebase/auth": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.6.tgz", - "integrity": "sha512-1Lj3AY40Z2weCK6FuJqUEkeVJpRaaCo1LT6P5s3VIR99PDYLHeMm2m02rBaskE7ralJA975Vkv7sHrpykRfDrA==", + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.5.tgz", + "integrity": "sha512-Cgs/TlVot2QkbJyEphvKmu+2qxYlNN+Q2+29aqZwryrnn1eLwlC7nT89K6O91/744HJRtiThm02bMj2Wh61E3Q==", "dev": true, "requires": { "@firebase/auth-types": "0.10.3" @@ -368,26 +695,51 @@ "dev": true }, "@firebase/component": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.0.tgz", - "integrity": "sha512-v18csWtXb0ri+3m7wuGLY/UDgcb89vuMlZGQ//+7jEPLIQeLbylvZhol1uzW9WzoOpxMxOS2W5qyVGX36wZvEA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.2.tgz", + "integrity": "sha512-QT+o6VaBCz/k8wmC/DErU9dQK2QeIoHtkBkryZVTSRkrvulglEWNIpbPp86UbuqZZd1wwzoh6m7BL6JbdEp9SQ==", + "dev": true, "requires": { "@firebase/util": "1.1.0", "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "dev": true + } } }, "@firebase/database": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.10.2.tgz", - "integrity": "sha512-jMGtl5eBES9k0rOIZd6/EAuVB6m3LzRei1lvEiqWWBje2Xoz//7sjZcxOYtAKCCLldEI1EUrzW8Tv5yEAoPPpg==", + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.10.7.tgz", + "integrity": "sha512-7BFj8LFhGL+TmLiPOffOVfkrO2wm44mGcT0jqrkTkt1KydapmjABFJBRvONvlLij5LoWrJK1cSuE8wYDQrDq2Q==", "requires": { "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.0", + "@firebase/component": "0.5.4", "@firebase/database-types": "0.7.2", "@firebase/logger": "0.2.6", "@firebase/util": "1.1.0", "faye-websocket": "0.11.3", "tslib": "^2.1.0" + }, + "dependencies": { + "@firebase/component": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.4.tgz", + "integrity": "sha512-KoLDPTsvxWr6FT9kn/snffJItaWXZLHLJlZVKiiw+flKE6MVA8Eec+ctvM2zcsMZzC2Z47gFnVqywfBlOevmpQ==", + "requires": { + "@firebase/util": "1.1.0", + "tslib": "^2.1.0" + } + }, + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + } } }, "@firebase/database-types": { @@ -409,6 +761,13 @@ "integrity": "sha512-lfuSASuPKNdfebuFR8rjFamMQUPH9iiZHcKS755Rkm/5gRT0qC7BMhCh3ZkHf7NVbplzIc/GhmX2jM+igDRCag==", "requires": { "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + } } }, "@google-cloud/common": { @@ -429,14 +788,14 @@ } }, "@google-cloud/firestore": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.12.2.tgz", - "integrity": "sha512-5rurTAJXQ0SANEf8K9eA2JAB5zAh+pu4tGRnkZx5gBWQLZXdBFdtepS+irvKuSXw1KbeAQOuRANSc/nguys6SQ==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.13.1.tgz", + "integrity": "sha512-LtxboFZQ3MGwy1do8a0ykMJocM+TFgOpZoAihMwW498UDd641DJgJu0Kw0CD0bPpEaYUfhbeAUBq2ZO63DOz7g==", "optional": true, "requires": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.12.0", + "google-gax": "^2.17.0", "protobufjs": "^6.8.6" } }, @@ -492,18 +851,18 @@ } }, "@grpc/grpc-js": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.2.tgz", - "integrity": "sha512-UXepkOKCATJrhHGsxt+CGfpZy9zUn1q9mop5kfcXq1fBkTePxVNPOdnISlCbJFlCtld+pSLGyZCzr9/zVprFKA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.4.tgz", + "integrity": "sha512-AxtZcm0mArQhY9z8T3TynCYVEaSKxNCa9mVhVwBCUnsuUEe8Zn94bPYYKVQSLt+hJJ1y0ukr3mUvtWfcATL/IQ==", "optional": true, "requires": { "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.2.tgz", - "integrity": "sha512-q2Qle60Ht2OQBCp9S5hv1JbI4uBBq6/mqSevFNK3ZEgRDBCAkWqZPUhD/K9gXOHrHKluliHiVq2L9sw1mVyAIg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.4.tgz", + "integrity": "sha512-7xvDvW/vJEcmLUltCUGOgWRPM8Oofv0eCFSVMuKqaqWJaXSzmB+m9hiyqe34QofAl4WAzIKUZZlinIF9FOHyTQ==", "optional": true, "requires": { "@types/long": "^4.0.1", @@ -513,6 +872,48 @@ "yargs": "^16.1.1" }, "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "optional": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "optional": true + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -569,33 +970,6 @@ "esprima": "^4.0.0" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -633,6 +1007,15 @@ "tar": "^6.1.0" }, "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -641,39 +1024,39 @@ "requires": { "lru-cache": "^6.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, "@microsoft/api-extractor": { - "version": "7.15.2", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.15.2.tgz", - "integrity": "sha512-/Y/n+QOc1vM6Vg3OAUByT/wXdZciE7jV3ay33+vxl3aKva5cNsuOauL14T7XQWUiLko3ilPwrcnFcEjzXpLsuA==", + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.18.4.tgz", + "integrity": "sha512-Wx45VuIAu09Pk9Qwzt0I57OX31BaWO2r6+mfSXqYFsJjYTqwUkdFh92G1GKYgvuR9oF/ai7w10wrFpx5WZYbGg==", "dev": true, "requires": { - "@microsoft/api-extractor-model": "7.13.2", + "@microsoft/api-extractor-model": "7.13.4", "@microsoft/tsdoc": "0.13.2", "@microsoft/tsdoc-config": "~0.15.2", - "@rushstack/node-core-library": "3.38.0", - "@rushstack/rig-package": "0.2.12", - "@rushstack/ts-command-line": "4.7.10", + "@rushstack/node-core-library": "3.39.1", + "@rushstack/rig-package": "0.2.13", + "@rushstack/ts-command-line": "4.8.1", "colors": "~1.2.1", "lodash": "~4.17.15", "resolve": "~1.17.0", "semver": "~7.3.0", "source-map": "~0.6.1", - "typescript": "~4.2.4" + "typescript": "~4.3.5" }, "dependencies": { - "@microsoft/tsdoc": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", - "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", - "dev": true - }, "@rushstack/node-core-library": { - "version": "3.38.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.38.0.tgz", - "integrity": "sha512-cmvl0yQx8sSmbuXwiRYJi8TO+jpTtrLJQ8UmFHhKvgPVJAW8cV8dnpD1Xx/BvTGrJZ2XtRAIkAhBS9okBnap4w==", + "version": "3.39.1", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.39.1.tgz", + "integrity": "sha512-HHgMEHZTXQ3NjpQzWd5+fSt2Eod9yFwj6qBPbaeaNtDNkOL8wbLoxVimQNtcH0Qhn4wxF5u2NTDNFsxf2yd1jw==", "dev": true, "requires": { "@types/node": "10.17.13", @@ -687,24 +1070,21 @@ "z-schema": "~3.18.3" } }, - "@rushstack/ts-command-line": { - "version": "4.7.10", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.10.tgz", - "integrity": "sha512-8t042g8eerypNOEcdpxwRA3uCmz0duMo21rG4Z2mdz7JxJeylDmzjlU3wDdef2t3P1Z61JCdZB6fbm1Mh0zi7w==", - "dev": true, - "requires": { - "@types/argparse": "1.0.38", - "argparse": "~1.0.9", - "colors": "~1.2.1", - "string-argv": "~0.3.1" - } - }, "@types/node": { "version": "10.17.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -715,68 +1095,34 @@ } }, "typescript": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true } } }, "@microsoft/api-extractor-model": { - "version": "7.13.2", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.13.2.tgz", - "integrity": "sha512-gA9Q8q5TPM2YYk7rLinAv9KqcodrmRC13BVmNzLswjtFxpz13lRh0BmrqD01/sddGpGMIuWFYlfUM4VSWxnggA==", + "version": "7.13.4", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.13.4.tgz", + "integrity": "sha512-NYaR3hJinh089/Gkee8fvmEFf9zKkoUvNxgkqUlKBCDXH2+Ou4tNDuL8G6zjhKBPicHkp2VcL8l7q9H6txUkjQ==", "dev": true, "requires": { "@microsoft/tsdoc": "0.13.2", "@microsoft/tsdoc-config": "~0.15.2", - "@rushstack/node-core-library": "3.38.0" - }, - "dependencies": { - "@microsoft/tsdoc": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", - "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", - "dev": true - }, - "@rushstack/node-core-library": { - "version": "3.38.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.38.0.tgz", - "integrity": "sha512-cmvl0yQx8sSmbuXwiRYJi8TO+jpTtrLJQ8UmFHhKvgPVJAW8cV8dnpD1Xx/BvTGrJZ2XtRAIkAhBS9okBnap4w==", - "dev": true, - "requires": { - "@types/node": "10.17.13", - "colors": "~1.2.1", - "fs-extra": "~7.0.1", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.17.0", - "semver": "~7.3.0", - "timsort": "~0.3.0", - "z-schema": "~3.18.3" - } - }, - "@types/node": { - "version": "10.17.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", - "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } + "@rushstack/node-core-library": "3.39.1" } }, "@microsoft/tsdoc": { - "version": "0.12.24", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", - "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", + "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", "dev": true }, "@microsoft/tsdoc-config": { @@ -791,11 +1137,17 @@ "resolve": "~1.19.0" }, "dependencies": { - "@microsoft/tsdoc": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", - "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", - "dev": true + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } }, "resolve": { "version": "1.19.0", @@ -809,6 +1161,32 @@ } } }, + "@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.4", + "fastq": "^1.6.0" + } + }, "@panva/asn1.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", @@ -879,9 +1257,9 @@ "optional": true }, "@rushstack/node-core-library": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", - "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", + "version": "3.39.1", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.39.1.tgz", + "integrity": "sha512-HHgMEHZTXQ3NjpQzWd5+fSt2Eod9yFwj6qBPbaeaNtDNkOL8wbLoxVimQNtcH0Qhn4wxF5u2NTDNFsxf2yd1jw==", "dev": true, "requires": { "@types/node": "10.17.13", @@ -901,6 +1279,15 @@ "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -909,13 +1296,19 @@ "requires": { "lru-cache": "^6.0.0" } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true } } }, "@rushstack/rig-package": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.2.12.tgz", - "integrity": "sha512-nbePcvF8hQwv0ql9aeQxcaMPK/h1OLAC00W7fWCRWIvD2MchZOE8jumIIr66HGrfG2X1sw++m/ZYI4D+BM5ovQ==", + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.2.13.tgz", + "integrity": "sha512-qQMAFKvfb2ooaWU9DrGIK9d8QfyHy/HiuITJbWenlKgzcDXQvQgEduk57YF4Y7LLasDJ5ZzLaaXwlfX8qCRe5Q==", "dev": true, "requires": { "resolve": "~1.17.0", @@ -923,9 +1316,9 @@ } }, "@rushstack/ts-command-line": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.8.tgz", - "integrity": "sha512-8ghIWhkph7NnLCMDJtthpsb7TMOsVGXVDvmxjE/CeklTqjbbUFBjGXizJfpbEkRQTELuZQ2+vGn7sGwIWKN2uA==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.8.1.tgz", + "integrity": "sha512-rmxvYdCNRbyRs+DYAPye3g6lkCkWHleqO40K8UPvUAzFqEuj6+YCVssBiOmrUDCoM5gaegSNT0wFDYhz24DWtw==", "dev": true, "requires": { "@types/argparse": "1.0.38", @@ -975,6 +1368,30 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "optional": true }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, "@types/argparse": { "version": "1.0.38", "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", @@ -982,10 +1399,13 @@ "dev": true }, "@types/bcrypt": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-2.0.0.tgz", - "integrity": "sha512-/r/ihQBlYMUYHqcFXix76I3OLYTaUcU8xV2agtB2hCds2rfJI56UyKu0e2LkAW2/4HHmQKmQRFXqM8D6y3Tc5g==", - "dev": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.0.tgz", + "integrity": "sha512-agtcFKaruL8TmcvqbndlqHPSJgsolhf/qPWchFlgnW1gECTN/nKbFcoFnvKAQRFfKbh+BO6A3SWdJu9t+xF3Lw==", + "dev": true, + "requires": { + "@types/node": "*" + } }, "@types/bluebird": { "version": "3.5.35", @@ -994,9 +1414,9 @@ "dev": true }, "@types/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", "requires": { "@types/connect": "*", "@types/node": "*" @@ -1009,9 +1429,9 @@ "dev": true }, "@types/chai": { - "version": "4.2.18", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.18.tgz", - "integrity": "sha512-rS27+EkB/RE1Iz3u0XtVL5q36MGDWbgYe7zWiodyKNUnthxY0rukK5V36eiUCtCisB7NN8zKYH6DO2M37qxFEQ==", + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz", + "integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==", "dev": true }, "@types/chai-as-promised": { @@ -1024,9 +1444,9 @@ } }, "@types/connect": { - "version": "3.4.34", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", - "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", "requires": { "@types/node": "*" } @@ -1038,9 +1458,9 @@ "dev": true }, "@types/express": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.12.tgz", - "integrity": "sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q==", + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.18", @@ -1058,9 +1478,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz", - "integrity": "sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", + "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", "requires": { "@types/node": "*", "@types/qs": "*", @@ -1068,16 +1488,16 @@ } }, "@types/express-unless": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz", - "integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.2.tgz", + "integrity": "sha512-Q74UyYRX/zIgl1HSp9tUX2PlG8glkVm+59r7aK4KGKzC5jqKIOX6rrVLRQrzpZUQ84VukHtRoeAuon2nIssHPQ==", "requires": { "@types/express": "*" } }, "@types/firebase-token-generator": { "version": "2.0.28", - "resolved": "https://registry.npmjs.org/@types/firebase-token-generator/-/firebase-token-generator-2.0.28.tgz", + "resolved": "http://registry.npmjs.org/@types/firebase-token-generator/-/firebase-token-generator-2.0.28.tgz", "integrity": "sha1-Z1VIHZMk4mt6XItFXWgUg3aCw5Y=", "dev": true }, @@ -1097,9 +1517,9 @@ } }, "@types/lodash": { - "version": "4.14.170", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz", - "integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==", + "version": "4.14.171", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz", + "integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==", "dev": true }, "@types/long": { @@ -1120,45 +1540,45 @@ "dev": true }, "@types/minimist": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", - "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", + "version": "1.2.2", + "resolved": "http://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, "@types/mocha": { - "version": "2.2.48", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", - "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", + "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==", "dev": true }, "@types/nock": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@types/nock/-/nock-9.3.1.tgz", - "integrity": "sha512-eOVHXS5RnWOjTVhu3deCM/ruy9E6JCgeix2g7wpFiekQh3AaEAK1cz43tZDukKmtSmQnwvSySq7ubijCA32I7Q==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@types/nock/-/nock-11.1.0.tgz", + "integrity": "sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw==", "dev": true, "requires": { - "@types/node": "*" + "nock": "*" } }, "@types/node": { - "version": "15.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.0.tgz", - "integrity": "sha512-+aHJvoCsVhO2ZCuT4o5JtcPrCPyDE3+1nvbDprYes+pPkEsbjH7AGUCNtjMOXS0fqH14t+B7yLzaqSz92FPWyw==" + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz", + "integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==" }, "@types/qs": { - "version": "6.9.6", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", - "integrity": "sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA==" + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, "@types/range-parser": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", - "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "@types/request": { - "version": "2.48.5", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", - "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", + "version": "2.48.6", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.6.tgz", + "integrity": "sha512-vrZaV3Ij7j/l/3hz6OttZFtpRCu7zlq7XgkYHJP6FwVEAZkGQ095WqyJV08/GlW9eyXKVcp/xmtruHm8eHpw1g==", "dev": true, "requires": { "@types/caseless": "*", @@ -1178,21 +1598,32 @@ } }, "@types/serve-static": { - "version": "1.13.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", - "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", "requires": { "@types/mime": "^1", "@types/node": "*" } }, "@types/sinon": { - "version": "9.0.11", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.11.tgz", - "integrity": "sha512-PwP4UY33SeeVKodNE37ZlOsR9cReypbMJOhZ7BVE0lB+Hix3efCOxiJWiE5Ia+yL9Cn2Ch72EjFTRze8RZsNtg==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.2.tgz", + "integrity": "sha512-BHn8Bpkapj8Wdfxvh2jWIUoaYB/9/XhsL0oOvBfRagJtKlSl9NWPcFOz2lRukI9szwGxFtYZCTejJSqsGDbdmw==", "dev": true, "requires": { - "@types/sinonjs__fake-timers": "*" + "@sinonjs/fake-timers": "^7.1.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } } }, "@types/sinon-chai": { @@ -1205,16 +1636,10 @@ "@types/sinon": "*" } }, - "@types/sinonjs__fake-timers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", - "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", - "dev": true - }, "@types/tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", + "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==", "dev": true }, "@typescript-eslint/eslint-plugin": { @@ -1406,6 +1831,21 @@ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { "color-convert": "^2.0.1" + }, + "dependencies": { + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + } } }, "ansi-wrap": { @@ -1449,6 +1889,46 @@ "requires": { "@microsoft/tsdoc": "0.12.24", "@rushstack/node-core-library": "3.36.0" + }, + "dependencies": { + "@microsoft/tsdoc": { + "version": "0.12.24", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", + "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", + "dev": true + }, + "@rushstack/node-core-library": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", + "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", + "dev": true, + "requires": { + "@types/node": "10.17.13", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~3.18.3" + } + }, + "@types/node": { + "version": "10.17.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", + "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } } }, "append-buffer": { @@ -1533,7 +2013,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -1647,18 +2126,9 @@ } }, "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, "array-unique": { @@ -2033,9 +2503,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001233", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001233.tgz", - "integrity": "sha512-BmkbxLfStqiPA7IEzQpIk0UFZFf3A4E6fzjPJ6OR+bFC2L8ES9J8zGA/asoi47p8XDVkev+WJo2I2Nc8c/34Yg==", + "version": "1.0.30001228", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz", + "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==", "dev": true }, "caseless": { @@ -2197,6 +2667,7 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -2291,19 +2762,6 @@ "object-visit": "^1.0.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -2676,24 +3134,34 @@ } }, "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "requires": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" }, "dependencies": { + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -2739,6 +3207,23 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2804,9 +3289,9 @@ } }, "electron-to-chromium": { - "version": "1.3.746", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.746.tgz", - "integrity": "sha512-3ffyGODL38apwSsIgXaWnAKNXChsjXhAmBTjbqCbrv1fBbVltuNLWh0zdrQbwK/oxPQ/Gss/kYfFAPPGu9mszQ==", + "version": "1.3.736", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.736.tgz", + "integrity": "sha512-DY8dA7gR51MSo66DqitEQoUMQ0Z+A2DSXFi7tK304bdTVqczCAfUuyQw6Wdg8hIoo5zIxkU1L24RQtUce1Ioig==", "dev": true }, "emoji-regex": { @@ -3139,8 +3624,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.4.0", @@ -3395,6 +3879,73 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "dependencies": { + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + } + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3413,6 +3964,15 @@ "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==", "optional": true }, + "fastq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, "faye-websocket": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", @@ -3517,7 +4077,7 @@ }, "firebase-token-generator": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/firebase-token-generator/-/firebase-token-generator-2.0.0.tgz", + "resolved": "http://registry.npmjs.org/firebase-token-generator/-/firebase-token-generator-2.0.0.tgz", "integrity": "sha1-l2fXWewTq9yZuhFf1eqZ2Lk9EgY=", "dev": true }, @@ -4087,23 +4647,23 @@ } }, "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "version": "11.0.3", + "resolved": "http://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", "dev": true, "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" }, "dependencies": { - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true } } @@ -4135,9 +4695,9 @@ } }, "google-gax": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.14.1.tgz", - "integrity": "sha512-I5RDEN7MEptrCxeHX3ht7nKFGfyjgYX4hQKI9eVMBohMzVbFSwWUndo0CcKXu8es7NhB4gt2XYLm1AHkXhtHpA==", + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.17.1.tgz", + "integrity": "sha512-CoR7OYuEzIDt3mp7cLYL+oGPmYdImS1WEwIvjF0zk0LhEBBmvRjWHTpBwazLGsT8hz+6zPh/4fjIjNaUxzIvzg==", "optional": true, "requires": { "@grpc/grpc-js": "~1.3.0", @@ -4146,12 +4706,121 @@ "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.0.2", + "google-auth-library": "^7.3.0", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", "object-hash": "^2.1.1", "protobufjs": "^6.10.2", "retry-request": "^4.0.0" + }, + "dependencies": { + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "optional": true, + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "gaxios": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.0.tgz", + "integrity": "sha512-pHplNbslpwCLMyII/lHPWFQbJWOX0B3R1hwBEOvzYi1GmdKZruuEHK4N9V6f7tf1EaPYyF80mui1+344p6SmLg==", + "optional": true, + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.0.tgz", + "integrity": "sha512-L9XQUpvKJCM76YRSmcxrR4mFPzPGsgZUH+GgHMxAET8qc6+BhRJq63RLhWakgEO2KKVgeSDVfyiNjkGSADwNTA==", + "optional": true, + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, + "google-auth-library": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.3.0.tgz", + "integrity": "sha512-MPeeMlnsYnoiiVFMwX3hgaS684aiXrSqKoDP+xL4Ejg4Z0qLvIeg4XsaChemyFI8ZUO7ApwDAzNtgmhWSDNh5w==", + "optional": true, + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.0.tgz", + "integrity": "sha512-JUtEHXL4DY/N+xhlm7TC3qL797RPAtk0ZGXNs3/gWyiDHYoA/8Rjes0pztkda+sZv4ej1EoO2KhWgW5V9KTrSQ==", + "optional": true, + "requires": { + "node-forge": "^0.10.0" + } + }, + "gtoken": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.0.tgz", + "integrity": "sha512-mCcISYiaRZrJpfqOs0QWa6lfEM/C1V9ASkzFmuz43XBb5s1Vynh+CZy1ECeeJXVGx2PRByjYzb4Y4/zr1byr0w==", + "optional": true, + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.0.3", + "jws": "^4.0.0" + } + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "optional": true, + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + } } }, "google-p12-pem": { @@ -4325,14 +4994,15 @@ } }, "gulp-filter": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-6.0.0.tgz", - "integrity": "sha512-veQFW93kf6jBdWdF/RxMEIlDK2mkjHyPftM381DID2C9ImTVngwYpyyThxm4/EpgcNOT37BLefzMOjEKbyYg0Q==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-7.0.0.tgz", + "integrity": "sha512-ZGWtJo0j1mHfP77tVuhyqem4MRA5NfNRjoVe6VAkLGeQQ/QGo2VsFwp7zfPTGDsd1rwzBmoDHhxpE6f5B3Zuaw==", "dev": true, "requires": { - "multimatch": "^4.0.0", + "multimatch": "^5.0.0", "plugin-error": "^1.0.1", - "streamfilter": "^3.0.0" + "streamfilter": "^3.0.0", + "to-absolute-glob": "^2.0.2" } }, "gulp-header": { @@ -4345,6 +5015,48 @@ "lodash.template": "^4.5.0", "map-stream": "0.0.7", "through2": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } } }, "gulp-typescript": { @@ -4759,9 +5471,9 @@ "dev": true }, "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", + "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", "dev": true, "requires": { "has": "^1.0.3" @@ -4883,28 +5595,16 @@ "optional": true }, "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "dev": true }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true }, "is-plain-obj": { "version": "2.1.0", @@ -5081,6 +5781,15 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5122,6 +5831,23 @@ "istanbul-lib-coverage": "^3.0.0", "make-dir": "^3.0.0", "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "istanbul-lib-source-maps": { @@ -5133,6 +5859,14 @@ "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "istanbul-reports": { @@ -5330,15 +6064,25 @@ } }, "jwks-rsa": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.3.tgz", - "integrity": "sha512-/rkjXRWAp0cS00tunsHResw68P5iTQru8+jHufLNv3JHc4nObFEndfEUSuPugh09N+V9XYxKUqi7QrkmCHSSSg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-2.0.4.tgz", + "integrity": "sha512-iJqVCECYZZ+3oPmY1qXv3Fq+3ywDtuNEVBvG41pPlaR0zyGxa12nC0beAOBBUhETJmc05puS50mRQN4NkCGhmg==", "requires": { "@types/express-jwt": "0.0.42", - "debug": "^4.1.0", + "debug": "^4.3.2", "jose": "^2.0.5", "limiter": "^1.1.5", - "lru-memoizer": "^2.1.2" + "lru-memoizer": "^2.1.4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + } } }, "jws": { @@ -5471,12 +6215,12 @@ } }, "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^5.0.0" + "p-locate": "^4.1.0" } }, "lodash": { @@ -5601,6 +6345,13 @@ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } } }, "lru-memoizer": { @@ -5713,6 +6464,12 @@ "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", "dev": true }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -5780,6 +6537,14 @@ "dev": true, "requires": { "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } } }, "minizlib": { @@ -5790,6 +6555,14 @@ "requires": { "minipass": "^3.0.0", "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } } }, "mixin-deep": { @@ -5813,12 +6586,6 @@ } } }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, "mocha": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", @@ -5960,12 +6727,30 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6008,6 +6793,22 @@ "isexe": "^2.0.0" } }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==" + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -6037,9 +6838,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multimatch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", - "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", + "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", "dev": true, "requires": { "@types/minimatch": "^3.0.3", @@ -6047,14 +6848,6 @@ "array-union": "^2.1.0", "arrify": "^2.0.1", "minimatch": "^3.0.4" - }, - "dependencies": { - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - } } }, "mute-stdout": { @@ -6144,9 +6937,9 @@ } }, "nock": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.1.0.tgz", - "integrity": "sha512-3N3DUY8XYrxxzWazQ+nSBpiaJ3q6gcpNh4gXovC/QBxrsvNp4tq+wsLHF6mJ3nrn3lPLn7KCJqKxy/9aD+0fdw==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.1.1.tgz", + "integrity": "sha512-YKTR9MjfK3kS9/l4nuTxyYm30cgOExRHzkLNhL8nhEUyU4f8Za/dRxOqjhVT1vGs0svWo3dDnJTUX1qxYeWy5w==", "dev": true, "requires": { "debug": "^4.1.0", @@ -6431,6 +7224,21 @@ "yargs": "^15.0.2" }, "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -6448,6 +7256,21 @@ "wrap-ansi": "^6.2.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -6458,32 +7281,17 @@ "path-exists": "^4.0.0" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "path-exists": { "version": "4.0.0", @@ -6503,6 +7311,35 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -6520,12 +7357,6 @@ "strip-ansi": "^6.0.0" } }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -6767,20 +7598,31 @@ "dev": true }, "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", "requires": { - "yocto-queue": "^0.1.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^3.0.2" + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } } }, "p-map": { @@ -6795,8 +7637,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "package-hash": { "version": "4.0.0", @@ -6874,16 +7715,10 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -7003,33 +7838,6 @@ "path-exists": "^4.0.0" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7064,7 +7872,7 @@ }, "pretty-hrtime": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, @@ -7166,6 +7974,12 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7396,15 +8210,27 @@ "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" - } - }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "requires": { - "lodash": "^4.17.19" + }, + "dependencies": { + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "requires": { + "lodash": "^4.17.19" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } } }, "require-directory": { @@ -7489,6 +8315,12 @@ "debug": "^4.1.1" } }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -7504,6 +8336,15 @@ "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "run-sequence": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/run-sequence/-/run-sequence-2.2.1.tgz", @@ -7631,7 +8472,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -7741,6 +8582,21 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -7750,6 +8606,12 @@ "integrity": "sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==", "dev": true }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", @@ -7946,16 +8808,6 @@ "urix": "^0.1.0" } }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "source-map-url": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", @@ -7982,6 +8834,15 @@ "which": "^2.0.1" }, "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8037,8 +8898,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.16.1", @@ -8221,6 +9081,14 @@ "dev": true, "requires": { "has-flag": "^4.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + } } }, "sver-compat": { @@ -8286,9 +9154,9 @@ } }, "tar": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", - "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.3.tgz", + "integrity": "sha512-3rUqwucgVZXTeyJyL2jqtUau8/8r54SioM1xj3AmTX3HnWQdj2AydfJ2qYYayPyIIznSplcvU9mhBb7dR2XF3w==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -8297,6 +9165,20 @@ "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } } }, "teeny-request": { @@ -8504,19 +9386,37 @@ } }, "ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "requires": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.0.tgz", + "integrity": "sha512-FstYHtQz6isj8rBtYMN4bZdnXN1vq4HCbqn9vdNQcInRqtB86PePJQIxE6es0PhxKWhj2PHuwbG40H+bxkZPmg==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.6.1", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", - "source-map-support": "^0.5.17", "yn": "3.1.1" }, "dependencies": { + "acorn": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", + "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", + "dev": true + }, + "acorn-walk": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz", + "integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w==", + "dev": true + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -8525,11 +9425,6 @@ } } }, - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" - }, "tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", @@ -9114,14 +10009,10 @@ "dev": true }, "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true }, "yargs": { "version": "17.0.1", @@ -9136,12 +10027,87 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + } } }, "yargs-parser": { "version": "20.2.7", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==" + "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", + "optional": true }, "yargs-unparser": { "version": "2.0.0", @@ -9175,11 +10141,6 @@ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" - }, "z-schema": { "version": "3.18.4", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.18.4.tgz", diff --git a/package.json b/package.json index 14f0b0b771..8cbcedeef1 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,9 @@ "firestore": [ "lib/firestore" ], + "installations": [ + "lib/installations" + ], "instance-id": [ "lib/instance-id" ], @@ -122,6 +125,10 @@ "require": "./lib/firestore/index.js", "import": "./lib/esm/firestore/index.js" }, + "./installations": { + "require": "./lib/installations/index.js", + "import": "./lib/esm/installations/index.js" + }, "./instance-id": { "require": "./lib/instance-id/index.js", "import": "./lib/esm/instance-id/index.js" @@ -166,22 +173,22 @@ }, "devDependencies": { "@firebase/api-documenter": "^0.1.2", - "@firebase/app": "^0.6.13", - "@firebase/auth": "^0.16.2", - "@firebase/auth-types": "^0.10.1", + "@firebase/app": "^0.6.21", + "@firebase/auth": "^0.16.5", + "@firebase/auth-types": "^0.10.3", "@microsoft/api-extractor": "^7.11.2", - "@types/bcrypt": "^2.0.0", + "@types/bcrypt": "^5.0.0", "@types/chai": "^4.0.0", "@types/chai-as-promised": "^7.1.0", "@types/firebase-token-generator": "^2.0.28", "@types/jsonwebtoken": "^8.5.0", "@types/lodash": "^4.14.104", "@types/minimist": "^1.2.0", - "@types/mocha": "^2.2.48", - "@types/nock": "^9.1.0", + "@types/mocha": "^8.2.2", + "@types/nock": "^11.1.0", "@types/request": "^2.47.0", "@types/request-promise": "^4.1.41", - "@types/sinon": "^9.0.0", + "@types/sinon": "^10.0.2", "@types/sinon-chai": "^3.0.0", "@typescript-eslint/eslint-plugin": "^2.20.0", "@typescript-eslint/parser": "^2.20.0", @@ -190,11 +197,11 @@ "chai-as-promised": "^7.0.0", "chalk": "^4.1.1", "child-process-promise": "^2.2.1", - "del": "^2.2.1", + "del": "^6.0.0", "eslint": "^6.8.0", "firebase-token-generator": "^2.0.0", "gulp": "^4.0.2", - "gulp-filter": "^6.0.0", + "gulp-filter": "^7.0.0", "gulp-header": "^2.0.9", "gulp-typescript": "^5.0.1", "http-message-parser": "^0.0.34", @@ -210,7 +217,7 @@ "run-sequence": "^2.2.1", "sinon": "^9.0.0", "sinon-chai": "^3.0.0", - "ts-node": "^9.0.0", + "ts-node": "^10.2.0", "typescript": "^3.7.3", "yargs": "^17.0.1" } diff --git a/src/app-check/app-check-api-client-internal.ts b/src/app-check/app-check-api-client-internal.ts index a57fa0a8e4..e7427f838a 100644 --- a/src/app-check/app-check-api-client-internal.ts +++ b/src/app-check/app-check-api-client-internal.ts @@ -52,7 +52,7 @@ export class AppCheckApiClient { /** * Exchange a signed custom token to App Check token - * + * * @param customToken The custom token to be exchanged. * @param appId The mobile App ID. * @returns A promise that fulfills with a `AppCheckToken`. @@ -159,9 +159,9 @@ export class AppCheckApiClient { * * @param duration The duration as a string with the suffix "s" preceded by the * number of seconds, with fractional seconds. For example, 3 seconds with 0 nanoseconds - * is expressed as "3s", while 3 seconds and 1 nanosecond is expressed as "3.000000001s", + * is expressed as "3s", while 3 seconds and 1 nanosecond is expressed as "3.000000001s", * and 3 seconds and 1 microsecond is expressed as "3.000001s". - * + * * @returns The duration in milliseconds. */ private stringToMilliseconds(duration: string): number { diff --git a/src/app-check/app-check-api.ts b/src/app-check/app-check-api.ts index 4eeaa1bce5..ab959af04d 100644 --- a/src/app-check/app-check-api.ts +++ b/src/app-check/app-check-api.ts @@ -30,6 +30,17 @@ export interface AppCheckToken { ttlMillis: number; } +/** + * Interface representing App Check token options. + */ +export interface AppCheckTokenOptions { + /** + * The length of time, in milliseconds, for which the App Check token will + * be valid. This value must be between 30 minutes and 7 days, inclusive. + */ + ttlMillis?: number; +} + /** * Interface representing a decoded Firebase App Check token, returned from the * {@link AppCheck.verifyToken} method. diff --git a/src/app-check/app-check-namespace.ts b/src/app-check/app-check-namespace.ts index 4e5de379b2..128cedd474 100644 --- a/src/app-check/app-check-namespace.ts +++ b/src/app-check/app-check-namespace.ts @@ -17,6 +17,7 @@ import { App } from '../app'; import { AppCheckToken as TAppCheckToken, + AppCheckTokenOptions as TAppCheckTokenOptions, DecodedAppCheckToken as TDecodedAppCheckToken, VerifyAppCheckTokenResponse as TVerifyAppCheckTokenResponse, } from './app-check-api'; @@ -71,4 +72,6 @@ export namespace appCheck { * Type alias to {@link firebase-admin.app-check#VerifyAppCheckTokenResponse}. */ export type VerifyAppCheckTokenResponse = TVerifyAppCheckTokenResponse; + + export type AppCheckTokenOptions = TAppCheckTokenOptions; } diff --git a/src/app-check/app-check.ts b/src/app-check/app-check.ts index 815ab7bf8d..97fc24f933 100644 --- a/src/app-check/app-check.ts +++ b/src/app-check/app-check.ts @@ -18,13 +18,14 @@ import { App } from '../app'; import { AppCheckApiClient } from './app-check-api-client-internal'; import { - appCheckErrorFromCryptoSignerError, AppCheckTokenGenerator + appCheckErrorFromCryptoSignerError, AppCheckTokenGenerator, } from './token-generator'; import { AppCheckTokenVerifier } from './token-verifier'; import { cryptoSignerFromApp } from '../utils/crypto-signer'; import { AppCheckToken, + AppCheckTokenOptions, VerifyAppCheckTokenResponse, } from './app-check-api'; @@ -57,11 +58,12 @@ export class AppCheck { * back to a client. * * @param appId The app ID to use as the JWT app_id. + * @param options Optional options object when creating a new App Check Token. * * @returns A promise that fulfills with a `AppCheckToken`. */ - public createToken(appId: string): Promise { - return this.tokenGenerator.createCustomToken(appId) + public createToken(appId: string, options?: AppCheckTokenOptions): Promise { + return this.tokenGenerator.createCustomToken(appId, options) .then((customToken) => { return this.client.exchangeToken(customToken, appId); }); diff --git a/src/app-check/index.ts b/src/app-check/index.ts index cf057ad7ab..3ff1ae302d 100644 --- a/src/app-check/index.ts +++ b/src/app-check/index.ts @@ -27,6 +27,7 @@ import { AppCheck } from './app-check'; export { AppCheckToken, + AppCheckTokenOptions, DecodedAppCheckToken, VerifyAppCheckTokenResponse, } from './app-check-api'; diff --git a/src/app-check/token-generator.ts b/src/app-check/token-generator.ts index 0ebe9196eb..97ebb3fb40 100644 --- a/src/app-check/token-generator.ts +++ b/src/app-check/token-generator.ts @@ -16,24 +16,26 @@ */ import * as validator from '../utils/validator'; -import { toWebSafeBase64 } from '../utils'; - +import { toWebSafeBase64, transformMillisecondsToSecondsString } from '../utils'; import { CryptoSigner, CryptoSignerError, CryptoSignerErrorCode } from '../utils/crypto-signer'; -import { +import { FirebaseAppCheckError, AppCheckErrorCode, - APP_CHECK_ERROR_CODE_MAPPING, + APP_CHECK_ERROR_CODE_MAPPING, } from './app-check-api-client-internal'; +import { AppCheckTokenOptions } from './app-check-api'; import { HttpError } from '../utils/api-request'; -const ONE_HOUR_IN_SECONDS = 60 * 60; +const ONE_MINUTE_IN_SECONDS = 60; +const ONE_MINUTE_IN_MILLIS = ONE_MINUTE_IN_SECONDS * 1000; +const ONE_DAY_IN_MILLIS = 24 * 60 * 60 * 1000; // Audience to use for Firebase App Check Custom tokens const FIREBASE_APP_CHECK_AUDIENCE = 'https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1beta.TokenExchangeService'; /** * Class for generating Firebase App Check tokens. - * + * * @internal */ export class AppCheckTokenGenerator { @@ -59,16 +61,20 @@ export class AppCheckTokenGenerator { * Creates a new custom token that can be exchanged to an App Check token. * * @param appId The Application ID to use for the generated token. - * + * * @return A Promise fulfilled with a custom token signed with a service account key * that can be exchanged to an App Check token. */ - public createCustomToken(appId: string): Promise { + public createCustomToken(appId: string, options?: AppCheckTokenOptions): Promise { if (!validator.isNonEmptyString(appId)) { throw new FirebaseAppCheckError( 'invalid-argument', '`appId` must be a non-empty string.'); } + let customOptions = {}; + if (typeof options !== 'undefined') { + customOptions = this.validateTokenOptions(options); + } return this.signer.getAccountId().then((account) => { const header = { alg: this.signer.algorithm, @@ -81,8 +87,9 @@ export class AppCheckTokenGenerator { // eslint-disable-next-line @typescript-eslint/camelcase app_id: appId, aud: FIREBASE_APP_CHECK_AUDIENCE, - exp: iat + ONE_HOUR_IN_SECONDS, + exp: iat + (ONE_MINUTE_IN_SECONDS * 5), iat, + ...customOptions, }; const token = `${this.encodeSegment(header)}.${this.encodeSegment(body)}`; return this.signer.sign(Buffer.from(token)) @@ -98,6 +105,35 @@ export class AppCheckTokenGenerator { const buffer: Buffer = (segment instanceof Buffer) ? segment : Buffer.from(JSON.stringify(segment)); return toWebSafeBase64(buffer).replace(/=+$/, ''); } + + /** + * Checks if a given `AppCheckTokenOptions` object is valid. If successful, returns an object with + * custom properties. + * + * @param options An options object to be validated. + * @returns A custom object with ttl converted to protobuf Duration string format. + */ + private validateTokenOptions(options: AppCheckTokenOptions): {[key: string]: any} { + if (!validator.isNonNullObject(options)) { + throw new FirebaseAppCheckError( + 'invalid-argument', + 'AppCheckTokenOptions must be a non-null object.'); + } + if (typeof options.ttlMillis !== 'undefined') { + if (!validator.isNumber(options.ttlMillis)) { + throw new FirebaseAppCheckError('invalid-argument', + 'ttlMillis must be a duration in milliseconds.'); + } + // ttlMillis must be between 30 minutes and 7 days (inclusive) + if (options.ttlMillis < (ONE_MINUTE_IN_MILLIS * 30) || options.ttlMillis > (ONE_DAY_IN_MILLIS * 7)) { + throw new FirebaseAppCheckError( + 'invalid-argument', + 'ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive).'); + } + return { ttl: transformMillisecondsToSecondsString(options.ttlMillis) }; + } + return {}; + } } /** diff --git a/src/app-check/token-verifier.ts b/src/app-check/token-verifier.ts index 9924f62b3f..a60a3a5bbd 100644 --- a/src/app-check/token-verifier.ts +++ b/src/app-check/token-verifier.ts @@ -30,7 +30,7 @@ const JWKS_URL = 'https://firebaseappcheck.googleapis.com/v1beta/jwks'; /** * Class for verifying Firebase App Check tokens. - * + * * @internal */ export class AppCheckTokenVerifier { @@ -141,7 +141,7 @@ export class AppCheckTokenVerifier { /** * Maps JwtError to FirebaseAppCheckError - * + * * @param error JwtError to be mapped. * @returns FirebaseAppCheckError instance. */ diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 0fdabacbbd..920dce3323 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -31,6 +31,7 @@ import { Storage } from '../storage/index'; import { Database } from '../database/index'; import { Firestore } from '../firestore/index'; import { InstanceId } from '../instance-id/index'; +import { Installations } from '../installations/index'; import { ProjectManagement } from '../project-management/index'; import { SecurityRules } from '../security-rules/index'; import { RemoteConfig } from '../remote-config/index'; @@ -261,6 +262,16 @@ export class FirebaseApp implements app.App { return fn(this); } + /** + * Returns the InstanceId service instance associated with this app. + * + * @returns The InstanceId service instance of this app. + */ + public installations(): Installations { + const fn = require('../installations/index').getInstallations; + return fn(this); + } + /** * Returns the MachineLearning service instance associated with this app. * diff --git a/src/app/firebase-namespace.ts b/src/app/firebase-namespace.ts index 18b870dd4e..f39eed6b9d 100644 --- a/src/app/firebase-namespace.ts +++ b/src/app/firebase-namespace.ts @@ -20,7 +20,7 @@ import fs = require('fs'); import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { app, appCheck, auth, messaging, machineLearning, storage, firestore, database, - instanceId, projectManagement, securityRules , remoteConfig, AppOptions, + instanceId, installations, projectManagement, securityRules , remoteConfig, AppOptions, } from '../firebase-namespace-api'; import { FirebaseApp } from './firebase-app'; import { cert, refreshToken, applicationDefault } from './credential-factory'; @@ -34,6 +34,7 @@ import AppCheck = appCheck.AppCheck; import Auth = auth.Auth; import Database = database.Database; import Firestore = firestore.Firestore; +import Installations = installations.Installations; import InstanceId = instanceId.InstanceId; import MachineLearning = machineLearning.MachineLearning; import Messaging = messaging.Messaging; @@ -302,6 +303,18 @@ export class FirebaseNamespace { return Object.assign(fn, { MachineLearning: machineLearning }); } + /** + * Gets the `Installations` service namespace. The returned namespace can be used to get the + * `Installations` service for the default app or an explicitly specified app. + */ + get installations(): FirebaseServiceNamespace { + const fn: FirebaseServiceNamespace = (app?: App) => { + return this.ensureApp(app).installations(); + }; + const installations = require('../installations/installations').Installations; + return Object.assign(fn, { Installations: installations }); + } + /** * Gets the `InstanceId` service namespace. The returned namespace can be used to get the * `Instance` service for the default app or an explicitly specified app. diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index c833449532..f6934be4d7 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -1241,7 +1241,7 @@ export class OIDCConfig implements OIDCAuthProviderConfig { } if (typeof options.responseType !== 'undefined') { request.responseType = options.responseType; - } + } return request; } @@ -1358,18 +1358,18 @@ export class OIDCConfig implements OIDCAuthProviderConfig { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_CONFIG, `"${key}" is not a valid OAuthResponseType parameter.`, - ); + ); } }); - + const idToken = options.responseType.idToken; - if (typeof idToken !== 'undefined' && !validator.isBoolean(idToken)) { + if (typeof idToken !== 'undefined' && !validator.isBoolean(idToken)) { throw new FirebaseAuthError( AuthClientErrorCode.INVALID_ARGUMENT, '"OIDCAuthProviderConfig.responseType.idToken" must be a boolean.', ); } - + const code = options.responseType.code; if (typeof code !== 'undefined') { if (!validator.isBoolean(code)) { @@ -1378,16 +1378,16 @@ export class OIDCConfig implements OIDCAuthProviderConfig { '"OIDCAuthProviderConfig.responseType.code" must be a boolean.', ); } - + // If code flow is enabled, client secret must be provided. if (code && typeof options.clientSecret === 'undefined') { throw new FirebaseAuthError( AuthClientErrorCode.MISSING_OAUTH_CLIENT_SECRET, 'The OAuth configuration client secret is required to enable OIDC code flow.', - ); + ); } } - + const allKeys = Object.keys(options.responseType).length; const enabledCount = Object.values(options.responseType).filter(Boolean).length; // Only one of OAuth response types can be set to true. diff --git a/src/firebase-namespace-api.ts b/src/firebase-namespace-api.ts index d1bea3d509..185fbfd53f 100644 --- a/src/firebase-namespace-api.ts +++ b/src/firebase-namespace-api.ts @@ -19,6 +19,7 @@ import { auth } from './auth/auth-namespace'; import { database } from './database/database-namespace'; import { firestore } from './firestore/firestore-namespace'; import { instanceId } from './instance-id/instance-id-namespace'; +import { installations } from './installations/installations-namespace'; import { machineLearning } from './machine-learning/machine-learning-namespace'; import { messaging } from './messaging/messaging-namespace'; import { projectManagement } from './project-management/project-management-namespace'; @@ -47,6 +48,8 @@ export namespace app { auth(): auth.Auth; database(url?: string): database.Database; firestore(): firestore.Firestore; + installations(): installations.Installations; + /** @deprecated */ instanceId(): instanceId.InstanceId; machineLearning(): machineLearning.MachineLearning; messaging(): messaging.Messaging; @@ -82,6 +85,7 @@ export { auth } from './auth/auth-namespace'; export { database } from './database/database-namespace'; export { firestore } from './firestore/firestore-namespace'; export { instanceId } from './instance-id/instance-id-namespace'; +export { installations } from './installations/installations-namespace'; export { machineLearning } from './machine-learning/machine-learning-namespace'; export { messaging } from './messaging/messaging-namespace'; export { projectManagement } from './project-management/project-management-namespace'; diff --git a/src/installations/index.ts b/src/installations/index.ts new file mode 100644 index 0000000000..9974852875 --- /dev/null +++ b/src/installations/index.ts @@ -0,0 +1,63 @@ +/*! + * Copyright 2020 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Firebase Instance ID service. + * + * @packageDocumentation + */ + +import { App, getApp } from '../app/index'; +import { Installations } from './installations'; +import { FirebaseApp } from '../app/firebase-app'; + +export { Installations }; + +/** + * Gets the {@link Installations} service for the default app or a given app. + * + * `getInstallations()` can be called with no arguments to access the default + * app's `Installations` service or as `getInstallations(app)` to access the + * `Installations` service associated with a specific app. + * + * @example + * ```javascript + * // Get the Installations service for the default app + * const defaultInstallations = getInstallations(); + * ``` + * + * @example + * ```javascript + * // Get the Installations service for a given app + * const otherInstallations = getInstallations(otherApp); + *``` + * + * @param app Optional app whose `Installations` service to + * return. If not provided, the default `Installations` service will be + * returned. + * + * @returns The default `Installations` service if + * no app is provided or the `Installations` service associated with the + * provided app. + */ +export function getInstallations(app?: App): Installations { + if (typeof app === 'undefined') { + app = getApp(); + } + + const firebaseApp: FirebaseApp = app as FirebaseApp; + return firebaseApp.getOrInitService('installations', (app) => new Installations(app)); +} diff --git a/src/installations/installations-namespace.ts b/src/installations/installations-namespace.ts new file mode 100644 index 0000000000..dc51e08476 --- /dev/null +++ b/src/installations/installations-namespace.ts @@ -0,0 +1,58 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App } from '../app/index'; +import { Installations as TInstallations } from './installations'; + +/** + * Gets the {@link installations.Installations `Installations`} service for the + * default app or a given app. + * + * `admin.installations()` can be called with no arguments to access the default + * app's {@link installations.Installations `Installations`} service or as + * `admin.installations(app)` to access the + * {@link installations.Installations `Installations`} service associated with a + * specific app. + * + * @example + * ```javascript + * // Get the Installations service for the default app + * var defaultInstallations = admin.installations(); + * ``` + * + * @example + * ```javascript + * // Get the Installations service for a given app + * var otherInstallations = admin.installations(otherApp); + *``` + * + * @param app Optional app whose `Installations` service to + * return. If not provided, the default `Installations` service is + * returned. + * + * @return The default `Installations` service if + * no app is provided or the `Installations` service associated with the + * provided app. + */ +export declare function installations(app?: App): installations.Installations; + +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace installations { + /** + * Type alias to {@link firebase-admin.installations#Installations}. + */ + export type Installations = TInstallations; +} diff --git a/src/instance-id/instance-id-request-internal.ts b/src/installations/installations-request-handler.ts similarity index 76% rename from src/instance-id/instance-id-request-internal.ts rename to src/installations/installations-request-handler.ts index 52441b84ee..44d17e41d4 100644 --- a/src/instance-id/instance-id-request-internal.ts +++ b/src/installations/installations-request-handler.ts @@ -1,6 +1,6 @@ /*! * @license - * Copyright 2017 Google Inc. + * Copyright 2021 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import { App } from '../app/index'; import { FirebaseApp } from '../app/firebase-app'; -import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error'; +import { FirebaseInstallationsError, InstallationsClientErrorCode } from '../utils/error'; import { ApiSettings, AuthorizedHttpClient, HttpRequestConfig, HttpError, } from '../utils/api-request'; @@ -34,10 +34,10 @@ const FIREBASE_IID_TIMEOUT = 10000; /** HTTP error codes raised by the backend server. */ const ERROR_CODES: {[key: number]: string} = { - 400: 'Malformed instance ID argument.', + 400: 'Malformed installation ID argument.', 401: 'Request not authorized.', - 403: 'Project does not match instance ID or the client does not have sufficient privileges.', - 404: 'Failed to find the instance ID.', + 403: 'Project does not match installation ID or the client does not have sufficient privileges.', + 404: 'Failed to find the installation ID.', 409: 'Already deleted.', 429: 'Request throttled out by the backend server.', 500: 'Internal server error.', @@ -45,9 +45,9 @@ const ERROR_CODES: {[key: number]: string} = { }; /** - * Class that provides mechanism to send requests to the Firebase Instance ID backend endpoints. + * Class that provides mechanism to send requests to the FIS backend endpoints. */ -export class FirebaseInstanceIdRequestHandler { +export class FirebaseInstallationsRequestHandler { private readonly host: string = FIREBASE_IID_HOST; private readonly timeout: number = FIREBASE_IID_TIMEOUT; @@ -63,14 +63,14 @@ export class FirebaseInstanceIdRequestHandler { this.httpClient = new AuthorizedHttpClient(app as FirebaseApp); } - public deleteInstanceId(instanceId: string): Promise { - if (!validator.isNonEmptyString(instanceId)) { - return Promise.reject(new FirebaseInstanceIdError( - InstanceIdClientErrorCode.INVALID_INSTANCE_ID, - 'Instance ID must be a non-empty string.', + public deleteInstallation(fid: string): Promise { + if (!validator.isNonEmptyString(fid)) { + return Promise.reject(new FirebaseInstallationsError( + InstallationsClientErrorCode.INVALID_INSTALLATION_ID, + 'Installation ID must be a non-empty string.', )); } - return this.invokeRequestHandler(new ApiSettings(instanceId, 'DELETE')); + return this.invokeRequestHandler(new ApiSettings(fid, 'DELETE')); } /** @@ -99,8 +99,8 @@ export class FirebaseInstanceIdRequestHandler { response.data.error : response.text; const template: string = ERROR_CODES[response.status]; const message: string = template ? - `Instance ID "${apiSettings.getEndpoint()}": ${template}` : errorMessage; - throw new FirebaseInstanceIdError(InstanceIdClientErrorCode.API_ERROR, message); + `Installation ID "${apiSettings.getEndpoint()}": ${template}` : errorMessage; + throw new FirebaseInstallationsError(InstallationsClientErrorCode.API_ERROR, message); } // In case of timeouts and other network errors, the HttpClient returns a // FirebaseError wrapped in the response. Simply throw it here. @@ -117,9 +117,9 @@ export class FirebaseInstanceIdRequestHandler { .then((projectId) => { if (!validator.isNonEmptyString(projectId)) { // Assert for an explicit projct ID (either via AppOptions or the cert itself). - throw new FirebaseInstanceIdError( - InstanceIdClientErrorCode.INVALID_PROJECT_ID, - 'Failed to determine project ID for InstanceId. Initialize the ' + throw new FirebaseInstallationsError( + InstallationsClientErrorCode.INVALID_PROJECT_ID, + 'Failed to determine project ID for Installations. Initialize the ' + 'SDK with service account credentials or set project ID as an app option. ' + 'Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.', ); diff --git a/src/installations/installations.ts b/src/installations/installations.ts new file mode 100644 index 0000000000..5604cef34b --- /dev/null +++ b/src/installations/installations.ts @@ -0,0 +1,65 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { App } from '../app/index'; +import { FirebaseInstallationsError, InstallationsClientErrorCode } from '../utils/error'; +import { FirebaseInstallationsRequestHandler } from './installations-request-handler'; +import * as validator from '../utils/validator'; + +/** + * The `Installations` service for the current app. + */ +export class Installations { + + private app_: App; + private requestHandler: FirebaseInstallationsRequestHandler; + + /** + * @param app The app for this Installations service. + * @constructor + */ + constructor(app: App) { + if (!validator.isNonNullObject(app) || !('options' in app)) { + throw new FirebaseInstallationsError( + InstallationsClientErrorCode.INVALID_ARGUMENT, + 'First argument passed to admin.installations() must be a valid Firebase app instance.', + ); + } + + this.app_ = app; + this.requestHandler = new FirebaseInstallationsRequestHandler(app); + } + + /** + * Deletes the specified installation ID and the associated data from Firebase. + * + * @param fid The Firebase installation ID to be deleted. + * + * @return A promise fulfilled when the installation ID is deleted. + */ + public deleteInstallation(fid: string): Promise { + return this.requestHandler.deleteInstallation(fid); + } + + /** + * Returns the app associated with this Installations instance. + * + * @returns The app associated with this Installations instance. + */ + get app(): App { + return this.app_; + } +} diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index 40043b9467..7a1d5226f8 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -29,6 +29,10 @@ export { InstanceId }; /** * Gets the {@link InstanceId} service for the default app or a given app. * + * This API is deprecated. Developers are advised to use the + * {@link firebase-admin.installations#getInstallations} + * API to delete their instance IDs and Firebase installation IDs. + * * `getInstanceId()` can be called with no arguments to access the default * app's `InstanceId` service or as `getInstanceId(app)` to access the * `InstanceId` service associated with a specific app. @@ -52,6 +56,8 @@ export { InstanceId }; * @returns The default `InstanceId` service if * no app is provided or the `InstanceId` service associated with the * provided app. + * + * @deprecated */ export function getInstanceId(app?: App): InstanceId { if (typeof app === 'undefined') { diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index eb4492b2a1..cd01932007 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -14,19 +14,23 @@ * limitations under the License. */ +import { FirebaseApp } from '../app/firebase-app'; import { App } from '../app/index'; -import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../utils/error'; -import { FirebaseInstanceIdRequestHandler } from './instance-id-request-internal'; +import { + FirebaseInstallationsError, FirebaseInstanceIdError, + InstallationsClientErrorCode, InstanceIdClientErrorCode, +} from '../utils/error'; import * as validator from '../utils/validator'; /** * The `InstanceId` service enables deleting the Firebase instance IDs * associated with Firebase client app instances. + * + * @deprecated */ export class InstanceId { private app_: App; - private requestHandler: FirebaseInstanceIdRequestHandler; /** * @param app The app for this InstanceId service. @@ -42,7 +46,6 @@ export class InstanceId { } this.app_ = app; - this.requestHandler = new FirebaseInstanceIdRequestHandler(app); } /** @@ -60,9 +63,18 @@ export class InstanceId { * @returns A promise fulfilled when the instance ID is deleted. */ public deleteInstanceId(instanceId: string): Promise { - return this.requestHandler.deleteInstanceId(instanceId) - .then(() => { - // Return nothing on success + return (this.app as FirebaseApp).installations().deleteInstallation(instanceId) + .catch((err) => { + if (err instanceof FirebaseInstallationsError) { + let code = err.code.replace('installations/', ''); + if (code === InstallationsClientErrorCode.INVALID_INSTALLATION_ID.code) { + code = InstanceIdClientErrorCode.INVALID_INSTANCE_ID.code; + } + + throw new FirebaseInstanceIdError({ code, message: err.message }); + } + + throw err; }); } diff --git a/src/messaging/messaging-internal.ts b/src/messaging/messaging-internal.ts index 3e832a2a8c..178ca0a0d7 100644 --- a/src/messaging/messaging-internal.ts +++ b/src/messaging/messaging-internal.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { renameProperties } from '../utils/index'; +import { renameProperties, transformMillisecondsToSecondsString } from '../utils/index'; import { MessagingClientErrorCode, FirebaseMessagingError, } from '../utils/error'; import * as validator from '../utils/validator'; @@ -579,28 +579,3 @@ function validateAndroidFcmOptions(fcmOptions: AndroidFcmOptions | undefined): v MessagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value'); } } - -/** - * Transforms milliseconds to the format expected by FCM service. - * Returns the duration in seconds with up to nine fractional - * digits, terminated by 's'. Example: "3.5s". - * - * @param milliseconds The duration in milliseconds. - * @returns The resulting formatted string in seconds with up to nine fractional - * digits, terminated by 's'. - */ -function transformMillisecondsToSecondsString(milliseconds: number): string { - let duration: string; - const seconds = Math.floor(milliseconds / 1000); - const nanos = (milliseconds - seconds * 1000) * 1000000; - if (nanos > 0) { - let nanoString = nanos.toString(); - while (nanoString.length < 9) { - nanoString = '0' + nanoString; - } - duration = `${seconds}.${nanoString}s`; - } else { - duration = `${seconds}s`; - } - return duration; -} diff --git a/src/utils/error.ts b/src/utils/error.ts index 551203a128..00ad8ab594 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -224,6 +224,23 @@ export class FirebaseInstanceIdError extends FirebaseError { constructor(info: ErrorInfo, message?: string) { // Override default message if custom message provided. super({ code: 'instance-id/' + info.code, message: message || info.message }); + (this as any).__proto__ = FirebaseInstanceIdError.prototype; + } +} + +/** + * Firebase Installations service error code structure. This extends `FirebaseError`. + * + * @param info The error code info. + * @param message The error message. This will override the default + * message if provided. + * @constructor + */ +export class FirebaseInstallationsError extends FirebaseError { + constructor(info: ErrorInfo, message?: string) { + // Override default message if custom message provided. + super({ code: 'installations/' + info.code, message: message || info.message }); + (this as any).__proto__ = FirebaseInstallationsError.prototype; } } @@ -808,7 +825,7 @@ export class MessagingClientErrorCode { }; } -export class InstanceIdClientErrorCode { +export class InstallationsClientErrorCode { public static INVALID_ARGUMENT = { code: 'invalid-argument', message: 'Invalid argument provided.', @@ -817,13 +834,20 @@ export class InstanceIdClientErrorCode { code: 'invalid-project-id', message: 'Invalid project ID provided.', }; - public static INVALID_INSTANCE_ID = { - code: 'invalid-instance-id', - message: 'Invalid instance ID provided.', + public static INVALID_INSTALLATION_ID = { + code: 'invalid-installation-id', + message: 'Invalid installation ID provided.', }; public static API_ERROR = { code: 'api-error', - message: 'Instance ID API call failed.', + message: 'Installation ID API call failed.', + }; +} + +export class InstanceIdClientErrorCode extends InstallationsClientErrorCode { + public static INVALID_INSTANCE_ID = { + code: 'invalid-instance-id', + message: 'Invalid instance ID provided.', }; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 372dc6a5a6..a3b1b45bfc 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -191,3 +191,29 @@ export function generateUpdateMask( } return updateMask; } + +/** + * Transforms milliseconds to a protobuf Duration type string. + * Returns the duration in seconds with up to nine fractional + * digits, terminated by 's'. Example: "3 seconds 0 nano seconds as 3s, + * 3 seconds 1 nano seconds as 3.000000001s". + * + * @param milliseconds The duration in milliseconds. + * @returns The resulting formatted string in seconds with up to nine fractional + * digits, terminated by 's'. + */ +export function transformMillisecondsToSecondsString(milliseconds: number): string { + let duration: string; + const seconds = Math.floor(milliseconds / 1000); + const nanos = Math.floor((milliseconds - seconds * 1000) * 1000000); + if (nanos > 0) { + let nanoString = nanos.toString(); + while (nanoString.length < 9) { + nanoString = '0' + nanoString; + } + duration = `${seconds}.${nanoString}s`; + } else { + duration = `${seconds}s`; + } + return duration; +} diff --git a/test/integration/app-check.spec.ts b/test/integration/app-check.spec.ts index 32386f32bc..82ad153498 100644 --- a/test/integration/app-check.spec.ts +++ b/test/integration/app-check.spec.ts @@ -53,6 +53,20 @@ describe('admin.appCheck', () => { expect(token).to.have.keys(['token', 'ttlMillis']); expect(token.token).to.be.a('string').and.to.not.be.empty; expect(token.ttlMillis).to.be.a('number'); + expect(token.ttlMillis).to.equals(3600000); + }); + }); + + it('should succeed with a valid token and a custom ttl', function() { + if (!appId) { + this.skip(); + } + return admin.appCheck().createToken(appId as string, { ttlMillis: 1800000 }) + .then((token) => { + expect(token).to.have.keys(['token', 'ttlMillis']); + expect(token.token).to.be.a('string').and.to.not.be.empty; + expect(token.ttlMillis).to.be.a('number'); + expect(token.ttlMillis).to.equals(1800000); }); }); diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 84ab010b93..d52b77783c 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -275,6 +275,56 @@ describe('admin.auth', () => { }); }); + it('getUserByProviderUid() returns a user record with the matching provider id', async () => { + // TODO(rsgowman): Once we can link a provider id with a user, just do that + // here instead of creating a new user. + const randomUid = 'import_' + generateRandomString(20).toLowerCase(); + const importUser: UserImportRecord = { + uid: randomUid, + email: 'user@example.com', + phoneNumber: '+15555550000', + emailVerified: true, + disabled: false, + metadata: { + lastSignInTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + creationTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + }, + providerData: [{ + displayName: 'User Name', + email: 'user@example.com', + phoneNumber: '+15555550000', + photoURL: 'http://example.com/user', + providerId: 'google.com', + uid: 'google_uid', + }], + }; + + await getAuth().importUsers([importUser]); + + try { + await getAuth().getUserByProviderUid('google.com', 'google_uid') + .then((userRecord) => { + expect(userRecord.uid).to.equal(importUser.uid); + }); + } finally { + await safeDelete(importUser.uid); + } + }); + + it('getUserByProviderUid() redirects to getUserByEmail if given an email', () => { + return getAuth().getUserByProviderUid('email', mockUserData.email) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + + it('getUserByProviderUid() redirects to getUserByPhoneNumber if given a phone number', () => { + return getAuth().getUserByProviderUid('phone', mockUserData.phoneNumber) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + describe('getUsers()', () => { /** * Filters a list of object to another list of objects that only contains @@ -791,6 +841,11 @@ describe('admin.auth', () => { .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); + it('getUserByProviderUid() fails when called with a non-existing provider id', () => { + return getAuth().getUserByProviderUid('google.com', nonexistentUid) + .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); + }); + it('updateUser() fails when called with a non-existing UID', () => { return getAuth().updateUser(nonexistentUid, { emailVerified: true, diff --git a/test/integration/installations.spec.ts b/test/integration/installations.spec.ts new file mode 100644 index 0000000000..98eb3ad72a --- /dev/null +++ b/test/integration/installations.spec.ts @@ -0,0 +1,31 @@ +/*! + * Copyright 2021 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getInstallations } from '../../lib/installations/index'; +import * as chai from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +chai.should(); +chai.use(chaiAsPromised); + +describe('admin.installations', () => { + it('deleteInstallation() fails when called with fictive-ID0 instance ID', () => { + // instance ids have to conform to /[cdef][A-Za-z0-9_-]{9}[AEIMQUYcgkosw048]/ + return getInstallations().deleteInstallation('fictive-ID0') + .should.eventually.be + .rejectedWith('Installation ID "fictive-ID0": Failed to find the installation ID.'); + }); +}); diff --git a/test/integration/instance-id.spec.ts b/test/integration/instance-id.spec.ts index 841d110772..2155205990 100644 --- a/test/integration/instance-id.spec.ts +++ b/test/integration/instance-id.spec.ts @@ -26,6 +26,6 @@ describe('admin.instanceId', () => { // instance ids have to conform to /[cdef][A-Za-z0-9_-]{9}[AEIMQUYcgkosw048]/ return getInstanceId().deleteInstanceId('fictive-ID0') .should.eventually.be - .rejectedWith('Instance ID "fictive-ID0": Failed to find the instance ID.'); + .rejectedWith('Installation ID "fictive-ID0": Failed to find the installation ID.'); }); }); diff --git a/test/unit/app-check/app-check.spec.ts b/test/unit/app-check/app-check.spec.ts index 0818a27945..5b8b48cc6c 100644 --- a/test/unit/app-check/app-check.spec.ts +++ b/test/unit/app-check/app-check.spec.ts @@ -23,7 +23,7 @@ import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { AppCheck } from '../../../src/app-check/app-check'; +import { AppCheck } from '../../../src/app-check/index'; import { AppCheckApiClient, FirebaseAppCheckError } from '../../../src/app-check/app-check-api-client-internal'; import { AppCheckTokenGenerator } from '../../../src/app-check/token-generator'; import { HttpClient } from '../../../src/utils/api-request'; @@ -147,6 +147,15 @@ describe('AppCheck', () => { .should.eventually.be.rejected.and.deep.equal(INTERNAL_ERROR); }); + it('should propagate API errors with custom options', () => { + const stub = sinon + .stub(AppCheckApiClient.prototype, 'exchangeToken') + .rejects(INTERNAL_ERROR); + stubs.push(stub); + return appCheck.createToken(APP_ID, { ttlMillis: 1800000 }) + .should.eventually.be.rejected.and.deep.equal(INTERNAL_ERROR); + }); + it('should resolve with AppCheckToken on success', () => { const response = { token: 'token', ttlMillis: 3000 }; const stub = sinon @@ -172,7 +181,7 @@ describe('AppCheck', () => { }); it('should resolve with VerifyAppCheckTokenResponse on success', () => { - const response = { + const response = { sub: 'app-id', iss: 'https://firebaseappcheck.googleapis.com/123456', // eslint-disable-next-line @typescript-eslint/camelcase diff --git a/test/unit/app-check/token-generator.spec.ts b/test/unit/app-check/token-generator.spec.ts index ba47850f30..4a0e8deee9 100644 --- a/test/unit/app-check/token-generator.spec.ts +++ b/test/unit/app-check/token-generator.spec.ts @@ -43,7 +43,7 @@ chai.use(chaiAsPromised); const expect = chai.expect; const ALGORITHM = 'RS256'; -const ONE_HOUR_IN_SECONDS = 60 * 60; +const FIVE_MIN_IN_SECONDS = 5 * 60; const FIREBASE_APP_CHECK_AUDIENCE = 'https://firebaseappcheck.googleapis.com/google.firebase.appcheck.v1beta.TokenExchangeService'; /** @@ -137,7 +137,7 @@ describe('AppCheckTokenGenerator', () => { // eslint-disable-next-line @typescript-eslint/camelcase app_id: APP_ID, iat: 1, - exp: ONE_HOUR_IN_SECONDS + 1, + exp: FIVE_MIN_IN_SECONDS + 1, aud: FIREBASE_APP_CHECK_AUDIENCE, iss: mocks.certificateObject.client_email, sub: mocks.certificateObject.client_email, @@ -177,7 +177,7 @@ describe('AppCheckTokenGenerator', () => { }); }); - it('should be fulfilled with a JWT which expires after one hour', () => { + it('should be fulfilled with a JWT which expires after five minutes', () => { clock = sinon.useFakeTimers(1000); let token: string; @@ -185,7 +185,7 @@ describe('AppCheckTokenGenerator', () => { .then((result) => { token = result; - clock!.tick((ONE_HOUR_IN_SECONDS * 1000) - 1); + clock!.tick((FIVE_MIN_IN_SECONDS * 1000) - 1); // Token should still be valid return verifyToken(token, mocks.keyPairs[0].public); @@ -210,7 +210,7 @@ describe('AppCheckTokenGenerator', () => { expect(appCheckError).to.have.property('code', 'app-check/invalid-argument'); expect(appCheckError).to.have.property('message', 'test error.'); }); - + it('should convert CryptoSignerError HttpError to FirebaseAppCheckError', () => { const cryptoError = new CryptoSignerError({ code: CryptoSignerErrorCode.SERVER_ERROR, @@ -243,7 +243,7 @@ describe('AppCheckTokenGenerator', () => { 'Error returned from server while signing a custom token: '+ '{"status":500,"headers":{},"data":{"error":{}},"text":"{\\"error\\":{}}"}'); }); - + it('should convert CryptoSignerError HttpError with no errorcode to FirebaseAppCheckError', () => { const cryptoError = new CryptoSignerError({ code: CryptoSignerErrorCode.SERVER_ERROR, diff --git a/test/unit/app/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts index 8ef2a69d4a..043d8eb679 100644 --- a/test/unit/app/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -34,7 +34,7 @@ import { } from '../../../src/app/firebase-namespace'; import { auth, messaging, machineLearning, storage, firestore, database, - instanceId, projectManagement, securityRules , remoteConfig, appCheck, + instanceId, installations, projectManagement, securityRules , remoteConfig, appCheck, } from '../../../src/firebase-namespace-api'; import { FirebaseAppError, AppErrorCodes } from '../../../src/utils/error'; @@ -44,6 +44,7 @@ import Messaging = messaging.Messaging; import MachineLearning = machineLearning.MachineLearning; import Storage = storage.Storage; import Firestore = firestore.Firestore; +import Installations = installations.Installations; import InstanceId = instanceId.InstanceId; import ProjectManagement = projectManagement.ProjectManagement; import SecurityRules = securityRules.SecurityRules; @@ -561,6 +562,32 @@ describe('FirebaseApp', () => { }); }); + describe('installations()', () => { + it('should throw if the app has already been deleted', () => { + const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); + + return app.delete().then(() => { + expect(() => { + return app.installations(); + }).to.throw(`Firebase app named "${mocks.appName}" has already been deleted.`); + }); + }); + + it('should return the InstanceId client', () => { + const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); + + const fis: Installations = app.installations(); + expect(fis).not.be.null; + }); + + it('should return a cached version of InstanceId on subsequent calls', () => { + const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); + const service1: Installations = app.installations(); + const service2: Installations = app.installations(); + expect(service1).to.equal(service2); + }); + }); + describe('instanceId()', () => { it('should throw if the app has already been deleted', () => { const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); diff --git a/test/unit/app/firebase-namespace.spec.ts b/test/unit/app/firebase-namespace.spec.ts index 220d76579b..aee55a965f 100644 --- a/test/unit/app/firebase-namespace.spec.ts +++ b/test/unit/app/firebase-namespace.spec.ts @@ -49,11 +49,12 @@ import { getSdkVersion } from '../../../src/utils/index'; import { app, auth, messaging, machineLearning, storage, firestore, database, - instanceId, projectManagement, securityRules , remoteConfig, appCheck, + instanceId, installations, projectManagement, securityRules , remoteConfig, appCheck, } from '../../../src/firebase-namespace-api'; import { AppCheck as AppCheckImpl } from '../../../src/app-check/app-check'; import { Auth as AuthImpl } from '../../../src/auth/auth'; import { InstanceId as InstanceIdImpl } from '../../../src/instance-id/instance-id'; +import { Installations as InstallationsImpl } from '../../../src/installations/installations'; import { MachineLearning as MachineLearningImpl } from '../../../src/machine-learning/machine-learning'; import { Messaging as MessagingImpl } from '../../../src/messaging/messaging'; import { ProjectManagement as ProjectManagementImpl } from '../../../src/project-management/project-management'; @@ -68,6 +69,7 @@ import AppCheck = appCheck.AppCheck; import Auth = auth.Auth; import Database = database.Database; import Firestore = firestore.Firestore; +import Installations = installations.Installations; import InstanceId = instanceId.InstanceId; import MachineLearning = machineLearning.MachineLearning; import Messaging = messaging.Messaging; @@ -598,6 +600,46 @@ describe('FirebaseNamespace', () => { }); }); + describe('#installations()', () => { + it('should throw when called before initializing an app', () => { + expect(() => { + firebaseNamespace.installations(); + }).to.throw(DEFAULT_APP_NOT_FOUND); + }); + + it('should throw when default app is not initialized', () => { + firebaseNamespace.initializeApp(mocks.appOptions, 'testApp'); + expect(() => { + firebaseNamespace.installations(); + }).to.throw(DEFAULT_APP_NOT_FOUND); + }); + + it('should return a valid namespace when the default app is initialized', () => { + const app: App = firebaseNamespace.initializeApp(mocks.appOptions); + const fis: Installations = firebaseNamespace.installations(); + expect(fis).to.not.be.null; + expect(fis.app).to.be.deep.equal(app); + }); + + it('should return a valid namespace when the named app is initialized', () => { + const app: App = firebaseNamespace.initializeApp(mocks.appOptions, 'testApp'); + const fis: Installations = firebaseNamespace.installations(app); + expect(fis).to.not.be.null; + expect(fis.app).to.be.deep.equal(app); + }); + + it('should return a reference to Installations type', () => { + expect(firebaseNamespace.installations.Installations).to.be.deep.equal(InstallationsImpl); + }); + + it('should return a cached version of Installations on subsequent calls', () => { + firebaseNamespace.initializeApp(mocks.appOptions); + const service1: Installations = firebaseNamespace.installations(); + const service2: Installations = firebaseNamespace.installations(); + expect(service1).to.equal(service2); + }); + }); + describe('#instanceId()', () => { it('should throw when called before initializing an app', () => { expect(() => { @@ -792,7 +834,7 @@ describe('FirebaseNamespace', () => { after(clearGlobalAppDefaultCred); }); - + describe('#appCheck()', () => { it('should throw when called before initializing an app', () => { expect(() => { diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index ea3bd9ff90..d422b90b61 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -448,7 +448,7 @@ describe('FirebaseTokenVerifier', () => { createTokenVerifier(mockAppWithAgent); expect(verifierSpy.args[0][1]).to.equal(agentForApp); - + verifierSpy.restore(); }); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 7434f07366..9c0b51f831 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -65,10 +65,13 @@ import './storage/index.spec'; import './firestore/firestore.spec'; import './firestore/index.spec'; +// Installations +import './installations/installations.spec'; +import './installations/installations-request-handler.spec'; + // InstanceId import './instance-id/index.spec'; import './instance-id/instance-id.spec'; -import './instance-id/instance-id-request.spec'; // ProjectManagement import './project-management/index.spec'; diff --git a/test/unit/instance-id/instance-id-request.spec.ts b/test/unit/installations/installations-request-handler.spec.ts similarity index 72% rename from test/unit/instance-id/instance-id-request.spec.ts rename to test/unit/installations/installations-request-handler.spec.ts index 37c7347a0e..36e696dd2d 100644 --- a/test/unit/instance-id/instance-id-request.spec.ts +++ b/test/unit/installations/installations-request-handler.spec.ts @@ -28,7 +28,7 @@ import * as mocks from '../../resources/mocks'; import { FirebaseApp } from '../../../src/app/firebase-app'; import { HttpClient } from '../../../src/utils/api-request'; -import { FirebaseInstanceIdRequestHandler } from '../../../src/instance-id/instance-id-request-internal'; +import { FirebaseInstallationsRequestHandler } from '../../../src/installations/installations-request-handler'; chai.should(); chai.use(sinonChai); @@ -36,7 +36,7 @@ chai.use(chaiAsPromised); const expect = chai.expect; -describe('FirebaseInstanceIdRequestHandler', () => { +describe('FirebaseInstallationsRequestHandler', () => { const projectId = 'project_id'; const mockAccessToken: string = utils.generateRandomAccessToken(); let stubs: sinon.SinonStub[] = []; @@ -69,24 +69,24 @@ describe('FirebaseInstanceIdRequestHandler', () => { describe('Constructor', () => { it('should succeed with a FirebaseApp instance', () => { expect(() => { - return new FirebaseInstanceIdRequestHandler(mockApp); + return new FirebaseInstallationsRequestHandler(mockApp); }).not.to.throw(Error); }); }); - describe('deleteInstanceId', () => { + describe('deleteInstallation', () => { const httpMethod = 'DELETE'; const host = 'console.firebase.google.com'; - const path = `/v1/project/${projectId}/instanceId/test-iid`; + const path = `/v1/project/${projectId}/instanceId/test-fid`; const timeout = 10000; - it('should be fulfilled given a valid instance ID', () => { + it('should be fulfilled given a valid installation ID', () => { const stub = sinon.stub(HttpClient.prototype, 'send') .resolves(utils.responseFrom('')); stubs.push(stub); - const requestHandler = new FirebaseInstanceIdRequestHandler(mockApp); - return requestHandler.deleteInstanceId('test-iid') + const requestHandler = new FirebaseInstallationsRequestHandler(mockApp); + return requestHandler.deleteInstallation('test-fid') .then(() => { expect(stub).to.have.been.calledOnce.and.calledWith({ method: httpMethod, @@ -102,14 +102,14 @@ describe('FirebaseInstanceIdRequestHandler', () => { .rejects(utils.errorFrom({}, 404)); stubs.push(stub); - const requestHandler = new FirebaseInstanceIdRequestHandler(mockApp); - return requestHandler.deleteInstanceId('test-iid') + const requestHandler = new FirebaseInstallationsRequestHandler(mockApp); + return requestHandler.deleteInstallation('test-fid') .then(() => { throw new Error('Unexpected success'); }) .catch((error) => { - expect(error.code).to.equal('instance-id/api-error'); - expect(error.message).to.equal('Instance ID "test-iid": Failed to find the instance ID.'); + expect(error.code).to.equal('installations/api-error'); + expect(error.message).to.equal('Installation ID "test-fid": Failed to find the installation ID.'); }); }); @@ -118,14 +118,14 @@ describe('FirebaseInstanceIdRequestHandler', () => { .rejects(utils.errorFrom({}, 409)); stubs.push(stub); - const requestHandler = new FirebaseInstanceIdRequestHandler(mockApp); - return requestHandler.deleteInstanceId('test-iid') + const requestHandler = new FirebaseInstallationsRequestHandler(mockApp); + return requestHandler.deleteInstallation('test-fid') .then(() => { throw new Error('Unexpected success'); }) .catch((error) => { - expect(error.code).to.equal('instance-id/api-error'); - expect(error.message).to.equal('Instance ID "test-iid": Already deleted.'); + expect(error.code).to.equal('installations/api-error'); + expect(error.message).to.equal('Installation ID "test-fid": Already deleted.'); }); }); @@ -135,13 +135,13 @@ describe('FirebaseInstanceIdRequestHandler', () => { .rejects(utils.errorFrom(expectedResult, 511)); stubs.push(stub); - const requestHandler = new FirebaseInstanceIdRequestHandler(mockApp); - return requestHandler.deleteInstanceId('test-iid') + const requestHandler = new FirebaseInstallationsRequestHandler(mockApp); + return requestHandler.deleteInstallation('test-fid') .then(() => { throw new Error('Unexpected success'); }) .catch((error) => { - expect(error.code).to.equal('instance-id/api-error'); + expect(error.code).to.equal('installations/api-error'); expect(error.message).to.equal('test error'); }); }); diff --git a/test/unit/installations/installations.spec.ts b/test/unit/installations/installations.spec.ts new file mode 100644 index 0000000000..6a38c8413c --- /dev/null +++ b/test/unit/installations/installations.spec.ts @@ -0,0 +1,190 @@ +/*! + * @license + * Copyright 2017 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +import * as _ from 'lodash'; +import * as chai from 'chai'; +import * as sinon from 'sinon'; +import * as sinonChai from 'sinon-chai'; +import * as chaiAsPromised from 'chai-as-promised'; + +import * as utils from '../utils'; +import * as mocks from '../../resources/mocks'; + +import { Installations } from '../../../src/installations/installations'; +import { FirebaseInstallationsRequestHandler } from '../../../src/installations/installations-request-handler'; +import { FirebaseApp } from '../../../src/app/firebase-app'; +import { FirebaseInstallationsError, InstallationsClientErrorCode } from '../../../src/utils/error'; + +chai.should(); +chai.use(sinonChai); +chai.use(chaiAsPromised); + +const expect = chai.expect; + +describe('Installations', () => { + let fis: Installations; + let mockApp: FirebaseApp; + let mockCredentialApp: FirebaseApp; + let getTokenStub: sinon.SinonStub; + + let nullAccessTokenClient: Installations; + let malformedAccessTokenClient: Installations; + let rejectedPromiseAccessTokenClient: Installations; + + let googleCloudProject: string | undefined; + let gcloudProject: string | undefined; + + const noProjectIdError = 'Failed to determine project ID for Installations. Initialize the SDK ' + + 'with service account credentials or set project ID as an app option. Alternatively set the ' + + 'GOOGLE_CLOUD_PROJECT environment variable.'; + + beforeEach(() => { + mockApp = mocks.app(); + getTokenStub = utils.stubGetAccessToken(undefined, mockApp); + mockCredentialApp = mocks.mockCredentialApp(); + fis = new Installations(mockApp); + + googleCloudProject = process.env.GOOGLE_CLOUD_PROJECT; + gcloudProject = process.env.GCLOUD_PROJECT; + + nullAccessTokenClient = new Installations(mocks.appReturningNullAccessToken()); + malformedAccessTokenClient = new Installations(mocks.appReturningMalformedAccessToken()); + rejectedPromiseAccessTokenClient = new Installations(mocks.appRejectedWhileFetchingAccessToken()); + }); + + afterEach(() => { + getTokenStub.restore(); + process.env.GOOGLE_CLOUD_PROJECT = googleCloudProject; + process.env.GCLOUD_PROJECT = gcloudProject; + return mockApp.delete(); + }); + + + describe('Constructor', () => { + const invalidApps = [null, NaN, 0, 1, true, false, '', 'a', [], [1, 'a'], {}, { a: 1 }, _.noop]; + invalidApps.forEach((invalidApp) => { + it('should throw given invalid app: ' + JSON.stringify(invalidApp), () => { + expect(() => { + const iidAny: any = Installations; + return new iidAny(invalidApp); + }).to.throw('First argument passed to admin.installations() must be a valid Firebase app instance.'); + }); + }); + + it('should throw given no app', () => { + expect(() => { + const iidAny: any = Installations; + return new iidAny(); + }).to.throw('First argument passed to admin.installations() must be a valid Firebase app instance.'); + }); + + it('should reject given an invalid credential without project ID', () => { + // Project ID not set in the environment. + delete process.env.GOOGLE_CLOUD_PROJECT; + delete process.env.GCLOUD_PROJECT; + const installations = new Installations(mockCredentialApp); + return installations.deleteInstallation('iid') + .should.eventually.rejectedWith(noProjectIdError); + }); + + it('should not throw given a valid app', () => { + expect(() => { + return new Installations(mockApp); + }).not.to.throw(); + }); + }); + + describe('app', () => { + it('returns the app from the constructor', () => { + // We expect referential equality here + expect(fis.app).to.equal(mockApp); + }); + + it('is read-only', () => { + expect(() => { + (fis as any).app = mockApp; + }).to.throw('Cannot set property app of # which has only a getter'); + }); + }); + + describe('deleteInstallation()', () => { + + // Stubs used to simulate underlying api calls. + let stubs: sinon.SinonStub[] = []; + const expectedError = new FirebaseInstallationsError(InstallationsClientErrorCode.API_ERROR); + const testInstallationId = 'test-iid'; + + afterEach(() => { + _.forEach(stubs, (stub) => stub.restore()); + stubs = []; + }); + + it('should be rejected given no installation ID', () => { + return (fis as any).deleteInstallation() + .should.eventually.be.rejected.and.have.property('code', 'installations/invalid-installation-id'); + }); + + it('should be rejected given an invalid installation ID', () => { + return fis.deleteInstallation('') + .should.eventually.be.rejected.and.have.property('code', 'installations/invalid-installation-id'); + }); + + it('should be rejected given an app which returns null access tokens', () => { + return nullAccessTokenClient.deleteInstallation(testInstallationId) + .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); + }); + + it('should be rejected given an app which returns invalid access tokens', () => { + return malformedAccessTokenClient.deleteInstallation(testInstallationId) + .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); + }); + + it('should be rejected given an app which fails to generate access tokens', () => { + return rejectedPromiseAccessTokenClient.deleteInstallation(testInstallationId) + .should.eventually.be.rejected.and.have.property('code', 'app/invalid-credential'); + }); + + it('should resolve without errors on success', () => { + const stub = sinon.stub(FirebaseInstallationsRequestHandler.prototype, 'deleteInstallation') + .resolves(); + stubs.push(stub); + return fis.deleteInstallation(testInstallationId) + .then(() => { + // Confirm underlying API called with expected parameters. + expect(stub).to.have.been.calledOnce.and.calledWith(testInstallationId); + }); + }); + + it('should throw an error when the backend returns an error', () => { + // Stub deleteInstallation to throw a backend error. + const stub = sinon.stub(FirebaseInstallationsRequestHandler.prototype, 'deleteInstallation') + .rejects(expectedError); + stubs.push(stub); + return fis.deleteInstallation(testInstallationId) + .then(() => { + throw new Error('Unexpected success'); + }, (error) => { + // Confirm underlying API called with expected parameters. + expect(stub).to.have.been.calledOnce.and.calledWith(testInstallationId); + // Confirm expected error returned. + expect(error).to.equal(expectedError); + }); + }); + }); +}); diff --git a/test/unit/instance-id/index.spec.ts b/test/unit/instance-id/index.spec.ts index b5a96eb39e..2f1d690e6a 100644 --- a/test/unit/instance-id/index.spec.ts +++ b/test/unit/instance-id/index.spec.ts @@ -35,7 +35,7 @@ describe('InstanceId', () => { let mockApp: App; let mockCredentialApp: App; - const noProjectIdError = 'Failed to determine project ID for InstanceId. Initialize the SDK ' + const noProjectIdError = 'Failed to determine project ID for Installations. Initialize the SDK ' + 'with service account credentials or set project ID as an app option. Alternatively set the ' + 'GOOGLE_CLOUD_PROJECT environment variable.'; diff --git a/test/unit/instance-id/instance-id.spec.ts b/test/unit/instance-id/instance-id.spec.ts index e6669c7fa8..a610d7678d 100644 --- a/test/unit/instance-id/instance-id.spec.ts +++ b/test/unit/instance-id/instance-id.spec.ts @@ -27,9 +27,12 @@ import * as utils from '../utils'; import * as mocks from '../../resources/mocks'; import { InstanceId } from '../../../src/instance-id/index'; -import { FirebaseInstanceIdRequestHandler } from '../../../src/instance-id/instance-id-request-internal'; +import { Installations } from '../../../src/installations/index'; import { FirebaseApp } from '../../../src/app/firebase-app'; -import { FirebaseInstanceIdError, InstanceIdClientErrorCode } from '../../../src/utils/error'; +import { + FirebaseInstanceIdError, InstanceIdClientErrorCode, + FirebaseInstallationsError, InstallationsClientErrorCode, +} from '../../../src/utils/error'; chai.should(); chai.use(sinonChai); @@ -50,7 +53,7 @@ describe('InstanceId', () => { let googleCloudProject: string | undefined; let gcloudProject: string | undefined; - const noProjectIdError = 'Failed to determine project ID for InstanceId. Initialize the SDK ' + const noProjectIdError = 'Failed to determine project ID for Installations. Initialize the SDK ' + 'with service account credentials or set project ID as an app option. Alternatively set the ' + 'GOOGLE_CLOUD_PROJECT environment variable.'; @@ -127,7 +130,6 @@ describe('InstanceId', () => { // Stubs used to simulate underlying api calls. let stubs: sinon.SinonStub[] = []; - const expectedError = new FirebaseInstanceIdError(InstanceIdClientErrorCode.API_ERROR); const testInstanceId = 'test-iid'; afterEach(() => { @@ -161,7 +163,7 @@ describe('InstanceId', () => { }); it('should resolve without errors on success', () => { - const stub = sinon.stub(FirebaseInstanceIdRequestHandler.prototype, 'deleteInstanceId') + const stub = sinon.stub(Installations.prototype, 'deleteInstallation') .resolves(); stubs.push(stub); return iid.deleteInstanceId(testInstanceId) @@ -171,10 +173,11 @@ describe('InstanceId', () => { }); }); - it('should throw an error when the backend returns an error', () => { + it('should throw a FirebaseInstanceIdError error when the backend returns an error', () => { // Stub deleteInstanceId to throw a backend error. - const stub = sinon.stub(FirebaseInstanceIdRequestHandler.prototype, 'deleteInstanceId') - .returns(Promise.reject(expectedError)); + const originalError = new FirebaseInstallationsError(InstallationsClientErrorCode.API_ERROR); + const stub = sinon.stub(Installations.prototype, 'deleteInstallation') + .rejects(originalError); stubs.push(stub); return iid.deleteInstanceId(testInstanceId) .then(() => { @@ -183,7 +186,9 @@ describe('InstanceId', () => { // Confirm underlying API called with expected parameters. expect(stub).to.have.been.calledOnce.and.calledWith(testInstanceId); // Confirm expected error returned. - expect(error).to.equal(expectedError); + const expectedError = new FirebaseInstanceIdError(InstanceIdClientErrorCode.API_ERROR); + expect(error).to.be.instanceOf(FirebaseInstanceIdError) + expect(error).to.deep.include(expectedError); }); }); }); diff --git a/test/unit/utils/index.spec.ts b/test/unit/utils/index.spec.ts index f85510f33c..7d007f2f78 100644 --- a/test/unit/utils/index.spec.ts +++ b/test/unit/utils/index.spec.ts @@ -22,7 +22,7 @@ import * as sinon from 'sinon'; import * as mocks from '../../resources/mocks'; import { addReadonlyGetter, getExplicitProjectId, findProjectId, - toWebSafeBase64, formatString, generateUpdateMask, + toWebSafeBase64, formatString, generateUpdateMask, transformMillisecondsToSecondsString, } from '../../../src/utils/index'; import { isNonEmptyString } from '../../../src/utils/validator'; import { FirebaseApp } from '../../../src/app/firebase-app'; @@ -383,3 +383,16 @@ describe('generateUpdateMask()', () => { .to.deep.equal(['b', 'c', 'd', 'e', 'f', 'k', 'l', 'n']); }); }); + + +describe('transformMillisecondsToSecondsString()', () => { + [ + [3000.000001, '3s'], [3000.001, '3.000001000s'], + [3000, '3s'], [3500, '3.500000000s'] + ].forEach((duration) => { + it('should transform to protobuf duration string when provided milliseconds:' + JSON.stringify(duration[0]), + () => { + expect(transformMillisecondsToSecondsString(duration[0] as number)).to.equal(duration[1]); + }); + }); +}); diff --git a/test/unit/utils/jwt.spec.ts b/test/unit/utils/jwt.spec.ts index 3b7a02a4de..4f31c88a16 100644 --- a/test/unit/utils/jwt.spec.ts +++ b/test/unit/utils/jwt.spec.ts @@ -685,7 +685,7 @@ describe('JwksFetcher', () => { expect(https.request).not.to.have.been.called; return jwksFetcher.fetchPublicKeys() - .then((result) => { + .then((result) => { expect(https.request).to.have.been.calledOnce; expect(result).to.have.key(mocks.jwksResponse.keys[0].kid); }); From 744caca5919081609cd072019bf7e083321c6951 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Tue, 21 Sep 2021 10:09:30 -0700 Subject: [PATCH 39/41] fix: Decoupled FirebaseNamespace from new module entry points (#1432) * fix: Decoupled FirebaseNamespace from new module entry points * fix: Updated comments * Trigger checks --- etc/firebase-admin.app.api.md | 4 +- src/app/firebase-app.ts | 148 ++--------------- src/app/firebase-namespace.ts | 199 +++++++++++------------ src/app/lifecycle.ts | 160 +++++++++++++++--- src/default-namespace.ts | 2 +- src/instance-id/instance-id.ts | 4 +- test/resources/mocks.ts | 17 +- test/unit/app/firebase-app.spec.ts | 25 ++- test/unit/app/firebase-namespace.spec.ts | 60 ------- test/unit/app/index.spec.ts | 14 +- test/unit/firebase.spec.ts | 27 ++- test/unit/utils.ts | 6 +- 12 files changed, 288 insertions(+), 378 deletions(-) diff --git a/etc/firebase-admin.app.api.md b/etc/firebase-admin.app.api.md index 13b925d89e..a6ad65cd57 100644 --- a/etc/firebase-admin.app.api.md +++ b/etc/firebase-admin.app.api.md @@ -54,7 +54,7 @@ export interface FirebaseError { } // @public (undocumented) -export function getApp(name?: string): App; +export function getApp(appName?: string): App; // @public (undocumented) export function getApps(): App[]; @@ -68,7 +68,7 @@ export interface GoogleOAuthAccessToken { } // @public (undocumented) -export function initializeApp(options?: AppOptions, name?: string): App; +export function initializeApp(options?: AppOptions, appName?: string): App; // @public export function refreshToken(refreshTokenPathOrObject: string | object, httpAgent?: Agent): Credential; diff --git a/src/app/firebase-app.ts b/src/app/firebase-app.ts index 920dce3323..42ce2fd6dc 100644 --- a/src/app/firebase-app.ts +++ b/src/app/firebase-app.ts @@ -15,34 +15,16 @@ * limitations under the License. */ -import { AppOptions, app } from '../firebase-namespace-api'; +import { AppOptions, App } from './core'; +import { AppStore } from './lifecycle'; import { Credential } from './credential'; import { getApplicationDefault } from './credential-internal'; import * as validator from '../utils/validator'; import { deepCopy } from '../utils/deep-copy'; -import { FirebaseNamespaceInternals } from './firebase-namespace'; import { AppErrorCodes, FirebaseAppError } from '../utils/error'; -import { Auth } from '../auth/index'; -import { AppCheck } from '../app-check/index'; -import { MachineLearning } from '../machine-learning/index'; -import { Messaging } from '../messaging/index'; -import { Storage } from '../storage/index'; -import { Database } from '../database/index'; -import { Firestore } from '../firestore/index'; -import { InstanceId } from '../instance-id/index'; -import { Installations } from '../installations/index'; -import { ProjectManagement } from '../project-management/index'; -import { SecurityRules } from '../security-rules/index'; -import { RemoteConfig } from '../remote-config/index'; - const TOKEN_EXPIRY_THRESHOLD_MILLIS = 5 * 60 * 1000; -/** - * Type representing a callback which is called every time an app lifecycle event occurs. - */ -export type AppHook = (event: string, app: app.App) => void; - /** * Type representing a Firebase OAuth access token (derived from a Google OAuth2 access token) which * can be used to authenticate to Firebase services such as the Realtime Database and Auth. @@ -159,7 +141,8 @@ export class FirebaseAppInternals { * * @internal */ -export class FirebaseApp implements app.App { +export class FirebaseApp implements App { + public INTERNAL: FirebaseAppInternals; private name_: string; @@ -167,7 +150,7 @@ export class FirebaseApp implements app.App { private services_: {[name: string]: unknown} = {}; private isDeleted_ = false; - constructor(options: AppOptions, name: string, private firebaseInternals_: FirebaseNamespaceInternals) { + constructor(options: AppOptions, name: string, private readonly appStore?: AppStore) { this.name_ = name; this.options_ = deepCopy(options); @@ -197,121 +180,6 @@ export class FirebaseApp implements app.App { this.INTERNAL = new FirebaseAppInternals(credential); } - /** - * Returns the AppCheck service instance associated with this app. - * - * @returns The AppCheck service instance of this app. - */ - public appCheck(): AppCheck { - const fn = require('../app-check/index').getAppCheck; - return fn(this); - } - - /** - * Returns the Auth service instance associated with this app. - * - * @returns The Auth service instance of this app. - */ - public auth(): Auth { - const fn = require('../auth/index').getAuth; - return fn(this); - } - - /** - * Returns the Database service for the specified URL, and the current app. - * - * @returns The Database service instance of this app. - */ - public database(url?: string): Database { - const fn = require('../database/index').getDatabaseWithUrl; - return fn(url, this); - } - - /** - * Returns the Messaging service instance associated with this app. - * - * @returns The Messaging service instance of this app. - */ - public messaging(): Messaging { - const fn = require('../messaging/index').getMessaging; - return fn(this); - } - - /** - * Returns the Storage service instance associated with this app. - * - * @returns The Storage service instance of this app. - */ - public storage(): Storage { - const fn = require('../storage/index').getStorage; - return fn(this); - } - - public firestore(): Firestore { - const fn = require('../firestore/index').getFirestore; - return fn(this); - } - - /** - * Returns the InstanceId service instance associated with this app. - * - * @returns The InstanceId service instance of this app. - */ - public instanceId(): InstanceId { - const fn = require('../instance-id/index').getInstanceId; - return fn(this); - } - - /** - * Returns the InstanceId service instance associated with this app. - * - * @returns The InstanceId service instance of this app. - */ - public installations(): Installations { - const fn = require('../installations/index').getInstallations; - return fn(this); - } - - /** - * Returns the MachineLearning service instance associated with this app. - * - * @returns The Machine Learning service instance of this app - */ - public machineLearning(): MachineLearning { - const fn = require('../machine-learning/index').getMachineLearning; - return fn(this); - } - - /** - * Returns the ProjectManagement service instance associated with this app. - * - * @returns The ProjectManagement service instance of this app. - */ - public projectManagement(): ProjectManagement { - const fn = require('../project-management/index').getProjectManagement; - return fn(this); - } - - /** - * Returns the SecurityRules service instance associated with this app. - * - * @returns The SecurityRules service instance of this app. - */ - public securityRules(): SecurityRules { - const fn = require('../security-rules/index').getSecurityRules; - return fn(this); - } - - /** - * Returns the RemoteConfig service instance associated with this app. - * - * @returns The RemoteConfig service instance of this app. - */ - public remoteConfig(): RemoteConfig { - const fn = require('../remote-config/index').getRemoteConfig; - return fn(this); - } - /** * Returns the name of the FirebaseApp instance. * @@ -346,7 +214,11 @@ export class FirebaseApp implements app.App { */ public delete(): Promise { this.checkDestroyed_(); - this.firebaseInternals_.removeApp(this.name_); + + // Also remove the instance from the AppStore. This is needed to support the existing + // app.delete() use case. In the future we can remove this API, and deleteApp() will + // become the only way to tear down an App. + this.appStore?.removeApp(this.name); return Promise.all(Object.keys(this.services_).map((serviceName) => { const service = this.services_[serviceName]; diff --git a/src/app/firebase-namespace.ts b/src/app/firebase-namespace.ts index f39eed6b9d..44716e0cae 100644 --- a/src/app/firebase-namespace.ts +++ b/src/app/firebase-namespace.ts @@ -15,18 +15,13 @@ * limitations under the License. */ -import fs = require('fs'); - -import { AppErrorCodes, FirebaseAppError } from '../utils/error'; +import { App as AppCore } from './core'; +import { AppStore, defaultAppStore } from './lifecycle'; import { app, appCheck, auth, messaging, machineLearning, storage, firestore, database, instanceId, installations, projectManagement, securityRules , remoteConfig, AppOptions, } from '../firebase-namespace-api'; -import { FirebaseApp } from './firebase-app'; import { cert, refreshToken, applicationDefault } from './credential-factory'; -import { getApplicationDefault } from './credential-internal'; - -import * as validator from '../utils/validator'; import { getSdkVersion } from '../utils/index'; import App = app.App; @@ -43,15 +38,6 @@ import RemoteConfig = remoteConfig.RemoteConfig; import SecurityRules = securityRules.SecurityRules; import Storage = storage.Storage; -const DEFAULT_APP_NAME = '[DEFAULT]'; - -/** - * Constant holding the environment variable name with the default config. - * If the environment variable contains a string that starts with '{' it will be parsed as JSON, - * otherwise it will be assumed to be pointing to a file. - */ -export const FIREBASE_CONFIG_VAR = 'FIREBASE_CONFIG'; - export interface FirebaseServiceNamespace { (app?: App): T; [key: string]: any; @@ -62,8 +48,7 @@ export interface FirebaseServiceNamespace { */ export class FirebaseNamespaceInternals { - private apps_: {[appName: string]: App} = {}; - constructor(public firebase_: {[key: string]: any}) {} + constructor(private readonly appStore: AppStore) {} /** * Initializes the App instance. @@ -76,39 +61,9 @@ export class FirebaseNamespaceInternals { * * @returns A new App instance. */ - public initializeApp(options?: AppOptions, appName = DEFAULT_APP_NAME): App { - if (typeof options === 'undefined') { - options = this.loadOptionsFromEnvVar(); - options.credential = getApplicationDefault(); - } - if (typeof appName !== 'string' || appName === '') { - throw new FirebaseAppError( - AppErrorCodes.INVALID_APP_NAME, - `Invalid Firebase app name "${appName}" provided. App name must be a non-empty string.`, - ); - } else if (appName in this.apps_) { - if (appName === DEFAULT_APP_NAME) { - throw new FirebaseAppError( - AppErrorCodes.DUPLICATE_APP, - 'The default Firebase app already exists. This means you called initializeApp() ' + - 'more than once without providing an app name as the second argument. In most cases ' + - 'you only need to call initializeApp() once. But if you do want to initialize ' + - 'multiple apps, pass a second argument to initializeApp() to give each app a unique ' + - 'name.', - ); - } else { - throw new FirebaseAppError( - AppErrorCodes.DUPLICATE_APP, - `Firebase app named "${appName}" already exists. This means you called initializeApp() ` + - 'more than once with the same app name as the second argument. Make sure you provide a ' + - 'unique name every time you call initializeApp().', - ); - } - } - - const app = new FirebaseApp(options, appName, this); - this.apps_[appName] = app; - return app; + public initializeApp(options?: AppOptions, appName?: string): App { + const app = this.appStore.initializeApp(options, appName); + return extendApp(app); } /** @@ -118,67 +73,16 @@ export class FirebaseNamespaceInternals { * @param appName Optional name of the FirebaseApp instance to return. * @returns The App instance which has the provided name. */ - public app(appName = DEFAULT_APP_NAME): App { - if (typeof appName !== 'string' || appName === '') { - throw new FirebaseAppError( - AppErrorCodes.INVALID_APP_NAME, - `Invalid Firebase app name "${appName}" provided. App name must be a non-empty string.`, - ); - } else if (!(appName in this.apps_)) { - let errorMessage: string = (appName === DEFAULT_APP_NAME) - ? 'The default Firebase app does not exist. ' : `Firebase app named "${appName}" does not exist. `; - errorMessage += 'Make sure you call initializeApp() before using any of the Firebase services.'; - - throw new FirebaseAppError(AppErrorCodes.NO_APP, errorMessage); - } - - return this.apps_[appName]; + public app(appName?: string): App { + const app = this.appStore.getApp(appName); + return extendApp(app); } /* * Returns an array of all the non-deleted App instances. */ public get apps(): App[] { - // Return a copy so the caller cannot mutate the array - return Object.keys(this.apps_).map((appName) => this.apps_[appName]); - } - - /* - * Removes the specified App instance. - */ - public removeApp(appName: string): void { - if (typeof appName === 'undefined') { - throw new FirebaseAppError( - AppErrorCodes.INVALID_APP_NAME, - 'No Firebase app name provided. App name must be a non-empty string.', - ); - } - - const appToRemove = this.app(appName); - delete this.apps_[appToRemove.name]; - } - - /** - * Parse the file pointed to by the FIREBASE_CONFIG_VAR, if it exists. - * Or if the FIREBASE_CONFIG_ENV contains a valid JSON object, parse it directly. - * If the environment variable contains a string that starts with '{' it will be parsed as JSON, - * otherwise it will be assumed to be pointing to a file. - */ - private loadOptionsFromEnvVar(): AppOptions { - const config = process.env[FIREBASE_CONFIG_VAR]; - if (!validator.isNonEmptyString(config)) { - return {}; - } - try { - const contents = config.startsWith('{') ? config : fs.readFileSync(config, 'utf8'); - return JSON.parse(contents) as AppOptions; - } catch (error) { - // Throw a nicely formed error message if the file contents cannot be parsed - throw new FirebaseAppError( - AppErrorCodes.INVALID_APP_OPTIONS, - 'Failed to parse app options file: ' + error, - ); - } + return this.appStore.getApps().map((app) => extendApp(app)); } } @@ -205,8 +109,8 @@ export class FirebaseNamespace { public Promise: any = Promise; /* tslint:enable */ - constructor() { - this.INTERNAL = new FirebaseNamespaceInternals(this); + constructor(appStore?: AppStore) { + this.INTERNAL = new FirebaseNamespaceInternals(appStore ?? new AppStore()); } /** @@ -417,3 +321,82 @@ export class FirebaseNamespace { return app; } } + +/** + * In order to maintain backward compatibility, we instantiate a default namespace instance in + * this module, and delegate all app lifecycle operations to it. In a future implementation where + * the old admin namespace is no longer supported, we should remove this. + * + * @internal + */ +export const defaultNamespace = new FirebaseNamespace(defaultAppStore); + +function extendApp(app: AppCore): App { + const result: App = app as App; + if ((result as any).__extended) { + return result; + } + + result.auth = () => { + const fn = require('../auth/index').getAuth; + return fn(app); + }; + + result.appCheck = () => { + const fn = require('../app-check/index').getAppCheck; + return fn(app); + }; + + result.database = (url?: string) => { + const fn = require('../database/index').getDatabaseWithUrl; + return fn(url, app); + }; + + result.messaging = () => { + const fn = require('../messaging/index').getMessaging; + return fn(app); + }; + + result.storage = () => { + const fn = require('../storage/index').getStorage; + return fn(app); + }; + + result.firestore = () => { + const fn = require('../firestore/index').getFirestore; + return fn(app); + }; + + result.instanceId = () => { + const fn = require('../instance-id/index').getInstanceId; + return fn(app); + } + + result.installations = () => { + const fn = require('../installations/index').getInstallations; + return fn(app); + }; + + result.machineLearning = () => { + const fn = require('../machine-learning/index').getMachineLearning; + return fn(app); + } + + result.projectManagement = () => { + const fn = require('../project-management/index').getProjectManagement; + return fn(app); + }; + + result.securityRules = () => { + const fn = require('../security-rules/index').getSecurityRules; + return fn(app); + }; + + result.remoteConfig = () => { + const fn = require('../remote-config/index').getRemoteConfig; + return fn(app); + }; + + (result as any).__extended = true; + return result; +} diff --git a/src/app/lifecycle.ts b/src/app/lifecycle.ts index ed999e6650..9c7cfdd31d 100644 --- a/src/app/lifecycle.ts +++ b/src/app/lifecycle.ts @@ -15,30 +15,122 @@ * limitations under the License. */ +import fs = require('fs'); + +import * as validator from '../utils/validator'; import { AppErrorCodes, FirebaseAppError } from '../utils/error'; import { App, AppOptions } from './core'; -import { FirebaseNamespace } from './firebase-namespace'; +import { getApplicationDefault } from './credential-internal'; +import { FirebaseApp } from './firebase-app'; -/** - * In order to maintain backward compatibility, we instantiate a default namespace instance in - * this module, and delegate all app lifecycle operations to it. In a future implementation where - * the old admin namespace is no longer supported, we should remove this, and implement app - * lifecycle management in this module itself. - * - * @internal - */ -export const defaultNamespace = new FirebaseNamespace(); +const DEFAULT_APP_NAME = '[DEFAULT]'; + +export class AppStore { + + private readonly appStore = new Map(); + + public initializeApp(options?: AppOptions, appName: string = DEFAULT_APP_NAME): App { + if (typeof options === 'undefined') { + options = loadOptionsFromEnvVar(); + options.credential = getApplicationDefault(); + } + + if (typeof appName !== 'string' || appName === '') { + throw new FirebaseAppError( + AppErrorCodes.INVALID_APP_NAME, + `Invalid Firebase app name "${appName}" provided. App name must be a non-empty string.`, + ); + } else if (this.appStore.has(appName)) { + if (appName === DEFAULT_APP_NAME) { + throw new FirebaseAppError( + AppErrorCodes.DUPLICATE_APP, + 'The default Firebase app already exists. This means you called initializeApp() ' + + 'more than once without providing an app name as the second argument. In most cases ' + + 'you only need to call initializeApp() once. But if you do want to initialize ' + + 'multiple apps, pass a second argument to initializeApp() to give each app a unique ' + + 'name.', + ); + } else { + throw new FirebaseAppError( + AppErrorCodes.DUPLICATE_APP, + `Firebase app named "${appName}" already exists. This means you called initializeApp() ` + + 'more than once with the same app name as the second argument. Make sure you provide a ' + + 'unique name every time you call initializeApp().', + ); + } + } + + const app = new FirebaseApp(options, appName, this); + this.appStore.set(app.name, app); + return app; + } + + public getApp(appName: string = DEFAULT_APP_NAME): App { + if (typeof appName !== 'string' || appName === '') { + throw new FirebaseAppError( + AppErrorCodes.INVALID_APP_NAME, + `Invalid Firebase app name "${appName}" provided. App name must be a non-empty string.`, + ); + } else if (!this.appStore.has(appName)) { + let errorMessage: string = (appName === DEFAULT_APP_NAME) + ? 'The default Firebase app does not exist. ' : `Firebase app named "${appName}" does not exist. `; + errorMessage += 'Make sure you call initializeApp() before using any of the Firebase services.'; + + throw new FirebaseAppError(AppErrorCodes.NO_APP, errorMessage); + } + + return this.appStore.get(appName)!; + } + + public getApps(): App[] { + // Return a copy so the caller cannot mutate the array + return Array.from(this.appStore.values()); + } + + public deleteApp(app: App): Promise { + if (typeof app !== 'object' || app === null || !('options' in app)) { + throw new FirebaseAppError(AppErrorCodes.INVALID_ARGUMENT, 'Invalid app argument.'); + } + + // Make sure the given app already exists. + const existingApp = getApp(app.name); -export function initializeApp(options?: AppOptions, name?: string): App { - return defaultNamespace.initializeApp(options, name); + // Delegate delete operation to the App instance itself. That will also remove the App + // instance from the AppStore. + return (existingApp as FirebaseApp).delete(); + } + + public clearAllApps(): Promise { + const promises: Array> = []; + this.getApps().forEach((app) => { + promises.push(this.deleteApp(app)); + }) + + return Promise.all(promises).then(); + } + + /** + * Removes the specified App instance from the store. This is currently called by the + * {@link FirebaseApp.delete} method. Can be removed once the app deletion is handled + * entirely by the {@link deleteApp} top-level function. + */ + public removeApp(appName: string): void { + this.appStore.delete(appName); + } +} + +export const defaultAppStore = new AppStore(); + +export function initializeApp(options?: AppOptions, appName: string = DEFAULT_APP_NAME): App { + return defaultAppStore.initializeApp(options, appName); } -export function getApp(name?: string): App { - return defaultNamespace.app(name); +export function getApp(appName: string = DEFAULT_APP_NAME): App { + return defaultAppStore.getApp(appName); } export function getApps(): App[] { - return defaultNamespace.apps; + return defaultAppStore.getApps(); } /** @@ -59,14 +151,36 @@ export function getApps(): App[] { * ``` */ export function deleteApp(app: App): Promise { - if (typeof app !== 'object' || app === null || !('options' in app)) { - throw new FirebaseAppError(AppErrorCodes.INVALID_ARGUMENT, 'Invalid app argument.'); - } + return defaultAppStore.deleteApp(app); +} - // Make sure the given app already exists. - const existingApp = getApp(app.name); +/** + * Constant holding the environment variable name with the default config. + * If the environment variable contains a string that starts with '{' it will be parsed as JSON, + * otherwise it will be assumed to be pointing to a file. + */ +export const FIREBASE_CONFIG_VAR = 'FIREBASE_CONFIG'; - // Delegate delete operation to the App instance itself for now. This will tear down any - // local app state, and also remove it from the global map. - return (existingApp as any).delete(); +/** + * Parse the file pointed to by the FIREBASE_CONFIG_VAR, if it exists. + * Or if the FIREBASE_CONFIG_ENV contains a valid JSON object, parse it directly. + * If the environment variable contains a string that starts with '{' it will be parsed as JSON, + * otherwise it will be assumed to be pointing to a file. + */ +function loadOptionsFromEnvVar(): AppOptions { + const config = process.env[FIREBASE_CONFIG_VAR]; + if (!validator.isNonEmptyString(config)) { + return {}; + } + + try { + const contents = config.startsWith('{') ? config : fs.readFileSync(config, 'utf8'); + return JSON.parse(contents) as AppOptions; + } catch (error) { + // Throw a nicely formed error message if the file contents cannot be parsed + throw new FirebaseAppError( + AppErrorCodes.INVALID_APP_OPTIONS, + 'Failed to parse app options file: ' + error, + ); + } } diff --git a/src/default-namespace.ts b/src/default-namespace.ts index 60dd22fe3a..12e855e2d8 100644 --- a/src/default-namespace.ts +++ b/src/default-namespace.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { defaultNamespace as firebaseAdmin } from './app/lifecycle'; +import { defaultNamespace as firebaseAdmin } from './app/firebase-namespace'; // Inject a circular default export to allow users to use both: // diff --git a/src/instance-id/instance-id.ts b/src/instance-id/instance-id.ts index cd01932007..6bd92d991d 100644 --- a/src/instance-id/instance-id.ts +++ b/src/instance-id/instance-id.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FirebaseApp } from '../app/firebase-app'; +import { getInstallations } from '../installations'; import { App } from '../app/index'; import { FirebaseInstallationsError, FirebaseInstanceIdError, @@ -63,7 +63,7 @@ export class InstanceId { * @returns A promise fulfilled when the instance ID is deleted. */ public deleteInstanceId(instanceId: string): Promise { - return (this.app as FirebaseApp).installations().deleteInstallation(instanceId) + return getInstallations(this.app).deleteInstallation(instanceId) .catch((err) => { if (err instanceof FirebaseInstallationsError) { let code = err.code.replace('installations/', ''); diff --git a/test/resources/mocks.ts b/test/resources/mocks.ts index b6b4b6bc97..026faa4f01 100644 --- a/test/resources/mocks.ts +++ b/test/resources/mocks.ts @@ -26,7 +26,6 @@ import * as _ from 'lodash'; import * as jwt from 'jsonwebtoken'; import { AppOptions } from '../../src/firebase-namespace-api'; -import { FirebaseNamespace } from '../../src/app/firebase-namespace'; import { FirebaseApp } from '../../src/app/firebase-app'; import { Credential, GoogleOAuthAccessToken, cert } from '../../src/app/index'; @@ -91,22 +90,18 @@ export class MockCredential implements Credential { } export function app(): FirebaseApp { - const namespaceInternals = new FirebaseNamespace().INTERNAL; - namespaceInternals.removeApp = _.noop; - return new FirebaseApp(appOptions, appName, namespaceInternals); + return new FirebaseApp(appOptions, appName); } export function mockCredentialApp(): FirebaseApp { return new FirebaseApp({ credential: new MockCredential(), databaseURL, - }, appName, new FirebaseNamespace().INTERNAL); + }, appName); } export function appWithOptions(options: AppOptions): FirebaseApp { - const namespaceInternals = new FirebaseNamespace().INTERNAL; - namespaceInternals.removeApp = _.noop; - return new FirebaseApp(options, appName, namespaceInternals); + return new FirebaseApp(options, appName); } export function appReturningNullAccessToken(): FirebaseApp { @@ -117,7 +112,7 @@ export function appReturningNullAccessToken(): FirebaseApp { } as any, databaseURL, projectId, - }, appName, new FirebaseNamespace().INTERNAL); + }, appName); } export function appReturningMalformedAccessToken(): FirebaseApp { @@ -127,7 +122,7 @@ export function appReturningMalformedAccessToken(): FirebaseApp { } as any, databaseURL, projectId, - }, appName, new FirebaseNamespace().INTERNAL); + }, appName); } export function appRejectedWhileFetchingAccessToken(): FirebaseApp { @@ -137,7 +132,7 @@ export function appRejectedWhileFetchingAccessToken(): FirebaseApp { } as any, databaseURL, projectId, - }, appName, new FirebaseNamespace().INTERNAL); + }, appName); } export const refreshToken = { diff --git a/test/unit/app/firebase-app.spec.ts b/test/unit/app/firebase-app.spec.ts index 043d8eb679..c6ec9eaaf6 100644 --- a/test/unit/app/firebase-app.spec.ts +++ b/test/unit/app/firebase-app.spec.ts @@ -29,9 +29,8 @@ import * as mocks from '../../resources/mocks'; import { GoogleOAuthAccessToken } from '../../../src/app/index'; import { ServiceAccountCredential } from '../../../src/app/credential-internal'; import { FirebaseApp, FirebaseAccessToken } from '../../../src/app/firebase-app'; -import { - FirebaseNamespace, FirebaseNamespaceInternals, FIREBASE_CONFIG_VAR -} from '../../../src/app/firebase-namespace'; +import { FirebaseNamespace } from '../../../src/app/firebase-namespace'; +import { AppStore, FIREBASE_CONFIG_VAR } from '../../../src/app/lifecycle'; import { auth, messaging, machineLearning, storage, firestore, database, instanceId, installations, projectManagement, securityRules , remoteConfig, appCheck, @@ -77,7 +76,6 @@ describe('FirebaseApp', () => { let clock: sinon.SinonFakeTimers; let getTokenStub: sinon.SinonStub; let firebaseNamespace: FirebaseNamespace; - let firebaseNamespaceInternals: FirebaseNamespaceInternals; let firebaseConfigVar: string | undefined; beforeEach(() => { @@ -90,10 +88,7 @@ describe('FirebaseApp', () => { firebaseConfigVar = process.env[FIREBASE_CONFIG_VAR]; delete process.env[FIREBASE_CONFIG_VAR]; firebaseNamespace = new FirebaseNamespace(); - firebaseNamespaceInternals = firebaseNamespace.INTERNAL; - - sinon.stub(firebaseNamespaceInternals, 'removeApp'); - mockApp = new FirebaseApp(mocks.appOptions, mocks.appName, firebaseNamespaceInternals); + mockApp = new FirebaseApp(mocks.appOptions, mocks.appName); }); afterEach(() => { @@ -106,7 +101,6 @@ describe('FirebaseApp', () => { } deleteSpy.resetHistory(); - (firebaseNamespaceInternals.removeApp as any).restore(); }); describe('#name', () => { @@ -124,14 +118,14 @@ describe('FirebaseApp', () => { it('should be case sensitive', () => { const newMockAppName = mocks.appName.toUpperCase(); - mockApp = new FirebaseApp(mocks.appOptions, newMockAppName, firebaseNamespaceInternals); + mockApp = new FirebaseApp(mocks.appOptions, newMockAppName); expect(mockApp.name).to.not.equal(mocks.appName); expect(mockApp.name).to.equal(newMockAppName); }); it('should respect leading and trailing whitespace', () => { const newMockAppName = ' ' + mocks.appName + ' '; - mockApp = new FirebaseApp(mocks.appOptions, newMockAppName, firebaseNamespaceInternals); + mockApp = new FirebaseApp(mocks.appOptions, newMockAppName); expect(mockApp.name).to.not.equal(mocks.appName); expect(mockApp.name).to.equal(newMockAppName); }); @@ -328,10 +322,11 @@ describe('FirebaseApp', () => { }); it('should call removeApp() on the Firebase namespace internals', () => { - return mockApp.delete().then(() => { - expect(firebaseNamespaceInternals.removeApp) - .to.have.been.calledOnce - .and.calledWith(mocks.appName); + const store = new AppStore(); + const stub = sinon.stub(store, 'removeApp').resolves(); + const app = new FirebaseApp(mockApp.options, mockApp.name, store); + return app.delete().then(() => { + expect(stub).to.have.been.calledOnce.and.calledWith(mocks.appName); }); }); diff --git a/test/unit/app/firebase-namespace.spec.ts b/test/unit/app/firebase-namespace.spec.ts index aee55a965f..7cfa53429e 100644 --- a/test/unit/app/firebase-namespace.spec.ts +++ b/test/unit/app/firebase-namespace.spec.ts @@ -260,66 +260,6 @@ describe('FirebaseNamespace', () => { }); }); - describe('#INTERNAL.removeApp()', () => { - const invalidAppNames = [null, NaN, 0, 1, true, false, [], ['a'], {}, { a: 1 }, _.noop]; - invalidAppNames.forEach((invalidAppName) => { - it('should throw given non-string app name: ' + JSON.stringify(invalidAppName), () => { - expect(() => { - firebaseNamespace.INTERNAL.removeApp(invalidAppName as any); - }).to.throw(`Invalid Firebase app name "${invalidAppName}" provided. App name must be a non-empty string.`); - }); - }); - - it('should throw given empty string app name', () => { - expect(() => { - firebaseNamespace.INTERNAL.removeApp(''); - }).to.throw('Invalid Firebase app name "" provided. App name must be a non-empty string.'); - }); - - it('should throw given an app name which does not correspond to an existing app', () => { - expect(() => { - firebaseNamespace.INTERNAL.removeApp(mocks.appName); - }).to.throw(`Firebase app named "${mocks.appName}" does not exist.`); - }); - - it('should throw given no app name if the default app does not exist', () => { - expect(() => { - (firebaseNamespace as any).INTERNAL.removeApp(); - }).to.throw('No Firebase app name provided. App name must be a non-empty string.'); - }); - - it('should throw given no app name even if the default app exists', () => { - firebaseNamespace.initializeApp(mocks.appOptions); - expect(() => { - (firebaseNamespace as any).INTERNAL.removeApp(); - }).to.throw('No Firebase app name provided. App name must be a non-empty string.'); - }); - - it('should remove the app corresponding to the provided app name from the namespace\'s app list', () => { - firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - firebaseNamespace.INTERNAL.removeApp(mocks.appName); - expect(() => { - return firebaseNamespace.app(mocks.appName); - }).to.throw(`Firebase app named "${mocks.appName}" does not exist.`); - }); - - it('should remove the default app from the namespace\'s app list if the default app name is provided', () => { - firebaseNamespace.initializeApp(mocks.appOptions); - firebaseNamespace.INTERNAL.removeApp(DEFAULT_APP_NAME); - expect(() => { - return firebaseNamespace.app(); - }).to.throw('The default Firebase app does not exist.'); - }); - - it('should not be idempotent', () => { - firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName); - firebaseNamespace.INTERNAL.removeApp(mocks.appName); - expect(() => { - firebaseNamespace.INTERNAL.removeApp(mocks.appName); - }).to.throw(`Firebase app named "${mocks.appName}" does not exist.`); - }); - }); - describe('#auth()', () => { it('should throw when called before initializing an app', () => { expect(() => { diff --git a/test/unit/app/index.spec.ts b/test/unit/app/index.spec.ts index 4cf35282f5..9de58baf84 100644 --- a/test/unit/app/index.spec.ts +++ b/test/unit/app/index.spec.ts @@ -30,6 +30,7 @@ import { Credential, applicationDefault, cert, refreshToken, } from '../../../src/app/index'; import { clearGlobalAppDefaultCred } from '../../../src/app/credential-factory'; +import { defaultAppStore } from '../../../src/app/lifecycle'; chai.should(); chai.use(chaiAsPromised); @@ -39,12 +40,7 @@ const expect = chai.expect; describe('firebase-admin/app', () => { afterEach(() => { - const deletePromises: Array> = []; - getApps().forEach((app) => { - deletePromises.push(deleteApp(app)); - }); - - return Promise.all(deletePromises); + return defaultAppStore.clearAllApps(); }); describe('#initializeApp()', () => { @@ -95,6 +91,12 @@ describe('firebase-admin/app', () => { } as any); }).to.throw('Invalid Firebase app options'); }); + + it('should initialize App instance without extended service methods', () => { + const app = initializeApp(mocks.appOptions); + expect((app as any).__extended).to.be.undefined; + expect((app as any).auth).to.be.undefined; + }); }); describe('#getApp()', () => { diff --git a/test/unit/firebase.spec.ts b/test/unit/firebase.spec.ts index 0edb814ce0..3313499b5f 100644 --- a/test/unit/firebase.spec.ts +++ b/test/unit/firebase.spec.ts @@ -32,6 +32,7 @@ import { FirebaseApp, FirebaseAppInternals } from '../../src/app/firebase-app'; import { RefreshTokenCredential, ServiceAccountCredential, isApplicationDefault } from '../../src/app/credential-internal'; +import { defaultAppStore, initializeApp } from '../../src/app/lifecycle'; chai.should(); chai.use(chaiAsPromised); @@ -54,12 +55,7 @@ describe('Firebase', () => { }); afterEach(() => { - const deletePromises: Array> = []; - firebaseAdmin.apps.forEach((app) => { - deletePromises.push(app.delete()); - }); - - return Promise.all(deletePromises); + return defaultAppStore.clearAllApps(); }); describe('#initializeApp()', () => { @@ -165,6 +161,23 @@ describe('Firebase', () => { return getAppInternals().getToken() .should.eventually.have.keys(['accessToken', 'expirationTime']); }); + + it('should initialize App instance with extended service methods', () => { + const app = firebaseAdmin.initializeApp(mocks.appOptions); + expect((app as any).__extended).to.be.true; + expect(app.auth).to.be.not.undefined; + }); + + it('should add extended service methods when retrieved via namespace', () => { + const app = initializeApp(mocks.appOptions); + expect((app as any).__extended).to.be.undefined; + expect((app as any).auth).to.be.undefined; + + const extendedApp = firebaseAdmin.app(); + expect(app).to.equal(extendedApp); + expect((app as any).__extended).to.be.true; + expect((app as any).auth).to.be.not.undefined; + }); }); describe('#database()', () => { @@ -266,6 +279,6 @@ describe('Firebase', () => { }); function getAppInternals(): FirebaseAppInternals { - return (firebaseAdmin.app() as FirebaseApp).INTERNAL; + return (firebaseAdmin.app() as unknown as FirebaseApp).INTERNAL; } }); diff --git a/test/unit/utils.ts b/test/unit/utils.ts index dee46114e6..2eb608397e 100644 --- a/test/unit/utils.ts +++ b/test/unit/utils.ts @@ -17,10 +17,7 @@ import * as _ from 'lodash'; import * as sinon from 'sinon'; - import * as mocks from '../resources/mocks'; - -import { FirebaseNamespace } from '../../src/app/firebase-namespace'; import { AppOptions } from '../../src/firebase-namespace-api'; import { FirebaseApp, FirebaseAppInternals, FirebaseAccessToken } from '../../src/app/firebase-app'; import { HttpError, HttpResponse } from '../../src/utils/api-request'; @@ -32,8 +29,7 @@ import { HttpError, HttpResponse } from '../../src/utils/api-request'; * @return A new FirebaseApp instance with the provided options. */ export function createAppWithOptions(options: object): FirebaseApp { - const mockFirebaseNamespaceInternals = new FirebaseNamespace().INTERNAL; - return new FirebaseApp(options as AppOptions, mocks.appName, mockFirebaseNamespaceInternals); + return new FirebaseApp(options as AppOptions, mocks.appName); } From ccd4aca735e721f5adabc036e128982e19f67ab9 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 30 Sep 2021 11:46:52 -0700 Subject: [PATCH 40/41] chore: Merging main branch into modular-sdk (#1443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(core): Automate Daily Integration Tests (#1130) * Automate daily integration tests * Rename to nightly * Change to 6am and 8pm PT & remove tar verification * Fix schedule comment * Updating Google Cloud naming (#1122) * Reinstating tag that devsite needs present to supress machine translation. * Updating a couple of references to GCP/Google Cloud Platform per new branding guidelines. * update typo in interface name (#1138) FireabseErrorInterface -> FirebaseErrorInterface * Improve token verification logic with Auth Emulator. (#1148) * Improve token verification logic with Auth Emulator. * Clean up comments. * Fix linting issues. * Address review comments. * Use mock for auth emulator unit test. * Implement session cookies. * Call useEmulator() only once. * Update tests. * Delete unused test helper. * Add unit tests for checking revocation. * Fix typo in test comments. * feat: Exporting all types of Messages so they can be used by consumers (#1147) * feat: Exporting all types of Messages so they can be used by consumers Fixes https://github.com/firebase/firebase-admin-node/issues/1146 * feat(exportMessageTypes): Testing TokenMessage * feat(exportMessageTypes): Added tests for all Message types * feat(exportMessageTypes): Fixed build * feat(exportMessageTypes): Better unit tests * feat(exportMessageTypes): Deleted unneeded separate TS test * feat(exportMessageTypes): Fixed build * feat(exportMessageTypes): Fixed linting * feat(auth): Implement getUserByProviderId (#769) RELEASE NOTE: Added a new getUserByProviderId() to lookup user accounts by their providers. * Allow enabling of anonymous provider via tenant configuration (#802) RELEASE NOTES: Allow enabling of anonymous provider via tenant configuration. * feat(auth): Add ability to link a federated ID with the `updateUser()` method. (#770) * (chore): Export UserProvider type and add it to toc.yaml (#1165) - Export UserProvider type - Add UserProvider to toc.yaml * [chore] Release 9.5.0 (#1167) Release 9.5.0 * chore: Updated doc generator for typedoc 0.19.0 (#1166) * Update HOME.md (#1181) Quick addition of a little bit of clarifying verbiage per an internal bug report. Thanks! * feat(rtdb): Support emulator mode for rules management operations (#1190) * feat(rtdb): Support emulator mode for rules management operations * fix: Adding namespace to emulated URL string * fix: Consolidated unit testing * fix: Removed extra whitespace * fix: Decoupled proactive token refresh from FirebaseApp (#1194) * fix: Decoupled proactive token refresh from FirebaseApp * fix: Defined constants for duration values * fix: Logging errors encountered while scheduling a refresh * fix: Renamed some variables for clarity * fix(rtdb): Fixing the RTDB token listener callback (#1203) * Add emulator-based integration tests. (#1155) * Add emulator-based integration tests. * Move emulator stuff out of package.json. * Update CONTRIBUTING.md too. * Add npx. * Skip new unsupported tests. * Inline commands in ci.yml. * Disable one flaky tests in emulator. (#1205) * [chore] Release 9.6.0 (#1209) * (chore): Add JWT Decoder and Signature Verifier (#1204) * (chore): Add JWT Decoder * Add signature verifier and key fetcher abstractions * Add unit tests for utils/jwt * chore: Add Mailgun send email action (#1210) * Add Mailgun send email github action * Add send email action in nightly workflow * chore: Fix bug in send-email action code (#1214) * chore: Fix bug in send-email action code * Add run id and trigger on repo dispatch event * Change dispatch event name in nightly workflow (#1216) - Change dispatch event name to `firebase_nightly_build` * chore: Clean up nightly workflow trigger tests (#1212) - Remove failing integration test added to trigger the send email workflow in nightly builds. * Add support for FIREBASE_STORAGE_EMULATOR_HOST env var (#1175) * Add support for FIREBASE_STORAGE_EMULATOR_HOST env var * Fixes lint error * Add test for FIREBASE_STORAGE_EMULATOR_HOST support * Lint fix * Minor fixes to storage tests * Address review comments * Address review suggestion Co-authored-by: Samuel Bushi * Revert "Disable one flaky tests in emulator. (#1205)" (#1227) This reverts commit 19660d921d20732857bf54393a09e8b5bce15d63. * fix(rtdb): Fixing a token refresh livelock in Cloud Functions (#1234) * [chore] Release 9.7.0 (#1240) * fix: adds missing EMAIL_NOT_FOUND error code (#1246) Catch `EMAIL_NOT_FOUND` and translate to `auth/email-not-found` when `/accounts:sendOobCode` is called for password reset on a user that does not exist. Fixes https://github.com/firebase/firebase-admin-node/issues/1202 * build(deps-dev): bump lodash from 4.17.19 to 4.17.21 (#1255) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Upgraded RTDB and other @firebase dependencies (#1250) * build(deps): bump y18n from 3.2.1 to 3.2.2 (#1208) Bumps [y18n](https://github.com/yargs/y18n) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/yargs/y18n/releases) - [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/y18n/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix storage emulator env formatting (#1257) * Fix storage emulator env formatting * Repair test * Rename test * Dang tests 2 good 4 me * Fix test * Fix tests again * build(deps): bump hosted-git-info from 2.8.8 to 2.8.9 (#1260) Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9. - [Release notes](https://github.com/npm/hosted-git-info/releases) - [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md) - [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hiranya Jayathilaka * feat: Add abuse reduction support (#1264) - Add abuse reduction support APIs * Fix @types/node conflict with grpc and port type (#1258) Upgraded the @types/node dependency to v12.12.47 * [chore] Release 9.8.0 (#1266) * build(deps): bump handlebars from 4.7.6 to 4.7.7 (#1253) Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.7.6 to 4.7.7. - [Release notes](https://github.com/wycats/handlebars.js/releases) - [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) - [Commits](https://github.com/wycats/handlebars.js/compare/v4.7.6...v4.7.7) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump jose from 2.0.4 to 2.0.5 (#1265) Bumps [jose](https://github.com/panva/jose) from 2.0.4 to 2.0.5. - [Release notes](https://github.com/panva/jose/releases) - [Changelog](https://github.com/panva/jose/blob/v2.0.5/CHANGELOG.md) - [Commits](https://github.com/panva/jose/compare/v2.0.4...v2.0.5) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: Revert regression introduced in #1257 (#1277) * fix(auth): make MFA uid optional for updateUser operations (#1278) * fix(auth): make MFA uid optional for updateUser operations MFA `uid` should be optional for `updateUser` operations. When not specified, the backend will provision a `uid` for the enrolled second factor. Fixes https://github.com/firebase/firebase-admin-node/issues/1276 * chore: Enabled dependabot (#1279) * chore: Remove gulp-replace dependency (#1285) * build(deps-dev): bump gulp-header from 1.8.12 to 2.0.9 (#1283) Bumps [gulp-header](https://github.com/tracker1/gulp-header) from 1.8.12 to 2.0.9. - [Release notes](https://github.com/tracker1/gulp-header/releases) - [Changelog](https://github.com/gulp-community/gulp-header/blob/master/changelog.md) - [Commits](https://github.com/tracker1/gulp-header/compare/v1.8.12...v2.0.9) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump run-sequence from 1.2.2 to 2.2.1 (#1282) Bumps [run-sequence](https://github.com/OverZealous/run-sequence) from 1.2.2 to 2.2.1. - [Release notes](https://github.com/OverZealous/run-sequence/releases) - [Changelog](https://github.com/OverZealous/run-sequence/blob/master/CHANGELOG.md) - [Commits](https://github.com/OverZealous/run-sequence/compare/v1.2.2...v2.2.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump sinon from 9.0.2 to 9.2.4 (#1289) Bumps [sinon](https://github.com/sinonjs/sinon) from 9.0.2 to 9.2.4. - [Release notes](https://github.com/sinonjs/sinon/releases) - [Changelog](https://github.com/sinonjs/sinon/blob/master/CHANGELOG.md) - [Commits](https://github.com/sinonjs/sinon/compare/v9.0.2...v9.2.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump nyc from 14.1.1 to 15.1.0 (#1290) Bumps [nyc](https://github.com/istanbuljs/nyc) from 14.1.1 to 15.1.0. - [Release notes](https://github.com/istanbuljs/nyc/releases) - [Changelog](https://github.com/istanbuljs/nyc/blob/master/CHANGELOG.md) - [Commits](https://github.com/istanbuljs/nyc/compare/v14.1.1...v15.1.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump chalk from 1.1.3 to 4.1.1 (#1288) Bumps [chalk](https://github.com/chalk/chalk) from 1.1.3 to 4.1.1. - [Release notes](https://github.com/chalk/chalk/releases) - [Commits](https://github.com/chalk/chalk/compare/v1.1.3...v4.1.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @microsoft/api-extractor from 7.11.2 to 7.15.2 (#1291) Bumps [@microsoft/api-extractor](https://github.com/microsoft/rushstack) from 7.11.2 to 7.15.2. - [Release notes](https://github.com/microsoft/rushstack/releases) - [Commits](https://github.com/microsoft/rushstack/compare/@microsoft/api-extractor_v7.11.2...@microsoft/api-extractor_v7.15.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Teporarily disabling sendToDeviceGroup integration test (#1292) * feat(auth): Added code flow support for OIDC flow. (#1220) * OIDC codeflow support * improve configs to simulate the real cases * update for changes in signiture * resolve comments * improve validator logic * remove unnecessary logic * add tests and fix errors * add auth-api-request rests * Update supported Node version to 10.13.0v (#1300) * Fixed integration test failure of skipped tests (#1299) * Fix integration test failure of skipped testss * Trigger integration tests * [chore] Release 9.9.0 (#1302) * Update OIDC reference docs (#1305) * Add OAuthResponseType to ToC (#1303) * fix(auth): Better type hierarchies for Auth API (#1294) * fix(auth): Better type heirarchies for Auth API * fix: Moved factorId back to the base types * fix: Updated API report * fix: Fixed a grammar error in comment * fix: Update to comment text * build(deps-dev): bump nock from 13.0.5 to 13.0.11 (#1311) Bumps [nock](https://github.com/nock/nock) from 13.0.5 to 13.0.11. - [Release notes](https://github.com/nock/nock/releases) - [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md) - [Commits](https://github.com/nock/nock/compare/v13.0.5...v13.0.11) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump ws from 7.3.1 to 7.4.6 (#1309) Bumps [ws](https://github.com/websockets/ws) from 7.3.1 to 7.4.6. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.3.1...7.4.6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump del from 2.2.2 to 6.0.0 (#1310) Bumps [del](https://github.com/sindresorhus/del) from 2.2.2 to 6.0.0. - [Release notes](https://github.com/sindresorhus/del/releases) - [Commits](https://github.com/sindresorhus/del/compare/v2.2.2...v6.0.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/jsonwebtoken from 8.5.0 to 8.5.1 (#1315) Bumps [@types/jsonwebtoken](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jsonwebtoken) from 8.5.0 to 8.5.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jsonwebtoken) --- updated-dependencies: - dependency-name: "@types/jsonwebtoken" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump nock from 13.0.11 to 13.1.0 (#1313) Bumps [nock](https://github.com/nock/nock) from 13.0.11 to 13.1.0. - [Release notes](https://github.com/nock/nock/releases) - [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md) - [Commits](https://github.com/nock/nock/compare/v13.0.11...v13.1.0) --- updated-dependencies: - dependency-name: nock dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/sinon-chai from 3.2.4 to 3.2.5 (#1316) Bumps [@types/sinon-chai](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/sinon-chai) from 3.2.4 to 3.2.5. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/sinon-chai) --- updated-dependencies: - dependency-name: "@types/sinon-chai" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump bcrypt from 5.0.0 to 5.0.1 (#1324) Bumps [bcrypt](https://github.com/kelektiv/node.bcrypt.js) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/kelektiv/node.bcrypt.js/releases) - [Changelog](https://github.com/kelektiv/node.bcrypt.js/blob/master/CHANGELOG.md) - [Commits](https://github.com/kelektiv/node.bcrypt.js/compare/v5.0.0...v5.0.1) --- updated-dependencies: - dependency-name: bcrypt dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @google-cloud/firestore from 4.5.0 to 4.12.2 (#1325) Bumps [@google-cloud/firestore](https://github.com/googleapis/nodejs-firestore) from 4.5.0 to 4.12.2. - [Release notes](https://github.com/googleapis/nodejs-firestore/releases) - [Changelog](https://github.com/googleapis/nodejs-firestore/blob/master/CHANGELOG.md) - [Commits](https://github.com/googleapis/nodejs-firestore/compare/v4.5.0...v4.12.2) --- updated-dependencies: - dependency-name: "@google-cloud/firestore" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/mocha from 2.2.48 to 8.2.2 (#1323) Bumps [@types/mocha](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/mocha) from 2.2.48 to 8.2.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/mocha) --- updated-dependencies: - dependency-name: "@types/mocha" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @firebase/app from 0.6.21 to 0.6.26 (#1329) Bumps [@firebase/app](https://github.com/firebase/firebase-js-sdk/tree/HEAD/packages/app) from 0.6.21 to 0.6.26. - [Release notes](https://github.com/firebase/firebase-js-sdk/releases) - [Changelog](https://github.com/firebase/firebase-js-sdk/blob/@firebase/app@0.6.26/packages/app/CHANGELOG.md) - [Commits](https://github.com/firebase/firebase-js-sdk/commits/@firebase/app@0.6.26/packages/app) --- updated-dependencies: - dependency-name: "@firebase/app" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @firebase/database from 0.10.0 to 0.10.4 (#1328) Bumps [@firebase/database](https://github.com/firebase/firebase-js-sdk/tree/HEAD/packages/database) from 0.10.0 to 0.10.4. - [Release notes](https://github.com/firebase/firebase-js-sdk/releases) - [Changelog](https://github.com/firebase/firebase-js-sdk/blob/master/packages/database/CHANGELOG.md) - [Commits](https://github.com/firebase/firebase-js-sdk/commits/@firebase/database@0.10.4/packages/database) --- updated-dependencies: - dependency-name: "@firebase/database" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump request-promise from 4.2.5 to 4.2.6 (#1331) Bumps [request-promise](https://github.com/request/request-promise) from 4.2.5 to 4.2.6. - [Release notes](https://github.com/request/request-promise/releases) - [Commits](https://github.com/request/request-promise/compare/v4.2.5...v4.2.6) --- updated-dependencies: - dependency-name: request-promise dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump gulp-filter from 6.0.0 to 7.0.0 (#1334) Bumps [gulp-filter](https://github.com/sindresorhus/gulp-filter) from 6.0.0 to 7.0.0. - [Release notes](https://github.com/sindresorhus/gulp-filter/releases) - [Commits](https://github.com/sindresorhus/gulp-filter/compare/v6.0.0...v7.0.0) --- updated-dependencies: - dependency-name: gulp-filter dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/minimist from 1.2.0 to 1.2.1 (#1336) Bumps [@types/minimist](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/minimist) from 1.2.0 to 1.2.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/minimist) --- updated-dependencies: - dependency-name: "@types/minimist" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(docs): replace all global.html -> admin.html (#1341) Co-authored-by: Hiranya Jayathilaka * feat(fis): Adding the admin.installations() API for deleting Firebase installation IDs (#1187) * feat(fis): Added admin.installations() API * fix: Marked IID APIs deprecated * fix: Deprecated App.instanceId() method; Added more unit and integration tests * fix: Throwing FirebaseInstallationsError from constructor * fix: Some docs updates * fix: Minor update to API doc comment * fix: Added Installations class to API ref toc * fix: Minor doc updates * fix: Updated TOC for new Auth type aliases (#1342) * [chore] Release 9.10.0 (#1345) * build(deps-dev): bump @types/request-promise from 4.1.46 to 4.1.47 (#1338) Bumps [@types/request-promise](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/request-promise) from 4.1.46 to 4.1.47. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/request-promise) --- updated-dependencies: - dependency-name: "@types/request-promise" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @firebase/database from 0.10.4 to 0.10.5 (#1350) Bumps [@firebase/database](https://github.com/firebase/firebase-js-sdk/tree/HEAD/packages/database) from 0.10.4 to 0.10.5. - [Release notes](https://github.com/firebase/firebase-js-sdk/releases) - [Changelog](https://github.com/firebase/firebase-js-sdk/blob/master/packages/database/CHANGELOG.md) - [Commits](https://github.com/firebase/firebase-js-sdk/commits/@firebase/database@0.10.5/packages/database) --- updated-dependencies: - dependency-name: "@firebase/database" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/nock from 9.3.1 to 11.1.0 (#1351) Bumps [@types/nock](https://github.com/nock/nock) from 9.3.1 to 11.1.0. - [Release notes](https://github.com/nock/nock/releases) - [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md) - [Commits](https://github.com/nock/nock/compare/v9.3.1...v11.1.0) --- updated-dependencies: - dependency-name: "@types/nock" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/sinon from 9.0.4 to 10.0.2 (#1326) Bumps [@types/sinon](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/sinon) from 9.0.4 to 10.0.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/sinon) --- updated-dependencies: - dependency-name: "@types/sinon" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @firebase/database from 0.10.5 to 0.10.6 (#1356) Bumps [@firebase/database](https://github.com/firebase/firebase-js-sdk/tree/HEAD/packages/database) from 0.10.5 to 0.10.6. - [Release notes](https://github.com/firebase/firebase-js-sdk/releases) - [Changelog](https://github.com/firebase/firebase-js-sdk/blob/master/packages/database/CHANGELOG.md) - [Commits](https://github.com/firebase/firebase-js-sdk/commits/@firebase/database@0.10.6/packages/database) --- updated-dependencies: - dependency-name: "@firebase/database" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump jwks-rsa from 2.0.2 to 2.0.3 (#1361) Bumps [jwks-rsa](https://github.com/auth0/node-jwks-rsa) from 2.0.2 to 2.0.3. - [Release notes](https://github.com/auth0/node-jwks-rsa/releases) - [Changelog](https://github.com/auth0/node-jwks-rsa/blob/master/CHANGELOG.md) - [Commits](https://github.com/auth0/node-jwks-rsa/compare/v2.0.2...v2.0.3) --- updated-dependencies: - dependency-name: jwks-rsa dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump yargs from 16.1.0 to 17.0.1 (#1357) Bumps [yargs](https://github.com/yargs/yargs) from 16.1.0 to 17.0.1. - [Release notes](https://github.com/yargs/yargs/releases) - [Changelog](https://github.com/yargs/yargs/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/yargs/compare/v16.1.0...v17.0.1) --- updated-dependencies: - dependency-name: yargs dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/chai from 4.2.11 to 4.2.21 (#1365) Bumps [@types/chai](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/chai) from 4.2.11 to 4.2.21. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/chai) --- updated-dependencies: - dependency-name: "@types/chai" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update index.ts (#1367) Fix typo in comments: `sendMulticase` -> `sendMulticast` * build(deps): bump @google-cloud/firestore from 4.12.2 to 4.13.1 (#1369) Bumps [@google-cloud/firestore](https://github.com/googleapis/nodejs-firestore) from 4.12.2 to 4.13.1. - [Release notes](https://github.com/googleapis/nodejs-firestore/releases) - [Changelog](https://github.com/googleapis/nodejs-firestore/blob/master/CHANGELOG.md) - [Commits](https://github.com/googleapis/nodejs-firestore/compare/v4.12.2...v4.13.1) --- updated-dependencies: - dependency-name: "@google-cloud/firestore" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(fac): Add custom TTL options for App Check (#1363) * Add custom ttl options for App Check * PR fixes * Add integration tests * PR fixes * Reduce App Check custom token exp to 5 mins (#1372) * Add AppCheckTokenOptions type to ToC (#1375) Add `AppCheckTokenOptions` to `ToC` * Fix typo and formatting in docs (#1378) - Fix typo and add back-ticks * [chore] Release 9.11.0 (#1376) - Release 9.11.0 * build(deps-dev): bump nock from 13.1.0 to 13.1.1 (#1370) Bumps [nock](https://github.com/nock/nock) from 13.1.0 to 13.1.1. - [Release notes](https://github.com/nock/nock/releases) - [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md) - [Commits](https://github.com/nock/nock/compare/v13.1.0...v13.1.1) --- updated-dependencies: - dependency-name: nock dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/bcrypt from 2.0.0 to 5.0.0 (#1384) Bumps [@types/bcrypt](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/bcrypt) from 2.0.0 to 5.0.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/bcrypt) --- updated-dependencies: - dependency-name: "@types/bcrypt" dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @firebase/database from 0.10.6 to 0.10.7 (#1385) Bumps [@firebase/database](https://github.com/firebase/firebase-js-sdk/tree/HEAD/packages/database) from 0.10.6 to 0.10.7. - [Release notes](https://github.com/firebase/firebase-js-sdk/releases) - [Changelog](https://github.com/firebase/firebase-js-sdk/blob/master/packages/database/CHANGELOG.md) - [Commits](https://github.com/firebase/firebase-js-sdk/commits/@firebase/database@0.10.7/packages/database) --- updated-dependencies: - dependency-name: "@firebase/database" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/lodash from 4.14.157 to 4.14.171 (#1386) Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.14.157 to 4.14.171. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash) --- updated-dependencies: - dependency-name: "@types/lodash" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/request from 2.48.5 to 2.48.6 (#1387) Bumps [@types/request](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/request) from 2.48.5 to 2.48.6. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/request) --- updated-dependencies: - dependency-name: "@types/request" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @types/minimist from 1.2.1 to 1.2.2 (#1388) Bumps [@types/minimist](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/minimist) from 1.2.1 to 1.2.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/minimist) --- updated-dependencies: - dependency-name: "@types/minimist" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump jwks-rsa from 2.0.3 to 2.0.4 (#1393) Bumps [jwks-rsa](https://github.com/auth0/node-jwks-rsa) from 2.0.3 to 2.0.4. - [Release notes](https://github.com/auth0/node-jwks-rsa/releases) - [Changelog](https://github.com/auth0/node-jwks-rsa/blob/master/CHANGELOG.md) - [Commits](https://github.com/auth0/node-jwks-rsa/compare/v2.0.3...2.0.4) --- updated-dependencies: - dependency-name: jwks-rsa dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump @microsoft/api-extractor from 7.15.2 to 7.18.4 (#1379) * build(deps-dev): bump @microsoft/api-extractor from 7.15.2 to 7.18.4 Bumps [@microsoft/api-extractor](https://github.com/microsoft/rushstack) from 7.15.2 to 7.18.4. - [Release notes](https://github.com/microsoft/rushstack/releases) - [Commits](https://github.com/microsoft/rushstack/compare/@microsoft/api-extractor_v7.15.2...@microsoft/api-extractor_v7.18.4) --- updated-dependencies: - dependency-name: "@microsoft/api-extractor" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * fix: Updated API report with the new API Extractor version Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Hiranya Jayathilaka * build(deps): bump tar from 6.1.0 to 6.1.3 (#1399) Bumps [tar](https://github.com/npm/node-tar) from 6.1.0 to 6.1.3. - [Release notes](https://github.com/npm/node-tar/releases) - [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-tar/compare/v6.1.0...v6.1.3) --- updated-dependencies: - dependency-name: tar dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps-dev): bump ts-node from 9.0.0 to 10.2.0 (#1402) Bumps [ts-node](https://github.com/TypeStrong/ts-node) from 9.0.0 to 10.2.0. - [Release notes](https://github.com/TypeStrong/ts-node/releases) - [Commits](https://github.com/TypeStrong/ts-node/compare/v9.0.0...v10.2.0) --- updated-dependencies: - dependency-name: ts-node dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Add emulator tests to nightlies (#1409) * Add emulator tests to nightlies - Run emulator based integration tests as part of the nightly tests. * Trigger CI * fix: Throw error on user disabled and check revoked set true (#1401) * fix: Throw error on user disabled and check revoked set true * resolve 2 calls on getUser(), improve tests and lints * remove currentUser.reload * add return * Tweak tests * small fix * Use async and await instead of chain in integration test and change CI dependency * retry * Add special case of authEmulator * fix emulator on issue * remove firebase-tool version changes * useMockIdToken for unit test * fix typos * build(deps-dev): bump yargs from 17.0.1 to 17.1.1 (#1412) Bumps [yargs](https://github.com/yargs/yargs) from 17.0.1 to 17.1.1. - [Release notes](https://github.com/yargs/yargs/releases) - [Changelog](https://github.com/yargs/yargs/blob/master/CHANGELOG.md) - [Commits](https://github.com/yargs/yargs/compare/v17.0.1...v17.1.1) --- updated-dependencies: - dependency-name: yargs dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump path-parse from 1.0.6 to 1.0.7 (#1413) Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7. - [Release notes](https://github.com/jbgutierrez/path-parse/releases) - [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7) --- updated-dependencies: - dependency-name: path-parse dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix: Update comments in index files (#1414) * add comments in index files * change signature in firebase-admin-node * small fixes * small fixes * fixes * [chore] Release 9.11.1 (#1415) * fix typo (#1420) * build(deps-dev): bump @microsoft/api-extractor from 7.18.4 to 7.18.7 (#1423) Bumps [@microsoft/api-extractor](https://github.com/microsoft/rushstack) from 7.18.4 to 7.18.7. - [Release notes](https://github.com/microsoft/rushstack/releases) - [Commits](https://github.com/microsoft/rushstack/compare/@microsoft/api-extractor_v7.18.4...@microsoft/api-extractor_v7.18.7) --- updated-dependencies: - dependency-name: "@microsoft/api-extractor" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(rc): Add Remote Config Parameter Value Type Support (#1424) go/admin-sdk-rc-parameter-value-types Add RC Parameter Value Type. Update unit tests. - Integration tests will be added following the REST API launch. Added release:stage to trigger existing integration tests (to test backward compatibility). - Do not merge until the BE is updated. Update: - Integration tests are updated. RELEASE NOTE: Added Remote Config Parameter Value Type Support. * build(deps-dev): bump @types/lodash from 4.14.171 to 4.14.173 (#1435) Bumps [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) from 4.14.171 to 4.14.173. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash) --- updated-dependencies: - dependency-name: "@types/lodash" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump tar from 6.1.3 to 6.1.11 (#1430) Bumps [tar](https://github.com/npm/node-tar) from 6.1.3 to 6.1.11. - [Release notes](https://github.com/npm/node-tar/releases) - [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/npm/node-tar/compare/v6.1.3...v6.1.11) --- updated-dependencies: - dependency-name: tar dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Pin @types/jsonwebtoken to 8.5.1 (#1438) * fix(rtdb): Changed admin.database to use database-compat package (#1437) * fix: Changed admin.database to use database-compat package * fix: Upgraded to latest staged versions of compat packages * fix: Added dom for test compilation which includes Auth types * fix: Using single quotes as per our convention * fix: Using the new database-compat/standalone entrypoint * fix: Changed versions to released tags * fix(fac): Verify Token: Change the jwks cache duration from 1 day to 6 hours (#1439) Change the jwks cache duration (used by the verify token API) from 1 day to 6 hours. * [chore] Release 9.12.0 (#1442) * fix: Merged missing commits from #1401 * fix: Updated integration tests * fix: Temporary with for RTDB type incompatibility issue * fix: Cleaned up faulty API doc annotations Co-authored-by: Lahiru Maramba Co-authored-by: egilmorez Co-authored-by: batuxd <9674241+suchcodemuchwow@users.noreply.github.com> Co-authored-by: Yuchen Shi Co-authored-by: Marc Bornträger Co-authored-by: rsgowman Co-authored-by: Abe Haskins Co-authored-by: Samuel Bushi Co-authored-by: bojeil-google Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nikhil Agarwal <54072321+nikhilag@users.noreply.github.com> Co-authored-by: Xin Li Co-authored-by: NothingEverHappens Co-authored-by: Daniel Hritzkiv Co-authored-by: Arthur Gubaidullin --- .github/workflows/nightly.yml | 4 +- etc/firebase-admin.api.md | 2 + etc/firebase-admin.installations.api.md | 1 - etc/firebase-admin.remote-config.api.md | 4 + gulpfile.js | 4 +- package-lock.json | 3391 ++++++++--------- package.json | 8 +- src/app-check/token-generator.ts | 4 +- src/app-check/token-verifier.ts | 2 +- src/app/firebase-namespace.ts | 2 +- src/auth/auth-config.ts | 2 - src/auth/base-auth.ts | 60 +- src/auth/tenant-manager.ts | 12 +- src/auth/token-generator.ts | 6 +- src/auth/token-verifier.ts | 1 - src/database/database.ts | 7 +- src/database/index.ts | 6 +- src/installations/installations-namespace.ts | 2 +- src/installations/installations.ts | 3 +- src/instance-id/index.ts | 3 + src/remote-config/index.ts | 1 + src/remote-config/remote-config-api.ts | 12 + src/remote-config/remote-config-namespace.ts | 6 + src/utils/crypto-signer.ts | 8 +- src/utils/error.ts | 4 + src/utils/jwt.ts | 6 +- test/integration/app.spec.ts | 6 +- test/integration/auth.spec.ts | 97 +- test/integration/remote-config.spec.ts | 12 +- test/unit/app-check/token-generator.spec.ts | 100 +- test/unit/app/firebase-namespace.spec.ts | 2 +- test/unit/auth/auth.spec.ts | 95 +- test/unit/auth/token-verifier.spec.ts | 1 - test/unit/index.spec.ts | 4 + .../remote-config-api-client.spec.ts | 5 +- test/unit/remote-config/remote-config.spec.ts | 8 +- test/unit/utils/jwt.spec.ts | 16 +- 37 files changed, 2074 insertions(+), 1833 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 536827b1e5..e79c33a813 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -82,7 +82,7 @@ jobs: subject: 'Nightly build ${{github.run_id}} of ${{github.repository}} failed!' html: > Nightly workflow ${{github.run_id}} failed on: ${{github.repository}} -

    Navigate to the +

    Navigate to the failed workflow. continue-on-error: true @@ -97,6 +97,6 @@ jobs: subject: 'Nightly build ${{github.run_id}} of ${{github.repository}} cancelled!' html: > Nightly workflow ${{github.run_id}} cancelled on: ${{github.repository}} -

    Navigate to the +

    Navigate to the cancelled workflow. continue-on-error: true diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 29d2e783d8..af4bef9361 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -431,6 +431,8 @@ export namespace remoteConfig { export type ListVersionsOptions = ListVersionsOptions; // Warning: (ae-forgotten-export) The symbol "ListVersionsResult" needs to be exported by the entry point default-namespace.d.ts export type ListVersionsResult = ListVersionsResult; + // Warning: (ae-forgotten-export) The symbol "ParameterValueType" needs to be exported by the entry point default-namespace.d.ts + export type ParameterValueType = ParameterValueType; // Warning: (ae-forgotten-export) The symbol "RemoteConfig" needs to be exported by the entry point default-namespace.d.ts export type RemoteConfig = RemoteConfig; // Warning: (ae-forgotten-export) The symbol "RemoteConfigCondition" needs to be exported by the entry point default-namespace.d.ts diff --git a/etc/firebase-admin.installations.api.md b/etc/firebase-admin.installations.api.md index a3581a317f..4a4d11e06f 100644 --- a/etc/firebase-admin.installations.api.md +++ b/etc/firebase-admin.installations.api.md @@ -15,7 +15,6 @@ export function getInstallations(app?: App): Installations; // @public export class Installations { - constructor(app: App); get app(): App; deleteInstallation(fid: string): Promise; } diff --git a/etc/firebase-admin.remote-config.api.md b/etc/firebase-admin.remote-config.api.md index 933f8536d7..fb07bfad76 100644 --- a/etc/firebase-admin.remote-config.api.md +++ b/etc/firebase-admin.remote-config.api.md @@ -38,6 +38,9 @@ export interface ListVersionsResult { versions: Version[]; } +// @public +export type ParameterValueType = 'STRING' | 'BOOLEAN' | 'NUMBER' | 'JSON'; + // @public export class RemoteConfig { // (undocumented) @@ -67,6 +70,7 @@ export interface RemoteConfigParameter { }; defaultValue?: RemoteConfigParameterValue; description?: string; + valueType?: ParameterValueType; } // @public diff --git a/gulpfile.js b/gulpfile.js index a056ca2bc9..749b6ff517 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -54,7 +54,9 @@ var paths = { // emitted. var buildProject = ts.createProject('tsconfig.json', { rootDir: 'src', declarationMap: true }); -var buildTest = ts.createProject('tsconfig.json'); +// Include dom libraries during test compilation since we use some web SDK +// libraries in our tests. +var buildTest = ts.createProject('tsconfig.json', { lib: ['es2018', 'dom'] }); var banner = `/*! firebase-admin v${pkg.version} */\n`; diff --git a/package-lock.json b/package-lock.json index a32e9e19e6..d3f686c872 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,35 +5,35 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", + "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.14.5" } }, "@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.15.0.tgz", + "integrity": "sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==", "dev": true }, "@babel/core": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz", - "integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.3", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.14.2", - "@babel/helpers": "^7.14.0", - "@babel/parser": "^7.14.3", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2", + "version": "7.15.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz", + "integrity": "sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-compilation-targets": "^7.15.4", + "@babel/helper-module-transforms": "^7.15.4", + "@babel/helpers": "^7.15.4", + "@babel/parser": "^7.15.5", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -42,97 +42,21 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.4.tgz", + "integrity": "sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw==", "dev": true, "requires": { - "@babel/types": "^7.14.2", + "@babel/types": "^7.15.4", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -146,148 +70,149 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz", + "integrity": "sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", + "@babel/compat-data": "^7.15.0", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", "semver": "^6.3.0" } }, "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz", + "integrity": "sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" + "@babel/helper-get-function-arity": "^7.15.4", + "@babel/template": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz", + "integrity": "sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA==", + "dev": true, + "requires": { + "@babel/types": "^7.15.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz", + "integrity": "sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.15.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz", + "integrity": "sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.15.4" } }, "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz", + "integrity": "sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.15.4" } }, "@babel/helper-module-transforms": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", - "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - } + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.15.7.tgz", + "integrity": "sha512-ZNqjjQG/AuFfekFTY+7nY4RgBSklgTu970c7Rj3m/JOhIu5KPBUuTA9AY6zaKcUvk4g6EbDXdBnhi35FAssdSw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.15.4", + "@babel/helper-replace-supers": "^7.15.4", + "@babel/helper-simple-access": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/helper-validator-identifier": "^7.15.7", + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz", + "integrity": "sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.15.4" } }, "@babel/helper-replace-supers": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.3.tgz", - "integrity": "sha512-Rlh8qEWZSTfdz+tgNV/N4gz1a0TMNwCUcENhMjHTHKp3LseYH5Jha0NSlyTQWMnjbYcwFt+bqAMqSLHVXkQ6UA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz", + "integrity": "sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" + "@babel/helper-member-expression-to-functions": "^7.15.4", + "@babel/helper-optimise-call-expression": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz", + "integrity": "sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg==", "dev": true, "requires": { - "@babel/types": "^7.13.12" + "@babel/types": "^7.15.4" } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz", + "integrity": "sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.15.4" } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", + "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", "dev": true }, "@babel/helpers": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", - "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.15.4.tgz", + "integrity": "sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ==", "dev": true, "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" + "@babel/template": "^7.15.4", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -345,216 +270,55 @@ } }, "@babel/parser": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.3.tgz", - "integrity": "sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ==", + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.7.tgz", + "integrity": "sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g==", "dev": true }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz", + "integrity": "sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4" } }, "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", + "version": "7.15.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.4.tgz", + "integrity": "sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.15.4", + "@babel/helper-function-name": "^7.15.4", + "@babel/helper-hoist-variables": "^7.15.4", + "@babel/helper-split-export-declaration": "^7.15.4", + "@babel/parser": "^7.15.4", + "@babel/types": "^7.15.4", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - }, - "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, "@babel/types": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", - "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", + "version": "7.15.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.6.tgz", + "integrity": "sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.14.0", + "@babel/helper-validator-identifier": "^7.14.9", "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - } } }, "@cspotcode/source-map-consumer": { @@ -586,101 +350,87 @@ "js-yaml": "4.0.0", "resolve": "~1.17.0", "tslib": "^2.1.0" - }, - "dependencies": { - "@microsoft/tsdoc": { - "version": "0.12.24", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", - "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", - "dev": true - }, - "@rushstack/node-core-library": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", - "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", - "dev": true, - "requires": { - "@types/node": "10.17.13", - "colors": "~1.2.1", - "fs-extra": "~7.0.1", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.17.0", - "semver": "~7.3.0", - "timsort": "~0.3.0", - "z-schema": "~3.18.3" - } - }, - "@rushstack/ts-command-line": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.8.tgz", - "integrity": "sha512-8ghIWhkph7NnLCMDJtthpsb7TMOsVGXVDvmxjE/CeklTqjbbUFBjGXizJfpbEkRQTELuZQ2+vGn7sGwIWKN2uA==", - "dev": true, - "requires": { - "@types/argparse": "1.0.38", - "argparse": "~1.0.9", - "colors": "~1.2.1", - "string-argv": "~0.3.1" - } - }, - "@types/node": { - "version": "10.17.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", - "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "tslib": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", - "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", - "dev": true - } } }, "@firebase/app": { - "version": "0.6.26", - "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.26.tgz", - "integrity": "sha512-y4tpb+uiYLQC5+/AHBtIGZMaTjJ2BHQEsXmPqxyhfVFDzWMcXFsc//RVxA/0OejajhJR6GeqDcIS3m47mUD+Aw==", - "dev": true, - "requires": { - "@firebase/app-types": "0.6.2", - "@firebase/component": "0.5.2", - "@firebase/logger": "0.2.6", - "@firebase/util": "1.1.0", - "dom-storage": "2.1.0", - "tslib": "^2.1.0", - "xmlhttprequest": "1.8.0" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.7.1.tgz", + "integrity": "sha512-B4z6E1EPQc0mOjF35IPKdDRCFnT/fNQIHfM+v7F9obB7ItPhGILK3LxaQfuampSQpF6GG6TPFDbrWK6myXAq+g==", + "dev": true, + "requires": { + "@firebase/component": "0.5.7", + "@firebase/logger": "0.3.0", + "@firebase/util": "1.4.0", + "tslib": "^2.1.0" + } + }, + "@firebase/app-compat": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.1.2.tgz", + "integrity": "sha512-kF1maoqA8bZqJ4v/ojVvA7kIyyXEPkJmL48otGrC8LIgdcen7xCx3JFDe0DGeQywg+qujvdkJz/TptFN1cvAgw==", + "dev": true, + "requires": { + "@firebase/app": "0.7.1", + "@firebase/component": "0.5.7", + "@firebase/logger": "0.3.0", + "@firebase/util": "1.4.0", + "tslib": "^2.1.0" + } + }, + "@firebase/app-types": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.7.0.tgz", + "integrity": "sha512-6fbHQwDv2jp/v6bXhBw2eSRbNBpxHcd1NBF864UksSMVIqIyri9qpJB1Mn6sGZE+bnDsSQBC5j2TbMxYsJQkQg==" + }, + "@firebase/auth": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.18.0.tgz", + "integrity": "sha512-iK+VXkdDkum8SmJNgz9ZcOboRLrUN1VW7AHHkpZb76VJvoYRoCPD+A9O/v/ziI0LpwIZJwi1GFes9XjZTlfLiA==", + "dev": true, + "requires": { + "@firebase/component": "0.5.7", + "@firebase/logger": "0.3.0", + "@firebase/util": "1.4.0", + "node-fetch": "2.6.2", + "selenium-webdriver": "4.0.0-rc-1", + "tslib": "^2.1.0" }, "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "node-fetch": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==", "dev": true } } }, - "@firebase/app-types": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.2.tgz", - "integrity": "sha512-2VXvq/K+n8XMdM4L2xy5bYp2ZXMawJXluUIDzUBvMthVR+lhxK4pfFiqr1mmDbv9ydXvEAuFsD+6DpcZuJcSSw==" - }, - "@firebase/auth": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.5.tgz", - "integrity": "sha512-Cgs/TlVot2QkbJyEphvKmu+2qxYlNN+Q2+29aqZwryrnn1eLwlC7nT89K6O91/744HJRtiThm02bMj2Wh61E3Q==", + "@firebase/auth-compat": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.1.3.tgz", + "integrity": "sha512-eDDtY5If+ERJxalt+plvX6avZspuwo4/kPXssvV+csm414awhDzQBtSDPDajgbH3YB9V+O3LAFHeWcP3rrHS5w==", "dev": true, "requires": { - "@firebase/auth-types": "0.10.3" + "@firebase/auth": "0.18.0", + "@firebase/auth-types": "0.11.0", + "@firebase/component": "0.5.7", + "@firebase/util": "1.4.0", + "node-fetch": "2.6.2", + "selenium-webdriver": "^4.0.0-beta.2", + "tslib": "^2.1.0" + }, + "dependencies": { + "@firebase/auth-types": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.11.0.tgz", + "integrity": "sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw==", + "dev": true + }, + "node-fetch": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA==", + "dev": true + } } }, "@firebase/auth-interop-types": { @@ -694,86 +444,87 @@ "integrity": "sha512-zExrThRqyqGUbXOFrH/sowuh2rRtfKHp9SBVY2vOqKWdCX1Ztn682n9WLtlUDsiYVIbBcwautYWk2HyCGFv0OA==", "dev": true }, - "@firebase/component": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.2.tgz", - "integrity": "sha512-QT+o6VaBCz/k8wmC/DErU9dQK2QeIoHtkBkryZVTSRkrvulglEWNIpbPp86UbuqZZd1wwzoh6m7BL6JbdEp9SQ==", - "dev": true, - "requires": { - "@firebase/util": "1.1.0", - "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "dev": true - } + "@firebase/component": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.7.tgz", + "integrity": "sha512-CiAHUPXh2hn/lpzMShNmfAxHNQhKQwmQUJSYMPCjf2bCCt4Z2vLGpS+UWEuNFm9Zf8LNmkS+Z+U/s4Obi5carg==", + "requires": { + "@firebase/util": "1.4.0", + "tslib": "^2.1.0" } }, "@firebase/database": { - "version": "0.10.7", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.10.7.tgz", - "integrity": "sha512-7BFj8LFhGL+TmLiPOffOVfkrO2wm44mGcT0jqrkTkt1KydapmjABFJBRvONvlLij5LoWrJK1cSuE8wYDQrDq2Q==", + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.12.1.tgz", + "integrity": "sha512-Ethk0hc476qnkSKNBa+8Yc7iM8AO69HYWsaD+QUC983FZtnuMyNLHtEeSUbLQYvyHo7cOjcc52slop14WmfZeQ==", "requires": { "@firebase/auth-interop-types": "0.1.6", - "@firebase/component": "0.5.4", - "@firebase/database-types": "0.7.2", - "@firebase/logger": "0.2.6", - "@firebase/util": "1.1.0", - "faye-websocket": "0.11.3", + "@firebase/component": "0.5.7", + "@firebase/logger": "0.3.0", + "@firebase/util": "1.4.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "@firebase/database-compat": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.1.1.tgz", + "integrity": "sha512-K3DFWiw0YkLZtlfA9TOGPw6zVXKu5dQ1XqIGztUufFVRYW8IizReXVxzSSmJNR4Adr2LiU9j66Wenc6e5UfwaQ==", + "requires": { + "@firebase/component": "0.5.7", + "@firebase/database": "0.12.1", + "@firebase/database-types": "0.9.1", + "@firebase/logger": "0.3.0", + "@firebase/util": "1.4.0", "tslib": "^2.1.0" }, "dependencies": { - "@firebase/component": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.5.4.tgz", - "integrity": "sha512-KoLDPTsvxWr6FT9kn/snffJItaWXZLHLJlZVKiiw+flKE6MVA8Eec+ctvM2zcsMZzC2Z47gFnVqywfBlOevmpQ==", + "@firebase/database-types": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.9.1.tgz", + "integrity": "sha512-RUixK/YrbpxbfdE+nYP0wMcEsz1xPTnafP0q3UlSS/+fW744OITKtR1J0cMRaXbvY7EH0wUVTNVkrtgxYY8IgQ==", "requires": { - "@firebase/util": "1.1.0", - "tslib": "^2.1.0" + "@firebase/app-types": "0.7.0", + "@firebase/util": "1.4.0" } - }, - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } }, "@firebase/database-types": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.2.tgz", - "integrity": "sha512-cdAd/dgwvC0r3oLEDUR+ULs1vBsEvy0b27nlzKhU6LQgm9fCDzgaH9nFGv8x+S9dly4B0egAXkONkVoWcOAisg==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.7.3.tgz", + "integrity": "sha512-dSOJmhKQ0nL8O4EQMRNGpSExWCXeHtH57gGg0BfNAdWcKhC8/4Y+qfKLfWXzyHvrSecpLmO0SmAi/iK2D5fp5A==", "requires": { - "@firebase/app-types": "0.6.2" + "@firebase/app-types": "0.6.3" + }, + "dependencies": { + "@firebase/app-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.6.3.tgz", + "integrity": "sha512-/M13DPPati7FQHEQ9Minjk1HGLm/4K4gs9bR4rzLCWJg64yGtVC0zNg9gDpkw9yc2cvol/mNFxqTtd4geGrwdw==" + } } }, "@firebase/logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.2.6.tgz", - "integrity": "sha512-KIxcUvW/cRGWlzK9Vd2KB864HlUnCfdTH0taHE0sXW5Xl7+W68suaeau1oKNEqmc3l45azkd4NzXTCWZRZdXrw==" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-7oQ+TctqekfgZImWkKuda50JZfkmAKMgh5qY4aR4pwRyqZXuJXN1H/BKkHvN1y0S4XWtF0f/wiCLKHhyi1ppPA==", + "requires": { + "tslib": "^2.1.0" + } }, "@firebase/util": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.1.0.tgz", - "integrity": "sha512-lfuSASuPKNdfebuFR8rjFamMQUPH9iiZHcKS755Rkm/5gRT0qC7BMhCh3ZkHf7NVbplzIc/GhmX2jM+igDRCag==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.4.0.tgz", + "integrity": "sha512-Qn58d+DVi1nGn0bA9RV89zkz0zcbt6aUcRdyiuub/SuEvjKYstWmHcHwh1C0qmE1wPf9a3a+AuaRtduaGaRT7A==", "requires": { "tslib": "^2.1.0" - }, - "dependencies": { - "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" - } } }, "@google-cloud/common": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.6.0.tgz", - "integrity": "sha512-aHIFTqJZmeTNO9md8XxV+ywuvXF3xBm5WNmgWeeCK+XN5X+kGW0WEX94wGwj+/MdOnrVf4dL2RvSIt9J5yJG6Q==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.7.2.tgz", + "integrity": "sha512-5Q9f74IbZaY6xAwJSNFy5SrGwbm1j7mpv+6A/r+K2dymjsXBH5UauB0tziaMwWoVVaMq1IQnZF9lgtfqqvxcUg==", "optional": true, "requires": { "@google-cloud/projectify": "^2.0.0", @@ -783,26 +534,26 @@ "ent": "^2.2.0", "extend": "^3.0.2", "google-auth-library": "^7.0.2", - "retry-request": "^4.1.1", + "retry-request": "^4.2.2", "teeny-request": "^7.0.0" } }, "@google-cloud/firestore": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.13.1.tgz", - "integrity": "sha512-LtxboFZQ3MGwy1do8a0ykMJocM+TFgOpZoAihMwW498UDd641DJgJu0Kw0CD0bPpEaYUfhbeAUBq2ZO63DOz7g==", + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-4.15.1.tgz", + "integrity": "sha512-2PWsCkEF1W02QbghSeRsNdYKN1qavrHBP3m72gPDMHQSYrGULOaTi7fSJquQmAtc4iPVB2/x6h80rdLHTATQtA==", "optional": true, "requires": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", - "google-gax": "^2.17.0", + "google-gax": "^2.24.1", "protobufjs": "^6.8.6" } }, "@google-cloud/paginator": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.5.tgz", - "integrity": "sha512-N4Uk4BT1YuskfRhKXBs0n9Lg2YTROZc6IMpkO/8DIHODtm5s3xY8K5vVBo23v/2XulY3azwITQlYWgT4GdLsUw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.6.tgz", + "integrity": "sha512-XCTm/GfQIlc1ZxpNtTSs/mnZxC2cePNhxU3X8EzHXKIJ2JFncmJj2Fcd2IP+gbmZaSZnY0juFxbUCkIeuu/2eQ==", "optional": true, "requires": { "arrify": "^2.0.0", @@ -810,39 +561,37 @@ } }, "@google-cloud/projectify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", - "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", + "integrity": "sha512-+rssMZHnlh0twl122gXY4/aCrk0G1acBqkHFfYddtsqpYXGxA29nj9V5V9SfC+GyOG00l650f6lG9KL+EpFEWQ==", "optional": true }, "@google-cloud/promisify": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.3.tgz", - "integrity": "sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.4.tgz", + "integrity": "sha512-j8yRSSqswWi1QqUGKVEKOG03Q7qOoZP6/h2zN2YO+F5h2+DHU0bSrHCK9Y7lo2DI9fBd8qGAw795sf+3Jva4yA==", "optional": true }, "@google-cloud/storage": { - "version": "5.8.5", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.8.5.tgz", - "integrity": "sha512-i0gB9CRwQeOBYP7xuvn14M40LhHCwMjceBjxE4CTvsqL519sVY5yVKxLiAedHWGwUZHJNRa7Q2CmNfkdRwVNPg==", + "version": "5.14.4", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.14.4.tgz", + "integrity": "sha512-CjpGuk+ZZB7b3yMXPQrPb0TMIhXqbDzrGxngeSl2S2fItFp2pZDnYhvFuB0/8S73cA2T/4x3g1tl6PB1OuuaoQ==", "optional": true, "requires": { - "@google-cloud/common": "^3.6.0", + "@google-cloud/common": "^3.7.0", "@google-cloud/paginator": "^3.0.0", "@google-cloud/promisify": "^2.0.0", "arrify": "^2.0.0", "async-retry": "^1.3.1", "compressible": "^2.0.12", - "date-and-time": "^1.0.0", + "date-and-time": "^2.0.0", "duplexify": "^4.0.0", "extend": "^3.0.2", - "gaxios": "^4.0.0", - "gcs-resumable-upload": "^3.1.4", + "gcs-resumable-upload": "^3.3.0", "get-stream": "^6.0.0", "hash-stream-validation": "^0.2.2", "mime": "^2.2.0", "mime-types": "^2.0.8", - "onetime": "^5.1.0", "p-limit": "^3.0.1", "pumpify": "^2.0.0", "snakeize": "^0.1.0", @@ -851,18 +600,18 @@ } }, "@grpc/grpc-js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.4.tgz", - "integrity": "sha512-AxtZcm0mArQhY9z8T3TynCYVEaSKxNCa9mVhVwBCUnsuUEe8Zn94bPYYKVQSLt+hJJ1y0ukr3mUvtWfcATL/IQ==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.3.7.tgz", + "integrity": "sha512-CKQVuwuSPh40tgOkR7c0ZisxYRiN05PcKPW72mQL5y++qd7CwBRoaJZvU5xfXnCJDFBmS3qZGQ71Frx6Ofo2XA==", "optional": true, "requires": { "@types/node": ">=12.12.47" } }, "@grpc/proto-loader": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.4.tgz", - "integrity": "sha512-7xvDvW/vJEcmLUltCUGOgWRPM8Oofv0eCFSVMuKqaqWJaXSzmB+m9hiyqe34QofAl4WAzIKUZZlinIF9FOHyTQ==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.5.tgz", + "integrity": "sha512-GZdzyVQI1Bln/kCzIYgTKu+rQJ5dno0gVrfmLe4jqQu7T2e7svSwJzpCBqVU5hhBSJP3peuPjOMWsj5GR61YmQ==", "optional": true, "requires": { "@types/long": "^4.0.1", @@ -872,48 +621,6 @@ "yargs": "^16.1.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "optional": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "optional": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "optional": true - }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -970,6 +677,33 @@ "esprima": "^4.0.0" } }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1007,15 +741,6 @@ "tar": "^6.1.0" }, "dependencies": { - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -1024,42 +749,42 @@ "requires": { "lru-cache": "^6.0.0" } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true } } }, "@microsoft/api-extractor": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.18.4.tgz", - "integrity": "sha512-Wx45VuIAu09Pk9Qwzt0I57OX31BaWO2r6+mfSXqYFsJjYTqwUkdFh92G1GKYgvuR9oF/ai7w10wrFpx5WZYbGg==", + "version": "7.18.11", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.18.11.tgz", + "integrity": "sha512-WfN5MZry4TrF60OOcGadFDsGECF9JNKNT+8P/8crYAumAYQRitI2cUiQRlCWrgmFgCWNezsNZeI/2BggdnUqcg==", "dev": true, "requires": { - "@microsoft/api-extractor-model": "7.13.4", + "@microsoft/api-extractor-model": "7.13.9", "@microsoft/tsdoc": "0.13.2", "@microsoft/tsdoc-config": "~0.15.2", - "@rushstack/node-core-library": "3.39.1", - "@rushstack/rig-package": "0.2.13", - "@rushstack/ts-command-line": "4.8.1", + "@rushstack/node-core-library": "3.41.0", + "@rushstack/rig-package": "0.3.1", + "@rushstack/ts-command-line": "4.9.1", "colors": "~1.2.1", "lodash": "~4.17.15", "resolve": "~1.17.0", "semver": "~7.3.0", "source-map": "~0.6.1", - "typescript": "~4.3.5" + "typescript": "~4.4.2" }, "dependencies": { + "@microsoft/tsdoc": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", + "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", + "dev": true + }, "@rushstack/node-core-library": { - "version": "3.39.1", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.39.1.tgz", - "integrity": "sha512-HHgMEHZTXQ3NjpQzWd5+fSt2Eod9yFwj6qBPbaeaNtDNkOL8wbLoxVimQNtcH0Qhn4wxF5u2NTDNFsxf2yd1jw==", + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.41.0.tgz", + "integrity": "sha512-JxdmqR+SHU04jTDaZhltMZL3/XTz2ZZM47DTN+FSPUGUVp6WmxLlvJnT5FoHrOZWUjL/FoIlZUdUPTSXjTjIcg==", "dev": true, "requires": { - "@types/node": "10.17.13", + "@types/node": "12.20.24", "colors": "~1.2.1", "fs-extra": "~7.0.1", "import-lazy": "~4.0.0", @@ -1070,21 +795,24 @@ "z-schema": "~3.18.3" } }, - "@types/node": { - "version": "10.17.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", - "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "@rushstack/ts-command-line": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.9.1.tgz", + "integrity": "sha512-zzoWB6OqVbMjnxlxbAUqbZqDWITUSHqwFCx7JbH5CVrjR9kcsB4NeWkN1I8GcR92beiOGvO3yPlB2NRo5Ugh+A==", "dev": true, "requires": { - "yallist": "^4.0.0" + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "colors": "~1.2.1", + "string-argv": "~0.3.1" } }, + "@types/node": { + "version": "12.20.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.24.tgz", + "integrity": "sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==", + "dev": true + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -1095,34 +823,68 @@ } }, "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz", + "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", "dev": true } } }, "@microsoft/api-extractor-model": { - "version": "7.13.4", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.13.4.tgz", - "integrity": "sha512-NYaR3hJinh089/Gkee8fvmEFf9zKkoUvNxgkqUlKBCDXH2+Ou4tNDuL8G6zjhKBPicHkp2VcL8l7q9H6txUkjQ==", + "version": "7.13.9", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.13.9.tgz", + "integrity": "sha512-t/XKTr8MlHRWgDr1fkyCzTQRR5XICf/WzIFs8yw1JLU8Olw99M3by4/dtpOZNskfqoW+J8NwOxovduU2csi4Ww==", "dev": true, "requires": { "@microsoft/tsdoc": "0.13.2", "@microsoft/tsdoc-config": "~0.15.2", - "@rushstack/node-core-library": "3.39.1" + "@rushstack/node-core-library": "3.41.0" + }, + "dependencies": { + "@microsoft/tsdoc": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", + "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", + "dev": true + }, + "@rushstack/node-core-library": { + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.41.0.tgz", + "integrity": "sha512-JxdmqR+SHU04jTDaZhltMZL3/XTz2ZZM47DTN+FSPUGUVp6WmxLlvJnT5FoHrOZWUjL/FoIlZUdUPTSXjTjIcg==", + "dev": true, + "requires": { + "@types/node": "12.20.24", + "colors": "~1.2.1", + "fs-extra": "~7.0.1", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.17.0", + "semver": "~7.3.0", + "timsort": "~0.3.0", + "z-schema": "~3.18.3" + } + }, + "@types/node": { + "version": "12.20.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.24.tgz", + "integrity": "sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } } }, "@microsoft/tsdoc": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", - "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", + "version": "0.12.24", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", + "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", "dev": true }, "@microsoft/tsdoc-config": { @@ -1137,17 +899,11 @@ "resolve": "~1.19.0" }, "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } + "@microsoft/tsdoc": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz", + "integrity": "sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==", + "dev": true }, "resolve": { "version": "1.19.0", @@ -1162,28 +918,28 @@ } }, "@nodelib/fs.scandir": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", - "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "requires": { - "@nodelib/fs.stat": "2.0.4", + "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "@nodelib/fs.stat": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true }, "@nodelib/fs.walk": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", - "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "requires": { - "@nodelib/fs.scandir": "2.1.4", + "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, @@ -1257,9 +1013,9 @@ "optional": true }, "@rushstack/node-core-library": { - "version": "3.39.1", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.39.1.tgz", - "integrity": "sha512-HHgMEHZTXQ3NjpQzWd5+fSt2Eod9yFwj6qBPbaeaNtDNkOL8wbLoxVimQNtcH0Qhn4wxF5u2NTDNFsxf2yd1jw==", + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", + "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", "dev": true, "requires": { "@types/node": "10.17.13", @@ -1279,15 +1035,6 @@ "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", "dev": true }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -1296,19 +1043,13 @@ "requires": { "lru-cache": "^6.0.0" } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true } } }, "@rushstack/rig-package": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.2.13.tgz", - "integrity": "sha512-qQMAFKvfb2ooaWU9DrGIK9d8QfyHy/HiuITJbWenlKgzcDXQvQgEduk57YF4Y7LLasDJ5ZzLaaXwlfX8qCRe5Q==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.3.1.tgz", + "integrity": "sha512-DXQmrPWOCNoE2zPzHCShE1y47FlgbAg48wpaY058Qo/yKDzL0GlEGf5Ra2NIt22pMcp0R/HHh+kZGbqTnF4CrA==", "dev": true, "requires": { "resolve": "~1.17.0", @@ -1316,9 +1057,9 @@ } }, "@rushstack/ts-command-line": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.8.1.tgz", - "integrity": "sha512-rmxvYdCNRbyRs+DYAPye3g6lkCkWHleqO40K8UPvUAzFqEuj6+YCVssBiOmrUDCoM5gaegSNT0wFDYhz24DWtw==", + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.7.8.tgz", + "integrity": "sha512-8ghIWhkph7NnLCMDJtthpsb7TMOsVGXVDvmxjE/CeklTqjbbUFBjGXizJfpbEkRQTELuZQ2+vGn7sGwIWKN2uA==", "dev": true, "requires": { "@types/argparse": "1.0.38", @@ -1337,9 +1078,9 @@ } }, "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" @@ -1363,9 +1104,9 @@ "dev": true }, "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "optional": true }, "@tsconfig/node10": { @@ -1408,9 +1149,9 @@ } }, "@types/bluebird": { - "version": "3.5.35", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.35.tgz", - "integrity": "sha512-2WeeXK7BuQo7yPI4WGOBum90SzF/f8rqlvpaXx4rjeTmNssGRDHWf7fgDUH90xMB3sUOu716fUK5d+OVx0+ncQ==", + "version": "3.5.36", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", + "integrity": "sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==", "dev": true }, "@types/body-parser": { @@ -1429,9 +1170,9 @@ "dev": true }, "@types/chai": { - "version": "4.2.21", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz", - "integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==", + "version": "4.2.22", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz", + "integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==", "dev": true }, "@types/chai-as-promised": { @@ -1496,15 +1237,15 @@ } }, "@types/firebase-token-generator": { - "version": "2.0.28", - "resolved": "http://registry.npmjs.org/@types/firebase-token-generator/-/firebase-token-generator-2.0.28.tgz", - "integrity": "sha1-Z1VIHZMk4mt6XItFXWgUg3aCw5Y=", + "version": "2.0.29", + "resolved": "https://registry.npmjs.org/@types/firebase-token-generator/-/firebase-token-generator-2.0.29.tgz", + "integrity": "sha512-IHFsMicKhaDYFvJmTokF8wLtIGUZVgh1Tie0jYOcgnwHFT1es+hoj2d+SlVx63q91fRHDP2veTUq1yIkqEOT/A==", "dev": true }, "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, "@types/jsonwebtoken": { @@ -1517,9 +1258,9 @@ } }, "@types/lodash": { - "version": "4.14.171", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.171.tgz", - "integrity": "sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg==", + "version": "4.14.175", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.175.tgz", + "integrity": "sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw==", "dev": true }, "@types/long": { @@ -1534,21 +1275,21 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", "dev": true }, "@types/minimist": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, "@types/mocha": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.2.tgz", - "integrity": "sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", + "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", "dev": true }, "@types/nock": { @@ -1561,9 +1302,9 @@ } }, "@types/node": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz", - "integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA==" + "version": "16.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.2.tgz", + "integrity": "sha512-zCclL4/rx+W5SQTzFs9wyvvyCwoK9QtBpratqz2IYJ3O8Umrn0m3nsTv0wQBk9sRGpvUe9CwPDrQFB10f1FIjQ==" }, "@types/qs": { "version": "6.9.7", @@ -1576,9 +1317,9 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "@types/request": { - "version": "2.48.6", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.6.tgz", - "integrity": "sha512-vrZaV3Ij7j/l/3hz6OttZFtpRCu7zlq7XgkYHJP6FwVEAZkGQ095WqyJV08/GlW9eyXKVcp/xmtruHm8eHpw1g==", + "version": "2.48.7", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.7.tgz", + "integrity": "sha512-GWP9AZW7foLd4YQxyFZDBepl0lPsWLMEXDZUjQ/c1gqVPDPECrRZyEzuhJdnPWioFCq3Tv0qoGpMD6U+ygd4ZA==", "dev": true, "requires": { "@types/caseless": "*", @@ -1588,9 +1329,9 @@ } }, "@types/request-promise": { - "version": "4.1.47", - "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.47.tgz", - "integrity": "sha512-eRSZhAS8SMsrWOM8vbhxFGVZhTbWSJvaRKyufJTdIf4gscUouQvOBlfotPSPHbMR3S7kfkyKbhb1SWPmQdy3KQ==", + "version": "4.1.48", + "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.48.tgz", + "integrity": "sha512-sLsfxfwP5G3E3U64QXxKwA6ctsxZ7uKyl4I28pMj3JvV+ztWECRns73GL71KMOOJME5u1A5Vs5dkBqyiR1Zcnw==", "dev": true, "requires": { "@types/bluebird": "*", @@ -1607,23 +1348,12 @@ } }, "@types/sinon": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.2.tgz", - "integrity": "sha512-BHn8Bpkapj8Wdfxvh2jWIUoaYB/9/XhsL0oOvBfRagJtKlSl9NWPcFOz2lRukI9szwGxFtYZCTejJSqsGDbdmw==", + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.4.tgz", + "integrity": "sha512-fOYjrxQv8zJsqOY6V6ecP4eZhQBxtY80X0er1VVnUIAIZo74jHm8e1vguG5Yt4Iv8W2Wr7TgibB8MfRe32k9pA==", "dev": true, "requires": { "@sinonjs/fake-timers": "^7.1.0" - }, - "dependencies": { - "@sinonjs/fake-timers": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", - "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - } } }, "@types/sinon-chai": { @@ -1732,9 +1462,15 @@ "dev": true }, "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, "agent-base": { @@ -1821,9 +1557,9 @@ } }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -1831,21 +1567,6 @@ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { "color-convert": "^2.0.1" - }, - "dependencies": { - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - } } }, "ansi-wrap": { @@ -1870,67 +1591,130 @@ "normalize-path": "^2.1.1" }, "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } - } - } - }, - "api-extractor-model-me": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/api-extractor-model-me/-/api-extractor-model-me-0.1.1.tgz", - "integrity": "sha512-Ez801ZMADfkseOWNRFquvyQYDm3D9McpxfkKMWL6JFCGcpub0miJ+TFNphIR1nSZbrsxz3kIeOovNMY4VlL6Bw==", - "dev": true, - "requires": { - "@microsoft/tsdoc": "0.12.24", - "@rushstack/node-core-library": "3.36.0" - }, - "dependencies": { - "@microsoft/tsdoc": { - "version": "0.12.24", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.12.24.tgz", - "integrity": "sha512-Mfmij13RUTmHEMi9vRUhMXD7rnGR2VvxeNYtaGtaJ4redwwjT4UXYJ+nzmVJF7hhd4pn/Fx5sncDKxMVFJSWPg==", - "dev": true }, - "@rushstack/node-core-library": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-3.36.0.tgz", - "integrity": "sha512-bID2vzXpg8zweXdXgQkKToEdZwVrVCN9vE9viTRk58gqzYaTlz4fMId6V3ZfpXN6H0d319uGi2KDlm+lUEeqCg==", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "@types/node": "10.17.13", - "colors": "~1.2.1", - "fs-extra": "~7.0.1", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.17.0", - "semver": "~7.3.0", - "timsort": "~0.3.0", - "z-schema": "~3.18.3" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "@types/node": { - "version": "10.17.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", - "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", - "dev": true + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "lru-cache": "^6.0.0" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } }, + "api-extractor-model-me": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/api-extractor-model-me/-/api-extractor-model-me-0.1.1.tgz", + "integrity": "sha512-Ez801ZMADfkseOWNRFquvyQYDm3D9McpxfkKMWL6JFCGcpub0miJ+TFNphIR1nSZbrsxz3kIeOovNMY4VlL6Bw==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "0.12.24", + "@rushstack/node-core-library": "3.36.0" + } + }, "append-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", @@ -1962,9 +1746,9 @@ "dev": true }, "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", "dev": true, "requires": { "delegates": "^1.0.0", @@ -2013,6 +1797,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -2194,12 +1979,12 @@ "dev": true }, "async-retry": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz", - "integrity": "sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", "optional": true, "requires": { - "retry": "0.12.0" + "retry": "0.13.1" } }, "async-settle": { @@ -2376,32 +2161,12 @@ } }, "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "fill-range": "^7.0.1" } }, "browser-stdout": { @@ -2411,16 +2176,16 @@ "dev": true }, "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.17.1.tgz", + "integrity": "sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", + "caniuse-lite": "^1.0.30001259", + "electron-to-chromium": "^1.3.846", "escalade": "^3.1.1", - "node-releases": "^1.1.71" + "nanocolors": "^0.1.5", + "node-releases": "^1.1.76" } }, "buffer": { @@ -2446,9 +2211,9 @@ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, "cache-base": { @@ -2503,9 +2268,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001228", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz", - "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==", + "version": "1.0.30001261", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz", + "integrity": "sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA==", "dev": true }, "caseless": { @@ -2538,9 +2303,9 @@ } }, "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -2590,6 +2355,45 @@ "upath": "^1.1.1" }, "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -2610,6 +2414,34 @@ } } } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, @@ -2667,7 +2499,6 @@ "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -2762,18 +2593,25 @@ "object-visit": "^1.0.0" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, "colors": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz", @@ -2897,9 +2735,9 @@ "dev": true }, "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -2938,9 +2776,9 @@ } }, "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, "create-require": { @@ -3003,15 +2841,15 @@ } }, "date-and-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-1.0.1.tgz", - "integrity": "sha512-7u+uNfnjWkX+YFQfivvW24TjaJG6ahvTrfw1auq7KlC7osuGcZBIWGBvB9UcENjH6JnLVhMqlRripk1dSHjAUA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-2.0.0.tgz", + "integrity": "sha512-HJSzj25iPm8E01nt+rSmCIlwjsmjvKfUivG/kXBglpymcHF1FolWAqWwTEV4FvN1Lx5UjPf0J1W4H8yQsVBfFg==", "optional": true }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { "ms": "2.1.2" } @@ -3038,9 +2876,9 @@ } }, "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, "default-compare": { @@ -3147,26 +2985,6 @@ "p-map": "^4.0.0", "rimraf": "^3.0.2", "slash": "^3.0.0" - }, - "dependencies": { - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } } }, "delayed-stream": { @@ -3214,14 +3032,6 @@ "dev": true, "requires": { "path-type": "^4.0.0" - }, - "dependencies": { - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - } } }, "doctrine": { @@ -3233,12 +3043,6 @@ "esutils": "^2.0.2" } }, - "dom-storage": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/dom-storage/-/dom-storage-2.1.0.tgz", - "integrity": "sha512-g6RpyWXzl0RR6OTElHKBl7nwnK87GUyZMYC7JWsB/IA73vpqK2K6LT39x4VepLxlSsWBFrPVLnsSR5Jyty0+2Q==", - "dev": true - }, "dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -3249,9 +3053,9 @@ } }, "duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", "optional": true, "requires": { "end-of-stream": "^1.4.1", @@ -3289,9 +3093,9 @@ } }, "electron-to-chromium": { - "version": "1.3.736", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.736.tgz", - "integrity": "sha512-DY8dA7gR51MSo66DqitEQoUMQ0Z+A2DSXFi7tK304bdTVqczCAfUuyQw6Wdg8hIoo5zIxkU1L24RQtUce1Ioig==", + "version": "1.3.854", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.854.tgz", + "integrity": "sha512-00/IIC1mFPkq32MhUJyLdcTp7+wsKK2G3Sb65GSas9FKJQGYkDcZ4GwJkkxf5YyM3ETvl6n+toV8OmtXl4IA/g==", "dev": true }, "emoji-regex": { @@ -3323,22 +3127,24 @@ } }, "es-abstract": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", - "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", + "version": "1.18.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.7.tgz", + "integrity": "sha512-uFG1gyVX91tZIiDWNmPsL8XNpiCk/6tkB7MZphoSJflS4w+KgWyQ2gjCVDnsPxFAo9WjRXG3eqONNYdfbJjAtw==", "dev": true, "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", "has": "^1.0.3", "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.10.3", + "is-regex": "^1.1.4", + "is-string": "^1.0.7", + "object-inspect": "^1.11.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", "string.prototype.trimend": "^1.0.4", @@ -3540,6 +3346,12 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, "js-yaml": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", @@ -3624,7 +3436,8 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, "esquery": { "version": "1.4.0", @@ -3738,12 +3551,12 @@ } }, "ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", "dev": true, "requires": { - "type": "^2.0.0" + "type": "^2.5.0" }, "dependencies": { "type": { @@ -3789,6 +3602,17 @@ "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" + }, + "dependencies": { + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + } } }, "extglob": { @@ -3880,70 +3704,16 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", - "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.0", + "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.2", - "picomatch": "^2.2.1" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "dependencies": { - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - } - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } + "micromatch": "^4.0.4" } }, "fast-json-stable-stringify": { @@ -3965,18 +3735,18 @@ "optional": true }, "fastq": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", - "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, "requires": { "reusify": "^1.0.4" } }, "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "requires": { "websocket-driver": ">=0.5.1" } @@ -4007,32 +3777,18 @@ "optional": true }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "to-regex-range": "^5.0.1" } }, "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, "requires": { "commondir": "^1.0.1", @@ -4060,6 +3816,111 @@ "is-glob": "^4.0.0", "micromatch": "^3.0.4", "resolve-dir": "^1.0.1" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } } }, "fined": { @@ -4077,7 +3938,7 @@ }, "firebase-token-generator": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/firebase-token-generator/-/firebase-token-generator-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/firebase-token-generator/-/firebase-token-generator-2.0.0.tgz", "integrity": "sha1-l2fXWewTq9yZuhFf1eqZ2Lk9EgY=", "dev": true }, @@ -4375,22 +4236,22 @@ } }, "gaxios": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.0.tgz", - "integrity": "sha512-pHplNbslpwCLMyII/lHPWFQbJWOX0B3R1hwBEOvzYi1GmdKZruuEHK4N9V6f7tf1EaPYyF80mui1+344p6SmLg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.2.tgz", + "integrity": "sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q==", "optional": true, "requires": { "abort-controller": "^3.0.0", "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" + "node-fetch": "^2.6.1" } }, "gcp-metadata": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.2.1.tgz", - "integrity": "sha512-tSk+REe5iq/N+K+SK1XjZJUrFPuDqGZVzCy2vocIHIGmPlTGsa8owXMJwGkrXr73NO0AzhPW4MF2DEHz7P2AVw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", "optional": true, "requires": { "gaxios": "^4.0.0", @@ -4398,9 +4259,9 @@ } }, "gcs-resumable-upload": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.4.tgz", - "integrity": "sha512-5dyDfHrrVcIskiw/cPssVD4HRiwoHjhk1Nd6h5W3pQ/qffDvhfy4oNCr1f3ZXFPwTnxkCbibsB+73oOM+NvmJQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.3.1.tgz", + "integrity": "sha512-WyC0i4VkslIdrdmeM5PNuGzANALLXTG5RoHb08OE30gYT+FEvCDPiA8KOjV2s1wOu9ngEW4+IuzBjtP/ni7UdQ==", "optional": true, "requires": { "abort-controller": "^3.0.0", @@ -4458,6 +4319,16 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "optional": true }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -4474,9 +4345,9 @@ } }, "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -4647,9 +4518,9 @@ } }, "globby": { - "version": "11.0.3", - "resolved": "http://registry.npmjs.org/globby/-/globby-11.0.3.tgz", - "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", "dev": true, "requires": { "array-union": "^2.1.0", @@ -4658,14 +4529,6 @@ "ignore": "^5.1.4", "merge2": "^1.3.0", "slash": "^3.0.0" - }, - "dependencies": { - "ignore": { - "version": "5.1.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true - } } }, "glogg": { @@ -4678,9 +4541,9 @@ } }, "google-auth-library": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.1.1.tgz", - "integrity": "sha512-+Q1linq/To3DYLyPz4UTEkQ0v5EOXadMM/S+taLV3W9611hq9zqg8kgGApqbTQnggtwdO9yU1y2YT7+83wdTRg==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.10.0.tgz", + "integrity": "sha512-ICsqaU+lxMHVlDUzMrfVIEqnARw2AwBiZ/2KnNM6BcTf9Nott+Af87DTIzmlnW865p3REUP2MVL0xkPC3a61aQ==", "optional": true, "requires": { "arrify": "^2.0.0", @@ -4695,9 +4558,9 @@ } }, "google-gax": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.17.1.tgz", - "integrity": "sha512-CoR7OYuEzIDt3mp7cLYL+oGPmYdImS1WEwIvjF0zk0LhEBBmvRjWHTpBwazLGsT8hz+6zPh/4fjIjNaUxzIvzg==", + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-2.26.0.tgz", + "integrity": "sha512-D/8fjTydl9p3ejxuW3ZVHVZqKzz6zYaz5MMEucijsJonJ4RHqWAzHFKZMKSc7yyUiTEBGqG7nU2S8NUPUUYDEA==", "optional": true, "requires": { "@grpc/grpc-js": "~1.3.0", @@ -4706,136 +4569,28 @@ "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "fast-text-encoding": "^1.0.3", - "google-auth-library": "^7.3.0", + "google-auth-library": "^7.6.1", "is-stream-ended": "^0.1.4", "node-fetch": "^2.6.1", "object-hash": "^2.1.1", - "protobufjs": "^6.10.2", + "proto3-json-serializer": "^0.1.1", + "protobufjs": "6.11.2", "retry-request": "^4.0.0" - }, - "dependencies": { - "duplexify": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", - "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", - "optional": true, - "requires": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, - "gaxios": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.0.tgz", - "integrity": "sha512-pHplNbslpwCLMyII/lHPWFQbJWOX0B3R1hwBEOvzYi1GmdKZruuEHK4N9V6f7tf1EaPYyF80mui1+344p6SmLg==", - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.3.0" - } - }, - "gcp-metadata": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.0.tgz", - "integrity": "sha512-L9XQUpvKJCM76YRSmcxrR4mFPzPGsgZUH+GgHMxAET8qc6+BhRJq63RLhWakgEO2KKVgeSDVfyiNjkGSADwNTA==", - "optional": true, - "requires": { - "gaxios": "^4.0.0", - "json-bigint": "^1.0.0" - } - }, - "google-auth-library": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.3.0.tgz", - "integrity": "sha512-MPeeMlnsYnoiiVFMwX3hgaS684aiXrSqKoDP+xL4Ejg4Z0qLvIeg4XsaChemyFI8ZUO7ApwDAzNtgmhWSDNh5w==", - "optional": true, - "requires": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^4.0.0", - "gcp-metadata": "^4.2.0", - "gtoken": "^5.0.4", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - } - }, - "google-p12-pem": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.0.tgz", - "integrity": "sha512-JUtEHXL4DY/N+xhlm7TC3qL797RPAtk0ZGXNs3/gWyiDHYoA/8Rjes0pztkda+sZv4ej1EoO2KhWgW5V9KTrSQ==", - "optional": true, - "requires": { - "node-forge": "^0.10.0" - } - }, - "gtoken": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.0.tgz", - "integrity": "sha512-mCcISYiaRZrJpfqOs0QWa6lfEM/C1V9ASkzFmuz43XBb5s1Vynh+CZy1ECeeJXVGx2PRByjYzb4Y4/zr1byr0w==", - "optional": true, - "requires": { - "gaxios": "^4.0.0", - "google-p12-pem": "^3.0.3", - "jws": "^4.0.0" - } - }, - "json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "optional": true, - "requires": { - "bignumber.js": "^9.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "optional": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "optional": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - } } }, "google-p12-pem": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.3.tgz", - "integrity": "sha512-wS0ek4ZtFx/ACKYF3JhyGe5kzH7pgiQ7J5otlumqR9psmWMYc+U9cErKlCYVYHoUaidXHdZ2xbo34kB+S+24hA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.2.tgz", + "integrity": "sha512-tjf3IQIt7tWCDsa0ofDQ1qqSCNzahXDxdAGJDbruWqu3eCg5CKLYKN+hi0s6lfvzYZ1GDVr+oDF9OOWlDSdf0A==", "optional": true, "requires": { "node-forge": "^0.10.0" } }, "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, "growl": { "version": "1.10.5", @@ -4844,9 +4599,9 @@ "dev": true }, "gtoken": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.2.1.tgz", - "integrity": "sha512-OY0BfPKe3QnMsY9MzTHTSKn+Vl2l1CcLe6BwDEQj00mbbkl5nyQ/7EUREstg4fQNZ8iYE7br4JJ7TdKeDOPWmw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.1.tgz", + "integrity": "sha512-yqOREjzLHcbzz1UrQoxhBtpk8KjrVhuqPE7od1K2uhyxG2BHjKZetlbLw/SPZak/QqTIQW+addS+EcjqQsZbwQ==", "optional": true, "requires": { "gaxios": "^4.0.0", @@ -5015,48 +4770,6 @@ "lodash.template": "^4.5.0", "map-stream": "0.0.7", "through2": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - } } }, "gulp-typescript": { @@ -5166,6 +4879,15 @@ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -5193,6 +4915,26 @@ "kind-of": "^4.0.0" }, "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", @@ -5266,12 +5008,12 @@ "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" }, "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "optional": true, "requires": { - "@tootallnate/once": "1", + "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } @@ -5312,9 +5054,15 @@ "dev": true }, "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", "dev": true }, "import-fresh": { @@ -5386,6 +5134,17 @@ "through": "^2.3.6" } }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -5435,10 +5194,13 @@ "dev": true }, "is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", - "dev": true + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } }, "is-binary-path": { "version": "1.0.1", @@ -5450,12 +5212,13 @@ } }, "is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, "requires": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" } }, "is-buffer": { @@ -5465,15 +5228,15 @@ "dev": true }, "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", "dev": true }, "is-core-module": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", - "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.7.0.tgz", + "integrity": "sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==", "dev": true, "requires": { "has": "^1.0.3" @@ -5500,10 +5263,13 @@ } }, "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "dev": true + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-descriptor": { "version": "0.1.6", @@ -5542,9 +5308,9 @@ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -5563,31 +5329,20 @@ "dev": true }, "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "has-tostringtag": "^1.0.0" } }, - "is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", - "dev": true - }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -5622,13 +5377,13 @@ } }, "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", "dev": true, "requires": { "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" + "has-tostringtag": "^1.0.0" } }, "is-relative": { @@ -5641,9 +5396,9 @@ } }, "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, "is-stream-ended": { "version": "0.1.4", @@ -5652,10 +5407,13 @@ "optional": true }, "is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", - "dev": true + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } }, "is-symbol": { "version": "1.0.4", @@ -5723,9 +5481,9 @@ "dev": true }, "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.1.tgz", + "integrity": "sha512-GvCYYTxaCPqwMjobtVcVKvSHtAGe48MNhGjpK8LtVF8K0ISX7hCKl85LgtuaSneWVyQmaGcW3iXVV3GaZSLpmQ==", "dev": true }, "istanbul-lib-hook": { @@ -5775,21 +5533,21 @@ "which": "^2.0.1" } }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5831,23 +5589,6 @@ "istanbul-lib-coverage": "^3.0.0", "make-dir": "^3.0.0", "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } } }, "istanbul-lib-source-maps": { @@ -5859,14 +5600,6 @@ "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, "istanbul-reports": { @@ -6040,6 +5773,50 @@ "verror": "1.10.0" } }, + "jszip": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -6073,16 +5850,6 @@ "jose": "^2.0.5", "limiter": "^1.1.5", "lru-memoizer": "^2.1.4" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "requires": { - "ms": "2.1.2" - } - } } }, "jws": { @@ -6180,6 +5947,15 @@ "type-check": "~0.3.2" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, "liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -6215,12 +5991,12 @@ } }, "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" } }, "lodash": { @@ -6345,13 +6121,6 @@ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } } }, "lru-memoizer": { @@ -6435,6 +6204,58 @@ "stack-trace": "0.0.10" }, "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, "findup-sync": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", @@ -6455,6 +6276,57 @@ "requires": { "is-extglob": "^2.1.0" } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, @@ -6471,24 +6343,13 @@ "dev": true }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "braces": "^3.0.1", + "picomatch": "^2.2.3" } }, "mime": { @@ -6498,22 +6359,31 @@ "optional": true }, "mime-db": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", - "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==" + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "optional": true }, "mime-types": { - "version": "2.1.31", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", - "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz", + "integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==", "requires": { - "mime-db": "1.48.0" + "mime-db": "1.49.0" + }, + "dependencies": { + "mime-db": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" + } } }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true }, "minimatch": { "version": "3.0.4", @@ -6531,20 +6401,12 @@ "dev": true }, "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", "dev": true, "requires": { "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } } }, "minizlib": { @@ -6555,14 +6417,6 @@ "requires": { "minipass": "^3.0.0", "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } } }, "mixin-deep": { @@ -6586,6 +6440,12 @@ } } }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, "mocha": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", @@ -6641,15 +6501,6 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, "chokidar": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", @@ -6666,21 +6517,29 @@ "readdirp": "~3.5.0" } }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6721,36 +6580,12 @@ "binary-extensions": "^2.0.0" } }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6775,15 +6610,6 @@ "has-flag": "^4.0.0" } }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6793,22 +6619,6 @@ "isexe": "^2.0.0" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -6874,12 +6684,18 @@ } }, "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", "dev": true, "optional": true }, + "nanocolors": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/nanocolors/-/nanocolors-0.1.12.tgz", + "integrity": "sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==", + "dev": true + }, "nanoid": { "version": "3.1.20", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", @@ -6934,12 +6750,23 @@ "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } } }, "nock": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.1.1.tgz", - "integrity": "sha512-YKTR9MjfK3kS9/l4nuTxyYm30cgOExRHzkLNhL8nhEUyU4f8Za/dRxOqjhVT1vGs0svWo3dDnJTUX1qxYeWy5w==", + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.1.3.tgz", + "integrity": "sha512-YKj0rKQWMGiiIO+Y65Ut8OEgYM3PplLU2+GAhnPmqZdBd6z5IskgdBqWmjzA6lH3RF0S2a3wiAlrMOF5Iv2Jeg==", "dev": true, "requires": { "debug": "^4.1.0", @@ -6955,9 +6782,12 @@ "dev": true }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", + "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "requires": { + "whatwg-url": "^5.0.0" + } }, "node-forge": { "version": "0.10.0", @@ -6974,9 +6804,9 @@ } }, "node-releases": { - "version": "1.1.72", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", - "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "version": "1.1.76", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.76.tgz", + "integrity": "sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA==", "dev": true }, "node-version": { @@ -7224,21 +7054,6 @@ "yargs": "^15.0.2" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -7256,21 +7071,6 @@ "wrap-ansi": "^6.2.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -7281,17 +7081,41 @@ "path-exists": "^4.0.0" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } }, - "is-fullwidth-code-point": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-map": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } }, "path-exists": { "version": "4.0.0", @@ -7311,35 +7135,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -7357,6 +7152,12 @@ "strip-ansi": "^6.0.0" } }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -7438,9 +7239,9 @@ "optional": true }, "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", "dev": true }, "object-keys": { @@ -7523,6 +7324,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -7598,37 +7400,26 @@ "dev": true }, "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } + "p-limit": "^3.0.2" } }, "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "requires": { "aggregate-error": "^3.0.0" @@ -7637,7 +7428,8 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "package-hash": { "version": "4.0.0", @@ -7651,6 +7443,12 @@ "release-zalgo": "^1.0.0" } }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7715,7 +7513,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, @@ -7764,15 +7562,10 @@ } }, "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true }, "pathval": { "version": "1.1.1", @@ -7838,6 +7631,33 @@ "path-exists": "^4.0.0" } }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7872,7 +7692,7 @@ }, "pretty-hrtime": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, @@ -7909,6 +7729,12 @@ "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true }, + "proto3-json-serializer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.4.tgz", + "integrity": "sha512-bFzdsKU/zaTobWrRxRniMZIzzcgKYlmBWL1gAcTXZ2M7TQTGPI0JoYYs6bN7tpWj59ZCfwg7Ii/A2e8BbQGYnQ==", + "optional": true + }, "protobufjs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz", @@ -7998,6 +7824,19 @@ "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", "path-type": "^1.0.0" + }, + "dependencies": { + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + } } }, "read-pkg-up": { @@ -8031,6 +7870,99 @@ "readable-stream": "^2.0.2" }, "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -8060,6 +7992,16 @@ "requires": { "safe-buffer": "~5.1.0" } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, @@ -8083,9 +8025,9 @@ } }, "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, "release-zalgo": { @@ -8210,27 +8152,15 @@ "request-promise-core": "1.1.4", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" - }, - "dependencies": { - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } + } + }, + "request-promise-core": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", + "dev": true, + "requires": { + "lodash": "^4.17.19" } }, "require-directory": { @@ -8301,18 +8231,19 @@ "dev": true }, "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "optional": true }, "retry-request": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.3.tgz", - "integrity": "sha512-QnRZUpuPNgX0+D1xVxul6DbJ9slvo4Rm6iV/dn63e048MvGbUZiKySVt6Tenp04JqmchxjiLltGerOJys7kJYQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.2.2.tgz", + "integrity": "sha512-xA93uxUD/rogV7BV59agW/JHPGXeREMWiZc9jhcwY4YdZ7QOtC7qbomYg0n4wyk2lJhggjvKvhNX8wln/Aldhg==", "optional": true, "requires": { - "debug": "^4.1.1" + "debug": "^4.1.1", + "extend": "^3.0.2" } }, "reusify": { @@ -8472,7 +8403,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -8485,6 +8416,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "selenium-webdriver": { + "version": "4.0.0-rc-1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.0.0-rc-1.tgz", + "integrity": "sha512-bcrwFPRax8fifRP60p7xkWDGSJJoMkPAzufMlk5K2NyLPht/YZzR2WcIk1+3gR8VOCLlst1P2PI+MXACaFzpIw==", + "dev": true, + "requires": { + "jszip": "^3.6.0", + "rimraf": "^3.0.2", + "tmp": "^0.2.1", + "ws": ">=7.4.6" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -8514,6 +8457,12 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -8558,10 +8507,21 @@ "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", + "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" }, "sinon": { "version": "9.2.4", @@ -8577,26 +8537,20 @@ "supports-color": "^7.1.0" }, "dependencies": { + "@sinonjs/fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, @@ -8834,15 +8788,6 @@ "which": "^2.0.1" }, "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8881,9 +8826,9 @@ } }, "spdx-license-ids": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", - "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", + "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", "dev": true }, "split-string": { @@ -8898,7 +8843,8 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true }, "sshpk": { "version": "1.16.1", @@ -8997,13 +8943,13 @@ "dev": true }, "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "string.prototype.padend": { @@ -9046,11 +8992,11 @@ } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } }, "strip-bom": { @@ -9081,14 +9027,6 @@ "dev": true, "requires": { "has-flag": "^4.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - } } }, "sver-compat": { @@ -9154,9 +9092,9 @@ } }, "tar": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.3.tgz", - "integrity": "sha512-3rUqwucgVZXTeyJyL2jqtUau8/8r54SioM1xj3AmTX3HnWQdj2AydfJ2qYYayPyIIznSplcvU9mhBb7dR2XF3w==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -9165,29 +9103,15 @@ "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - } } }, "teeny-request": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.1.0.tgz", - "integrity": "sha512-hPfSc05a7Mf3syqVhSkrVMb844sMiP60MrfGMts3ft6V6UlSkEIGQzgwf0dy1KjdE3FV2lJ5s7QCBFcaoQLA6g==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.1.3.tgz", + "integrity": "sha512-Ew3aoFzgQEatLA5OBIjdr1DWJUaC1xardG+qbPPo5k/y/3fMwXLxpjh5UB5dVfElktLaQbbMs80chkz53ByvSg==", "optional": true, "requires": { - "http-proxy-agent": "^4.0.0", + "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.1", "stream-events": "^1.0.5", @@ -9300,12 +9224,12 @@ "dev": true }, "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "rimraf": "^3.0.0" } }, "to-absolute-glob": { @@ -9357,13 +9281,12 @@ } }, "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "^7.0.0" } }, "to-through": { @@ -9385,10 +9308,15 @@ "punycode": "^2.1.1" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "ts-node": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.0.tgz", - "integrity": "sha512-FstYHtQz6isj8rBtYMN4bZdnXN1vq4HCbqn9vdNQcInRqtB86PePJQIxE6es0PhxKWhj2PHuwbG40H+bxkZPmg==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz", + "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==", "dev": true, "requires": { "@cspotcode/source-map-support": "0.6.1", @@ -9406,15 +9334,9 @@ }, "dependencies": { "acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", - "dev": true - }, - "acorn-walk": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz", - "integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w==", + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", + "integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==", "dev": true }, "diff": { @@ -9425,6 +9347,11 @@ } } }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, "tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", @@ -9499,9 +9426,9 @@ } }, "typescript": { - "version": "3.9.9", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", - "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", "dev": true }, "unbox-primitive": { @@ -9715,6 +9642,14 @@ "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + } } }, "vinyl": { @@ -9847,6 +9782,11 @@ } } }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, "websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", @@ -9862,6 +9802,15 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -9990,18 +9939,18 @@ "typedarray-to-buffer": "^3.1.5" } }, + "ws": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.2.tgz", + "integrity": "sha512-Q6B6H2oc8QY3llc3cB8kVmQ6pnJWVQbP7Q5algTcIxx7YEpc0oU4NBVHlztA7Ekzfhw2r0rPducMUiCGWKQRzw==", + "dev": true + }, "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", "optional": true }, - "xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=", - "dev": true - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -10009,15 +9958,19 @@ "dev": true }, "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz", - "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==", + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz", + "integrity": "sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q==", "dev": true, "requires": { "cliui": "^7.0.2", @@ -10027,87 +9980,12 @@ "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true - } } }, "yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "optional": true + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" }, "yargs-unparser": { "version": "2.0.0", @@ -10141,6 +10019,11 @@ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + }, "z-schema": { "version": "3.18.4", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.18.4.tgz", diff --git a/package.json b/package.json index 8cbcedeef1..cb99b8fc2e 100644 --- a/package.json +++ b/package.json @@ -159,7 +159,7 @@ } }, "dependencies": { - "@firebase/database": "^0.10.0", + "@firebase/database-compat": "^0.1.1", "@firebase/database-types": "^0.7.2", "@types/node": ">=12.12.47", "dicer": "^0.3.0", @@ -173,15 +173,15 @@ }, "devDependencies": { "@firebase/api-documenter": "^0.1.2", - "@firebase/app": "^0.6.21", - "@firebase/auth": "^0.16.5", + "@firebase/app-compat": "^0.1.2", + "@firebase/auth-compat": "^0.1.3", "@firebase/auth-types": "^0.10.3", "@microsoft/api-extractor": "^7.11.2", "@types/bcrypt": "^5.0.0", "@types/chai": "^4.0.0", "@types/chai-as-promised": "^7.1.0", "@types/firebase-token-generator": "^2.0.28", - "@types/jsonwebtoken": "^8.5.0", + "@types/jsonwebtoken": "8.5.1", "@types/lodash": "^4.14.104", "@types/minimist": "^1.2.0", "@types/mocha": "^8.2.2", diff --git a/src/app-check/token-generator.ts b/src/app-check/token-generator.ts index 97ebb3fb40..adc6898786 100644 --- a/src/app-check/token-generator.ts +++ b/src/app-check/token-generator.ts @@ -62,7 +62,7 @@ export class AppCheckTokenGenerator { * * @param appId The Application ID to use for the generated token. * - * @return A Promise fulfilled with a custom token signed with a service account key + * @returns A Promise fulfilled with a custom token signed with a service account key * that can be exchanged to an App Check token. */ public createCustomToken(appId: string, options?: AppCheckTokenOptions): Promise { @@ -141,7 +141,7 @@ export class AppCheckTokenGenerator { * details from a CryptoSignerError. * * @param err The Error to convert into a FirebaseAppCheckError error - * @return A Firebase App Check error that can be returned to the user. + * @returns A Firebase App Check error that can be returned to the user. */ export function appCheckErrorFromCryptoSignerError(err: Error): Error { if (!(err instanceof CryptoSignerError)) { diff --git a/src/app-check/token-verifier.ts b/src/app-check/token-verifier.ts index a60a3a5bbd..661b805597 100644 --- a/src/app-check/token-verifier.ts +++ b/src/app-check/token-verifier.ts @@ -44,7 +44,7 @@ export class AppCheckTokenVerifier { * Verifies the format and signature of a Firebase App Check token. * * @param token The Firebase Auth JWT token to verify. - * @return A promise fulfilled with the decoded claims of the Firebase App Check token. + * @returns A promise fulfilled with the decoded claims of the Firebase App Check token. */ public verifyToken(token: string): Promise { if (!validator.isString(token)) { diff --git a/src/app/firebase-namespace.ts b/src/app/firebase-namespace.ts index 44716e0cae..642d5ad23c 100644 --- a/src/app/firebase-namespace.ts +++ b/src/app/firebase-namespace.ts @@ -135,7 +135,7 @@ export class FirebaseNamespace { }; // eslint-disable-next-line @typescript-eslint/no-var-requires - return Object.assign(fn, require('@firebase/database')); + return Object.assign(fn, require('@firebase/database-compat/standalone')); } /** diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index f6934be4d7..12909ffb40 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -1369,7 +1369,6 @@ export class OIDCConfig implements OIDCAuthProviderConfig { '"OIDCAuthProviderConfig.responseType.idToken" must be a boolean.', ); } - const code = options.responseType.code; if (typeof code !== 'undefined') { if (!validator.isBoolean(code)) { @@ -1378,7 +1377,6 @@ export class OIDCConfig implements OIDCAuthProviderConfig { '"OIDCAuthProviderConfig.responseType.code" must be a boolean.', ); } - // If code flow is enabled, client secret must be provided. if (code && typeof options.clientSecret === 'undefined') { throw new FirebaseAuthError( diff --git a/src/auth/base-auth.ts b/src/auth/base-auth.ts index da3ed253c8..0bd67e2b65 100644 --- a/src/auth/base-auth.ts +++ b/src/auth/base-auth.ts @@ -20,7 +20,7 @@ import { deepCopy } from '../utils/deep-copy'; import * as validator from '../utils/validator'; import { AbstractAuthRequestHandler, useEmulator } from './auth-api-request'; -import { FirebaseTokenGenerator, EmulatedSigner } from './token-generator'; +import { FirebaseTokenGenerator, EmulatedSigner, handleCryptoSignerError } from './token-generator'; import { FirebaseTokenVerifier, createSessionCookieVerifier, createIdTokenVerifier, DecodedIdToken, @@ -37,7 +37,6 @@ import { import { UserImportOptions, UserImportRecord, UserImportResult } from './user-import-builder'; import { ActionCodeSettings } from './action-code-settings-builder'; import { cryptoSignerFromApp } from '../utils/crypto-signer'; -import { FirebaseApp } from '../app/firebase-app'; /** Represents the result of the {@link BaseAuth.getUsers} API. */ export interface GetUsersResult { @@ -109,6 +108,19 @@ export interface SessionCookieOptions { expiresIn: number; } +/** + * @internal + */ +export function createFirebaseTokenGenerator(app: App, + tenantId?: string): FirebaseTokenGenerator { + try { + const signer = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app); + return new FirebaseTokenGenerator(signer, tenantId); + } catch (err) { + throw handleCryptoSignerError(err); + } +} + /** * Common parent interface for both `Auth` and `TenantAwareAuth` APIs. */ @@ -134,13 +146,12 @@ export abstract class BaseAuth { */ constructor( app: App, - /** @internal */protected readonly authRequestHandler: AbstractAuthRequestHandler, + /** @internal */ protected readonly authRequestHandler: AbstractAuthRequestHandler, tokenGenerator?: FirebaseTokenGenerator) { if (tokenGenerator) { this.tokenGenerator = tokenGenerator; } else { - const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app as FirebaseApp); - this.tokenGenerator = new FirebaseTokenGenerator(cryptoSigner); + this.tokenGenerator = createFirebaseTokenGenerator(app); } this.sessionCookieVerifier = createSessionCookieVerifier(app); @@ -171,8 +182,12 @@ export abstract class BaseAuth { * Verifies a Firebase ID token (JWT). If the token is valid, the promise is * fulfilled with the token's decoded claims; otherwise, the promise is * rejected. - * An optional flag can be passed to additionally check whether the ID token - * was revoked. + * + * If `checkRevoked` is set to true, first verifies whether the corresponding + * user is disabled. If yes, an `auth/user-disabled` error is thrown. If no, + * verifies if the session corresponding to the ID token was revoked. If the + * corresponding user's session was invalidated, an `auth/id-token-revoked` + * error is thrown. If not specified the check is not applied. * * See {@link https://firebase.google.com/docs/auth/admin/verify-id-tokens | Verify ID Tokens} * for code samples and detailed documentation. @@ -193,7 +208,7 @@ export abstract class BaseAuth { .then((decodedIdToken: DecodedIdToken) => { // Whether to check if the token was revoked. if (checkRevoked || isEmulator) { - return this.verifyDecodedJWTNotRevoked( + return this.verifyDecodedJWTNotRevokedOrDisabled( decodedIdToken, AuthClientErrorCode.ID_TOKEN_REVOKED); } @@ -669,11 +684,14 @@ export abstract class BaseAuth { /** * Verifies a Firebase session cookie. Returns a Promise with the cookie claims. - * Rejects the promise if the cookie could not be verified. If `checkRevoked` is - * set to true, verifies if the session corresponding to the session cookie was - * revoked. If the corresponding user's session was revoked, an - * `auth/session-cookie-revoked` error is thrown. If not specified the check is - * not performed. + * Rejects the promise if the cookie could not be verified. + * + * If `checkRevoked` is set to true, first verifies whether the corresponding + * user is disabled: If yes, an `auth/user-disabled` error is thrown. If no, + * verifies if the session corresponding to the session cookie was revoked. + * If the corresponding user's session was invalidated, an + * `auth/session-cookie-revoked` error is thrown. If not specified the check + * is not performed. * * See {@link https://firebase.google.com/docs/auth/admin/manage-cookies#verify_session_cookie_and_check_permissions | * Verify Session Cookies} @@ -696,7 +714,7 @@ export abstract class BaseAuth { .then((decodedIdToken: DecodedIdToken) => { // Whether to check if the token was revoked. if (checkRevoked || isEmulator) { - return this.verifyDecodedJWTNotRevoked( + return this.verifyDecodedJWTNotRevokedOrDisabled( decodedIdToken, AuthClientErrorCode.SESSION_COOKIE_REVOKED); } @@ -1038,19 +1056,25 @@ export abstract class BaseAuth { } /** - * Verifies the decoded Firebase issued JWT is not revoked. Returns a promise that resolves - * with the decoded claims on success. Rejects the promise with revocation error if revoked. + * Verifies the decoded Firebase issued JWT is not revoked or disabled. Returns a promise that + * resolves with the decoded claims on success. Rejects the promise with revocation error if revoked + * or user disabled. * * @param decodedIdToken The JWT's decoded claims. * @param revocationErrorInfo The revocation error info to throw on revocation * detection. - * @returns A Promise that will be fulfilled after a successful verification. + * @returns A promise that will be fulfilled after a successful verification. */ - private verifyDecodedJWTNotRevoked( + private verifyDecodedJWTNotRevokedOrDisabled( decodedIdToken: DecodedIdToken, revocationErrorInfo: ErrorInfo): Promise { // Get tokens valid after time for the corresponding user. return this.getUser(decodedIdToken.sub) .then((user: UserRecord) => { + if (user.disabled) { + throw new FirebaseAuthError( + AuthClientErrorCode.USER_DISABLED, + 'The user record is disabled.'); + } // If no tokens valid after time available, token is not revoked. if (user.tokensValidAfterTime) { // Get the ID token authentication time and convert to milliseconds UTC. diff --git a/src/auth/tenant-manager.ts b/src/auth/tenant-manager.ts index 37d5f34b31..571f237d9b 100644 --- a/src/auth/tenant-manager.ts +++ b/src/auth/tenant-manager.ts @@ -19,15 +19,12 @@ import { App } from '../app'; import * as utils from '../utils/index'; import { AuthClientErrorCode, FirebaseAuthError } from '../utils/error'; -import { BaseAuth, SessionCookieOptions } from './base-auth'; +import { BaseAuth, createFirebaseTokenGenerator, SessionCookieOptions } from './base-auth'; import { Tenant, TenantServerResponse, CreateTenantRequest, UpdateTenantRequest } from './tenant'; -import { FirebaseTokenGenerator, EmulatedSigner } from './token-generator'; import { - AuthRequestHandler, TenantAwareAuthRequestHandler, useEmulator, + AuthRequestHandler, TenantAwareAuthRequestHandler, } from './auth-api-request'; import { DecodedIdToken } from './token-verifier'; -import { FirebaseApp } from '../app/firebase-app'; -import { cryptoSignerFromApp } from '../utils/crypto-signer'; /** * Interface representing the object returned from a @@ -83,9 +80,8 @@ export class TenantAwareAuth extends BaseAuth { * @internal */ constructor(app: App, tenantId: string) { - const cryptoSigner = useEmulator() ? new EmulatedSigner() : cryptoSignerFromApp(app as FirebaseApp); - const tokenGenerator = new FirebaseTokenGenerator(cryptoSigner, tenantId); - super(app, new TenantAwareAuthRequestHandler(app, tenantId), tokenGenerator); + super(app, new TenantAwareAuthRequestHandler( + app, tenantId), createFirebaseTokenGenerator(app, tenantId)); utils.addReadonlyGetter(this, 'tenantId', tenantId); } diff --git a/src/auth/token-generator.ts b/src/auth/token-generator.ts index 71da2390b1..7e930916ab 100644 --- a/src/auth/token-generator.ts +++ b/src/auth/token-generator.ts @@ -84,6 +84,8 @@ export class EmulatedSigner implements CryptoSigner { /** * Class for generating different types of Firebase Auth tokens (JWTs). + * + * @internal */ export class FirebaseTokenGenerator { @@ -202,8 +204,8 @@ export class FirebaseTokenGenerator { * Creates a new FirebaseAuthError by extracting the error code, message and other relevant * details from a CryptoSignerError. * - * @param {Error} err The Error to convert into a FirebaseAuthError error - * @return {FirebaseAuthError} A Firebase Auth error that can be returned to the user. + * @param err The Error to convert into a FirebaseAuthError error + * @returns A Firebase Auth error that can be returned to the user. */ export function handleCryptoSignerError(err: Error): Error { if (!(err instanceof CryptoSignerError)) { diff --git a/src/auth/token-verifier.ts b/src/auth/token-verifier.ts index 4a88420f5a..12c7e72a29 100644 --- a/src/auth/token-verifier.ts +++ b/src/auth/token-verifier.ts @@ -370,7 +370,6 @@ export class FirebaseTokenVerifier { fullDecodedToken: DecodedToken, projectId: string | null, isEmulator: boolean): void { - const header = fullDecodedToken && fullDecodedToken.header; const payload = fullDecodedToken && fullDecodedToken.payload; diff --git a/src/database/database.ts b/src/database/database.ts index c7b731c134..dc17419d5e 100644 --- a/src/database/database.ts +++ b/src/database/database.ts @@ -17,12 +17,12 @@ import { URL } from 'url'; import * as path from 'path'; -import { Database as DatabaseImpl } from '@firebase/database'; import { FirebaseDatabase } from '@firebase/database-types'; +import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; +import { Database as DatabaseImpl } from '@firebase/database-compat/standalone'; import { App } from '../app'; import { FirebaseApp } from '../app/firebase-app'; -import { FirebaseDatabaseError, AppErrorCodes, FirebaseAppError } from '../utils/error'; import * as validator from '../utils/validator'; import { AuthorizedHttpClient, HttpRequestConfig, HttpError } from '../utils/api-request'; import { getSdkVersion } from '../utils/index'; @@ -124,7 +124,8 @@ export class DatabaseService { let db: Database = this.databases[dbUrl]; if (typeof db === 'undefined') { - const rtdb = require('@firebase/database'); // eslint-disable-line @typescript-eslint/no-var-requires + // eslint-disable-next-line @typescript-eslint/no-var-requires + const rtdb = require('@firebase/database-compat/standalone'); db = rtdb.initStandalone(this.appInternal, dbUrl, getSdkVersion()).instance; const rulesClient = new DatabaseRulesClient(this.app, dbUrl); diff --git a/src/database/index.ts b/src/database/index.ts index 57e9264f4e..53a72f5fe4 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -24,7 +24,7 @@ import * as rtdb from '@firebase/database-types'; import { enableLogging as enableLoggingFunc, ServerValue as serverValueConst, -} from '@firebase/database'; +} from '@firebase/database-compat/standalone'; import { App, getApp } from '../app'; import { FirebaseApp } from '../app/firebase-app'; @@ -40,11 +40,13 @@ export { ThenableReference, } from '@firebase/database-types'; +// TODO: Remove the following any-cast once the typins in @firebase/database-types are fixed. + /** * {@link https://firebase.google.com/docs/reference/js/firebase.database#enablelogging | enableLogging} * function from the `@firebase/database` package. */ -export const enableLogging: typeof rtdb.enableLogging = enableLoggingFunc; +export const enableLogging: typeof rtdb.enableLogging = enableLoggingFunc as any; /** * {@link https://firebase.google.com/docs/reference/js/firebase.database.ServerValue | ServerValue} diff --git a/src/installations/installations-namespace.ts b/src/installations/installations-namespace.ts index dc51e08476..8a2495c788 100644 --- a/src/installations/installations-namespace.ts +++ b/src/installations/installations-namespace.ts @@ -43,7 +43,7 @@ import { Installations as TInstallations } from './installations'; * return. If not provided, the default `Installations` service is * returned. * - * @return The default `Installations` service if + * @returns The default `Installations` service if * no app is provided or the `Installations` service associated with the * provided app. */ diff --git a/src/installations/installations.ts b/src/installations/installations.ts index 5604cef34b..98ab0f0ceb 100644 --- a/src/installations/installations.ts +++ b/src/installations/installations.ts @@ -30,6 +30,7 @@ export class Installations { /** * @param app The app for this Installations service. * @constructor + * @internal */ constructor(app: App) { if (!validator.isNonNullObject(app) || !('options' in app)) { @@ -48,7 +49,7 @@ export class Installations { * * @param fid The Firebase installation ID to be deleted. * - * @return A promise fulfilled when the installation ID is deleted. + * @returns A promise fulfilled when the installation ID is deleted. */ public deleteInstallation(fid: string): Promise { return this.requestHandler.deleteInstallation(fid); diff --git a/src/instance-id/index.ts b/src/instance-id/index.ts index 7a1d5226f8..22fff0a89a 100644 --- a/src/instance-id/index.ts +++ b/src/instance-id/index.ts @@ -49,6 +49,9 @@ export { InstanceId }; * const otherInstanceId = getInstanceId(otherApp); *``` * + * This API is deprecated. Developers are advised to use the `admin.installations()` + * API to delete their instance IDs and Firebase installation IDs. + * * @param app Optional app whose `InstanceId` service to * return. If not provided, the default `InstanceId` service will be * returned. diff --git a/src/remote-config/index.ts b/src/remote-config/index.ts index ae7eaaad45..1797298da5 100644 --- a/src/remote-config/index.ts +++ b/src/remote-config/index.ts @@ -29,6 +29,7 @@ export { InAppDefaultValue, ListVersionsOptions, ListVersionsResult, + ParameterValueType, RemoteConfigCondition, RemoteConfigParameter, RemoteConfigParameterGroup, diff --git a/src/remote-config/remote-config-api.ts b/src/remote-config/remote-config-api.ts index b8d49640c7..90f3bd4970 100644 --- a/src/remote-config/remote-config-api.ts +++ b/src/remote-config/remote-config-api.ts @@ -20,6 +20,12 @@ export type TagColor = 'BLUE' | 'BROWN' | 'CYAN' | 'DEEP_ORANGE' | 'GREEN' | 'INDIGO' | 'LIME' | 'ORANGE' | 'PINK' | 'PURPLE' | 'TEAL'; +/** + * Type representing a Remote Config parameter value data type. + * Defaults to `STRING` if unspecified. + */ +export type ParameterValueType = 'STRING' | 'BOOLEAN' | 'NUMBER' | 'JSON' + /** * Interface representing a Remote Config condition. * A condition targets a specific group of users. A list of these conditions make up @@ -99,6 +105,12 @@ export interface RemoteConfigParameter { * Unicode characters. */ description?: string; + + /** + * The data type for all values of this parameter in the current version of the template. + * Defaults to `ParameterValueType.STRING` if unspecified. + */ + valueType?: ParameterValueType; } /** diff --git a/src/remote-config/remote-config-namespace.ts b/src/remote-config/remote-config-namespace.ts index b2d6426e50..40b2698f66 100644 --- a/src/remote-config/remote-config-namespace.ts +++ b/src/remote-config/remote-config-namespace.ts @@ -20,6 +20,7 @@ import { InAppDefaultValue as TInAppDefaultValue, ListVersionsOptions as TListVersionsOptions, ListVersionsResult as TListVersionsResult, + ParameterValueType as TParameterValueType, RemoteConfigCondition as TRemoteConfigCondition, RemoteConfigParameter as TRemoteConfigParameter, RemoteConfigParameterGroup as TRemoteConfigParameterGroup, @@ -82,6 +83,11 @@ export namespace remoteConfig { */ export type ListVersionsResult = TListVersionsResult; + /** + * Type alias to {@link firebase-admin.remote-config#ParameterValueType}. + */ + export type ParameterValueType = TParameterValueType; + /** * Type alias to {@link firebase-admin.remote-config#RemoteConfig}. */ diff --git a/src/utils/crypto-signer.ts b/src/utils/crypto-signer.ts index d34ace3feb..789e2c81f3 100644 --- a/src/utils/crypto-signer.ts +++ b/src/utils/crypto-signer.ts @@ -47,7 +47,7 @@ export interface CryptoSigner { /** * Returns the ID of the service account used to sign tokens. * - * @return {Promise} A promise that resolves with a service account ID. + * @returns A promise that resolves with a service account ID. */ getAccountId(): Promise; } @@ -224,17 +224,17 @@ export class CryptoSignerError extends Error { (this as any).__proto__ = CryptoSignerError.prototype; } - /** @return {string} The error code. */ + /** @returns The error code. */ public get code(): string { return this.errorInfo.code; } - /** @return {string} The error message. */ + /** @returns The error message. */ public get message(): string { return this.errorInfo.message; } - /** @return {object} The error data. */ + /** @returns The error data. */ public get cause(): Error | undefined { return this.errorInfo.cause; } diff --git a/src/utils/error.ts b/src/utils/error.ts index 00ad8ab594..cde0d24335 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -721,6 +721,10 @@ export class AuthClientErrorCode { code: 'not-found', message: 'The requested resource was not found.', }; + public static USER_DISABLED = { + code: 'user-disabled', + message: 'The user record is disabled.', + } public static USER_NOT_DISABLED = { code: 'user-not-disabled', message: 'The user must be disabled in order to bulk delete it (or you must pass force=true).', diff --git a/src/utils/jwt.ts b/src/utils/jwt.ts index b5ca551f7f..ec54193b32 100644 --- a/src/utils/jwt.ts +++ b/src/utils/jwt.ts @@ -31,7 +31,7 @@ const JWT_CALLBACK_ERROR_PREFIX = 'error in secret or public key callback: '; const NO_MATCHING_KID_ERROR_MESSAGE = 'no-matching-kid-error'; const NO_KID_IN_HEADER_ERROR_MESSAGE = 'no-kid-in-header-error'; -const ONE_DAY_IN_SECONDS = 24 * 3600; +const HOUR_IN_SECONDS = 3600; export type Dictionary = { [key: string]: any } @@ -60,7 +60,7 @@ export class JwksFetcher implements KeyFetcher { this.client = jwks({ jwksUri: jwksUrl, - cache: false, // disable jwks-rsa LRU cache as the keys are always cached for 24 hours. + cache: false, // disable jwks-rsa LRU cache as the keys are always cached for 6 hours. }); } @@ -84,7 +84,7 @@ export class JwksFetcher implements KeyFetcher { map[signingKey.kid] = signingKey.getPublicKey(); return map; }, {}); - this.publicKeysExpireAt = Date.now() + (ONE_DAY_IN_SECONDS * 1000); + this.publicKeysExpireAt = Date.now() + (HOUR_IN_SECONDS * 6 * 1000); this.publicKeys = newKeys; return newKeys; }).catch((err) => { diff --git a/test/integration/app.spec.ts b/test/integration/app.spec.ts index fcbe9d341b..3470ae088a 100644 --- a/test/integration/app.spec.ts +++ b/test/integration/app.spec.ts @@ -31,7 +31,7 @@ describe('admin', () => { describe('Dependency lazy loading', () => { const tempCache: {[key: string]: any} = {}; - const dependencies = ['@firebase/database', '@google-cloud/firestore']; + const dependencies = ['@firebase/database-compat/standalone', '@google-cloud/firestore']; let lazyLoadingApp: App; before(() => { @@ -49,14 +49,14 @@ describe('admin', () => { }); it('does not load RTDB by default', () => { - const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; + const firebaseRtdb = require.cache[require.resolve('@firebase/database-compat/standalone')]; expect(firebaseRtdb).to.be.undefined; }); it('loads RTDB when calling admin.database', () => { const rtdbNamespace = admin.database; expect(rtdbNamespace).to.not.be.null; - const firebaseRtdb = require.cache[require.resolve('@firebase/database')]; + const firebaseRtdb = require.cache[require.resolve('@firebase/database-compat/standalone')]; expect(firebaseRtdb).to.not.be.undefined; }); diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index d52b77783c..466a6ebe3d 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -19,8 +19,8 @@ import * as crypto from 'crypto'; import * as bcrypt from 'bcrypt'; import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import firebase from '@firebase/app'; -import '@firebase/auth'; +import firebase from '@firebase/app-compat'; +import '@firebase/auth-compat'; import { clone } from 'lodash'; import { User, FirebaseAuth } from '@firebase/auth-types'; import { @@ -325,6 +325,56 @@ describe('admin.auth', () => { }); }); + it('getUserByProviderUid() returns a user record with the matching provider id', async () => { + // TODO(rsgowman): Once we can link a provider id with a user, just do that + // here instead of creating a new user. + const randomUid = 'import_' + generateRandomString(20).toLowerCase(); + const importUser: UserImportRecord = { + uid: randomUid, + email: 'user@example.com', + phoneNumber: '+15555550000', + emailVerified: true, + disabled: false, + metadata: { + lastSignInTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + creationTime: 'Thu, 01 Jan 1970 00:00:00 UTC', + }, + providerData: [{ + displayName: 'User Name', + email: 'user@example.com', + phoneNumber: '+15555550000', + photoURL: 'http://example.com/user', + providerId: 'google.com', + uid: 'google_uid', + }], + }; + + await getAuth().importUsers([importUser]); + + try { + await getAuth().getUserByProviderUid('google.com', 'google_uid') + .then((userRecord) => { + expect(userRecord.uid).to.equal(importUser.uid); + }); + } finally { + await safeDelete(importUser.uid); + } + }); + + it('getUserByProviderUid() redirects to getUserByEmail if given an email', () => { + return getAuth().getUserByProviderUid('email', mockUserData.email) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + + it('getUserByProviderUid() redirects to getUserByPhoneNumber if given a phone number', () => { + return getAuth().getUserByProviderUid('phone', mockUserData.phoneNumber) + .then((userRecord) => { + expect(userRecord.uid).to.equal(newUserUid); + }); + }); + describe('getUsers()', () => { /** * Filters a list of object to another list of objects that only contains @@ -846,6 +896,11 @@ describe('admin.auth', () => { .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); }); + it('getUserByProviderUid() fails when called with a non-existing provider id', () => { + return getAuth().getUserByProviderUid('google.com', nonexistentUid) + .should.eventually.be.rejected.and.have.property('code', 'auth/user-not-found'); + }); + it('updateUser() fails when called with a non-existing UID', () => { return getAuth().updateUser(nonexistentUid, { emailVerified: true, @@ -2046,6 +2101,44 @@ describe('admin.auth', () => { .should.eventually.be.rejected.and.have.property('code', 'auth/argument-error'); }); }); + + it('fails with checkRevoked set to true and corresponding user disabled', async () => { + const expiresIn = 24 * 60 * 60 * 1000; + const customToken = await getAuth().createCustomToken(uid, { admin: true, groupId: '1234' }); + const { user } = await clientAuth().signInWithCustomToken(customToken); + expect(user).to.exist; + + const idToken = await user!.getIdToken(); + const decodedIdTokenClaims = await getAuth().verifyIdToken(idToken); + expect(decodedIdTokenClaims.uid).to.be.equal(uid); + + const sessionCookie = await getAuth().createSessionCookie(idToken, { expiresIn }); + let decodedIdToken = await getAuth().verifySessionCookie(sessionCookie, true); + expect(decodedIdToken.uid).to.equal(uid); + + const userRecord = await getAuth().updateUser(uid, { disabled : true }); + // Ensure disabled field has been updated. + expect(userRecord.uid).to.equal(uid); + expect(userRecord.disabled).to.equal(true); + + try { + // If it is in emulator mode, a user-disabled error will be thrown. + decodedIdToken = await getAuth().verifySessionCookie(sessionCookie, false); + expect(decodedIdToken.uid).to.equal(uid); + } catch (error) { + if (authEmulatorHost) { + expect(error).to.have.property('code', 'auth/user-disabled'); + } else { + throw error; + } + } + + try { + await getAuth().verifySessionCookie(sessionCookie, true); + } catch (error) { + expect(error).to.have.property('code', 'auth/user-disabled'); + } + }); }); describe('importUsers()', () => { diff --git a/test/integration/remote-config.spec.ts b/test/integration/remote-config.spec.ts index 7a4c6c9a3c..b8b75500af 100644 --- a/test/integration/remote-config.spec.ts +++ b/test/integration/remote-config.spec.ts @@ -17,7 +17,12 @@ import * as chai from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../src/utils/deep-copy'; -import { RemoteConfigCondition, RemoteConfigTemplate, getRemoteConfig, } from '../../lib/remote-config/index'; +import { + getRemoteConfig, + ParameterValueType, + RemoteConfigCondition, + RemoteConfigTemplate, +} from '../../lib/remote-config/index'; chai.should(); chai.use(chaiAsPromised); @@ -28,11 +33,13 @@ const VALID_PARAMETERS = { // eslint-disable-next-line @typescript-eslint/camelcase holiday_promo_enabled: { defaultValue: { useInAppDefault: true }, - description: 'promo indicator' + description: 'promo indicator', + valueType: 'STRING' as ParameterValueType, }, // eslint-disable-next-line @typescript-eslint/camelcase welcome_message: { defaultValue: { value: `welcome text ${Date.now()}` }, + valueType: 'STRING' as ParameterValueType, conditionalValues: { ios: { value: 'welcome ios text' }, android: { value: 'welcome android text' }, @@ -52,6 +59,7 @@ const VALID_PARAMETER_GROUPS = { 'android': { value: 'A Droid must love a pumpkin spice latte.' }, }, description: 'Description of the parameter.', + valueType: 'STRING' as ParameterValueType, }, }, }, diff --git a/test/unit/app-check/token-generator.spec.ts b/test/unit/app-check/token-generator.spec.ts index 4a0e8deee9..b90341c2ed 100644 --- a/test/unit/app-check/token-generator.spec.ts +++ b/test/unit/app-check/token-generator.spec.ts @@ -122,11 +122,58 @@ describe('AppCheckTokenGenerator', () => { }).to.throw(FirebaseAppCheckError).with.property('code', 'app-check/invalid-argument'); }); - it('should be fulfilled with a Firebase Custom JWT', () => { + const invalidOptions = [null, NaN, 0, 1, true, false, [], _.noop]; + invalidOptions.forEach((invalidOption) => { + it('should throw given an invalid options: ' + JSON.stringify(invalidOption), () => { + expect(() => { + tokenGenerator.createCustomToken(APP_ID, invalidOption as any); + }).to.throw(FirebaseAppCheckError).with.property('message', 'AppCheckTokenOptions must be a non-null object.'); + }); + }); + + const invalidTtls = [null, NaN, '0', 'abc', '', true, false, [], {}, { a: 1 }, _.noop]; + invalidTtls.forEach((invalidTtl) => { + it('should throw given an options object with invalid ttl: ' + JSON.stringify(invalidTtl), () => { + expect(() => { + tokenGenerator.createCustomToken(APP_ID, { ttlMillis: invalidTtl as any }); + }).to.throw(FirebaseAppCheckError).with.property('message', + 'ttlMillis must be a duration in milliseconds.'); + }); + }); + + const THIRTY_MIN_IN_MS = 1800000; + const SEVEN_DAYS_IN_MS = 604800000; + [-100, -1, 0, 10, THIRTY_MIN_IN_MS - 1, SEVEN_DAYS_IN_MS + 1, SEVEN_DAYS_IN_MS * 2].forEach((ttlMillis) => { + it('should throw given options with ttl < 30 minutes or ttl > 7 days:' + JSON.stringify(ttlMillis), () => { + expect(() => { + tokenGenerator.createCustomToken(APP_ID, { ttlMillis }); + }).to.throw(FirebaseAppCheckError).with.property( + 'message', 'ttlMillis must be a duration in milliseconds between 30 minutes and 7 days (inclusive).'); + }); + }); + + it('should be fulfilled with a Firebase Custom JWT with only an APP ID', () => { return tokenGenerator.createCustomToken(APP_ID) .should.eventually.be.a('string').and.not.be.empty; }); + [ + [THIRTY_MIN_IN_MS, '1800s'], + [THIRTY_MIN_IN_MS + 1, '1800.001000000s'], + [SEVEN_DAYS_IN_MS / 2, '302400s'], + [SEVEN_DAYS_IN_MS - 1, '604799.999000000s'], + [SEVEN_DAYS_IN_MS, '604800s'] + ].forEach((ttl) => { + it('should be fulfilled with a Firebase Custom JWT with a valid custom ttl' + JSON.stringify(ttl[0]), () => { + return tokenGenerator.createCustomToken(APP_ID, { ttlMillis: ttl[0] as number }) + .then((token) => { + const decoded = jwt.decode(token) as { [key: string]: any }; + + expect(decoded['ttl']).to.equal(ttl[1]); + }); + }); + }); + it('should be fulfilled with a JWT with the correct decoded payload', () => { clock = sinon.useFakeTimers(1000); @@ -147,6 +194,57 @@ describe('AppCheckTokenGenerator', () => { }); }); + [{}, { ttlMillis: undefined }, { a: 123 }].forEach((options) => { + it('should be fulfilled with no ttl in the decoded payload when ttl is not provided in options', () => { + clock = sinon.useFakeTimers(1000); + + return tokenGenerator.createCustomToken(APP_ID, options) + .then((token) => { + const decoded = jwt.decode(token); + const expected: { [key: string]: any } = { + // eslint-disable-next-line @typescript-eslint/camelcase + app_id: APP_ID, + iat: 1, + exp: FIVE_MIN_IN_SECONDS + 1, + aud: FIREBASE_APP_CHECK_AUDIENCE, + iss: mocks.certificateObject.client_email, + sub: mocks.certificateObject.client_email, + }; + + expect(decoded).to.deep.equal(expected); + }); + }); + }); + + [ + [1800000.000001, '1800.000000001s'], + [1800000.001, '1800.000000999s'], + [172800000, '172800s'], + [604799999, '604799.999000000s'], + [604800000, '604800s'] + ].forEach((ttl) => { + it('should be fulfilled with a JWT with custom ttl in decoded payload', () => { + clock = sinon.useFakeTimers(1000); + + return tokenGenerator.createCustomToken(APP_ID, { ttlMillis: ttl[0] as number }) + .then((token) => { + const decoded = jwt.decode(token); + const expected: { [key: string]: any } = { + // eslint-disable-next-line @typescript-eslint/camelcase + app_id: APP_ID, + iat: 1, + exp: FIVE_MIN_IN_SECONDS + 1, + aud: FIREBASE_APP_CHECK_AUDIENCE, + iss: mocks.certificateObject.client_email, + sub: mocks.certificateObject.client_email, + ttl: ttl[1], + }; + + expect(decoded).to.deep.equal(expected); + }); + }); + }); + it('should be fulfilled with a JWT with the correct header', () => { clock = sinon.useFakeTimers(1000); diff --git a/test/unit/app/firebase-namespace.spec.ts b/test/unit/app/firebase-namespace.spec.ts index 7cfa53429e..467854d124 100644 --- a/test/unit/app/firebase-namespace.spec.ts +++ b/test/unit/app/firebase-namespace.spec.ts @@ -35,7 +35,7 @@ import { Query, Reference, ServerValue, -} from '@firebase/database'; +} from '@firebase/database-compat/standalone'; import { FieldPath, diff --git a/test/unit/auth/auth.spec.ts b/test/unit/auth/auth.spec.ts index a488c7e453..4c112dfe21 100644 --- a/test/unit/auth/auth.spec.ts +++ b/test/unit/auth/auth.spec.ts @@ -478,6 +478,53 @@ AUTH_CONFIGS.forEach((testConfig) => { .should.eventually.be.rejectedWith('Decoding Firebase ID token failed'); }); + it('should be rejected with checkRevoked set to true and corresponding user disabled', () => { + const expectedAccountInfoResponse = getValidGetAccountInfoResponse(tenantId); + expectedAccountInfoResponse.users[0].disabled = true; + const expectedUserRecordDisabled = getValidUserRecord(expectedAccountInfoResponse); + const getUserStub = sinon.stub(testConfig.Auth.prototype, 'getUser') + .resolves(expectedUserRecordDisabled); + expect(expectedUserRecordDisabled.disabled).to.be.equal(true); + stubs.push(getUserStub); + return auth.verifyIdToken(mockIdToken, true) + .then(() => { + throw new Error('Unexpected success'); + }, (error) => { + // Confirm underlying API called with expected parameters. + expect(getUserStub).to.have.been.calledOnce.and.calledWith(uid); + // Confirm expected error returned. + expect(error).to.have.property('code', 'auth/user-disabled'); + }); + }); + + it('verifyIdToken() should reject user disabled before ID tokens revoked', () => { + const expectedAccountInfoResponse = getValidGetAccountInfoResponse(tenantId); + const expectedAccountInfoResponseUserDisabled = Object.assign({}, expectedAccountInfoResponse); + expectedAccountInfoResponseUserDisabled.users[0].disabled = true; + const expectedUserRecordDisabled = getValidUserRecord(expectedAccountInfoResponseUserDisabled); + const validSince = new Date(expectedUserRecordDisabled.tokensValidAfterTime!); + // Restore verifyIdToken stub. + stub.restore(); + // One second before validSince. + const oneSecBeforeValidSince = new Date(validSince.getTime() - 1000); + stub = sinon.stub(FirebaseTokenVerifier.prototype, 'verifyJWT') + .resolves(getDecodedIdToken(expectedUserRecordDisabled.uid, oneSecBeforeValidSince)); + stubs.push(stub); + const getUserStub = sinon.stub(testConfig.Auth.prototype, 'getUser') + .resolves(expectedUserRecordDisabled); + expect(expectedUserRecordDisabled.disabled).to.be.equal(true); + stubs.push(getUserStub); + return auth.verifyIdToken(mockIdToken, true) + .then(() => { + throw new Error('Unexpected success'); + }, (error) => { + // Confirm expected error returned. + expect(error).to.have.property('code', 'auth/user-disabled'); + // Confirm underlying API called with expected parameters. + expect(getUserStub).to.have.been.calledOnce.and.calledWith(expectedUserRecordDisabled.uid); + }); + }); + it('should work with a non-cert credential when the GOOGLE_CLOUD_PROJECT environment variable is present', () => { process.env.GOOGLE_CLOUD_PROJECT = mocks.projectId; @@ -834,6 +881,53 @@ AUTH_CONFIGS.forEach((testConfig) => { }); }); + it('should be rejected with checkRevoked set to true and corresponding user disabled', () => { + const expectedAccountInfoResponse = getValidGetAccountInfoResponse(tenantId); + expectedAccountInfoResponse.users[0].disabled = true; + const expectedUserRecordDisabled = getValidUserRecord(expectedAccountInfoResponse); + const getUserStub = sinon.stub(testConfig.Auth.prototype, 'getUser') + .resolves(expectedUserRecordDisabled); + expect(expectedUserRecordDisabled.disabled).to.be.equal(true); + stubs.push(getUserStub); + return auth.verifySessionCookie(mockSessionCookie, true) + .then(() => { + throw new Error('Unexpected success'); + }, (error) => { + // Confirm underlying API called with expected parameters. + expect(getUserStub).to.have.been.calledOnce.and.calledWith(uid); + // Confirm expected error returned. + expect(error).to.have.property('code', 'auth/user-disabled'); + }); + }); + + it('verifySessionCookie() should reject user disabled before ID tokens revoked', () => { + const expectedAccountInfoResponse = getValidGetAccountInfoResponse(tenantId); + const expectedAccountInfoResponseUserDisabled = Object.assign({}, expectedAccountInfoResponse); + expectedAccountInfoResponseUserDisabled.users[0].disabled = true; + const expectedUserRecordDisabled = getValidUserRecord(expectedAccountInfoResponseUserDisabled); + const validSince = new Date(expectedUserRecordDisabled.tokensValidAfterTime!); + // Restore verifySessionCookie stub. + stub.restore(); + // One second before validSince. + const oneSecBeforeValidSince = new Date(validSince.getTime() - 1000); + stub = sinon.stub(FirebaseTokenVerifier.prototype, 'verifyJWT') + .resolves(getDecodedIdToken(expectedUserRecordDisabled.uid, oneSecBeforeValidSince)); + stubs.push(stub); + const getUserStub = sinon.stub(testConfig.Auth.prototype, 'getUser') + .resolves(expectedUserRecordDisabled); + expect(expectedUserRecordDisabled.disabled).to.be.equal(true); + stubs.push(getUserStub); + return auth.verifySessionCookie(mockSessionCookie, true) + .then(() => { + throw new Error('Unexpected success'); + }, (error) => { + // Confirm underlying API called with expected parameters. + expect(getUserStub).to.have.been.calledOnce.and.calledWith(expectedUserRecordDisabled.uid); + // Confirm expected error returned. + expect(error).to.have.property('code', 'auth/user-disabled'); + }); + }); + it('should be fulfilled with checkRevoked set to true when no validSince available', () => { // Simulate no validSince set on the user. const noValidSinceGetAccountInfoResponse = getValidGetAccountInfoResponse(tenantId); @@ -3595,7 +3689,6 @@ AUTH_CONFIGS.forEach((testConfig) => { }); describe('auth emulator support', () => { - let mockAuth = testConfig.init(mocks.app()); const userRecord = getValidUserRecord(getValidGetAccountInfoResponse()); const validSince = new Date(userRecord.tokensValidAfterTime!); diff --git a/test/unit/auth/token-verifier.spec.ts b/test/unit/auth/token-verifier.spec.ts index d422b90b61..6a4b67db6a 100644 --- a/test/unit/auth/token-verifier.spec.ts +++ b/test/unit/auth/token-verifier.spec.ts @@ -448,7 +448,6 @@ describe('FirebaseTokenVerifier', () => { createTokenVerifier(mockAppWithAgent); expect(verifierSpy.args[0][1]).to.equal(agentForApp); - verifierSpy.restore(); }); diff --git a/test/unit/index.spec.ts b/test/unit/index.spec.ts index 9c0b51f831..c13e053161 100644 --- a/test/unit/index.spec.ts +++ b/test/unit/index.spec.ts @@ -69,6 +69,10 @@ import './firestore/index.spec'; import './installations/installations.spec'; import './installations/installations-request-handler.spec'; +// Installations +import './installations/installations.spec'; +import './installations/installations-request-handler.spec'; + // InstanceId import './instance-id/index.spec'; import './instance-id/instance-id.spec'; diff --git a/test/unit/remote-config/remote-config-api-client.spec.ts b/test/unit/remote-config/remote-config-api-client.spec.ts index 70ca7bae97..79422b5c17 100644 --- a/test/unit/remote-config/remote-config-api-client.spec.ts +++ b/test/unit/remote-config/remote-config-api-client.spec.ts @@ -71,7 +71,7 @@ describe('RemoteConfigApiClient', () => { const TEST_RESPONSE = { conditions: [{ name: 'ios', expression: 'exp' }], - parameters: { param: { defaultValue: { value: 'true' } } }, + parameters: { param: { defaultValue: { value: 'true' }, valueType: 'BOOLEAN' } }, parameterGroups: { group: { parameters: { paramabc: { defaultValue: { value: 'true' } } }, } }, version: VERSION_INFO, }; @@ -130,6 +130,7 @@ describe('RemoteConfigApiClient', () => { defaultValue: { value: 'true' }, conditionalValues: { ios: { useInAppDefault: true } }, description: 'this is a promo', + valueType: 'BOOLEAN' }, }, parameterGroups: { @@ -427,7 +428,7 @@ describe('RemoteConfigApiClient', () => { }); VALIDATION_ERROR_MESSAGES.forEach((message) => { - it('should reject with failed-precondition when a validation error occurres', () => { + it('should reject with failed-precondition when a validation error occurs', () => { const stub = sinon .stub(HttpClient.prototype, 'send') .rejects(utils.errorFrom({ diff --git a/test/unit/remote-config/remote-config.spec.ts b/test/unit/remote-config/remote-config.spec.ts index e2a95aeb47..90f24c2984 100644 --- a/test/unit/remote-config/remote-config.spec.ts +++ b/test/unit/remote-config/remote-config.spec.ts @@ -20,6 +20,7 @@ import * as _ from 'lodash'; import * as chai from 'chai'; import * as sinon from 'sinon'; import { + ParameterValueType, RemoteConfig, RemoteConfigTemplate, RemoteConfigCondition, @@ -51,6 +52,7 @@ describe('RemoteConfig', () => { 'android_en': { value: 'A Droid must love a pumpkin spice latte.' }, }, description: 'Description of the parameter.', + valueType: 'STRING' as ParameterValueType, }, }, }, @@ -91,6 +93,7 @@ describe('RemoteConfig', () => { defaultValue: { value: 'true' }, conditionalValues: { ios: { useInAppDefault: true } }, description: 'this is a promo', + valueType: 'BOOLEAN', }, }, parameterGroups: PARAMETER_GROUPS, @@ -110,6 +113,7 @@ describe('RemoteConfig', () => { defaultValue: { value: 'true' }, conditionalValues: { ios: { useInAppDefault: true } }, description: 'this is a promo', + valueType: 'BOOLEAN', }, }, parameterGroups: PARAMETER_GROUPS, @@ -383,7 +387,7 @@ describe('RemoteConfig', () => { }); }); - it('should resolve with an empty versions list if the no results are availble for requested list options', () => { + it('should resolve with an empty versions list if no results are available for requested list options', () => { const stub = sinon .stub(RemoteConfigApiClient.prototype, 'listVersions') .resolves({} as any); @@ -498,6 +502,7 @@ describe('RemoteConfig', () => { expect(p1.defaultValue).deep.equals({ value: 'true' }); expect(p1.conditionalValues).deep.equals({ ios: { useInAppDefault: true } }); expect(p1.description).equals('this is a promo'); + expect(p1.valueType).equals('BOOLEAN'); expect(newTemplate.parameterGroups).deep.equals(PARAMETER_GROUPS); @@ -662,6 +667,7 @@ describe('RemoteConfig', () => { expect(p1.defaultValue).deep.equals({ value: 'true' }); expect(p1.conditionalValues).deep.equals({ ios: { useInAppDefault: true } }); expect(p1.description).equals('this is a promo'); + expect(p1.valueType).equals('BOOLEAN'); expect(template.parameterGroups).deep.equals(PARAMETER_GROUPS); diff --git a/test/unit/utils/jwt.spec.ts b/test/unit/utils/jwt.spec.ts index 4f31c88a16..6689c7ef7a 100644 --- a/test/unit/utils/jwt.spec.ts +++ b/test/unit/utils/jwt.spec.ts @@ -33,7 +33,7 @@ import { const expect = chai.expect; const ONE_HOUR_IN_SECONDS = 60 * 60; -const ONE_DAY_IN_SECONDS = 86400; +const SIX_HOURS_IN_SECONDS = ONE_HOUR_IN_SECONDS * 6; const publicCertPath = '/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com'; const jwksPath = '/v1alpha/jwks'; @@ -709,24 +709,24 @@ describe('JwksFetcher', () => { return keyFetcher.fetchPublicKeys().then(() => { expect(https.request).to.have.been.calledOnce; - clock!.tick((ONE_DAY_IN_SECONDS - 1) * 1000); + clock!.tick((SIX_HOURS_IN_SECONDS - 1) * 1000); return keyFetcher.fetchPublicKeys(); }).then(() => { expect(https.request).to.have.been.calledOnce; - clock!.tick(ONE_DAY_IN_SECONDS * 1000); // 24 hours in milliseconds + clock!.tick(SIX_HOURS_IN_SECONDS * 1000); // 6 hours in milliseconds return keyFetcher.fetchPublicKeys(); }).then(() => { - // App check keys do not contain cache headers so we cache the keys for 24 hours. - // 24 hours has passed + // App check keys do not contain cache headers so we cache the keys for 6 hours. + // 6 hours has passed expect(https.request).to.have.been.calledTwice; - clock!.tick((ONE_DAY_IN_SECONDS - 1) * 1000); + clock!.tick((SIX_HOURS_IN_SECONDS - 1) * 1000); return keyFetcher.fetchPublicKeys(); }).then(() => { expect(https.request).to.have.been.calledTwice; - clock!.tick(ONE_DAY_IN_SECONDS * 1000); + clock!.tick(SIX_HOURS_IN_SECONDS * 1000); return keyFetcher.fetchPublicKeys(); }).then(() => { - // 48 hours have passed + // 12 hours have passed expect(https.request).to.have.been.calledThrice; }); }); From 8ab8a4534a8a8a163c5b4916035939a426552d77 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Fri, 1 Oct 2021 09:36:44 -0700 Subject: [PATCH 41/41] fix: Remove CR (\r) from generated content (#1444) --- docgen/post-process.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docgen/post-process.js b/docgen/post-process.js index 4012797471..9c0a79bdcd 100644 --- a/docgen/post-process.js +++ b/docgen/post-process.js @@ -90,7 +90,7 @@ async function fixTitleOf(file) { if (updated) { console.log(`Updating title in ${file}`); - const content = Buffer.from(buffer.join('\r\n')); + const content = Buffer.from(buffer.join('\n')); await fs.writeFile(file, content); } } @@ -110,7 +110,7 @@ async function fixTocTitles(file) { } console.log(`Updating titles in ${file}`); - const content = Buffer.from(buffer.join('\r\n')); + const content = Buffer.from(buffer.join('\n')); await fs.writeFile(file, content); } @@ -166,7 +166,7 @@ async function writeExtraContentTo(target, extra) { output.push(line); } - const outputBuffer = Buffer.from(output.join('\r\n')); + const outputBuffer = Buffer.from(output.join('\n')); console.log(`Writing extra content to ${target}`); await fs.writeFile(target, outputBuffer); }