├── .nvmrc ├── packages └── one-service-worker-demo │ ├── .gitignore │ ├── .eslintignore │ ├── .npmrc │ ├── .eslintrc │ ├── static │ ├── audio │ │ └── gong.m4a │ ├── images │ │ └── one.png │ ├── manifest.webmanifest │ └── index.html │ ├── src │ ├── client │ │ ├── react.js │ │ ├── prop-types.js │ │ ├── react-dom.js │ │ ├── config.js │ │ ├── index.js │ │ └── sw.js │ ├── server │ │ ├── index.js │ │ └── server.js │ ├── components │ │ ├── index.jsx │ │ ├── views │ │ │ ├── Metrics.jsx │ │ │ ├── Offline.jsx │ │ │ ├── Performance.jsx │ │ │ ├── Sync.jsx │ │ │ ├── Permissions.jsx │ │ │ ├── Standalone.jsx │ │ │ ├── StorageQuota.jsx │ │ │ └── Connection.jsx │ │ └── Client.jsx │ ├── hooks │ │ ├── usePerformance.js │ │ ├── useStorageQuota.js │ │ ├── useInterval.js │ │ ├── index.js │ │ ├── useSync.js │ │ ├── useOffline.js │ │ ├── useNotificationComposer.js │ │ ├── useStorage.js │ │ ├── useServiceWorker.js │ │ ├── ducks │ │ │ └── permissions.js │ │ ├── usePermissions.js │ │ ├── useStandalone.js │ │ └── useWebPush.js │ └── middleware │ │ └── index.js │ ├── README.md │ └── package.json ├── .github ├── labeler.yml ├── workflows │ ├── pr-labeler.yml │ ├── stale.yml │ ├── release.yml │ └── main.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── pull_request_template.md ├── .npmrc ├── one-service-worker.png ├── .eslintignore ├── .gitignore ├── CODEOWNERS ├── .prettierrc ├── .editorconfig ├── __tests__ ├── utility │ ├── errors │ │ ├── __snapshots__ │ │ │ └── index.spec.js.snap │ │ ├── index.spec.js │ │ └── errors.spec.js │ ├── events │ │ ├── __snapshots__ │ │ │ └── index.spec.js.snap │ │ └── index.spec.js │ ├── runtime │ │ ├── __snapshots__ │ │ │ └── index.spec.js.snap │ │ ├── index.spec.js │ │ └── config.spec.js │ ├── index.spec.js │ ├── __snapshots__ │ │ └── index.spec.js.snap │ └── validation │ │ ├── options.spec.js │ │ └── validation.spec.js ├── core │ ├── __snapshots__ │ │ ├── subscription.spec.js.snap │ │ ├── index.spec.js.snap │ │ └── service-worker.spec.js.snap │ ├── index.spec.js │ └── subscription.spec.js ├── middleware │ ├── __snapshots__ │ │ └── index.spec.js.snap │ ├── index.spec.js │ ├── manifest.spec.js │ └── worker.spec.js ├── integration │ ├── jest.setup.js │ ├── helpers │ │ ├── utility.js │ │ ├── constants.js │ │ └── platform.js │ └── lifecycle.spec.js ├── cache │ ├── __snapshots__ │ │ └── index.spec.js.snap │ └── index.spec.js ├── index.spec.js ├── helpers.js ├── jest.setup.js └── __snapshots__ │ └── index.spec.js.snap ├── docs ├── api │ ├── README.md │ ├── Validation.md │ └── Errors.md ├── guides │ ├── README.md │ ├── WithReact.md │ ├── Patterns.md │ └── Cache.md ├── Demo.md └── Development.md ├── babel.config.js ├── .eslintrc.json ├── commitlint.config.js ├── src ├── utility │ ├── errors │ │ ├── index.js │ │ ├── __mocks__ │ │ │ └── errors.js │ │ └── errors.js │ ├── events │ │ ├── index.js │ │ ├── __mocks__ │ │ │ └── events.js │ │ ├── middleware.js │ │ └── events.js │ ├── validation │ │ ├── index.js │ │ ├── __mocks__ │ │ │ ├── options.js │ │ │ └── validation.js │ │ ├── options.js │ │ └── validation.js │ ├── runtime │ │ ├── config.js │ │ ├── index.js │ │ └── environment.js │ └── index.js ├── core │ ├── __mocks__ │ │ ├── subscription.js │ │ └── service-worker.js │ ├── index.js │ └── subscription.js ├── middleware │ ├── utility.js │ ├── messenger.js │ ├── navigationPreload.js │ ├── shell.js │ ├── index.js │ ├── manifest.js │ ├── lifecycle.js │ ├── expiration.js │ └── caching.js ├── cache │ ├── __mocks__ │ │ ├── meta-data.js │ │ └── cache.js │ ├── index.js │ └── meta-data.js ├── initialization.js └── index.js ├── CHANGELOG.md ├── scripts ├── print.js ├── build.js ├── clean.js └── stats.js ├── jest.integration.config.js ├── jest.config.js └── SECURITY.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | 3 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | one-app-team-review-requested: 2 | - '**/*' 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | registry=https://registry.npmjs.org 3 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | registry=https://registry.npmjs.org 3 | -------------------------------------------------------------------------------- /one-service-worker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/one-service-worker/HEAD/one-service-worker.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # libraries 2 | node_modules 3 | # outputs 4 | /test-results 5 | # builds 6 | dist 7 | /es 8 | /index.* 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | /.jest-cache 4 | /test-results 5 | 6 | /es 7 | /index.* 8 | 9 | one*tgz 10 | .idea 11 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/articles/about-code-owners 2 | 3 | * @americanexpress/one-app-team @americanexpress/one-amex-admins 4 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "import/no-unresolved": [2, { 4 | "ignore": ["one-service-worker$"] 5 | }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/static/audio/gong.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/one-service-worker/HEAD/packages/one-service-worker-demo/static/audio/gong.m4a -------------------------------------------------------------------------------- /packages/one-service-worker-demo/static/images/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/americanexpress/one-service-worker/HEAD/packages/one-service-worker-demo/static/images/one.png -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel", 3 | "semi": true, 4 | "singleQuote": true, 5 | "arrowParens": "avoid", 6 | "bracketSpacing": true, 7 | "trailingComma": "all", 8 | "useTabs": false 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | max_line_length = 100 11 | 12 | [*.md] 13 | max_line_length = 80 14 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.github/workflows/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Labeler" 2 | on: 3 | pull_request_target: 4 | types: [opened, reopened] 5 | 6 | jobs: 7 | triage: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/labeler@v3 11 | with: 12 | repo-token: "${{ secrets.GITHUB_TOKEN }}" -------------------------------------------------------------------------------- /__tests__/utility/errors/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`errors consistently exports 1`] = ` 4 | "OneServiceWorkerError :: function 5 | errorFactory :: function 6 | notEnabled :: function 7 | notSupported :: function 8 | failedToInstall :: function 9 | failure :: function" 10 | `; 11 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | 2 | [👈 Return to README](../../README.md) 3 | 4 | 5 | # 🎛️ API 6 | 7 | * [Core](./Core.md) 8 | * [Cache](./Cache.md) 9 | * [Middleware](./Middleware.md) 10 | * [Runtime](./Runtime.md) 11 | * [Events](./Events.md) 12 | * [Errors](./Errors.md) 13 | * [Validation](./Validation.md) 14 | 15 | [☝️ Return To Top](#%EF%B8%8F-api) 16 | -------------------------------------------------------------------------------- /__tests__/utility/events/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`events consistently exports 1`] = ` 4 | "on :: function 5 | off :: function 6 | once :: function 7 | emit :: function 8 | emitter :: function 9 | eventListeners :: object 10 | calls :: object 11 | createMiddleware :: function 12 | createMiddlewareContext :: function 13 | createMiddlewareFactory :: function" 14 | `; 15 | -------------------------------------------------------------------------------- /docs/guides/README.md: -------------------------------------------------------------------------------- 1 | 2 | [👈 Return to README](../../README.md) 3 | 4 | 5 | # 👩‍🍳 Guides 6 | 7 | * [Analytics](./Analytics.md) 8 | * [Basics](./Basics.md) 9 | * [Best Practices](./BestPractices.md) 10 | * [Cache](./Cache.md) 11 | * [Patterns](./Patterns.md) 12 | * [Web Push](./WebPush.md) 13 | * [With React](./WithReact.md) 14 | * [Testing A Service Worker](./Testing.md) 15 | 16 | [☝️ Return To Top](#-guides) 17 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/static/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "One Service Worker", 3 | "short_name": "One Service Worker", 4 | "description": "One Service Worker Demo", 5 | "scope": "/", 6 | "start_url": "/", 7 | "display": "standalone", 8 | "background_color": "#006FCF", 9 | "theme_color": "#006FCF", 10 | "icons": [ 11 | { 12 | "src": "/images/one.png", 13 | "type": "image/png", 14 | "sizes": "512x512" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /__tests__/core/__snapshots__/subscription.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`subscription applicationServerKeyToBase64Array transforms vapid public key to an array of char codes 1`] = `"[66,69,102,78,65,110,68,118,98,70,111,67,69,49,99,50,110,116,107,117,117,50,47,76,54,75,106,81,114,57,87,98,68,51,75,89,107,113,56,65,73,80,107,100,66,97,70,103,117,89,120,54,120,103,76,56,52,53,67,106,73,56,70,52,80,98,101,106,119,120,74,43,102,72,120,102,114,85,99,76,77,89,108,67,81,90,107,61]"`; 4 | -------------------------------------------------------------------------------- /__tests__/core/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`core consistently exports 1`] = ` 4 | "getRegistration :: function 5 | getRegistrations :: function 6 | register :: function 7 | getWorker :: function 8 | postMessage :: function 9 | update :: function 10 | unregister :: function 11 | escapeHatch :: function 12 | showNotification :: function 13 | getNotifications :: function 14 | getSubscription :: function 15 | subscribe :: function 16 | unsubscribe :: function 17 | registerTag :: function 18 | getTags :: function 19 | urlBase64ToUint8Array :: function" 20 | `; 21 | -------------------------------------------------------------------------------- /__tests__/utility/runtime/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`runtime consistently exports 1`] = ` 4 | "isDevelopment :: function 5 | isEventsEnabled :: function 6 | isNonStandardEnabled :: function 7 | isNavigationPreloadEnabled :: function 8 | isServiceWorker :: function 9 | isServiceWorkerSupported :: function 10 | isNotificationSupported :: function 11 | isPushSupported :: function 12 | isBackgroundSyncSupported :: function 13 | isCacheStorageSupported :: function 14 | isIndexedDBSupported :: function 15 | isPermissionsSupported :: function 16 | isOffline :: function 17 | configure :: function 18 | getConfig :: function" 19 | `; 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /__tests__/middleware/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`middleware consistently exports 1`] = ` 4 | "manifest :: function 5 | cacheBusting :: function 6 | cacheRouter :: function 7 | cacheStrategy :: function 8 | precache :: function 9 | expiration :: function 10 | appShell :: function 11 | clientsClaim :: function 12 | skipWaiting :: function 13 | escapeHatchRoute :: function 14 | messageContext :: function 15 | messenger :: function 16 | navigationPreloadResponse :: function 17 | navigationPreloadActivation :: function 18 | onInstall :: function 19 | onActivate :: function 20 | onMessage :: function 21 | onPush :: function 22 | onSync :: function 23 | onFetch :: function" 24 | `; 25 | -------------------------------------------------------------------------------- /__tests__/integration/jest.setup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | require('@babel/polyfill'); 18 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | presets: ['amex'], 19 | }; 20 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/client/react.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export default window.React; 18 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/client/prop-types.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export default window.PropTypes; 18 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/client/react-dom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export default window.ReactDOM; 18 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | stale: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/stale@v3 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity.' 17 | stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity.' 18 | stale-issue-label: 'stale-issue' 19 | exempt-issue-labels: 'enhancement,documentation,good-first-issue,question' 20 | stale-pr-label: 'stale-pr' 21 | exempt-pr-labels: 'work-in-progress' 22 | days-before-stale: 30 23 | days-before-close: -1 24 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "amex", 5 | "plugin:prettier/recommended" 6 | ], 7 | "env": { 8 | "browser": true, 9 | "serviceworker": true 10 | }, 11 | "overrides": [ 12 | { 13 | "files": [ 14 | "**/__tests__/**", 15 | "**/__mocks__/**" 16 | ], 17 | "extends": [ 18 | "amex/test", 19 | "plugin:prettier/recommended" 20 | ], 21 | "rules": { 22 | "jest/no-large-snapshots": "off" 23 | } 24 | }, 25 | { 26 | "files": [ 27 | "**/*.md" 28 | ], 29 | "rules": { 30 | "no-restricted-globals": "off" 31 | } 32 | }, 33 | { 34 | "files": [ 35 | "**/*.{md,js}" 36 | ], 37 | "rules": { 38 | "unicorn/prevent-abbreviations": "off" 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,either express 11 | * or implied. See the License for the specific language governing permissions and limitations 12 | * under the License. 13 | */ 14 | 15 | module.exports = { 16 | extends: ['@commitlint/config-conventional'], 17 | rules: { 18 | 'scope-case': [2, 'always', ['pascal-case', 'camel-case', 'kebab-case']], 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /src/utility/errors/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export { 18 | OneServiceWorkerError, 19 | errorFactory, 20 | notEnabled, 21 | notSupported, 22 | failedToInstall, 23 | failure, 24 | } from './errors'; 25 | -------------------------------------------------------------------------------- /__tests__/integration/helpers/utility.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | // eslint-disable-next-line import/prefer-default-export 18 | export const sleep = timeToSleep => new Promise(resolve => setTimeout(resolve, timeToSleep)); 19 | -------------------------------------------------------------------------------- /src/utility/events/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export { on, off, once, emit, emitter, eventListeners, calls } from './events'; 18 | export { createMiddleware, createMiddlewareContext, createMiddlewareFactory } from './middleware'; 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.4](https://github.com/americanexpress/one-service-worker/compare/v1.0.3...v1.0.4) (2020-09-02) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **semantic-release:** branches ([f3ef0e9](https://github.com/americanexpress/one-service-worker/commit/f3ef0e9272c59c4d3bece102b424e18a29176dd1)) 7 | 8 | ## [1.0.3](https://github.com/americanexpress/one-service-worker/compare/v1.0.2...v1.0.3) (2020-06-15) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * **app-shell:** match with cache name ([4e2fee0](https://github.com/americanexpress/one-service-worker/commit/4e2fee034d8866837a3be463784f18cf4b4a65be)) 14 | 15 | ## [1.0.2](https://github.com/americanexpress/one-service-worker/compare/v1.0.1...v1.0.2) (2020-06-12) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * **cache/name:** remove inconsistent references ([56d1289](https://github.com/americanexpress/one-service-worker/commit/56d1289027e3ad2f856a74e22d0728d370881623)) 21 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/client/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export default { 18 | logLevel: 'info', 19 | logging: true, 20 | strict: true, 21 | events: true, 22 | navigationPreload: true, 23 | nonStandard: true, 24 | }; 25 | -------------------------------------------------------------------------------- /__tests__/cache/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`cache consistently exports 1`] = ` 4 | "cachePrefix :: string 5 | cacheDelimiter :: string 6 | defaultCacheName :: string 7 | defaultCacheOptions :: object 8 | createCacheName :: function 9 | normalizeRequest :: function 10 | open :: function 11 | has :: function 12 | keys :: function 13 | match :: function 14 | matchAll :: function 15 | add :: function 16 | addAll :: function 17 | put :: function 18 | remove :: function 19 | removeAll :: function 20 | entries :: function 21 | clear :: function 22 | metaDataCacheName :: string 23 | createMetaCacheName :: function 24 | createMetaCacheEntryName :: function 25 | createMetaRequest :: function 26 | createMetaResponse :: function 27 | getMetaStore :: function 28 | getMetaData :: function 29 | setMetaData :: function 30 | deleteMetaData :: function 31 | createCacheEntryName :: function" 32 | `; 33 | -------------------------------------------------------------------------------- /__tests__/integration/helpers/constants.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export const serverAddress = 'http://localhost:3030/'; 18 | export const registerServiceWorkerSelector = '#register-service-worker'; 19 | export const unregisterServiceWorkerSelector = `#unregister-service-worker`; 20 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/server/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import app from './server'; 18 | 19 | const port = 3030; 20 | 21 | app.listen(port, function onListen(error) { 22 | if (error) console.error(error); 23 | else console.log('\nserver is listening on %s', port); 24 | }); 25 | -------------------------------------------------------------------------------- /scripts/print.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const print = require('./stats'); 18 | const pkg = require('../package.json'); 19 | 20 | const files = 21 | process.env.NODE_ENV === 'production' 22 | ? pkg.files 23 | : pkg.files.filter(filename => /.*\.min\..*/.test(filename) === false); 24 | 25 | print(files); 26 | -------------------------------------------------------------------------------- /src/utility/events/__mocks__/events.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const imports = jest.requireActual('../events'); 18 | 19 | Object.keys(imports).forEach(key => typeof imports[key] === 'function' && jest.spyOn(imports, key)); 20 | 21 | export const { on, off, once, emit, emitter, eventListeners, calls } = imports; 22 | -------------------------------------------------------------------------------- /src/utility/validation/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export { 18 | TYPES, 19 | ENUMS, 20 | expectedType, 21 | expectedArrayOfType, 22 | enumerableException, 23 | unknownKey, 24 | unknownEventName, 25 | validateInput, 26 | } from './validation'; 27 | export { getCacheOptions } from './options'; 28 | -------------------------------------------------------------------------------- /__tests__/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import * as library from '../src'; 18 | import { printExports } from './helpers'; 19 | 20 | describe('library', () => { 21 | test('consistently exports', () => { 22 | expect.assertions(1); 23 | expect(printExports(library)).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /jest.integration.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | module.exports = { 18 | displayName: { 19 | name: 'service-worker-integration', 20 | color: 'blue', 21 | }, 22 | setupFilesAfterEnv: ['/__tests__/integration/jest.setup.js'], 23 | testMatch: ['/__tests__/integration/*.spec.js'], 24 | }; 25 | -------------------------------------------------------------------------------- /__tests__/core/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import * as core from '../../src/core'; 18 | import { printExports } from '../helpers'; 19 | 20 | describe('core', () => { 21 | test('consistently exports', () => { 22 | expect.assertions(1); 23 | expect(printExports(core)).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /__tests__/cache/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import * as cache from '../../src/cache'; 18 | import { printExports } from '../helpers'; 19 | 20 | describe('cache', () => { 21 | test('consistently exports', () => { 22 | expect.assertions(1); 23 | expect(printExports(cache)).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/utility/validation/__mocks__/options.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const imports = jest.requireActual('../options'); 18 | 19 | Object.keys(imports).forEach(key => typeof imports[key] === 'function' && jest.spyOn(imports, key)); 20 | 21 | // eslint-disable-next-line import/prefer-default-export 22 | export const { getCacheOptions } = imports; 23 | -------------------------------------------------------------------------------- /__tests__/utility/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import * as utility from '../../src/utility'; 18 | import { printExports } from '../helpers'; 19 | 20 | describe('utility', () => { 21 | test('consistently exports', () => { 22 | expect.assertions(1); 23 | expect(printExports(utility, true)).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/core/__mocks__/subscription.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const imports = jest.requireActual('../subscription'); 18 | 19 | Object.keys(imports).forEach(key => typeof imports[key] === 'function' && jest.spyOn(imports, key)); 20 | 21 | // eslint-disable-next-line import/prefer-default-export 22 | export const { urlBase64ToUint8Array } = imports; 23 | -------------------------------------------------------------------------------- /__tests__/middleware/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import * as middleware from '../../src/middleware'; 18 | import { printExports } from '../helpers'; 19 | 20 | describe('middleware', () => { 21 | test('consistently exports', () => { 22 | expect.assertions(1); 23 | expect(printExports(middleware)).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright 2020 American Express Travel Related Services Company, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 15 | * or implied. See the License for the specific language governing 16 | * permissions and limitations under the License. 17 | */ 18 | 19 | const { execSync } = require('child_process'); 20 | 21 | const pkg = require('../package.json'); 22 | const printStats = require('./stats'); 23 | 24 | (function build() { 25 | execSync('rollup -c', { 26 | stdio: 'inherit', 27 | }); 28 | printStats(pkg.files); 29 | })(); 30 | -------------------------------------------------------------------------------- /__tests__/utility/errors/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import * as errors from '../../../src/utility/errors'; 18 | import { printExports } from '../../helpers'; 19 | 20 | describe('errors', () => { 21 | test('consistently exports', () => { 22 | expect.assertions(1); 23 | expect(printExports(errors)).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /__tests__/utility/events/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import * as events from '../../../src/utility/events'; 18 | import { printExports } from '../../helpers'; 19 | 20 | describe('events', () => { 21 | test('consistently exports', () => { 22 | expect.assertions(1); 23 | expect(printExports(events)).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /__tests__/utility/runtime/index.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import * as runtime from '../../../src/utility/runtime'; 18 | import { printExports } from '../../helpers'; 19 | 20 | describe('runtime', () => { 21 | test('consistently exports', () => { 22 | expect.assertions(1); 23 | expect(printExports(runtime)).toMatchSnapshot(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/README.md: -------------------------------------------------------------------------------- 1 | # One Service Worker Demo 2 | 3 | [👈 Go to `README`](../../README.md) 4 | 5 | The package here is a demo built around the `@americanexpress/one-service-worker` library. 6 | 7 | ## 🤹‍ Usage 8 | 9 | ### Installation 10 | 11 | To install, we can run: 12 | 13 | ```bash 14 | npm install 15 | ``` 16 | 17 | ## 📜 Available Scripts 18 | 19 | **`npm run build`** 20 | 21 | Runs `rollup` to bundle and uses`babel` to compile `src` files to transpiled JavaScript using 22 | [`babel-preset-amex`][babel-amex]. 23 | 24 | **`npm start`** 25 | 26 | Runs the demo locally. 27 | 28 | **`npm dev`** 29 | 30 | Runs the demo locally in watch mode. 31 | 32 | **`npm test`** 33 | 34 | Runs unit tests **and** verifies the format of all commit messages on the current branch. 35 | 36 | #### Notes 37 | 38 | - The demo will run on `http://localhost:3030` to avoid any disruptions or interference on port `3000`. 39 | - Web Push will require configuration if you are behind a proxy, or it will most likely fail. 40 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/components/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import Client from './Client'; 20 | 21 | export const Context = React.createContext(); 22 | 23 | export default function Application() { 24 | return ( 25 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/utility/errors/__mocks__/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const imports = jest.requireActual('../errors'); 18 | 19 | Object.keys(imports).forEach(key => typeof imports[key] === 'function' && jest.spyOn(imports, key)); 20 | 21 | export const { 22 | OneServiceWorkerError, 23 | factory, 24 | notEnabled, 25 | notSupported, 26 | failedToInstall, 27 | failure, 28 | } = imports; 29 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/usePerformance.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | export default function usePerformance(name) { 20 | React.useEffect(() => { 21 | if (process.env.ONE_SW_MARK_PERFORMANCE === 'true') { 22 | performance.mark(name); 23 | return () => performance.measure(name); 24 | } 25 | 26 | return null; 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/middleware/utility.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export function noop() {} 18 | 19 | export function isResponseSuccessful(response) { 20 | return response.status >= 200 && response.status < 300; 21 | } 22 | 23 | export function isNavigateRequest(request) { 24 | return request.mode === 'navigate'; 25 | } 26 | 27 | export function matchRequest({ event, request }) { 28 | return event.request.url === request.url; 29 | } 30 | -------------------------------------------------------------------------------- /src/utility/runtime/config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | let config = { 18 | development: process.env.NODE_ENV === 'development', 19 | events: true, 20 | nonStandard: true, 21 | navigationPreload: true, 22 | }; 23 | 24 | export function configure(newConfig = Object.create(null)) { 25 | config = { ...config, ...newConfig }; 26 | return config; 27 | } 28 | 29 | export function getConfig() { 30 | return config; 31 | } 32 | -------------------------------------------------------------------------------- /__tests__/integration/lifecycle.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { createServiceWorkerLifecycleTest } from './helpers/lifecycle'; 18 | 19 | describe('platforms - lifecycle', () => { 20 | describe('webkit', () => createServiceWorkerLifecycleTest('webkit')); 21 | describe('firefox', () => createServiceWorkerLifecycleTest('firefox')); 22 | describe('chromium', () => createServiceWorkerLifecycleTest('chromium')); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/client/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import ReactDOM from 'react-dom'; 19 | import { configure } from '@americanexpress/one-service-worker'; 20 | 21 | import App from '../components'; 22 | import config from './config'; 23 | 24 | configure(config); 25 | 26 | const appElement = document.querySelector('#root'); 27 | 28 | ReactDOM.render(React.createElement(App), appElement); 29 | -------------------------------------------------------------------------------- /src/core/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export { 18 | getRegistration, 19 | getRegistrations, 20 | register, 21 | getWorker, 22 | postMessage, 23 | update, 24 | unregister, 25 | escapeHatch, 26 | showNotification, 27 | getNotifications, 28 | getSubscription, 29 | subscribe, 30 | unsubscribe, 31 | registerTag, 32 | getTags, 33 | } from './service-worker'; 34 | export { urlBase64ToUint8Array } from './subscription'; 35 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/useStorageQuota.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | export default function StorageQuotaView() { 20 | const [quota, setQuote] = React.useState(null); 21 | 22 | const estimate = navigator.storage.estimate().then(quote => setQuote(quote)); 23 | 24 | React.useEffect(() => { 25 | estimate(); 26 | }, []); 27 | 28 | return { 29 | quota, 30 | estimate, 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | One Service Worker 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/cache/__mocks__/meta-data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const imported = jest.requireActual('../meta-data'); 18 | 19 | Object.keys(imported).forEach( 20 | key => typeof imported[key] === 'function' && jest.spyOn(imported, key), 21 | ); 22 | 23 | export const { 24 | metaDataCacheName, 25 | createMetaCacheName, 26 | createMetaCacheEntryName, 27 | createMetaRequest, 28 | createMetaResponse, 29 | getMetaStore, 30 | getMetaData, 31 | setMetaData, 32 | deleteMetaData, 33 | } = imported; 34 | -------------------------------------------------------------------------------- /src/utility/runtime/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export { 18 | isDevelopment, 19 | isEventsEnabled, 20 | isNonStandardEnabled, 21 | isNavigationPreloadEnabled, 22 | isServiceWorker, 23 | isServiceWorkerSupported, 24 | isNotificationSupported, 25 | isPushSupported, 26 | isBackgroundSyncSupported, 27 | isCacheStorageSupported, 28 | isIndexedDBSupported, 29 | isPermissionsSupported, 30 | isOffline, 31 | } from './environment'; 32 | export { configure, getConfig } from './config'; 33 | -------------------------------------------------------------------------------- /src/utility/validation/__mocks__/validation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const imports = jest.requireActual('../validation'); 18 | 19 | Object.keys(imports).forEach(key => typeof imports[key] === 'function' && jest.spyOn(imports, key)); 20 | 21 | // eslint-disable-next-line import/prefer-default-export 22 | export const { 23 | TYPES, 24 | ENUMS, 25 | expectedType, 26 | expectedArrayOfType, 27 | enumerableException, 28 | unknownKey, 29 | unknownEventName, 30 | validateInput, 31 | } = imports; 32 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/useInterval.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | export default function useInterval(callback, timeout = false) { 20 | // eslint-disable-next-line consistent-return 21 | React.useEffect(() => { 22 | if (typeof callback === 'function' && typeof timeout === 'number') { 23 | const id = setInterval(callback, timeout); 24 | return () => { 25 | clearInterval(id); 26 | }; 27 | } 28 | }, [callback, timeout]); 29 | } 30 | -------------------------------------------------------------------------------- /__tests__/utility/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`utility consistently exports 1`] = `"OneServiceWorkerError :: function errorFactory :: function notEnabled :: function notSupported :: function failedToInstall :: function failure :: function TYPES :: object ENUMS :: object expectedType :: function expectedArrayOfType :: function enumerableException :: function unknownKey :: function unknownEventName :: function validateInput :: function getCacheOptions :: function configure :: function getConfig :: function isDevelopment :: function isEventsEnabled :: function isNonStandardEnabled :: function isNavigationPreloadEnabled :: function isServiceWorker :: function isServiceWorkerSupported :: function isNotificationSupported :: function isPushSupported :: function isBackgroundSyncSupported :: function isCacheStorageSupported :: function isIndexedDBSupported :: function isPermissionsSupported :: function isOffline :: function on :: function once :: function off :: function emit :: function emitter :: function eventListeners :: object calls :: object createMiddleware :: function createMiddlewareContext :: function createMiddlewareFactory :: function"`; 4 | -------------------------------------------------------------------------------- /src/core/__mocks__/service-worker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const imports = jest.requireActual('../service-worker'); 18 | 19 | Object.keys(imports).forEach(key => typeof imports[key] === 'function' && jest.spyOn(imports, key)); 20 | 21 | export const { 22 | register, 23 | getRegistration, 24 | getRegistrations, 25 | getWorker, 26 | postMessage, 27 | update, 28 | unregister, 29 | escapeHatch, 30 | getNotifications, 31 | showNotification, 32 | getSubscription, 33 | subscribe, 34 | unsubscribe, 35 | registerTag, 36 | getTags, 37 | } = imports; 38 | -------------------------------------------------------------------------------- /__tests__/integration/helpers/platform.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { webkit, firefox, chromium } from 'playwright'; 18 | 19 | // eslint-disable-next-line import/prefer-default-export 20 | export function getPlatform(platform = 'chromium') { 21 | switch (platform) { 22 | default: 23 | return null; 24 | case 'firefox': 25 | return firefox; 26 | case 'webkit': 27 | return webkit; 28 | case 'chromium': 29 | return chromium; 30 | } 31 | } 32 | 33 | export function createBrowser(platform) { 34 | return getPlatform(platform).launch(); 35 | } 36 | -------------------------------------------------------------------------------- /docs/guides/WithReact.md: -------------------------------------------------------------------------------- 1 | # React usage 2 | 3 | [👈 Go to `README`](../../README.md) 4 | 5 | [👆 Back to `Guides`](./README.md) 6 | 7 | 8 | It can be beneficial to register a service worker as early as possible during app start. 9 | In this example, we use a functional component to register a service worker and set 10 | the registration to memory. Using context, we can hand our reference of `registration` 11 | instance to the rest of our app. 12 | 13 | ### `src/app.jsx` 14 | 15 | ```jsx 16 | import React from 'react'; 17 | 18 | import { register } from '@americanexpress/one-service-worker'; 19 | 20 | export const Context = React.createContext(); 21 | 22 | export default function App() { 23 | const [registration, setRegistration] = React.useState(null); 24 | const [error, setError] = React.useState(null); 25 | 26 | React.useEffect(() => { 27 | register({ 28 | scope: '/', 29 | url: '/sw.js', 30 | }) 31 | .then(setRegistration) 32 | .catch(setError); 33 | }, []); 34 | 35 | return ( 36 | 37 | {children} 38 | 39 | ); 40 | } 41 | ``` 42 | 43 | [☝️ Return To Top](#-react-usage) 44 | -------------------------------------------------------------------------------- /src/cache/__mocks__/cache.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const imported = jest.requireActual('../cache'); 18 | 19 | Object.keys(imported).forEach( 20 | key => typeof imported[key] === 'function' && jest.spyOn(imported, key), 21 | ); 22 | 23 | export const { 24 | cachePrefix, 25 | cacheDelimiter, 26 | defaultCacheName, 27 | defaultCacheOptions, 28 | createCacheName, 29 | normalizeRequest, 30 | open, 31 | has, 32 | keys, 33 | match, 34 | matchAll, 35 | add, 36 | addAll, 37 | put, 38 | remove, 39 | removeAll, 40 | entries, 41 | clear, 42 | } = imported; 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | release_package: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | persist-credentials: false 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: '18.x' 17 | - name: Get yarn cache directory path 18 | id: yarn-cache-dir-path 19 | run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT 20 | - uses: actions/cache@v1 21 | with: 22 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 23 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 24 | restore-keys: ${{ runner.os }}-yarn- 25 | - name: Install package 26 | env: 27 | NODE_ENV: development 28 | run: yarn 29 | - name: Semantic release 30 | env: 31 | GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} 32 | GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} 33 | GIT_COMMITTER_EMAIL: ${{ secrets.GIT_COMMITTER_EMAIL }} 34 | GIT_COMMITTER_NAME: ${{ secrets.GIT_COMMITTER_NAME }} 35 | GITHUB_TOKEN: ${{ secrets.PA_TOKEN }} 36 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 37 | run: yarn semantic-release 38 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export { default as useInterval } from './useInterval'; 18 | export { default as usePerformance } from './usePerformance'; 19 | export { default as useStorage } from './useStorage'; 20 | export { default as useOffline } from './useOffline'; 21 | export { default as usePermissions } from './usePermissions'; 22 | export { default as useServiceWorker } from './useServiceWorker'; 23 | export { default as useStandalone } from './useStandalone'; 24 | export { default as useSync } from './useSync'; 25 | export { default as useWebPush } from './useWebPush'; 26 | -------------------------------------------------------------------------------- /src/cache/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export { 18 | cachePrefix, 19 | cacheDelimiter, 20 | defaultCacheName, 21 | defaultCacheOptions, 22 | createCacheName, 23 | normalizeRequest, 24 | open, 25 | has, 26 | keys, 27 | match, 28 | matchAll, 29 | add, 30 | addAll, 31 | put, 32 | remove, 33 | removeAll, 34 | entries, 35 | clear, 36 | } from './cache'; 37 | export { 38 | metaDataCacheName, 39 | createMetaCacheName, 40 | createMetaCacheEntryName, 41 | createMetaRequest, 42 | createMetaResponse, 43 | getMetaStore, 44 | getMetaData, 45 | setMetaData, 46 | deleteMetaData, 47 | createCacheEntryName, 48 | } from './meta-data'; 49 | -------------------------------------------------------------------------------- /src/utility/validation/options.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | // eslint-disable-next-line import/prefer-default-export 18 | export function getCacheOptions({ ignoreSearch, ignoreMethod, ignoreVary, cacheName } = {}) { 19 | const cacheQueryOptions = {}; 20 | if (typeof ignoreSearch === 'boolean') cacheQueryOptions.ignoreSearch = ignoreSearch; 21 | if (typeof ignoreMethod === 'boolean') cacheQueryOptions.ignoreMethod = ignoreMethod; 22 | if (typeof ignoreVary === 'boolean') cacheQueryOptions.ignoreVary = ignoreVary; 23 | if (typeof cacheName === 'string') cacheQueryOptions.cacheName = cacheName; 24 | if (Object.keys(cacheQueryOptions).length > 0) return cacheQueryOptions; 25 | return undefined; 26 | } 27 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const pkg = require('./package.json'); 18 | 19 | module.exports = { 20 | preset: 'amex-jest-preset', 21 | displayName: { 22 | name: 'service-worker', 23 | color: 'green', 24 | }, 25 | collectCoverageFrom: ['src/**/*.js'], 26 | testMatch: ['/__tests__/**/*.spec.js'], 27 | testPathIgnorePatterns: ['/__tests__/integration/'], 28 | setupFilesAfterEnv: ['/__tests__/jest.setup.js'], 29 | testEnvironment: '/jest.environment.js', 30 | testEnvironmentOptions: { 31 | target: 'shared', 32 | env: { 33 | NAME: pkg.name, 34 | VERSION: pkg.version, 35 | NODE_ENV: 'production', 36 | }, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/useSync.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import { getTags, registerTag, on, off } from '@americanexpress/one-service-worker'; 20 | 21 | export default function useSync() { 22 | const [tags, setTags] = React.useState([]); 23 | 24 | React.useEffect(() => { 25 | const registerTagHandler = tag => setTags(tags.concat(tag)); 26 | on('registertag', registerTagHandler); 27 | getTags().then(setTags); 28 | return () => { 29 | off('registertag', registerTagHandler); 30 | }; 31 | }, []); 32 | 33 | return React.useMemo( 34 | () => ({ 35 | tags, 36 | registerTag, 37 | }), 38 | [tags], 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # American Express Responsible Disclosure Policy 2 | 3 | At American Express, we take cybersecurity seriously and value the contributions of the security 4 | community at large. The responsible disclosure of potential issues helps us ensure the security and 5 | privacy of our customers and data. If you believe you’ve found a security issue in one of our 6 | products or services please send it to us and include the following details with your report: 7 | - A description of the issue and where it is located. 8 | - A description of the steps required to reproduce the issue. 9 | 10 | Please note that this should not be construed as encouragement or permission to perform any of the 11 | following activities: 12 | - Hack, penetrate, or otherwise attempt to gain unauthorized access to American Express 13 | applications, systems, or data in violation of applicable law; 14 | - Download, copy, disclose or use any proprietary or confidential American Express data, including 15 | customer data; and 16 | - Adversely impact American Express or the operation of American Express applications or systems. 17 | 18 | American Express does not waive any rights or claims with respect to such activities. 19 | 20 | Please email your message and any attachments to 21 | [responsible.disclosure@aexp.com](mailto:responsible.disclosure@aexp.com) 22 | 23 | #### Thank you for helping us keep American Express customers and data safe. 24 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/server/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | // eslint-disable-next-line import/no-extraneous-dependencies 18 | import express from 'express'; 19 | // eslint-disable-next-line import/no-extraneous-dependencies 20 | import bodyParser from 'body-parser'; 21 | 22 | import { 23 | setHeadersMiddleware, 24 | sendHtmlMiddleware, 25 | dispatchMiddleware, 26 | subscriptionMiddleware, 27 | } from '../middleware'; 28 | 29 | const app = express(); 30 | 31 | app 32 | .use(setHeadersMiddleware) 33 | .use(express.static(`${__dirname}/public`)) 34 | .use(sendHtmlMiddleware) 35 | .use(bodyParser.json()) 36 | .use('/dispatch', dispatchMiddleware) 37 | .use('/subscribe', subscriptionMiddleware); 38 | 39 | export default app; 40 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/components/views/Metrics.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | export default function MetricsView() { 20 | // const [connection, setConnection] = React.useState(() => navigator.connection); 21 | 22 | React.useEffect(() => { 23 | // navigator.connection.addEventListener('change', () => setConnection(navigator.connection)); 24 | }, []); 25 | 26 | return ( 27 |
28 |
29 | 30 |

