Skip to content

Commit 285c55d

Browse files
committed
Implement useEmulator for Database
1 parent f5d122a commit 285c55d

File tree

5 files changed

+95
-8
lines changed

5 files changed

+95
-8
lines changed

.changeset/bright-ducks-jump.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@firebase/database': minor
3+
---
4+
5+
Add a useEmulator(host, port) method

packages/database/src/api/Database.ts

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,12 @@ import { FirebaseDatabase } from '@firebase/database-types';
3434
*/
3535
export class Database implements FirebaseService {
3636
INTERNAL: DatabaseInternals;
37-
private root_: Reference;
37+
38+
/** Track if the instance has been used (root or repo accessed) */
39+
private instanceUsed_: boolean = false;
40+
41+
/** Backing state for root_ */
42+
private rootInternal_: Reference;
3843

3944
static readonly ServerValue = {
4045
TIMESTAMP: {
@@ -51,25 +56,69 @@ export class Database implements FirebaseService {
5156

5257
/**
5358
* The constructor should not be called by users of our public API.
54-
* @param {!Repo} repo_
59+
* @param {!Repo} repoInternal_
5560
*/
56-
constructor(private repo_: Repo) {
57-
if (!(repo_ instanceof Repo)) {
61+
constructor(private repoInternal_: Repo) {
62+
if (!(repoInternal_ instanceof Repo)) {
5863
fatal(
5964
"Don't call new Database() directly - please use firebase.database()."
6065
);
6166
}
6267

63-
/** @type {Reference} */
64-
this.root_ = new Reference(repo_, Path.Empty);
65-
68+
this.repo_ = repoInternal_;
6669
this.INTERNAL = new DatabaseInternals(this);
6770
}
6871

72+
private get repo_(): Repo {
73+
this.instanceUsed_ = true;
74+
return this.repoInternal_;
75+
}
76+
77+
private set repo_(repo: Repo) {
78+
if (repo instanceof Repo) {
79+
this.root_ = new Reference(repo, Path.Empty);
80+
}
81+
82+
this.repoInternal_ = repo;
83+
}
84+
85+
get root_(): Reference {
86+
this.instanceUsed_ = true;
87+
return this.rootInternal_;
88+
}
89+
90+
set root_(root: Reference) {
91+
this.rootInternal_ = root;
92+
}
93+
6994
get app(): FirebaseApp {
7095
return this.repo_.app;
7196
}
7297

98+
/**
99+
* Modify this instance to communicate with the Realtime Database emulator.
100+
*
101+
* <p>Note: this must be called before this instance has been used to do any operations.
102+
*
103+
* @param host the emulator host (ex: localhost)
104+
* @param port the emulator port (ex: 8080)
105+
*/
106+
useEmulator(host: string, port: number) {
107+
if (this.instanceUsed_) {
108+
fatal(
109+
'Cannot call useEmulator() after instance has already been initialized.'
110+
);
111+
return;
112+
}
113+
114+
// Get a new Repo which has the emulator settings applied
115+
const manager = RepoManager.getInstance();
116+
const oldRepo = this.repo_;
117+
118+
this.repo_ = manager.cloneRepoForEmulator(oldRepo, host, port);
119+
manager.deleteRepo(oldRepo);
120+
}
121+
73122
/**
74123
* Returns a reference to the root or to the path specified in the provided
75124
* argument.

packages/database/src/core/Repo.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class Repo {
8383
public repoInfo_: RepoInfo,
8484
forceRestClient: boolean,
8585
public app: FirebaseApp,
86-
authTokenProvider: AuthTokenProvider
86+
public authTokenProvider: AuthTokenProvider
8787
) {
8888
this.stats_ = StatsManager.getCollection(repoInfo_);
8989

packages/database/src/core/RepoManager.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,21 @@ export class RepoManager {
8787
}
8888
}
8989

90+
/**
91+
* Create a new repo based on an old one but pointing to a particular host and port.
92+
*/
93+
cloneRepoForEmulator(repo: Repo, host: string, port: number): Repo {
94+
const nodeAdmin = repo.repoInfo_.nodeAdmin;
95+
const url = `http://${host}:${port}?ns=${repo.repoInfo_.namespace}`;
96+
const authTokenProvider = nodeAdmin
97+
? new EmulatorAdminTokenProvider()
98+
: repo.authTokenProvider;
99+
100+
const parsedUrl = parseRepoInfo(url, nodeAdmin);
101+
102+
return this.createRepo(parsedUrl.repoInfo, repo.app, authTokenProvider);
103+
}
104+
90105
/**
91106
* This function should only ever be called to CREATE a new database instance.
92107
*

packages/database/test/database.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,22 @@ describe('Database Tests', () => {
260260
const ref = (db as any).refFromURL();
261261
}).to.throw(/Expects at least 1/);
262262
});
263+
264+
it('can call useEmulator before use', () => {
265+
const db = (firebase as any).database();
266+
db.useEmulator('localhost', 1234);
267+
expect(db.ref().toString()).to.equal('http://localhost:1234/');
268+
});
269+
270+
it('cannot call useEmulator after use', () => {
271+
const db = (firebase as any).database();
272+
273+
db.ref().set({
274+
hello: 'world'
275+
});
276+
277+
expect(() => {
278+
db.useEmulator('localhost', 1234);
279+
}).to.throw(/Cannot call useEmulator/);
280+
});
263281
});

0 commit comments

Comments
 (0)