Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement useEmulator for Database #3904

Merged
merged 17 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
7 changes: 7 additions & 0 deletions .changeset/bright-ducks-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'firebase': minor
'@firebase/database': minor
'@firebase/database-types': minor
---

Add a useEmulator(host, port) method to Realtime Database
2 changes: 2 additions & 0 deletions packages/database-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface DataSnapshot {

export interface Database {
app: FirebaseApp;
useEmulator(host: string, port: number): void;
goOffline(): void;
goOnline(): void;
ref(path?: string | Reference): Reference;
Expand All @@ -43,6 +44,7 @@ export interface Database {
export class FirebaseDatabase implements Database {
private constructor();
app: FirebaseApp;
useEmulator(host: string, port: number): void;
goOffline(): void;
goOnline(): void;
ref(path?: string | Reference): Reference;
Expand Down
63 changes: 56 additions & 7 deletions packages/database/src/api/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ import { FirebaseDatabase } from '@firebase/database-types';
*/
export class Database implements FirebaseService {
INTERNAL: DatabaseInternals;
private root_: Reference;

/** Track if the instance has been used (root or repo accessed) */
private instanceUsed_: boolean = false;

/** Backing state for root_ */
private rootInternal_: Reference;

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

/**
* The constructor should not be called by users of our public API.
* @param {!Repo} repo_
* @param {!Repo} repoInternal_
*/
constructor(private repo_: Repo) {
if (!(repo_ instanceof Repo)) {
constructor(private repoInternal_: Repo) {
if (!(repoInternal_ instanceof Repo)) {
fatal(
"Don't call new Database() directly - please use firebase.database()."
);
}

/** @type {Reference} */
this.root_ = new Reference(repo_, Path.Empty);

this.repo_ = repoInternal_;
this.INTERNAL = new DatabaseInternals(this);
}

private get repo_(): Repo {
this.instanceUsed_ = true;
return this.repoInternal_;
}

private set repo_(repo: Repo) {
if (repo instanceof Repo) {
this.root_ = new Reference(repo, Path.Empty);
}

this.repoInternal_ = repo;
}

get root_(): Reference {
this.instanceUsed_ = true;
return this.rootInternal_;
}

set root_(root: Reference) {
this.rootInternal_ = root;
}

get app(): FirebaseApp {
return this.repo_.app;
}

/**
* Modify this instance to communicate with the Realtime Database emulator.
*
* <p>Note: this must be called before this instance has been used to do any operations.
samtstern marked this conversation as resolved.
Show resolved Hide resolved
*
* @param host the emulator host (ex: localhost)
* @param port the emulator port (ex: 8080)
*/
useEmulator(host: string, port: number): void {
if (this.instanceUsed_) {
fatal(
'Cannot call useEmulator() after instance has already been initialized.'
);
return;
}

// Get a new Repo which has the emulator settings applied
const manager = RepoManager.getInstance();
const oldRepo = this.repo_;

this.repo_ = manager.cloneRepoForEmulator(oldRepo, host, port);
manager.deleteRepo(oldRepo);
}

/**
* Returns a reference to the root or to the path specified in the provided
* argument.
Expand Down
2 changes: 1 addition & 1 deletion packages/database/src/core/Repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class Repo {
public repoInfo_: RepoInfo,
forceRestClient: boolean,
public app: FirebaseApp,
authTokenProvider: AuthTokenProvider
public authTokenProvider: AuthTokenProvider
) {
this.stats_ = StatsManager.getCollection(repoInfo_);

Expand Down
15 changes: 15 additions & 0 deletions packages/database/src/core/RepoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ export class RepoManager {
}
}

/**
* Create a new repo based on an old one but pointing to a particular host and port.
*/
cloneRepoForEmulator(repo: Repo, host: string, port: number): Repo {
const nodeAdmin = repo.repoInfo_.nodeAdmin;
const url = `http://${host}:${port}?ns=${repo.repoInfo_.namespace}`;
const authTokenProvider = nodeAdmin
? new EmulatorAdminTokenProvider()
: repo.authTokenProvider;

const parsedUrl = parseRepoInfo(url, nodeAdmin);

return this.createRepo(parsedUrl.repoInfo, repo.app, authTokenProvider);
}

/**
* This function should only ever be called to CREATE a new database instance.
*
Expand Down
18 changes: 18 additions & 0 deletions packages/database/test/database.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,22 @@ describe('Database Tests', () => {
const ref = (db as any).refFromURL();
}).to.throw(/Expects at least 1/);
});

it('can call useEmulator before use', () => {
const db = (firebase as any).database();
db.useEmulator('localhost', 1234);
expect(db.ref().toString()).to.equal('http://localhost:1234/');
});

it('cannot call useEmulator after use', () => {
const db = (firebase as any).database();

db.ref().set({
hello: 'world'
});

expect(() => {
db.useEmulator('localhost', 1234);
}).to.throw(/Cannot call useEmulator/);
});
});
9 changes: 9 additions & 0 deletions packages/firebase/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5647,6 +5647,15 @@ declare namespace firebase.database {
* ```
*/
app: firebase.app.App;
/**
* Modify this instance to communicate with the Realtime Database emulator.
*
* <p>Note: this must be called before this instance has been used to do any operations.
samtstern marked this conversation as resolved.
Show resolved Hide resolved
*
* @param host the emulator host (ex: localhost)
* @param port the emulator port (ex: 8080)
*/
useEmulator(host: string, port: number): void;
/**
* Disconnects from the server (all Database operations will be completed
* offline).
Expand Down