Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Adds support for multi-region RTDB.
93 changes: 72 additions & 21 deletions spec/providers/database.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,30 +424,72 @@ describe('Database Functions', () => {
});
});

describe('resourceToInstanceAndPath', () => {
it('should return the correct instance and path strings', () => {
const [instance, path] = database.resourceToInstanceAndPath(
'projects/_/instances/foo/refs/bar'
);
describe('extractInstanceAndPath', () => {
it('should return the correct us-central prod instance and path strings', () => {
const [instance, path] = database.extractInstanceAndPath({
context: {
resource: {
name: 'projects/_/instances/foo/refs/bar',
},
},
});
expect(instance).to.equal('https://foo.firebaseio.com');
expect(path).to.equal('/bar');
});

it('should return the correct staging instance and path strings', () => {
const [instance, path] = database.extractInstanceAndPath({
context: {
resource: {
name: 'projects/_/instances/foo/refs/bar',
},
domain: 'firebaseio-staging.com',
},
});
expect(instance).to.equal('https://foo.firebaseio-staging.com');
expect(path).to.equal('/bar');
});

it('should return the correct multi-region instance and path strings', () => {
const [instance, path] = database.extractInstanceAndPath({
context: {
resource: {
name: 'projects/_/instances/foo/refs/bar',
},
domain: 'euw1.firebasedatabase.app',
},
});
expect(instance).to.equal('https://foo.euw1.firebasedatabase.app');
expect(path).to.equal('/bar');
});

it('should throw an error if the given instance name contains anything except alphanumerics and dashes', () => {
expect(() => {
return database.resourceToInstanceAndPath(
'projects/_/instances/a.bad.name/refs/bar'
);
return database.extractInstanceAndPath({
context: {
resource: {
name: 'projects/_/instances/a.bad.name/refs/bar',
},
},
});
}).to.throw(Error);
expect(() => {
return database.resourceToInstanceAndPath(
'projects/_/instances/a_different_bad_name/refs/bar'
);
return database.extractInstanceAndPath({
context: {
resource: {
name: 'projects/_/instances/a_different_bad_name/refs/bar',
},
},
});
}).to.throw(Error);
expect(() => {
return database.resourceToInstanceAndPath(
'projects/_/instances/BAD!!!!/refs/bar'
);
return database.extractInstanceAndPath({
context: {
resource: {
name: 'projects/_/instances/BAD!!!!/refs/bar',
},
},
});
}).to.throw(Error);
});
});
Expand All @@ -457,17 +499,22 @@ describe('Database Functions', () => {
const apps = new appsNamespace.Apps();

const populate = (data: any) => {
const [instance, path] = database.resourceToInstanceAndPath(
'projects/_/instances/other-subdomain/refs/foo'
);
const [instance, path] = database.extractInstanceAndPath({
context: {
resource: {
name: 'projects/_/instances/other-subdomain/refs/foo',
},
domain: 'firebaseio-staging.com',
},
});
subject = new database.DataSnapshot(data, path, apps.admin, instance);
};

describe('#ref: firebase.database.Reference', () => {
it('should return a ref for correct instance, not the default instance', () => {
populate({});
expect(subject.ref.toJSON()).to.equal(
'https://other-subdomain.firebaseio.com/foo'
'https://other-subdomain.firebaseio-staging.com/foo'
);
});
});
Expand Down Expand Up @@ -648,9 +695,13 @@ describe('Database Functions', () => {
});

it('should return null for the root', () => {
const [instance, path] = database.resourceToInstanceAndPath(
'projects/_/instances/foo/refs/'
);
const [instance, path] = database.extractInstanceAndPath({
context: {
resource: {
name: 'projects/_/instances/foo/refs/',
},
},
});
const snapshot = new database.DataSnapshot(
null,
path,
Expand Down
29 changes: 17 additions & 12 deletions src/providers/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,7 @@ export class RefBuilder {
) => PromiseLike<any> | any
): CloudFunction<DataSnapshot> {
const dataConstructor = (raw: Event) => {
const [dbInstance, path] = resourceToInstanceAndPath(
raw.context.resource.name
);
const [dbInstance, path] = extractInstanceAndPath(raw);
return new DataSnapshot(
raw.data.delta,
path,
Expand All @@ -243,9 +241,7 @@ export class RefBuilder {
) => PromiseLike<any> | any
): CloudFunction<DataSnapshot> {
const dataConstructor = (raw: Event) => {
const [dbInstance, path] = resourceToInstanceAndPath(
raw.context.resource.name
);
const [dbInstance, path] = extractInstanceAndPath(raw);
return new DataSnapshot(raw.data.data, path, this.apps.admin, dbInstance);
};
return this.onOperation(handler, 'ref.delete', dataConstructor);
Expand All @@ -271,9 +267,7 @@ export class RefBuilder {
}

private changeConstructor = (raw: Event): Change<DataSnapshot> => {
const [dbInstance, path] = resourceToInstanceAndPath(
raw.context.resource.name
);
const [dbInstance, path] = extractInstanceAndPath(raw);
const before = new DataSnapshot(
raw.data.data,
path,
Expand All @@ -293,9 +287,13 @@ export class RefBuilder {
};
}

/* Utility function to extract database reference from resource string */
/**
* Utility function to extract database reference from resource string.
* The raw event may contain fields not part of the public API.
*/
/** @hidden */
export function resourceToInstanceAndPath(resource: string) {
export function extractInstanceAndPath(rawEvent: any) {
const resource = rawEvent.context.resource.name;
const resourceRegex = `projects/([^/]+)/instances/([a-zA-Z0-9\-^/]+)/refs(/.+)?`;
const match = resource.match(new RegExp(resourceRegex));
if (!match) {
Expand All @@ -310,7 +308,14 @@ export function resourceToInstanceAndPath(resource: string) {
`Expect project to be '_' in a Firebase Realtime Database event`
);
}
const dbInstance = 'https://' + dbInstanceName + '.firebaseio.com';
let domain = 'firebaseio.com';
if (_.has(rawEvent, 'context.domain')) {
// See go/rtdb-multi-region-function-sdk.
// Multi-region RTDB are served from different domains.
// Since region is not part of the resource name, it is provided through context.
domain = _.get(rawEvent, 'context.domain');
}
const dbInstance = 'https://' + dbInstanceName + '.' + domain;
return [dbInstance, path];
}

Expand Down