Skip to content

Commit 0ee16a4

Browse files
mmermerkayaFeiyang1
authored andcommitted
Add Firebase Installations package
1 parent 0acd32c commit 0ee16a4

40 files changed

+3167
-12
lines changed

.github/CODEOWNERS

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
# Used for approving minor changes, large-scale refactorings, and emergency situations.
1414
# (secret team to avoid review requests)
1515
#
16-
# - @Feiyang1
17-
# - @hiranya911
18-
# - @mikelehen
16+
# - @Feiyang1
17+
# - @hiranya911
18+
# - @mikelehen
1919
# - @bojeil-google
2020
# - @depoll
2121
# - @hsubox76
@@ -27,9 +27,9 @@
2727
# Used for approving firestore changes.
2828
# (secret team to avoid review requests)
2929
#
30-
# - @mikelehen
31-
# - @schmidt-sebastian
32-
# - @wilhuff
30+
# - @mikelehen
31+
# - @schmidt-sebastian
32+
# - @wilhuff
3333
# - @gsoltis
3434
# - @var-const
3535
# - @rsgowman
@@ -67,3 +67,6 @@ packages/testing @tonymeng @ryanpbrewster @firebase/jssdk-global-approvers
6767

6868
# RxFire Code
6969
packages/rxfire @davideast @jamesdaniels @firebase/jssdk-global-approvers
70+
71+
# Installations
72+
packages/installations @mmermerkaya @andirayo @firebase/jssdk-global-approvers

packages/installations/karma.conf.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
const karmaBase = require('../../config/karma.base');
19+
20+
module.exports = function(config) {
21+
config.set({
22+
...karmaBase,
23+
files: ['src/**/*.test.ts'],
24+
preprocessors: { '**/*.ts': ['webpack', 'sourcemap'] },
25+
frameworks: ['mocha']
26+
});
27+
};

