강상위

add 01_KVP sbm demo based on firebase

Showing 1000 changed files with 3813 additions and 0 deletions

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

1 +{
2 + "projects": {
3 + "default": "sbm-demo-22904"
4 + }
5 +}
1 +# Contributing to the Firebase FriendlyChat Codelab
2 +
3 +We'd love for you to contribute to our source code and to make the Firebase FriendlyChat Codelab even better than it is today! Here are the guidelines we'd like you to follow:
4 +
5 + - [Code of Conduct](#coc)
6 + - [Question or Problem?](#question)
7 + - [Issues and Bugs](#issue)
8 + - [Feature Requests](#feature)
9 + - [Submission Guidelines](#submit)
10 + - [Coding Rules](#rules)
11 + - [Signing the CLA](#cla)
12 +
13 +## <a name="coc"></a> Code of Conduct
14 +
15 +As contributors and maintainers of the Firebase FriendlyChat Codelab project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities.
16 +
17 +Communication through any of Firebase's channels (GitHub, StackOverflow, Google+, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
18 +
19 +We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the project to do the same.
20 +
21 +If any member of the community violates this code of conduct, the maintainers of the Firebase Android Quickstarts project may take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate.
22 +
23 +If you are subject to or witness unacceptable behavior, or have any other concerns, please drop us a line at nivco@google.com.
24 +
25 +## <a name="question"></a> Got a Question or Problem?
26 +
27 +If you have questions about how to use the Firebase FriendlyChat Codelab, please direct these to [StackOverflow][stackoverflow] and use the `firebase` tag. We are also available on GitHub issues.
28 +
29 +If you feel that we're missing an important bit of documentation, feel free to
30 +file an issue so we can help. Here's an example to get you started:
31 +
32 +```
33 +What are you trying to do or find out more about?
34 +
35 +Where have you looked?
36 +
37 +Where did you expect to find this information?
38 +```
39 +
40 +## <a name="issue"></a> Found an Issue?
41 +If you find a bug in the source code or a mistake in the documentation, you can help us by
42 +submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request
43 +with a fix.
44 +
45 +See [below](#submit) for some guidelines.
46 +
47 +## <a name="submit"></a> Submission Guidelines
48 +
49 +### Submitting an Issue
50 +Before you submit your issue search the archive, maybe your question was already answered.
51 +
52 +If your issue appears to be a bug, and hasn't been reported, open a new issue.
53 +Help us to maximize the effort we can spend fixing issues and adding new
54 +features, by not reporting duplicate issues. Providing the following information will increase the
55 +chances of your issue being dealt with quickly:
56 +
57 +* **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
58 +* **Motivation for or Use Case** - explain why this is a bug for you
59 +* **Browsers and Operating System** - is this a problem with all browsers or only IE9?
60 +* **Reproduce the Error** - provide a live example (using JSBin) or a unambiguous set of steps.
61 +* **Related Issues** - has a similar issue been reported before?
62 +* **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
63 + causing the problem (line of code or commit)
64 +
65 +**If you get help, help others. Good karma rulez!**
66 +
67 +Here's a template to get you started:
68 +
69 +```
70 +Browser:
71 +Browser version:
72 +Operating system:
73 +Operating system version:
74 +
75 +What steps will reproduce the problem:
76 +1.
77 +2.
78 +3.
79 +
80 +What is the expected result?
81 +
82 +What happens instead of that?
83 +
84 +Please provide any other information below, and attach a screenshot if possible.
85 +```
86 +
87 +### Submitting a Pull Request
88 +Before you submit your pull request consider the following guidelines:
89 +
90 +* Search [GitHub](https://github.com/firebase/codelab-friendlychat/pulls) for an open or closed Pull Request
91 + that relates to your submission. You don't want to duplicate effort.
92 +* Please sign our [Contributor License Agreement (CLA)](#cla) before sending pull
93 + requests. We cannot accept code without this.
94 +* Make your changes in a new git branch:
95 +
96 + ```shell
97 + git checkout -b my-fix-branch master
98 + ```
99 +
100 +* Create your patch, **including appropriate test cases**.
101 +* Follow our [Coding Rules](#rules).
102 +* Avoid checking in files that shouldn't be tracked (e.g `node_modules`, `gulp-cache`, `.tmp`, `.idea`). We recommend using a [global](#global-gitignore) gitignore for this.
103 +* Make sure **not** to include a recompiled version of the files found in `/css` and `/js` as part of your PR. We will generate these automatically.
104 +* Commit your changes using a descriptive commit message.
105 +
106 + ```shell
107 + git commit -a
108 + ```
109 + Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
110 +
111 +* Build your changes locally to ensure all the tests pass:
112 +
113 + ```shell
114 + gulp
115 + ```
116 +
117 +* Push your branch to GitHub:
118 +
119 + ```shell
120 + git push origin my-fix-branch
121 + ```
122 +
123 +* In GitHub, send a pull request to `codelab-friendlychat:master`.
124 +* If we suggest changes then:
125 + * Make the required updates.
126 + * Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
127 +
128 + ```shell
129 + git rebase master -i
130 + git push origin my-fix-branch -f
131 + ```
132 +
133 +That's it! Thank you for your contribution!
134 +
135 +#### After your pull request is merged
136 +
137 +After your pull request is merged, you can safely delete your branch and pull the changes
138 +from the main (upstream) repository:
139 +
140 +* Delete the remote branch on GitHub either through the GitHub Android UI or your local shell as follows:
141 +
142 + ```shell
143 + git push origin --delete my-fix-branch
144 + ```
145 +
146 +* Check out the master branch:
147 +
148 + ```shell
149 + git checkout master -f
150 + ```
151 +
152 +* Delete the local branch:
153 +
154 + ```shell
155 + git branch -D my-fix-branch
156 + ```
157 +
158 +* Update your master with the latest upstream version:
159 +
160 + ```shell
161 + git pull --ff upstream master
162 + ```
163 +
164 +## <a name="cla"></a> Signing the CLA
165 +
166 +Please sign our [Contributor License Agreement][google-cla] (CLA) before sending pull requests. For any code
167 +changes to be accepted, the CLA must be signed. It's a quick process, we promise!
168 +
169 +*This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).*
170 +
171 +[github]: https://github.com/firebase/friendlychat
172 +[google-cla]: https://cla.developers.google.com
173 +[js-style-guide]: http://google.github.io/styleguide/javascriptguide.xml
174 +[py-style-guide]: http://google.github.io/styleguide/pyguide.html
175 +[jsbin]: http://jsbin.com/
176 +[stackoverflow]: http://stackoverflow.com/questions/tagged/firebase
177 +[global-gitignore]: https://help.github.com/articles/ignoring-files/#create-a-global-gitignore
This diff is collapsed. Click to expand it.
1 +# Firebase Codelab: FriendlyChat
2 +
3 +This is the source code for the Firebase FriendlyChat codelab. It includes start and end versions of the
4 +code for Web and Cloud Functions. To get started open the codelab instructions:
5 +
6 + - [Firebase Web Codelab](https://codelabs.developers.google.com/codelabs/firebase-web/).
7 + - [Firebase SDK for Cloud Functions Codelab](https://codelabs.developers.google.com/codelabs/firebase-cloud-functions/).
8 +
9 +
10 +## How to make contributions?
11 +Please read and follow the steps in the [CONTRIBUTING.md](CONTRIBUTING.md)
12 +
13 +
14 +## License
15 +See [LICENSE](LICENSE)
1 +{
2 + "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 + "project": {
4 + "name": "cloud-functions-angular-start"
5 + },
6 + "apps": [
7 + {
8 + "root": "src",
9 + "outDir": "dist",
10 + "assets": [
11 + "assets",
12 + "favicon.ico",
13 + "firebase-messaging-sw.js"
14 + ],
15 + "index": "index.html",
16 + "main": "main.ts",
17 + "polyfills": "polyfills.ts",
18 + "test": "test.ts",
19 + "tsconfig": "tsconfig.app.json",
20 + "testTsconfig": "tsconfig.spec.json",
21 + "prefix": "app",
22 + "styles": [
23 + "styles.css"
24 + ],
25 + "scripts": [],
26 + "environmentSource": "environments/environment.ts",
27 + "environments": {
28 + "dev": "environments/environment.ts",
29 + "prod": "environments/environment.prod.ts"
30 + }
31 + }
32 + ],
33 + "e2e": {
34 + "protractor": {
35 + "config": "./protractor.conf.js"
36 + }
37 + },
38 + "lint": [
39 + {
40 + "project": "src/tsconfig.app.json",
41 + "exclude": "**/node_modules/**"
42 + },
43 + {
44 + "project": "src/tsconfig.spec.json",
45 + "exclude": "**/node_modules/**"
46 + },
47 + {
48 + "project": "e2e/tsconfig.e2e.json",
49 + "exclude": "**/node_modules/**"
50 + }
51 + ],
52 + "test": {
53 + "karma": {
54 + "config": "./karma.conf.js"
55 + }
56 + },
57 + "defaults": {
58 + "styleExt": "css",
59 + "component": {}
60 + }
61 +}
1 +# Editor configuration, see http://editorconfig.org
2 +root = true
3 +
4 +[*]
5 +charset = utf-8
6 +indent_style = space
7 +indent_size = 2
8 +insert_final_newline = true
9 +trim_trailing_whitespace = true
10 +
11 +[*.md]
12 +max_line_length = off
13 +trim_trailing_whitespace = false
1 +{}
...\ No newline at end of file ...\ No newline at end of file
1 +# See http://help.github.com/ignore-files/ for more about ignoring files.
2 +
3 +# compiled output
4 +/dist
5 +/dist-server
6 +/tmp
7 +/out-tsc
8 +
9 +# dependencies
10 +/node_modules
11 +/functions/node_modules
12 +
13 +# IDEs and editors
14 +/.idea
15 +.project
16 +.classpath
17 +.c9/
18 +*.launch
19 +.settings/
20 +*.sublime-workspace
21 +
22 +# IDE - VSCode
23 +.vscode/*
24 +!.vscode/settings.json
25 +!.vscode/tasks.json
26 +!.vscode/launch.json
27 +!.vscode/extensions.json
28 +
29 +# misc
30 +/.sass-cache
31 +/connect.lock
32 +/coverage
33 +/libpeerconnection.log
34 +npm-debug.log
35 +yarn-error.log
36 +testem.log
37 +/typings
38 +
39 +# e2e
40 +/e2e/*.js
41 +/e2e/*.map
42 +
43 +# System Files
44 +.DS_Store
45 +Thumbs.db
1 +# Firebase SDK for Cloud Functions (Angular) Codelab - Start code
2 +
3 +This folder contains the start code of the [Firebase SDK for Cloud Functions (Angular) Codelab](https://codelabs.developers.google.com/codelabs/firebase-cloud-functions-angular/).
4 +
5 +If you'd like to jump directly to the end and see the finished code head to the [cloud-functions-angular](../cloud-functions-angular) directory.
6 +
7 +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0.
8 +
9 +# Running locally
10 +
11 +## Development server
12 +
13 +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
14 +
15 +## Code scaffolding
16 +
17 +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
18 +
19 +## Build
20 +
21 +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
22 +
23 +## Running unit tests
24 +
25 +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
26 +
27 +## Running end-to-end tests
28 +
29 +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
30 +
31 +## Further help
32 +
33 +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
34 +
35 +# Deploying
36 +
37 +Run `firebase deploy` to deploy hosting, database, and functions. Run
38 +`firebase deploy --except` to only deploy hosting and database.
1 +{
2 + "rules": {
3 + "messages": {
4 + ".read": "auth !== null",
5 + ".write": "auth !== null"
6 + },
7 + "fcmTokens": {
8 + "$token": {
9 + ".read": "data.val() === auth.uid", // Users can only read their own device tokens
10 + ".write": "!data.exists() || data.val() === auth.uid", // Users can only write to their own device tokens
11 + ".validate": "newData.val() === auth.uid" // value has to be the UID of the user
12 + }
13 + }
14 + }
15 +}
1 +import { CloudFunctionsAngularStartPage } from './app.po';
2 +
3 +describe('cloud-functions-angular-start App', () => {
4 + let page: CloudFunctionsAngularStartPage;
5 +
6 + beforeEach(() => {
7 + page = new CloudFunctionsAngularStartPage();
8 + });
9 +
10 + it('should display welcome message', () => {
11 + page.navigateTo();
12 + expect(page.getParagraphText()).toEqual('Welcome to app!');
13 + });
14 +});
1 +import { browser, element, by } from 'protractor';
2 +
3 +export class CloudFunctionsAngularStartPage {
4 + navigateTo() {
5 + return browser.get('/');
6 + }
7 +
8 + getParagraphText() {
9 + return element(by.css('app-root h1')).getText();
10 + }
11 +}
1 +{
2 + "extends": "../tsconfig.json",
3 + "compilerOptions": {
4 + "outDir": "../out-tsc/e2e",
5 + "baseUrl": "./",
6 + "module": "commonjs",
7 + "target": "es5",
8 + "types":[
9 + "jasmine",
10 + "jasminewd2",
11 + "node"
12 + ]
13 + }
14 +}
1 +{
2 + "database": {
3 + "rules": "database.rules.json"
4 + },
5 + "hosting": {
6 + "public": "dist",
7 + "ignore": [
8 + "firebase.json",
9 + "**/.*",
10 + "**/node_modules/**"
11 + ],
12 + "rewrites": [
13 + {
14 + "source": "**",
15 + "destination": "/index.html"
16 + }
17 + ]
18 + },
19 + "storage": {
20 + "rules": "storage.rules"
21 + }
22 +}
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +// TODO(DEVELOPER): Import the Cloud Functions for Firebase and the Firebase Admin modules here.
18 +
19 +// TODO(DEVELOPER): Write the addWelcomeMessages Function here.
20 +
21 +// TODO(DEVELOPER): Write the blurOffensiveImages Function here.
22 +
23 +// TODO(DEVELOPER): Write the sendNotifications Function here.
24 +
25 +// (OPTIONAL) TODO(DEVELOPER): Write the annotateMessages Function here.
1 +{
2 + "name": "functions",
3 + "description": "Cloud Functions for Firebase",
4 + "dependencies": {
5 + "firebase-admin": "~5.12.0",
6 + "firebase-functions": "^1.0.1"
7 + },
8 + "scripts": {
9 + "serve": "firebase serve --only functions",
10 + "shell": "firebase functions:shell",
11 + "start": "npm run shell",
12 + "deploy": "firebase deploy --only functions",
13 + "logs": "firebase functions:log"
14 + },
15 + "private": true
16 +}
1 +// Karma configuration file, see link for more information
2 +// https://karma-runner.github.io/0.13/config/configuration-file.html
3 +
4 +module.exports = function (config) {
5 + config.set({
6 + basePath: '',
7 + frameworks: ['jasmine', '@angular/cli'],
8 + plugins: [
9 + require('karma-jasmine'),
10 + require('karma-chrome-launcher'),
11 + require('karma-jasmine-html-reporter'),
12 + require('karma-coverage-istanbul-reporter'),
13 + require('@angular/cli/plugins/karma')
14 + ],
15 + client:{
16 + clearContext: false // leave Jasmine Spec Runner output visible in browser
17 + },
18 + coverageIstanbulReporter: {
19 + reports: [ 'html', 'lcovonly' ],
20 + fixWebpackSourcePaths: true
21 + },
22 + angularCli: {
23 + environment: 'dev'
24 + },
25 + reporters: ['progress', 'kjhtml'],
26 + port: 9876,
27 + colors: true,
28 + logLevel: config.LOG_INFO,
29 + autoWatch: true,
30 + browsers: ['Chrome'],
31 + singleRun: false
32 + });
33 +};
1 +{
2 + "name": "cloud-functions-angular-start",
3 + "version": "0.0.0",
4 + "license": "MIT",
5 + "scripts": {
6 + "ng": "ng",
7 + "start": "ng serve",
8 + "build": "ng build --prod",
9 + "test": "ng test",
10 + "lint": "ng lint",
11 + "e2e": "ng e2e"
12 + },
13 + "private": true,
14 + "dependencies": {
15 + "@angular/animations": "^5.2.0",
16 + "@angular/cdk": "^5.2.0",
17 + "@angular/common": "^5.2.0",
18 + "@angular/compiler": "^5.2.0",
19 + "@angular/core": "^5.2.0",
20 + "@angular/forms": "^5.2.0",
21 + "@angular/http": "^5.2.0",
22 + "@angular/material": "^5.2.0",
23 + "@angular/platform-browser": "^5.2.0",
24 + "@angular/platform-browser-dynamic": "^5.2.0",
25 + "@angular/router": "^5.2.0",
26 + "angularfire2": "^5.0.0-rc.6.0",
27 + "core-js": "^2.4.1",
28 + "firebase": "^4.12.1",
29 + "rxjs": "^5.5.6",
30 + "zone.js": "^0.8.19"
31 + },
32 + "devDependencies": {
33 + "@angular/cli": "~1.7.4",
34 + "@angular/compiler-cli": "^5.2.0",
35 + "@angular/language-service": "^5.2.0",
36 + "@types/jasmine": "~2.8.3",
37 + "@types/jasminewd2": "~2.0.2",
38 + "@types/node": "~6.0.60",
39 + "codelyzer": "^4.0.1",
40 + "jasmine-core": "~2.8.0",
41 + "jasmine-spec-reporter": "~4.2.1",
42 + "karma": "~2.0.0",
43 + "karma-chrome-launcher": "~2.2.0",
44 + "karma-coverage-istanbul-reporter": "^1.2.1",
45 + "karma-jasmine": "~1.1.0",
46 + "karma-jasmine-html-reporter": "^0.2.2",
47 + "protractor": "~5.1.2",
48 + "ts-node": "~4.1.0",
49 + "tslint": "~5.9.1",
50 + "typescript": "~2.5.3"
51 + }
52 +}
1 +// Protractor configuration file, see link for more information
2 +// https://github.com/angular/protractor/blob/master/lib/config.ts
3 +
4 +const { SpecReporter } = require('jasmine-spec-reporter');
5 +
6 +exports.config = {
7 + allScriptsTimeout: 11000,
8 + specs: [
9 + './e2e/**/*.e2e-spec.ts'
10 + ],
11 + capabilities: {
12 + 'browserName': 'chrome'
13 + },
14 + directConnect: true,
15 + baseUrl: 'http://localhost:4200/',
16 + framework: 'jasmine',
17 + jasmineNodeOpts: {
18 + showColors: true,
19 + defaultTimeoutInterval: 30000,
20 + print: function() {}
21 + },
22 + onPrepare() {
23 + require('ts-node').register({
24 + project: 'e2e/tsconfig.e2e.json'
25 + });
26 + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 + }
28 +};
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +main, #messages-card {
17 + height: 100%;
18 + padding-bottom: 0;
19 +}
20 +#messages-card-container {
21 + height: calc(100% - 150px);
22 + padding-bottom: 0;
23 +}
24 +#messages-card {
25 + margin-top: 15px;
26 +}
27 +.mdl-layout__header-row span {
28 + margin-left: 15px;
29 + margin-top: 17px;
30 +}
31 +.mdl-grid {
32 + max-width: 1024px;
33 + margin: auto;
34 +}
35 +.material-icons {
36 + font-size: 36px;
37 + top: 8px;
38 + position: relative;
39 +}
40 +.mdl-layout__header-row {
41 + padding: 0;
42 + margin: 0 auto;
43 +}
44 +.mdl-card__supporting-text {
45 + width: auto;
46 + height: 100%;
47 + padding-top: 0;
48 + padding-bottom: 0;
49 +}
50 +#messages {
51 + overflow-y: auto;
52 + margin-bottom: 10px;
53 + height: calc(100% - 80px);
54 +}
55 +#message-filler {
56 + flex-grow: 1;
57 +}
58 +.message-container:first-of-type {
59 + border-top-width: 0;
60 +}
61 +.message-container {
62 + display: block;
63 + margin-top: 10px;
64 + border-top: 1px solid #f3f3f3;
65 + padding-top: 10px;
66 + /*opacity: 0;*/
67 + transition: opacity 1s ease-in-out;
68 +}
69 +.message-container.visible {
70 + opacity: 1;
71 +}
72 +.message-container .pic {
73 + background-image: url('/assets/images/profile_placeholder.png');
74 + background-repeat: no-repeat;
75 + width: 30px;
76 + height: 30px;
77 + background-size: 30px;
78 + border-radius: 20px;
79 +}
80 +.message-container .spacing {
81 + display: table-cell;
82 + vertical-align: top;
83 +}
84 +.message-container .message {
85 + display: table-cell;
86 + width: calc(100% - 40px);
87 + padding: 5px 0 5px 10px;
88 +}
89 +.message-container .name {
90 + display: inline-block;
91 + width: 100%;
92 + padding-left: 40px;
93 + color: #bbb;
94 + font-style: italic;
95 + font-size: 12px;
96 + box-sizing: border-box;
97 +}
98 +#message-form {
99 + display: flex;
100 + flex-direction: row;
101 + width: calc(100% - 48px);
102 + float: left;
103 +}
104 +#image-form {
105 + display: flex;
106 + flex-direction: row;
107 + width: 48px;
108 + float: right;
109 +}
110 +#message-form .mdl-textfield {
111 + width: calc(100% - 100px);
112 +}
113 +#message-form button, #image-form button {
114 + width: 100px;
115 + margin: 15px 0 0 10px;
116 +}
117 +.mdl-card {
118 + min-height: 0;
119 +}
120 +.mdl-card {
121 + background: linear-gradient(white, #f9f9f9);
122 + justify-content: space-between;
123 +}
124 +#user-container {
125 + position: absolute;
126 + display: flex;
127 + flex-direction: row;
128 + top: 22px;
129 + width: 100%;
130 + right: 0;
131 + padding-left: 10px;
132 + justify-content: flex-end;
133 + padding-right: 10px;
134 +}
135 +#user-container #user-pic {
136 + top: -3px;
137 + position: relative;
138 + display: inline-block;
139 + background-image: url('/assets/images/profile_placeholder.png');
140 + background-repeat: no-repeat;
141 + width: 40px;
142 + height: 40px;
143 + background-size: 40px;
144 + border-radius: 20px;
145 +}
146 +#user-container #user-name {
147 + font-size: 16px;
148 + line-height: 36px;
149 + padding-right: 10px;
150 + padding-left: 20px;
151 +}
152 +#image-form #submitImage {
153 + width: auto;
154 + padding: 0 6px 0 1px;
155 + min-width: 0;
156 +}
157 +#image-form #submitImage .material-icons {
158 + top: -1px;
159 +}
160 +.message img {
161 + max-width: 300px;
162 + max-height: 200px;
163 +}
164 +#mediaCapture {
165 + display: none;
166 +}
167 +@media screen and (max-width: 610px) {
168 + header {
169 + height: 113px;
170 + padding-bottom: 80px !important;
171 + }
172 + #user-container {
173 + top: 72px;
174 + background-color: rgb(3,155,229);
175 + height: 38px;
176 + padding-top: 3px;
177 + padding-right: 2px;
178 + }
179 + #user-container #user-pic {
180 + top: 2px;
181 + width: 33px;
182 + height: 33px;
183 + background-size: 33px;
184 + }
185 +}
186 +.mdl-textfield__label:after {
187 + background-color: #0288D1;
188 +}
189 +.mdl-textfield--floating-label.is-focused .mdl-textfield__label {
190 + color: #0288D1;
191 +}
192 +.mdl-button .material-icons {
193 + top: -1px;
194 + margin-right: 5px;
195 +}
1 +<div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header">
2 + <!-- Header section containing logo -->
3 + <header class="mdl-layout__header mdl-color-text--white mdl-color--light-blue-700">
4 + <div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
5 + <div class="mdl-layout__header-row mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--12-col-desktop">
6 + <h3><i class="material-icons">chat_bubble_outline</i> Friendly Chat</h3>
7 + </div>
8 + <div id="user-container">
9 + <div *ngIf="currentUser" id="user-pic" [ngStyle]="profilePicStyles"></div>
10 + <div *ngIf="currentUser" id="user-name">{{ (user | async)?.displayName }}</div>
11 + <button *ngIf="currentUser" id="sign-out" (click)="logout()" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-color-text--white">
12 + Sign-out
13 + </button>
14 + <button *ngIf="!currentUser" id="sign-in" (click)="login()" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-color-text--white">
15 + <i class="material-icons">account_circle</i>Sign-in with Google
16 + </button>
17 + </div>
18 + </div>
19 + </header>
20 +
21 + <main class="mdl-layout__content mdl-color--grey-100">
22 + <div id="messages-card-container" class="mdl-cell mdl-cell--12-col mdl-grid">
23 +
24 + <!-- Messages container -->
25 + <div id="messages-card" class="mdl-card mdl-shadow--2dp mdl-cell mdl-cell--12-col mdl-cell--6-col-tablet mdl-cell--6-col-desktop">
26 + <div class="mdl-card__supporting-text mdl-color-text--grey-600">
27 + <div id="messages">
28 + <span id="message-filler"></span>
29 + <div class="message-container" *ngFor="let message of messages | async">
30 + <div class="spacing">
31 + <div class="pic" [style.background-image]="'url(' + message.photoUrl + ')'"></div>
32 + </div>
33 + <div class="message">
34 + <span *ngIf="message.text" [style]="message.sentiment | stylize">{{ message.text }}</span>
35 + <img *ngIf="message.imageUrl" [src]="message.imageUrl + (message.moderated ? '&amp;moderated=1' : '')">
36 + </div>
37 + <div class="name">{{ message.name }}</div>
38 + </div>
39 + </div>
40 + <div *ngIf="topics"><em>Recent topics: {{ topics }}</em></div>
41 + <form id="message-form" (submit)="saveMessage($event, box)">
42 + <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
43 + <input #box class="mdl-textfield__input" type="text" id="message"
44 + (keyup)="update(box.value)" (blur)="update(box.value)"
45 + placeholder="Message...">
46 + </div>
47 + <button id="submit" [disabled]="!value" type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect">
48 + Send
49 + </button>
50 + </form>
51 + <form id="image-form" action="#">
52 + <input id="mediaCapture" type="file" accept="image/*,capture=camera" (change)="saveImageMessage($event)">
53 + <button id="submitImage" title="Add an image" (click)="onImageClick($event)"
54 + class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-color--amber-400 mdl-color-text--white">
55 + <i class="material-icons">image</i>
56 + </button>
57 + </form>
58 + </div>
59 + </div>
60 + </div>
61 + </main>
62 +</div>
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +import { TestBed, async } from '@angular/core/testing';
17 +
18 +import { AppComponent } from './app.component';
19 +
20 +describe('AppComponent', () => {
21 + beforeEach(async(() => {
22 + TestBed.configureTestingModule({
23 + declarations: [
24 + AppComponent
25 + ],
26 + }).compileComponents();
27 + }));
28 +
29 + it('should create the app', async(() => {
30 + const fixture = TestBed.createComponent(AppComponent);
31 + const app = fixture.debugElement.componentInstance;
32 + expect(app).toBeTruthy();
33 + }));
34 +
35 + it(`should have as title 'app works!'`, async(() => {
36 + const fixture = TestBed.createComponent(AppComponent);
37 + const app = fixture.debugElement.componentInstance;
38 + expect(app.title).toEqual('app works!');
39 + }));
40 +
41 + it('should render title in a h1 tag', async(() => {
42 + const fixture = TestBed.createComponent(AppComponent);
43 + fixture.detectChanges();
44 + const compiled = fixture.debugElement.nativeElement;
45 + expect(compiled.querySelector('h1').textContent).toContain('app works!');
46 + }));
47 +});
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +import { Component, Inject } from '@angular/core';
17 +import { Observable } from 'rxjs/Observable';
18 +import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
19 +import { AngularFireAuth } from 'angularfire2/auth';
20 +import { MatSnackBar } from '@angular/material';
21 +import * as firebase from 'firebase';
22 +
23 +const LOADING_IMAGE_URL = 'https://www.google.com/images/spin-32.gif';
24 +const PROFILE_PLACEHOLDER_IMAGE_URL = '/assets/images/profile_placeholder.png';
25 +
26 +@Component({
27 + selector: 'app-root',
28 + templateUrl: './app.component.html',
29 + styleUrls: ['./app.component.css']
30 +})
31 +export class AppComponent {
32 + user: Observable<firebase.User>;
33 + currentUser: firebase.User;
34 + messages: Observable<any[]>;
35 + profilePicStyles: {};
36 + topics = '';
37 + value = '';
38 +
39 + constructor(public db: AngularFireDatabase, public afAuth: AngularFireAuth, public snackBar: MatSnackBar) {
40 + this.user = afAuth.authState;
41 + this.user.subscribe((user: firebase.User) => {
42 + console.log(user);
43 + this.currentUser = user;
44 +
45 + if (user) { // User is signed in!
46 + this.profilePicStyles = {
47 + 'background-image': `url(${this.currentUser.photoURL})`
48 + };
49 +
50 + // We load currently existing chat messages.
51 + this.messages = this.db.list<any>('/messages', ref => ref.limitToLast(12)).valueChanges();
52 + this.messages.subscribe((messages) => {
53 + // Calculate list of recently discussed topics
54 + const topicsMap = {};
55 + const topics = [];
56 + let hasEntities = false;
57 + messages.forEach((message) => {
58 + if (message.entities) {
59 + for (let entity of message.entities) {
60 + if (!topicsMap.hasOwnProperty(entity.name)) {
61 + topicsMap[entity.name] = 0
62 + }
63 + topicsMap[entity.name] += entity.salience;
64 + hasEntities = true;
65 + }
66 + }
67 + });
68 + if (hasEntities) {
69 + for (let name in topicsMap) {
70 + topics.push({ name, score: topicsMap[name] });
71 + }
72 + topics.sort((a, b) => b.score - a.score);
73 + this.topics = topics.map((topic) => topic.name).join(', ');
74 + }
75 +
76 + // Make sure new message scroll into view
77 + setTimeout(() => {
78 + const messageList = document.getElementById('messages');
79 + messageList.scrollTop = messageList.scrollHeight;
80 + document.getElementById('message').focus();
81 + }, 500);
82 + });
83 +
84 + // We save the Firebase Messaging Device token and enable notifications.
85 + this.saveMessagingDeviceToken();
86 + } else { // User is signed out!
87 + this.profilePicStyles = {
88 + 'background-image': PROFILE_PLACEHOLDER_IMAGE_URL
89 + };
90 + this.topics = '';
91 + }
92 + });
93 + }
94 +
95 + login() {
96 + this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider());
97 + }
98 +
99 + logout() {
100 + this.afAuth.auth.signOut();
101 + }
102 +
103 + // TODO: Refactor into text message form component
104 + update(value: string) {
105 + this.value = value;
106 + }
107 +
108 + // Returns true if user is signed-in. Otherwise false and displays a message.
109 + checkSignedInWithMessage() {
110 + // Return true if the user is signed in Firebase
111 + if (this.currentUser) {
112 + return true;
113 + }
114 +
115 + this.snackBar
116 + .open('You must sign-in first', 'Sign in', {
117 + duration: 5000
118 + })
119 + .onAction()
120 + .subscribe(() => this.login());
121 +
122 + return false;
123 + };
124 +
125 + // TODO: Refactor into text message form component
126 + saveMessage(event: any, el: HTMLInputElement) {
127 + event.preventDefault();
128 +
129 + if (this.value && this.checkSignedInWithMessage()) {
130 + // Add a new message entry to the Firebase Database.
131 + const messages = this.db.list('/messages');
132 + messages.push({
133 + name: this.currentUser.displayName,
134 + text: this.value,
135 + photoUrl: this.currentUser.photoURL || PROFILE_PLACEHOLDER_IMAGE_URL
136 + }).then(() => {
137 + // Clear message text field and SEND button state.
138 + el.value = '';
139 + }, (err) => {
140 + this.snackBar.open('Error writing new message to Firebase Database.', null, {
141 + duration: 5000
142 + });
143 + console.error(err);
144 + });
145 + }
146 + }
147 +
148 + // TODO: Refactor into image message form component
149 + saveImageMessage(event: any) {
150 + event.preventDefault();
151 + const file = event.target.files[0];
152 +
153 + // Clear the selection in the file picker input.
154 + const imageForm = <HTMLFormElement>document.getElementById('image-form');
155 + imageForm.reset();
156 +
157 + // Check if the file is an image.
158 + if (!file.type.match('image.*')) {
159 + this.snackBar.open('You can only share images', null, {
160 + duration: 5000
161 + });
162 + return;
163 + }
164 +
165 + // Check if the user is signed-in
166 + if (this.checkSignedInWithMessage()) {
167 +
168 + // We add a message with a loading icon that will get updated with the shared image.
169 + const messages = this.db.list('/messages');
170 + messages.push({
171 + name: this.currentUser.displayName,
172 + imageUrl: LOADING_IMAGE_URL,
173 + photoUrl: this.currentUser.photoURL || PROFILE_PLACEHOLDER_IMAGE_URL
174 + }).then((data) => {
175 + // Upload the image to Cloud Storage.
176 + const filePath = `${this.currentUser.uid}/${data.key}/${file.name}`;
177 + return firebase.storage().ref(filePath).put(file)
178 + .then((snapshot) => {
179 + // Get the file's Storage URI and update the chat message placeholder.
180 + const fullPath = snapshot.metadata.fullPath;
181 + const imageUrl = firebase.storage().ref(fullPath).toString();
182 + return firebase.storage().refFromURL(imageUrl).getMetadata();
183 + }).then((metadata) => {
184 + // TODO: Instead of saving the download URL, save the GCS URI and
185 + // dynamically load the download URL when displaying the image
186 + // message.
187 + return data.update({
188 + imageUrl: metadata.downloadURLs[0]
189 + });
190 + });
191 + }).then(console.log, (err) => {
192 + this.snackBar.open('There was an error uploading a file to Cloud Storage.', null, {
193 + duration: 5000
194 + });
195 + console.error(err);
196 + });
197 + }
198 + }
199 +
200 + // TODO: Refactor into image message form component
201 + onImageClick(event: any) {
202 + event.preventDefault();
203 + document.getElementById('mediaCapture').click();
204 + }
205 +
206 + // Saves the messaging device token to the datastore.
207 + saveMessagingDeviceToken() {
208 + return firebase.messaging().getToken()
209 + .then((currentToken) => {
210 + if (currentToken) {
211 + console.log('Got FCM device token:', currentToken);
212 + // Save the Device Token to the datastore.
213 + firebase.database()
214 + .ref('/fcmTokens')
215 + .child(currentToken)
216 + .set(this.currentUser.uid);
217 + } else {
218 + // Need to request permissions to show notifications.
219 + return this.requestNotificationsPermissions();
220 + }
221 + }).catch((err) => {
222 + this.snackBar.open('Unable to get messaging token.', null, {
223 + duration: 5000
224 + });
225 + console.error(err);
226 + });
227 + };
228 +
229 + // Requests permissions to show notifications.
230 + requestNotificationsPermissions() {
231 + console.log('Requesting notifications permission...');
232 + return firebase.messaging().requestPermission()
233 + // Notification permission granted.
234 + .then(() => this.saveMessagingDeviceToken())
235 + .catch((err) => {
236 + this.snackBar.open('Unable to get permission to notify.', null, {
237 + duration: 5000
238 + });
239 + console.error(err);
240 + });
241 + };
242 +}
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +import { BrowserModule } from '@angular/platform-browser';
17 +import { NgModule } from '@angular/core';
18 +import { FormsModule } from '@angular/forms';
19 +import { HttpModule } from '@angular/http';
20 +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
21 +import { AngularFireModule } from 'angularfire2';
22 +import { AngularFireDatabaseModule } from 'angularfire2/database';
23 +import { AngularFireAuthModule } from 'angularfire2/auth';
24 +import { MatSnackBarModule } from '@angular/material';
25 +
26 +import { AppComponent } from './app.component';
27 +import { StylizePipe } from './stylize.pipe';
28 +import { environment } from '../environments/environment';
29 +
30 +const configErrMsg = `You have not configured and imported the Firebase SDK.
31 +Make sure you go through the codelab setup instructions.`;
32 +
33 +const bucketErrMsg = `Your Firebase Storage bucket has not been enabled. Sorry
34 +about that. This is actually a Firebase bug that occurs rarely. Please go and
35 +re-generate the Firebase initialization snippet (step 4 of the codelab) and make
36 +sure the storageBucket attribute is not empty. You may also need to visit the
37 +Storage tab and paste the name of your bucket which is displayed there.`;
38 +
39 +if (!environment.firebase) {
40 + if (!environment.firebase.apiKey) {
41 + window.alert(configErrMsg);
42 + } else if (environment.firebase.storageBucket === '') {
43 + window.alert(bucketErrMsg);
44 + }
45 +}
46 +
47 +@NgModule({
48 + declarations: [
49 + AppComponent,
50 + StylizePipe
51 + ],
52 + imports: [
53 + BrowserModule,
54 + FormsModule,
55 + HttpModule,
56 + BrowserAnimationsModule,
57 + MatSnackBarModule,
58 + AngularFireModule.initializeApp(environment.firebase),
59 + AngularFireDatabaseModule,
60 + AngularFireAuthModule
61 + ],
62 + providers: [],
63 + bootstrap: [AppComponent]
64 +})
65 +export class AppModule { }
1 +import { Pipe, PipeTransform } from '@angular/core';
2 +import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
3 +
4 +interface Sentiment {
5 + score: number,
6 + magnitude: number
7 +}
8 +
9 +/*
10 + * Styles a message based on its sentiment.
11 + */
12 +@Pipe({name: 'stylize'})
13 +export class StylizePipe implements PipeTransform {
14 + constructor(private sanitizer: DomSanitizer) {}
15 +
16 + transform(sentiment: Sentiment): string|SafeStyle {
17 + if (!sentiment) {
18 + return '';
19 + }
20 +
21 + let style = '';
22 +
23 + // Change font based on positive/negative score.
24 + if (sentiment.score >= 0.9) {
25 + style += `font-family: 'Bonbon', 'Roboto', 'Helvetica', sans-serif;`;
26 + } else if (sentiment.score >= 0.5) {
27 + style += `font-family: 'Crafty Girls', 'Roboto', 'Helvetica', sans-serif;`;
28 + } else if (sentiment.score <= -0.9) {
29 + style += `font-family: 'Creepster', 'Roboto', 'Helvetica', sans-serif;`;
30 + } else if (sentiment.score <= -0.5) {
31 + style += `font-family: 'Julee', 'Roboto', 'Helvetica', sans-serif;`;
32 + }
33 +
34 + // Make bold if the magnitude is greater than 1.
35 + if (sentiment.magnitude >= 1) {
36 + style += `font-weight: bold;`;
37 + }
38 +
39 + return style ? this.sanitizer.bypassSecurityTrustStyle(style) : '';
40 + }
41 +}
1 +{
2 + "name": "Friendly Chat",
3 + "short_name": "Friendly Chat",
4 + "start_url": "/index.html",
5 + "display": "standalone",
6 + "orientation": "portrait"
7 +}
1 +{
2 + "version": "1.0.0",
3 + "name": "Friendly Chat",
4 + "launch_path": "/index.html",
5 + "description": "Chat with friends using Firebase 2.0",
6 + "developer": {
7 + "name": "Google inc.",
8 + "url": "https://firebase.google.com"
9 + },
10 + "installs_allowed_from": [
11 + "*"
12 + ],
13 + "default_locale": "en",
14 + "permissions": {
15 + },
16 + "locales": {
17 + "en": {
18 + "name": "Friendly Chat",
19 + "description": "Chat with friends using Firebase 2.0"
20 + }
21 + }
22 +}
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +export const environment = {
17 + production: true,
18 + firebase: {
19 + // ***********************************************************************************************************************
20 + // * TODO(DEVELOPER): Update values according to: Firebase Console > Overview > Add Firebase to your web app. *
21 + // ***********************************************************************************************************************
22 + apiKey: '',
23 + authDomain: '',
24 + databaseURL: '',
25 + projectId: '',
26 + storageBucket: '',
27 + messagingSenderId: ''
28 + }
29 +};
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +// The file contents for the current environment will overwrite these during build.
17 +// The build system defaults to the dev environment which uses `environment.ts`, but if you do
18 +// `ng build --env=prod` then `environment.prod.ts` will be used instead.
19 +// The list of which env maps to which file can be found in `.angular-cli.json`.
20 +
21 +export const environment = {
22 + production: false,
23 + firebase: {
24 + // ***********************************************************************************************************************
25 + // * TODO(DEVELOPER): Update values according to: Firebase Console > Overview > Add Firebase to your web app. *
26 + // ***********************************************************************************************************************
27 + apiKey: '',
28 + authDomain: '',
29 + databaseURL: '',
30 + projectId: '',
31 + storageBucket: '',
32 + messagingSenderId: ''
33 + }
34 +};
1 +importScripts('https://www.gstatic.com/firebasejs/3.6.6/firebase-app.js');
2 +importScripts('https://www.gstatic.com/firebasejs/3.6.6/firebase-messaging.js');
3 +
4 +// Initialize the Firebase app in the service worker by passing in the
5 +// messagingSenderId.
6 +firebase.initializeApp({
7 + // TODO add your messagingSenderId
8 + messagingSenderId: ''
9 +});
10 +var messaging = firebase.messaging();
1 +<!doctype html>
2 +<!--
3 + Copyright 2017 Google Inc. All rights reserved.
4 + Licensed under the Apache License, Version 2.0 (the "License");
5 + you may not use this file except in compliance with the License.
6 + You may obtain a copy of the License at
7 + https://www.apache.org/licenses/LICENSE-2.0
8 + Unless required by applicable law or agreed to in writing, software
9 + distributed under the License is distributed on an "AS IS" BASIS,
10 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 + See the License for the specific language governing permissions and
12 + limitations under the License
13 +-->
14 +<html lang="en">
15 +<head>
16 + <meta charset="utf-8">
17 + <meta http-equiv="X-UA-Compatible" content="IE=edge">
18 + <meta name="description" content="Learn how to use the Firebase platform on the Web">
19 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
20 + <title>Friendly Chat</title>
21 +
22 + <!-- Disable tap highlight on IE -->
23 + <meta name="msapplication-tap-highlight" content="no">
24 +
25 + <!-- Web Application Manifest -->
26 + <link rel="manifest" href="assets/manifest.json">
27 +
28 + <!-- Add to homescreen for Chrome on Android -->
29 + <meta name="mobile-web-app-capable" content="yes">
30 + <meta name="application-name" content="Friendly Chat">
31 + <meta name="theme-color" content="#303F9F">
32 +
33 + <!-- Add to homescreen for Safari on iOS -->
34 + <meta name="apple-mobile-web-app-capable" content="yes">
35 + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
36 + <meta name="apple-mobile-web-app-title" content="Friendly Chat">
37 + <meta name="apple-mobile-web-app-status-bar-style" content="#303F9F">
38 +
39 + <!-- Tile icon for Win8 -->
40 + <meta name="msapplication-TileColor" content="#3372DF">
41 + <meta name="msapplication-navbutton-color" content="#303F9F">
42 +
43 + <!-- Material Design Lite -->
44 + <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
45 + <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.orange-indigo.min.css">
46 +
47 + <!-- App Styling -->
48 + <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Creepster:regular,bold|Julee:regular,bold|Crafty+Girls:regular,bold|Bonbon:regular,bold|Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&amp;lang=en">
49 +
50 + <base href="/">
51 +
52 + <link rel="icon" type="image/x-icon" href="favicon.ico">
53 +</head>
54 +<body>
55 + <app-root>Loading...</app-root>
56 +</body>
57 +</html>
1 +import { enableProdMode } from '@angular/core';
2 +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 +
4 +import { AppModule } from './app/app.module';
5 +import { environment } from './environments/environment';
6 +
7 +if (environment.production) {
8 + enableProdMode();
9 +}
10 +
11 +platformBrowserDynamic().bootstrapModule(AppModule)
12 + .catch(err => console.log(err));
1 +/**
2 + * This file includes polyfills needed by Angular and is loaded before the app.
3 + * You can add your own extra polyfills to this file.
4 + *
5 + * This file is divided into 2 sections:
6 + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 + * file.
9 + *
10 + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 + *
14 + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 + */
16 +
17 +/***************************************************************************************************
18 + * BROWSER POLYFILLS
19 + */
20 +
21 +/** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 +// import 'core-js/es6/symbol';
23 +// import 'core-js/es6/object';
24 +// import 'core-js/es6/function';
25 +// import 'core-js/es6/parse-int';
26 +// import 'core-js/es6/parse-float';
27 +// import 'core-js/es6/number';
28 +// import 'core-js/es6/math';
29 +// import 'core-js/es6/string';
30 +// import 'core-js/es6/date';
31 +// import 'core-js/es6/array';
32 +// import 'core-js/es6/regexp';
33 +// import 'core-js/es6/map';
34 +// import 'core-js/es6/weak-map';
35 +// import 'core-js/es6/set';
36 +
37 +/** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 +// import 'classlist.js'; // Run `npm install --save classlist.js`.
39 +
40 +/** IE10 and IE11 requires the following for the Reflect API. */
41 +// import 'core-js/es6/reflect';
42 +
43 +
44 +/** Evergreen browsers require these. **/
45 +// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46 +import 'core-js/es7/reflect';
47 +
48 +
49 +/**
50 + * Required to support Web Animations `@angular/platform-browser/animations`.
51 + * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
52 + **/
53 +// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
54 +
55 +/**
56 + * By default, zone.js will patch all possible macroTask and DomEvents
57 + * user can disable parts of macroTask/DomEvents patch by setting following flags
58 + */
59 +
60 + // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
61 + // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
62 + // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
63 +
64 + /*
65 + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
66 + * with the following flag, it will bypass `zone.js` patch for IE/Edge
67 + */
68 +// (window as any).__Zone_enable_cross_context_check = true;
69 +
70 +/***************************************************************************************************
71 + * Zone JS is required by default for Angular itself.
72 + */
73 +import 'zone.js/dist/zone'; // Included with Angular CLI.
74 +
75 +
76 +
77 +/***************************************************************************************************
78 + * APPLICATION IMPORTS
79 + */
1 +/**
2 + * Copyright 2015 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +html, body {
18 + font-family: 'Roboto', 'Helvetica', sans-serif;
19 +}
20 +.cdk-visually-hidden {
21 + display: none;
22 +}
1 +// This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 +
3 +import 'zone.js/dist/zone-testing';
4 +import { getTestBed } from '@angular/core/testing';
5 +import {
6 + BrowserDynamicTestingModule,
7 + platformBrowserDynamicTesting
8 +} from '@angular/platform-browser-dynamic/testing';
9 +
10 +declare const require: any;
11 +
12 +// First, initialize the Angular testing environment.
13 +getTestBed().initTestEnvironment(
14 + BrowserDynamicTestingModule,
15 + platformBrowserDynamicTesting()
16 +);
17 +// Then we find all the tests.
18 +const context = require.context('./', true, /\.spec\.ts$/);
19 +// And load the modules.
20 +context.keys().map(context);
1 +{
2 + "extends": "../tsconfig.json",
3 + "compilerOptions": {
4 + "outDir": "../out-tsc/app",
5 + "baseUrl": "./",
6 + "module": "es2015",
7 + "types": []
8 + },
9 + "exclude": [
10 + "test.ts",
11 + "**/*.spec.ts"
12 + ]
13 +}
1 +{
2 + "extends": "../tsconfig.json",
3 + "compilerOptions": {
4 + "outDir": "../out-tsc/spec",
5 + "baseUrl": "./",
6 + "module": "commonjs",
7 + "types": [
8 + "jasmine",
9 + "node"
10 + ]
11 + },
12 + "files": [
13 + "test.ts"
14 + ],
15 + "include": [
16 + "**/*.spec.ts",
17 + "**/*.d.ts"
18 + ]
19 +}
1 +/* SystemJS module definition */
2 +declare var module: NodeModule;
3 +interface NodeModule {
4 + id: string;
5 +}
1 +service firebase.storage {
2 + match /b/{bucket}/o {
3 + match /{userId}/{postId}/{fileName} {
4 + allow write: if request.auth.uid == userId;
5 + allow read;
6 + }
7 + }
8 +}
1 +{
2 + "compileOnSave": false,
3 + "compilerOptions": {
4 + "outDir": "./dist/out-tsc",
5 + "sourceMap": true,
6 + "declaration": false,
7 + "moduleResolution": "node",
8 + "emitDecoratorMetadata": true,
9 + "experimentalDecorators": true,
10 + "target": "es5",
11 + "typeRoots": [
12 + "node_modules/@types"
13 + ],
14 + "lib": [
15 + "es2017",
16 + "dom"
17 + ]
18 + }
19 +}
1 +{
2 + "rulesDirectory": [
3 + "node_modules/codelyzer"
4 + ],
5 + "rules": {
6 + "arrow-return-shorthand": true,
7 + "callable-types": true,
8 + "class-name": true,
9 + "comment-format": [
10 + true,
11 + "check-space"
12 + ],
13 + "curly": true,
14 + "deprecation": {
15 + "severity": "warn"
16 + },
17 + "eofline": true,
18 + "forin": true,
19 + "import-blacklist": [
20 + true,
21 + "rxjs",
22 + "rxjs/Rx"
23 + ],
24 + "import-spacing": true,
25 + "indent": [
26 + true,
27 + "spaces"
28 + ],
29 + "interface-over-type-literal": true,
30 + "label-position": true,
31 + "max-line-length": [
32 + true,
33 + 140
34 + ],
35 + "member-access": false,
36 + "member-ordering": [
37 + true,
38 + {
39 + "order": [
40 + "static-field",
41 + "instance-field",
42 + "static-method",
43 + "instance-method"
44 + ]
45 + }
46 + ],
47 + "no-arg": true,
48 + "no-bitwise": true,
49 + "no-console": [
50 + true,
51 + "debug",
52 + "info",
53 + "time",
54 + "timeEnd",
55 + "trace"
56 + ],
57 + "no-construct": true,
58 + "no-debugger": true,
59 + "no-duplicate-super": true,
60 + "no-empty": false,
61 + "no-empty-interface": true,
62 + "no-eval": true,
63 + "no-inferrable-types": [
64 + true,
65 + "ignore-params"
66 + ],
67 + "no-misused-new": true,
68 + "no-non-null-assertion": true,
69 + "no-shadowed-variable": true,
70 + "no-string-literal": false,
71 + "no-string-throw": true,
72 + "no-switch-case-fall-through": true,
73 + "no-trailing-whitespace": true,
74 + "no-unnecessary-initializer": true,
75 + "no-unused-expression": true,
76 + "no-use-before-declare": true,
77 + "no-var-keyword": true,
78 + "object-literal-sort-keys": false,
79 + "one-line": [
80 + true,
81 + "check-open-brace",
82 + "check-catch",
83 + "check-else",
84 + "check-whitespace"
85 + ],
86 + "prefer-const": true,
87 + "quotemark": [
88 + true,
89 + "single"
90 + ],
91 + "radix": true,
92 + "semicolon": [
93 + true,
94 + "always"
95 + ],
96 + "triple-equals": [
97 + true,
98 + "allow-null-check"
99 + ],
100 + "typedef-whitespace": [
101 + true,
102 + {
103 + "call-signature": "nospace",
104 + "index-signature": "nospace",
105 + "parameter": "nospace",
106 + "property-declaration": "nospace",
107 + "variable-declaration": "nospace"
108 + }
109 + ],
110 + "unified-signatures": true,
111 + "variable-name": false,
112 + "whitespace": [
113 + true,
114 + "check-branch",
115 + "check-decl",
116 + "check-operator",
117 + "check-separator",
118 + "check-type"
119 + ],
120 + "directive-selector": [
121 + true,
122 + "attribute",
123 + "app",
124 + "camelCase"
125 + ],
126 + "component-selector": [
127 + true,
128 + "element",
129 + "app",
130 + "kebab-case"
131 + ],
132 + "no-output-on-prefix": true,
133 + "use-input-property-decorator": true,
134 + "use-output-property-decorator": true,
135 + "use-host-property-decorator": true,
136 + "no-input-rename": true,
137 + "no-output-rename": true,
138 + "use-life-cycle-interface": true,
139 + "use-pipe-transform-interface": true,
140 + "component-class-suffix": true,
141 + "directive-class-suffix": true
142 + }
143 +}
1 +{
2 + "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 + "project": {
4 + "name": "cloud-functions-angular-start"
5 + },
6 + "apps": [
7 + {
8 + "root": "src",
9 + "outDir": "dist",
10 + "assets": [
11 + "assets",
12 + "favicon.ico",
13 + "firebase-messaging-sw.js"
14 + ],
15 + "index": "index.html",
16 + "main": "main.ts",
17 + "polyfills": "polyfills.ts",
18 + "test": "test.ts",
19 + "tsconfig": "tsconfig.app.json",
20 + "testTsconfig": "tsconfig.spec.json",
21 + "prefix": "app",
22 + "styles": [
23 + "styles.css"
24 + ],
25 + "scripts": [],
26 + "environmentSource": "environments/environment.ts",
27 + "environments": {
28 + "dev": "environments/environment.ts",
29 + "prod": "environments/environment.prod.ts"
30 + }
31 + }
32 + ],
33 + "e2e": {
34 + "protractor": {
35 + "config": "./protractor.conf.js"
36 + }
37 + },
38 + "lint": [
39 + {
40 + "project": "src/tsconfig.app.json",
41 + "exclude": "**/node_modules/**"
42 + },
43 + {
44 + "project": "src/tsconfig.spec.json",
45 + "exclude": "**/node_modules/**"
46 + },
47 + {
48 + "project": "e2e/tsconfig.e2e.json",
49 + "exclude": "**/node_modules/**"
50 + }
51 + ],
52 + "test": {
53 + "karma": {
54 + "config": "./karma.conf.js"
55 + }
56 + },
57 + "defaults": {
58 + "styleExt": "css",
59 + "component": {}
60 + }
61 +}
1 +# Editor configuration, see http://editorconfig.org
2 +root = true
3 +
4 +[*]
5 +charset = utf-8
6 +indent_style = space
7 +indent_size = 2
8 +insert_final_newline = true
9 +trim_trailing_whitespace = true
10 +
11 +[*.md]
12 +max_line_length = off
13 +trim_trailing_whitespace = false
1 +{}
...\ No newline at end of file ...\ No newline at end of file
1 +# See http://help.github.com/ignore-files/ for more about ignoring files.
2 +
3 +# compiled output
4 +/dist
5 +/dist-server
6 +/tmp
7 +/out-tsc
8 +
9 +# dependencies
10 +/node_modules
11 +/functions/node_modules
12 +
13 +# IDEs and editors
14 +/.idea
15 +.project
16 +.classpath
17 +.c9/
18 +*.launch
19 +.settings/
20 +*.sublime-workspace
21 +
22 +# IDE - VSCode
23 +.vscode/*
24 +!.vscode/settings.json
25 +!.vscode/tasks.json
26 +!.vscode/launch.json
27 +!.vscode/extensions.json
28 +
29 +# misc
30 +/.sass-cache
31 +/connect.lock
32 +/coverage
33 +/libpeerconnection.log
34 +npm-debug.log
35 +yarn-error.log
36 +testem.log
37 +/typings
38 +
39 +# e2e
40 +/e2e/*.js
41 +/e2e/*.map
42 +
43 +# System Files
44 +.DS_Store
45 +Thumbs.db
1 +# Firebase SDK for Cloud Functions (Angular) Codelab - Start code
2 +
3 +This folder contains the start code of the [Firebase SDK for Cloud Functions (Angular) Codelab](https://codelabs.developers.google.com/codelabs/firebase-cloud-functions-angular/).
4 +
5 +If you'd like to jump directly to the end and see the finished code head to the [cloud-functions-angular](../cloud-functions-angular) directory.
6 +
7 +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0.
8 +
9 +# Running locally
10 +
11 +## Development server
12 +
13 +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
14 +
15 +## Code scaffolding
16 +
17 +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
18 +
19 +## Build
20 +
21 +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
22 +
23 +## Running unit tests
24 +
25 +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
26 +
27 +## Running end-to-end tests
28 +
29 +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
30 +
31 +## Further help
32 +
33 +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
34 +
35 +# Deploying
36 +
37 +Run `firebase deploy` to deploy hosting, database, and functions. Run
38 +`firebase deploy --except` to only deploy hosting and database.
1 +{
2 + "rules": {
3 + "messages": {
4 + ".read": "auth !== null",
5 + ".write": "auth !== null"
6 + },
7 + "fcmTokens": {
8 + "$token": {
9 + ".read": "data.val() === auth.uid", // Users can only read their own device tokens
10 + ".write": "!data.exists() || data.val() === auth.uid", // Users can only write to their own device tokens
11 + ".validate": "newData.val() === auth.uid" // value has to be the UID of the user
12 + }
13 + }
14 + }
15 +}
1 +import { CloudFunctionsAngularStartPage } from './app.po';
2 +
3 +describe('cloud-functions-angular-start App', () => {
4 + let page: CloudFunctionsAngularStartPage;
5 +
6 + beforeEach(() => {
7 + page = new CloudFunctionsAngularStartPage();
8 + });
9 +
10 + it('should display welcome message', () => {
11 + page.navigateTo();
12 + expect(page.getParagraphText()).toEqual('Welcome to app!');
13 + });
14 +});
1 +import { browser, element, by } from 'protractor';
2 +
3 +export class CloudFunctionsAngularStartPage {
4 + navigateTo() {
5 + return browser.get('/');
6 + }
7 +
8 + getParagraphText() {
9 + return element(by.css('app-root h1')).getText();
10 + }
11 +}
1 +{
2 + "extends": "../tsconfig.json",
3 + "compilerOptions": {
4 + "outDir": "../out-tsc/e2e",
5 + "baseUrl": "./",
6 + "module": "commonjs",
7 + "target": "es5",
8 + "types":[
9 + "jasmine",
10 + "jasminewd2",
11 + "node"
12 + ]
13 + }
14 +}
1 +{
2 + "database": {
3 + "rules": "database.rules.json"
4 + },
5 + "hosting": {
6 + "public": "dist",
7 + "ignore": [
8 + "firebase.json",
9 + "**/.*",
10 + "**/node_modules/**"
11 + ],
12 + "rewrites": [
13 + {
14 + "source": "**",
15 + "destination": "/index.html"
16 + }
17 + ]
18 + },
19 + "storage": {
20 + "rules": "storage.rules"
21 + }
22 +}
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +// Import the Firebase SDK for Google Cloud Functions.
18 +const functions = require('firebase-functions');
19 +// Import and initialize the Firebase Admin SDK.
20 +const admin = require('firebase-admin');
21 +admin.initializeApp(functions.config().firebase);
22 +
23 +const Storage = require('@google-cloud/storage');
24 +const vision = require('@google-cloud/vision');
25 +const exec = require('child-process-promise').exec;
26 +
27 +const visionClient = new vision.ImageAnnotatorClient();
28 +const storageClient = new Storage();
29 +
30 +const language = require('@google-cloud/language');
31 +const languageClient = new language.LanguageServiceClient();
32 +
33 +
34 +// Adds a message that welcomes new users into the chat.
35 +exports.addWelcomeMessages = functions.auth.user().onCreate((user) => {
36 + console.log('A new user signed in for the first time.');
37 + const fullName = user.displayName || 'Anonymous';
38 +
39 + // Saves the new welcome message into the database
40 + // which then displays it in the FriendlyChat clients.
41 + return admin.database().ref('messages').push({
42 + name: 'Firebase Bot',
43 + photoUrl: '/assets/images/firebase-logo.png', // Firebase logo
44 + text: `${fullName} signed in for the first time! Welcome!`
45 + });
46 +});
47 +
48 +
49 +// Blurs the given image located in the given bucket using ImageMagick.
50 +function blurImage(object) {
51 + const filePath = object.name;
52 + const bucket = storageClient.bucket(object.bucket);
53 + const fileName = filePath.split('/').pop();
54 + const tempLocalFile = `/tmp/${fileName}`;
55 + const messageId = filePath.split('/')[1];
56 +
57 + // Download file from bucket.
58 + return bucket
59 + .file(filePath)
60 + .download({ destination: tempLocalFile })
61 + .then(() => {
62 + console.log('Image has been downloaded to', tempLocalFile);
63 + // Blur the image using ImageMagick.
64 + return exec(`convert ${tempLocalFile} -channel RGBA -blur 0x24 ${tempLocalFile}`);
65 + })
66 + .then(() => {
67 + console.log('Image has been blurred');
68 + // Uploading the Blurred image back into the bucket.
69 + return bucket.upload(tempLocalFile, { destination: filePath });
70 + })
71 + .then(() => {
72 + console.log('Blurred image has been uploaded to', filePath);
73 + // Indicate that the message has been moderated.
74 + return admin.database().ref(`/messages/${messageId}`).update({ moderated: true });
75 + })
76 + .then(() => {
77 + console.log('Marked the image as moderated in the database.');
78 + });
79 +}
80 +
81 +// Blurs uploaded images that are flagged as Adult or Violence.
82 +exports.blurOffensiveImages = functions.storage.object().onFinalize((object) => {
83 + // Exit if this is a deletion or a deploy event.
84 + if (object.resourceState === 'not_exists') {
85 + return console.log('This is a deletion event.');
86 + } else if (!object.name) {
87 + return console.log('This is a deploy event.');
88 + }
89 +
90 + const messageId = object.name.split('/')[1];
91 +
92 + return admin.database().ref(`/messages/${messageId}/moderated`).once('value')
93 + .then((snapshot) => {
94 + // The image has already been moderated.
95 + if (snapshot.val()) {
96 + return;
97 + }
98 +
99 + // Check the image content using the Cloud Vision API.
100 + return visionClient.safeSearchDetection(`gs://${object.bucket}/${object.name}`);
101 + })
102 + .then((results) => {
103 + if (!results) {
104 + return;
105 + }
106 + const detections = results[0].safeSearchAnnotation;
107 + if (detections.adult || detections.violence) {
108 + console.log('The image', object.name, 'has been detected as inappropriate.');
109 + return blurImage(object);
110 + } else {
111 + console.log('The image', object.name, ' has been detected as OK.');
112 + }
113 + });
114 +});
115 +
116 +
117 +// Sends a notifications to all users when a new message is posted.
118 +exports.sendNotifications = functions.database.ref('/messages/{messageId}').onWrite((change, context) => {
119 + // Only send a notification when a message has been created.
120 + if (change.before.val()) {
121 + return;
122 + }
123 +
124 + // Notification details.
125 + const original = change.after.val();
126 + const text = original.text;
127 + const payload = {
128 + notification: {
129 + title: `${original.name} posted ${text ? 'a message' : 'an image'}`,
130 + body: text ? (text.length <= 100 ? text : text.substring(0, 97) + '...') : '',
131 + icon: original.photoUrl || '/assets/images/profile_placeholder.png',
132 + click_action: `https://${functions.config().firebase.authDomain}`
133 + }
134 + };
135 +
136 + // Get the list of device tokens.
137 + return admin.database().ref('fcmTokens').once('value').then(allTokens => {
138 + if (allTokens.val()) {
139 + // Listing all tokens.
140 + const tokens = Object.keys(allTokens.val());
141 +
142 + // Send notifications to all tokens.
143 + return admin.messaging().sendToDevice(tokens, payload).then(response => {
144 + // For each message check if there was an error.
145 + const tokensToRemove = [];
146 + response.results.forEach((result, index) => {
147 + const error = result.error;
148 + if (error) {
149 + console.error('Failure sending notification to', tokens[index], error);
150 + // Cleanup the tokens who are not registered anymore.
151 + if (error.code === 'messaging/invalid-registration-token' ||
152 + error.code === 'messaging/registration-token-not-registered') {
153 + tokensToRemove.push(allTokens.ref.child(tokens[index]).remove());
154 + }
155 + }
156 + });
157 + return Promise.all(tokensToRemove);
158 + });
159 + }
160 + });
161 +});
162 +
163 +// Annotates messages using the Cloud Natural Language API
164 +exports.annotateMessages = functions.database.ref('/messages/{messageId}').onWrite((change, context) => {
165 + const messageId = context.params.messageId;
166 +
167 + // Only annotate new messages.
168 + if (change.before.exists()) {
169 + return null;
170 + }
171 +
172 + // Annotation arguments.
173 + const original = change.after.val();
174 + const request = {
175 + document: {
176 + content: original.text,
177 + type: 'PLAIN_TEXT'
178 + },
179 + features: {
180 + extractDocumentSentiment: true,
181 + extractEntities: true
182 + }
183 + };
184 +
185 + console.log('Annotating new message.');
186 +
187 + // Detect the sentiment and entities of the new message.
188 + return languageClient.annotateText(request)
189 + .then((result) => {
190 + console.log('Saving annotations.');
191 +
192 + // Update the message with the results.
193 + return admin.database().ref(`/messages/${messageId}`).update({
194 + sentiment: result[0].documentSentiment,
195 + entities: result[0].entities.map((entity) => {
196 + return {
197 + name: entity.name,
198 + salience: entity.salience
199 + };
200 + })
201 + });
202 + });
203 +});
204 +
1 +{
2 + "name": "functions",
3 + "description": "Cloud Functions for Firebase",
4 + "dependencies": {
5 + "@google-cloud/language": "^1.2.0",
6 + "@google-cloud/storage": "^1.6.0",
7 + "@google-cloud/vision": "^0.19.0",
8 + "child-process-promise": "^2.2.1",
9 + "firebase-admin": "~5.12.0",
10 + "firebase-functions": "^1.0.1"
11 + },
12 + "scripts": {
13 + "serve": "firebase serve --only functions",
14 + "shell": "firebase functions:shell",
15 + "start": "npm run shell",
16 + "deploy": "firebase deploy --only functions",
17 + "logs": "firebase functions:log"
18 + },
19 + "private": true
20 +}
1 +// Karma configuration file, see link for more information
2 +// https://karma-runner.github.io/0.13/config/configuration-file.html
3 +
4 +module.exports = function (config) {
5 + config.set({
6 + basePath: '',
7 + frameworks: ['jasmine', '@angular/cli'],
8 + plugins: [
9 + require('karma-jasmine'),
10 + require('karma-chrome-launcher'),
11 + require('karma-jasmine-html-reporter'),
12 + require('karma-coverage-istanbul-reporter'),
13 + require('@angular/cli/plugins/karma')
14 + ],
15 + client:{
16 + clearContext: false // leave Jasmine Spec Runner output visible in browser
17 + },
18 + coverageIstanbulReporter: {
19 + reports: [ 'html', 'lcovonly' ],
20 + fixWebpackSourcePaths: true
21 + },
22 + angularCli: {
23 + environment: 'dev'
24 + },
25 + reporters: ['progress', 'kjhtml'],
26 + port: 9876,
27 + colors: true,
28 + logLevel: config.LOG_INFO,
29 + autoWatch: true,
30 + browsers: ['Chrome'],
31 + singleRun: false
32 + });
33 +};
1 +{
2 + "name": "cloud-functions-angular-start",
3 + "version": "0.0.0",
4 + "license": "MIT",
5 + "scripts": {
6 + "ng": "ng",
7 + "start": "ng serve",
8 + "build": "ng build --prod",
9 + "test": "ng test",
10 + "lint": "ng lint",
11 + "e2e": "ng e2e"
12 + },
13 + "private": true,
14 + "dependencies": {
15 + "@angular/animations": "^5.2.0",
16 + "@angular/cdk": "^5.2.0",
17 + "@angular/common": "^5.2.0",
18 + "@angular/compiler": "^5.2.0",
19 + "@angular/core": "^5.2.0",
20 + "@angular/forms": "^5.2.0",
21 + "@angular/http": "^5.2.0",
22 + "@angular/material": "^5.2.0",
23 + "@angular/platform-browser": "^5.2.0",
24 + "@angular/platform-browser-dynamic": "^5.2.0",
25 + "@angular/router": "^5.2.0",
26 + "angularfire2": "^5.0.0-rc.6.0",
27 + "core-js": "^2.4.1",
28 + "firebase": "^4.12.1",
29 + "rxjs": "^5.5.6",
30 + "zone.js": "^0.8.19"
31 + },
32 + "devDependencies": {
33 + "@angular/cli": "~1.7.4",
34 + "@angular/compiler-cli": "^5.2.0",
35 + "@angular/language-service": "^5.2.0",
36 + "@types/jasmine": "~2.8.3",
37 + "@types/jasminewd2": "~2.0.2",
38 + "@types/node": "~6.0.60",
39 + "codelyzer": "^4.0.1",
40 + "jasmine-core": "~2.8.0",
41 + "jasmine-spec-reporter": "~4.2.1",
42 + "karma": "~2.0.0",
43 + "karma-chrome-launcher": "~2.2.0",
44 + "karma-coverage-istanbul-reporter": "^1.2.1",
45 + "karma-jasmine": "~1.1.0",
46 + "karma-jasmine-html-reporter": "^0.2.2",
47 + "protractor": "~5.1.2",
48 + "ts-node": "~4.1.0",
49 + "tslint": "~5.9.1",
50 + "typescript": "~2.5.3"
51 + }
52 +}
1 +// Protractor configuration file, see link for more information
2 +// https://github.com/angular/protractor/blob/master/lib/config.ts
3 +
4 +const { SpecReporter } = require('jasmine-spec-reporter');
5 +
6 +exports.config = {
7 + allScriptsTimeout: 11000,
8 + specs: [
9 + './e2e/**/*.e2e-spec.ts'
10 + ],
11 + capabilities: {
12 + 'browserName': 'chrome'
13 + },
14 + directConnect: true,
15 + baseUrl: 'http://localhost:4200/',
16 + framework: 'jasmine',
17 + jasmineNodeOpts: {
18 + showColors: true,
19 + defaultTimeoutInterval: 30000,
20 + print: function() {}
21 + },
22 + onPrepare() {
23 + require('ts-node').register({
24 + project: 'e2e/tsconfig.e2e.json'
25 + });
26 + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 + }
28 +};
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +main, #messages-card {
17 + height: 100%;
18 + padding-bottom: 0;
19 +}
20 +#messages-card-container {
21 + height: calc(100% - 150px);
22 + padding-bottom: 0;
23 +}
24 +#messages-card {
25 + margin-top: 15px;
26 +}
27 +.mdl-layout__header-row span {
28 + margin-left: 15px;
29 + margin-top: 17px;
30 +}
31 +.mdl-grid {
32 + max-width: 1024px;
33 + margin: auto;
34 +}
35 +.material-icons {
36 + font-size: 36px;
37 + top: 8px;
38 + position: relative;
39 +}
40 +.mdl-layout__header-row {
41 + padding: 0;
42 + margin: 0 auto;
43 +}
44 +.mdl-card__supporting-text {
45 + width: auto;
46 + height: 100%;
47 + padding-top: 0;
48 + padding-bottom: 0;
49 +}
50 +#messages {
51 + overflow-y: auto;
52 + margin-bottom: 10px;
53 + height: calc(100% - 80px);
54 +}
55 +#message-filler {
56 + flex-grow: 1;
57 +}
58 +.message-container:first-of-type {
59 + border-top-width: 0;
60 +}
61 +.message-container {
62 + display: block;
63 + margin-top: 10px;
64 + border-top: 1px solid #f3f3f3;
65 + padding-top: 10px;
66 + /*opacity: 0;*/
67 + transition: opacity 1s ease-in-out;
68 +}
69 +.message-container.visible {
70 + opacity: 1;
71 +}
72 +.message-container .pic {
73 + background-image: url('/assets/images/profile_placeholder.png');
74 + background-repeat: no-repeat;
75 + width: 30px;
76 + height: 30px;
77 + background-size: 30px;
78 + border-radius: 20px;
79 +}
80 +.message-container .spacing {
81 + display: table-cell;
82 + vertical-align: top;
83 +}
84 +.message-container .message {
85 + display: table-cell;
86 + width: calc(100% - 40px);
87 + padding: 5px 0 5px 10px;
88 +}
89 +.message-container .name {
90 + display: inline-block;
91 + width: 100%;
92 + padding-left: 40px;
93 + color: #bbb;
94 + font-style: italic;
95 + font-size: 12px;
96 + box-sizing: border-box;
97 +}
98 +#message-form {
99 + display: flex;
100 + flex-direction: row;
101 + width: calc(100% - 48px);
102 + float: left;
103 +}
104 +#image-form {
105 + display: flex;
106 + flex-direction: row;
107 + width: 48px;
108 + float: right;
109 +}
110 +#message-form .mdl-textfield {
111 + width: calc(100% - 100px);
112 +}
113 +#message-form button, #image-form button {
114 + width: 100px;
115 + margin: 15px 0 0 10px;
116 +}
117 +.mdl-card {
118 + min-height: 0;
119 +}
120 +.mdl-card {
121 + background: linear-gradient(white, #f9f9f9);
122 + justify-content: space-between;
123 +}
124 +#user-container {
125 + position: absolute;
126 + display: flex;
127 + flex-direction: row;
128 + top: 22px;
129 + width: 100%;
130 + right: 0;
131 + padding-left: 10px;
132 + justify-content: flex-end;
133 + padding-right: 10px;
134 +}
135 +#user-container #user-pic {
136 + top: -3px;
137 + position: relative;
138 + display: inline-block;
139 + background-image: url('/assets/images/profile_placeholder.png');
140 + background-repeat: no-repeat;
141 + width: 40px;
142 + height: 40px;
143 + background-size: 40px;
144 + border-radius: 20px;
145 +}
146 +#user-container #user-name {
147 + font-size: 16px;
148 + line-height: 36px;
149 + padding-right: 10px;
150 + padding-left: 20px;
151 +}
152 +#image-form #submitImage {
153 + width: auto;
154 + padding: 0 6px 0 1px;
155 + min-width: 0;
156 +}
157 +#image-form #submitImage .material-icons {
158 + top: -1px;
159 +}
160 +.message img {
161 + max-width: 300px;
162 + max-height: 200px;
163 +}
164 +#mediaCapture {
165 + display: none;
166 +}
167 +@media screen and (max-width: 610px) {
168 + header {
169 + height: 113px;
170 + padding-bottom: 80px !important;
171 + }
172 + #user-container {
173 + top: 72px;
174 + background-color: rgb(3,155,229);
175 + height: 38px;
176 + padding-top: 3px;
177 + padding-right: 2px;
178 + }
179 + #user-container #user-pic {
180 + top: 2px;
181 + width: 33px;
182 + height: 33px;
183 + background-size: 33px;
184 + }
185 +}
186 +.mdl-textfield__label:after {
187 + background-color: #0288D1;
188 +}
189 +.mdl-textfield--floating-label.is-focused .mdl-textfield__label {
190 + color: #0288D1;
191 +}
192 +.mdl-button .material-icons {
193 + top: -1px;
194 + margin-right: 5px;
195 +}
1 +<div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header">
2 + <!-- Header section containing logo -->
3 + <header class="mdl-layout__header mdl-color-text--white mdl-color--light-blue-700">
4 + <div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
5 + <div class="mdl-layout__header-row mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--12-col-desktop">
6 + <h3><i class="material-icons">chat_bubble_outline</i> Friendly Chat</h3>
7 + </div>
8 + <div id="user-container">
9 + <div *ngIf="currentUser" id="user-pic" [ngStyle]="profilePicStyles"></div>
10 + <div *ngIf="currentUser" id="user-name">{{ (user | async)?.displayName }}</div>
11 + <button *ngIf="currentUser" id="sign-out" (click)="logout()" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-color-text--white">
12 + Sign-out
13 + </button>
14 + <button *ngIf="!currentUser" id="sign-in" (click)="login()" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-color-text--white">
15 + <i class="material-icons">account_circle</i>Sign-in with Google
16 + </button>
17 + </div>
18 + </div>
19 + </header>
20 +
21 + <main class="mdl-layout__content mdl-color--grey-100">
22 + <div id="messages-card-container" class="mdl-cell mdl-cell--12-col mdl-grid">
23 +
24 + <!-- Messages container -->
25 + <div id="messages-card" class="mdl-card mdl-shadow--2dp mdl-cell mdl-cell--12-col mdl-cell--6-col-tablet mdl-cell--6-col-desktop">
26 + <div class="mdl-card__supporting-text mdl-color-text--grey-600">
27 + <div id="messages">
28 + <span id="message-filler"></span>
29 + <div class="message-container" *ngFor="let message of messages | async">
30 + <div class="spacing">
31 + <div class="pic" [style.background-image]="'url(' + message.photoUrl + ')'"></div>
32 + </div>
33 + <div class="message">
34 + <span *ngIf="message.text" [style]="message.sentiment | stylize">{{ message.text }}</span>
35 + <img *ngIf="message.imageUrl" [src]="message.imageUrl + (message.moderated ? '&amp;moderated=1' : '')">
36 + </div>
37 + <div class="name">{{ message.name }}</div>
38 + </div>
39 + </div>
40 + <div *ngIf="topics"><em>Recent topics: {{ topics }}</em></div>
41 + <form id="message-form" (submit)="saveMessage($event, box)">
42 + <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
43 + <input #box class="mdl-textfield__input" type="text" id="message"
44 + (keyup)="update(box.value)" (blur)="update(box.value)"
45 + placeholder="Message...">
46 + </div>
47 + <button id="submit" [disabled]="!value" type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect">
48 + Send
49 + </button>
50 + </form>
51 + <form id="image-form" action="#">
52 + <input id="mediaCapture" type="file" accept="image/*,capture=camera" (change)="saveImageMessage($event)">
53 + <button id="submitImage" title="Add an image" (click)="onImageClick($event)"
54 + class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-color--amber-400 mdl-color-text--white">
55 + <i class="material-icons">image</i>
56 + </button>
57 + </form>
58 + </div>
59 + </div>
60 + </div>
61 + </main>
62 +</div>
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +import { TestBed, async } from '@angular/core/testing';
17 +
18 +import { AppComponent } from './app.component';
19 +
20 +describe('AppComponent', () => {
21 + beforeEach(async(() => {
22 + TestBed.configureTestingModule({
23 + declarations: [
24 + AppComponent
25 + ],
26 + }).compileComponents();
27 + }));
28 +
29 + it('should create the app', async(() => {
30 + const fixture = TestBed.createComponent(AppComponent);
31 + const app = fixture.debugElement.componentInstance;
32 + expect(app).toBeTruthy();
33 + }));
34 +
35 + it(`should have as title 'app works!'`, async(() => {
36 + const fixture = TestBed.createComponent(AppComponent);
37 + const app = fixture.debugElement.componentInstance;
38 + expect(app.title).toEqual('app works!');
39 + }));
40 +
41 + it('should render title in a h1 tag', async(() => {
42 + const fixture = TestBed.createComponent(AppComponent);
43 + fixture.detectChanges();
44 + const compiled = fixture.debugElement.nativeElement;
45 + expect(compiled.querySelector('h1').textContent).toContain('app works!');
46 + }));
47 +});
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +import { Component, Inject } from '@angular/core';
17 +import { Observable } from 'rxjs/Observable';
18 +import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
19 +import { AngularFireAuth } from 'angularfire2/auth';
20 +import { MatSnackBar } from '@angular/material';
21 +import * as firebase from 'firebase';
22 +
23 +const LOADING_IMAGE_URL = 'https://www.google.com/images/spin-32.gif';
24 +const PROFILE_PLACEHOLDER_IMAGE_URL = '/assets/images/profile_placeholder.png';
25 +
26 +@Component({
27 + selector: 'app-root',
28 + templateUrl: './app.component.html',
29 + styleUrls: ['./app.component.css']
30 +})
31 +export class AppComponent {
32 + user: Observable<firebase.User>;
33 + currentUser: firebase.User;
34 + messages: Observable<any[]>;
35 + profilePicStyles: {};
36 + topics = '';
37 + value = '';
38 +
39 + constructor(public db: AngularFireDatabase, public afAuth: AngularFireAuth, public snackBar: MatSnackBar) {
40 + this.user = afAuth.authState;
41 + this.user.subscribe((user: firebase.User) => {
42 + console.log(user);
43 + this.currentUser = user;
44 +
45 + if (user) { // User is signed in!
46 + this.profilePicStyles = {
47 + 'background-image': `url(${this.currentUser.photoURL})`
48 + };
49 +
50 + // We load currently existing chat messages.
51 + this.messages = this.db.list<any>('/messages', ref => ref.limitToLast(12)).valueChanges();
52 + this.messages.subscribe((messages) => {
53 + // Calculate list of recently discussed topics
54 + const topicsMap = {};
55 + const topics = [];
56 + let hasEntities = false;
57 + messages.forEach((message) => {
58 + if (message.entities) {
59 + for (let entity of message.entities) {
60 + if (!topicsMap.hasOwnProperty(entity.name)) {
61 + topicsMap[entity.name] = 0
62 + }
63 + topicsMap[entity.name] += entity.salience;
64 + hasEntities = true;
65 + }
66 + }
67 + });
68 + if (hasEntities) {
69 + for (let name in topicsMap) {
70 + topics.push({ name, score: topicsMap[name] });
71 + }
72 + topics.sort((a, b) => b.score - a.score);
73 + this.topics = topics.map((topic) => topic.name).join(', ');
74 + }
75 +
76 + // Make sure new message scroll into view
77 + setTimeout(() => {
78 + const messageList = document.getElementById('messages');
79 + messageList.scrollTop = messageList.scrollHeight;
80 + document.getElementById('message').focus();
81 + }, 500);
82 + });
83 +
84 + // We save the Firebase Messaging Device token and enable notifications.
85 + this.saveMessagingDeviceToken();
86 + } else { // User is signed out!
87 + this.profilePicStyles = {
88 + 'background-image': PROFILE_PLACEHOLDER_IMAGE_URL
89 + };
90 + this.topics = '';
91 + }
92 + });
93 + }
94 +
95 + login() {
96 + this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider());
97 + }
98 +
99 + logout() {
100 + this.afAuth.auth.signOut();
101 + }
102 +
103 + // TODO: Refactor into text message form component
104 + update(value: string) {
105 + this.value = value;
106 + }
107 +
108 + // Returns true if user is signed-in. Otherwise false and displays a message.
109 + checkSignedInWithMessage() {
110 + // Return true if the user is signed in Firebase
111 + if (this.currentUser) {
112 + return true;
113 + }
114 +
115 + this.snackBar
116 + .open('You must sign-in first', 'Sign in', {
117 + duration: 5000
118 + })
119 + .onAction()
120 + .subscribe(() => this.login());
121 +
122 + return false;
123 + };
124 +
125 + // TODO: Refactor into text message form component
126 + saveMessage(event: any, el: HTMLInputElement) {
127 + event.preventDefault();
128 +
129 + if (this.value && this.checkSignedInWithMessage()) {
130 + // Add a new message entry to the Firebase Database.
131 + const messages = this.db.list('/messages');
132 + messages.push({
133 + name: this.currentUser.displayName,
134 + text: this.value,
135 + photoUrl: this.currentUser.photoURL || PROFILE_PLACEHOLDER_IMAGE_URL
136 + }).then(() => {
137 + // Clear message text field and SEND button state.
138 + el.value = '';
139 + }, (err) => {
140 + this.snackBar.open('Error writing new message to Firebase Database.', null, {
141 + duration: 5000
142 + });
143 + console.error(err);
144 + });
145 + }
146 + }
147 +
148 + // TODO: Refactor into image message form component
149 + saveImageMessage(event: any) {
150 + event.preventDefault();
151 + const file = event.target.files[0];
152 +
153 + // Clear the selection in the file picker input.
154 + const imageForm = <HTMLFormElement>document.getElementById('image-form');
155 + imageForm.reset();
156 +
157 + // Check if the file is an image.
158 + if (!file.type.match('image.*')) {
159 + this.snackBar.open('You can only share images', null, {
160 + duration: 5000
161 + });
162 + return;
163 + }
164 +
165 + // Check if the user is signed-in
166 + if (this.checkSignedInWithMessage()) {
167 +
168 + // We add a message with a loading icon that will get updated with the shared image.
169 + const messages = this.db.list('/messages');
170 + messages.push({
171 + name: this.currentUser.displayName,
172 + imageUrl: LOADING_IMAGE_URL,
173 + photoUrl: this.currentUser.photoURL || PROFILE_PLACEHOLDER_IMAGE_URL
174 + }).then((data) => {
175 + // Upload the image to Cloud Storage.
176 + const filePath = `${this.currentUser.uid}/${data.key}/${file.name}`;
177 + return firebase.storage().ref(filePath).put(file)
178 + .then((snapshot) => {
179 + // Get the file's Storage URI and update the chat message placeholder.
180 + const fullPath = snapshot.metadata.fullPath;
181 + const imageUrl = firebase.storage().ref(fullPath).toString();
182 + return firebase.storage().refFromURL(imageUrl).getMetadata();
183 + }).then((metadata) => {
184 + // TODO: Instead of saving the download URL, save the GCS URI and
185 + // dynamically load the download URL when displaying the image
186 + // message.
187 + return data.update({
188 + imageUrl: metadata.downloadURLs[0]
189 + });
190 + });
191 + }).then(console.log, (err) => {
192 + this.snackBar.open('There was an error uploading a file to Cloud Storage.', null, {
193 + duration: 5000
194 + });
195 + console.error(err);
196 + });
197 + }
198 + }
199 +
200 + // TODO: Refactor into image message form component
201 + onImageClick(event: any) {
202 + event.preventDefault();
203 + document.getElementById('mediaCapture').click();
204 + }
205 +
206 + // Saves the messaging device token to the datastore.
207 + saveMessagingDeviceToken() {
208 + return firebase.messaging().getToken()
209 + .then((currentToken) => {
210 + if (currentToken) {
211 + console.log('Got FCM device token:', currentToken);
212 + // Save the Device Token to the datastore.
213 + firebase.database()
214 + .ref('/fcmTokens')
215 + .child(currentToken)
216 + .set(this.currentUser.uid);
217 + } else {
218 + // Need to request permissions to show notifications.
219 + return this.requestNotificationsPermissions();
220 + }
221 + }).catch((err) => {
222 + this.snackBar.open('Unable to get messaging token.', null, {
223 + duration: 5000
224 + });
225 + console.error(err);
226 + });
227 + };
228 +
229 + // Requests permissions to show notifications.
230 + requestNotificationsPermissions() {
231 + console.log('Requesting notifications permission...');
232 + return firebase.messaging().requestPermission()
233 + // Notification permission granted.
234 + .then(() => this.saveMessagingDeviceToken())
235 + .catch((err) => {
236 + this.snackBar.open('Unable to get permission to notify.', null, {
237 + duration: 5000
238 + });
239 + console.error(err);
240 + });
241 + };
242 +}
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +import { BrowserModule } from '@angular/platform-browser';
17 +import { NgModule } from '@angular/core';
18 +import { FormsModule } from '@angular/forms';
19 +import { HttpModule } from '@angular/http';
20 +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
21 +import { AngularFireModule } from 'angularfire2';
22 +import { AngularFireDatabaseModule } from 'angularfire2/database';
23 +import { AngularFireAuthModule } from 'angularfire2/auth';
24 +import { MatSnackBarModule } from '@angular/material';
25 +
26 +import { AppComponent } from './app.component';
27 +import { StylizePipe } from './stylize.pipe';
28 +import { environment } from '../environments/environment';
29 +
30 +const configErrMsg = `You have not configured and imported the Firebase SDK.
31 +Make sure you go through the codelab setup instructions.`;
32 +
33 +const bucketErrMsg = `Your Firebase Storage bucket has not been enabled. Sorry
34 +about that. This is actually a Firebase bug that occurs rarely. Please go and
35 +re-generate the Firebase initialization snippet (step 4 of the codelab) and make
36 +sure the storageBucket attribute is not empty. You may also need to visit the
37 +Storage tab and paste the name of your bucket which is displayed there.`;
38 +
39 +if (!environment.firebase) {
40 + if (!environment.firebase.apiKey) {
41 + window.alert(configErrMsg);
42 + } else if (environment.firebase.storageBucket === '') {
43 + window.alert(bucketErrMsg);
44 + }
45 +}
46 +
47 +@NgModule({
48 + declarations: [
49 + AppComponent,
50 + StylizePipe
51 + ],
52 + imports: [
53 + BrowserModule,
54 + FormsModule,
55 + HttpModule,
56 + BrowserAnimationsModule,
57 + MatSnackBarModule,
58 + AngularFireModule.initializeApp(environment.firebase),
59 + AngularFireDatabaseModule,
60 + AngularFireAuthModule
61 + ],
62 + providers: [],
63 + bootstrap: [AppComponent]
64 +})
65 +export class AppModule { }
1 +import { Pipe, PipeTransform } from '@angular/core';
2 +import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
3 +
4 +interface Sentiment {
5 + score: number,
6 + magnitude: number
7 +}
8 +
9 +/*
10 + * Styles a message based on its sentiment.
11 + */
12 +@Pipe({name: 'stylize'})
13 +export class StylizePipe implements PipeTransform {
14 + constructor(private sanitizer: DomSanitizer) {}
15 +
16 + transform(sentiment: Sentiment): string|SafeStyle {
17 + if (!sentiment) {
18 + return '';
19 + }
20 +
21 + let style = '';
22 +
23 + // Change font based on positive/negative score.
24 + if (sentiment.score >= 0.9) {
25 + style += `font-family: 'Bonbon', 'Roboto', 'Helvetica', sans-serif;`;
26 + } else if (sentiment.score >= 0.5) {
27 + style += `font-family: 'Crafty Girls', 'Roboto', 'Helvetica', sans-serif;`;
28 + } else if (sentiment.score <= -0.9) {
29 + style += `font-family: 'Creepster', 'Roboto', 'Helvetica', sans-serif;`;
30 + } else if (sentiment.score <= -0.5) {
31 + style += `font-family: 'Julee', 'Roboto', 'Helvetica', sans-serif;`;
32 + }
33 +
34 + // Make bold if the magnitude is greater than 1.
35 + if (sentiment.magnitude >= 1) {
36 + style += `font-weight: bold;`;
37 + }
38 +
39 + return style ? this.sanitizer.bypassSecurityTrustStyle(style) : '';
40 + }
41 +}
1 +{
2 + "name": "Friendly Chat",
3 + "short_name": "Friendly Chat",
4 + "start_url": "/index.html",
5 + "display": "standalone",
6 + "orientation": "portrait"
7 +}
1 +{
2 + "version": "1.0.0",
3 + "name": "Friendly Chat",
4 + "launch_path": "/index.html",
5 + "description": "Chat with friends using Firebase 2.0",
6 + "developer": {
7 + "name": "Google inc.",
8 + "url": "https://firebase.google.com"
9 + },
10 + "installs_allowed_from": [
11 + "*"
12 + ],
13 + "default_locale": "en",
14 + "permissions": {
15 + },
16 + "locales": {
17 + "en": {
18 + "name": "Friendly Chat",
19 + "description": "Chat with friends using Firebase 2.0"
20 + }
21 + }
22 +}
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +export const environment = {
17 + production: true,
18 + firebase: {
19 + // ***********************************************************************************************************************
20 + // * TODO(DEVELOPER): Update values according to: Firebase Console > Overview > Add Firebase to your web app. *
21 + // ***********************************************************************************************************************
22 + apiKey: '',
23 + authDomain: '',
24 + databaseURL: '',
25 + projectId: '',
26 + storageBucket: '',
27 + messagingSenderId: ''
28 + }
29 +};
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +// The file contents for the current environment will overwrite these during build.
17 +// The build system defaults to the dev environment which uses `environment.ts`, but if you do
18 +// `ng build --env=prod` then `environment.prod.ts` will be used instead.
19 +// The list of which env maps to which file can be found in `.angular-cli.json`.
20 +
21 +export const environment = {
22 + production: false,
23 + firebase: {
24 + // ***********************************************************************************************************************
25 + // * TODO(DEVELOPER): Update values according to: Firebase Console > Overview > Add Firebase to your web app. *
26 + // ***********************************************************************************************************************
27 + apiKey: '',
28 + authDomain: '',
29 + databaseURL: '',
30 + projectId: '',
31 + storageBucket: '',
32 + messagingSenderId: ''
33 + }
34 +};
1 +importScripts('https://www.gstatic.com/firebasejs/3.6.6/firebase-app.js');
2 +importScripts('https://www.gstatic.com/firebasejs/3.6.6/firebase-messaging.js');
3 +
4 +// Initialize the Firebase app in the service worker by passing in the
5 +// messagingSenderId.
6 +firebase.initializeApp({
7 + // TODO add your messagingSenderId
8 + messagingSenderId: '662518903527'
9 +});
10 +var messaging = firebase.messaging();
1 +<!doctype html>
2 +<!--
3 + Copyright 2017 Google Inc. All rights reserved.
4 + Licensed under the Apache License, Version 2.0 (the "License");
5 + you may not use this file except in compliance with the License.
6 + You may obtain a copy of the License at
7 + https://www.apache.org/licenses/LICENSE-2.0
8 + Unless required by applicable law or agreed to in writing, software
9 + distributed under the License is distributed on an "AS IS" BASIS,
10 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 + See the License for the specific language governing permissions and
12 + limitations under the License
13 +-->
14 +<html lang="en">
15 +<head>
16 + <meta charset="utf-8">
17 + <meta http-equiv="X-UA-Compatible" content="IE=edge">
18 + <meta name="description" content="Learn how to use the Firebase platform on the Web">
19 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
20 + <title>Friendly Chat</title>
21 +
22 + <!-- Disable tap highlight on IE -->
23 + <meta name="msapplication-tap-highlight" content="no">
24 +
25 + <!-- Web Application Manifest -->
26 + <link rel="manifest" href="assets/manifest.json">
27 +
28 + <!-- Add to homescreen for Chrome on Android -->
29 + <meta name="mobile-web-app-capable" content="yes">
30 + <meta name="application-name" content="Friendly Chat">
31 + <meta name="theme-color" content="#303F9F">
32 +
33 + <!-- Add to homescreen for Safari on iOS -->
34 + <meta name="apple-mobile-web-app-capable" content="yes">
35 + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
36 + <meta name="apple-mobile-web-app-title" content="Friendly Chat">
37 + <meta name="apple-mobile-web-app-status-bar-style" content="#303F9F">
38 +
39 + <!-- Tile icon for Win8 -->
40 + <meta name="msapplication-TileColor" content="#3372DF">
41 + <meta name="msapplication-navbutton-color" content="#303F9F">
42 +
43 + <!-- Material Design Lite -->
44 + <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
45 + <link rel="stylesheet" href="https://code.getmdl.io/1.3.0/material.orange-indigo.min.css">
46 +
47 + <!-- App Styling -->
48 + <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Creepster:regular,bold|Julee:regular,bold|Crafty+Girls:regular,bold|Bonbon:regular,bold|Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&amp;lang=en">
49 +
50 + <base href="/">
51 +
52 + <link rel="icon" type="image/x-icon" href="favicon.ico">
53 +</head>
54 +<body>
55 + <app-root>Loading...</app-root>
56 +</body>
57 +</html>
1 +import { enableProdMode } from '@angular/core';
2 +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 +
4 +import { AppModule } from './app/app.module';
5 +import { environment } from './environments/environment';
6 +
7 +if (environment.production) {
8 + enableProdMode();
9 +}
10 +
11 +platformBrowserDynamic().bootstrapModule(AppModule)
12 + .catch(err => console.log(err));
1 +/**
2 + * This file includes polyfills needed by Angular and is loaded before the app.
3 + * You can add your own extra polyfills to this file.
4 + *
5 + * This file is divided into 2 sections:
6 + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 + * file.
9 + *
10 + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 + *
14 + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 + */
16 +
17 +/***************************************************************************************************
18 + * BROWSER POLYFILLS
19 + */
20 +
21 +/** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 +// import 'core-js/es6/symbol';
23 +// import 'core-js/es6/object';
24 +// import 'core-js/es6/function';
25 +// import 'core-js/es6/parse-int';
26 +// import 'core-js/es6/parse-float';
27 +// import 'core-js/es6/number';
28 +// import 'core-js/es6/math';
29 +// import 'core-js/es6/string';
30 +// import 'core-js/es6/date';
31 +// import 'core-js/es6/array';
32 +// import 'core-js/es6/regexp';
33 +// import 'core-js/es6/map';
34 +// import 'core-js/es6/weak-map';
35 +// import 'core-js/es6/set';
36 +
37 +/** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 +// import 'classlist.js'; // Run `npm install --save classlist.js`.
39 +
40 +/** IE10 and IE11 requires the following for the Reflect API. */
41 +// import 'core-js/es6/reflect';
42 +
43 +
44 +/** Evergreen browsers require these. **/
45 +// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46 +import 'core-js/es7/reflect';
47 +
48 +
49 +/**
50 + * Required to support Web Animations `@angular/platform-browser/animations`.
51 + * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
52 + **/
53 +// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
54 +
55 +/**
56 + * By default, zone.js will patch all possible macroTask and DomEvents
57 + * user can disable parts of macroTask/DomEvents patch by setting following flags
58 + */
59 +
60 + // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
61 + // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
62 + // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
63 +
64 + /*
65 + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
66 + * with the following flag, it will bypass `zone.js` patch for IE/Edge
67 + */
68 +// (window as any).__Zone_enable_cross_context_check = true;
69 +
70 +/***************************************************************************************************
71 + * Zone JS is required by default for Angular itself.
72 + */
73 +import 'zone.js/dist/zone'; // Included with Angular CLI.
74 +
75 +
76 +
77 +/***************************************************************************************************
78 + * APPLICATION IMPORTS
79 + */
1 +/**
2 + * Copyright 2015 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +html, body {
18 + font-family: 'Roboto', 'Helvetica', sans-serif;
19 +}
20 +.cdk-visually-hidden {
21 + display: none;
22 +}
1 +// This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 +
3 +import 'zone.js/dist/zone-testing';
4 +import { getTestBed } from '@angular/core/testing';
5 +import {
6 + BrowserDynamicTestingModule,
7 + platformBrowserDynamicTesting
8 +} from '@angular/platform-browser-dynamic/testing';
9 +
10 +declare const require: any;
11 +
12 +// First, initialize the Angular testing environment.
13 +getTestBed().initTestEnvironment(
14 + BrowserDynamicTestingModule,
15 + platformBrowserDynamicTesting()
16 +);
17 +// Then we find all the tests.
18 +const context = require.context('./', true, /\.spec\.ts$/);
19 +// And load the modules.
20 +context.keys().map(context);
1 +{
2 + "extends": "../tsconfig.json",
3 + "compilerOptions": {
4 + "outDir": "../out-tsc/app",
5 + "baseUrl": "./",
6 + "module": "es2015",
7 + "types": []
8 + },
9 + "exclude": [
10 + "test.ts",
11 + "**/*.spec.ts"
12 + ]
13 +}
1 +{
2 + "extends": "../tsconfig.json",
3 + "compilerOptions": {
4 + "outDir": "../out-tsc/spec",
5 + "baseUrl": "./",
6 + "module": "commonjs",
7 + "types": [
8 + "jasmine",
9 + "node"
10 + ]
11 + },
12 + "files": [
13 + "test.ts"
14 + ],
15 + "include": [
16 + "**/*.spec.ts",
17 + "**/*.d.ts"
18 + ]
19 +}
1 +/* SystemJS module definition */
2 +declare var module: NodeModule;
3 +interface NodeModule {
4 + id: string;
5 +}
1 +service firebase.storage {
2 + match /b/{bucket}/o {
3 + match /{userId}/{postId}/{fileName} {
4 + allow write: if request.auth.uid == userId;
5 + allow read;
6 + }
7 + }
8 +}
1 +{
2 + "compileOnSave": false,
3 + "compilerOptions": {
4 + "outDir": "./dist/out-tsc",
5 + "sourceMap": true,
6 + "declaration": false,
7 + "moduleResolution": "node",
8 + "emitDecoratorMetadata": true,
9 + "experimentalDecorators": true,
10 + "target": "es5",
11 + "typeRoots": [
12 + "node_modules/@types"
13 + ],
14 + "lib": [
15 + "es2017",
16 + "dom"
17 + ]
18 + }
19 +}
1 +{
2 + "rulesDirectory": [
3 + "node_modules/codelyzer"
4 + ],
5 + "rules": {
6 + "arrow-return-shorthand": true,
7 + "callable-types": true,
8 + "class-name": true,
9 + "comment-format": [
10 + true,
11 + "check-space"
12 + ],
13 + "curly": true,
14 + "deprecation": {
15 + "severity": "warn"
16 + },
17 + "eofline": true,
18 + "forin": true,
19 + "import-blacklist": [
20 + true,
21 + "rxjs",
22 + "rxjs/Rx"
23 + ],
24 + "import-spacing": true,
25 + "indent": [
26 + true,
27 + "spaces"
28 + ],
29 + "interface-over-type-literal": true,
30 + "label-position": true,
31 + "max-line-length": [
32 + true,
33 + 140
34 + ],
35 + "member-access": false,
36 + "member-ordering": [
37 + true,
38 + {
39 + "order": [
40 + "static-field",
41 + "instance-field",
42 + "static-method",
43 + "instance-method"
44 + ]
45 + }
46 + ],
47 + "no-arg": true,
48 + "no-bitwise": true,
49 + "no-console": [
50 + true,
51 + "debug",
52 + "info",
53 + "time",
54 + "timeEnd",
55 + "trace"
56 + ],
57 + "no-construct": true,
58 + "no-debugger": true,
59 + "no-duplicate-super": true,
60 + "no-empty": false,
61 + "no-empty-interface": true,
62 + "no-eval": true,
63 + "no-inferrable-types": [
64 + true,
65 + "ignore-params"
66 + ],
67 + "no-misused-new": true,
68 + "no-non-null-assertion": true,
69 + "no-shadowed-variable": true,
70 + "no-string-literal": false,
71 + "no-string-throw": true,
72 + "no-switch-case-fall-through": true,
73 + "no-trailing-whitespace": true,
74 + "no-unnecessary-initializer": true,
75 + "no-unused-expression": true,
76 + "no-use-before-declare": true,
77 + "no-var-keyword": true,
78 + "object-literal-sort-keys": false,
79 + "one-line": [
80 + true,
81 + "check-open-brace",
82 + "check-catch",
83 + "check-else",
84 + "check-whitespace"
85 + ],
86 + "prefer-const": true,
87 + "quotemark": [
88 + true,
89 + "single"
90 + ],
91 + "radix": true,
92 + "semicolon": [
93 + true,
94 + "always"
95 + ],
96 + "triple-equals": [
97 + true,
98 + "allow-null-check"
99 + ],
100 + "typedef-whitespace": [
101 + true,
102 + {
103 + "call-signature": "nospace",
104 + "index-signature": "nospace",
105 + "parameter": "nospace",
106 + "property-declaration": "nospace",
107 + "variable-declaration": "nospace"
108 + }
109 + ],
110 + "unified-signatures": true,
111 + "variable-name": false,
112 + "whitespace": [
113 + true,
114 + "check-branch",
115 + "check-decl",
116 + "check-operator",
117 + "check-separator",
118 + "check-type"
119 + ],
120 + "directive-selector": [
121 + true,
122 + "attribute",
123 + "app",
124 + "camelCase"
125 + ],
126 + "component-selector": [
127 + true,
128 + "element",
129 + "app",
130 + "kebab-case"
131 + ],
132 + "no-output-on-prefix": true,
133 + "use-input-property-decorator": true,
134 + "use-output-property-decorator": true,
135 + "use-host-property-decorator": true,
136 + "no-input-rename": true,
137 + "no-output-rename": true,
138 + "use-life-cycle-interface": true,
139 + "use-pipe-transform-interface": true,
140 + "component-class-suffix": true,
141 + "directive-class-suffix": true
142 + }
143 +}
1 +# Firebase SDK for Cloud Functions Codelab - Start code
2 +
3 +This folder contains the start code of the [Firebase SDK for Cloud Functions Codelab](https://codelabs.developers.google.com/codelabs/firebase-cloud-functions/).
4 +
5 +If you'd like to jump directly to the end and see the finished code head to the [cloud-functions](../cloud-functions) directory.
1 +{
2 + "rules": {
3 + "messages": {
4 + ".read": true,
5 + // Users can only write messages if they are authenticated.
6 + ".write": "auth !== null",
7 + "$messageId": {
8 + // The name has to be the real display name.
9 + ".validate": "newData.child('name').val() === auth.token.name",
10 + "text": {
11 + // The text has to be less than 300 char.
12 + ".validate": "newData.isString() && newData.val().length < 300"
13 + }
14 + }
15 + },
16 + "fcmTokens": {
17 + // User can write their tokens to the database but not read the list of tokens.
18 + ".read": false,
19 + ".write": true
20 + }
21 + }
22 +}
1 +// Import and configure the Firebase SDK
2 +// These scripts are made available when the app is served or deployed on Firebase Hosting
3 +// If you do not want to serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup
4 +importScripts('/__/firebase/5.1.0/firebase-app.js');
5 +importScripts('/__/firebase/5.1.0/firebase-messaging.js');
6 +importScripts('/__/firebase/init.js');
7 +
8 +firebase.messaging();
1 +{
2 + "database": {
3 + "rules": "database-rules.json"
4 + },
5 + "storage": {
6 + "rules": "storage.rules"
7 + },
8 + "hosting": {
9 + "public": "./",
10 + "ignore": [
11 + "firebase.json",
12 + "database-rules.json",
13 + "storage.rules",
14 + "functions"
15 + ],
16 + "headers": [{
17 + "source" : "**/*.@(js|html)",
18 + "headers" : [ {
19 + "key" : "Cache-Control",
20 + "value" : "max-age=0"
21 + } ]
22 + }]
23 + }
24 +}
1 +{
2 + "parserOptions": {
3 + // Required for certain syntax usages
4 + "ecmaVersion": 6
5 + },
6 + "plugins": [
7 + "promise"
8 + ],
9 + "extends": "eslint:recommended",
10 + "rules": {
11 + // Removed rule "disallow the use of console" from recommended eslint rules
12 + "no-console": "off",
13 +
14 + // Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules
15 + "no-regex-spaces": "off",
16 +
17 + // Removed rule "disallow the use of debugger" from recommended eslint rules
18 + "no-debugger": "off",
19 +
20 + // Removed rule "disallow unused variables" from recommended eslint rules
21 + "no-unused-vars": "off",
22 +
23 + // Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules
24 + "no-mixed-spaces-and-tabs": "off",
25 +
26 + // Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules
27 + "no-undef": "off",
28 +
29 + // Warn against template literal placeholder syntax in regular strings
30 + "no-template-curly-in-string": 1,
31 +
32 + // Warn if return statements do not either always or never specify values
33 + "consistent-return": 1,
34 +
35 + // Warn if no return statements in callbacks of array methods
36 + "array-callback-return": 1,
37 +
38 + // Require the use of === and !==
39 + "eqeqeq": 2,
40 +
41 + // Disallow the use of alert, confirm, and prompt
42 + "no-alert": 2,
43 +
44 + // Disallow the use of arguments.caller or arguments.callee
45 + "no-caller": 2,
46 +
47 + // Disallow null comparisons without type-checking operators
48 + "no-eq-null": 2,
49 +
50 + // Disallow the use of eval()
51 + "no-eval": 2,
52 +
53 + // Warn against extending native types
54 + "no-extend-native": 1,
55 +
56 + // Warn against unnecessary calls to .bind()
57 + "no-extra-bind": 1,
58 +
59 + // Warn against unnecessary labels
60 + "no-extra-label": 1,
61 +
62 + // Disallow leading or trailing decimal points in numeric literals
63 + "no-floating-decimal": 2,
64 +
65 + // Warn against shorthand type conversions
66 + "no-implicit-coercion": 1,
67 +
68 + // Warn against function declarations and expressions inside loop statements
69 + "no-loop-func": 1,
70 +
71 + // Disallow new operators with the Function object
72 + "no-new-func": 2,
73 +
74 + // Warn against new operators with the String, Number, and Boolean objects
75 + "no-new-wrappers": 1,
76 +
77 + // Disallow throwing literals as exceptions
78 + "no-throw-literal": 2,
79 +
80 + // Require using Error objects as Promise rejection reasons
81 + "prefer-promise-reject-errors": 2,
82 +
83 + // Enforce “for” loop update clause moving the counter in the right direction
84 + "for-direction": 2,
85 +
86 + // Enforce return statements in getters
87 + "getter-return": 2,
88 +
89 + // Disallow await inside of loops
90 + "no-await-in-loop": 2,
91 +
92 + // Disallow comparing against -0
93 + "no-compare-neg-zero": 2,
94 +
95 + // Warn against catch clause parameters from shadowing variables in the outer scope
96 + "no-catch-shadow": 1,
97 +
98 + // Disallow identifiers from shadowing restricted names
99 + "no-shadow-restricted-names": 2,
100 +
101 + // Enforce return statements in callbacks of array methods
102 + "callback-return": 2,
103 +
104 + // Require error handling in callbacks
105 + "handle-callback-err": 2,
106 +
107 + // Warn against string concatenation with __dirname and __filename
108 + "no-path-concat": 1,
109 +
110 + // Prefer using arrow functions for callbacks
111 + "prefer-arrow-callback": 1,
112 +
113 + // Return inside each then() to create readable and reusable Promise chains.
114 + // Forces developers to return console logs and http calls in promises.
115 + "promise/always-return": 2,
116 +
117 + //Enforces the use of catch() on un-returned promises
118 + "promise/catch-or-return": 2,
119 +
120 + // Warn against nested then() or catch() statements
121 + "promise/no-nesting": 1
122 + }
123 +}
1 +/**
2 + * Copyright 2017 Google Inc. All Rights Reserved.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +// TODO(DEVELOPER): Import the Cloud Functions for Firebase and the Firebase Admin modules here.
18 +
19 +// TODO(DEVELOPER): Write the addWelcomeMessages Function here.
20 +
21 +// TODO(DEVELOPER): Write the blurOffensiveImages Function here.
22 +
23 +// TODO(DEVELOPER): Write the sendNotifications Function here.
1 +{
2 + "name": "friendlychat-codelab",
3 + "description": "Firebase SDK for Cloud Functions codelab",
4 + "dependencies": {
5 + "firebase-admin": "~5.12.1",
6 + "firebase-functions": "^1.1.0"
7 + },
8 + "devDependencies": {
9 + "eslint": "^4.12.0",
10 + "eslint-plugin-promise": "^3.6.0"
11 + }
12 +}
1 +<!doctype html>
2 +<!--
3 + Copyright 2015 Google Inc. All rights reserved.
4 + Licensed under the Apache License, Version 2.0 (the "License");
5 + you may not use this file except in compliance with the License.
6 + You may obtain a copy of the License at
7 + https://www.apache.org/licenses/LICENSE-2.0
8 + Unless required by applicable law or agreed to in writing, software
9 + distributed under the License is distributed on an "AS IS" BASIS,
10 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 + See the License for the specific language governing permissions and
12 + limitations under the License
13 +-->
14 +<html lang="en">
15 +<head>
16 + <meta charset="utf-8">
17 + <meta http-equiv="X-UA-Compatible" content="IE=edge">
18 + <meta name="description" content="Learn how to use the Firebase platform on the Web">
19 + <meta name="viewport" content="width=device-width, initial-scale=1.0">
20 + <title>Friendly Chat</title>
21 +
22 + <!-- Disable tap highlight on IE -->
23 + <meta name="msapplication-tap-highlight" content="no">
24 +
25 + <!-- Web Application Manifest -->
26 + <link rel="manifest" href="manifest.json">
27 +
28 + <!-- Add to homescreen for Chrome on Android -->
29 + <meta name="mobile-web-app-capable" content="yes">
30 + <meta name="application-name" content="Friendly Chat">
31 + <meta name="theme-color" content="#303F9F">
32 +
33 + <!-- Add to homescreen for Safari on iOS -->
34 + <meta name="apple-mobile-web-app-capable" content="yes">
35 + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
36 + <meta name="apple-mobile-web-app-title" content="Friendly Chat">
37 + <meta name="apple-mobile-web-app-status-bar-style" content="#303F9F">
38 +
39 + <!-- Tile icon for Win8 -->
40 + <meta name="msapplication-TileColor" content="#3372DF">
41 + <meta name="msapplication-navbutton-color" content="#303F9F">
42 +
43 + <!-- Material Design Lite -->
44 + <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
45 + <link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.orange-indigo.min.css">
46 + <script defer src="https://code.getmdl.io/1.1.3/material.min.js"></script>
47 +
48 + <!-- App Styling -->
49 + <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&amp;lang=en">
50 + <link rel="stylesheet" href="styles/main.css">
51 +</head>
52 +<body>
53 +<div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header">
54 +
55 + <!-- Header section containing logo -->
56 + <header class="mdl-layout__header mdl-color-text--white mdl-color--light-blue-700">
57 + <div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
58 + <div class="mdl-layout__header-row mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--12-col-desktop">
59 + <h3><i class="material-icons">chat_bubble_outline</i> Friendly Chat</h3>
60 + </div>
61 + <div id="user-container">
62 + <div hidden id="user-pic"></div>
63 + <div hidden id="user-name"></div>
64 + <button hidden id="sign-out" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-color-text--white">
65 + Sign-out
66 + </button>
67 + <button hidden id="sign-in" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-color-text--white">
68 + <i class="material-icons">account_circle</i>Sign-in with Google
69 + </button>
70 + </div>
71 + </div>
72 + </header>
73 +
74 + <main class="mdl-layout__content mdl-color--grey-100">
75 + <div id="messages-card-container" class="mdl-cell mdl-cell--12-col mdl-grid">
76 +
77 + <!-- Messages container -->
78 + <div id="messages-card" class="mdl-card mdl-shadow--2dp mdl-cell mdl-cell--12-col mdl-cell--6-col-tablet mdl-cell--6-col-desktop">
79 + <div class="mdl-card__supporting-text mdl-color-text--grey-600">
80 + <div id="messages">
81 + <span id="message-filler"></span>
82 + </div>
83 + <form id="message-form" action="#">
84 + <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
85 + <input class="mdl-textfield__input" type="text" id="message">
86 + <label class="mdl-textfield__label" for="message">Message...</label>
87 + </div>
88 + <button id="submit" disabled type="submit" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect">
89 + Send
90 + </button>
91 + </form>
92 + <form id="image-form" action="#">
93 + <input id="mediaCapture" type="file" accept="image/*" capture="camera">
94 + <button id="submitImage" title="Add an image" class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-color--amber-400 mdl-color-text--white">
95 + <i class="material-icons">image</i>
96 + </button>
97 + </form>
98 + </div>
99 + </div>
100 +
101 + <div id="must-signin-snackbar" class="mdl-js-snackbar mdl-snackbar">
102 + <div class="mdl-snackbar__text"></div>
103 + <button class="mdl-snackbar__action" type="button"></button>
104 + </div>
105 +
106 + </div>
107 + </main>
108 +</div>
109 +
110 +<!-- Import and configure the Firebase SDK -->
111 +<!-- These scripts are made available when the app is served or deployed on Firebase Hosting -->
112 +<!-- If you do not want to serve/host your project using Firebase Hosting see https://firebase.google.com/docs/web/setup -->
113 +<script src="/__/firebase/5.1.0/firebase-app.js"></script>
114 +<script src="/__/firebase/5.1.0/firebase-auth.js"></script>
115 +<script src="/__/firebase/5.1.0/firebase-database.js"></script>
116 +<script src="/__/firebase/5.1.0/firebase-storage.js"></script>
117 +<script src="/__/firebase/5.1.0/firebase-messaging.js"></script>
118 +<script src="/__/firebase/init.js"></script>
119 +
120 +<script src="scripts/main.js"></script>
121 +</body>
122 +</html>
1 +{
2 + "name": "Friendly Chat",
3 + "short_name": "Friendly Chat",
4 + "start_url": "/index.html",
5 + "display": "standalone",
6 + "orientation": "portrait",
7 + "gcm_sender_id": "103953800507"
8 +}
1 +service firebase.storage {
2 + match /b/{bucket}/o {
3 + match /{userId}/{messageId}/{fileName} {
4 + allow write: if request.auth.uid == userId;
5 + allow read;
6 + }
7 + }
8 +}
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff could not be displayed because it is too large.