UNPKG

11.2 kBJavaScriptView Raw
1"use strict";
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.
23Object.defineProperty(exports, "__esModule", { value: true });
24exports.extractInstanceAndPath = exports.RefBuilder = exports._refWithOptions = exports.InstanceBuilder = exports._instanceWithOptions = exports.ref = exports.instance = exports.service = exports.provider = exports.DataSnapshot = void 0;
25const app_1 = require("../../common/app");
26const config_1 = require("../../common/config");
27const database_1 = require("../../common/providers/database");
28Object.defineProperty(exports, "DataSnapshot", { enumerable: true, get: function () { return database_1.DataSnapshot; } });
29const path_1 = require("../../common/utilities/path");
30const utils_1 = require("../../common/utilities/utils");
31const cloud_functions_1 = require("../cloud-functions");
32/** @internal */
33exports.provider = "google.firebase.database";
34/** @internal */
35exports.service = "firebaseio.com";
36const databaseURLRegex = new RegExp("^https://([^.]+).");
37const 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 */
53function instance(instance) {
54 return _instanceWithOptions(instance, {});
55}
56exports.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 */
89function ref(path) {
90 return _refWithOptions(path, {});
91}
92exports.ref = ref;
93/** @internal */
94function _instanceWithOptions(instance, options) {
95 return new InstanceBuilder(instance, options);
96}
97exports._instanceWithOptions = _instanceWithOptions;
98/**
99 * The Firebase Realtime Database instance builder interface.
100 *
101 * Access via [`database.instance()`](providers_database_.html#instance).
102 */
103class 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}
116exports.InstanceBuilder = InstanceBuilder;
117/** @internal */
118function _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}
146exports._refWithOptions = _refWithOptions;
147/**
148 * The Firebase Realtime Database reference builder interface.
149 *
150 * Access via [`functions.database.ref()`](functions.database#.ref).
151 */
152class 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}
231exports.RefBuilder = RefBuilder;
232const 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 */
243function 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}
263exports.extractInstanceAndPath = extractInstanceAndPath;