1 | ;
|
2 | // The MIT License (MIT)
|
3 | //
|
4 | // Copyright (c) 2017 Firebase
|
5 | //
|
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
|
7 | // of this software and associated documentation files (the "Software"), to deal
|
8 | // in the Software without restriction, including without limitation the rights
|
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10 | // copies of the Software, and to permit persons to whom the Software is
|
11 | // furnished to do so, subject to the following conditions:
|
12 | //
|
13 | // The above copyright notice and this permission notice shall be included in all
|
14 | // copies or substantial portions of the Software.
|
15 | //
|
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22 | // SOFTWARE.
|
23 | Object.defineProperty(exports, "__esModule", { value: true });
|
24 | exports.extractInstanceAndPath = exports.RefBuilder = exports._refWithOptions = exports.InstanceBuilder = exports._instanceWithOptions = exports.ref = exports.instance = exports.service = exports.provider = exports.DataSnapshot = void 0;
|
25 | const app_1 = require("../../common/app");
|
26 | const config_1 = require("../../common/config");
|
27 | const database_1 = require("../../common/providers/database");
|
28 | Object.defineProperty(exports, "DataSnapshot", { enumerable: true, get: function () { return database_1.DataSnapshot; } });
|
29 | const path_1 = require("../../common/utilities/path");
|
30 | const utils_1 = require("../../common/utilities/utils");
|
31 | const cloud_functions_1 = require("../cloud-functions");
|
32 | /** @internal */
|
33 | exports.provider = "google.firebase.database";
|
34 | /** @internal */
|
35 | exports.service = "firebaseio.com";
|
36 | const databaseURLRegex = new RegExp("^https://([^.]+).");
|
37 | const emulatorDatabaseURLRegex = new RegExp("^http://.*ns=([^&]+)");
|
38 | /**
|
39 | * Registers a function that triggers on events from a specific
|
40 | * Firebase Realtime Database instance.
|
41 | *
|
42 | * @remarks
|
43 | * Use this method together with `ref` to specify the instance on which to
|
44 | * watch for database events. For example: `firebase.database.instance('my-app-db-2').ref('/foo/bar')`
|
45 | *
|
46 | * Note that `functions.database.ref` used without `instance` watches the
|
47 | * *default* instance for events.
|
48 | *
|
49 | * @param instance The instance name of the database instance
|
50 | * to watch for write events.
|
51 | * @returns Firebase Realtime Database instance builder interface.
|
52 | */
|
53 | function instance(instance) {
|
54 | return _instanceWithOptions(instance, {});
|
55 | }
|
56 | exports.instance = instance;
|
57 | /**
|
58 | * Registers a function that triggers on Firebase Realtime Database write
|
59 | * events.
|
60 | *
|
61 | * @remarks
|
62 | * This method behaves very similarly to the method of the same name in the
|
63 | * client and Admin Firebase SDKs. Any change to the Database that affects the
|
64 | * data at or below the provided `path` will fire an event in Cloud Functions.
|
65 | *
|
66 | * There are three important differences between listening to a Realtime
|
67 | * Database event in Cloud Functions and using the Realtime Database in the
|
68 | * client and Admin SDKs:
|
69 | *
|
70 | * 1. Cloud Functions allows wildcards in the `path` name. Any `path` component
|
71 | * in curly brackets (`{}`) is a wildcard that matches all strings. The value
|
72 | * that matched a certain invocation of a Cloud Function is returned as part
|
73 | * of the [`EventContext.params`](cloud_functions_eventcontext.html#params object. For
|
74 | * example, `ref("messages/{messageId}")` matches changes at
|
75 | * `/messages/message1` or `/messages/message2`, resulting in
|
76 | * `event.params.messageId` being set to `"message1"` or `"message2"`,
|
77 | * respectively.
|
78 | *
|
79 | * 2. Cloud Functions do not fire an event for data that already existed before
|
80 | * the Cloud Function was deployed.
|
81 | *
|
82 | * 3. Cloud Function events have access to more information, including a
|
83 | * snapshot of the previous event data and information about the user who
|
84 | * triggered the Cloud Function.
|
85 | *
|
86 | * @param path The path within the Database to watch for write events.
|
87 | * @returns Firebase Realtime Database builder interface.
|
88 | */
|
89 | function ref(path) {
|
90 | return _refWithOptions(path, {});
|
91 | }
|
92 | exports.ref = ref;
|
93 | /** @internal */
|
94 | function _instanceWithOptions(instance, options) {
|
95 | return new InstanceBuilder(instance, options);
|
96 | }
|
97 | exports._instanceWithOptions = _instanceWithOptions;
|
98 | /**
|
99 | * The Firebase Realtime Database instance builder interface.
|
100 | *
|
101 | * Access via [`database.instance()`](providers_database_.html#instance).
|
102 | */
|
103 | class InstanceBuilder {
|
104 | constructor(instance, options) {
|
105 | this.instance = instance;
|
106 | this.options = options;
|
107 | }
|
108 | /**
|
109 | * @returns Firebase Realtime Database reference builder interface.
|
110 | */
|
111 | ref(path) {
|
112 | const normalized = (0, path_1.normalizePath)(path);
|
113 | return new RefBuilder(() => `projects/_/instances/${this.instance}/refs/${normalized}`, this.options);
|
114 | }
|
115 | }
|
116 | exports.InstanceBuilder = InstanceBuilder;
|
117 | /** @internal */
|
118 | function _refWithOptions(path, options) {
|
119 | const resourceGetter = () => {
|
120 | const normalized = (0, path_1.normalizePath)(path);
|
121 | const databaseURL = (0, config_1.firebaseConfig)().databaseURL;
|
122 | if (!databaseURL) {
|
123 | throw new Error("Missing expected firebase config value databaseURL, " +
|
124 | "config is actually" +
|
125 | JSON.stringify((0, config_1.firebaseConfig)()) +
|
126 | "\n If you are unit testing, please set process.env.FIREBASE_CONFIG");
|
127 | }
|
128 | let instance;
|
129 | const prodMatch = databaseURL.match(databaseURLRegex);
|
130 | if (prodMatch) {
|
131 | instance = prodMatch[1];
|
132 | }
|
133 | else {
|
134 | const emulatorMatch = databaseURL.match(emulatorDatabaseURLRegex);
|
135 | if (emulatorMatch) {
|
136 | instance = emulatorMatch[1];
|
137 | }
|
138 | }
|
139 | if (!instance) {
|
140 | throw new Error("Invalid value for config firebase.databaseURL: " + databaseURL);
|
141 | }
|
142 | return `projects/_/instances/${instance}/refs/${normalized}`;
|
143 | };
|
144 | return new RefBuilder(resourceGetter, options);
|
145 | }
|
146 | exports._refWithOptions = _refWithOptions;
|
147 | /**
|
148 | * The Firebase Realtime Database reference builder interface.
|
149 | *
|
150 | * Access via [`functions.database.ref()`](functions.database#.ref).
|
151 | */
|
152 | class RefBuilder {
|
153 | constructor(triggerResource, options) {
|
154 | this.triggerResource = triggerResource;
|
155 | this.options = options;
|
156 | this.changeConstructor = (raw) => {
|
157 | const [dbInstance, path] = extractInstanceAndPath(raw.context.resource.name, raw.context.domain);
|
158 | const before = new database_1.DataSnapshot(raw.data.data, path, (0, app_1.getApp)(), dbInstance);
|
159 | const after = new database_1.DataSnapshot((0, utils_1.applyChange)(raw.data.data, raw.data.delta), path, (0, app_1.getApp)(), dbInstance);
|
160 | return {
|
161 | before,
|
162 | after,
|
163 | };
|
164 | };
|
165 | }
|
166 | /**
|
167 | * Event handler that fires every time a Firebase Realtime Database write
|
168 | * of any kind (creation, update, or delete) occurs.
|
169 | *
|
170 | * @param handler Event handler that runs every time a Firebase Realtime Database
|
171 | * write occurs.
|
172 | * @returns A function that you can export and deploy.
|
173 | */
|
174 | onWrite(handler) {
|
175 | return this.onOperation(handler, "ref.write", this.changeConstructor);
|
176 | }
|
177 | /**
|
178 | * Event handler that fires every time data is updated in
|
179 | * Firebase Realtime Database.
|
180 | *
|
181 | * @param handler Event handler which is run every time a Firebase Realtime Database
|
182 | * write occurs.
|
183 | * @returns A function which you can export and deploy.
|
184 | */
|
185 | onUpdate(handler) {
|
186 | return this.onOperation(handler, "ref.update", this.changeConstructor);
|
187 | }
|
188 | /**
|
189 | * Event handler that fires every time new data is created in
|
190 | * Firebase Realtime Database.
|
191 | *
|
192 | * @param handler Event handler that runs every time new data is created in
|
193 | * Firebase Realtime Database.
|
194 | * @returns A function that you can export and deploy.
|
195 | */
|
196 | onCreate(handler) {
|
197 | const dataConstructor = (raw) => {
|
198 | const [dbInstance, path] = extractInstanceAndPath(raw.context.resource.name, raw.context.domain);
|
199 | return new database_1.DataSnapshot(raw.data.delta, path, (0, app_1.getApp)(), dbInstance);
|
200 | };
|
201 | return this.onOperation(handler, "ref.create", dataConstructor);
|
202 | }
|
203 | /**
|
204 | * Event handler that fires every time data is deleted from
|
205 | * Firebase Realtime Database.
|
206 | *
|
207 | * @param handler Event handler that runs every time data is deleted from
|
208 | * Firebase Realtime Database.
|
209 | * @returns A function that you can export and deploy.
|
210 | */
|
211 | onDelete(handler) {
|
212 | const dataConstructor = (raw) => {
|
213 | const [dbInstance, path] = extractInstanceAndPath(raw.context.resource.name, raw.context.domain);
|
214 | return new database_1.DataSnapshot(raw.data.data, path, (0, app_1.getApp)(), dbInstance);
|
215 | };
|
216 | return this.onOperation(handler, "ref.delete", dataConstructor);
|
217 | }
|
218 | onOperation(handler, eventType, dataConstructor) {
|
219 | return (0, cloud_functions_1.makeCloudFunction)({
|
220 | handler,
|
221 | provider: exports.provider,
|
222 | service: exports.service,
|
223 | eventType,
|
224 | legacyEventType: `providers/${exports.provider}/eventTypes/${eventType}`,
|
225 | triggerResource: this.triggerResource,
|
226 | dataConstructor,
|
227 | options: this.options,
|
228 | });
|
229 | }
|
230 | }
|
231 | exports.RefBuilder = RefBuilder;
|
232 | const resourceRegex = /^projects\/([^/]+)\/instances\/([a-zA-Z0-9-]+)\/refs(\/.+)?/;
|
233 | /**
|
234 | * Utility function to extract database reference from resource string
|
235 | *
|
236 | * @param optional database domain override for the original of the source database.
|
237 | * It defaults to `firebaseio.com`.
|
238 | * Multi-region RTDB will be served from different domains.
|
239 | * Since region is not part of the resource name, it is provided through context.
|
240 | *
|
241 | * @internal
|
242 | */
|
243 | function extractInstanceAndPath(resource, domain = "firebaseio.com") {
|
244 | const match = resource.match(new RegExp(resourceRegex));
|
245 | if (!match) {
|
246 | throw new Error(`Unexpected resource string for Firebase Realtime Database event: ${resource}. ` +
|
247 | 'Expected string in the format of "projects/_/instances/{firebaseioSubdomain}/refs/{ref=**}"');
|
248 | }
|
249 | const [, project, dbInstanceName, path] = match;
|
250 | if (project !== "_") {
|
251 | throw new Error(`Expect project to be '_' in a Firebase Realtime Database event`);
|
252 | }
|
253 | const emuHost = process.env.FIREBASE_DATABASE_EMULATOR_HOST;
|
254 | if (emuHost) {
|
255 | const dbInstance = `http://${emuHost}/?ns=${dbInstanceName}`;
|
256 | return [dbInstance, path];
|
257 | }
|
258 | else {
|
259 | const dbInstance = "https://" + dbInstanceName + "." + domain;
|
260 | return [dbInstance, path];
|
261 | }
|
262 | }
|
263 | exports.extractInstanceAndPath = extractInstanceAndPath;
|