packages/installations/package.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "@firebase/installations",
3+
"version": "0.0.1",
4+
"main": "dist/index.cjs.js",
5+
"module": "dist/index.esm.js",
6+
"types": "dist/index.d.ts",
7+
"private": true,
8+
"author": "Firebase <[email protected]> (https://firebase.google.com/)",
9+
"license": "Apache-2.0",
10+
"scripts": {
11+
"build": "rollup -c && dts-bundle-generator -o dist/index.d.ts src/index.ts",
12+
"test": "yarn type-check && yarn test:karma && yarn lint",
13+
"test:karma": "karma start --single-run",
14+
"test:debug": "karma start --browsers=Chrome --auto-watch",
15+
"type-check": "tsc -p . --noEmit",
16+
"lint": "tslint --project .",
17+
"lint:fix": "yarn lint --fix && prettier --write 'src/**/*.ts'",
18+
"serve": "yarn serve:build && yarn serve:host",
19+
"serve:build": "rollup -c test-app/rollup.config.js",
20+
"serve:host": "http-server -c-1 test-app"
21+
},
22+
"devDependencies": {
23+
"@types/chai": "4.1.7",
24+
"@types/chai-as-promised": "7.1.0",
25+
"@types/mocha": "5.2.6",
26+
"@types/sinon": "7.0.10",
27+
"@types/sinon-chai": "3.2.2",
28+
"chai": "4.2.0",
29+
"chai-as-promised": "7.1.1",
30+
"dts-bundle-generator": "2.1.0",
31+
"http-server": "0.11.1",
32+
"mocha": "6.0.2",
33+
"rollup": "1.6.0",
34+
"rollup-plugin-commonjs": "9.2.2",
35+
"rollup-plugin-node-resolve": "4.0.1",
36+
"rollup-plugin-replace": "2.1.1",
37+
"rollup-plugin-typescript2": "0.20.1",
38+
"rollup-plugin-uglify": "6.0.2",
39+
"sinon": "7.3.0",
40+
"sinon-chai": "3.3.0",
41+
"tslint": "5.14.0",
42+
"tslint-config-prettier": "1.18.0",
43+
"tslint-no-unused-expression-chai": "0.1.4",
44+
"tslint-plugin-prettier": "2.0.1",
45+
"typescript": "3.3.4000"
46+
},
47+
"dependencies": {
48+
"@firebase/app-types": "0.x",
49+
"@firebase/util": "0.2.11",
50+
"idb": "3.0.2"
51+
}
52+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import typescriptPlugin from 'rollup-plugin-typescript2';
19+
import replace from 'rollup-plugin-replace';
20+
import pkg from './package.json';
21+
import typescript from 'typescript';
22+
23+
const deps = Object.keys({ ...pkg.peerDependencies, ...pkg.dependencies });
24+
25+
export default [
26+
{
27+
input: 'src/index.ts',
28+
output: [
29+
{ file: pkg.main, format: 'cjs', sourcemap: true },
30+
{ file: pkg.module, format: 'es', sourcemap: true }
31+
],
32+
plugins: [
33+
typescriptPlugin({ typescript }),
34+
replace({
35+
__VERSION__: pkg.version
36+
})
37+
],
38+
external: id => deps.some(dep => id === dep || id.startsWith(`${dep}/`))
39+
}
40+
];
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { expect } from 'chai';
19+
import { restore, SinonStub, stub } from 'sinon';
20+
import { createInstallation, generateAuthToken } from './api';
21+
import {
22+
INSTALLATIONS_API_URL,
23+
INTERNAL_AUTH_VERSION,
24+
PACKAGE_VERSION
25+
} from './constants';
26+
import {
27+
CreateInstallationResponse,
28+
GenerateAuthTokenResponse
29+
} from './interfaces/api-response';
30+
import { AppConfig } from './interfaces/app-config';
31+
import {
32+
CompletedAuthToken,
33+
InProgressInstallationEntry,
34+
RegisteredInstallationEntry,
35+
RequestStatus
36+
} from './interfaces/installation-entry';
37+
import { compareHeaders } from './testing/compare-headers';
38+
import './testing/setup';
39+
40+
const FID = 'defenders-of-the-faith';
41+
42+
describe('api', () => {
43+
let appConfig: AppConfig;
44+
let fetchSpy: SinonStub<[RequestInfo, RequestInit?], Promise<Response>>;
45+
46+
beforeEach(async () => {
47+
appConfig = {
48+
apiKey: 'apiKey',
49+
projectId: 'projectId',
50+
appId: '1:777777777777:web:d93b5ca1475efe57'
51+
};
52+
});
53+
54+
afterEach(() => {
55+
restore();
56+
});
57+
58+
describe('createInstallation', () => {
59+
let inProgressInstallationEntry: InProgressInstallationEntry;
60+
61+
beforeEach(() => {
62+
inProgressInstallationEntry = {
63+
fid: FID,
64+
registrationStatus: RequestStatus.IN_PROGRESS,
65+
registrationTime: Date.now()
66+
};
67+
68+
const response: CreateInstallationResponse = {
69+
refreshToken: 'refreshToken',
70+
authToken: {
71+
token:
72+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
73+
expiresIn: '604800s'
74+
}
75+
};
76+
77+
fetchSpy = stub(self, 'fetch').resolves(
78+
new Response(JSON.stringify(response))
79+
);
80+
});
81+
82+
it('registers a pending InstallationEntry', async () => {
83+
const registeredInstallationEntry = await createInstallation(
84+
appConfig,
85+
inProgressInstallationEntry
86+
);
87+
expect(registeredInstallationEntry.registrationStatus).to.equal(
88+
RequestStatus.COMPLETED
89+
);
90+
});
91+
92+
it('calls the createInstallation server API with correct parameters', async () => {
93+
const expectedHeaders = new Headers({
94+
'Content-Type': 'application/json',
95+
Accept: 'application/json',
96+
'x-goog-api-key': 'apiKey'
97+
});
98+
const expectedBody = {
99+
fid: FID,
100+
authVersion: INTERNAL_AUTH_VERSION,
101+
appId: appConfig.appId,
102+
sdkVersion: PACKAGE_VERSION
103+
};
104+
const expectedRequest: RequestInit = {
105+
method: 'POST',
106+
headers: expectedHeaders,
107+
body: JSON.stringify(expectedBody)
108+
};
109+
const expectedEndpoint = `${INSTALLATIONS_API_URL}/projects/projectId/installations`;
110+
111+
await createInstallation(appConfig, inProgressInstallationEntry);
112+
expect(fetchSpy).to.be.calledOnceWith(expectedEndpoint, expectedRequest);
113+
const actualHeaders = fetchSpy.lastCall.lastArg.headers;
114+
compareHeaders(expectedHeaders, actualHeaders);
115+
});
116+
});
117+
118+
describe('generateAuthToken', () => {
119+
let registeredInstallationEntry: RegisteredInstallationEntry;
120+
121+
beforeEach(async () => {
122+
registeredInstallationEntry = {
123+
fid: FID,
124+
registrationStatus: RequestStatus.COMPLETED,
125+
refreshToken: 'refreshToken',
126+
authToken: {
127+
requestStatus: RequestStatus.NOT_STARTED
128+
}
129+
};
130+
131+
const response: GenerateAuthTokenResponse = {
132+
token:
133+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCeKKF2QT4fwpMeJf36POk6yJV_adQssw5c',
134+
expiresIn: '604800s'
135+
};
136+
137+
fetchSpy = stub(self, 'fetch').resolves(
138+
new Response(JSON.stringify(response))
139+
);
140+
});
141+
142+
it('fetches a new Authentication Token', async () => {
143+
const completedAuthToken: CompletedAuthToken = await generateAuthToken(
144+
appConfig,
145+
registeredInstallationEntry
146+
);
147+
expect(completedAuthToken.requestStatus).to.equal(
148+
RequestStatus.COMPLETED
149+
);
150+
});
151+
152+
it('calls the generateAuthToken server API with correct parameters', async () => {
153+
const expectedHeaders = new Headers({
154+
'Content-Type': 'application/json',
155+
Accept: 'application/json',
156+
Authorization: `${INTERNAL_AUTH_VERSION} refreshToken`,
157+
'x-goog-api-key': 'apiKey'
158+
});
159+
const expectedBody = {
160+
installation: {
161+
sdkVersion: PACKAGE_VERSION
162+
}
163+
};
164+
const expectedRequest: RequestInit = {
165+
method: 'POST',
166+
headers: expectedHeaders,
167+
body: JSON.stringify(expectedBody)
168+
};
169+
const expectedEndpoint = `${INSTALLATIONS_API_URL}/projects/projectId/installations/${FID}/authTokens:generate`;
170+
171+
await generateAuthToken(appConfig, registeredInstallationEntry);
172+
173+
expect(fetchSpy).to.be.calledOnceWith(expectedEndpoint, expectedRequest);
174+
const actualHeaders = fetchSpy.lastCall.lastArg.headers;
175+
compareHeaders(expectedHeaders, actualHeaders);
176+
});
177+
});
178+
});

0 commit comments

Comments
 (0)