Skip to content

Commit db51c76

Browse files
author
Lauren Long
committed
add retain, release, and ref counters
1 parent 22f550e commit db51c76

File tree

2 files changed

+205
-8
lines changed

2 files changed

+205
-8
lines changed

src/apps.ts

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,136 @@
11
import * as _ from 'lodash';
22
import * as firebase from 'firebase';
33
import { FirebaseEnv } from './env';
4+
import * as Promise from 'bluebird';
45

56
export interface AuthMode {
67
admin: boolean;
78
variable?: any;
89
}
910

11+
export interface RefCounter {
12+
admin: number;
13+
noauth: number;
14+
user: {[uuid: string]: number};
15+
}
16+
1017
export default class Apps {
1118
private static _noauth: firebase.app.App;
1219
private static _admin: firebase.app.App;
13-
1420
private _env: FirebaseEnv;
21+
private _refCounter: RefCounter;
1522

1623
constructor(env: FirebaseEnv) {
1724
this._env = env;
25+
this._refCounter = {
26+
admin: 0,
27+
noauth: 0,
28+
user: {},
29+
};
30+
}
31+
32+
_waitToDestroyApp(app: firebase.app.App) {
33+
if (!app) {
34+
console.log('resolved promise since app is empty');
35+
return Promise.resolve();
36+
}
37+
return Promise.delay(120000).then(() => {
38+
console.log('deleting app');
39+
return app.delete();
40+
}).then(() => {
41+
console.log('app deleted');
42+
});
43+
}
44+
45+
_changeRefCounter() {
46+
return Promise.delay(120000).then(() => {
47+
console.log('time passed');
48+
this._refCounter = {
49+
admin: 1000,
50+
noauth: 1000,
51+
user: {},
52+
};
53+
});
54+
}
55+
56+
_waitToDestroyUserApp(key: string) {
57+
try {
58+
let app = firebase.app(key);
59+
console.log('deleting user app...');
60+
return this._waitToDestroyApp(app);
61+
} catch (e) {
62+
return Promise.resolve();
63+
}
64+
}
65+
66+
_waitToDestroyAdmin() {
67+
console.log('deleting admin app...');
68+
return this._waitToDestroyApp(Apps._admin);
1869
}
1970

71+
_waitToDestroyNoauth() {
72+
console.log('deleting noauth app...');
73+
return this._waitToDestroyApp(Apps._noauth);
74+
}
75+
76+
retain(payload) {
77+
let auth: AuthMode | null = _.get(payload, 'auth', null);
78+
this._refCounter.admin ++;
79+
if (!auth || typeof auth !== 'object') {
80+
this._refCounter.noauth ++;
81+
} else if (auth.admin) {
82+
this._refCounter.admin ++;
83+
} else if (!auth.variable) {
84+
this._refCounter.noauth ++;
85+
} else {
86+
const key = JSON.stringify(auth.variable);
87+
let count = _.get(this._refCounter.user, key, 0);
88+
_.set(this._refCounter.user, key, count+1);
89+
}
90+
}
91+
92+
release(payload) {
93+
let auth: AuthMode | null = _.get(payload, 'auth', null);
94+
this._refCounter.admin --;
95+
if (!auth || typeof auth !== 'object') {
96+
this._refCounter.noauth --;
97+
} else if (auth.admin) {
98+
this._refCounter.admin --;
99+
} else if (!auth.variable) {
100+
this._refCounter.noauth --;
101+
} else {
102+
const key = JSON.stringify(auth.variable);
103+
let count = _.get(this._refCounter.user, key, 0);
104+
if (count < 2) {
105+
this._waitToDestroyUserApp(key);
106+
}
107+
_.set(this._refCounter.user, key, count-1);
108+
}
109+
if (this._refCounter.admin === 0) {
110+
this._waitToDestroyAdmin();
111+
}
112+
if (this._refCounter.noauth === 0) {
113+
this._waitToDestroyNoauth();
114+
}
115+
}
20116
get admin(): firebase.app.App {
21-
// TODO(inlined) add credential to env
22-
Apps._admin = Apps._admin || firebase.initializeApp(this.firebaseArgs, '__admin__');
23-
return Apps._admin;
117+
try {
118+
return firebase.app('__admin__');
119+
} catch (e) {
120+
console.log('Need to init admin app');
121+
Apps._admin = firebase.initializeApp(this.firebaseArgs, '__admin__');
122+
return Apps._admin;
123+
}
24124
}
25125

26126
get noauth(): firebase.app.App {
27-
Apps._noauth = Apps._noauth ||
28-
firebase.initializeApp(_.omit(this.firebaseArgs, 'credential'), '__noauth__');
29-
return Apps._noauth;
127+
try {
128+
return firebase.app('__noauth__');
129+
} catch (e) {
130+
console.log('Need to init noauth app');
131+
Apps._noauth = firebase.initializeApp(_.omit(this.firebaseArgs, 'credential'), '__noauth__');
132+
return Apps._noauth;
133+
}
30134
}
31135

32136
forMode(auth: AuthMode): firebase.app.App {

test/apps.spec.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from 'chai';
2-
2+
import * as sinon from 'sinon';
33
import { FakeEnv } from './support/helpers';
44
import Apps from '../src/apps';
55
import * as firebase from 'firebase';
@@ -31,4 +31,97 @@ describe('apps', () => {
3131
const userAppAgain = apps.forMode({ admin: false, variable: claims });
3232
expect(userApp).to.equal(userAppAgain);
3333
});
34+
35+
it('should retain/release ref counters appropriately without auth', function() {
36+
apps.retain();
37+
expect(apps._refCounter).to.deep.equal({
38+
admin: 1,
39+
noauth: 1,
40+
user: {},
41+
});
42+
apps.release();
43+
expect(apps._refCounter).to.deep.equal({
44+
admin: 0,
45+
noauth: 0,
46+
user: {},
47+
});
48+
});
49+
50+
it('should retain/release ref counters appropriately with admin auth', function() {
51+
apps.retain({auth: {admin: true}});
52+
expect(apps._refCounter).to.deep.equal({
53+
admin: 2,
54+
noauth: 0,
55+
user: {},
56+
});
57+
apps.release({auth: {admin: true}});
58+
expect(apps._refCounter).to.deep.equal({
59+
admin: 0,
60+
noauth: 0,
61+
user: {},
62+
});
63+
});
64+
65+
it('should retain/release ref counters appropriately with user auth', function() {
66+
apps.retain({auth: {admin: false, variable: 1111}});
67+
expect(apps._refCounter).to.deep.equal({
68+
admin: 1,
69+
noauth: 0,
70+
user: {'1111': 1},
71+
});
72+
apps.release({auth: {admin: false, variable: 1111}});
73+
expect(apps._refCounter).to.deep.equal({
74+
admin: 0,
75+
noauth: 0,
76+
user: {'1111': 0},
77+
});
78+
});
79+
80+
it('should trigger app deletion when ref count is 0', function() {
81+
// overwrite some deletion functions
82+
// apps.retain();
83+
// expect deletion functions to be called
84+
});
85+
86+
it('waits 2 min before destroying app', function(done) {
87+
let clock = sinon.useFakeTimers();
88+
let testApp = firebase.initializeApp(apps.firebaseArgs, 'test');
89+
let spy = sinon.spy();
90+
testApp.delete = spy;
91+
92+
apps._waitToDestroyApp(testApp);
93+
clock.tick(100000);
94+
expect(spy.called).to.be.false;
95+
clock.tick(20000);
96+
expect(spy.called).to.be.true;
97+
98+
clock.restore();
99+
done();
100+
});
101+
102+
it('should be able to recreate admin after destruction', function() {
103+
let getAdmin = () => {
104+
firebase.app('__admin__');
105+
};
106+
let admin = apps.admin;
107+
108+
return admin.delete().then(() => {
109+
expect(getAdmin).to.throw(Error);
110+
expect(apps.admin).to.be.an('object'); // Calling getter creates another app
111+
expect(getAdmin).to.not.throw(Error);
112+
});
113+
});
114+
115+
it('should be able to recreate noauth after destruction', function() {
116+
let getNoauth = () => {
117+
firebase.app('__noauth__');
118+
};
119+
let noauth = apps.noauth;
120+
121+
return noauth.delete().then(() => {
122+
expect(getNoauth).to.throw(Error);
123+
expect(apps.noauth).to.be.an('object'); // Calling getter creates another app
124+
expect(getNoauth).to.not.throw(Error);
125+
});
126+
});
34127
});

0 commit comments

Comments
 (0)