├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.js ├── .template-lintrc.js ├── .watchmanconfig ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── addon └── services │ ├── client.ts │ ├── server.ts │ └── window-messenger-events.ts ├── app └── services │ ├── window-messenger-client.js │ ├── window-messenger-events.js │ └── window-messenger-server.js ├── config ├── deploy.js ├── ember-try.js └── environment.js ├── ember-cli-build.js ├── index.js ├── package.json ├── testem.js ├── tests ├── dummy │ ├── app │ │ ├── app.js │ │ ├── components │ │ │ ├── .gitkeep │ │ │ ├── x-iframe.hbs │ │ │ └── x-iframe.js │ │ ├── config │ │ │ └── environment.d.ts │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ ├── application.js │ │ │ ├── client-server-one.js │ │ │ └── demo.js │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── index.html │ │ ├── models │ │ │ └── .gitkeep │ │ ├── router.js │ │ ├── routes │ │ │ ├── .gitkeep │ │ │ ├── client-server-one.js │ │ │ ├── client-server-two.js │ │ │ ├── client-server-two │ │ │ │ └── example.js │ │ │ └── demo.js │ │ ├── styles │ │ │ └── app.css │ │ └── templates │ │ │ ├── application.hbs │ │ │ ├── client-server-one.hbs │ │ │ ├── client-server-two.hbs │ │ │ ├── client-server-two │ │ │ └── example.hbs │ │ │ ├── demo.hbs │ │ │ ├── index.hbs │ │ │ └── not-found.hbs │ ├── config │ │ ├── ember-cli-update.json │ │ ├── environment.js │ │ ├── optional-features.json │ │ └── targets.js │ └── public │ │ └── robots.txt ├── helpers │ └── .gitkeep ├── index.html ├── test-helper.js └── unit │ └── services │ ├── window-messenger-client-test.ts │ ├── window-messenger-events-test.ts │ └── window-messenger-server-test.ts ├── tsconfig.json ├── types ├── dummy │ └── index.d.ts └── global.d.ts ├── vendor ├── .gitkeep └── ie11-promise.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /package.json.ember-try 23 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: '@typescript-eslint/parser', 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | sourceType: 'module', 9 | ecmaFeatures: { 10 | legacyDecorators: true, 11 | }, 12 | }, 13 | plugins: ['ember', '@typescript-eslint'], 14 | extends: [ 15 | 'eslint:recommended', 16 | 'plugin:@typescript-eslint/recommended', 17 | 'plugin:ember/recommended', 18 | 'plugin:prettier/recommended', 19 | ], 20 | env: { 21 | browser: true, 22 | }, 23 | rules: {}, 24 | overrides: [ 25 | // node files 26 | { 27 | files: [ 28 | './.eslintrc.js', 29 | './.prettierrc.js', 30 | './.template-lintrc.js', 31 | './ember-cli-build.js', 32 | './index.js', 33 | './testem.js', 34 | './blueprints/*/index.js', 35 | './config/**/*.js', 36 | './tests/dummy/config/**/*.js', 37 | ], 38 | parserOptions: { 39 | sourceType: 'script', 40 | }, 41 | env: { 42 | browser: false, 43 | node: true, 44 | }, 45 | plugins: ['node'], 46 | extends: ['plugin:node/recommended'], 47 | rules: { 48 | '@typescript-eslint/no-var-requires': 'off', 49 | }, 50 | }, 51 | { 52 | // Test files: 53 | files: ['tests/**/*-test.{js,ts}'], 54 | extends: ['plugin:qunit/recommended'], 55 | }, 56 | ], 57 | }; 58 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | concurrency: 6 | group: ${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | lint: 11 | name: Lint 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 20 14 | steps: 15 | - uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 1 18 | - uses: volta-cli/action@v1 19 | - name: Install Dependencies 20 | run: yarn install --frozen-lockfile 21 | - name: Lint 22 | run: yarn lint 23 | - name: TypeScript build 24 | run: yarn ts:build 25 | 26 | build-windows: 27 | runs-on: windows-latest 28 | needs: lint 29 | timeout-minutes: 10 30 | strategy: 31 | fail-fast: false 32 | matrix: 33 | browser: [ 34 | 'ie' 35 | ] 36 | steps: 37 | - uses: actions/checkout@v2 38 | with: 39 | fetch-depth: 1 40 | - uses: volta-cli/action@v1 41 | - name: Install dependencies 42 | run: yarn install --frozen-lockfile 43 | - name: Test 44 | run: yarn test:ember --launch ${{ matrix.browser }} 45 | 46 | test: 47 | name: Tests 48 | runs-on: ${{ matrix.os }} 49 | timeout-minutes: 20 50 | needs: lint 51 | 52 | strategy: 53 | matrix: 54 | os: [ubuntu-latest] 55 | browser: [chrome, firefox] 56 | node: ["12", "14", "16"] 57 | 58 | steps: 59 | - uses: actions/checkout@v2 60 | with: 61 | fetch-depth: 1 62 | - uses: volta-cli/action@v1 63 | with: 64 | node-version: ${{ matrix.node }} 65 | - name: Install Dependencies 66 | run: yarn install --frozen-lockfile 67 | - name: Test 68 | run: yarn test:ember --launch ${{ matrix.browser }} 69 | 70 | floating-dependencies: 71 | name: Floating Dependencies 72 | runs-on: ${{ matrix.os }} 73 | timeout-minutes: 20 74 | needs: lint 75 | 76 | strategy: 77 | matrix: 78 | os: [ubuntu-latest] 79 | browser: [chrome, firefox] 80 | 81 | steps: 82 | - uses: actions/checkout@v2 83 | with: 84 | fetch-depth: 1 85 | - uses: volta-cli/action@v1 86 | - name: Install Dependencies 87 | run: yarn install --no-lockfile --non-interactive 88 | - name: Test 89 | run: yarn test:ember --launch ${{ matrix.browser }} 90 | 91 | try-scenarios: 92 | name: Tests - ${{ matrix.ember-try-scenario }} 93 | runs-on: ubuntu-latest 94 | timeout-minutes: 20 95 | continue-on-error: ${{ matrix.allow-failure }} 96 | needs: test 97 | 98 | strategy: 99 | fail-fast: true 100 | matrix: 101 | ember-try-scenario: [ 102 | ember-lts-3.20, 103 | ember-lts-3.24, 104 | ember-release, 105 | ember-beta, 106 | ember-default-with-jquery, 107 | ember-classic, 108 | embroider-safe, 109 | embroider-optimized 110 | ] 111 | allow-failure: [false] 112 | include: 113 | - ember-try-scenario: ember-canary 114 | allow-failure: true 115 | 116 | steps: 117 | - uses: actions/checkout@v2 118 | with: 119 | fetch-depth: 1 120 | - uses: volta-cli/action@v1 121 | - name: Install Dependencies 122 | run: yarn install --frozen-lockfile 123 | - name: Test 124 | env: 125 | EMBER_TRY_SCENARIO: ${{ matrix.ember-try-scenario }} 126 | run: node_modules/.bin/ember try:one $EMBER_TRY_SCENARIO 127 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /connect.lock 17 | /coverage/ 18 | /libpeerconnection.log 19 | /npm-debug.log* 20 | /testem.log 21 | /yarn-error.log 22 | 23 | # ember-try 24 | /.node_modules.ember-try/ 25 | /bower.json.ember-try 26 | /package.json.ember-try 27 | 28 | # Editors 29 | .vscode 30 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.env* 13 | /.eslintcache 14 | /.eslintignore 15 | /.eslintrc.js 16 | /.git/ 17 | /.github/ 18 | /.gitignore 19 | /.prettierignore 20 | /.prettierrc.js 21 | /.template-lintrc.js 22 | /.travis.yml 23 | /.vscode 24 | /.watchmanconfig 25 | /bower.json 26 | /config/ember-try.js 27 | /CONTRIBUTING.md 28 | /ember-cli-build.js 29 | /testem.js 30 | /tests/ 31 | /yarn-error.log 32 | /yarn.lock 33 | .gitkeep 34 | 35 | # ember-try 36 | /.node_modules.ember-try/ 37 | /bower.json.ember-try 38 | /package.json.ember-try 39 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | 18 | # ember-try 19 | /.node_modules.ember-try/ 20 | /bower.json.ember-try 21 | /package.json.ember-try 22 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | }; 6 | -------------------------------------------------------------------------------- /.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ember-window-messenger 2 | 3 | ## [3.3.0] - 2021-12-14 4 | 5 | ### Added 6 | 7 | - Add test waiter to make window-messenger-client service notify Ember testing harness of pending promises. 8 | 9 | ## [3.2.0] - 2021-12-13 10 | 11 | ### Added 12 | 13 | - `has` method to server service to check if resource registration exists. 14 | 15 | ## [3.1.1] - 2021-12-01 16 | 17 | ### Changed 18 | 19 | - Ignore `.github` folder for NPM publishing 20 | 21 | ## [3.1.0] - 2021-12-01 22 | 23 | ### Added 24 | 25 | - TypeScript definitions support. 26 | - `addon/services/*` converted to TypeScript. 27 | 28 | ## [3.0.0] - 2021-11-25 29 | 30 | ## Breaking 31 | - Drop support for Node < 12 32 | 33 | ## Changed 34 | - Update to Ember.js v3.28.x, no deprecations, Octane and everything. 35 | - Ember v4.0 compliant 36 | - Embroider compliant 37 | 38 | ## [2.0.1] - 2018-10-06 39 | ### Changed 40 | - Internal: Clean up obsolete files 41 | 42 | ## [2.0.0] - 2018-10-06 43 | ### Breaking 44 | - Drop support for Ember v1.13 45 | 46 | ### Changed 47 | - Internal: Upgrade to Ember.js v3.4 48 | 49 | ## [1.2.2] - 2018-03-31 50 | ### Changed 51 | - Moved Bower under devDependencies 52 | 53 | ## [1.2.1] - 2018-03-31 54 | ### Changed 55 | - Upgrade to Ember v3.0 56 | 57 | ## [1.2.0] - 2018-02-04 58 | ### Changed 59 | - Upgrade to Babel 6.6.0+ 60 | - Upgrade to Ember-CLI 2.18 and Ember.js 2.18 61 | 62 | ## [1.1.0] - 2017-09-10 63 | ### Changed 64 | - Upgrade to Ember-CLI 2.15 and Ember.js 2.15 65 | 66 | ## [1.0.1] - 2017-08-17 67 | ### Fixed 68 | - PR #6 - issue with winow.opener check + ember-assign-polyfill 69 | - PR #8 - using global event to post back to the client 70 | 71 | ## [1.0.0] - 2017-04-24 72 | ### Added 73 | - Service for dispatching messages, so only one post message event listener is registered 74 | 75 | ### Changed 76 | - Refactored tests to be more meaningful 77 | - Upgraded to Ember-CLI 2.13.beta-4 for Babel 6 support 78 | 79 | ## [0.3.0] - 2016-02-08 80 | ### Changed 81 | - Update dependencies 82 | 83 | ### Removed 84 | - `ember-uuid` dependency, in favor of Ember's `guidFor` and keeping it simple 85 | 86 | ## [0.2.1] - 2016-01-20 87 | ### Added 88 | - Add promise label for better instrumentation with Ember Inspector 89 | 90 | ## [0.2.0] - 2016-01-18 91 | ### Changed 92 | - Migrate to ember-uuid. 93 | - Update toolset 94 | - Update docs 95 | 96 | ### Fixed 97 | - Breaking unit testing in consuming app 98 | 99 | ### Removed 100 | - The initializer 101 | 102 | ## [0.1.1] - 2015-10-19 103 | ### Added 104 | - Unit tests for server: resolve, reject, query scenarios 105 | 106 | ### Changed 107 | - Refactoring 108 | 109 | ## [0.1.0] - 2015-10-18 110 | ### Added 111 | - An initial release of this addon. 112 | 113 | [2.0.1]: https://github.com/raido/ember-window-messenger/compare/v2.0.0...v2.0.1 114 | [2.0.0]: https://github.com/raido/ember-window-messenger/compare/v1.2.2...v2.0.0 115 | [1.2.2]: https://github.com/raido/ember-window-messenger/compare/v1.2.1...v1.2.2 116 | [1.2.1]: https://github.com/raido/ember-window-messenger/compare/v1.2.0...v1.2.1 117 | [1.2.0]: https://github.com/raido/ember-window-messenger/compare/v1.1.0...v1.2.0 118 | [1.1.0]: https://github.com/raido/ember-window-messenger/compare/v1.0.1...v1.1.0 119 | [1.0.1]: https://github.com/raido/ember-window-messenger/compare/v1.0.0...v1.0.1 120 | [1.0.0]: https://github.com/raido/ember-window-messenger/compare/v0.3.0...v1.0.0 121 | [0.3.0]: https://github.com/raido/ember-window-messenger/compare/v0.2.1...v0.3.0 122 | [0.2.1]: https://github.com/raido/ember-window-messenger/compare/v0.2.0...v0.2.1 123 | [0.2.0]: https://github.com/raido/ember-window-messenger/compare/v0.1.0...v0.2.0 124 | [0.1.1]: https://github.com/raido/ember-window-messenger/compare/v0.1.0...v0.1.1 125 | [0.1.0]: https://github.com/raido/ember-window-messenger/tree/v0.1.0 126 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | * `git clone ` 6 | * `cd ember-window-messenger` 7 | * `yarn install` 8 | 9 | ## Linting 10 | 11 | * `yarn lint` 12 | * `yarn lint:fix` 13 | 14 | ## Running tests 15 | 16 | * `ember test` – Runs the test suite on the current Ember version 17 | * `ember test --server` – Runs the test suite in "watch mode" 18 | * `ember try:each` – Runs the test suite against multiple Ember versions 19 | 20 | ## Running the dummy application 21 | 22 | * `ember serve` 23 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 24 | 25 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-window-messenger 2 | 3 | [![CI](https://github.com/raido/ember-window-messenger/actions/workflows/ci.yml/badge.svg)](https://github.com/raido/ember-window-messenger/actions/workflows/ci.yml) 4 | [![npm version](https://badge.fury.io/js/ember-window-messenger.svg)](https://badge.fury.io/js/ember-window-messenger) 5 | 6 | This Ember addon is a lightweight postMessage client/server implementation. It is built on promises so the `fetch` and `rpc` methods can be used directly in your route `model()` hooks. 7 | 8 | For changelog see [CHANGELOG.md](https://github.com/raido/ember-window-messenger/blob/main/CHANGELOG.md) 9 | 10 | **It supports JSON only messages for now** 11 | 12 | 13 | Compatibility 14 | ------------------------------------------------------------------------------ 15 | 16 | * Ember.js v3.20 or above 17 | * Ember CLI v3.20 or above 18 | * Node.js v12 or above 19 | 20 | ## Usage 21 | 22 | `ember install ember-window-messenger` 23 | 24 | #### Configuration 25 | 26 | Add `target:origin` map to your `config/environment.js`. This effectively defines which targets (windows, frames) is your app communicating with. 27 | 28 | ```javascript 29 | APP: { 30 | // Here you can pass flags/options to your application instance 31 | // when it is created 32 | 'ember-window-messenger': { 33 | 'parent': 'http://localhost:4200', 34 | 'target-1': 'http://localhost:4200', 35 | 'target-2': 'http://localhost:4200', 36 | 'popup': 'http://localhost:4200' 37 | } 38 | } 39 | ``` 40 | 41 | This list is also used for validation, to check if message comes from an allowed origin. 42 | 43 | ### Examples 44 | 45 | If you dare, fire up the dummy app in this addon and test it out. Below are the basic examples, see dummy app for more. 46 | 47 | #### Setup server on parent 48 | 49 | ```javascript 50 | // app/service/your-server.js 51 | import Service, { inject as service } from '@ember/service'; 52 | 53 | export default class YourServerService extends Service { 54 | @service('window-messenger-server'); 55 | server; 56 | 57 | setup() { 58 | this.server.on('demo-data', this.onDemoDataRequest); 59 | } 60 | 61 | teardown() { 62 | this.server.off('demo-data', this.onDemoDataRequest); 63 | } 64 | 65 | onDemoDataRequest = (resolve, reject, query) => { 66 | resolve('Some data'); 67 | } 68 | } 69 | 70 | // app/routes/your-route.js 71 | import Route from '@ember/routing/route'; 72 | import { inject as service } from '@ember/service'; 73 | 74 | export default class YourRoute extends Route { 75 | @service('your-server'); 76 | yourServer; 77 | 78 | activate() { 79 | super.activate(); 80 | this.yourServer.setup(); 81 | } 82 | 83 | deactivate() { 84 | super.deactivate(); 85 | this.yourServer.teardown(); 86 | } 87 | } 88 | ``` 89 | 90 | #### Fetch from parent 91 | 92 | ```javascript 93 | import Route from '@ember/routing/route'; 94 | import { inject as service } from '@ember/service'; 95 | 96 | export default class YourRoute extends Route { 97 | @service('window-messenger-client') 98 | client; 99 | 100 | model() { 101 | return this.client.fetch('demo-data'); 102 | } 103 | } 104 | ``` 105 | 106 | #### Fetch from a specific target 107 | 108 | This can be used from parent window to frames/tabs communication. 109 | 110 | ```javascript 111 | import Route from '@ember/routing/route'; 112 | import { inject as service } from '@ember/service'; 113 | 114 | export default class YourRoute extends Route { 115 | @service('window-messenger-client') 116 | client; 117 | 118 | model() { 119 | return this.client.fetch('popup:demo-data'); 120 | } 121 | } 122 | ``` 123 | 124 | #### Execute RPC call 125 | 126 | Internally it is the same as `fetch`, but provides semantic sugar to your app code. 127 | 128 | ```javascript 129 | import Controller from '@ember/controller'; 130 | import { inject as service } from '@ember/service'; 131 | import { action } from '@ember/object'; 132 | 133 | export default class YourController extends Controller { 134 | @service('window-messenger-client') 135 | client; 136 | 137 | @action 138 | runMe() { 139 | this.client.rpc('start-worker').then((response) => { 140 | // handle response here 141 | }); 142 | } 143 | } 144 | ``` 145 | 146 | ### iFrames, popup windows 147 | 148 | If you want to communicate with an iframe or a popup window opened with `window.open`, then you have to register your window instance on the client with matching target name from `config/environment` map. 149 | 150 | #### iFrame 151 | 152 | ```javascript 153 | // app/components/x-frame.js 154 | import Component from '@glimmer/component'; 155 | import { inject as service } from '@ember/service'; 156 | 157 | export default class XFrameComponent extends Component { 158 | @service('window-messenger-client') 159 | client; 160 | 161 | register(frameElement) { 162 | this.client.addTarget(this.args.target, frameElement.contentWindow); 163 | }, 164 | 165 | unregister() { 166 | this.client.removeTarget(this.args.target); 167 | } 168 | } 169 | ``` 170 | ```html 171 | 172 | 173 | 178 | 179 | 180 | 181 | ``` 182 | #### Popup with window.open 183 | 184 | ```javascript 185 | // app/controller/your-controller.js 186 | import Controller from '@ember/controller'; 187 | import { inject as service } from '@ember/service'; 188 | import { action } from '@ember/object'; 189 | import { tracked } from '@glimmer/tracking'; 190 | 191 | export default class YourController extends Controller { 192 | @service('window-messenger-client') 193 | client; 194 | 195 | @tracked 196 | model = null; 197 | 198 | @action 199 | openPopup() { 200 | let win = window.open('/some/path', 'Example popup', 'toolbar=no,resizable=no,width=400,height=400'); 201 | this.client.addTarget('popup', win); 202 | } 203 | 204 | @action 205 | fetchFromPopup() { 206 | this.client.fetch('popup:some-data').then((name) => { 207 | this.model = name; 208 | }); 209 | } 210 | } 211 | ``` 212 | 213 | #### Open popup if it isn't already open, or has been closed by the user 214 | 215 | ```javascript 216 | // app/controller/your-controller.js 217 | import Controller from '@ember/controller'; 218 | import { inject as service } from '@ember/service'; 219 | import { action } from '@ember/object'; 220 | 221 | export default class YourController extends Controller { 222 | @service('window-messenger-client') 223 | client; 224 | 225 | @action 226 | openPopup() { 227 | if (!this.client.hasTarget('popup')) { 228 | let win = window.open('/some/path', 'Example popup', 'toolbar=no,resizable=no,width=400,height=400'); 229 | this.client.addTarget('popup', win); 230 | } 231 | } 232 | } 233 | ``` 234 | 235 | 236 | License 237 | ------------------------------------------------------------------------------ 238 | 239 | This project is licensed under the [MIT License](LICENSE.md). 240 | -------------------------------------------------------------------------------- /addon/services/client.ts: -------------------------------------------------------------------------------- 1 | import { typeOf } from '@ember/utils'; 2 | import RSVP, { Promise as EmberPromise } from 'rsvp'; 3 | import { assert } from '@ember/debug'; 4 | import { dasherize } from '@ember/string'; 5 | import { join as runJoin } from '@ember/runloop'; 6 | import { guidFor } from '@ember/object/internals'; 7 | import Service, { inject as service } from '@ember/service'; 8 | import WindowMessengerEventService from './window-messenger-events'; 9 | import { ServerResponseMessage } from './server'; 10 | import { buildWaiter } from '@ember/test-waiters'; 11 | 12 | const testWaiter = buildWaiter( 13 | 'ember-window-messenger:client-wait-for-response' 14 | ); 15 | 16 | export default class WindowMessengerClientService extends Service { 17 | @service('window-messenger-events') 18 | windowMessengerEvents!: WindowMessengerEventService; 19 | 20 | callbacks: CallbacksMap = {}; 21 | targets: { [key: string]: Window } = {}; 22 | 23 | targetOriginMap: { [key: string]: string } = {}; // This is set from environment config automatically 24 | 25 | /** 26 | * Add new contentWindow target 27 | * 28 | * @param {String} name String name of the target 29 | * @param {contentWindow} targetWindow DOM contentWindow 30 | * @public 31 | */ 32 | addTarget(name: string, targetWindow: Window) { 33 | this.targets[name] = targetWindow; 34 | } 35 | 36 | /** 37 | * Remove contentWindow target 38 | * 39 | * @param {String} name 40 | * @public 41 | */ 42 | removeTarget(name: string) { 43 | delete this.targets[name]; 44 | } 45 | 46 | /** 47 | * Tests whether a target is currently registered and open. 48 | * 49 | * @param {String} name 50 | * @public 51 | * 52 | * @return {Boolean} 53 | */ 54 | hasTarget(name: string) { 55 | if (!(name in this.targets)) { 56 | return false; 57 | } 58 | return this.targets[name].opener && !this.targets[name].opener.closed; 59 | } 60 | 61 | /* 62 | * @private 63 | * @return {Window} 64 | */ 65 | _getWindow(): Window { 66 | return window; 67 | } 68 | 69 | /** 70 | * Parse : uri 71 | * 72 | * @param {String} uri 73 | * @return {Object} 74 | */ 75 | _parseURI(uri: string) { 76 | const split = uri.split(':'); 77 | const resource = split[1] || split[0]; 78 | return { 79 | target: split[1] ? split[0] : 'parent', 80 | resource: dasherize(resource), 81 | }; 82 | } 83 | 84 | /** 85 | * Determine if resource target is parent or not 86 | * 87 | * @param {String} target 88 | * @private 89 | * @return {Boolean} 90 | */ 91 | _isTargetParent(target: string) { 92 | const win = this._getWindow(); 93 | const isEmbedded = win.self !== (win.top || win.opener); 94 | return isEmbedded || target === 'parent'; 95 | } 96 | 97 | /** 98 | * @private 99 | * @return {Window} 100 | */ 101 | _getWindowParent(): Window { 102 | const win = this._getWindow(); 103 | return win.opener || win.parent; 104 | } 105 | 106 | /** 107 | * @param {String} target 108 | * @private 109 | * @return {Object} 110 | */ 111 | _targetFor(target: string) { 112 | return this._isTargetParent(target) 113 | ? this._getWindowParent() 114 | : this.targets[target]; 115 | } 116 | 117 | /** 118 | * @param {String} target 119 | * @private 120 | * @return {String} 121 | */ 122 | _targetOriginFor(target: string) { 123 | return this.targetOriginMap[target]; 124 | } 125 | 126 | /** 127 | * Fetch data from server side 128 | * 129 | * @param {String} path 130 | * @param {Object} queryParams 131 | * @return {Promise} 132 | */ 133 | fetch(path: string, queryParams?: Query & Q): RSVP.Promise { 134 | const uri = this._parseURI(path); 135 | const targetName = uri.target; 136 | const queryObject = queryParams ? { ...queryParams } : {}; 137 | 138 | const targetOrigin = this._targetOriginFor(targetName); 139 | assert( 140 | `Target origin for target: ${targetName} does not exist`, 141 | targetOrigin 142 | ); 143 | 144 | const target = this._targetFor(targetName); 145 | assert(`Target window is not registered for: ${targetName}`, target); 146 | 147 | return new EmberPromise((resolve, reject) => { 148 | this._lazyRegisterMessagesListener(); 149 | 150 | const uuid = guidFor(queryObject); 151 | const query = { 152 | id: uuid, 153 | type: 'ember-window-messenger-client', 154 | name: uri.resource, 155 | query: queryObject, 156 | }; 157 | 158 | this.callbacks[uuid] = { 159 | testWaiterToken: testWaiter.beginAsync(uuid), 160 | success: (json) => { 161 | runJoin(null, resolve, json); 162 | }, 163 | error: (json) => { 164 | runJoin(null, reject, json); 165 | }, 166 | }; 167 | target.postMessage(JSON.stringify(query), targetOrigin); 168 | }, `ember-window-messenger: ${path}`); 169 | } 170 | 171 | /** 172 | * Fetch data from server side 173 | * 174 | * @param {String} path 175 | * @param {Object} queryParams 176 | * @return {Promise} 177 | */ 178 | rpc(path: string, queryParams?: Query & Q) { 179 | return this.fetch(path, queryParams); 180 | } 181 | 182 | /** 183 | * Handle message event from Messenger Events 184 | * 185 | * @private 186 | * @param {Object} message 187 | */ 188 | _onMessage = (message: ServerResponseMessage) => { 189 | const { response, id, error } = message; 190 | const inQueue = this.callbacks[id]; 191 | 192 | if (typeOf(inQueue) === 'object') { 193 | if (error) { 194 | inQueue.error(response); 195 | } else { 196 | inQueue.success(response); 197 | } 198 | this.finishAsyncTestWaiter(inQueue.testWaiterToken); 199 | } 200 | delete this.callbacks[id]; 201 | }; 202 | 203 | _lazyRegisterMessagesListener() { 204 | this.windowMessengerEvents.on( 205 | 'from:ember-window-messenger-server', 206 | this._onMessage 207 | ); 208 | } 209 | 210 | private finishAsyncTestWaiter(token: string) { 211 | testWaiter.endAsync(token); 212 | } 213 | 214 | willDestroy() { 215 | super.willDestroy(); 216 | Object.keys(this.callbacks).forEach((uuid) => { 217 | const handler = this.callbacks[uuid]; 218 | this.finishAsyncTestWaiter(handler.testWaiterToken); 219 | }); 220 | this.windowMessengerEvents.off( 221 | 'from:ember-window-messenger-server', 222 | this._onMessage 223 | ); 224 | } 225 | } 226 | 227 | type Query = Record; 228 | type Payload = 229 | | Record 230 | | string 231 | | boolean 232 | | number 233 | | null 234 | | undefined; 235 | type SuccessMethod = (json: Payload) => void; 236 | type ErrorMethod = (json: Payload) => void; 237 | 238 | type CallbacksMap = { 239 | [key: string]: { 240 | testWaiterToken: string; 241 | success: SuccessMethod; 242 | error: ErrorMethod; 243 | }; 244 | }; 245 | 246 | declare module '@ember/service' { 247 | interface Registry { 248 | 'window-messenger-client': WindowMessengerClientService; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /addon/services/server.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@ember/debug'; 2 | import { guidFor } from '@ember/object/internals'; 3 | import Service, { inject as service } from '@ember/service'; 4 | import WindowMessengerEventService, { 5 | ParsedTransmittedMessage as WindowMessengerEventsParsedTransmittedMessage, 6 | TransmittedMessageEvent, 7 | } from './window-messenger-events'; 8 | 9 | export default class WindowMessengerServerService extends Service { 10 | @service('window-messenger-events') 11 | windowMessengerEvents!: WindowMessengerEventService; 12 | 13 | private _registeredResources: { 14 | [key: string]: OnEventCallback; 15 | } = {}; 16 | 17 | /** 18 | * Send response to back to client 19 | * 20 | * @param {String} uuid 21 | * @param {Object} payload 22 | * @param {MessageEvent} event 23 | * @param {Boolean} hasError 24 | */ 25 | _respond( 26 | uuid: string, 27 | payload: MessagePayload, 28 | event: TransmittedMessageEvent, 29 | hasError: boolean 30 | ) { 31 | const query: ServerResponseMessage = { 32 | id: uuid, 33 | type: 'ember-window-messenger-server', 34 | response: payload, 35 | error: hasError, 36 | }; 37 | assert( 38 | "ember-window-messenger :: window-messenger-server :: cannot post respond, 'event.source' is null", 39 | event.source 40 | ); 41 | // Not really sure why TS fails with method overload matching. 42 | // Related issues on Github - https://github.com/Microsoft/TypeScript/issues/30042 43 | 44 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 45 | // @ts-ignore 46 | event.source?.postMessage(JSON.stringify(query), event.origin); 47 | } 48 | 49 | /** 50 | * Handle message that we got from Messenger Events 51 | * 52 | * @param {Object} message 53 | * @param {Object} event 54 | */ 55 | _onMessage = ( 56 | message: WindowMessengerEventsParsedTransmittedMessage & 57 | ParsedTransmittedMessage, 58 | event: TransmittedMessageEvent 59 | ) => { 60 | this.trigger( 61 | message.name, 62 | (response) => { 63 | this._respond(message.id, response, event, false); 64 | }, 65 | (response) => { 66 | this._respond(message.id, response, event, true); 67 | }, 68 | message.query 69 | ); 70 | }; 71 | 72 | _lazyRegisterMessagesListener() { 73 | this.windowMessengerEvents.on( 74 | 'from:ember-window-messenger-client', 75 | this._onMessage 76 | ); 77 | } 78 | 79 | willDestroy() { 80 | super.willDestroy(); 81 | this.windowMessengerEvents.off( 82 | 'from:ember-window-messenger-client', 83 | this._onMessage 84 | ); 85 | } 86 | 87 | on( 88 | resourceName: string, 89 | callback: OnEventCallback 90 | ) { 91 | this._lazyRegisterMessagesListener(); 92 | this._registeredResources[resourceName] = callback; 93 | } 94 | 95 | has(resourceName: string): boolean { 96 | return resourceName in this._registeredResources; 97 | } 98 | 99 | off( 100 | resourceName: string, 101 | callback: OnEventCallback 102 | ) { 103 | if (this._registeredResources[resourceName] === callback) { 104 | delete this._registeredResources[resourceName]; 105 | } 106 | } 107 | 108 | trigger( 109 | resourceName: string, 110 | resolve: ResolveMethod, 111 | reject: RejectMethod, 112 | query: Query & Q 113 | ) { 114 | const cb = this._registeredResources[resourceName]; 115 | if (cb) { 116 | cb(resolve, reject, query); 117 | } 118 | } 119 | } 120 | 121 | type MessagePayload = 122 | | Record 123 | | string 124 | | boolean 125 | | number 126 | | null 127 | | undefined; 128 | type ResolveMethod = ( 129 | data?: MessagePayload & ResolvedData 130 | ) => void; 131 | type RejectMethod = ( 132 | data?: MessagePayload & RejectedData 133 | ) => void; 134 | type Query = MessagePayload; 135 | 136 | interface ParsedTransmittedMessage { 137 | id: ReturnType; 138 | name: string; 139 | query: Query; 140 | } 141 | 142 | export type ServerResponseMessage = { 143 | id: ReturnType; 144 | type: 'ember-window-messenger-server'; 145 | response: MessagePayload | undefined; 146 | error: boolean; 147 | }; 148 | 149 | export type OnEventCallback = ( 150 | resolve: ResolveMethod, 151 | reject: RejectMethod, 152 | query: Query & Q 153 | ) => void; 154 | 155 | declare module '@ember/service' { 156 | interface Registry { 157 | 'window-messenger-server': WindowMessengerServerService; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /addon/services/window-messenger-events.ts: -------------------------------------------------------------------------------- 1 | import { A } from '@ember/array'; 2 | import Service from '@ember/service'; 3 | 4 | export default class WindowMessengerEventService extends Service { 5 | registeredEvents: { [key: string]: OnEventCallback } = {}; 6 | 7 | targetOriginMap: { [key: string]: string } = {}; // This is set from environment config automatically 8 | 9 | get allowedOrigins() { 10 | const map = this.targetOriginMap; 11 | return A( 12 | Object.keys(map).map((key) => { 13 | return map[key]; 14 | }) 15 | ); 16 | } 17 | 18 | constructor() { 19 | super(); 20 | window.addEventListener('message', this._onMessage); 21 | } 22 | 23 | on(eventName: string, callback: OnEventCallback) { 24 | if (eventName in this.registeredEvents) { 25 | return; 26 | } 27 | this.registeredEvents[eventName] = callback; 28 | } 29 | 30 | off(eventName: string, callback: OnEventCallback) { 31 | if (this.registeredEvents[eventName] === callback) { 32 | delete this.registeredEvents[eventName]; 33 | } 34 | } 35 | 36 | trigger( 37 | eventName: string, 38 | message: ParsedTransmittedMessage, 39 | event: TransmittedMessageEvent 40 | ) { 41 | const cb = this.registeredEvents[eventName]; 42 | if (cb) { 43 | cb(message, event); 44 | } 45 | } 46 | 47 | /** 48 | * Check if message origin is allowed 49 | * 50 | * @param {String} origin 51 | * @private 52 | * @return {Boolean} 53 | */ 54 | 55 | _isOriginAllowed(origin: string) { 56 | return this.allowedOrigins.includes(origin); 57 | } 58 | 59 | _parseMessage(data: TransmittedMessage): ParsedTransmittedMessage | null { 60 | let message = null; 61 | if (typeof data === 'string') { 62 | try { 63 | message = JSON.parse(data); 64 | } catch (e) { 65 | /* Ignored */ 66 | } 67 | } 68 | return message; 69 | } 70 | 71 | _onMessage = (event: TransmittedMessageEvent) => { 72 | if (this._isOriginAllowed(event.origin)) { 73 | const message = this._parseMessage(event.data); 74 | if (message !== null) { 75 | this.trigger(`from:${message.type}`, message, event); 76 | } 77 | } 78 | return null; 79 | }; 80 | 81 | willDestroy() { 82 | super.willDestroy(); 83 | // Remove event listener when this service is getting destroyed 84 | window.removeEventListener('message', this._onMessage); 85 | } 86 | } 87 | 88 | type TransmittedMessage = string; 89 | export type TransmittedMessageEvent = MessageEvent; 90 | 91 | export type OnEventCallback

= ( 92 | message: ParsedTransmittedMessage & P, 93 | event: MessageEvent 94 | ) => void; 95 | 96 | export interface ParsedTransmittedMessage { 97 | type: string; 98 | } 99 | 100 | declare module '@ember/service' { 101 | interface Registry { 102 | 'window-messenger-events': WindowMessengerEventService; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/services/window-messenger-client.js: -------------------------------------------------------------------------------- 1 | import config from '../config/environment'; 2 | import Client from 'ember-window-messenger/services/client'; 3 | 4 | export default class WindowMessengerClientService extends Client { 5 | targetOriginMap = config.APP['ember-window-messenger'] || {}; 6 | } 7 | -------------------------------------------------------------------------------- /app/services/window-messenger-events.js: -------------------------------------------------------------------------------- 1 | import config from '../config/environment'; 2 | import EventsService from 'ember-window-messenger/services/window-messenger-events'; 3 | 4 | export default class WindowMessengerEventsService extends EventsService { 5 | targetOriginMap = config.APP['ember-window-messenger'] || {}; 6 | } 7 | -------------------------------------------------------------------------------- /app/services/window-messenger-server.js: -------------------------------------------------------------------------------- 1 | export { default } from 'ember-window-messenger/services/server'; 2 | -------------------------------------------------------------------------------- /config/deploy.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = function (deployTarget) { 5 | let ENV = { 6 | build: {}, 7 | // include other plugin configuration that applies to all deploy targets here 8 | }; 9 | 10 | if (deployTarget === 'development') { 11 | ENV.build.environment = 'development'; 12 | // configure other plugins for development deploy target here 13 | } 14 | 15 | if (deployTarget === 'staging') { 16 | ENV.build.environment = 'production'; 17 | // configure other plugins for staging deploy target here 18 | } 19 | 20 | if (deployTarget === 'production') { 21 | ENV.build.environment = 'production'; 22 | // configure other plugins for production deploy target here 23 | } 24 | 25 | // Note: if you need to build some configuration asynchronously, you can return 26 | // a promise that resolves with the ENV object instead of returning the 27 | // ENV object synchronously. 28 | return ENV; 29 | }; 30 | -------------------------------------------------------------------------------- /config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | useYarn: true, 9 | scenarios: [ 10 | { 11 | name: 'ember-lts-3.20', 12 | npm: { 13 | devDependencies: { 14 | 'ember-source': '~3.20.5', 15 | }, 16 | }, 17 | }, 18 | { 19 | name: 'ember-lts-3.24', 20 | npm: { 21 | devDependencies: { 22 | 'ember-source': '~3.24.3', 23 | }, 24 | }, 25 | }, 26 | { 27 | name: 'ember-release', 28 | npm: { 29 | devDependencies: { 30 | 'ember-source': await getChannelURL('release'), 31 | }, 32 | }, 33 | }, 34 | { 35 | name: 'ember-beta', 36 | npm: { 37 | devDependencies: { 38 | 'ember-source': await getChannelURL('beta'), 39 | }, 40 | }, 41 | }, 42 | { 43 | name: 'ember-canary', 44 | npm: { 45 | devDependencies: { 46 | 'ember-source': await getChannelURL('canary'), 47 | }, 48 | }, 49 | }, 50 | { 51 | name: 'ember-default-with-jquery', 52 | env: { 53 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 54 | 'jquery-integration': true, 55 | }), 56 | }, 57 | npm: { 58 | devDependencies: { 59 | '@ember/jquery': '^1.1.0', 60 | }, 61 | }, 62 | }, 63 | { 64 | name: 'ember-classic', 65 | env: { 66 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 67 | 'application-template-wrapper': true, 68 | 'default-async-observers': false, 69 | 'template-only-glimmer-components': false, 70 | }), 71 | }, 72 | npm: { 73 | devDependencies: { 74 | 'ember-source': '~3.28.0', 75 | }, 76 | ember: { 77 | edition: 'classic', 78 | }, 79 | }, 80 | }, 81 | embroiderSafe(), 82 | embroiderOptimized(), 83 | ], 84 | }; 85 | }; 86 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (/* environment, appConfig */) { 4 | return {}; 5 | }; 6 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon'); 4 | 5 | module.exports = function (defaults) { 6 | let app = new EmberAddon(defaults, { 7 | // Add options here 8 | }); 9 | 10 | /* 11 | This build file specifies the options for the dummy test app of this 12 | addon, located in `/tests/dummy` 13 | This build file does *not* influence how the addon or the app using it 14 | behave. You most likely want to be modifying `./index.js` or app's build file 15 | */ 16 | 17 | app.import({ test: 'vendor/ie11-promise.js' }); 18 | 19 | const { maybeEmbroider } = require('@embroider/test-setup'); 20 | return maybeEmbroider(app, { 21 | skipBabel: [ 22 | { 23 | package: 'qunit', 24 | }, 25 | ], 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | name: require('./package').name, 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-window-messenger", 3 | "version": "3.3.0", 4 | "description": "Simple window postMessage Ember addon", 5 | "keywords": [ 6 | "ember-addon", 7 | "postMessage", 8 | "communication" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/raido/ember-window-messenger" 13 | }, 14 | "license": "MIT", 15 | "author": "Raido Kuli ", 16 | "directories": { 17 | "doc": "doc", 18 | "test": "tests" 19 | }, 20 | "scripts": { 21 | "build": "ember build --environment=production", 22 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", 23 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", 24 | "lint:hbs": "ember-template-lint .", 25 | "lint:hbs:fix": "ember-template-lint . --fix", 26 | "lint:js": "eslint . --cache", 27 | "lint:js:fix": "eslint . --fix", 28 | "start": "ember serve", 29 | "test": "npm-run-all lint test:*", 30 | "test:ember": "ember test", 31 | "test:ember-compatibility": "ember try:each", 32 | "ts:build": "tsc --build", 33 | "prepack": "ember ts:precompile", 34 | "postpack": "ember ts:clean" 35 | }, 36 | "dependencies": { 37 | "@ember/test-waiters": "^3.0.0", 38 | "ember-cli-babel": "^7.26.6", 39 | "ember-cli-typescript": "^4.2.1" 40 | }, 41 | "devDependencies": { 42 | "@ember/optional-features": "^2.0.0", 43 | "@ember/render-modifiers": "^2.0.0", 44 | "@ember/test-helpers": "^2.4.2", 45 | "@embroider/test-setup": "~0.47.2", 46 | "@glimmer/component": "^1.0.4", 47 | "@glimmer/tracking": "^1.0.4", 48 | "@types/ember-qunit": "^3.4.15", 49 | "@types/ember-resolver": "^5.0.10", 50 | "@types/ember__application": "^3.16.3", 51 | "@types/ember__array": "^3.16.4", 52 | "@types/ember__component": "^3.16.6", 53 | "@types/ember__controller": "^3.16.6", 54 | "@types/ember__debug": "^3.16.5", 55 | "@types/ember__engine": "^3.16.3", 56 | "@types/ember__error": "^3.16.1", 57 | "@types/ember__object": "^3.12.6", 58 | "@types/ember__polyfills": "^3.12.1", 59 | "@types/ember__routing": "^3.16.15", 60 | "@types/ember__runloop": "^3.16.3", 61 | "@types/ember__service": "^3.16.1", 62 | "@types/ember__string": "^3.16.3", 63 | "@types/ember__template": "^3.16.1", 64 | "@types/ember__test": "^3.16.1", 65 | "@types/ember__test-helpers": "^2.0.2", 66 | "@types/ember__utils": "^3.16.2", 67 | "@types/htmlbars-inline-precompile": "^1.0.1", 68 | "@types/qunit": "^2.11.2", 69 | "@types/rsvp": "^4.0.4", 70 | "@typescript-eslint/eslint-plugin": "^5.5.0", 71 | "@typescript-eslint/parser": "^5.5.0", 72 | "broccoli-asset-rev": "^3.0.0", 73 | "ember-auto-import": "~2.2.4", 74 | "ember-cli": "~3.28.4", 75 | "ember-cli-dependency-checker": "^3.2.0", 76 | "ember-cli-deploy": "^1.0.2", 77 | "ember-cli-deploy-build": "~2.0.0", 78 | "ember-cli-deploy-git": "~1.3.4", 79 | "ember-cli-deploy-git-ci": "^1.0.1", 80 | "ember-cli-htmlbars": "^5.7.1", 81 | "ember-cli-inject-live-reload": "^2.1.0", 82 | "ember-cli-sri": "^2.1.1", 83 | "ember-cli-terser": "^4.0.2", 84 | "ember-disable-prototype-extensions": "^1.1.3", 85 | "ember-export-application-global": "^2.0.1", 86 | "ember-load-initializers": "^2.1.2", 87 | "ember-maybe-import-regenerator": "~1.0.0", 88 | "ember-page-title": "^6.2.2", 89 | "ember-qunit": "^5.1.4", 90 | "ember-resolver": "^8.0.2", 91 | "ember-source": "~3.28.0", 92 | "ember-source-channel-url": "^3.0.0", 93 | "ember-template-lint": "^3.6.0", 94 | "ember-try": "^1.4.0", 95 | "eslint": "^7.32.0", 96 | "eslint-config-prettier": "^8.3.0", 97 | "eslint-plugin-ember": "^10.5.4", 98 | "eslint-plugin-node": "^11.1.0", 99 | "eslint-plugin-prettier": "^3.4.1", 100 | "eslint-plugin-qunit": "^6.2.0", 101 | "loader.js": "^4.7.0", 102 | "npm-run-all": "^4.1.5", 103 | "prettier": "^2.3.2", 104 | "qunit": "^2.16.0", 105 | "qunit-dom": "^1.6.0", 106 | "typescript": "^4.5.2", 107 | "webpack": "5" 108 | }, 109 | "engines": { 110 | "node": "12.* || 14.* || >= 16" 111 | }, 112 | "ember": { 113 | "edition": "octane" 114 | }, 115 | "ember-addon": { 116 | "configPath": "tests/dummy/config" 117 | }, 118 | "homepage": "https://raido.github.io/ember-window-messenger", 119 | "volta": { 120 | "node": "12.22.7", 121 | "yarn": "1.22.17" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | Firefox: { 23 | ci: ['--headless'], 24 | }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /tests/dummy/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'dummy/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /tests/dummy/app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raido/ember-window-messenger/2bf52399ee5421ad0cbde88871d166bd7dbdec82/tests/dummy/app/components/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/components/x-iframe.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/dummy/app/components/x-iframe.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Component from '@glimmer/component'; 3 | import { action } from '@ember/object'; 4 | 5 | export default class XIframeComponent extends Component { 6 | @service('window-messenger-client') 7 | client; 8 | 9 | @action 10 | register(element) { 11 | this.client.addTarget(this.args.target, element.contentWindow); 12 | } 13 | 14 | @action 15 | unregister() { 16 | this.client.removeTarget(this.args.target); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/dummy/app/config/environment.d.ts: -------------------------------------------------------------------------------- 1 | export default config; 2 | 3 | /** 4 | * Type declarations for 5 | * import config from 'my-app/config/environment' 6 | */ 7 | declare const config: { 8 | environment: string; 9 | modulePrefix: string; 10 | podModulePrefix: string; 11 | locationType: string; 12 | rootURL: string; 13 | APP: Record; 14 | }; 15 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raido/ember-window-messenger/2bf52399ee5421ad0cbde88871d166bd7dbdec82/tests/dummy/app/controllers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class ApplicationController extends Controller { 5 | @service('router') 6 | router; 7 | 8 | get hideHeader() { 9 | return this.router.currentURL.indexOf('client-') !== -1; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/client-server-one.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { inject as service } from '@ember/service'; 3 | import { action } from '@ember/object'; 4 | import { tracked } from '@glimmer/tracking'; 5 | 6 | export default class ClientServerOneController extends Controller { 7 | @service('window-messenger-server') 8 | server; 9 | 10 | @service('window-messenger-client') 11 | client; 12 | 13 | @tracked 14 | model = null; 15 | 16 | @action 17 | askParent() { 18 | this.client.fetch('demo-data', { action: 'yes' }).then((response) => { 19 | this.model = JSON.stringify(response); 20 | }); 21 | } 22 | 23 | @action 24 | askParentFail() { 25 | this.client.fetch('demo-data', { action: 'nope' }).catch((response) => { 26 | this.model = response; 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/dummy/app/controllers/demo.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { inject as service } from '@ember/service'; 3 | import { action } from '@ember/object'; 4 | import { tracked } from '@glimmer/tracking'; 5 | 6 | export default class DemoController extends Controller { 7 | @service('window-messenger-server') 8 | server; 9 | 10 | @service('window-messenger-client') 11 | client; 12 | 13 | @service('router') 14 | router; 15 | 16 | @tracked 17 | popup = false; 18 | 19 | @tracked 20 | model = null; 21 | 22 | get clientServerOneSrc() { 23 | return this.router.urlFor('client-server-one'); 24 | } 25 | 26 | get clientServerTwoSrc() { 27 | return this.router.urlFor('client-server-two'); 28 | } 29 | 30 | @action 31 | askTarget1() { 32 | this.client.fetch('target-1:name').then((name) => { 33 | this.model = name; 34 | }); 35 | } 36 | 37 | @action 38 | openPopup() { 39 | let win = window.open( 40 | this.router.urlFor('client-server-two.example'), 41 | 'Example popup', 42 | 'toolbar=no,resizable=no,width=400,height=400' 43 | ); 44 | this.client.addTarget('popup', win); 45 | this.popup = true; 46 | } 47 | 48 | @action 49 | askPopup() { 50 | this.client.fetch('popup:popup-name').then((name) => { 51 | this.model = name; 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/dummy/app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raido/ember-window-messenger/2bf52399ee5421ad0cbde88871d166bd7dbdec82/tests/dummy/app/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raido/ember-window-messenger/2bf52399ee5421ad0cbde88871d166bd7dbdec82/tests/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'dummy/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () { 10 | // dummy app 11 | this.route('client-server-one', {}); 12 | this.route('client-server-two', {}, function () { 13 | this.route('example', {}); 14 | }); 15 | this.route('demo', {}); 16 | 17 | // 404 18 | this.route('not-found', { path: '/*path' }); 19 | }); 20 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raido/ember-window-messenger/2bf52399ee5421ad0cbde88871d166bd7dbdec82/tests/dummy/app/routes/.gitkeep -------------------------------------------------------------------------------- /tests/dummy/app/routes/client-server-one.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class ClientServerOneRoute extends Route { 4 | setupController(controller) { 5 | controller.server.on('name', (resolve) => { 6 | resolve('My name is: Target 1 - client/server one'); 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/client-server-two.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Route from '@ember/routing/route'; 3 | 4 | export default class ClientServerTwoRoute extends Route { 5 | @service('window-messenger-server') 6 | server; 7 | 8 | activate() { 9 | this.server.on('popup-name', (resolve) => { 10 | resolve('I am a popup window :)'); 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/client-server-two/example.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Route from '@ember/routing/route'; 3 | 4 | export default class ClientServerTwoExampleRoute extends Route { 5 | @service('window-messenger-client') 6 | client; 7 | 8 | model() { 9 | return this.client.fetch('demo-data'); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/dummy/app/routes/demo.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class DemoRoute extends Route { 4 | setupController(controller) { 5 | controller.server.on('demo-data', (resolve, reject, query) => { 6 | this.model = JSON.stringify(query); 7 | if (query.action === 'nope') { 8 | reject('No can do'); 9 | } else { 10 | resolve({ 11 | name: 'Demo', 12 | version: '1.2.3', 13 | }); 14 | } 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/dummy/app/styles/app.css: -------------------------------------------------------------------------------- 1 | iframe { 2 | width: 500px; 3 | height: 500px; 4 | } 5 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{#unless this.hideHeader}} 2 | {{page-title 'ember-window-messenger'}} 3 |

ember-window-messenger

4 | {{/unless}} 5 | 6 | {{outlet}} -------------------------------------------------------------------------------- /tests/dummy/app/templates/client-server-one.hbs: -------------------------------------------------------------------------------- 1 |

This here is client/server one

2 | 3 |

Got response: {{this.model}}

4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/dummy/app/templates/client-server-two.hbs: -------------------------------------------------------------------------------- 1 |

This here is client/server two

2 | 3 | Route model hook example 4 | 5 | {{outlet}} -------------------------------------------------------------------------------- /tests/dummy/app/templates/client-server-two/example.hbs: -------------------------------------------------------------------------------- 1 |

Name = {{this.model.name}}

2 |

Version= {{this.model.version}}

3 | 4 | {{outlet}} -------------------------------------------------------------------------------- /tests/dummy/app/templates/demo.hbs: -------------------------------------------------------------------------------- 1 |
2 | 6 | 10 |
11 | 12 |

Got server query: {{this.model}}

13 | 14 | 15 | 16 | 17 | {{#if this.popup}} 18 | 19 | {{/if}} -------------------------------------------------------------------------------- /tests/dummy/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 | See demo -------------------------------------------------------------------------------- /tests/dummy/app/templates/not-found.hbs: -------------------------------------------------------------------------------- 1 |

Not found

2 |

This page doesn"t exist. Head home?

-------------------------------------------------------------------------------- /tests/dummy/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "3.28.4", 7 | "blueprints": [ 8 | { 9 | "name": "addon", 10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output", 11 | "codemodsSource": "ember-addon-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": ["--yarn", "--no-welcome"] 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tests/dummy/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const TESTS_DEV_SERVER_PORT = 4 | process.env.EMBER_CLI_INJECT_LIVE_RELOAD_PORT || 7357; 5 | 6 | module.exports = function (environment) { 7 | let ENV = { 8 | modulePrefix: 'dummy', 9 | environment, 10 | rootURL: '/', 11 | locationType: 'auto', 12 | EmberENV: { 13 | FEATURES: { 14 | // Here you can enable experimental features on an ember canary build 15 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 16 | }, 17 | EXTEND_PROTOTYPES: { 18 | // Prevent Ember Data from overriding Date.parse. 19 | Date: false, 20 | }, 21 | }, 22 | 23 | APP: { 24 | // Here you can pass flags/options to your application instance 25 | // when it is created 26 | 'ember-window-messenger': { 27 | parent: 'http://localhost:4200', 28 | 'target-1': 'http://localhost:4200', 29 | 'target-2': 'http://localhost:4200', 30 | popup: 'http://localhost:4200', 31 | }, 32 | }, 33 | }; 34 | 35 | if (environment === 'development') { 36 | // ENV.APP.LOG_RESOLVER = true; 37 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 38 | // ENV.APP.LOG_TRANSITIONS = true; 39 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 40 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 41 | } 42 | 43 | if (environment === 'test') { 44 | // Testem prefers this... 45 | ENV.locationType = 'none'; 46 | 47 | // keep test console output quieter 48 | ENV.APP.LOG_ACTIVE_GENERATION = false; 49 | ENV.APP.LOG_VIEW_LOOKUPS = false; 50 | 51 | ENV.APP.rootElement = '#ember-testing'; 52 | ENV.APP.autoboot = false; 53 | 54 | ENV.APP['ember-window-messenger'] = { 55 | parent: `http://localhost:${TESTS_DEV_SERVER_PORT}`, 56 | 'target-1': `http://localhost:${TESTS_DEV_SERVER_PORT}`, 57 | }; 58 | } 59 | 60 | if (environment === 'production') { 61 | ENV.rootURL = '/ember-window-messenger'; 62 | ENV.locationType = 'hash'; 63 | ENV.APP['ember-window-messenger'] = { 64 | parent: 'https://raido.github.io', 65 | 'target-1': 'https://raido.github.io', 66 | 'target-2': 'https://raido.github.io', 67 | popup: 'https://raido.github.io', 68 | }; 69 | } 70 | 71 | return ENV; 72 | }; 73 | -------------------------------------------------------------------------------- /tests/dummy/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /tests/dummy/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | // Ember's browser support policy is changing, and IE11 support will end in 10 | // v4.0 onwards. 11 | // 12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy 13 | // 14 | // If you need IE11 support on a version of Ember that still offers support 15 | // for it, uncomment the code block below. 16 | 17 | const isCI = Boolean(process.env.CI); 18 | const isProduction = process.env.EMBER_ENV === 'production'; 19 | 20 | if (isCI || isProduction) { 21 | browsers.push('ie 11'); 22 | } 23 | 24 | module.exports = { 25 | browsers, 26 | }; 27 | -------------------------------------------------------------------------------- /tests/dummy/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /tests/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raido/ember-window-messenger/2bf52399ee5421ad0cbde88871d166bd7dbdec82/tests/helpers/.gitkeep -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dummy Tests 7 | 8 | 9 | 10 | {{content-for "head"}} {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} {{content-for "test-head-footer"}} 17 | 18 | 19 | {{content-for "body"}} {{content-for "test-body"}} 20 | 21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {{content-for "body-footer"}} {{content-for "test-body-footer"}} 35 | 36 | 37 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'dummy/app'; 2 | import config from 'dummy/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /tests/unit/services/window-messenger-client-test.ts: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | import { settled } from '@ember/test-helpers'; 4 | import WindowMessengerClientService from 'ember-window-messenger/services/client'; 5 | import WindowMessengerServerService from 'ember-window-messenger/services/server'; 6 | 7 | module('Unit | Service | window messenger client', function (hooks) { 8 | setupTest(hooks); 9 | 10 | test('it should receive response from the server for targeted request', async function (assert) { 11 | assert.expect(1); 12 | const client: WindowMessengerClientService = this.owner.lookup( 13 | 'service:window-messenger-client' 14 | ); 15 | client.addTarget('target-1', window); 16 | const server: WindowMessengerServerService = this.owner.lookup( 17 | 'service:window-messenger-server' 18 | ); 19 | 20 | server.on('client-request', (resolve) => { 21 | resolve('Hello'); 22 | }); 23 | await client 24 | .fetch('target-1:client-request') 25 | .then((response) => assert.equal(response, 'Hello')); 26 | }); 27 | 28 | test('it should throw error if target window is not registered', async function (assert) { 29 | const client: WindowMessengerClientService = this.owner.lookup( 30 | 'service:window-messenger-client' 31 | ); 32 | 33 | assert.throws(() => { 34 | client.fetch('target-1:client-request'); 35 | }, /Target window is not registered for: target-1/); 36 | }); 37 | 38 | test('it should add and remove target', async function (assert) { 39 | const client: WindowMessengerClientService = this.owner.lookup( 40 | 'service:window-messenger-client' 41 | ); 42 | client.addTarget('target-1', window); 43 | client.removeTarget('target-1'); 44 | 45 | assert.throws(() => { 46 | client.fetch('target-1:client-request'); 47 | }, /Target window is not registered for: target-1/); 48 | }); 49 | 50 | test('it should receive response from the server', async function (assert) { 51 | assert.expect(1); 52 | const client: WindowMessengerClientService = this.owner.lookup( 53 | 'service:window-messenger-client' 54 | ); 55 | const server: WindowMessengerServerService = this.owner.lookup( 56 | 'service:window-messenger-server' 57 | ); 58 | 59 | server.on('client-request', (resolve) => { 60 | resolve('Hello'); 61 | }); 62 | await client 63 | .fetch('client-request') 64 | .then((response) => assert.equal(response, 'Hello')); 65 | }); 66 | 67 | test('it should receive response from the server - rpc', async function (assert) { 68 | assert.expect(1); 69 | const client: WindowMessengerClientService = this.owner.lookup( 70 | 'service:window-messenger-client' 71 | ); 72 | const server: WindowMessengerServerService = this.owner.lookup( 73 | 'service:window-messenger-server' 74 | ); 75 | 76 | server.on('client-request', (resolve) => { 77 | resolve('I am RPC'); 78 | }); 79 | await client 80 | .rpc('client-request') 81 | .then((response) => assert.equal(response, 'I am RPC')); 82 | }); 83 | 84 | test('it should receive rejection from the server', async function (assert) { 85 | assert.expect(1); 86 | const client: WindowMessengerClientService = this.owner.lookup( 87 | 'service:window-messenger-client' 88 | ); 89 | const server: WindowMessengerServerService = this.owner.lookup( 90 | 'service:window-messenger-server' 91 | ); 92 | 93 | server.on('client-request', (_resolve, reject) => { 94 | reject('Failed'); 95 | }); 96 | await client 97 | .fetch('client-request') 98 | .catch((response) => assert.equal(response, 'Failed')); 99 | }); 100 | 101 | test('it should receive object from the server', async function (assert) { 102 | assert.expect(1); 103 | const client: WindowMessengerClientService = this.owner.lookup( 104 | 'service:window-messenger-client' 105 | ); 106 | const server: WindowMessengerServerService = this.owner.lookup( 107 | 'service:window-messenger-server' 108 | ); 109 | 110 | const model = { 111 | complex: { 112 | id: 1, 113 | }, 114 | }; 115 | 116 | server.on('client-request', (resolve) => { 117 | resolve(model); 118 | }); 119 | await client 120 | .fetch('client-request') 121 | .then((response) => assert.deepEqual(response, model)); 122 | }); 123 | 124 | test('it should complex rejection object from the server', async function (assert) { 125 | assert.expect(1); 126 | const client: WindowMessengerClientService = this.owner.lookup( 127 | 'service:window-messenger-client' 128 | ); 129 | const server: WindowMessengerServerService = this.owner.lookup( 130 | 'service:window-messenger-server' 131 | ); 132 | 133 | const error = { 134 | complex: { 135 | id: 1, 136 | }, 137 | }; 138 | 139 | server.on('client-request', (_resolve, reject) => { 140 | reject(error); 141 | }); 142 | await client 143 | .fetch('client-request') 144 | .catch((response) => assert.deepEqual(response, error)); 145 | }); 146 | 147 | test('it should not receive server response if destroyed', async function (assert) { 148 | assert.expect(0); 149 | const client: WindowMessengerClientService = this.owner.lookup( 150 | 'service:window-messenger-client' 151 | ); 152 | const server: WindowMessengerServerService = this.owner.lookup( 153 | 'service:window-messenger-server' 154 | ); 155 | 156 | server.on('client-request', (resolve) => { 157 | resolve('Hello'); 158 | }); 159 | client.fetch('client-request').then(() => assert.ok(true)); 160 | 161 | client.destroy(); 162 | await settled(); 163 | }); 164 | }); 165 | -------------------------------------------------------------------------------- /tests/unit/services/window-messenger-events-test.ts: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | import { settled } from '@ember/test-helpers'; 4 | import WindowMessengerEventService, { 5 | OnEventCallback, 6 | } from 'ember-window-messenger/services/window-messenger-events'; 7 | import waitUntil from '@ember/test-helpers/wait-until'; 8 | 9 | module('Unit | Service | window messenger events', function (hooks) { 10 | setupTest(hooks); 11 | 12 | test('it should register and deregister message event listener when destroyed', async function (assert) { 13 | assert.expect(1); 14 | 15 | const service: WindowMessengerEventService = this.owner.lookup( 16 | 'service:window-messenger-events' 17 | ); 18 | 19 | const message = JSON.stringify({ 20 | id: +new Date(), 21 | type: 'test-dummy', 22 | name: 'hello-world', 23 | query: {}, 24 | }); 25 | 26 | service.on<{ name: string }>('from:test-dummy', (payload) => { 27 | assert.equal(payload.name, 'hello-world'); 28 | service.destroy(); 29 | }); 30 | // send first message 31 | window.postMessage(message, '*'); 32 | 33 | await settled(); 34 | 35 | // send second message 36 | window.postMessage(message, '*'); 37 | }); 38 | 39 | test('it should register and deregister message event listener manually', async function (assert) { 40 | assert.expect(1); 41 | 42 | const service: WindowMessengerEventService = this.owner.lookup( 43 | 'service:window-messenger-events' 44 | ); 45 | 46 | const message = JSON.stringify({ 47 | id: +new Date(), 48 | type: 'test-dummy', 49 | name: 'hello-world', 50 | query: {}, 51 | }); 52 | 53 | let gotTheMessage = false; 54 | const handler: OnEventCallback<{ name: string }> = (payload) => { 55 | assert.equal(payload.name, 'hello-world'); 56 | gotTheMessage = true; 57 | }; 58 | service.on<{ name: string }>('from:test-dummy', handler); 59 | // send first message 60 | window.postMessage(message, '*'); 61 | 62 | await waitUntil(() => { 63 | return gotTheMessage; 64 | }); 65 | 66 | service.off('from:test-dummy', handler); 67 | 68 | // send second message 69 | window.postMessage(message, '*'); 70 | 71 | await settled(); 72 | }); 73 | 74 | test('it should handle message, if not allowed origin', async function (assert) { 75 | assert.expect(0); 76 | 77 | const service: WindowMessengerEventService = this.owner.lookup( 78 | 'service:window-messenger-events' 79 | ); 80 | const message = JSON.stringify({ 81 | id: +new Date(), 82 | type: 'test-dummy', 83 | name: 'hello-world', 84 | query: {}, 85 | }); 86 | 87 | service.on('from:test-dummy', () => { 88 | assert.ok(true); 89 | }); 90 | 91 | window.postMessage(message, 'http://localhost:9999'); 92 | await settled(); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /tests/unit/services/window-messenger-server-test.ts: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | import { settled } from '@ember/test-helpers'; 4 | import WindowMessengerServerService, { 5 | OnEventCallback, 6 | } from 'ember-window-messenger/services/server'; 7 | import WindowMessengerClientService from 'ember-window-messenger/services/client'; 8 | 9 | module('Unit | Service | window messenger server', function (hooks) { 10 | setupTest(hooks); 11 | 12 | test('has() resource method', async function (assert) { 13 | const server: WindowMessengerServerService = this.owner.lookup( 14 | 'service:window-messenger-server' 15 | ); 16 | assert.false(server.has('client-request'), 'has no resource'); 17 | server.on('client-request', (resolve /*, reject, query*/) => { 18 | resolve(); 19 | }); 20 | assert.true(server.has('client-request'), 'has resource'); 21 | }); 22 | 23 | test("it should receive client's request", async function (assert) { 24 | assert.expect(1); 25 | const server: WindowMessengerServerService = this.owner.lookup( 26 | 'service:window-messenger-server' 27 | ); 28 | const client: WindowMessengerClientService = this.owner.lookup( 29 | 'service:window-messenger-client' 30 | ); 31 | 32 | server.on('client-request', (resolve /*, reject, query*/) => { 33 | assert.ok(true); 34 | resolve(); 35 | }); 36 | await client.fetch('client-request'); 37 | }); 38 | 39 | test("it should not receive client's request if not a match", async function (assert) { 40 | assert.expect(0); 41 | 42 | const server: WindowMessengerServerService = this.owner.lookup( 43 | 'service:window-messenger-server' 44 | ); 45 | const client: WindowMessengerClientService = this.owner.lookup( 46 | 'service:window-messenger-client' 47 | ); 48 | 49 | server.on('client-request', (/*resolve, reject, query*/) => { 50 | assert.ok(true); 51 | }); 52 | client.fetch('client-request-no-match'); 53 | }); 54 | 55 | test('it should receive query from client', async function (assert) { 56 | assert.expect(1); 57 | const server: WindowMessengerServerService = this.owner.lookup( 58 | 'service:window-messenger-server' 59 | ); 60 | const client: WindowMessengerClientService = this.owner.lookup( 61 | 'service:window-messenger-client' 62 | ); 63 | 64 | server.on( 65 | 'client-request', 66 | (resolve, _reject, query) => { 67 | assert.equal(query.id, 1, 'it should have got query parameters'); 68 | resolve(); 69 | } 70 | ); 71 | 72 | await client.fetch('client-request', { 73 | id: 1, 74 | }); 75 | }); 76 | 77 | test('it should not receive client request if destroyed', async function (assert) { 78 | assert.expect(0); 79 | const server: WindowMessengerServerService = this.owner.lookup( 80 | 'service:window-messenger-server' 81 | ); 82 | const client: WindowMessengerClientService = this.owner.lookup( 83 | 'service:window-messenger-client' 84 | ); 85 | 86 | server.on('client-request', () => { 87 | assert.ok(true); 88 | }); 89 | client.fetch('client-request'); 90 | server.destroy(); 91 | client.destroy(); // TODO remove once timeout is implemented 92 | await settled(); 93 | }); 94 | 95 | test('it should not receive client request if manually de-registering', async function (assert) { 96 | assert.expect(0); 97 | const server: WindowMessengerServerService = this.owner.lookup( 98 | 'service:window-messenger-server' 99 | ); 100 | const client: WindowMessengerClientService = this.owner.lookup( 101 | 'service:window-messenger-client' 102 | ); 103 | 104 | const handler: OnEventCallback = () => { 105 | assert.ok(true); 106 | }; 107 | server.on('client-request', handler); 108 | server.off('client-request', handler); 109 | client.fetch('client-request'); 110 | client.destroy(); // TODO remove once timeout is implemented 111 | await settled(); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "allowJs": true, 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "alwaysStrict": true, 10 | "strictNullChecks": true, 11 | "strictPropertyInitialization": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noEmitOnError": false, 17 | "noEmit": true, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "baseUrl": ".", 21 | "module": "es6", 22 | "experimentalDecorators": true, 23 | "paths": { 24 | "dummy/tests/*": [ 25 | "tests/*" 26 | ], 27 | "dummy/*": [ 28 | "tests/dummy/app/*", 29 | "app/*" 30 | ], 31 | "ember-window-messenger": [ 32 | "addon" 33 | ], 34 | "ember-window-messenger/*": [ 35 | "addon/*" 36 | ], 37 | "ember-window-messenger/test-support": [ 38 | "addon-test-support" 39 | ], 40 | "ember-window-messenger/test-support/*": [ 41 | "addon-test-support/*" 42 | ], 43 | "*": [ 44 | "types/*" 45 | ] 46 | } 47 | }, 48 | "include": [ 49 | "app/**/*", 50 | "addon/**/*", 51 | "tests/**/*", 52 | "types/**/*", 53 | "test-support/**/*", 54 | "addon-test-support/**/*" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /types/dummy/index.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raido/ember-window-messenger/2bf52399ee5421ad0cbde88871d166bd7dbdec82/types/dummy/index.d.ts -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | // Types for compiled templates 2 | declare module 'ember-window-messenger/templates/*' { 3 | import { TemplateFactory } from 'htmlbars-inline-precompile'; 4 | const tmpl: TemplateFactory; 5 | export default tmpl; 6 | } 7 | -------------------------------------------------------------------------------- /vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raido/ember-window-messenger/2bf52399ee5421ad0cbde88871d166bd7dbdec82/vendor/.gitkeep -------------------------------------------------------------------------------- /vendor/ie11-promise.js: -------------------------------------------------------------------------------- 1 | // https://github.com/babel/ember-cli-babel/issues/250 2 | self.Promise = self.Promise || Ember.RSVP.Promise; 3 | --------------------------------------------------------------------------------