├── .editorconfig ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── angular.json ├── commitlint.config.js ├── logo.svg ├── package-lock.json ├── package.json ├── prettier.config.js ├── projects └── universal │ ├── LICENSE │ ├── karma.conf.js │ ├── ng-package.json │ ├── package.json │ ├── src │ ├── classes │ │ ├── blob-mock.ts │ │ ├── dom-string-list-mock.ts │ │ ├── location-mock.ts │ │ └── storage-mock.ts │ ├── constants │ │ ├── tests │ │ │ ├── universal-local-storage.spec.ts │ │ │ └── universal-navigator.spec.ts │ │ ├── universal-animation-frame.ts │ │ ├── universal-caches.ts │ │ ├── universal-crypto.ts │ │ ├── universal-history.ts │ │ ├── universal-local-storage.ts │ │ ├── universal-location.ts │ │ ├── universal-media-devices.ts │ │ ├── universal-navigator.ts │ │ ├── universal-performance.ts │ │ ├── universal-providers.ts │ │ ├── universal-session-storage.ts │ │ ├── universal-speech-synthesis.ts │ │ ├── universal-user-agent.ts │ │ └── universal-window.ts │ ├── mocks.js │ ├── module.ts │ ├── public-api.ts │ ├── test.ts │ ├── tokens │ │ ├── ssr-location.ts │ │ └── ssr-user-agent.ts │ └── utils │ │ ├── event-target.ts │ │ ├── functions.ts │ │ ├── provide-location.ts │ │ ├── provide-user-agent.ts │ │ └── tests │ │ ├── functions.spec.ts │ │ ├── provide-location.spec.ts │ │ └── provide-user-agent.spec.ts │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── scripts ├── coveralls.js ├── postbuild.js └── syncVersions.js ├── tsconfig.eslint.json ├── tsconfig.json └── web-api.svg /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | // TODO: move rules to @tinkoff/eslint-config-angular 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/eslint-recommended', 7 | '@tinkoff/eslint-config-angular', 8 | ], 9 | ignorePatterns: ['projects/**/test.ts'], 10 | parserOptions: { 11 | ecmaVersion: 2020, 12 | sourceType: 'module', 13 | project: [require.resolve('./tsconfig.eslint.json')], 14 | }, 15 | parser: '@typescript-eslint/parser', 16 | rules: { 17 | // TODO: move rules to @tinkoff/eslint-config-angular 18 | '@typescript-eslint/no-useless-constructor': 'off', 19 | '@typescript-eslint/no-inferrable-types': ['error', {ignoreParameters: true}], 20 | '@typescript-eslint/explicit-member-accessibility': [ 21 | 'error', 22 | {accessibility: 'no-public'}, 23 | ], 24 | '@typescript-eslint/prefer-readonly': ['error'], 25 | 'no-console': ['error', {allow: ['info', 'assert', 'warn', 'error']}], 26 | 27 | 'no-prototype-builtins': 'off', 28 | // note you must disable the base rule as it can report incorrect errors 29 | 'no-unused-vars': 'off', 30 | '@typescript-eslint/no-unused-vars': ['error', {argsIgnorePattern: '^_'}], 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | 2 | # ================================================================================== 3 | # ================================================================================== 4 | # @ng-web-apis/universal codeowners 5 | # ================================================================================== 6 | # ================================================================================== 7 | # 8 | # Configuration of code ownership and review approvals for the @ng-web-apis/universal repo. 9 | # 10 | # More info: https://help.github.com/articles/about-codeowners/ 11 | # 12 | 13 | * @waterplea @MarsiBarsi 14 | # will be requested for review when someone opens a pull request 15 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | open_collective: ng-web-apis 4 | issuehunt: ng-web-apis 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Bug report 3 | about: Create a report to help us improve 4 | title: '[BUG] ' 5 | labels: '' 6 | assignees: waterplea 7 | --- 8 | 9 | # 🐞 Bug report 10 | 11 | ### Description 12 | 13 | 14 | 15 | ### Reproduction 16 | 17 | 18 | 19 | http://www.stackblitz.com/... 20 | 21 | ### Expected behavior 22 | 23 | 24 | 25 | ### Versions 26 | 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Angular [e.g. 8] 30 | 31 | ### Additional context 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature request 3 | about: Suggest an idea for this project 4 | title: '[FEATURE]' 5 | labels: '' 6 | assignees: waterplea 7 | --- 8 | 9 | # 🚀 Feature request 10 | 11 | ### Is your feature request related to a problem? 12 | 13 | 14 | I'm always frustrated when... 15 | 16 | ### Describe the solution you'd like 17 | 18 | 19 | 20 | 21 | ### Describe alternatives you've considered 22 | 23 | 24 | 25 | 26 | ### Additional context 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## PR Checklist 2 | 3 | Please check if your PR fulfills the following requirements: 4 | 5 | - [ ] The commit message follows [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.4/) 6 | - [ ] Tests for the changes have been added (for bug fixes / features) 7 | - [ ] Docs have been added / updated (for bug fixes / features) 8 | 9 | ## PR Type 10 | 11 | What kind of change does this PR introduce? 12 | 13 | 14 | 15 | - [ ] Bugfix 16 | - [ ] Feature 17 | - [ ] Refactoring (no functional changes, no api changes) 18 | - [ ] Other... Please describe: 19 | 20 | ## What is the current behavior? 21 | 22 | 23 | 24 | Issue Number: N/A 25 | 26 | ## What is the new behavior? 27 | 28 | ## Does this PR introduce a breaking change? 29 | 30 | - [ ] Yes 31 | - [ ] No 32 | 33 | 34 | 35 | ## Other information 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /tmp 4 | /out-tsc 5 | # Only exists if Bazel was run 6 | /bazel-out 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # misc 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | yarn-error.log 39 | testem.log 40 | /typings 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "14" 4 | script: 5 | - npm run lint 6 | - npm run build 7 | - npm run test 8 | notifications: 9 | webhooks: https://coveralls.io/webhook 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [2.1.0](https://github.com/ng-web-apis/universal/compare/v2.0.0...v2.1.0) (2022-12-14) 6 | 7 | 8 | ### Features 9 | 10 | * provide typesafe mocks for `SSR_USER_AGENT` & `SSR_LOCATION` ([#41](https://github.com/ng-web-apis/universal/issues/41)) ([ee98d1b](https://github.com/ng-web-apis/universal/commit/ee98d1b8cc6685947f8471d6733d5573f993815b)) 11 | 12 | ## [2.0.0](https://github.com/ng-web-apis/universal/compare/v1.13.0...v2.0.0) (2022-01-28) 13 | 14 | 15 | ### Features 16 | 17 | * update to Angular 12 and Ivy distribution ([#25](https://github.com/ng-web-apis/universal/issues/25)) ([a3e9eaa](https://github.com/ng-web-apis/universal/commit/a3e9eaa63931141d9f78d4d8d326c77f26562f61)) 18 | 19 | ## [1.13.0](https://github.com/ng-web-apis/universal/compare/v1.12.0...v1.13.0) (2022-01-28) 20 | 21 | ### Features 22 | 23 | - **MEDIA_DEVICES:** add SSR mock for the new token ([7232e1b](https://github.com/ng-web-apis/universal/commit/7232e1b15d55941cd6d0b3fca8fb457719990547)) 24 | 25 | ## [1.12.0](https://github.com/ng-web-apis/universal/compare/v1.9.5...v1.12.0) (2021-07-13) 26 | 27 | ### Features 28 | 29 | - **module:** add module with providers ([#13](https://github.com/ng-web-apis/universal/issues/13)) ([ea56681](https://github.com/ng-web-apis/universal/commit/ea566810063aa9673f36fe17e62b748356057dea)) 30 | - **tokens:** sync with new tokens from common package ([#15](https://github.com/ng-web-apis/universal/issues/15)) ([2f064fb](https://github.com/ng-web-apis/universal/commit/2f064fb0c391b3d42c0bb197c84d16b7dbb54db2)) 31 | 32 | ### [1.9.5](https://github.com/ng-web-apis/universal/compare/v1.9.4...v1.9.5) (2021-03-12) 33 | 34 | ### Bug Fixes 35 | 36 | - **WINDOW:** fix `navigator` used in `clientInformation` ([89cbabb](https://github.com/ng-web-apis/universal/commit/89cbabb28ef01a5a18e5bf675b6fe3e58e7ef976)) 37 | 38 | ### [1.9.4](https://github.com/ng-web-apis/universal/compare/v1.9.3...v1.9.4) (2021-03-08) 39 | 40 | ### Bug Fixes 41 | 42 | - **WINDOW:** provide typesafe mock for `WINDOW` ([#9](https://github.com/ng-web-apis/universal/issues/9)) ([9ec57bb](https://github.com/ng-web-apis/universal/commit/9ec57bb53171bcc8715afd3e87eb94dc59ea010b)) 43 | 44 | ### [1.9.3](https://github.com/ng-web-apis/universal/compare/v1.9.2...v1.9.3) (2021-01-12) 45 | 46 | ### Bug Fixes 47 | 48 | - **LOCAL_STORAGE:** fix incorrect provider ([676d5e4](https://github.com/ng-web-apis/universal/commit/676d5e46de22a864024ff8f7e2c835332460c3b8)) 49 | 50 | ### [1.9.2](https://github.com/ng-web-apis/universal/compare/v1.9.1...v1.9.2) (2020-11-30) 51 | 52 | ### Bug Fixes 53 | 54 | - **mocks:** add mocks to side effects so they are not tree shaken ([777a1e0](https://github.com/ng-web-apis/universal/commit/777a1e0b1969a2b8a7459de51c25fdadd12b8c53)) 55 | 56 | ### [1.9.1](https://github.com/ng-web-apis/universal/compare/v1.9.0...v1.9.1) (2020-11-25) 57 | 58 | ### Bug Fixes 59 | 60 | - **navigator:** fix rejected Promise error and add mocks for native classes ([#6](https://github.com/ng-web-apis/universal/issues/6)) ([6db2660](https://github.com/ng-web-apis/universal/commit/6db2660a103053b844ba4790eaf73f0be79e42a1)) 61 | 62 | ## [1.9.0](https://github.com/ng-web-apis/universal/compare/v1.8.0...v1.9.0) (2020-11-17) 63 | 64 | ### Bug Fixes 65 | 66 | - **navigator:** fix always pending Promises ([c75c60a](https://github.com/ng-web-apis/universal/commit/c75c60ade5a9e6ae820fddca5260e594397d02e9)) 67 | 68 | ## [1.8.0](https://github.com/ng-web-apis/universal/compare/v1.6.0...v1.8.0) (2020-10-27) 69 | 70 | ### Features 71 | 72 | - **SPEECH_SYNTHESIS:** add new mock ([#5](https://github.com/ng-web-apis/universal/issues/5)) ([dbb5e43](https://github.com/ng-web-apis/universal/commit/dbb5e43eae1364611e74ac40f74e7f504ad06634)) 73 | 74 | ## [1.6.0](https://github.com/ng-web-apis/universal/compare/v1.5.0...v1.6.0) (2020-08-10) 75 | 76 | ### Features 77 | 78 | - **UNIVERSAL_SESSION_STORAGE:** add SESSION_STORAGE support ([#3](https://github.com/ng-web-apis/universal/issues/3)) ([0625aa2](https://github.com/ng-web-apis/universal/commit/0625aa20f225179927176d6958e9ecb56962c904)) 79 | 80 | ## [1.5.0](https://github.com/ng-web-apis/universal/compare/v1.4.0...v1.5.0) (2020-07-24) 81 | 82 | ### Features 83 | 84 | - **page-visibility:** cover new token from common package in README and sync minor version ([b95998a](https://github.com/ng-web-apis/universal/commit/b95998abf1aa6b8469631d43e7eb5a76a7765374)) 85 | 86 | ## 1.4.0 (2020-05-06) 87 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project at ng.web.apis@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | > Thank you for considering contributing to our project. Your help if very welcome! 4 | 5 | When contributing, it's better to first discuss the change you wish to make via issue, 6 | email, or any other method with the owners of this repository before making a change. 7 | 8 | All members of our community are expected to follow our [Code of Conduct](CODE_OF_CONDUCT.md). 9 | Please make sure you are welcoming and friendly in all of our spaces. 10 | 11 | ## Getting started 12 | 13 | In order to make your contribution please make a fork of the repository. After you've pulled 14 | the code, follow these steps to kick start the development: 15 | 16 | 1. Run `npm ci` to install dependencies 17 | 2. Run `npm start` to launch demo project where you could test your changes 18 | 3. Use following commands to ensure code quality 19 | 20 | ``` 21 | npm run lint 22 | npm run build 23 | npm run test 24 | ``` 25 | 26 | ## Pull Request Process 27 | 28 | 1. We follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.4/) 29 | in our commit messages, i.e. `feat(core): improve typing` 30 | 2. Update [README.md](README.md) to reflect changes related to public API and everything relevant 31 | 3. Make sure you cover all code changes with unit tests 32 | 4. When you are ready, create Pull Request of your fork into original repository 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alexander Inkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ___ 2 | ___ 3 | **Attention!** This repository is archived and the library has been moved to [tinkoff/ng-web-apis](https://github.com/Tinkoff/ng-web-apis) monorepository 4 | ___ 5 | ___ 6 | # ![logo](logo.svg) Angular Universal fallbacks 7 | 8 | > Part of [Web APIs for Angular](https://ng-web-apis.github.io/) 9 | 10 | [![npm version](https://img.shields.io/npm/v/@ng-web-apis/universal.svg)](https://npmjs.com/package/@ng-web-apis/universal) 11 | [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@ng-web-apis/universal)](https://bundlephobia.com/result?p=@ng-web-apis/universal) 12 | [![Travis (.org)](https://img.shields.io/travis/ng-web-apis/universal)](https://travis-ci.org/ng-web-apis/universal) 13 | [![Coveralls github](https://img.shields.io/coveralls/github/ng-web-apis/universal)](https://coveralls.io/github/ng-web-apis/universal?branch=master) 14 | 15 | A set of fallbacks to seamlessly use 16 | [@ng-web-apis/common](https://github.com/ng-web-apis/common) in 17 | [Angular Universal](https://github.com/angular/universal) apps. 18 | These packages have synced versions down to minor. 19 | 20 | ## How to use 21 | 22 | Add constants imported from this package to providers of your `ServerAppModule`. 23 | Typically, you can also use these mocks for tests. Idea of this package is — you shouldn't 24 | have to mock DOM on the server side or test `isPlatformBrowser` all the time. Instead, 25 | you leverage Angular DI system to abstract from implementation. When possible, this package 26 | will provide the same functionality on the server side as you have in browser. In other cases 27 | you will get type-safe mocks and you can at least be sure you will not have 28 | `cannot read propery of null` or `undefined is not a function` errors in SSR. 29 | 30 | > **IMPORTANT:** This library relies on **_Node.js_ v10** and above on your server side 31 | 32 | ## Mocks 33 | 34 | Add following line to your `server.ts` to mock native classes used in other @ng-web-apis packages: 35 | 36 | ```js 37 | import '@ng-web-apis/universal/mocks'; 38 | ``` 39 | 40 | > It is recommended to keep the import statement at the top of your `server.ts` file 41 | ## Tokens 42 | 43 | You can provide tokens from this package into your `app.server.module.ts` 44 | to have type safe mocks for global objects on server side with `UniversalModule`: 45 | 46 | ```ts 47 | @NgModule({ 48 | imports: [ 49 | AppBrowserModule, 50 | ServerModule, 51 | UniversalModule, // <-- add this 52 | ], 53 | bootstrap: [AppComponent], 54 | }) 55 | export class AppServerModule {} 56 | ``` 57 | 58 | Alternatively, if you have a standalone app that is initialized using the bootstrapApplication function, you can import `UniversalModule` in the following manner: 59 | 60 | ```ts 61 | const serverConfig: ApplicationConfig = { 62 | providers: [ 63 | provideServerRendering(), 64 | importProvidersFrom(UniversalModule), // <-- add this 65 | ], 66 | }; 67 | 68 | const config = mergeApplicationConfig(appConfig, serverConfig); 69 | const bootstrap = () => bootstrapApplication(AppComponent, config); 70 | ``` 71 | 72 | ## Special cases 73 | 74 | When you use plain SSR without prerender you can retrieve some of the information 75 | from requests. Use the following helpers to harvest that info: 76 | 77 | **server.ts:** 78 | 79 | ```typescript 80 | import {provideLocation, provideUserAgent} from '@ng-web-apis/universal'; 81 | // ... 82 | app.get('/**/*', (req: Request, res: Response) => { 83 | res.render('../dist/index', { 84 | req, 85 | res, 86 | providers: [provideLocation(req), provideUserAgent(req)], 87 | }); 88 | }); 89 | ``` 90 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "universal": { 7 | "projectType": "library", 8 | "root": "projects/universal", 9 | "sourceRoot": "projects/universal/src", 10 | "architect": { 11 | "build": { 12 | "builder": "@angular-devkit/build-angular:ng-packagr", 13 | "options": { 14 | "tsConfig": "projects/universal/tsconfig.lib.json", 15 | "project": "projects/universal/ng-package.json" 16 | } 17 | }, 18 | "test": { 19 | "builder": "@angular-devkit/build-angular:karma", 20 | "options": { 21 | "main": "projects/universal/src/test.ts", 22 | "tsConfig": "projects/universal/tsconfig.spec.json", 23 | "karmaConfig": "projects/universal/karma.conf.js", 24 | "codeCoverage": true, 25 | "browsers": "ChromeHeadless" 26 | } 27 | } 28 | } 29 | } 30 | }, 31 | "defaultProject": "universal" 32 | } 33 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']}; 2 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ng-web-apis/universal", 3 | "version": "2.1.0", 4 | "scripts": { 5 | "postinstall": "husky install", 6 | "build": "ng build", 7 | "postbuild": "node scripts/postbuild.js", 8 | "test": "ng test", 9 | "posttest": "node scripts/coveralls.js", 10 | "lint": "eslint . --fix", 11 | "typecheck": "tsc --noEmit --skipLibCheck", 12 | "release": "standard-version", 13 | "release:patch": "npm run release -- --release-as patch", 14 | "release:minor": "npm run release -- --release-as minor", 15 | "release:major": "npm run release -- --release-as major", 16 | "publish": "npm run build && npm publish ./dist/universal" 17 | }, 18 | "description": "A set of fallback for @ng-web-apis/common for Angular Universal", 19 | "keywords": [ 20 | "angular", 21 | "ng", 22 | "window", 23 | "api", 24 | "web api", 25 | "navigator", 26 | "user agent", 27 | "ssr", 28 | "server side rendering", 29 | "local storage" 30 | ], 31 | "license": "MIT", 32 | "author": { 33 | "name": "Alex Inkin", 34 | "email": "alexander@inkin.ru" 35 | }, 36 | "contributors": [ 37 | "Roman Sedov <79601794011@ya.ru> (http://marsibarsi.me/)" 38 | ], 39 | "repository": "https://github.com/ng-web-apis/universal", 40 | "bugs": "https://github.com/ng-web-apis/universal/issues", 41 | "homepage": "https://github.com/ng-web-apis/universal#README", 42 | "schematics": "./schematics/collection.json", 43 | "dependencies": { 44 | "@angular/common": "12.2.15", 45 | "@angular/compiler": "12.2.15", 46 | "@angular/core": "12.2.15", 47 | "@angular/platform-browser": "12.2.15", 48 | "@angular/platform-browser-dynamic": "12.2.15", 49 | "core-js": "3.20.3", 50 | "rxjs": "7.5.2", 51 | "tslib": "2.3.1", 52 | "zone.js": "0.11.4" 53 | }, 54 | "devDependencies": { 55 | "@angular-devkit/build-angular": "12.2.15", 56 | "@angular-devkit/core": "12.2.15", 57 | "@angular/cli": "12.2.15", 58 | "@angular/compiler-cli": "12.2.15", 59 | "@angular/language-service": "12.2.15", 60 | "@commitlint/cli": "^11.0.0", 61 | "@commitlint/config-conventional": "^11.0.0", 62 | "@ng-web-apis/common": "2.0.0", 63 | "@tinkoff/eslint-config": "^1.22.0", 64 | "@tinkoff/eslint-config-angular": "^1.23.0", 65 | "@tinkoff/prettier-config": "^1.22.0", 66 | "@types/jasmine": "3.10.3", 67 | "@types/jasminewd2": "2.0.10", 68 | "@types/node": "9.6.61", 69 | "coveralls": "3.1.1", 70 | "husky": "7.0.4", 71 | "jasmine-core": "4.0.0", 72 | "jasmine-spec-reporter": "7.0.0", 73 | "karma": "6.3.11", 74 | "karma-chrome-launcher": "3.1.0", 75 | "karma-coverage-istanbul-reporter": "3.0.3", 76 | "karma-jasmine": "4.0.1", 77 | "karma-jasmine-html-reporter": "1.7.0", 78 | "lint-staged": "12.2.1", 79 | "ng-packagr": "12.2.6", 80 | "prettier": "2.5.1", 81 | "standard-version": "9.3.2", 82 | "ts-node": "9.0.0", 83 | "tslint": "6.1.3", 84 | "typescript": "4.3.5" 85 | }, 86 | "husky": { 87 | "hooks": { 88 | "pre-commit": "lint-staged && npm run typecheck" 89 | } 90 | }, 91 | "lint-staged": { 92 | "*.{js,ts,html,md,less,json}": [ 93 | "prettier --write", 94 | "git add" 95 | ], 96 | "*.{js,ts}": [ 97 | "eslint --fix", 98 | "git add" 99 | ] 100 | }, 101 | "standard-version": { 102 | "scripts": { 103 | "postbump": "node scripts/syncVersions.js && git add **/package.json" 104 | } 105 | }, 106 | "engines": { 107 | "node": ">= 10", 108 | "npm": ">= 3" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | const base = require('@tinkoff/prettier-config/angular'); 2 | 3 | module.exports = { 4 | ...base, 5 | overrides: [ 6 | ...base.overrides, 7 | { 8 | files: ['*.js', '*.ts'], 9 | options: {printWidth: 90, parser: 'typescript'}, 10 | }, 11 | { 12 | files: '*.html', 13 | options: {printWidth: 80, parser: 'html'}, 14 | }, 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /projects/universal/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alexander Inkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /projects/universal/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 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-devkit/build-angular/plugins/karma'), 14 | ], 15 | client: { 16 | clearContext: false, // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage/universal'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true, 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['ChromeHeadless'], 29 | singleRun: true, 30 | customLaunchers: { 31 | ChromeHeadless: { 32 | base: 'Chrome', 33 | flags: [ 34 | '--no-sandbox', 35 | '--headless', 36 | '--disable-gpu', 37 | '--remote-debugging-port=9222', 38 | ], 39 | }, 40 | }, 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /projects/universal/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/universal", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/universal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ng-web-apis/universal", 3 | "version": "2.1.0", 4 | "peerDependencies": { 5 | "@angular/core": ">=12.0.0", 6 | "@ng-web-apis/common": ">=2.0.0", 7 | "@types/node": ">=10.0.0", 8 | "rxjs": ">=6.0.0" 9 | }, 10 | "description": "A set of fallback for @ng-web-apis/common for Angular Universal", 11 | "keywords": [ 12 | "angular", 13 | "ng", 14 | "window", 15 | "api", 16 | "web api", 17 | "navigator", 18 | "user agent", 19 | "ssr", 20 | "server side rendering", 21 | "local storage" 22 | ], 23 | "license": "MIT", 24 | "author": { 25 | "name": "Alex Inkin", 26 | "email": "alexander@inkin.ru" 27 | }, 28 | "sideEffects": [ 29 | "mocks.js" 30 | ], 31 | "contributors": [ 32 | "Roman Sedov <79601794011@ya.ru> (http://marsibarsi.me/)" 33 | ], 34 | "repository": "https://github.com/ng-web-apis/universal", 35 | "bugs": "https://github.com/ng-web-apis/universal/issues", 36 | "homepage": "https://github.com/ng-web-apis/universal#README" 37 | } -------------------------------------------------------------------------------- /projects/universal/src/classes/blob-mock.ts: -------------------------------------------------------------------------------- 1 | import {alwaysRejected} from '../utils/functions'; 2 | 3 | export class BlobMock implements Blob { 4 | size = 0; 5 | type = ''; 6 | arrayBuffer = () => alwaysRejected(); 7 | stream = () => new ReadableStream(); 8 | text = () => alwaysRejected(); 9 | slice = () => this; 10 | } 11 | -------------------------------------------------------------------------------- /projects/universal/src/classes/dom-string-list-mock.ts: -------------------------------------------------------------------------------- 1 | export class DOMStringListMock extends Array implements DOMStringList { 2 | contains(): boolean { 3 | return false; 4 | } 5 | 6 | item(): null { 7 | return null; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /projects/universal/src/classes/location-mock.ts: -------------------------------------------------------------------------------- 1 | import {emptyFunction} from '../utils/functions'; 2 | import {DOMStringListMock} from './dom-string-list-mock'; 3 | 4 | export class LocationMock implements Location { 5 | readonly ancestorOrigins = new DOMStringListMock(); 6 | hash = ''; 7 | host = ''; 8 | hostname = ''; 9 | href = ''; 10 | readonly origin = ''; 11 | pathname = ''; 12 | port = ''; 13 | protocol = ''; 14 | search = ''; 15 | 16 | assign = emptyFunction; 17 | 18 | reload = emptyFunction; 19 | 20 | replace = emptyFunction; 21 | } 22 | -------------------------------------------------------------------------------- /projects/universal/src/classes/storage-mock.ts: -------------------------------------------------------------------------------- 1 | export class StorageMock implements Storage { 2 | private readonly storage = new Map(); 3 | 4 | get length(): number { 5 | return this.storage.size; 6 | } 7 | 8 | getItem(key: string): string | null { 9 | return this.storage.has(key) ? this.storage.get(key)! : null; 10 | } 11 | 12 | setItem(key: string, value: string) { 13 | this.storage.set(key, value); 14 | } 15 | 16 | clear() { 17 | this.storage.clear(); 18 | } 19 | 20 | key(index: number): string | null { 21 | return index < this.storage.size ? [...this.storage.keys()][index] : null; 22 | } 23 | 24 | removeItem(key: string): void { 25 | this.storage.delete(key); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /projects/universal/src/constants/tests/universal-local-storage.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | import {LOCAL_STORAGE} from '@ng-web-apis/common'; 3 | import {UNIVERSAL_LOCAL_STORAGE} from '../universal-local-storage'; 4 | 5 | describe('UNIVERSAL_LOCAL_STORAGE', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [UNIVERSAL_LOCAL_STORAGE], 9 | }); 10 | }); 11 | 12 | it('Sets and retrieves item', () => { 13 | const localStorage = TestBed.get(LOCAL_STORAGE); 14 | 15 | localStorage.setItem('test', 'value'); 16 | 17 | expect(localStorage.getItem('test')).toBe('value'); 18 | expect(localStorage.length).toBe(1); 19 | expect(localStorage.key(0)).toBe('test'); 20 | }); 21 | 22 | it('Removes item', () => { 23 | const localStorage = TestBed.get(LOCAL_STORAGE); 24 | 25 | localStorage.setItem('test', 'value'); 26 | localStorage.removeItem('test'); 27 | 28 | expect(localStorage.getItem('test')).toBe(null); 29 | expect(localStorage.length).toBe(0); 30 | expect(localStorage.key(0)).toBe(null); 31 | }); 32 | 33 | it('Clears storage', () => { 34 | const localStorage = TestBed.get(LOCAL_STORAGE); 35 | 36 | localStorage.setItem('test1', 'value1'); 37 | localStorage.setItem('test2', 'value2'); 38 | 39 | expect(localStorage.length).toBe(2); 40 | 41 | localStorage.clear(); 42 | 43 | expect(localStorage.length).toBe(0); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /projects/universal/src/constants/tests/universal-navigator.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | import {NAVIGATOR} from '@ng-web-apis/common'; 3 | import {provideUserAgent} from '../../utils/provide-user-agent'; 4 | import {UNIVERSAL_NAVIGATOR} from '../universal-navigator'; 5 | 6 | describe('UNIVERSAL_NAVIGATOR', () => { 7 | const req = { 8 | headers: { 9 | 'user-agent': 'Chrome', 10 | }, 11 | }; 12 | 13 | it('Mocks the hell out of window.navigator', () => { 14 | TestBed.configureTestingModule({ 15 | providers: [UNIVERSAL_NAVIGATOR], 16 | }); 17 | 18 | const mock: Navigator = TestBed.get(NAVIGATOR); 19 | 20 | expect(mock.userAgent).toBe(''); 21 | expect(mock.plugins.refresh).not.toThrow(); 22 | expect(mock.plugins.item(0)).toBeNull(); 23 | expect(mock.plugins.namedItem('whatever')).toBeNull(); 24 | }); 25 | 26 | it('Reads provided user agent', () => { 27 | TestBed.configureTestingModule({ 28 | providers: [provideUserAgent(req), UNIVERSAL_NAVIGATOR], 29 | }); 30 | 31 | expect(TestBed.get(NAVIGATOR).userAgent).toBe('Chrome'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-animation-frame.ts: -------------------------------------------------------------------------------- 1 | import {ValueProvider} from '@angular/core'; 2 | import {ANIMATION_FRAME} from '@ng-web-apis/common'; 3 | import {NEVER} from 'rxjs'; 4 | 5 | export const UNIVERSAL_ANIMATION_FRAME: ValueProvider = { 6 | provide: ANIMATION_FRAME, 7 | useValue: NEVER, 8 | }; 9 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-caches.ts: -------------------------------------------------------------------------------- 1 | import {ValueProvider} from '@angular/core'; 2 | import {CACHES} from '@ng-web-apis/common'; 3 | import {alwaysRejected} from '../utils/functions'; 4 | 5 | export const CACHES_MOCK = { 6 | delete: () => Promise.resolve(false), 7 | has: () => Promise.resolve(false), 8 | keys: () => Promise.resolve([]), 9 | match: alwaysRejected, 10 | open: alwaysRejected, 11 | }; 12 | 13 | export const UNIVERSAL_CACHES: ValueProvider = { 14 | provide: CACHES, 15 | useValue: CACHES_MOCK, 16 | }; 17 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-crypto.ts: -------------------------------------------------------------------------------- 1 | import {ValueProvider} from '@angular/core'; 2 | import {CRYPTO} from '@ng-web-apis/common'; 3 | import {alwaysRejected, identity} from '../utils/functions'; 4 | 5 | export const CRYPTO_MOCK = { 6 | subtle: new Proxy( 7 | {}, 8 | { 9 | get: () => () => alwaysRejected, 10 | }, 11 | ), 12 | getRandomValues: identity, 13 | }; 14 | 15 | export const UNIVERSAL_CRYPTO: ValueProvider = { 16 | provide: CRYPTO, 17 | useValue: CRYPTO_MOCK, 18 | }; 19 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-history.ts: -------------------------------------------------------------------------------- 1 | import {ValueProvider} from '@angular/core'; 2 | import {HISTORY} from '@ng-web-apis/common'; 3 | import {emptyFunction} from '../utils/functions'; 4 | 5 | export const HISTORY_MOCK = { 6 | length: 0, 7 | scrollRestoration: 'auto', 8 | state: {}, 9 | back: emptyFunction, 10 | forward: emptyFunction, 11 | go: emptyFunction, 12 | pushState: emptyFunction, 13 | replaceState: emptyFunction, 14 | }; 15 | 16 | export const UNIVERSAL_HISTORY: ValueProvider = { 17 | provide: HISTORY, 18 | useValue: HISTORY_MOCK, 19 | }; 20 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-local-storage.ts: -------------------------------------------------------------------------------- 1 | import {ClassProvider} from '@angular/core'; 2 | import {LOCAL_STORAGE} from '@ng-web-apis/common'; 3 | import {StorageMock} from '../classes/storage-mock'; 4 | 5 | export const UNIVERSAL_LOCAL_STORAGE: ClassProvider = { 6 | provide: LOCAL_STORAGE, 7 | useClass: StorageMock, 8 | }; 9 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-location.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider, Optional} from '@angular/core'; 2 | import {LOCATION} from '@ng-web-apis/common'; 3 | 4 | import {LocationMock} from '../classes/location-mock'; 5 | import {SSR_LOCATION} from '../tokens/ssr-location'; 6 | 7 | export const UNIVERSAL_LOCATION: FactoryProvider = { 8 | provide: LOCATION, 9 | deps: [[new Optional(), SSR_LOCATION]], 10 | useFactory: (location: Location | null) => location || new LocationMock(), 11 | }; 12 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-media-devices.ts: -------------------------------------------------------------------------------- 1 | import {ValueProvider} from '@angular/core'; 2 | import {MEDIA_DEVICES} from '@ng-web-apis/common'; 3 | import {NAVIGATOR_MOCK} from './universal-navigator'; 4 | 5 | export const UNIVERSAL_MEDIA_DEVICES: ValueProvider = { 6 | provide: MEDIA_DEVICES, 7 | useValue: NAVIGATOR_MOCK.mediaDevices, 8 | }; 9 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-navigator.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider, Optional} from '@angular/core'; 2 | import {NAVIGATOR} from '@ng-web-apis/common'; 3 | import {SSR_USER_AGENT} from '../tokens/ssr-user-agent'; 4 | import {EVENT_TARGET} from '../utils/event-target'; 5 | import { 6 | alwaysFalse, 7 | alwaysRejected, 8 | alwaysZero, 9 | emptyArray, 10 | emptyFunction, 11 | emptyObject, 12 | } from '../utils/functions'; 13 | 14 | function getArray() { 15 | return new (class extends Array { 16 | item = () => null; 17 | namedItem = () => null; 18 | refresh() {} 19 | })(); 20 | } 21 | 22 | /** For older version of TS and Angular that do not support all properties from Navigator */ 23 | interface NavigatorLike extends Navigator { 24 | [key: string]: any; 25 | } 26 | 27 | export const NAVIGATOR_MOCK: NavigatorLike = { 28 | appCodeName: '', 29 | appName: '', 30 | appVersion: '', 31 | platform: '', 32 | product: '', 33 | productSub: '', 34 | userAgent: '', 35 | vendor: '', 36 | vendorSub: '', 37 | 38 | onLine: false, 39 | 40 | confirmSiteSpecificTrackingException: alwaysFalse, 41 | confirmWebWideTrackingException: alwaysFalse, 42 | share: alwaysRejected, 43 | registerProtocolHandler: emptyFunction, 44 | unregisterProtocolHandler: emptyFunction, 45 | removeSiteSpecificTrackingException: emptyFunction, 46 | removeWebWideTrackingException: emptyFunction, 47 | storeSiteSpecificTrackingException: emptyFunction, 48 | storeWebWideTrackingException: emptyFunction, 49 | 50 | credentials: { 51 | create: alwaysRejected, 52 | get: alwaysRejected, 53 | preventSilentAccess: alwaysRejected, 54 | store: alwaysRejected, 55 | }, 56 | 57 | msSaveBlob: alwaysFalse, 58 | msSaveOrOpenBlob: alwaysFalse, 59 | 60 | sendBeacon: alwaysFalse, 61 | 62 | hardwareConcurrency: 0, 63 | 64 | getDisplayMedia: alwaysRejected, 65 | 66 | language: '', 67 | languages: [], 68 | 69 | storage: { 70 | estimate: alwaysRejected, 71 | persist: alwaysRejected, 72 | persisted: alwaysRejected, 73 | }, 74 | 75 | activeVRDisplays: [], 76 | authentication: { 77 | getAssertion: alwaysRejected, 78 | makeCredential: alwaysRejected, 79 | }, 80 | clipboard: { 81 | ...EVENT_TARGET, 82 | readText: alwaysRejected, 83 | writeText: alwaysRejected, 84 | }, 85 | cookieEnabled: false, 86 | doNotTrack: null, 87 | gamepadInputEmulation: 'keyboard', 88 | geolocation: { 89 | clearWatch: emptyFunction, 90 | getCurrentPosition: emptyFunction, 91 | watchPosition: alwaysZero, 92 | }, 93 | maxTouchPoints: 0, 94 | mediaDevices: { 95 | ...EVENT_TARGET, 96 | ondevicechange: null, 97 | enumerateDevices: alwaysRejected, 98 | getSupportedConstraints: emptyObject, 99 | getUserMedia: alwaysRejected, 100 | }, 101 | mimeTypes: getArray(), 102 | msManipulationViewsEnabled: false, 103 | msMaxTouchPoints: 0, 104 | msPointerEnabled: false, 105 | permissions: { 106 | query: alwaysRejected, 107 | }, 108 | plugins: getArray(), 109 | pointerEnabled: false, 110 | serviceWorker: { 111 | ...EVENT_TARGET, 112 | controller: null, 113 | oncontrollerchange: null, 114 | onmessage: null, 115 | onmessageerror: null, 116 | ready: alwaysRejected(), 117 | getRegistration: alwaysRejected, 118 | getRegistrations: alwaysRejected, 119 | register: alwaysRejected, 120 | startMessages: emptyFunction, 121 | }, 122 | webdriver: false, 123 | getGamepads: emptyArray, 124 | getUserMedia: emptyFunction, 125 | getVRDisplays: alwaysRejected, 126 | javaEnabled: alwaysFalse, 127 | msLaunchUri: emptyFunction, 128 | requestMediaKeySystemAccess: alwaysRejected, 129 | vibrate: alwaysFalse, 130 | }; 131 | 132 | export function navigatorFactory(userAgent: string | null): NavigatorLike { 133 | return { 134 | ...NAVIGATOR_MOCK, 135 | userAgent: userAgent || '', 136 | }; 137 | } 138 | 139 | export const UNIVERSAL_NAVIGATOR: FactoryProvider = { 140 | provide: NAVIGATOR, 141 | deps: [[new Optional(), SSR_USER_AGENT]], 142 | useFactory: navigatorFactory, 143 | }; 144 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-performance.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider} from '@angular/core'; 2 | import {PERFORMANCE} from '@ng-web-apis/common'; 3 | 4 | export function performanceFactory(): Performance { 5 | return require('perf_hooks').performance; 6 | } 7 | 8 | export const UNIVERSAL_PERFORMANCE: FactoryProvider = { 9 | provide: PERFORMANCE, 10 | deps: [], 11 | useFactory: performanceFactory, 12 | }; 13 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-providers.ts: -------------------------------------------------------------------------------- 1 | import {Provider} from '@angular/core'; 2 | import {UNIVERSAL_ANIMATION_FRAME} from './universal-animation-frame'; 3 | import {UNIVERSAL_CACHES} from './universal-caches'; 4 | import {UNIVERSAL_CRYPTO} from './universal-crypto'; 5 | import {UNIVERSAL_HISTORY} from './universal-history'; 6 | import {UNIVERSAL_LOCAL_STORAGE} from './universal-local-storage'; 7 | import {UNIVERSAL_LOCATION} from './universal-location'; 8 | import {UNIVERSAL_MEDIA_DEVICES} from './universal-media-devices'; 9 | import {UNIVERSAL_NAVIGATOR} from './universal-navigator'; 10 | import {UNIVERSAL_PERFORMANCE} from './universal-performance'; 11 | import {UNIVERSAL_SESSION_STORAGE} from './universal-session-storage'; 12 | import {UNIVERSAL_SPEECH_SYNTHESIS} from './universal-speech-synthesis'; 13 | import {UNIVERSAL_USER_AGENT} from './universal-user-agent'; 14 | import {UNIVERSAL_WINDOW} from './universal-window'; 15 | 16 | export const UNIVERSAL_PROVIDERS: Provider[] = [ 17 | UNIVERSAL_ANIMATION_FRAME, 18 | UNIVERSAL_CACHES, 19 | UNIVERSAL_CRYPTO, 20 | UNIVERSAL_HISTORY, 21 | UNIVERSAL_LOCAL_STORAGE, 22 | UNIVERSAL_SESSION_STORAGE, 23 | UNIVERSAL_LOCATION, 24 | UNIVERSAL_MEDIA_DEVICES, 25 | UNIVERSAL_NAVIGATOR, 26 | UNIVERSAL_PERFORMANCE, 27 | UNIVERSAL_SPEECH_SYNTHESIS, 28 | UNIVERSAL_USER_AGENT, 29 | UNIVERSAL_WINDOW, 30 | ]; 31 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-session-storage.ts: -------------------------------------------------------------------------------- 1 | import {ClassProvider} from '@angular/core'; 2 | import {SESSION_STORAGE} from '@ng-web-apis/common'; 3 | import {StorageMock} from '../classes/storage-mock'; 4 | 5 | export const UNIVERSAL_SESSION_STORAGE: ClassProvider = { 6 | provide: SESSION_STORAGE, 7 | useClass: StorageMock, 8 | }; 9 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-speech-synthesis.ts: -------------------------------------------------------------------------------- 1 | import {ValueProvider} from '@angular/core'; 2 | import {SPEECH_SYNTHESIS} from '@ng-web-apis/common'; 3 | import {alwaysFalse, emptyArray, emptyFunction} from '../utils/functions'; 4 | 5 | export const SPEECH_SYNTHESIS_MOCK: SpeechSynthesis = { 6 | paused: false, 7 | pending: false, 8 | speaking: false, 9 | onvoiceschanged: emptyFunction, 10 | addEventListener: emptyFunction, 11 | removeEventListener: emptyFunction, 12 | dispatchEvent: alwaysFalse, 13 | cancel: emptyFunction, 14 | pause: emptyFunction, 15 | resume: emptyFunction, 16 | speak: emptyFunction, 17 | getVoices: emptyArray, 18 | }; 19 | 20 | export const UNIVERSAL_SPEECH_SYNTHESIS: ValueProvider = { 21 | provide: SPEECH_SYNTHESIS, 22 | useValue: SPEECH_SYNTHESIS_MOCK, 23 | }; 24 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-user-agent.ts: -------------------------------------------------------------------------------- 1 | import {FactoryProvider, Optional} from '@angular/core'; 2 | import {USER_AGENT} from '@ng-web-apis/common'; 3 | 4 | import {SSR_USER_AGENT} from '../tokens/ssr-user-agent'; 5 | 6 | export const UNIVERSAL_USER_AGENT: FactoryProvider = { 7 | provide: USER_AGENT, 8 | deps: [[new Optional(), SSR_USER_AGENT]], 9 | useFactory: (userAgent: string | null) => userAgent || '', 10 | }; 11 | -------------------------------------------------------------------------------- /projects/universal/src/constants/universal-window.ts: -------------------------------------------------------------------------------- 1 | import {DOCUMENT} from '@angular/common'; 2 | import {FactoryProvider, Optional} from '@angular/core'; 3 | import {WINDOW} from '@ng-web-apis/common'; 4 | 5 | import {BlobMock} from '../classes/blob-mock'; 6 | import {LocationMock} from '../classes/location-mock'; 7 | import {StorageMock} from '../classes/storage-mock'; 8 | import {SSR_LOCATION} from '../tokens/ssr-location'; 9 | import {SSR_USER_AGENT} from '../tokens/ssr-user-agent'; 10 | import {EVENT_TARGET} from '../utils/event-target'; 11 | import { 12 | alwaysFalse, 13 | alwaysNull, 14 | alwaysRejected, 15 | alwaysZero, 16 | emptyFunction, 17 | identity, 18 | } from '../utils/functions'; 19 | import {CACHES_MOCK} from './universal-caches'; 20 | import {CRYPTO_MOCK} from './universal-crypto'; 21 | import {NAVIGATOR_MOCK} from './universal-navigator'; 22 | import {performanceFactory} from './universal-performance'; 23 | import {SPEECH_SYNTHESIS_MOCK} from './universal-speech-synthesis'; 24 | 25 | const COMPUTED_STYLES: Partial = { 26 | getPropertyPriority: () => '', 27 | getPropertyValue: () => '', 28 | item: () => '', 29 | removeProperty: () => '', 30 | setProperty: emptyFunction, 31 | }; 32 | const COMPUTED_STYLES_HANDLER: ProxyHandler = { 33 | get: (obj, key: any) => (key in obj ? obj[key] : null), 34 | }; 35 | const COMPUTED_STYLES_PROXY = new Proxy( 36 | COMPUTED_STYLES as any, 37 | COMPUTED_STYLES_HANDLER, 38 | ); 39 | const CSS_RULES = new (class extends Array implements CSSRuleList { 40 | item = () => null; 41 | })(); 42 | const BAR_PROP: BarProp = { 43 | visible: false, 44 | }; 45 | const DB_REQUEST: IDBOpenDBRequest = { 46 | ...EVENT_TARGET, 47 | onblocked: null, 48 | onerror: null, 49 | onsuccess: null, 50 | onupgradeneeded: null, 51 | error: null, 52 | readyState: 'pending', 53 | result: null as any, // Cannot be accessed for 'pending' state anyway 54 | source: null as any, // null for open requests 55 | transaction: null, 56 | }; 57 | const SELF = ['frames', 'parent', 'self', 'top', 'window']; 58 | const WINDOW_HANDLER: ProxyHandler = { 59 | get: (windowRef, key: string) => { 60 | if (SELF.includes(key)) { 61 | return windowRef; 62 | } 63 | 64 | return key.startsWith('on') ? null : windowRef[key as keyof Window]; 65 | }, 66 | }; 67 | 68 | export function windowFactory( 69 | document: Document, 70 | location: Location | null, 71 | userAgent: string | null, 72 | ): Window { 73 | const windowMock: Window = { 74 | ...EVENT_TARGET, 75 | document, 76 | localStorage: new StorageMock(), 77 | location: location || new LocationMock(), 78 | navigator: {...NAVIGATOR_MOCK, userAgent: userAgent || ''}, 79 | performance: performanceFactory(), 80 | sessionStorage: new StorageMock(), 81 | speechSynthesis: SPEECH_SYNTHESIS_MOCK, 82 | caches: CACHES_MOCK, 83 | crypto: CRYPTO_MOCK, 84 | URL, 85 | URLSearchParams, 86 | setTimeout, 87 | setInterval, 88 | clearTimeout, 89 | clearInterval, 90 | console, 91 | Blob: BlobMock, 92 | alert: emptyFunction, 93 | clientInformation: {...NAVIGATOR_MOCK, userAgent: userAgent || ''}, 94 | // TODO: Candidate for token 95 | matchMedia: () => ({ 96 | ...EVENT_TARGET, 97 | matches: false, 98 | media: '', 99 | onchange: null, 100 | addListener: emptyFunction, 101 | removeListener: emptyFunction, 102 | }), 103 | // TODO: Candidate for token 104 | indexedDB: { 105 | cmp: alwaysZero, 106 | open: () => DB_REQUEST, 107 | deleteDatabase: () => DB_REQUEST, 108 | }, 109 | customElements: { 110 | define: emptyFunction, 111 | get: emptyFunction, 112 | upgrade: emptyFunction, 113 | whenDefined: alwaysRejected, 114 | }, 115 | styleMedia: { 116 | type: '', 117 | matchMedium: alwaysFalse, 118 | }, 119 | history: { 120 | length: 0, 121 | scrollRestoration: 'auto', 122 | state: {}, 123 | back: emptyFunction, 124 | forward: emptyFunction, 125 | go: emptyFunction, 126 | pushState: emptyFunction, 127 | replaceState: emptyFunction, 128 | }, 129 | closed: false, 130 | defaultStatus: '', 131 | devicePixelRatio: 1, 132 | doNotTrack: '', 133 | frameElement: null as any, // TODO: bug in TypeScript 134 | innerHeight: 0, 135 | innerWidth: 0, 136 | isSecureContext: false, 137 | length: 0, 138 | name: '', 139 | offscreenBuffering: false, 140 | opener: {}, 141 | origin: '', 142 | orientation: '', 143 | outerHeight: 0, 144 | outerWidth: 0, 145 | pageXOffset: 0, 146 | pageYOffset: 0, 147 | screenLeft: 0, 148 | screenTop: 0, 149 | screenX: 0, 150 | screenY: 0, 151 | scrollX: 0, 152 | scrollY: 0, 153 | status: '', 154 | blur: emptyFunction, 155 | cancelAnimationFrame: emptyFunction, 156 | captureEvents: emptyFunction, 157 | close: emptyFunction, 158 | confirm: alwaysFalse, 159 | departFocus: emptyFunction, 160 | focus: emptyFunction, 161 | moveBy: emptyFunction, 162 | moveTo: emptyFunction, 163 | open: alwaysNull, 164 | postMessage: emptyFunction, 165 | print: emptyFunction, 166 | prompt: alwaysNull, 167 | releaseEvents: emptyFunction, 168 | requestAnimationFrame: alwaysZero, 169 | resizeBy: emptyFunction, 170 | resizeTo: emptyFunction, 171 | scroll: emptyFunction, 172 | scrollBy: emptyFunction, 173 | scrollTo: emptyFunction, 174 | stop: emptyFunction, 175 | atob: identity, 176 | btoa: identity, 177 | fetch: alwaysRejected, 178 | createImageBitmap: alwaysRejected, 179 | queueMicrotask: emptyFunction, 180 | locationbar: BAR_PROP, 181 | menubar: BAR_PROP, 182 | personalbar: BAR_PROP, 183 | scrollbars: BAR_PROP, 184 | statusbar: BAR_PROP, 185 | toolbar: BAR_PROP, 186 | getComputedStyle: () => COMPUTED_STYLES_PROXY, 187 | getMatchedCSSRules: () => CSS_RULES, 188 | getSelection: () => null as any, // TODO: old TypeScript issue 189 | } as any; 190 | 191 | return new Proxy(windowMock, WINDOW_HANDLER); 192 | } 193 | 194 | export const UNIVERSAL_WINDOW: FactoryProvider = { 195 | provide: WINDOW, 196 | deps: [DOCUMENT, [new Optional(), SSR_LOCATION], [new Optional(), SSR_USER_AGENT]], 197 | useFactory: windowFactory, 198 | }; 199 | -------------------------------------------------------------------------------- /projects/universal/src/mocks.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | // Mutation Observer API 5 | global.MutationObserver = class { 6 | observe() {} 7 | disconnect() {} 8 | takeRecords() { 9 | return []; 10 | } 11 | }; 12 | 13 | // Intersection Observer API 14 | global.IntersectionObserver = class { 15 | observe() {} 16 | unobserve() {} 17 | disconnect() {} 18 | takeRecords() { 19 | return []; 20 | } 21 | }; 22 | 23 | // Web Audio API 24 | global.AudioContext = class {}; 25 | global.OfflineAudioContext = class {}; 26 | global.AudioBufferSourceNode = class {}; 27 | global.ConstantSourceNode = class {}; 28 | global.MediaElementAudioSourceNode = class {}; 29 | global.MediaStreamAudioSourceNode = class {}; 30 | global.MediaStreamTrackAudioSourceNode = class {}; 31 | global.OscillatorNode = class {}; 32 | global.MediaStreamAudioDestinationNode = class {}; 33 | global.AnalyserNode = class {}; 34 | global.BiquadFilterNode = class {}; 35 | global.ChannelMergerNode = class {}; 36 | global.ChannelSplitterNode = class {}; 37 | global.ConvolverNode = class {}; 38 | global.DelayNode = class {}; 39 | global.DynamicsCompressorNode = class {}; 40 | global.GainNode = class {}; 41 | global.IIRFilterNode = class {}; 42 | global.PannerNode = class {}; 43 | global.ScriptProcessorNode = class {}; 44 | global.StereoPannerNode = class {}; 45 | global.WaveShaperNode = class {}; 46 | global.AudioWorkletNode = class {}; 47 | })(); 48 | -------------------------------------------------------------------------------- /projects/universal/src/module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {UNIVERSAL_PROVIDERS} from './constants/universal-providers'; 3 | 4 | @NgModule({ 5 | providers: UNIVERSAL_PROVIDERS, 6 | }) 7 | export class UniversalModule {} 8 | -------------------------------------------------------------------------------- /projects/universal/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Public API Surface of @ng-web-apis/universal 3 | */ 4 | // Constants 5 | export * from './constants/universal-animation-frame'; 6 | export * from './constants/universal-caches'; 7 | export * from './constants/universal-crypto'; 8 | export * from './constants/universal-history'; 9 | export * from './constants/universal-local-storage'; 10 | export * from './constants/universal-location'; 11 | export * from './constants/universal-media-devices'; 12 | export * from './constants/universal-navigator'; 13 | export * from './constants/universal-performance'; 14 | export * from './constants/universal-session-storage'; 15 | export * from './constants/universal-speech-synthesis'; 16 | export * from './constants/universal-user-agent'; 17 | export * from './constants/universal-window'; 18 | 19 | export * from './constants/universal-providers'; 20 | 21 | // Utils 22 | export * from './utils/provide-location'; 23 | export * from './utils/provide-user-agent'; 24 | 25 | // Module 26 | export * from './module'; 27 | -------------------------------------------------------------------------------- /projects/universal/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | import 'zone.js/dist/zone'; 3 | import 'zone.js/dist/zone-testing'; 4 | 5 | import {getTestBed} from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting, 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | declare const require: any; 12 | 13 | // First, initialize the Angular testing environment. 14 | getTestBed().initTestEnvironment( 15 | BrowserDynamicTestingModule, 16 | platformBrowserDynamicTesting(), 17 | ); 18 | 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | 22 | // And load the modules. 23 | context.keys().map(context); 24 | -------------------------------------------------------------------------------- /projects/universal/src/tokens/ssr-location.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | 3 | export const SSR_LOCATION = new InjectionToken( 4 | 'Location object passed from server side', 5 | ); 6 | -------------------------------------------------------------------------------- /projects/universal/src/tokens/ssr-user-agent.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | 3 | export const SSR_USER_AGENT = new InjectionToken( 4 | 'User Agent string passed from server side', 5 | ); 6 | -------------------------------------------------------------------------------- /projects/universal/src/utils/event-target.ts: -------------------------------------------------------------------------------- 1 | import {alwaysFalse, emptyFunction} from './functions'; 2 | 3 | export const EVENT_TARGET: EventTarget = { 4 | addEventListener: emptyFunction, 5 | dispatchEvent: alwaysFalse, 6 | removeEventListener: emptyFunction, 7 | }; 8 | -------------------------------------------------------------------------------- /projects/universal/src/utils/functions.ts: -------------------------------------------------------------------------------- 1 | export function identity(v: T): T { 2 | return v; 3 | } 4 | 5 | export function emptyFunction() {} 6 | 7 | export function emptyArray(): any[] { 8 | return []; 9 | } 10 | 11 | export function emptyObject(): object { 12 | return {}; 13 | } 14 | 15 | export function alwaysFalse(): boolean { 16 | return false; 17 | } 18 | 19 | export function alwaysNull(): null { 20 | return null; 21 | } 22 | 23 | export function alwaysZero(): number { 24 | return 0; 25 | } 26 | 27 | export function alwaysRejected(): Promise { 28 | return Promise.reject().catch(emptyFunction); 29 | } 30 | -------------------------------------------------------------------------------- /projects/universal/src/utils/provide-location.ts: -------------------------------------------------------------------------------- 1 | import {ValueProvider} from '@angular/core'; 2 | import {IncomingMessage} from 'http'; 3 | 4 | import {DOMStringListMock} from '../classes/dom-string-list-mock'; 5 | import {SSR_LOCATION} from '../tokens/ssr-location'; 6 | import {emptyFunction} from './functions'; 7 | 8 | export function provideLocation(req: IncomingMessage): ValueProvider { 9 | const protocol = 'encrypted' in req.socket ? 'https' : 'http'; 10 | const url: any = new URL(`${protocol}://${req.headers['host']}${req.url}`); 11 | 12 | url.assign = emptyFunction; 13 | url.reload = emptyFunction; 14 | url.replace = emptyFunction; 15 | url.ancestorOrigins = new DOMStringListMock(); 16 | 17 | return { 18 | provide: SSR_LOCATION, 19 | useValue: url, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /projects/universal/src/utils/provide-user-agent.ts: -------------------------------------------------------------------------------- 1 | import {ValueProvider} from '@angular/core'; 2 | import {IncomingHttpHeaders} from 'http'; 3 | import {SSR_USER_AGENT} from '../tokens/ssr-user-agent'; 4 | 5 | export function provideUserAgent(req: {headers: IncomingHttpHeaders}): ValueProvider { 6 | return { 7 | provide: SSR_USER_AGENT, 8 | useValue: req.headers['user-agent'], 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /projects/universal/src/utils/tests/functions.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | alwaysFalse, 3 | alwaysNull, 4 | alwaysRejected, 5 | alwaysZero, 6 | emptyArray, 7 | emptyFunction, 8 | emptyObject, 9 | identity, 10 | } from '../functions'; 11 | 12 | describe('Functions', () => { 13 | it('identity returns the same item', () => { 14 | const item = {}; 15 | 16 | expect(identity(item)).toBe(item); 17 | }); 18 | 19 | it('emptyFunction returns nothing', () => { 20 | expect(emptyFunction()).toBeUndefined(); 21 | }); 22 | 23 | it('emptyArray returns empty array', () => { 24 | expect(emptyArray()).toEqual([]); 25 | }); 26 | 27 | it('emptyObject returns empty object', () => { 28 | expect(emptyObject()).toEqual({}); 29 | }); 30 | 31 | it('alwaysFalse returns false', () => { 32 | expect(alwaysFalse()).toBe(false); 33 | }); 34 | 35 | it('alwaysNull returns null', () => { 36 | expect(alwaysNull()).toBeNull(); 37 | }); 38 | 39 | it('alwaysZero returns 0', () => { 40 | expect(alwaysZero()).toBe(0); 41 | }); 42 | 43 | it('alwaysRejected returns a rejected Promise', () => { 44 | expect(alwaysRejected() instanceof Promise).toBe(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /projects/universal/src/utils/tests/provide-location.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | import {IncomingMessage} from 'http'; 3 | import {SSR_LOCATION} from '../../tokens/ssr-location'; 4 | import {provideLocation} from '../provide-location'; 5 | 6 | describe('provideLocation', () => { 7 | it('parses request', () => { 8 | const req: any = { 9 | url: '/hapica', 10 | socket: { 11 | encrypted: true, 12 | }, 13 | headers: { 14 | host: 'localhost:8080', 15 | }, 16 | }; 17 | 18 | TestBed.configureTestingModule({ 19 | providers: [provideLocation(req as IncomingMessage)], 20 | }); 21 | 22 | expect(String(TestBed.get(SSR_LOCATION))).toBe('https://localhost:8080/hapica'); 23 | }); 24 | 25 | it('has no items in ancestorOrigins', () => { 26 | const req: any = { 27 | url: '/hapica', 28 | socket: {}, 29 | headers: { 30 | host: 'localhost:8080', 31 | }, 32 | }; 33 | 34 | TestBed.configureTestingModule({ 35 | providers: [provideLocation(req as IncomingMessage)], 36 | }); 37 | 38 | expect(String(TestBed.get(SSR_LOCATION))).toBe('http://localhost:8080/hapica'); 39 | expect(TestBed.get(SSR_LOCATION).ancestorOrigins.contains()).toBe(false); 40 | expect(TestBed.get(SSR_LOCATION).ancestorOrigins.item()).toBeNull(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /projects/universal/src/utils/tests/provide-user-agent.spec.ts: -------------------------------------------------------------------------------- 1 | import {TestBed} from '@angular/core/testing'; 2 | import {SSR_USER_AGENT} from '../../tokens/ssr-user-agent'; 3 | import {provideUserAgent} from '../provide-user-agent'; 4 | 5 | describe('provideUserAgent', () => { 6 | const req = { 7 | headers: { 8 | 'user-agent': 'Chrome', 9 | }, 10 | }; 11 | 12 | it('parses request', () => { 13 | TestBed.configureTestingModule({ 14 | providers: [provideUserAgent(req)], 15 | }); 16 | 17 | expect(String(TestBed.get(SSR_USER_AGENT))).toBe('Chrome'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /projects/universal/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": ["node"], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "annotateForClosureCompiler": true, 13 | "skipTemplateCodegen": true, 14 | "strictMetadataEmit": true, 15 | "fullTemplateTypeCheck": true, 16 | "strictInjectionParameters": true, 17 | "enableResourceInlining": true 18 | }, 19 | "exclude": ["src/test.ts", "**/*.spec.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /projects/universal/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /scripts/coveralls.js: -------------------------------------------------------------------------------- 1 | const {execSync} = require('child_process'); 2 | const {readdirSync} = require('fs'); 3 | const {resolve} = require('path'); 4 | 5 | coveralls(); 6 | 7 | function coveralls() { 8 | const coverageFolder = resolve(__dirname, '..', 'coverage'); 9 | 10 | readdirSync(coverageFolder, {withFileTypes: true}) 11 | .filter(dirent => dirent.isDirectory()) 12 | .map(dirent => dirent.name) 13 | .forEach(folder => { 14 | execSync(`cat coverage/${folder}/lcov.info | coveralls`); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /scripts/postbuild.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const DIST_LIB_PATH = 'dist/universal/'; 4 | const README_PATH = 'README.md'; 5 | const MOCKS = 'mocks.js'; 6 | const MOCKS_PATH = 'projects/universal/src/' + MOCKS; 7 | const PATH_TO_MOCKS = DIST_LIB_PATH + MOCKS; 8 | const PATH_TO_README = DIST_LIB_PATH + README_PATH; 9 | 10 | copyExtraFiles(); 11 | 12 | function copyExtraFiles() { 13 | if (!fs.existsSync(README_PATH || !fs.existsSync(MOCKS_PATH))) { 14 | throw new Error('Requested files do not exit'); 15 | } else { 16 | copyReadmeIntoDistFolder(README_PATH, PATH_TO_README); 17 | fs.copyFileSync(MOCKS_PATH, PATH_TO_MOCKS); 18 | } 19 | } 20 | 21 | function copyReadmeIntoDistFolder(srcPath, toPath) { 22 | const fileBody = fs.readFileSync(srcPath).toString(); 23 | const withoutLogos = fileBody 24 | .replace('# ![logo](logo.svg) ', '') 25 | .replace(' ', ''); 26 | 27 | fs.writeFileSync(toPath, withoutLogos); 28 | } 29 | -------------------------------------------------------------------------------- /scripts/syncVersions.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const glob = require('glob'); 3 | const JSON_INDENTATION_LEVEL = 4; 4 | const {version} = require('../package.json'); 5 | 6 | // Sync libraries package.json versions with main package.json 7 | syncVersions('projects'); 8 | 9 | function syncVersions(root) { 10 | glob(root + '/**/package.json', (_, files) => { 11 | files.forEach(file => { 12 | const packageJson = JSON.parse(fs.readFileSync(file)); 13 | 14 | fs.writeFileSync( 15 | file, 16 | JSON.stringify( 17 | { 18 | ...packageJson, 19 | version, 20 | }, 21 | null, 22 | JSON_INDENTATION_LEVEL, 23 | ), 24 | ); 25 | }); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "baseUrl": "." 6 | }, 7 | "include": ["projects", "scripts", "**/*.js"], 8 | "exclude": ["**/node_modules", "**/.*/"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "angularCompilerOptions": { 4 | "enableIvy": true, 5 | "compilationMode": "partial" 6 | }, 7 | "compilerOptions": { 8 | "baseUrl": "./", 9 | "outDir": "./dist/out-tsc", 10 | "sourceMap": true, 11 | "declaration": false, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "emitDecoratorMetadata": true, 15 | "experimentalDecorators": true, 16 | "importHelpers": true, 17 | "strict": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "noImplicitReturns": true, 20 | "noUnusedParameters": true, 21 | "noUnusedLocals": true, 22 | "target": "es2015", 23 | "typeRoots": ["node_modules/@types"], 24 | "lib": ["es2018", "dom"] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /web-api.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | background 6 | 7 | 8 | 9 | 10 | 11 | 12 | Layer 1 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------