Metrics

31 |

first paint

32 |
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /src/middleware/messenger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export function createMessageContext({ getID = () => 'id', transformData = pass => pass } = {}) { 18 | return function messenger(event, context) { 19 | if (event.data) { 20 | const { [getID()]: id, ...data } = 'json' in event.data ? event.data.json() : event.data; 21 | 22 | if (id) { 23 | context.set('id', id); 24 | context.set('data', transformData(data, event, context)); 25 | } 26 | } 27 | }; 28 | } 29 | 30 | export function createMessenger(resolvers = {}) { 31 | return function messenger(event, context) { 32 | const { id, data } = context.get(); 33 | 34 | if (id in resolvers) { 35 | event.waitUntil(resolvers[id](data, event, context)); 36 | } 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /docs/Demo.md: -------------------------------------------------------------------------------- 1 | # One Service Worker Demo 2 | 3 | [👈 Go to `README`](../README.md) 4 | 5 | 6 | For convenience, we have a demo project to run for exploring the service worker 7 | and the library. We've bound a few `npm` scripts that can be run to manage the demo. 8 | 9 | While on installation, the demo will also be installed. 10 | However if you need to reinstall for any reason, you can run: 11 | 12 | ```bash 13 | npm run demo:install 14 | ``` 15 | 16 | Then build the demo project by running: 17 | 18 | ```bash 19 | npm run demo:build 20 | ``` 21 | 22 | ## Running the demo 23 | 24 | To start the server to run locally: 25 | 26 | ```bash 27 | npm run demo:start 28 | ``` 29 | 30 | ## Dev mode 31 | 32 | There is a dev mode available for auto-updating on code change of the library or demo. 33 | To run in this mode: 34 | 35 | ```bash 36 | npm run dev 37 | ``` 38 | 39 | If you plan on experimenting with the demo app, this mode is a convenience to allow 40 | you to see changes quickly. One thing to note: due to the service worker caching, 41 | we would need to manage the browser and use dev tools to make sure updates are received, 42 | this can be a common point of confusion. 43 | 44 | # Notes 45 | 46 | - The demo will run on `http://localhost:3030` to avoid any disruptions or interference on port `3000`. 47 | - Web Push will require configuration if you are behind a proxy, or it will most likely fail. Solutions welcome. 48 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | main: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [16.x, 18.x] 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | # Create a main branch for commitlint 16 | # https://github.com/conventional-changelog/commitlint/issues/6 17 | - run: git remote set-branches origin main && git fetch 18 | - name: Setup Node 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - name: Install required packages 23 | # add required dependencies for webkit, chromium and firefox (playwright) 24 | run: | 25 | sudo apt-get update 26 | npx --yes playwright install-deps 27 | - name: Get yarn cache directory path 28 | id: yarn-cache-dir-path 29 | run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT 30 | - uses: actions/cache@v2 31 | with: 32 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 33 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 34 | - name: Setup 35 | env: 36 | NODE_ENV: development 37 | run: | 38 | yarn install --frozen-lockfile 39 | (cd packages/one-service-worker-demo && yarn install --frozen-lockfile) 40 | - name: Testing 41 | env: 42 | TEMP_DIR: ${{ runner.temp }} 43 | run: yarn test 44 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/components/views/Offline.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import { useOffline } from '../../hooks'; 20 | 21 | export default function OfflineView() { 22 | const offline = useOffline(); 23 | 24 | return ( 25 |
26 |
27 | 28 |

Offline Detection

29 |
30 | 31 | {offline ? ( 32 |
33 |

Offline

34 |
35 | ) : ( 36 |
37 |

OnLine

38 |
39 | )} 40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/components/views/Performance.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import StorageQuotaView from './StorageQuota'; 20 | import ConnectionView from './Connection'; 21 | import OfflineView from './Offline'; 22 | import MetricsView from './Metrics'; 23 | 24 | export default function PerformanceView() { 25 | return ( 26 |
27 |
28 |

Performance

29 | 30 |
31 | 32 |
33 | 34 | 35 | 36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/components/views/Sync.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import { useSync } from '../../hooks'; 20 | 21 | export default function SyncView() { 22 | const { tags } = useSync(); 23 | 24 | return ( 25 |
26 |
27 |

Sync and Background Fetch

28 |
29 | 30 | {tags.length > 0 ? ( 31 |
32 |
33 |

Current Tags

34 |
35 | 36 |
    37 | {tags.map(tag => ( 38 |
  • {tag}
  • 39 | ))} 40 |
41 |
42 | ) : ( 43 |
44 |
45 |

No Tags

46 |
47 |
48 | )} 49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /docs/api/Validation.md: -------------------------------------------------------------------------------- 1 | # Validation 2 | 3 | [👈 Go to `README`](../../README.md) 4 | 5 | [👆 Back to `API`](./README.md) 6 | 7 | 8 | ## 📖 Table of Contents 9 | 10 | - [`validateInput`](#-validateinput) 11 | - [`getCacheOptions`](#-getcacheoptions) 12 | 13 | ### `validateInput` 14 | 15 | Takes in an object and checks the properties satisfy the `types` schema. 16 | 17 | ```js 18 | import { validateInput } from '@americanexpress/one-service-worker'; 19 | 20 | const logErrors = true; 21 | 22 | validateInput( 23 | { 24 | url: 39, 25 | }, 26 | logErrors, 27 | ); 28 | ``` 29 | 30 | **Parameters** 31 | 32 | | Name | Type | Required | Description | 33 | | --- | --- | --- | --- | 34 | | `configuration` | `Object` | `false` | Object to check the properties on | 35 | | `log` | `Boolean` | `false` | Toggles logging the exceptions | 36 | 37 | **Returns** 38 | 39 | `[ OneServiceWorkerError ]` 40 | 41 |   42 | 43 | ### `getCacheOptions` 44 | 45 | Extracts the `cacheOptions` from an object. If no matches, returns `undefined`. 46 | 47 | ```js 48 | const cacheOptions = { 49 | ignoreSearch: true, 50 | ignoreMethod: true, 51 | ignoreVary: true, 52 | cacheNam: 'my-cache', 53 | }; 54 | ``` 55 | 56 | **Parameters** 57 | 58 | | Name | Type | Required | Description | 59 | | --- | --- | --- | --- | 60 | | `options` | `Object` | `false` | Object to extract the cacheOptions properties | 61 | 62 | **Returns** 63 | 64 | `Object|undefined` 65 | 66 |   67 | 68 | [☝️ Return To Top](#-📖-table-of-contents) 69 | -------------------------------------------------------------------------------- /__tests__/utility/runtime/config.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { configure, getConfig } from '../../../src/utility/runtime/config'; 18 | 19 | describe('configuration', () => { 20 | test('getConfig returns the default configuration', () => { 21 | expect.assertions(1); 22 | 23 | expect(getConfig()).toEqual({ 24 | development: process.env.NODE_ENV === 'development', 25 | events: true, 26 | nonStandard: true, 27 | navigationPreload: true, 28 | }); 29 | }); 30 | 31 | test('configure changes the properties and getConfig retrieves the updated config', () => { 32 | expect.assertions(3); 33 | 34 | const currentEvents = getConfig().events; 35 | 36 | expect(configure()).toHaveProperty('events', currentEvents); 37 | expect( 38 | configure({ 39 | events: !currentEvents, 40 | }), 41 | ).toMatchObject({ 42 | events: !currentEvents, 43 | }); 44 | expect(getConfig()).toHaveProperty('events', !currentEvents); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /scripts/clean.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * Copyright 2020 American Express Travel Related Services Company, Inc. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 15 | * or implied. See the License for the specific language governing 16 | * permissions and limitations under the License. 17 | */ 18 | 19 | const path = require('path'); 20 | const fs = require('fs'); 21 | 22 | const pkg = require('../package.json'); 23 | 24 | (async function clean() { 25 | const packageRootPath = path.resolve(__dirname, '..'); 26 | 27 | pkg.files 28 | .concat('demo') 29 | // do not remove, jest.environment is directly sourced 30 | .filter(basePath => !['jest.environment.js'].includes(basePath)) 31 | .map(baseFilePath => path.join(packageRootPath, baseFilePath)) 32 | .forEach(function recursiveCleaner(filePath) { 33 | if (fs.existsSync(filePath)) { 34 | const stats = fs.statSync(filePath); 35 | if (stats.isDirectory()) { 36 | const dir = fs 37 | .readdirSync(filePath) 38 | .map(baseFilePath => path.join(filePath, baseFilePath)); 39 | dir.forEach(recursiveCleaner); 40 | fs.rmdirSync(filePath); 41 | } else fs.unlinkSync(filePath); 42 | } 43 | }); 44 | })(); 45 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/useOffline.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | export function createOfflineEventHandlers(setOffline) { 20 | return { 21 | offline() { 22 | setOffline(true); 23 | }, 24 | online() { 25 | setOffline(false); 26 | }, 27 | }; 28 | } 29 | 30 | // https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine/Online_and_offline_events 31 | export default function useOffline() { 32 | const [offline, setOffline] = React.useState(false); 33 | 34 | React.useLayoutEffect(() => { 35 | const handlers = createOfflineEventHandlers(setOffline); 36 | 37 | window.addEventListener('offline', handlers.offline); 38 | window.addEventListener('online', handlers.online); 39 | 40 | setOffline(!navigator.onLine); 41 | 42 | return () => { 43 | window.removeEventListener('offline', handlers.offline); 44 | window.removeEventListener('online', handlers.online); 45 | }; 46 | }, []); 47 | 48 | return offline; 49 | } 50 | -------------------------------------------------------------------------------- /src/core/subscription.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export function applicationServerKeyToBase64Array(applicationServerKey) { 18 | // the applicationServerKey is the public key used in web push and creating a subscription 19 | // Transform the applicationServerKey string to base64 20 | // Clean the string and generate the padding: 21 | const string = applicationServerKey.replace(/"/g, ''); 22 | const padding = '='.repeat((4 - (string.length % 4)) % 4); 23 | const base = `${string}${padding}`.replace(/-/g, '+').replace(/_/g, '/'); 24 | // Encode it to base64, if not already: 25 | // eslint-disable-next-line no-restricted-globals 26 | const base64 = self.atob(base); 27 | // get character codes for passing into typed arrays: 28 | return base64.split('').map(char => char.charCodeAt(0)); 29 | } 30 | 31 | // See: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Syntax 32 | export function urlBase64ToUint8Array(applicationServerKey) { 33 | if (applicationServerKey) { 34 | return new Uint8Array(applicationServerKeyToBase64Array(applicationServerKey)); 35 | } 36 | return new Uint8Array(0); 37 | } 38 | -------------------------------------------------------------------------------- /src/initialization.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { isServiceWorkerSupported, isServiceWorker } from './utility/runtime'; 18 | import { emitter, on, emit } from './utility/events'; 19 | 20 | export default function initialize() { 21 | if (isServiceWorker()) { 22 | emitter([ 23 | 'onunhandledrejection', 24 | 'onerror', 25 | 'oninstall', 26 | 'onactivate', 27 | 'onmessage', 28 | 'onfetch', 29 | 'onsync', 30 | 'onpush', 31 | 'onnotificationclick', 32 | 'onnotificationclose', 33 | ]); 34 | } else if (isServiceWorkerSupported()) { 35 | on('registration', registration => { 36 | emitter(['onupdatefound'], registration); 37 | emitter( 38 | ['onstatechange'], 39 | registration.installing || registration.waiting || registration.active, 40 | ); 41 | }); 42 | on('statechange', event => emit(event.target.state, event)); 43 | navigator.serviceWorker.ready.then(registration => { 44 | emitter(['oncontrollerchange', 'onmessage', 'onerror'], navigator.serviceWorker); 45 | return registration; 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/utility/errors/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export class OneServiceWorkerError extends Error { 18 | constructor(message, error) { 19 | super(message); 20 | 21 | if (error instanceof Error) { 22 | this.message = [this.message, error.message].filter(str => !!str).join('::'); 23 | this.stack = error.stack; 24 | } 25 | } 26 | } 27 | 28 | export const notEnabled = (feature = 'Events', error) => 29 | new OneServiceWorkerError(`[${feature} not enabled]`, error); 30 | export const notSupported = (feature = 'Service Worker', error) => 31 | new OneServiceWorkerError(`[${feature} not supported]`, error); 32 | export const failedToInstall = (feature = 'Service Worker', error) => 33 | new OneServiceWorkerError(`[${feature} failed to install]`, error); 34 | export const failure = (feature = 'Service Worker', error) => 35 | new OneServiceWorkerError(`[${feature} failed]`, error); 36 | 37 | // handler is used for taking the error and throwing, rejecting or logging 38 | export function errorFactory(fn, handler = n => n) { 39 | return function callback() { 40 | return handler(fn()); 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/components/views/Permissions.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | import { usePermissions } from '../../hooks'; 19 | 20 | const includePush = false; 21 | const permissionsToCheck = ['notifications']; 22 | if (includePush) permissionsToCheck.push({ name: 'push', userVisibleOnly: true }); 23 | 24 | export default function PermissionsView() { 25 | const permissions = usePermissions([['push', { userVisibleOnly: true }], ['notifications']]); 26 | 27 | return ( 28 |
29 |
30 |

Permissions

31 |
32 | 33 |
    34 | {React.Children.toArray( 35 | Object.keys(permissions) 36 | .filter(permit => typeof permit !== 'function') 37 | .map(permission => { 38 | return ( 39 |
  • 40 |

    {permission.name}

    41 |

    {permission.current}

    42 |
  • 43 | ); 44 | }), 45 | )} 46 |
47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /__tests__/utility/validation/options.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { getCacheOptions } from '../../../src/utility/validation/options'; 18 | 19 | beforeEach(() => { 20 | jest.clearAllMocks(); 21 | }); 22 | 23 | describe('getCacheOptions', () => { 24 | test('getCacheOptions returns undefined', () => { 25 | expect.assertions(1); 26 | 27 | expect(getCacheOptions()).toBe(undefined); 28 | }); 29 | 30 | test('getCacheOptions returns only options that will be passed', () => { 31 | expect.assertions(2); 32 | 33 | const { 34 | ignoreSearch = false, 35 | ignoreMethod = false, 36 | ignoreVary = false, 37 | cacheName = 'my-cache', 38 | random = '', 39 | } = {}; 40 | 41 | const options = { 42 | ignoreSearch, 43 | ignoreMethod, 44 | ignoreVary, 45 | cacheName, 46 | random, 47 | }; 48 | 49 | expect(Object.keys(getCacheOptions(options))).toHaveLength(4); 50 | expect(getCacheOptions(options)).toMatchObject({ 51 | ignoreSearch: false, 52 | ignoreMethod: false, 53 | ignoreVary: false, 54 | cacheName: 'my-cache', 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/utility/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | /* eslint-disable import/prefer-default-export, no-console */ 18 | 19 | export { 20 | OneServiceWorkerError, 21 | errorFactory, 22 | notEnabled, 23 | notSupported, 24 | failedToInstall, 25 | failure, 26 | } from './errors'; 27 | export { 28 | TYPES, 29 | ENUMS, 30 | expectedType, 31 | expectedArrayOfType, 32 | enumerableException, 33 | unknownKey, 34 | unknownEventName, 35 | validateInput, 36 | getCacheOptions, 37 | } from './validation'; 38 | export { 39 | configure, 40 | getConfig, 41 | isDevelopment, 42 | isEventsEnabled, 43 | isNonStandardEnabled, 44 | isNavigationPreloadEnabled, 45 | isServiceWorker, 46 | isServiceWorkerSupported, 47 | isNotificationSupported, 48 | isPushSupported, 49 | isBackgroundSyncSupported, 50 | isCacheStorageSupported, 51 | isIndexedDBSupported, 52 | isPermissionsSupported, 53 | isOffline, 54 | } from './runtime'; 55 | export { 56 | on, 57 | once, 58 | off, 59 | emit, 60 | emitter, 61 | eventListeners, 62 | calls, 63 | createMiddleware, 64 | createMiddlewareContext, 65 | createMiddlewareFactory, 66 | } from './events'; 67 | -------------------------------------------------------------------------------- /src/middleware/navigationPreload.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { isNavigationPreloadEnabled, isServiceWorker, isOffline } from '../utility/runtime'; 18 | import { getRegistration } from '../core'; 19 | 20 | import { noop } from './utility'; 21 | 22 | export function createNavigationPreloadActivation() { 23 | if (isServiceWorker()) { 24 | return function navigationPreloadActivation(event) { 25 | if (isNavigationPreloadEnabled()) { 26 | event.waitUntil( 27 | getRegistration().then(registration => registration.navigationPreload.enable()), 28 | ); 29 | } 30 | }; 31 | } 32 | return noop; 33 | } 34 | 35 | export function createNavigationPreloadResponse(fallback = event => fetch(event.request.clone())) { 36 | if (isServiceWorker()) { 37 | // eslint-disable-next-line consistent-return 38 | return function navigationPreloadResponse(event) { 39 | if (isNavigationPreloadEnabled() && isOffline() === false) { 40 | if (event.request.mode === 'navigate') { 41 | event.respondWith(event.preloadResponse.then(preloaded => preloaded || fallback(event))); 42 | return true; 43 | } 44 | } 45 | }; 46 | } 47 | return noop; 48 | } 49 | -------------------------------------------------------------------------------- /src/middleware/shell.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { match, normalizeRequest, put } from '../cache'; 18 | import { isServiceWorker, isOffline } from '../utility/runtime'; 19 | 20 | import { isNavigateRequest, isResponseSuccessful, noop } from './utility'; 21 | 22 | export default function createAppShell({ route = '/index.html', cacheName = 'offline' } = {}) { 23 | if (isServiceWorker()) { 24 | const request = normalizeRequest(route); 25 | 26 | return function appShell(event) { 27 | if (isOffline()) { 28 | if (isNavigateRequest(event.request)) { 29 | event.respondWith( 30 | match(request.clone(), { 31 | cacheName, 32 | }), 33 | ); 34 | return true; 35 | } 36 | } else if (event.request.url === request.url) { 37 | event.waitUntil( 38 | fetch(request.clone()).then(response => { 39 | if (isResponseSuccessful(response)) { 40 | event.waitUntil( 41 | put(request.clone(), response.clone(), { 42 | cacheName, 43 | }), 44 | ); 45 | } 46 | }), 47 | ); 48 | } 49 | 50 | return false; 51 | }; 52 | } 53 | return noop; 54 | } 55 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/components/views/Standalone.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import { useStandalone } from '../../hooks'; 20 | 21 | export default function StandaloneView() { 22 | const { prompt } = useStandalone(); 23 | 24 | return ( 25 |
26 | {/*
27 |
28 |

Performance

29 | 30 |
31 | 32 |
33 | 34 | 35 | 36 |
37 |
*/} 38 |
39 |

Standalone Mode via App Install

40 |
41 | 42 | {prompt ? ( 43 |
44 | 47 |
48 | ) : ( 49 |
50 |

No prompt yet

51 |
52 | )} 53 |
54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/middleware/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { createMiddlewareFactory } from '../utility/events'; 18 | 19 | export const onInstall = createMiddlewareFactory(); 20 | export const onActivate = createMiddlewareFactory(); 21 | export const onMessage = createMiddlewareFactory(); 22 | export const onPush = createMiddlewareFactory(); 23 | export const onSync = createMiddlewareFactory(); 24 | export const onFetch = createMiddlewareFactory(); 25 | 26 | export { default as manifest } from './manifest'; 27 | export { 28 | createCacheBusting as cacheBusting, 29 | createCacheRouter as cacheRouter, 30 | createCacheStrategy as cacheStrategy, 31 | createPrecache as precache, 32 | } from './caching'; 33 | 34 | export { default as expiration } from './expiration'; 35 | export { default as appShell } from './shell'; 36 | 37 | export { 38 | createClientsClaim as clientsClaim, 39 | createSkipWaiting as skipWaiting, 40 | createEscapeHatchRoute as escapeHatchRoute, 41 | } from './lifecycle'; 42 | 43 | export { createMessageContext as messageContext, createMessenger as messenger } from './messenger'; 44 | export { 45 | createNavigationPreloadResponse as navigationPreloadResponse, 46 | createNavigationPreloadActivation as navigationPreloadActivation, 47 | } from './navigationPreload'; 48 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/middleware/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export { dispatchMiddleware, subscriptionMiddleware } from './push'; 18 | 19 | export function setHeadersMiddleware(request, response, next) { 20 | if (request.url.endsWith('.js')) 21 | response.set({ 22 | 'Cache-Scope': 'demo', 23 | }); 24 | else if (request.accepts('html')) 25 | response.set({ 26 | 'Service-Worker-Navigation-Preload': true, 27 | Vary: 'Service-Worker-Navigation-Preload', 28 | }); 29 | return next(); 30 | } 31 | 32 | export function sendHtmlMiddleware(request, response, next) { 33 | if (request.url.endsWith('.html')) { 34 | const unpkg = 'unpkg.com'; 35 | const statics = 'www.aexp-static.com cdaas.americanexpress.com'; 36 | 37 | response.setHeader( 38 | 'Content-Security-Policy', 39 | [ 40 | "default-src 'none';", 41 | `script-src 'self' ${unpkg} ${statics};`, 42 | `connect-src 'self' ${unpkg} ${statics};`, 43 | `img-src 'self' ${statics};`, 44 | `style-src 'self' ${statics};`, 45 | `font-src 'self' ${statics};`, 46 | "manifest-src 'self';", 47 | ].join(' '), 48 | ); 49 | 50 | response.type('html').sendFile(`${__dirname}/public/index.html`); 51 | } else next(); 52 | } 53 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "one-service-worker-demo", 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "license": "Apache-2.0", 7 | "contributors": [ 8 | "Andres Escobar (https://github.com/anescobar1991)", 9 | "James Singleton (https://github.com/JamesSingleton)", 10 | "Jamie King (https://github.com/10xLaCroixDrinker)", 11 | "Jonathan Adshead (https://github.com/JAdshead)", 12 | "Michael Tobia (https://github.com/Francois-Esquire)", 13 | "Michael Tomcal (https://github.com/mtomcal)", 14 | "Stephanie Coates (https://github.com/stephaniecoates)", 15 | "Nelly Kiboi (https://github.com/nellyk)", 16 | "Nickolas Oliver (https://github.com/PixnBits)" 17 | ], 18 | "scripts": { 19 | "postinstall": "npm run build", 20 | "clean": "rimraf dist", 21 | "prebuild": "npm run clean", 22 | "build": "rollup -c", 23 | "build:watch": "rollup -c -w", 24 | "start": "node dist/index.js", 25 | "start:watch": "nodemon --watch dist/index.js dist/index.js", 26 | "dev": "npm-run-all --parallel build:watch start:watch" 27 | }, 28 | "dependencies": { 29 | "body-parser": "^1.20.3", 30 | "express": "^4.20.0", 31 | "prop-types": "^15.7.2", 32 | "react": "^16.13.1", 33 | "react-dom": "^16.13.1", 34 | "web-push": "^3.4.3" 35 | }, 36 | "devDependencies": { 37 | "@rollup/plugin-alias": "^3.0.0", 38 | "@rollup/plugin-buble": "^0.21.0", 39 | "@rollup/plugin-node-resolve": "^8.0.1", 40 | "babel-preset-amex": "^3.3.0", 41 | "nodemon": "^2.0.2", 42 | "rimraf": "^3.0.1", 43 | "rollup": "^2.0.2", 44 | "rollup-plugin-cleanup": "^3.1.1", 45 | "rollup-plugin-copy": "^3.3.0", 46 | "rollup-plugin-re": "^1.0.7" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Types of Changes 16 | 17 | - [ ] Bug fix (non-breaking change which fixes an issue) 18 | - [ ] New feature (non-breaking change which adds functionality) 19 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 20 | - [ ] Documentation (adding or updating documentation) 21 | - [ ] Dependency update 22 | 23 | ## Checklist: 24 | 25 | 26 | - [ ] My change requires a change to the documentation and I have updated the documentation accordingly. 27 | - [ ] My changes are in sync with the code style of this project. 28 | - [ ] There aren't any other open Pull Requests for the same issue/update. 29 | - [ ] These changes should be applied to a maintenance branch. 30 | - [ ] This change requires cross browser checks. 31 | - [ ] This change impacts caching for client browsers. 32 | - [ ] This change adds additional environment variable requirements for one-service-worker users. 33 | - [ ] I have added the Apache 2.0 license header to any new files created. 34 | 35 | ## What is the Impact to Developers Using one-service-worker? 36 | 37 | -------------------------------------------------------------------------------- /src/middleware/manifest.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { isServiceWorker } from '../utility/runtime'; 18 | 19 | import { matchRequest } from './utility'; 20 | 21 | export const defaultEndpoint = '/manifest.webmanifest'; 22 | export const defaultStartUrl = '/index.html'; 23 | export const defaultManifest = { 24 | name: 'one_service_worker_app', 25 | short_name: 'app', 26 | start_url: defaultStartUrl, 27 | }; 28 | 29 | export const createRequest = ({ route = defaultEndpoint } = {}) => new Request(route); 30 | export const createResponse = ({ 31 | event = { request: createRequest() }, 32 | manifest = defaultManifest, 33 | } = {}) => 34 | new Response(JSON.stringify(manifest), { 35 | url: event.request.url, 36 | status: 200, 37 | headers: new Headers({ 38 | 'content-type': 'application/json', 39 | }), 40 | }); 41 | 42 | // TODO(DX): validate input, helpful log responses, standalone validator/cli 43 | export default function createManifestMiddleware(manifest, route) { 44 | if (!isServiceWorker()) return function noop() {}; 45 | 46 | const request = createRequest({ route }); 47 | 48 | return function manifestMiddleware(event) { 49 | if (matchRequest({ event, request })) { 50 | event.respondWith(createResponse({ event, manifest })); 51 | return true; 52 | } 53 | 54 | return false; 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/components/views/StorageQuota.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import { useInterval } from '../../hooks'; 20 | 21 | export default function StorageQuotaView() { 22 | const [storageQuota, setQuote] = React.useState(null); 23 | 24 | const updateQuota = React.useCallback(() => { 25 | navigator.storage.estimate().then(quote => setQuote(quote)); 26 | }); 27 | 28 | useInterval(updateQuota, 1000 * 2); 29 | 30 | return ( 31 |
32 |
33 | 34 |

Storage Quota

35 |
36 | 37 | {storageQuota ? ( 38 | 39 |

40 | Quota: 41 | {storageQuota.quota || 0} 42 |

43 |

44 | Usage: 45 | {storageQuota.usage / 1000000 || 0} 46 | mb 47 |

48 |

49 | Percentage: 50 | {(storageQuota.usage / storageQuota.quota) * 100} 51 | % 52 |

53 |
54 | ) : ( 55 |

Loading Storage Info

56 | )} 57 |
58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/utility/runtime/environment.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | /* eslint-disable no-restricted-globals */ 18 | import { getConfig } from './config'; 19 | 20 | // mode 21 | 22 | export function isDevelopment() { 23 | return getConfig().development; 24 | } 25 | 26 | // configuration 27 | 28 | export function isEventsEnabled() { 29 | return getConfig().events; 30 | } 31 | 32 | export function isNonStandardEnabled() { 33 | return getConfig().nonStandard; 34 | } 35 | 36 | export function isNavigationPreloadEnabled() { 37 | return getConfig().navigationPreload; 38 | } 39 | 40 | // environment checks 41 | 42 | export function isServiceWorker() { 43 | return 'ServiceWorkerGlobalScope' in self; 44 | } 45 | 46 | export function isServiceWorkerSupported() { 47 | return 'serviceWorker' in navigator; 48 | } 49 | 50 | export function isNotificationSupported() { 51 | return 'Notification' in self; 52 | } 53 | 54 | export function isPushSupported() { 55 | return 'PushManager' in self; 56 | } 57 | 58 | export function isBackgroundSyncSupported() { 59 | return 'SyncManager' in self; 60 | } 61 | 62 | export function isCacheStorageSupported() { 63 | return 'caches' in self; 64 | } 65 | 66 | export function isIndexedDBSupported() { 67 | return 'indexedDB' in self; 68 | } 69 | 70 | export function isPermissionsSupported() { 71 | return 'permissions' in navigator; 72 | } 73 | 74 | export function isOffline() { 75 | return navigator.onLine === false; 76 | } 77 | -------------------------------------------------------------------------------- /src/middleware/lifecycle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { isServiceWorker } from '../utility/runtime'; 18 | import { clear, normalizeRequest } from '../cache'; 19 | 20 | export const defaultEscapeRoute = '/__sw/__escape'; 21 | 22 | export function createEscapeHatchRoute({ 23 | route = defaultEscapeRoute, 24 | response = new Response(null, { 25 | status: 202, 26 | statusText: 'OK', 27 | }), 28 | clearCache = true, 29 | } = {}) { 30 | const { url } = normalizeRequest(route); 31 | // eslint-disable-next-line consistent-return 32 | return function escapeHatchRoute(event) { 33 | if (event.request.url === url) { 34 | event.respondWith(response.clone()); 35 | if (clearCache) event.waitUntil(clear()); 36 | // eslint-disable-next-line no-restricted-globals 37 | event.waitUntil(self.registration.unregister()); 38 | return true; 39 | } 40 | }; 41 | } 42 | 43 | export function createSkipWaiting() { 44 | return function skipWaiting(event) { 45 | if (isServiceWorker()) { 46 | // eslint-disable-next-line no-restricted-globals 47 | event.waitUntil(self.skipWaiting()); 48 | } 49 | }; 50 | } 51 | 52 | export function createClientsClaim() { 53 | // TODO: request to claim from open windows as an opt-in with client-side mechanism 54 | return function clientsClaim(event) { 55 | if (isServiceWorker()) { 56 | // eslint-disable-next-line no-restricted-globals 57 | event.waitUntil(self.clients.claim()); 58 | } 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/middleware/expiration.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { getMetaData, setMetaData, deleteMetaData, remove } from '../cache'; 18 | import { isCacheStorageSupported, isServiceWorker } from '../utility/runtime'; 19 | 20 | export const ONE_DAY_IN_SECONDS = 24 * 60 * 60 * 1000; 21 | export const ONE_WEEK_IN_SECONDS = 7 * ONE_DAY_IN_SECONDS; 22 | export const ONE_MONTH_IN_SECONDS = 4 * 7 * ONE_DAY_IN_SECONDS; 23 | export const EXPIRATION_KEY = 'expires'; 24 | 25 | export default function createExpirationMiddleware({ maxAge = ONE_MONTH_IN_SECONDS } = {}) { 26 | if (!isCacheStorageSupported() || !isServiceWorker()) return function noop() {}; 27 | 28 | return function expirationMiddleware(event, context) { 29 | const { request } = context.get(); 30 | 31 | if (request && request instanceof Request) { 32 | const { url } = event.request; 33 | const now = Date.now(); 34 | const expired = now - maxAge; 35 | 36 | event.waitUntil( 37 | getMetaData({ 38 | url, 39 | }).then(meta => { 40 | const expiring = meta[EXPIRATION_KEY] || 0; 41 | 42 | if (EXPIRATION_KEY in meta && expiring < expired) { 43 | return deleteMetaData({ url }).then(() => remove(request.clone())); 44 | } 45 | 46 | return setMetaData({ 47 | url, 48 | metadata: { 49 | ...meta, 50 | [EXPIRATION_KEY]: now + maxAge, 51 | }, 52 | }); 53 | }), 54 | ); 55 | } 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /__tests__/core/subscription.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import webPush from 'web-push'; 18 | 19 | import { 20 | applicationServerKeyToBase64Array, 21 | urlBase64ToUint8Array, 22 | } from '../../src/core/subscription'; 23 | 24 | describe('subscription', () => { 25 | const VAPIDKeys = webPush.generateVAPIDKeys(); 26 | const applicationServerKey = VAPIDKeys.publicKey; 27 | 28 | describe('applicationServerKeyToBase64Array', () => { 29 | const vapidPublicKeySample = 30 | 'BEfNAnDvbFoCE1c2ntkuu2_L6KjQr9WbD3KYkq8AIPkdBaFguYx6xgL845CjI8F4PbejwxJ-fHxfrUcLMYlCQZk'; 31 | 32 | test('transforms vapid public key to an array of char codes', () => { 33 | const sample = applicationServerKeyToBase64Array(vapidPublicKeySample); 34 | // jest/no-large-snapshots... 35 | expect(JSON.stringify(sample)).toMatchSnapshot(); 36 | expect(sample).toMatchObject(expect.arrayContaining([expect.any(Number)])); 37 | }); 38 | }); 39 | 40 | test('urlBase64ToUint8Array returns Uint8Array', () => { 41 | expect.assertions(2); 42 | 43 | const typedArray = urlBase64ToUint8Array(); 44 | expect(typedArray).toEqual(expect.any(Uint8Array)); 45 | expect(typedArray.length).toEqual(0); 46 | }); 47 | 48 | test('urlBase64ToUint8Array returns Uint8Array of applicationServerKey', () => { 49 | expect.assertions(1); 50 | 51 | const typedArray = urlBase64ToUint8Array(applicationServerKey); 52 | // include padding (=...) 53 | expect(typedArray.length).toEqual(applicationServerKey.length + 1); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/components/Client.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import RegistrationView from './views/Registration'; 20 | import CacheView from './views/Cache'; 21 | import PerformanceView from './views/Performance'; 22 | import StandaloneView from './views/Standalone'; 23 | // import PermissionsView from './views/Permissions'; 24 | // import SyncView from './views/Sync'; 25 | import WebPushView from './views/WebPush'; 26 | 27 | export default function Client() { 28 | return ( 29 |
30 |
31 |

One Service Worker Panel

32 | one-amex 37 |
38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 |
49 | {/* */} 50 | 51 | {/* */} 52 |
53 | 54 |
55 |
56 | made with 57 | 58 | 💙 59 | 60 | at American Express 61 |
62 |
63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /__tests__/core/__snapshots__/service-worker.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`getNotifications getNotifications calls registration.getNotifications 1`] = ` 4 | Array [ 5 | Notification { 6 | "close": [Function], 7 | "title": "new notification", 8 | "vibrate": Array [ 9 | 100, 10 | 200, 11 | ], 12 | }, 13 | ] 14 | `; 15 | 16 | exports[`getSubscription getSubscription calls registration.pushManager.getSubscription 1`] = ` 17 | Object { 18 | "endpoint": "test.com/12345", 19 | "options": Object { 20 | "applicationServerKey": Uint8Array [], 21 | "userVisibleOnly": true, 22 | }, 23 | } 24 | `; 25 | 26 | exports[`subscribe subscribe calls registration.pushManager.subscribe 1`] = ` 27 | Object { 28 | "endpoint": "test.com/12345", 29 | "options": Object { 30 | "applicationServerKey": Uint8Array [], 31 | "userVisibleOnly": true, 32 | }, 33 | } 34 | `; 35 | 36 | exports[`subscribe subscribe does not transforms applicationServerKey if in the right format 1`] = ` 37 | Object { 38 | "endpoint": "test.com/12345", 39 | "options": Object { 40 | "applicationServerKey": Uint8Array [ 41 | 97, 42 | 112, 43 | 112, 44 | 108, 45 | 105, 46 | 99, 47 | 97, 48 | 116, 49 | 105, 50 | 111, 51 | 110, 52 | 83, 53 | 101, 54 | 114, 55 | 118, 56 | 101, 57 | 114, 58 | 75, 59 | 101, 60 | 121, 61 | ], 62 | "userVisibleOnly": true, 63 | }, 64 | } 65 | `; 66 | 67 | exports[`subscribe subscribe transforms applicationServerKey to urlBase64ToUint8Array if its a string 1`] = ` 68 | Object { 69 | "endpoint": "test.com/12345", 70 | "options": Object { 71 | "applicationServerKey": Uint8Array [ 72 | 97, 73 | 112, 74 | 112, 75 | 108, 76 | 105, 77 | 99, 78 | 97, 79 | 116, 80 | 105, 81 | 111, 82 | 110, 83 | 83, 84 | 101, 85 | 114, 86 | 118, 87 | 101, 88 | 114, 89 | 75, 90 | 101, 91 | 121, 92 | ], 93 | "userVisibleOnly": true, 94 | }, 95 | } 96 | `; 97 | 98 | exports[`unsubscribe unsubscribe calls subscription.unsubscribe 1`] = `true`; 99 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/components/views/Connection.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | export default function ConnectionView() { 20 | const [connection, setConnection] = React.useState(() => 21 | 'connection' in navigator ? navigator.connection : null, 22 | ); 23 | 24 | React.useEffect(() => { 25 | if (connection) 26 | navigator.connection.addEventListener('change', () => setConnection(navigator.connection)); 27 | }, []); 28 | 29 | return ( 30 |
31 |
32 | 33 |

Connection

34 |
35 | 36 | {connection ? ( 37 |
38 |

39 | Effective Type 40 | {connection.effectiveType} 41 |

42 |

43 | RTT 44 | {connection.rtt} 45 |

46 | 47 |
48 |

49 | Down Link 50 |

51 | {connection.downlink} 52 | megabits per second 53 |
54 | 55 |

56 | Save Data 57 | {connection.saveData} 58 |

59 |
60 | ) : ( 61 |
loading
62 | )} 63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/utility/events/middleware.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { expectedType } from '../validation'; 18 | 19 | export function checkMiddlewareStack(middlewareStack = []) { 20 | middlewareStack.forEach(middleware => { 21 | if (typeof middleware !== 'function') { 22 | throw expectedType({ 23 | key: 'middleware', 24 | type: 'function', 25 | }); 26 | } 27 | }); 28 | return middlewareStack; 29 | } 30 | 31 | export function createMiddlewareContext(defaultContext) { 32 | const ctx = defaultContext || {}; 33 | const context = { 34 | get(key) { 35 | if (key) return ctx[key]; 36 | return ctx; 37 | }, 38 | set(key, value) { 39 | ctx[key] = value; 40 | return ctx; 41 | }, 42 | }; 43 | return context; 44 | } 45 | 46 | export function createMiddleware(middlewares = [], getInitialContext = () => undefined) { 47 | const middlewareStack = checkMiddlewareStack([].concat(middlewares)); 48 | 49 | return function middlewareHandler(event) { 50 | const stack = [...middlewareStack]; 51 | 52 | const context = createMiddlewareContext(getInitialContext(event)); 53 | 54 | let result = false; 55 | while (stack.length > 0) { 56 | const middleware = stack.shift(); 57 | result = middleware(event, context); 58 | if (result) break; 59 | } 60 | 61 | return result; 62 | }; 63 | } 64 | 65 | export function createMiddlewareFactory(defaultMiddleware = [], getInitialContext) { 66 | return function middlewareCreator(middleware = []) { 67 | return createMiddleware([].concat(defaultMiddleware, middleware), getInitialContext); 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/useNotificationComposer.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | const initialNotificationState = { 20 | title: '', 21 | body: '', 22 | icon: '', 23 | image: '', 24 | badge: '', 25 | sound: '', 26 | dir: 'ltr', 27 | lang: 'en-US', 28 | renotify: false, 29 | requireInteraction: false, 30 | vibrate: [200], 31 | actions: [], 32 | }; 33 | 34 | function notificationReducer(state = {}, action) { 35 | switch (action.type) { 36 | case 'reset': 37 | return initialNotificationState; 38 | case 'add-vibrate-pattern': 39 | return { 40 | ...state, 41 | vibrate: state.vibrate.concat(Number.parseInt(action.vibration, 10)), 42 | }; 43 | case 'remove-vibrate-pattern': 44 | return { 45 | ...state, 46 | vibrate: state.vibrate.filter((_, index) => index !== action.index), 47 | }; 48 | default: { 49 | if (action.type in state) { 50 | return { 51 | ...state, 52 | [action.type]: action.value, 53 | }; 54 | } 55 | 56 | return state; 57 | } 58 | } 59 | } 60 | 61 | export default function NotificationComposer(defaults) { 62 | const [vibration, setVibration] = React.useState('100'); 63 | const [state, dispatch] = React.useReducer(notificationReducer, { 64 | ...initialNotificationState, 65 | ...defaults, 66 | }); 67 | 68 | return { 69 | state, 70 | dispatch, 71 | get vibration() { 72 | return vibration; 73 | }, 74 | set vibration(vibrate) { 75 | setVibration(vibrate); 76 | return vibrate; 77 | }, 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/useStorage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | export default function useStorage({ session = false, strict = true, onStorage } = {}) { 20 | const store = React.createRef(() => { 21 | if (session) store.current = sessionStorage; 22 | else store.current = localStorage; 23 | }); 24 | 25 | React.useEffect(() => { 26 | if (session) store.current = sessionStorage; 27 | else store.current = localStorage; 28 | }, [session]); 29 | 30 | const api = { 31 | store, 32 | get(id) { 33 | return store.current.getItem(id); 34 | }, 35 | set(id, value) { 36 | return store.current.setItem(id, value); 37 | }, 38 | delete(id) { 39 | return store.current.removeItem(id); 40 | }, 41 | clear() { 42 | return store.current.clear(); 43 | }, 44 | key(index) { 45 | return store.current.key(index); 46 | }, 47 | get map() { 48 | return new Map(this.keys.map(key => localStorage.getItem(key))); 49 | }, 50 | get keys() { 51 | return new Array(store.current.length).map((_, i) => localStorage.key(i)); 52 | }, 53 | get values() { 54 | return new Array(store.current.length).map((_, i) => 55 | localStorage.getItem(localStorage.key(i)), 56 | ); 57 | }, 58 | get length() { 59 | return store.current.length; 60 | }, 61 | }; 62 | 63 | React.useEffect(() => { 64 | if (typeof onStorage === 'function') store.current.onstorage = onStorage; 65 | else store.current.onstorage = null; 66 | }, [onStorage]); 67 | 68 | React.useEffect( 69 | // eslint-disable-next-line unicorn/consistent-function-scoping 70 | () => () => { 71 | if (session && strict) api.clear(); 72 | }, 73 | [session, strict], 74 | ); 75 | 76 | return api; 77 | } 78 | -------------------------------------------------------------------------------- /__tests__/helpers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | // eslint-disable-next-line import/prefer-default-export 18 | export function createFetchEvent(request = new Request('/index.html')) { 19 | const event = new global.FetchEvent('fetch', { 20 | request, 21 | }); 22 | ['waitUntil', 'respondWith'].forEach(method => { 23 | event[method] = event[method].bind(event); 24 | jest.spyOn(event, method); 25 | }); 26 | return event; 27 | } 28 | 29 | export function createEvent(type, parameters) { 30 | const bindTo = ['waitUntil']; 31 | let event; 32 | switch (type) { 33 | default: 34 | event = new ExtendableEvent(type, parameters); 35 | break; 36 | case 'sync': 37 | // eslint-disable-next-line no-undef 38 | event = new SyncEvent(type, parameters); 39 | break; 40 | case 'push': 41 | // eslint-disable-next-line no-undef 42 | event = new PushEvent(type, parameters); 43 | break; 44 | case 'message': 45 | event = new MessageEvent(type, parameters); 46 | event.clientId = 'asfjkbakfhbsjdfg'; 47 | break; 48 | case 'fetch': 49 | bindTo.push('respondWith'); 50 | event = new FetchEvent( 51 | type, 52 | parameters || { 53 | request: new Request('/index.html'), 54 | }, 55 | ); 56 | break; 57 | } 58 | bindTo.forEach(method => { 59 | event[method] = event[method].bind(event); 60 | jest.spyOn(event, method); 61 | }); 62 | return event; 63 | } 64 | 65 | export function printExports(exported, tighten = false) { 66 | return Object.entries(exported) 67 | .map(([name, value]) => [name, typeof value].join(' :: ')) 68 | .join(tighten ? ' ' : '\n'); 69 | } 70 | 71 | export function waitFor(asyncTarget) { 72 | return Promise.all(asyncTarget.mock.calls.reduce((array, next) => array.concat(next), [])); 73 | } 74 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/useServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import { 20 | getRegistration, 21 | register, 22 | unregister, 23 | escapeHatch, 24 | on, 25 | } from '@americanexpress/one-service-worker'; 26 | 27 | export default function useServiceWorker({ 28 | active = false, 29 | disabled = false, 30 | activateEscapeHatch = false, 31 | url = '/service-worker.js', 32 | scope = '/', 33 | updateViaCache = 'none', 34 | } = {}) { 35 | const [registration, setRegistration] = React.useState(null); 36 | const [error, setError] = React.useState(null); 37 | const [mounted, setMounted] = React.useState(false); 38 | 39 | React.useEffect(() => { 40 | on('register', setRegistration); 41 | getRegistration().then(setRegistration); 42 | }, []); 43 | 44 | React.useEffect(() => { 45 | if (!mounted) { 46 | if (disabled) { 47 | unregister() 48 | .catch(setError) 49 | .then(() => setMounted(true)); 50 | } else if (activateEscapeHatch) { 51 | escapeHatch() 52 | .catch(setError) 53 | .then(() => setMounted(true)); 54 | } else if (active && registration === null) { 55 | register(url, { 56 | scope, 57 | updateViaCache, 58 | }) 59 | .catch(setError) 60 | .then( 61 | successfulRegistration => 62 | successfulRegistration && setRegistration(successfulRegistration), 63 | ) 64 | .then(() => setMounted(true)); 65 | } else setMounted(true); 66 | } 67 | }, [registration, active, disabled, activateEscapeHatch, mounted]); 68 | 69 | React.useEffect(() => { 70 | on('register', currentRegistration => setRegistration(currentRegistration)); 71 | }, []); 72 | 73 | return { registration, error }; 74 | } 75 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/ducks/permissions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | export const permissionQueryNames = [ 18 | ['geolocation'], 19 | ['notifications'], 20 | ['push', { userVisibleOnly: true }], 21 | ['midi', { sysex: true }], 22 | ['camera'], 23 | ['microphone'], 24 | ['background-sync'], 25 | ['accelerometer'], 26 | ['gyroscope'], 27 | ['magnetometer'], 28 | ]; 29 | 30 | export const RESET_PERMISSIONS_KEY = '@@permissions/reset'; 31 | export const DEFAULT_STATE_KEY = 'default'; 32 | export const GRANTED_STATE_KEY = 'granted'; 33 | export const DENIED_STATE_KEY = 'denied'; 34 | 35 | export const permissionsInitialState = permissionQueryNames.reduce( 36 | (initialState, [name]) => ({ 37 | ...initialState, 38 | [name]: 'default', 39 | }), 40 | {}, 41 | ); 42 | 43 | export function permissionsReducer(state = permissionsInitialState, action) { 44 | switch (action.type) { 45 | case RESET_PERMISSIONS_KEY: 46 | return permissionsInitialState; 47 | case DEFAULT_STATE_KEY: 48 | return { ...state, [action.name]: DEFAULT_STATE_KEY }; 49 | case GRANTED_STATE_KEY: 50 | return { ...state, [action.name]: GRANTED_STATE_KEY }; 51 | case DENIED_STATE_KEY: 52 | return { ...state, [action.name]: DENIED_STATE_KEY }; 53 | default: 54 | return state; 55 | } 56 | } 57 | 58 | export function isDefault(featureName) { 59 | return state => state[featureName] === DEFAULT_STATE_KEY; 60 | } 61 | export function isGranted(featureName) { 62 | return state => state[featureName] === GRANTED_STATE_KEY; 63 | } 64 | export function isDenied(featureName) { 65 | return state => state[featureName] === DENIED_STATE_KEY; 66 | } 67 | 68 | export function setPermission(featureName, currentState) { 69 | return dispatch => dispatch({ type: currentState, name: featureName }); 70 | } 71 | export function resetPermissions() { 72 | return dispatch => 73 | dispatch({ 74 | type: RESET_PERMISSIONS_KEY, 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /scripts/stats.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | const fs = require('fs'); 18 | const path = require('path'); 19 | 20 | // eslint-disable-next-line import/no-extraneous-dependencies 21 | const chalk = require('chalk'); 22 | 23 | function filterExisting(baseDir = process.cwd()) { 24 | return function filterExistingOnly(filename) { 25 | return fs.existsSync(path.resolve(baseDir, filename)); 26 | }; 27 | } 28 | 29 | function mapFileSize(baseDir = process.cwd(), divisor = 1000) { 30 | return function getFileSizeFromBytes(filename) { 31 | const { size: byteSize } = fs.statSync(path.resolve(baseDir, filename)); 32 | return byteSize / divisor; 33 | }; 34 | } 35 | 36 | const clamp = (number, min, max) => Math.min(Math.max(number, min), max); 37 | 38 | const printFilesAndSize = files => { 39 | const sizes = files.filter(filterExisting()).map(mapFileSize()); 40 | return sizes 41 | .map( 42 | (size, i) => 43 | chalk`\t{white.bold |----- ${files[i]}}\n\t|\n\t| ${chalk 44 | .bgRgb(0, 0, 0) 45 | // start at red (0), stop at greenish aqua (180) 46 | .hsl(clamp(90 - size + 15, 0, 180), 100, 50) 47 | .bold(`\t${size}\t`)} {green kb}`, 48 | ) 49 | .join('\n\t|\n'); 50 | }; 51 | 52 | module.exports = function printOutFiles(files) { 53 | const flatFiles = files.reduce((array, nextPath) => { 54 | if (fs.existsSync(nextPath)) { 55 | const stats = fs.statSync(nextPath); 56 | if (stats.isDirectory()) { 57 | const dir = fs.readdirSync(nextPath).map(baseFilePath => path.join(nextPath, baseFilePath)); 58 | dir.forEach(pathName => array.push(pathName)); 59 | } else array.push(nextPath); 60 | } 61 | return array; 62 | }, []); 63 | 64 | const output = [ 65 | chalk`\n{bgCyan {black.bold Files} } (${flatFiles.length} total) 66 | 67 | {bgCyan {bold.black /}\t}| (files to be published) 68 | 69 | \t|----------------------- 70 | \t| 71 | ${printFilesAndSize(flatFiles)} 72 | \t| 73 | \t------------------------`, 74 | ]; 75 | 76 | process.stdout.write(`${output.join()}\n`); 77 | }; 78 | -------------------------------------------------------------------------------- /docs/api/Errors.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | [👈 Go to `README`](../../README.md) 4 | 5 | [👆 Back to `API`](./README.md) 6 | 7 | 8 | ## 📖 Table of Contents 9 | 10 | - [`OneServiceWorkerError`](#-oneserviceworkererror) 11 | - [`errorFactory`](#-errorfactory) 12 | 13 | ### `OneServiceWorkerError` 14 | 15 | An error class that extends `Error` for the library. 16 | 17 | ```js 18 | import { OneServiceWorkerError } from '@americanexpress/one-service-worker'; 19 | 20 | const message = 'something went wrong..'; 21 | const existingError = new Error( 22 | '[native]: DOM error or native API error occurred', 23 | ); 24 | const errorInstance = new OneServiceWorkerError(message, existingError); 25 | ``` 26 | 27 | **Parameters** 28 | 29 | | Name | Type | Required | Description | 30 | | --- | --- | --- | --- | 31 | | `message` | `String` | `false` | A message for the error | 32 | | `error` | `Error` | `false` | An existing error to reference | 33 | 34 |   35 | 36 | ### `errorFactory` 37 | 38 | A factory function to construct an error instance and handle the created error. 39 | It can be useful to build synchronous thrown exceptions and asynchronous promise rejections 40 | based on your use case. 41 | 42 | ```js 43 | import { 44 | OneServiceWorkerError, 45 | errorFactory, 46 | } from '@americanexpress/one-service-worker'; 47 | 48 | const exception = (function createExceptionFn() { 49 | let promiseMode = false; 50 | 51 | // errorFactory takes two functions 52 | const exceptionFn = errorFactory( 53 | // the first constructs the error for a particular exception 54 | () => new OneServiceWorkerError('doh!'), 55 | // the second decides to reject with a promise or throw the constructed error 56 | error => { 57 | if (promiseMode) { 58 | return Promise.reject(error); 59 | } 60 | 61 | throw error; 62 | }, 63 | ); 64 | 65 | return { 66 | reject() { 67 | promiseMode = true; 68 | return exceptionFn(); 69 | }, 70 | throw() { 71 | promiseMode = false; 72 | return exceptionFn(); 73 | }, 74 | }; 75 | })(); 76 | 77 | const onError = error => console.log('our error', error); 78 | 79 | try { 80 | exception.throw(); 81 | } catch (error) { 82 | onError(error); 83 | } 84 | 85 | exception.reject().catch(onError); 86 | ``` 87 | 88 | **Parameters** 89 | 90 | | Name | Type | Required | Description | 91 | | --- | --- | --- | --- | 92 | | `constructorFn` | `Function` | `false` | Name of an event | 93 | | `handlerFn` | `Function` | `false` | Handler for an event | 94 | 95 | **Returns** 96 | 97 | `Function` 98 | 99 |   100 | 101 | [☝️ Return To Top](#-📖-table-of-contents) 102 | -------------------------------------------------------------------------------- /__tests__/jest.setup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | require('@babel/polyfill'); 18 | 19 | (function setup({ target, useEvents, useNavigationPreload, useNonStandard }) { 20 | if (useEvents) process.env.ONE_SW_USE_EVENTS = true; 21 | if (useNonStandard) process.env.ONE_SW_USE_NON_STANDARD = true; 22 | if (useNavigationPreload) process.env.ONE_SW_USE_NAVIGATION_PRELOAD = true; 23 | 24 | jest.resetModules(); 25 | 26 | const spies = [[global, ['fetch']]]; 27 | 28 | if (target === 'client') { 29 | spies.push([global.window, ['addEventListener', 'removeEventListener']]); 30 | 31 | if (global.navigator.serviceWorker) { 32 | spies.push([ 33 | global.navigator.serviceWorker, 34 | ['register', 'getRegistration', 'getRegistrations'], 35 | ]); 36 | } 37 | } else { 38 | spies.push( 39 | [global.self, ['skipWaiting', 'addEventListener']], 40 | [global.clients, ['claim', 'get', 'matchAll', 'openWindow']], 41 | [global.registration, ['unregister', 'getNotifications', 'showNotification']], 42 | [global.registration.navigationPreload, ['enable', 'disable']], 43 | [global.registration.pushManager, ['subscribe', 'getSubscription']], 44 | [global.registration.pushManager.subscription, ['unsubscribe']], 45 | [global.registration.sync, ['register', 'getTags']], 46 | ); 47 | } 48 | 49 | spies.forEach(combo => { 50 | const [object, properties] = combo; 51 | 52 | if (object) { 53 | properties.forEach(property => { 54 | switch (property) { 55 | default: 56 | jest.spyOn(object, property); 57 | break; 58 | case 'getSubscription': 59 | case 'subscribe': 60 | object[property] = jest.fn( 61 | ( 62 | options = { 63 | userVisibleOnly: true, 64 | applicationServerKey: new Uint8Array(0), 65 | }, 66 | ) => 67 | Promise.resolve( 68 | Object.assign(global.registration.pushManager.subscription, { options }), 69 | ), 70 | ); 71 | break; 72 | } 73 | }); 74 | } 75 | }); 76 | })(global.options); 77 | -------------------------------------------------------------------------------- /__tests__/utility/errors/errors.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { 18 | OneServiceWorkerError, 19 | errorFactory, 20 | notEnabled, 21 | notSupported, 22 | failedToInstall, 23 | failure, 24 | } from '../../../src/utility/errors/errors'; 25 | 26 | describe('OneServiceWorkerError', () => { 27 | expect.assertions(1); 28 | 29 | test('returns error', () => { 30 | expect(new OneServiceWorkerError()).toBeInstanceOf(Error); 31 | }); 32 | 33 | test('returns error with message', () => { 34 | expect.assertions(2); 35 | 36 | const error = new OneServiceWorkerError('broken'); 37 | expect(error).toBeInstanceOf(Error); 38 | expect(error.message).toEqual('broken'); 39 | }); 40 | 41 | test('adds error stack if error is passed to constructor', () => { 42 | expect.assertions(3); 43 | 44 | const typeError = new TypeError(); 45 | const error = new OneServiceWorkerError('broken', typeError); 46 | expect(error).toBeInstanceOf(Error); 47 | expect(error.message).toEqual('broken'); 48 | expect(error.stack).toBeDefined(); 49 | }); 50 | }); 51 | 52 | describe('errorFactory', () => { 53 | test('returns null on invoction', () => { 54 | expect.assertions(1); 55 | 56 | const exception = errorFactory(() => null); 57 | expect(exception()).toBe(null); 58 | }); 59 | 60 | test('errorFactory throws error on invocation', () => { 61 | expect.assertions(1); 62 | 63 | const exception = errorFactory( 64 | () => notEnabled('Events'), 65 | error => { 66 | throw error; 67 | }, 68 | ); 69 | expect(() => exception()).toThrow(); 70 | }); 71 | 72 | test('errorFactory rejects error error on invocation', () => { 73 | expect.assertions(1); 74 | 75 | const exception = errorFactory( 76 | () => notSupported('Sync'), 77 | error => Promise.reject(error), 78 | ); 79 | expect(exception()).rejects.toBeInstanceOf(OneServiceWorkerError); 80 | }); 81 | }); 82 | 83 | describe('message generators', () => { 84 | [notEnabled, notSupported, failedToInstall, failure].forEach(msgFn => { 85 | test(`${msgFn.name} - returns string without arguments`, () => { 86 | expect.assertions(1); 87 | 88 | expect(msgFn()).toEqual(expect.any(OneServiceWorkerError)); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/usePermissions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import { 20 | isPermissionsSupported, 21 | isNotificationSupported, 22 | } from '@americanexpress/one-service-worker'; 23 | 24 | import { permissionsReducer, permissionQueryNames, DEFAULT_STATE_KEY } from './ducks/permissions'; 25 | 26 | export function requestPermission(name) { 27 | switch (name) { 28 | default: 29 | break; 30 | case 'notifications': 31 | case 'push': 32 | if (isNotificationSupported()) { 33 | return Notification.requestPermission(); 34 | } 35 | } 36 | return Promise.resolve(); 37 | } 38 | 39 | export function permissionsEffect(permissionsToUse = permissionQueryNames, dispatch = () => null) { 40 | const cleanup = []; 41 | 42 | return () => { 43 | if (isPermissionsSupported()) { 44 | permissionsToUse.forEach(permit => { 45 | const [name, descriptor = {}] = permit; 46 | 47 | navigator.permissions 48 | .query({ 49 | name, 50 | ...descriptor, 51 | }) 52 | .then(permission => { 53 | dispatch({ 54 | type: permission.state, 55 | name, 56 | }); 57 | 58 | const update = () => { 59 | dispatch({ 60 | type: permission.state, 61 | name, 62 | }); 63 | }; 64 | permission.addEventListener('change', update); 65 | cleanup.push(() => permission.removeEventListener('change', update)); 66 | }) 67 | // eslint-disable-next-line no-console 68 | .catch(error => console.warn(name, error)); 69 | }); 70 | } 71 | 72 | return () => { 73 | cleanup.forEach(fn => fn()); 74 | }; 75 | }; 76 | } 77 | 78 | export default function usePermissions(listOfPermissions = permissionQueryNames) { 79 | const [permissions, dispatch] = React.useReducer( 80 | permissionsReducer, 81 | listOfPermissions 82 | .map(([name]) => ({ [name]: DEFAULT_STATE_KEY })) 83 | .reduce((state, next) => ({ ...state, ...next }), {}), 84 | ); 85 | 86 | React.useLayoutEffect(permissionsEffect(listOfPermissions, dispatch), []); 87 | 88 | return { 89 | ...permissions, 90 | requestPermission, 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/useStandalone.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import { isServiceWorkerSupported, emit } from '@americanexpress/one-service-worker'; 20 | 21 | export function isStandalone() { 22 | return ( 23 | // safari: 24 | window.navigator.standalone === true || 25 | // other browsers: 26 | window.matchMedia('(display-mode: standalone)').matches 27 | ); 28 | } 29 | 30 | export function createStandaloneHandlers(setPrompt, setUserChoice) { 31 | function beforeinstallprompt(event) { 32 | event.preventDefault(); 33 | 34 | emit('beforeinstallprompt', event); 35 | 36 | function installApp() { 37 | return event.prompt().then(() => 38 | event.userChoice.then(choiceResult => { 39 | if (choiceResult.outcome === 'accepted') setPrompt(null); 40 | setUserChoice(choiceResult); 41 | return choiceResult; 42 | }), 43 | ); 44 | } 45 | 46 | setPrompt(() => installApp); 47 | } 48 | 49 | function appinstalled(event) { 50 | emit('appinstalled', event); 51 | 52 | window.removeEventListener('appinstalled', appinstalled); 53 | window.removeEventListener('beforeinstallprompt', beforeinstallprompt); 54 | } 55 | 56 | function bind() { 57 | window.addEventListener('appinstalled', appinstalled); 58 | window.addEventListener('beforeinstallprompt', beforeinstallprompt); 59 | return appinstalled; 60 | } 61 | 62 | return { 63 | bind, 64 | appinstalled, 65 | beforeinstallprompt, 66 | }; 67 | } 68 | 69 | // https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Add_to_home_screen 70 | export default function useStandalone() { 71 | const [prompt, setPrompt] = React.useState(null); 72 | const [userChoice, setUserChoice] = React.useState(null); 73 | const [standalone] = React.useState(() => { 74 | if (isServiceWorkerSupported()) { 75 | return isStandalone(); 76 | } 77 | return false; 78 | }); 79 | 80 | // eslint-disable-next-line consistent-return 81 | React.useEffect(() => { 82 | const handles = createStandaloneHandlers(setPrompt, setUserChoice); 83 | if (!standalone) { 84 | handles.bind(); 85 | } 86 | return handles.appinstalled; 87 | }, [standalone]); 88 | 89 | return { 90 | standalone, 91 | userChoice, 92 | prompt, 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/client/sw.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | /* eslint-disable no-restricted-globals */ 18 | 19 | import { 20 | configure, 21 | on, 22 | postMessage, 23 | showNotification, 24 | onMessage, 25 | skipWaiting, 26 | clientsClaim, 27 | cacheRouter, 28 | escapeHatchRoute, 29 | // navigationPreload, 30 | // navigationPreloadResponse, 31 | // manifest, 32 | // messageContext, 33 | // messenger, 34 | precache, 35 | appShell, 36 | expiration, 37 | } from '@americanexpress/one-service-worker'; 38 | 39 | import config from './config'; 40 | 41 | configure(config); 42 | 43 | self.addEventListener( 44 | 'message', 45 | onMessage([ 46 | event => { 47 | console.log('sw - on message data (service worker):', event.data); 48 | }, 49 | // eslint-disable-next-line consistent-return 50 | event => { 51 | if (event.data.id === 'ping') { 52 | console.log('sw - correlation:', event.data.correlation); 53 | event.waitUntil( 54 | postMessage( 55 | { 56 | id: 'pong', 57 | correlation: event.data.correlation, 58 | }, 59 | event.source, 60 | ), 61 | ); 62 | return true; 63 | } 64 | }, 65 | ]), 66 | ); 67 | 68 | on('install', [ 69 | skipWaiting(), 70 | precache(['/images/one.png', '/audio/gong.m4a'], { 71 | cacheName: 'pre-cache', 72 | }), 73 | ]); 74 | 75 | on('activate', [clientsClaim()]); 76 | 77 | on('fetch', [ 78 | escapeHatchRoute(), 79 | appShell({ 80 | route: '/index.html', 81 | }), 82 | cacheRouter({ 83 | match: event => /@americanexpress/.test(event.request.url), 84 | }), 85 | cacheRouter({ 86 | cacheName: 'unpkg-cache', 87 | match: event => /unpkg\.com/.test(event.request.url), 88 | }), 89 | cacheRouter({ 90 | cacheName: 'example-scripts-cache', 91 | match: event => /(scripts.*\.js$)/.test(event.request.url), 92 | }), 93 | cacheRouter({ 94 | cacheName: 'example-static-cache', 95 | match: event => /(static|dls).*\.(js|css|svg|png|ttf|woff)$/.test(event.request.url), 96 | }), 97 | expiration(), 98 | ]); 99 | 100 | on('push', [ 101 | event => { 102 | const { title, options } = event.data.json(); 103 | event.waitUntil(showNotification(title, options)); 104 | return true; 105 | }, 106 | ]); 107 | -------------------------------------------------------------------------------- /docs/guides/Patterns.md: -------------------------------------------------------------------------------- 1 | # Patterns 2 | 3 | [👈 Go to `README`](../../README.md) 4 | 5 | [👆 Back to `Guides`](./README.md) 6 | 7 | 8 | ## Scope based service worker scripts 9 | 10 | For some applications, having a mix of general and specialized service workers 11 | could be desired for organizing our origin. 12 | Depending on scope (`self.registration.scope`), we can configure our worker and 13 | utilize `importScripts` to load sets of functionality based on a given scope. 14 | 15 | **`/sw.js`** 16 | 17 | ```js 18 | try { 19 | switch (self.registration.scope.replace(location.origin, '')) { 20 | case '/': 21 | importScripts('/sw/lifecycle.js'); 22 | importScripts('/sw/push.js'); 23 | break; 24 | case '/statics': 25 | importScripts('/sw/lifecycle.js'); 26 | importScripts('/sw/sync.js'); 27 | importScripts('/sw/caching.js'); 28 | break; 29 | case '/data': 30 | importScripts('/sw/lifecycle.js'); 31 | importScripts('/sw/sync.js'); 32 | importScripts('/sw/data.js'); 33 | break; 34 | case '/payments': 35 | importScripts('/sw/lifecycle.js'); 36 | importScripts('/sw/payment.js'); 37 | break; 38 | default: 39 | // any rogue or accidental installations should be immediately removed 40 | self.unregister(); 41 | break; 42 | } 43 | } catch (error) { 44 | // in the event of failure, unregister 45 | self.unregister(); 46 | } 47 | ``` 48 | 49 | When we set one entry point for all service workers operating under one origin, 50 | our definition of functionality per scope gives us a high overview of what each 51 | scope is responsible for and its dependencies. In addition, our service worker 52 | scripts become modular and composable. 53 | 54 | It's important however to ensure that between scripts, there are no conflicts with 55 | events. As a measure to prevent this, it's important to focus on not repeating 56 | events used in the other scripts. 57 | 58 | If the need to remove the service workers registered on multiple scopes, 59 | we can use the `escapeHatch` function to remove them all, or use `unregister` 60 | with the desired `scope` for selective removal. 61 | 62 | **`/client.js`** 63 | 64 | ```js 65 | import { 66 | register, 67 | unregister, 68 | escapeHatch, 69 | } from '@americanexpress/one-service-worker'; 70 | 71 | export async function unregisterWorkers(scope) { 72 | if (typeof scope === 'string' && scope) await unregister(scope); 73 | else await escapeHatch(); 74 | } 75 | 76 | export async function registerWorkers() { 77 | const updateViaCache = 'none'; 78 | 79 | await register('/sw.js', { scope: '/', updateViaCache }); 80 | await register('/sw.js', { scope: '/data', updateViaCache }); 81 | // ... others 82 | } 83 | ``` 84 | 85 | The `updateViaCache` is important in the given scenario. By setting the value to `none`, 86 | we are ensuring our imported scripts in the worker are not sourced from the cache. 87 | 88 | An advantage of scope based service workers is having the client-side decide when 89 | to register a service worker at a given scope, based on the needs of the client 90 | side component. 91 | 92 | [☝️ Return To Top](#-patterns) 93 | -------------------------------------------------------------------------------- /docs/Development.md: -------------------------------------------------------------------------------- 1 | # Developing with the library 2 | 3 | 4 | [👈 Go to `README`](../README.md) 5 | 6 | 7 | For those developing in this library, there are several tools to create a richer 8 | DX for users and better explanation of failures during library usage. 9 | 10 | ## Errors 11 | 12 | Errors can be the source of much development pain when using an API. To help alleviate some 13 | of the pain points often associated with ambiguous error messages, it is pivotal to communicate 14 | effectively when creating an error for the library. 15 | 16 | ### Creating an error 17 | 18 | For error reporting in `one-service-worker`, you can create a `OneServiceWorkerError`: 19 | 20 | ```js 21 | import { 22 | // our domain 23 | OneServiceWorkerError, 24 | // functions that return error 25 | // with pre-baked message which 26 | // can take parameters 27 | notEnabled, 28 | notSupported, 29 | failedToInstall, 30 | failure, 31 | // helpers 32 | errorFactory, 33 | } from '@americanexpress/one-service-worker'; 34 | 35 | // these will create the error 36 | const errorSource = new OneServiceWorkerError('Error'); 37 | const createdError = errorFactory(() => errorSource); 38 | ``` 39 | 40 | ## Error factory 41 | 42 | The error factory creates a function that uses two callbacks to 43 | create the error and handle it. 44 | 45 | ```js 46 | import { 47 | notSupported, 48 | errorFactory, 49 | getRegistration, 50 | } from '@americanexpress/one-service-worker'; 51 | 52 | // errorFactory takes two functions 53 | const exception = errorFactory( 54 | // starting point 55 | () => notSupported('Service Worker'), 56 | // instead of throwing, we can reject with a promise 57 | error => Promise.reject(error), 58 | ); 59 | 60 | export function getRegistrationPromise() { 61 | if (isServiceWorkerSupported()) { 62 | // ... get to work 63 | return getRegistration(); 64 | } 65 | // we return our promise rejection on calls to exception 66 | return exception(); 67 | } 68 | ``` 69 | 70 | ### Exceptions 71 | 72 | Exceptions combine message generator that can be passed arguments, to yield a `OneServiceWorkerError` with the message: 73 | 74 | ```js 75 | import { 76 | notEnabled, 77 | notSupported, 78 | failedToInstall, 79 | failure, 80 | } from '@americanexpress/one-service-worker'; 81 | 82 | [ 83 | notEnabled(), 84 | notSupported('Permissions'), 85 | failedToInstall(), 86 | failure('Registration'), 87 | ].map(error => error instanceof OneServiceWorkerError); 88 | ``` 89 | 90 | When combined with `errorFactory`, it can be used to build 91 | reusable error handlers. 92 | 93 | ```js 94 | import { notEnabled, errorFactory } from '@americanexpress/one-service-worker'; 95 | // errorFactory takes two functions 96 | const exception = errorFactory( 97 | // define the target - an error in this case as input 98 | () => notEnabled('Events'), 99 | // and perform on the result of the previous operation 100 | error => { 101 | throw error; 102 | }, 103 | ); 104 | 105 | try { 106 | exception(); 107 | } catch (error) { 108 | console.error(error); 109 | } 110 | ``` 111 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/index.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`library consistently exports 1`] = ` 4 | "on :: function 5 | once :: function 6 | off :: function 7 | emit :: function 8 | emitter :: function 9 | eventListeners :: object 10 | calls :: object 11 | createMiddleware :: function 12 | createMiddlewareContext :: function 13 | createMiddlewareFactory :: function 14 | TYPES :: object 15 | ENUMS :: object 16 | expectedType :: function 17 | expectedArrayOfType :: function 18 | enumerableException :: function 19 | unknownKey :: function 20 | unknownEventName :: function 21 | validateInput :: function 22 | getCacheOptions :: function 23 | OneServiceWorkerError :: function 24 | errorFactory :: function 25 | notEnabled :: function 26 | notSupported :: function 27 | failedToInstall :: function 28 | failure :: function 29 | configure :: function 30 | getConfig :: function 31 | isDevelopment :: function 32 | isEventsEnabled :: function 33 | isNonStandardEnabled :: function 34 | isNavigationPreloadEnabled :: function 35 | isServiceWorker :: function 36 | isServiceWorkerSupported :: function 37 | isNotificationSupported :: function 38 | isPushSupported :: function 39 | isBackgroundSyncSupported :: function 40 | isCacheStorageSupported :: function 41 | isIndexedDBSupported :: function 42 | isPermissionsSupported :: function 43 | isOffline :: function 44 | getRegistration :: function 45 | getRegistrations :: function 46 | register :: function 47 | getWorker :: function 48 | postMessage :: function 49 | update :: function 50 | unregister :: function 51 | escapeHatch :: function 52 | showNotification :: function 53 | getNotifications :: function 54 | getSubscription :: function 55 | subscribe :: function 56 | unsubscribe :: function 57 | registerTag :: function 58 | getTags :: function 59 | urlBase64ToUint8Array :: function 60 | cachePrefix :: string 61 | cacheDelimiter :: string 62 | defaultCacheName :: string 63 | defaultCacheOptions :: object 64 | createCacheName :: function 65 | normalizeRequest :: function 66 | open :: function 67 | has :: function 68 | keys :: function 69 | match :: function 70 | matchAll :: function 71 | add :: function 72 | addAll :: function 73 | put :: function 74 | remove :: function 75 | removeAll :: function 76 | entries :: function 77 | clear :: function 78 | metaDataCacheName :: string 79 | createMetaCacheName :: function 80 | createMetaCacheEntryName :: function 81 | createMetaRequest :: function 82 | createMetaResponse :: function 83 | getMetaStore :: function 84 | getMetaData :: function 85 | setMetaData :: function 86 | deleteMetaData :: function 87 | createCacheEntryName :: function 88 | onInstall :: function 89 | onActivate :: function 90 | onMessage :: function 91 | onPush :: function 92 | onSync :: function 93 | onFetch :: function 94 | cacheBusting :: function 95 | cacheRouter :: function 96 | cacheStrategy :: function 97 | clientsClaim :: function 98 | escapeHatchRoute :: function 99 | manifest :: function 100 | messageContext :: function 101 | messenger :: function 102 | navigationPreloadActivation :: function 103 | navigationPreloadResponse :: function 104 | appShell :: function 105 | precache :: function 106 | skipWaiting :: function 107 | expiration :: function" 108 | `; 109 | -------------------------------------------------------------------------------- /src/utility/events/events.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { isEventsEnabled } from '../runtime'; 18 | import { createMiddlewareContext } from './middleware'; 19 | 20 | export const eventListeners = new Map(); 21 | export const calls = new Map(); 22 | 23 | export function emit(eventName, event) { 24 | if (isEventsEnabled()) { 25 | const listeners = eventListeners.get(eventName) || new Set(); 26 | const callsForEvent = calls.get(eventName) || new Set(); 27 | 28 | callsForEvent.add(event); 29 | 30 | // keep no more than three events per type in memory 31 | if (callsForEvent.size > 2) { 32 | [...callsForEvent] 33 | .reverse() 34 | .filter((_, index) => index > 2) 35 | .forEach(storedEvent => callsForEvent.delete(storedEvent)); 36 | } 37 | 38 | calls.set(eventName, callsForEvent); 39 | 40 | // error stack tracing in the context 41 | // which event 42 | // chain eventing identification 43 | // log context 44 | const context = createMiddlewareContext(); 45 | listeners.forEach(listener => listener(event, context)); 46 | } 47 | } 48 | 49 | export function on(eventName, callback) { 50 | if (isEventsEnabled()) { 51 | if (Array.isArray(callback)) { 52 | callback.forEach(cb => on(eventName, cb)); 53 | } else { 54 | const handlers = eventListeners.get(eventName) || new Set(); 55 | handlers.add(callback); 56 | // calls previous events 57 | if (calls.has(eventName)) calls.get(eventName).forEach(callback); 58 | eventListeners.set(eventName, handlers); 59 | } 60 | } 61 | } 62 | 63 | export function off(eventName, callback) { 64 | if (isEventsEnabled() && eventListeners.has(eventName)) { 65 | const handlers = eventListeners.get(eventName); 66 | handlers.delete(callback); 67 | if (handlers.size === 0) { 68 | if (calls.has(eventName)) calls.delete(eventName); 69 | eventListeners.delete(eventName); 70 | } else eventListeners.set(eventName, handlers); 71 | } 72 | } 73 | 74 | export function once(eventName, callback) { 75 | if (isEventsEnabled()) { 76 | const disposableHandle = (event, context) => { 77 | off(eventName, disposableHandle); 78 | callback(event, context); 79 | }; 80 | 81 | on(eventName, disposableHandle); 82 | } 83 | } 84 | 85 | // eslint-disable-next-line no-restricted-globals 86 | export const emitter = (events = [], target = self) => 87 | isEventsEnabled() && 88 | events.forEach(key => { 89 | if (key in target && target[key] === null) { 90 | const eventName = key.replace('on', ''); 91 | // eslint-disable-next-line no-param-reassign 92 | target.addEventListener(eventName, event => emit(eventName, event)); 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import initialize from './initialization'; 18 | 19 | initialize(); 20 | 21 | export { 22 | on, 23 | once, 24 | off, 25 | emit, 26 | emitter, 27 | eventListeners, 28 | calls, 29 | createMiddleware, 30 | createMiddlewareContext, 31 | createMiddlewareFactory, 32 | } from './utility/events'; 33 | export { 34 | TYPES, 35 | ENUMS, 36 | expectedType, 37 | expectedArrayOfType, 38 | enumerableException, 39 | unknownKey, 40 | unknownEventName, 41 | validateInput, 42 | getCacheOptions, 43 | } from './utility/validation'; 44 | export { 45 | OneServiceWorkerError, 46 | errorFactory, 47 | notEnabled, 48 | notSupported, 49 | failedToInstall, 50 | failure, 51 | } from './utility/errors'; 52 | export { 53 | configure, 54 | getConfig, 55 | isDevelopment, 56 | isEventsEnabled, 57 | isNonStandardEnabled, 58 | isNavigationPreloadEnabled, 59 | isServiceWorker, 60 | isServiceWorkerSupported, 61 | isNotificationSupported, 62 | isPushSupported, 63 | isBackgroundSyncSupported, 64 | isCacheStorageSupported, 65 | isIndexedDBSupported, 66 | isPermissionsSupported, 67 | isOffline, 68 | } from './utility/runtime'; 69 | 70 | export { 71 | getRegistration, 72 | getRegistrations, 73 | register, 74 | getWorker, 75 | postMessage, 76 | update, 77 | unregister, 78 | escapeHatch, 79 | showNotification, 80 | getNotifications, 81 | getSubscription, 82 | subscribe, 83 | unsubscribe, 84 | registerTag, 85 | getTags, 86 | urlBase64ToUint8Array, 87 | } from './core'; 88 | 89 | export { 90 | cachePrefix, 91 | cacheDelimiter, 92 | defaultCacheName, 93 | defaultCacheOptions, 94 | createCacheName, 95 | normalizeRequest, 96 | open, 97 | has, 98 | keys, 99 | match, 100 | matchAll, 101 | add, 102 | addAll, 103 | put, 104 | remove, 105 | removeAll, 106 | entries, 107 | clear, 108 | metaDataCacheName, 109 | createMetaCacheName, 110 | createMetaCacheEntryName, 111 | createMetaRequest, 112 | createMetaResponse, 113 | getMetaStore, 114 | getMetaData, 115 | setMetaData, 116 | deleteMetaData, 117 | createCacheEntryName, 118 | } from './cache'; 119 | 120 | export { 121 | onInstall, 122 | onActivate, 123 | onMessage, 124 | onPush, 125 | onSync, 126 | onFetch, 127 | cacheBusting, 128 | cacheRouter, 129 | cacheStrategy, 130 | clientsClaim, 131 | escapeHatchRoute, 132 | manifest, 133 | messageContext, 134 | messenger, 135 | navigationPreloadActivation, 136 | navigationPreloadResponse, 137 | appShell, 138 | precache, 139 | skipWaiting, 140 | expiration, 141 | } from './middleware'; 142 | -------------------------------------------------------------------------------- /__tests__/middleware/manifest.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import createManifest, { createResponse } from '../../src/middleware/manifest'; 18 | import { createMiddlewareContext } from '../../src/utility/events'; 19 | import { isServiceWorker } from '../../src/utility/runtime'; 20 | 21 | import { createFetchEvent } from '../helpers'; 22 | 23 | jest.mock('../../src/utility/runtime/environment'); 24 | 25 | describe('createManifest', () => { 26 | beforeAll(() => { 27 | isServiceWorker.mockImplementation(() => true); 28 | }); 29 | beforeEach(() => jest.clearAllMocks()); 30 | 31 | test('does nothing if not in the service worker', async () => { 32 | expect.assertions(3); 33 | 34 | isServiceWorker.mockImplementationOnce(() => false); 35 | 36 | const route = '/manifest.webmanifest'; 37 | const handler = createManifest(); 38 | const event = createFetchEvent(route); 39 | 40 | expect(handler).toBeInstanceOf(Function); 41 | expect(handler(event)).toBe(undefined); 42 | expect(event.respondWith).not.toHaveBeenCalled(); 43 | }); 44 | 45 | test('does nothing if route does not match', async () => { 46 | expect.assertions(3); 47 | 48 | const route = '/manifest.json'; 49 | const handler = createManifest(); 50 | const event = createFetchEvent(route); 51 | const context = createMiddlewareContext(); 52 | 53 | expect(handler).toBeInstanceOf(Function); 54 | expect(handler(event, context)).toBe(false); 55 | expect(event.respondWith).not.toHaveBeenCalled(); 56 | }); 57 | 58 | test('responds with default manifest on default path if no arguments set', async () => { 59 | expect.assertions(4); 60 | 61 | const route = '/manifest.webmanifest'; 62 | const handler = createManifest(); 63 | const event = createFetchEvent(route); 64 | const context = createMiddlewareContext(); 65 | 66 | expect(handler).toBeInstanceOf(Function); 67 | expect(handler(event, context)).toBe(true); 68 | expect(event.respondWith).toHaveBeenCalledTimes(1); 69 | expect(event.respondWith).toHaveBeenCalledWith(createResponse()); 70 | }); 71 | 72 | test('accepts custom manifest and route', async () => { 73 | expect.assertions(4); 74 | 75 | const route = '/manifest.json'; 76 | const manifest = { 77 | name: 'one-app-service-worker-app', 78 | }; 79 | const handler = createManifest(manifest, route); 80 | const event = createFetchEvent(route); 81 | const context = createMiddlewareContext(); 82 | 83 | expect(handler).toBeInstanceOf(Function); 84 | expect(handler(event, context)).toBe(true); 85 | expect(event.respondWith).toHaveBeenCalledTimes(1); 86 | expect(event.respondWith).toHaveBeenCalledWith( 87 | createResponse({ 88 | event, 89 | manifest, 90 | }), 91 | ); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /docs/guides/Cache.md: -------------------------------------------------------------------------------- 1 | [cache-storage-api]: https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage 2 | [cache-api]: https://developer.mozilla.org/en-US/docs/Web/API/Cache 3 | 4 | # Caching 5 | 6 | [👈 Go to `README`](../../README.md) 7 | 8 | [👆 Back to `Guides`](./README.md) 9 | 10 | 11 | With the [`caches` CacheStorage][cache-storage-api] and [`cache` Cache][cache-api], we can create and manage 12 | a service worker first caching solution. 13 | 14 | The first thing we need is to put a `fetch` handler in our service worker: 15 | 16 | ```js 17 | self.addEventListener('fetch', event => { 18 | // first, we make the decision to respond with: 19 | event.respondWith( 20 | // next, we check for any matches in cache: 21 | caches.match(event.request.clone()).then(match => { 22 | if (match) return match; 23 | // if nothing matches the cache, fetch the request: 24 | return fetch(event.request.clone()).then(response => { 25 | // in case of anything other than GET: 26 | if (event.request.method === 'GET') { 27 | // we do not wait for the new response to be added to the cache to send it back to the main thread. 28 | 29 | // to allow us to keep the service worker from terminating, 30 | // we call event.waitUntil and pass in our promise to 31 | // guarantee service worker lifetime will last for this promise 32 | event.waitUntil( 33 | // we open our cache to operate on it: 34 | caches.open('my-cache').then(cache => 35 | // add it to the cache: 36 | cache.put(event.request.clone(), response.clone()), 37 | ), 38 | ); 39 | } 40 | // the response is sent right after: 41 | return response; 42 | }); 43 | }), 44 | ); 45 | }); 46 | ``` 47 | 48 | > in production, you do not want to do this. This example serves to familiarize yourself with the api. 49 | 50 | This example will cache anything with a `GET` request (using `cache.put`) and use the `caches.match` to grab 51 | it. Two other takeaways: 52 | - notice the `event.waitUntil` and `event.respondWith` (only in `fetch` event) are used. `waitUntil` is useful 53 | for extending the service worker lifetime from terminating until a passed in promise is finished. `respondWith` 54 | is when you are sure you want to handle the `fetch` response. 55 | - `caches` is globally available on `window` and `self` of both threads. `caches.open('name')` is how we access the 56 | `cache` and operate on it. 57 | 58 | ## more ideas for using the cache 59 | 60 | ### deleting caches 61 | ```js 62 | (async function deleteCache() { 63 | await caches.delete('my-cache'); 64 | })(); 65 | 66 | (async function deleteCacheItem() { 67 | const cache = await caches.open('my-cache'); 68 | await cache.delete(new Request('/offline.html')); 69 | })(); 70 | ``` 71 | 72 | ### getting cache keys 73 | ```js 74 | (async function getCacheKeys() { 75 | const cachesOpened = await caches.keys(); 76 | console.log(cachesOpened); 77 | })(); 78 | 79 | (async function getCacheItems() { 80 | const cache = await caches.open('my-cache'); 81 | const requests = await cache.keys(); 82 | console.log(requests); 83 | })(); 84 | ``` 85 | 86 | ### pre caching 87 | ```js 88 | (async function precache() { 89 | const cache = await caches.open('my-cache'); 90 | await cache.add('/index.html'); 91 | const response = await cache.match(new Request('/index.html')); 92 | console.log(response); 93 | })(); 94 | ``` 95 | 96 | [☝️ Return To Top](#-caching) 97 | -------------------------------------------------------------------------------- /src/middleware/caching.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { match, addAll, put, clear, createCacheName } from '../cache'; 18 | import { isServiceWorker, isCacheStorageSupported } from '../utility/runtime'; 19 | 20 | import { noop } from './utility'; 21 | 22 | export const routerCacheName = createCacheName('router'); 23 | export const getCacheName = cacheName => (cacheName ? createCacheName(cacheName) : routerCacheName); 24 | export const isCacheWorkerSupported = () => isCacheStorageSupported() && isServiceWorker(); 25 | export const fetchAndCache = ({ request, cacheName, waitUntil, fetchOptions }) => 26 | fetch(request.clone(), fetchOptions).then(response => { 27 | waitUntil( 28 | put(request.clone(), response.clone(), { 29 | cacheName, 30 | }), 31 | ); 32 | return response; 33 | }); 34 | 35 | export function createCacheRouter({ cacheName, match: matcher, fetchOptions } = {}) { 36 | if (!isCacheWorkerSupported()) return noop; 37 | 38 | // eslint-disable-next-line unicorn/consistent-function-scoping 39 | let test = () => false; 40 | if (typeof matcher === 'function') test = matcher; 41 | else if (matcher instanceof RegExp) test = ({ request: { url } }) => matcher.test(url); 42 | 43 | const name = getCacheName(cacheName); 44 | // eslint-disable-next-line consistent-return 45 | return function cacheRouter(event, context) { 46 | if (test(event)) { 47 | context.set('cacheName', name); 48 | context.set('request', event.request.clone()); 49 | 50 | event.respondWith( 51 | match(event.request.clone()).then( 52 | cachedResponse => 53 | cachedResponse || 54 | fetchAndCache({ 55 | waitUntil: promise => event.waitUntil(promise), 56 | request: event.request, 57 | cacheName: name, 58 | fetchOptions, 59 | }), 60 | ), 61 | ); 62 | 63 | return true; 64 | } 65 | }; 66 | } 67 | 68 | export function createCacheStrategy() { 69 | if (!isCacheWorkerSupported()) return noop; 70 | 71 | return function cacheStrategy(event) { 72 | event.waitUntil( 73 | match(event.request.clone()).then( 74 | cachedResponse => cachedResponse && event.respondWith(cachedResponse), 75 | ), 76 | ); 77 | return true; 78 | }; 79 | } 80 | 81 | export function createCacheBusting(requestInvalidator, cacheInvalidator) { 82 | if (!isCacheWorkerSupported()) return noop; 83 | 84 | return function cacheBusting(event) { 85 | event.waitUntil(clear(requestInvalidator, cacheInvalidator)); 86 | }; 87 | } 88 | 89 | export function createPrecache(requests = [], { cacheName } = {}) { 90 | if (!isCacheWorkerSupported() || requests.length === 0) return noop; 91 | 92 | return function precacheMiddleware(event) { 93 | event.waitUntil( 94 | addAll(requests, { 95 | cacheName: createCacheName(cacheName), 96 | }), 97 | ); 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /src/utility/validation/validation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { OneServiceWorkerError } from '../errors'; 18 | 19 | export const TYPES = { 20 | url: 'string', 21 | path: 'string', 22 | partition: 'string', 23 | strategy: 'string', 24 | shell: 'string', 25 | scope: 'string', 26 | cache: 'string', 27 | cacheName: 'string', 28 | updateViaCache: 'string', 29 | offline: 'object', 30 | precache: 'object', 31 | maxAge: 'number', 32 | maxResources: 'number', 33 | maxContentSize: 'number', 34 | timeout: 'number', 35 | strict: 'boolean', 36 | }; 37 | 38 | export const ENUMS = { 39 | updateViaCache: ['all', 'imports', 'none'], 40 | }; 41 | 42 | export const expectedType = ({ key = '', type } = {}) => 43 | new OneServiceWorkerError(`Expected ${key} value to be a ${type || TYPES[key]}\n`); 44 | export const expectedArrayOfType = ({ key = '', type = 'string' } = {}) => 45 | new OneServiceWorkerError(`Expected value of ${key} to be an Array of ${type}s\n`); 46 | export const enumerableException = ({ key = '' } = {}) => 47 | new OneServiceWorkerError( 48 | `Expected ${key} value to match enumerable values\n\t[${(ENUMS[key] || []).join(', ')}]\n`, 49 | ); 50 | export const unknownKey = ({ key = '', keys = [] } = {}) => 51 | new OneServiceWorkerError( 52 | `Unknown key ${key} given, expected one of:\n\t{ ${keys.join(', ')} }\n`, 53 | ); 54 | export const unknownEventName = ({ eventName = '', enabledEvents = [] } = {}) => 55 | new OneServiceWorkerError( 56 | [ 57 | `event name "${eventName}" is not a supported event, please select one of the following:\n`, 58 | `[${enabledEvents.join(',\t')}]`, 59 | ].join('\n'), 60 | ); 61 | 62 | export function validateInput(config, log = true) { 63 | const exceptions = []; 64 | 65 | const keys = Object.keys(config); 66 | 67 | keys 68 | .map(key => [key, config[key], typeof config[key]]) 69 | .forEach(([key, value, type]) => { 70 | if (key in TYPES === false) { 71 | exceptions.push( 72 | unknownKey({ 73 | key, 74 | keys, 75 | }), 76 | ); 77 | } else if (type !== TYPES[key]) { 78 | exceptions.push( 79 | expectedType({ 80 | key, 81 | }), 82 | ); 83 | } 84 | 85 | if (['offline', 'precache'].includes(key)) { 86 | if (Array.isArray(value) === false) { 87 | exceptions.push( 88 | expectedArrayOfType({ 89 | key, 90 | type: 'string', 91 | }), 92 | ); 93 | } 94 | } 95 | 96 | if (key in ENUMS) { 97 | if (ENUMS[key].includes(value) === false) { 98 | exceptions.push( 99 | enumerableException({ 100 | key, 101 | }), 102 | ); 103 | } 104 | } 105 | }); 106 | 107 | if (log) { 108 | exceptions.forEach(error => { 109 | // eslint-disable-next-line no-console 110 | console.warn(error); 111 | }); 112 | } 113 | 114 | return exceptions; 115 | } 116 | -------------------------------------------------------------------------------- /__tests__/middleware/worker.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { onFetch, onInstall, onActivate, onMessage, onPush, onSync } from '../../src/middleware'; 18 | import { isNavigationPreloadEnabled } from '../../src/utility/runtime/environment'; 19 | 20 | import { createEvent } from '../helpers'; 21 | 22 | jest.mock('../../src/utility/runtime/environment'); 23 | 24 | describe('onMessage', () => { 25 | test('returns handler', () => { 26 | const handler = onMessage(); 27 | expect(handler).toBeInstanceOf(Function); 28 | }); 29 | 30 | test('responds to event', () => { 31 | const handler = onMessage(); 32 | const event = createEvent('message', { 33 | data: { 34 | partition: 'nav', 35 | active: true, 36 | }, 37 | }); 38 | expect(handler(event)).toBe(false); 39 | }); 40 | 41 | test('does not respond to event', () => { 42 | const handler = onMessage(); 43 | const event = createEvent('message', { 44 | data: { 45 | type: 'dismount', 46 | partition: 'nav', 47 | }, 48 | }); 49 | expect(handler(event)).toBe(false); 50 | }); 51 | }); 52 | 53 | describe('onInstall', () => { 54 | test('returns handler', () => { 55 | const handler = onInstall(); 56 | expect(handler).toBeInstanceOf(Function); 57 | }); 58 | 59 | test('responds to event', () => { 60 | const handler = onInstall(); 61 | const event = createEvent(); 62 | expect(handler(event)).toBe(false); 63 | }); 64 | 65 | test('enables navigation preload when on', () => { 66 | const handler = onInstall(); 67 | const event = createEvent(); 68 | isNavigationPreloadEnabled.mockImplementationOnce(() => true); 69 | expect(handler(event)).toBe(false); 70 | }); 71 | }); 72 | 73 | describe('onActivate', () => { 74 | test('returns handler', () => { 75 | const handler = onActivate(); 76 | expect(handler).toBeInstanceOf(Function); 77 | }); 78 | 79 | test('responds to event', () => { 80 | const handler = onActivate(); 81 | const event = createEvent(); 82 | expect(handler(event)).toBe(false); 83 | }); 84 | }); 85 | 86 | describe('onFetch', () => { 87 | test('returns handler', () => { 88 | const handler = onFetch(); 89 | expect(handler).toBeInstanceOf(Function); 90 | }); 91 | 92 | test('responds to event', () => { 93 | const handler = onFetch(); 94 | const event = createEvent('fetch', { 95 | request: new Request('/worker.js'), 96 | }); 97 | expect(handler(event)).toBe(false); 98 | }); 99 | }); 100 | 101 | describe('onPush', () => { 102 | test('returns handler', () => { 103 | const handler = onPush(); 104 | expect(handler).toBeInstanceOf(Function); 105 | }); 106 | 107 | test('responds to event', () => { 108 | const handler = onPush(); 109 | // eslint-disable-next-line no-undef 110 | const event = createEvent('push'); 111 | expect(handler(event)).toBe(false); 112 | }); 113 | }); 114 | 115 | describe('onSync', () => { 116 | test('returns handler', () => { 117 | const handler = onSync(); 118 | expect(handler).toBeInstanceOf(Function); 119 | }); 120 | 121 | test('responds to event', () => { 122 | const handler = onSync(); 123 | // eslint-disable-next-line no-undef 124 | const event = createEvent('sync'); 125 | expect(handler(event)).toBe(false); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /src/cache/meta-data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { match, put, remove, createCacheName, defaultCacheName } from './cache'; 18 | 19 | export const metaDataCacheName = '__meta'; 20 | 21 | export function createMetaCacheName() { 22 | return createCacheName(metaDataCacheName); 23 | } 24 | 25 | export function createMetaCacheEntryName(cacheName = defaultCacheName) { 26 | return `/${createMetaCacheName()}/${cacheName}`; 27 | } 28 | 29 | export function createCacheEntryName(cacheName) { 30 | console.warn( 31 | '[One Service Worker]: Deprecation Notice - %s is marked for deprecation and will not be accessible in the next major release.', 32 | 'createCacheEntryName', 33 | ); 34 | return createMetaCacheEntryName(cacheName); 35 | } 36 | 37 | export function createMetaRequest(cacheName) { 38 | return new Request(createMetaCacheEntryName(cacheName)); 39 | } 40 | 41 | export function createMetaResponse({ url, data = {} } = {}) { 42 | const headers = new Headers({}); 43 | headers.append('content-type', 'application/json'); 44 | return new Response(JSON.stringify(data), { 45 | url, 46 | headers, 47 | status: 200, 48 | }); 49 | } 50 | 51 | export function getMetaStore(cacheName) { 52 | return match(createMetaRequest(cacheName), { 53 | cacheName: createMetaCacheName(), 54 | }).then(exists => (exists ? exists.json() : Promise.resolve({}))); 55 | } 56 | 57 | export function getMetaData({ url, cacheName } = {}) { 58 | return getMetaStore(cacheName).then(data => { 59 | if (url) { 60 | const request = new Request(url); 61 | return data[request.url] || {}; 62 | } 63 | return data; 64 | }); 65 | } 66 | 67 | export function setMetaData({ url, cacheName, metadata } = {}) { 68 | return getMetaStore(cacheName).then(data => { 69 | const metaRequest = createMetaRequest(cacheName); 70 | let updates = null; 71 | 72 | let key = null; 73 | if (url) { 74 | ({ url: key } = new Request(url)); 75 | 76 | updates = { 77 | ...data, 78 | [key]: metadata, 79 | }; 80 | } else if (metadata) updates = metadata; 81 | else return Promise.resolve(key); 82 | 83 | return put( 84 | metaRequest.clone(), 85 | createMetaResponse({ 86 | url: metaRequest.url, 87 | data: updates, 88 | }), 89 | { 90 | cacheName: createMetaCacheName(), 91 | }, 92 | ).then(() => (key ? updates[key] : updates)); 93 | }); 94 | } 95 | 96 | export function deleteMetaData({ url, cacheName } = {}) { 97 | return getMetaStore(cacheName).then(data => { 98 | const updates = { ...data }; 99 | const metaRequest = createMetaRequest(cacheName); 100 | 101 | let deleted = false; 102 | 103 | if (url) { 104 | const request = new Request(url); 105 | delete updates[request.url]; 106 | deleted = true; 107 | 108 | if (Object.keys(updates).length === 0) { 109 | return remove(metaRequest, { 110 | cacheName: createMetaCacheName(cacheName), 111 | }); 112 | } 113 | 114 | return setMetaData({ 115 | metadata: updates, 116 | cacheName, 117 | }).then(() => deleted); 118 | } 119 | 120 | if (cacheName) { 121 | return remove(metaRequest, { 122 | cacheName: createMetaCacheName(cacheName), 123 | }); 124 | } 125 | 126 | return deleted; 127 | }); 128 | } 129 | -------------------------------------------------------------------------------- /packages/one-service-worker-demo/src/hooks/useWebPush.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import React from 'react'; 18 | 19 | import { 20 | getSubscription, 21 | subscribe, 22 | unsubscribe, 23 | on, 24 | off, 25 | } from '@americanexpress/one-service-worker'; 26 | 27 | export async function getApplicationServerKey() { 28 | const response = await fetch('/subscribe'); 29 | const applicationServerKey = await response.text(); 30 | return applicationServerKey; 31 | } 32 | 33 | export async function postSubscription(subscription) { 34 | const response = await fetch('/subscribe', { 35 | method: 'POST', 36 | body: JSON.stringify({ 37 | subscription, 38 | }), 39 | headers: new Headers({ 40 | 'Content-Type': 'application/json', 41 | }), 42 | }); 43 | return response.status === 202; 44 | } 45 | 46 | export async function deleteSubscription(subscription) { 47 | const response = await fetch('/subscribe', { 48 | method: 'DELETE', 49 | body: JSON.stringify({ 50 | subscription, 51 | }), 52 | headers: new Headers({ 53 | 'Content-Type': 'application/json', 54 | }), 55 | }); 56 | return response.status === 202; 57 | } 58 | 59 | export async function sendNotification(subscription, notification) { 60 | const response = await fetch('/dispatch', { 61 | method: 'POST', 62 | body: JSON.stringify({ 63 | subscription, 64 | notification, 65 | }), 66 | headers: new Headers({ 67 | 'Content-Type': 'application/json', 68 | }), 69 | }); 70 | return response.status === 202; 71 | } 72 | 73 | export function createSubscriptionHandlers(setSubscription) { 74 | return { 75 | subscribe(subscription) { 76 | setSubscription(subscription); 77 | }, 78 | unsubscribe() { 79 | setSubscription(null); 80 | }, 81 | }; 82 | } 83 | 84 | const localStorageKey = 'applicationServerKey'; 85 | 86 | export default function useWebPush() { 87 | const [subscription, setSubscription] = React.useState(null); 88 | const [applicationServerKey, setApplicationServerKey] = React.useState( 89 | () => localStorage.getItem(localStorageKey) || null, 90 | ); 91 | 92 | React.useEffect(() => { 93 | if (subscription) { 94 | postSubscription(subscription); 95 | } 96 | }, [subscription]); 97 | 98 | React.useEffect(() => { 99 | if (applicationServerKey) { 100 | localStorage.setItem(localStorageKey, applicationServerKey); 101 | } 102 | }, [applicationServerKey]); 103 | 104 | React.useEffect(() => { 105 | if (applicationServerKey === null) getApplicationServerKey().then(setApplicationServerKey); 106 | }, []); 107 | 108 | React.useLayoutEffect(() => { 109 | getApplicationServerKey().then(setApplicationServerKey); 110 | const handlers = createSubscriptionHandlers(setSubscription); 111 | on('subscribe', handlers.subscribe); 112 | on('unsubscribe', handlers.unsubscribe); 113 | getSubscription().then(sub => sub && setSubscription(sub)); 114 | return () => { 115 | off('subscribe', handlers.subscribe); 116 | off('unsubscribe', handlers.unsubscribe); 117 | }; 118 | }, []); 119 | 120 | return { 121 | applicationServerKey, 122 | subscription, 123 | subscribe: () => subscribe({ applicationServerKey }), 124 | unsubscribe, 125 | dispatch: notification => sendNotification(subscription, notification), 126 | }; 127 | } 128 | -------------------------------------------------------------------------------- /__tests__/utility/validation/validation.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 American Express Travel Related Services Company, Inc. 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 13 | * or implied. See the License for the specific language governing 14 | * permissions and limitations under the License. 15 | */ 16 | 17 | import { OneServiceWorkerError } from '../../../src/utility/errors/errors'; 18 | import { 19 | expectedType, 20 | expectedArrayOfType, 21 | enumerableException, 22 | unknownKey, 23 | unknownEventName, 24 | validateInput, 25 | } from '../../../src/utility/validation/validation'; 26 | 27 | describe('message generators', () => { 28 | [expectedType, expectedArrayOfType, enumerableException, unknownKey, unknownEventName].forEach( 29 | msgFn => { 30 | test(`${msgFn.name} - returns string without arguments`, () => { 31 | expect.assertions(1); 32 | 33 | expect(msgFn()).toEqual(expect.any(OneServiceWorkerError)); 34 | }); 35 | }, 36 | ); 37 | }); 38 | 39 | /* eslint-disable no-console */ 40 | 41 | describe('validateInput', () => { 42 | const { warn } = console; 43 | 44 | beforeAll(() => { 45 | console.warn = jest.fn(); 46 | }); 47 | afterAll(() => { 48 | console.warn = warn; 49 | }); 50 | 51 | test('should run through all cases of non valid config', () => { 52 | expect.assertions(14); 53 | 54 | let numberOfCalls = 0; 55 | const config = {}; 56 | 57 | // no keys 58 | expect(validateInput(config)).toMatchObject([]); 59 | expect(console.warn).toHaveBeenCalledTimes(numberOfCalls); 60 | 61 | // invalid key 62 | config.invalidKey = 'wrong-key'; 63 | expect(validateInput(config)).toMatchObject( 64 | expect.arrayContaining([expect.any(OneServiceWorkerError)]), 65 | ); 66 | expect(console.warn).toHaveBeenCalledTimes((numberOfCalls += 1)); 67 | 68 | // invalid value for partition | also expect invalidKey again 69 | config.partition = 98; 70 | expect(validateInput(config)).toMatchObject( 71 | expect.arrayContaining([expect.any(OneServiceWorkerError)]), 72 | ); 73 | expect(console.warn).toHaveBeenCalledTimes((numberOfCalls += 2)); 74 | 75 | // invalid value for updateViaCache 76 | delete config.invalidKey; 77 | config.partition = 'valid-partition-type'; 78 | config.updateViaCache = 'server'; 79 | expect(validateInput(config)).toMatchObject( 80 | expect.arrayContaining([expect.any(OneServiceWorkerError)]), 81 | ); 82 | expect(console.warn).toHaveBeenCalledTimes((numberOfCalls += 1)); 83 | 84 | // invalid values for offline 85 | config.updateViaCache = 'all'; 86 | config.offline = { 87 | path: 89, 88 | routes: 98, 89 | }; 90 | expect(validateInput(config)).toMatchObject( 91 | expect.arrayContaining([expect.any(OneServiceWorkerError)]), 92 | ); 93 | expect(console.warn).toHaveBeenCalledTimes((numberOfCalls += 1)); 94 | 95 | // invalid values for precache 96 | config.offline = []; 97 | config.precache = { 98 | resources: 'nope', 99 | maxResources: 'nada', 100 | maxAge: 'nil', 101 | }; 102 | expect(validateInput(config)).toMatchObject( 103 | expect.arrayContaining([expect.any(OneServiceWorkerError)]), 104 | ); 105 | expect(console.warn).toHaveBeenCalledTimes((numberOfCalls += 1)); 106 | 107 | console.warn.mockClear(); 108 | 109 | // does not log validations 110 | expect(validateInput(config, false)).toMatchObject( 111 | expect.arrayContaining([expect.any(OneServiceWorkerError)]), 112 | ); 113 | expect(console.warn).toHaveBeenCalledTimes(0); 114 | 115 | console.warn = warn; 116 | }); 117 | }); 118 | --------------------------------------------------------------------------------