├── .eslintignore ├── .eslintrc ├── .github └── browserstack.png ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── bower.json ├── deploy.sh ├── examples └── cache-and-update │ ├── bower.json │ ├── package-lock.json │ ├── package.json │ └── service │ └── Main.purs ├── karma.conf.js ├── package.json ├── src ├── Aff │ ├── ApplicationCache.purs │ ├── MessagePort.purs │ ├── Workers.purs │ └── Workers │ │ ├── Dedicated.purs │ │ ├── Service.purs │ │ └── Shared.purs ├── ApplicationCache.js ├── ApplicationCache.purs ├── Cache.js ├── Cache.purs ├── Fetch.js ├── Fetch.purs ├── GlobalScope.js ├── GlobalScope.purs ├── GlobalScope │ ├── Dedicated.js │ ├── Dedicated.purs │ ├── Service.js │ ├── Service.purs │ ├── Shared.js │ └── Shared.purs ├── MessagePort.js ├── MessagePort.purs ├── Workers.js ├── Workers.purs └── Workers │ ├── Class.purs │ ├── Dedicated.js │ ├── Dedicated.purs │ ├── Service.js │ ├── Service.purs │ ├── Shared.js │ └── Shared.purs └── test ├── Main.purs └── workers ├── worker01.purs ├── worker02.purs ├── worker03.purs ├── worker04.purs ├── worker05.purs ├── worker06.purs ├── worker07.purs ├── worker08.purs └── worker09.purs /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "commonjs": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 5 8 | }, 9 | "globals": { 10 | "Worker": true, 11 | "SharedWorker": true, 12 | "Request": true, 13 | "self": true, 14 | "location": true, 15 | "navigator": true, 16 | "fetch": true, 17 | }, 18 | "rules": { 19 | "import/no-extraneous-dependencies": 0, 20 | "import/no-unresolved": 0, 21 | "indent": ["error", 4], 22 | "prefer-arrow-callback": 0, 23 | "no-param-reassign": 0, 24 | "no-underscore-dangle": 0, 25 | "no-var": 0, 26 | "object-shorthand": 0, 27 | "prefer-template": 0 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/browserstack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/truqu/purescript-workers/843dfcc1090c880c9ab8a58b7c743b1860273f82/.github/browserstack.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Bower ### 2 | bower_components 3 | .bower-cache 4 | .bower-registry 5 | .bower-tmp 6 | 7 | ### Node ### 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (http://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Typescript v1 declaration files 46 | typings/ 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | 63 | # dotenv environment variables file 64 | .env 65 | 66 | 67 | ### PureScript ### 68 | # Dependencies 69 | .psci_modules 70 | 71 | # Generated files 72 | output 73 | generated-docs 74 | releases 75 | .pulp-cache 76 | .psc* 77 | .purs* 78 | .psa* 79 | dist 80 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "examples/serviceworker-cookbook"] 2 | path = examples/serviceworker-cookbook 3 | url = https://github.com/mozilla/serviceworker-cookbook 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 TruQu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PureScript Web Workers [![](https://img.shields.io/badge/doc-pursuit-60b5cc.svg)](http://pursuit.purescript.org/packages/purescript-workers) 2 | ===== 3 | 4 | This package offers bindings of the Web Workers (Dedicated, Shared & Service) APIs. 5 | 6 | > **DISCLAIMER** This package is still highly experimental. Do not hesitate to fill in an issue 7 | > if you encounter any trouble. Any feedback is welcome! 8 | 9 | ## Overview 10 | 11 | TODO 12 | 13 | ## How To: Run Examples 14 | 15 | The [examples](https://github.com/truqu/purescript-workers/tree/master/examples) contains few 16 | examples of the [Service Worker Cookbook](https://serviceworke.rs/) translated to PureScript. 17 | This is still work-in-progress and still relies on the [sources provided by 18 | Mozilla](https://github.com/truqu/purescript-workers/tree/master/examples) included as a 19 | submodule. 20 | 21 | Therefore, 22 | 23 | > *commands are written as if you were running them from the root of the repository* 24 | 25 | 1. Make sure the submodule is installed 26 | 27 | ``` 28 | git submodule init 29 | git submodule update 30 | ``` 31 | 32 | 2. Install dependencies of the Service Worker Cookbook 33 | 34 | ``` 35 | cd examples/serviceworker-cookbook 36 | npm i 37 | ``` 38 | 39 | 3. Install dependencies of an example 40 | 41 | ``` 42 | cd examples/cache-and-update 43 | npm i 44 | ``` 45 | 46 | 4. Compile the example's PureScript sources 47 | 48 | ``` 49 | cd examples/cache-and-update 50 | npm run build 51 | ``` 52 | 53 | 5. Start the Service Worker Cookbook server 54 | 55 | ``` 56 | cd examples/serviceworker-cookbook 57 | gulp start-server 58 | ``` 59 | 60 | > If the server doesn't start because you don't have any GCM API Key, you can simply remove all the `push-*` examples from there and try again. 61 | 62 | 6. Visit your localhost on port 3003 63 | 64 | 65 | ``` 66 | firefox http://localhost:3003/strategy-cache-and-update_demo.html 67 | ``` 68 | 69 | 70 | ## Changelog 71 | 72 | ### Roadmap 73 | 74 | - Re-write in PureScript examples from the [Service Worker Cookbook](https://github.com/mozilla/serviceworker-cookbook) 75 | 76 | - Write complete example & PoC of ServiceWorkers based on [Web Fundamentals - PWA Weather](https://github.com/ArturKlajnerok/pwa-weather) 77 | 78 | - Document undocumented functions and module 79 | 80 | ### v2.1.0 81 | 82 | - Implement Fetch, Request & Response API 83 | 84 | - Basic example for service workers from mozilla/serviceworkers-cookbook 85 | 86 | ### v2.0.0 87 | 88 | - Internal implementation rework 89 | 90 | - Supports for Service Workers 91 | 92 | - Supports for Cache & CacheStorage 93 | 94 | #### v1.0.0 95 | 96 | - Supports for the Dedicated Workers 97 | - Supports for the Shared Workers 98 | - Supports for the Application Cache 99 | 100 | ## Documentation 101 | 102 | Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-workers). 103 | 104 | Some additionals links: 105 | 106 | - [Web Workers API specifications](https://w3c.github.io/workers) 107 | - [Service Workers API specifications](https://www.w3.org/TR/service-workers-1) 108 | - [Fetch specifications](https://fetch.spec.whatwg.org) 109 | - [Service Workers examples](https://serviceworke.rs/) 110 | - [W3C Platform Tests](https://github.com/w3c/web-platform-tests) 111 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-workers", 3 | "version": "2.0.0", 4 | "description": "An API wrapper around Web Workers", 5 | "license": "MIT", 6 | "homepage": "https://github.com/truqu/purescript-workers", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/truqu/purescript-workers.git" 10 | }, 11 | "ignore": [ 12 | "**/.*", 13 | "node_modules", 14 | "bower_components", 15 | "output", 16 | "test", 17 | "dist", 18 | "release", 19 | "package.json", 20 | "karma.conf.js", 21 | "karma.browserstack.conf.js", 22 | "deploy.sh" 23 | ], 24 | "dependencies": { 25 | "purescript-aff": "^3.1.0", 26 | "purescript-eff": "^3.1.0", 27 | "purescript-exceptions": "https://github.com/truqu/purescript-exceptions.git#3.1.0", 28 | "purescript-generics": "^4.0.0", 29 | "purescript-nonempty": "^4.0.0", 30 | "purescript-prelude": "^3.1.0", 31 | "purescript-read": "^1.0.0", 32 | "purescript-http": "^3.0.0", 33 | "purescript-argonaut": "^3.0.0", 34 | "purescript-nullable": "^3.0.0" 35 | }, 36 | "devDependencies": { 37 | "purescript-spec": "^1.0.0", 38 | "purescript-psci-support": "^3.0.0", 39 | "purescript-spec-mocha": "https://github.com/truqu/purescript-spec-mocha.git#0.5.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git stash save 4 | git clean -df 5 | yes | pulp publish --no-push 6 | git stash pop 7 | -------------------------------------------------------------------------------- /examples/cache-and-update/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-example-cache-and-update", 3 | "version": "1.0.0", 4 | "description": "Example of https://serviceworke.rs/strategy-cache-and-update_service-worker_doc.html using the purescript-workers API", 5 | "license": "MIT", 6 | "homepage": "https://github.com/truqu/purescript-workers/examples/cache-and-update", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/truqu/purescript-workers.git" 10 | }, 11 | "dependencies": { 12 | "purescript-aff": "^3.1.0", 13 | "purescript-eff": "^3.1.0", 14 | "purescript-exceptions": "^3.0.0", 15 | "purescript-generics": "^4.0.0", 16 | "purescript-nonempty": "^4.0.0", 17 | "purescript-prelude": "^3.1.0", 18 | "purescript-read": "^1.0.0", 19 | "purescript-http": "^3.0.0", 20 | "purescript-argonaut": "^3.0.0", 21 | "purescript-nullable": "^3.0.0" 22 | }, 23 | "devDependencies": { 24 | "purescript-psci-support": "^3.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/cache-and-update/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-workers", 3 | "version": "1.0.0", 4 | "description": "Example of https://serviceworke.rs/strategy-cache-and-update_service-worker_doc.html using the purescript-workers API", 5 | "scripts": { 6 | "build": "pulp browserify -j 4 -O -I service --to ../serviceworker-cookbook/strategy-cache-and-update/service-worker.js", 7 | "install": "bower install && ln -s ../../../ bower_components/purescript-workers" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "bower": "1.8.0", 12 | "pulp": "11.0.0", 13 | "purescript": "0.11.5" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/Truqu/purescript-workers.git" 18 | }, 19 | "author": "Truqu ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/Truqu/purescript-workers/issues" 23 | }, 24 | "homepage": "https://github.com/Truqu/purescript-workers/examples/cache-and-update" 25 | } 26 | -------------------------------------------------------------------------------- /examples/cache-and-update/service/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Aff (Aff) 6 | import Control.Monad.Aff.Console (CONSOLE, log) 7 | import Control.Monad.Eff (Eff) 8 | import Control.Monad.Eff.Class (liftEff) 9 | import Data.Maybe (Maybe) 10 | 11 | import Cache as Cache 12 | import Cache (CACHE) 13 | import Fetch (FETCH, Request, Response, fetch, requestURL) 14 | import GlobalScope.Service (onInstall, onFetch, caches) 15 | import Workers (WORKER) 16 | 17 | 18 | main 19 | :: forall e 20 | . Eff (worker :: WORKER | e) Unit 21 | main = 22 | preCache *> fromCache 23 | 24 | 25 | -- Our cache name in the cache storage 26 | cacheName :: String 27 | cacheName = 28 | "cache-and-update" 29 | 30 | 31 | -- Register an event listener for the `install` event 32 | -- 33 | -- This is called once at the beginning when the cache first get initialized 34 | preCache 35 | :: forall e 36 | . Eff (worker :: WORKER | e) Unit 37 | preCache = onInstall $ do 38 | log "The service worker is being installed" 39 | storage <- liftEff caches 40 | cache <- Cache.openCache storage cacheName 41 | Cache.addAll cache 42 | [ "./controlled.html" 43 | , "./asset" 44 | ] 45 | 46 | 47 | -- Register an event listener for the `fetch` event 48 | -- 49 | -- There are 2 phases here: 50 | -- 51 | -- - first, we immediately handle the response to the request 52 | -- - secondly, we update the cache in background with the actual response 53 | fromCache 54 | :: forall e 55 | . Eff (worker :: WORKER | e) Unit 56 | fromCache = onFetch respondWith waitUntil 57 | where 58 | respondWith 59 | :: forall e' 60 | . Request 61 | -> Aff (worker :: WORKER, cache :: CACHE, console :: CONSOLE | e') (Maybe Response) 62 | respondWith req = do 63 | log "The service worker is serving the asset." 64 | storage <- liftEff caches 65 | cache <- Cache.openCache storage cacheName 66 | Cache.match cache (requestURL req) 67 | 68 | waitUntil 69 | :: forall e'' 70 | . Request 71 | -> Aff (worker :: WORKER, fetch :: FETCH, cache :: CACHE | e'') Unit 72 | waitUntil req = do 73 | storage <- liftEff caches 74 | cache <- Cache.openCache storage cacheName 75 | res <- fetch req 76 | Cache.put cache (requestURL req) res 77 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = (config) => { 2 | config.set({ 3 | autoWatch: true, 4 | singleRun: true, 5 | browsers: ['Chrome'], 6 | basePath: 'dist/karma', 7 | customHeaders: [{ 8 | match: '.*', 9 | name: 'Service-Worker-Allowed', 10 | value: '/', 11 | }], 12 | files: [ 13 | 'index.js', 14 | { 15 | watched: false, 16 | pattern: 'worker*.js', 17 | included: false, 18 | served: true, 19 | }, 20 | ], 21 | frameworks: [ 22 | 'mocha', 23 | ], 24 | plugins: [ 25 | 'karma-chrome-launcher', 26 | 'karma-firefox-launcher', 27 | 'karma-spec-reporter', 28 | 'karma-mocha', 29 | ], 30 | reporters: ['spec'], 31 | client: { 32 | mocha: { 33 | timeout: 10000, 34 | }, 35 | }, 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-workers", 3 | "version": "2.0.0", 4 | "description": "A wrapper around the Web Workers API", 5 | "scripts": { 6 | "prepare:release": "mkdir -p releases/github && cp -r README.md LICENSE src bower.json releases/github", 7 | 8 | "test:build:wrk09": "pulp browserify -j 4 --main \"Test.Workers.Worker09\" -I test --to dist/karma/worker09.js", 9 | "test:build:wrk08": "pulp browserify -j 4 --main \"Test.Workers.Worker08\" -I test --to dist/karma/worker08.js && npm run test:build:wrk09", 10 | "test:build:wrk07": "pulp browserify -j 4 --main \"Test.Workers.Worker07\" -I test --to dist/karma/worker07.js && npm run test:build:wrk08", 11 | "test:build:wrk06": "pulp browserify -j 4 --main \"Test.Workers.Worker06\" -I test --to dist/karma/worker06.js && npm run test:build:wrk07", 12 | "test:build:wrk05": "pulp browserify -j 4 --main \"Test.Workers.Worker05\" -I test --to dist/karma/worker05.js && npm run test:build:wrk06", 13 | "test:build:wrk04": "pulp browserify -j 4 --main \"Test.Workers.Worker04\" -I test --to dist/karma/worker04.js && npm run test:build:wrk05", 14 | "test:build:wrk03": "pulp browserify -j 4 --main \"Test.Workers.Worker03\" -I test --to dist/karma/worker03.js && npm run test:build:wrk04", 15 | "test:build:wrk02": "pulp browserify -j 4 --main \"Test.Workers.Worker02\" -I test --to dist/karma/worker02.js && npm run test:build:wrk03", 16 | "test:build:wrk01": "pulp browserify -j 4 --main \"Test.Workers.Worker01\" -I test --to dist/karma/worker01.js && npm run test:build:wrk02", 17 | "test:build": "mkdir -p dist/karma && pulp browserify -j 4 --main \"Test.Main\" -I test --to dist/karma/index.js && npm run test:build:wrk01", 18 | 19 | "test:run:browserstack": "karma start karma.browserstack.conf.js", 20 | "test:run": "karma start karma.conf.js", 21 | 22 | "test:browserstack": "npm run test:build && npm run test:run:browserstack", 23 | "test": "npm run test:build && npm run test:run", 24 | 25 | "start": "find . -type f -regex '.*\\(src\\|test\\).*' ! -regex '.*bower_components.*\\|.*node_modules.*|.*\\.sw.*' | entr -s 'npm run test -s'" 26 | }, 27 | "dependencies": {}, 28 | "devDependencies": { 29 | "bower": "1.8.0", 30 | "eslint": "3.19.0", 31 | "eslint-config-airbnb": "15.0.1", 32 | "eslint-plugin-import": "2.5.0", 33 | "eslint-plugin-jsx-a11y": "5.0.3", 34 | "eslint-plugin-react": "7.1.0", 35 | "karma": "1.7.0", 36 | "karma-browserstack-launcher": "1.3.0", 37 | "karma-chrome-launcher": "2.1.1", 38 | "karma-firefox-launcher": "1.0.1", 39 | "karma-mocha": "1.3.0", 40 | "karma-spec-reporter": "0.0.31", 41 | "mocha": "3.4.2", 42 | "pulp": "11.0.0", 43 | "purescript": "0.11.5" 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "git+https://github.com/Truqu/purescript-workers.git" 48 | }, 49 | "author": "Truqu ", 50 | "license": "MIT", 51 | "bugs": { 52 | "url": "https://github.com/Truqu/purescript-workers/issues" 53 | }, 54 | "homepage": "https://github.com/Truqu/purescript-workers#readme" 55 | } 56 | -------------------------------------------------------------------------------- /src/Aff/ApplicationCache.purs: -------------------------------------------------------------------------------- 1 | module Aff.ApplicationCache 2 | ( abort 3 | , swapCache 4 | , update 5 | , module ApplicationCache 6 | ) where 7 | 8 | import Prelude 9 | 10 | import Control.Monad.Aff (Aff) 11 | import Control.Monad.Eff.Class (liftEff) 12 | 13 | import ApplicationCache as A 14 | import ApplicationCache (APPCACHE, ApplicationCache, Status(..), status) 15 | 16 | 17 | abort 18 | :: forall e 19 | . ApplicationCache 20 | -> Aff (appcache :: APPCACHE | e) Unit 21 | abort = 22 | liftEff <<< A.abort 23 | 24 | 25 | swapCache 26 | :: forall e 27 | . ApplicationCache 28 | -> Aff (appcache :: APPCACHE | e) Unit 29 | swapCache = 30 | liftEff <<< A.swapCache 31 | 32 | 33 | update 34 | :: forall e 35 | . ApplicationCache 36 | -> Aff (appcache :: APPCACHE | e) Unit 37 | update = 38 | liftEff <<< A.update 39 | -------------------------------------------------------------------------------- /src/Aff/MessagePort.purs: -------------------------------------------------------------------------------- 1 | module Aff.MessagePort 2 | ( onMessage 3 | , onMessageError 4 | , close 5 | , start 6 | , module MessagePort 7 | ) where 8 | 9 | import Prelude (Unit, (<<<)) 10 | 11 | import Control.Monad.Aff (Aff) 12 | import Control.Monad.Eff (Eff) 13 | import Control.Monad.Eff.Class (liftEff) 14 | import Control.Monad.Eff.Exception (Error) 15 | 16 | import Aff.Workers (WORKER) 17 | import MessagePort as MP 18 | import MessagePort (MessagePort) 19 | 20 | 21 | -- | Event handler for the `message` event 22 | onMessage 23 | :: forall e e' msg 24 | . MessagePort 25 | -> (msg -> Eff ( | e') Unit) 26 | -> Aff (worker :: WORKER | e) Unit 27 | onMessage p = 28 | liftEff <<< MP.onMessage p 29 | 30 | 31 | -- | Event handler for the `messageError` event 32 | onMessageError 33 | :: forall e e' 34 | . MessagePort 35 | -> (Error -> Eff ( | e') Unit) 36 | -> Aff (worker :: WORKER | e) Unit 37 | onMessageError p = 38 | liftEff <<< MP.onMessageError p 39 | 40 | 41 | -- | TODO DOC 42 | close 43 | :: forall e 44 | . MessagePort 45 | -> Aff (worker :: WORKER | e) Unit 46 | close = 47 | liftEff <<< MP.close 48 | 49 | 50 | -- | TODO DOC 51 | start 52 | :: forall e 53 | . MessagePort 54 | -> Aff (worker :: WORKER | e) Unit 55 | start = 56 | liftEff <<< MP.start 57 | -------------------------------------------------------------------------------- /src/Aff/Workers.purs: -------------------------------------------------------------------------------- 1 | module Aff.Workers 2 | ( onError 3 | , postMessage 4 | , postMessage' 5 | , module Workers 6 | ) where 7 | 8 | import Prelude 9 | 10 | import Control.Monad.Aff (Aff) 11 | import Control.Monad.Eff (Eff) 12 | import Control.Monad.Eff.Class (liftEff) 13 | import Control.Monad.Eff.Exception (EXCEPTION, Error) 14 | 15 | import Workers as W 16 | import Workers (WORKER, Location(..), Navigator(..), Options, WorkerType(..), Credentials(..)) 17 | import Workers.Class (class AbstractWorker, class Channel) 18 | 19 | 20 | -- | Event handler for the `error` event. 21 | onError 22 | :: forall e e' worker. (AbstractWorker worker) 23 | => worker 24 | -> (Error -> Eff ( | e') Unit) 25 | -> Aff (worker :: WORKER | e) Unit 26 | onError w = 27 | liftEff <<< W.onError w 28 | 29 | 30 | -- | Clones message and transmits it to the Worker object. 31 | postMessage 32 | :: forall e msg channel. (Channel channel) 33 | => channel 34 | -> msg 35 | -> Aff (worker :: WORKER, exception :: EXCEPTION | e) Unit 36 | postMessage p = 37 | liftEff <<< W.postMessage p 38 | 39 | 40 | -- | Clones message and transmits it to the port object associated with 41 | -- | dedicatedportGlobal.transfer can be passed as a list of objects that are to be 42 | -- | transferred rather than cloned. 43 | postMessage' 44 | :: forall e msg transfer channel. (Channel channel) 45 | => channel 46 | -> msg 47 | -> Array transfer 48 | -> Aff (worker :: WORKER, exception :: EXCEPTION | e) Unit 49 | postMessage' p m = 50 | liftEff <<< W.postMessage' p m 51 | -------------------------------------------------------------------------------- /src/Aff/Workers/Dedicated.purs: -------------------------------------------------------------------------------- 1 | module Aff.Workers.Dedicated 2 | ( new 3 | , new' 4 | , terminate 5 | , onMessage 6 | , onMessageError 7 | , module Workers.Dedicated 8 | , module Aff.Workers 9 | ) where 10 | 11 | 12 | import Prelude (Unit, (<<<)) 13 | 14 | import Control.Monad.Aff (Aff) 15 | import Control.Monad.Eff (Eff) 16 | import Control.Monad.Eff.Exception (Error) 17 | import Control.Monad.Eff.Class (liftEff) 18 | 19 | import Aff.Workers (WORKER, Credentials(..), Location, Navigator, Options, WorkerType(..), onError, postMessage, postMessage') 20 | import Workers.Dedicated (Dedicated) 21 | import Workers.Dedicated as W 22 | 23 | 24 | 25 | -- | Returns a new Worker object. scriptURL will be fetched and executed in the background, 26 | -- | creating a new global environment for which worker represents the communication channel. 27 | new 28 | :: forall e 29 | . String 30 | -> Aff (worker :: WORKER | e) Dedicated 31 | new = 32 | liftEff <<< W.new 33 | 34 | 35 | -- | Returns a new Worker object. scriptURL will be fetched and executed in the background, 36 | -- | creating a new global environment for which worker represents the communication channel. 37 | -- | options can be used to define the name of that global environment via the name option, 38 | -- | primarily for debugging purposes. It can also ensure this new global environment supports 39 | -- | JavaScript modules (specify type: "module"), and if that is specified, can also be used 40 | -- | to specify how scriptURL is fetched through the credentials option 41 | new' 42 | :: forall e 43 | . String 44 | -> Options 45 | -> Aff (worker :: WORKER | e) Dedicated 46 | new' url = 47 | liftEff <<< W.new' url 48 | 49 | 50 | -- | Aborts worker’s associated global environment. 51 | terminate 52 | :: forall e 53 | . Dedicated 54 | -> Aff (worker :: WORKER | e) Unit 55 | terminate = 56 | liftEff <<< W.terminate 57 | 58 | 59 | -- | Event handler for the `message` event 60 | onMessage 61 | :: forall e e' msg 62 | . Dedicated 63 | -> (msg -> Eff ( | e') Unit) 64 | -> Aff (worker :: WORKER | e) Unit 65 | onMessage p = 66 | liftEff <<< W.onMessage p 67 | 68 | 69 | -- | Event handler for the `messageError` event 70 | onMessageError 71 | :: forall e e' 72 | . Dedicated 73 | -> (Error -> Eff ( | e') Unit) 74 | -> Aff (worker :: WORKER | e) Unit 75 | onMessageError p = 76 | liftEff <<< W.onMessageError p 77 | -------------------------------------------------------------------------------- /src/Aff/Workers/Service.purs: -------------------------------------------------------------------------------- 1 | module Aff.Workers.Service 2 | ( controller 3 | , getRegistration 4 | , onControllerChange 5 | , onMessage 6 | , ready 7 | , register 8 | , register' 9 | , startMessages 10 | , wait 11 | , onStateChange 12 | , scriptURL 13 | , state 14 | , active 15 | , installing 16 | , waiting 17 | , scope 18 | , update 19 | , unregister 20 | , onUpdateFound 21 | , module Workers.Service 22 | , module Aff.Workers 23 | ) where 24 | 25 | 26 | import Prelude (Unit, (<<<)) 27 | 28 | import Control.Monad.Aff (Aff) 29 | import Control.Monad.Eff (Eff) 30 | import Control.Monad.Eff.Class (liftEff) 31 | import Data.Maybe (Maybe) 32 | 33 | import Aff.Workers (WORKER, WorkerType(..), onError, postMessage, postMessage') 34 | import Workers.Service (Service, Registration, RegistrationOptions, State(..)) 35 | import Workers.Service as W 36 | 37 | 38 | -- SERVICE WORKER CONTAINER ~ navigator.serviceWorker globals 39 | 40 | controller 41 | :: forall e 42 | . Aff (worker :: WORKER | e) (Maybe Service) 43 | controller = 44 | liftEff W.controller 45 | 46 | 47 | getRegistration 48 | :: forall e 49 | . Maybe String 50 | -> Aff (worker :: WORKER | e) (Maybe Registration) 51 | getRegistration = 52 | W.getRegistration 53 | 54 | 55 | onControllerChange 56 | :: forall e e' 57 | . Eff ( | e') Unit 58 | -> Aff (worker :: WORKER | e) Unit 59 | onControllerChange = 60 | liftEff <<< W.onControllerChange 61 | 62 | 63 | onMessage 64 | :: forall e e' msg 65 | . (msg -> Eff ( | e') Unit) 66 | -> Aff (worker :: WORKER | e) Unit 67 | onMessage = 68 | liftEff <<< W.onMessage 69 | 70 | 71 | ready 72 | :: forall e 73 | . Aff (worker :: WORKER | e) Registration 74 | ready = 75 | W.ready 76 | 77 | 78 | register 79 | :: forall e 80 | . String 81 | -> Aff (worker :: WORKER | e) Registration 82 | register = 83 | W.register 84 | 85 | 86 | register' 87 | :: forall e 88 | . String 89 | -> RegistrationOptions 90 | -> Aff (worker :: WORKER | e) Registration 91 | register' = 92 | W.register' 93 | 94 | 95 | startMessages 96 | :: forall e 97 | . Aff (worker :: WORKER | e) Unit 98 | startMessages = 99 | liftEff W.startMessages 100 | 101 | 102 | wait 103 | :: forall e 104 | . Aff (worker :: WORKER | e) Service 105 | wait = 106 | W.wait 107 | 108 | 109 | -- SERVICE WORKER ~ instance methods 110 | 111 | onStateChange 112 | :: forall e e' 113 | . Service 114 | -> (State -> Eff ( | e') Unit) 115 | -> Aff (worker :: WORKER | e) Unit 116 | onStateChange service = 117 | liftEff <<< W.onStateChange service 118 | 119 | 120 | scriptURL 121 | :: Service 122 | -> String 123 | scriptURL = 124 | W.scriptURL 125 | 126 | 127 | state 128 | :: Service 129 | -> State 130 | state = 131 | W.state 132 | 133 | 134 | -- SERVICE WORKER REGISTRATION ~ instance method 135 | 136 | active 137 | :: Registration 138 | -> Maybe Service 139 | active = 140 | W.active 141 | 142 | 143 | installing 144 | :: Registration 145 | -> Maybe Service 146 | installing = 147 | W.installing 148 | 149 | 150 | waiting 151 | :: Registration 152 | -> Maybe Service 153 | waiting = 154 | W.waiting 155 | 156 | 157 | scope 158 | :: Registration 159 | -> String 160 | scope = 161 | W.scope 162 | 163 | 164 | update 165 | :: forall e 166 | . Registration 167 | -> Aff (worker :: WORKER | e) Unit 168 | update = 169 | W.update 170 | 171 | 172 | unregister 173 | :: forall e 174 | . Registration 175 | -> Aff (worker :: WORKER | e) Boolean 176 | unregister = 177 | W.unregister 178 | 179 | 180 | onUpdateFound 181 | :: forall e e' 182 | . Registration 183 | -> Eff ( | e') Unit 184 | -> Aff (worker :: WORKER | e) Unit 185 | onUpdateFound registration = 186 | liftEff <<< W.onUpdateFound registration 187 | -------------------------------------------------------------------------------- /src/Aff/Workers/Shared.purs: -------------------------------------------------------------------------------- 1 | module Aff.Workers.Shared 2 | ( port 3 | , new 4 | , new' 5 | , module Workers.Shared 6 | , module Aff.MessagePort 7 | , module Aff.Workers 8 | ) where 9 | 10 | import Prelude ((<<<)) 11 | 12 | import Control.Monad.Aff (Aff) 13 | import Control.Monad.Eff.Class (liftEff) 14 | 15 | import Aff.MessagePort (MessagePort, close, start, onMessage, onMessageError) 16 | import Aff.Workers (WORKER, Credentials(..), Location, Navigator, Options, WorkerType(..), onError, postMessage, postMessage') 17 | import Workers.Shared as W 18 | import Workers.Shared (Shared) 19 | 20 | 21 | -- | Returns a new Worker object. scriptURL will be fetched and executed in the background, 22 | -- | creating a new global environment for which worker represents the communication channel. 23 | new 24 | :: forall e 25 | . String 26 | -> Aff (worker :: WORKER | e) Shared 27 | new = 28 | liftEff <<< W.new 29 | 30 | 31 | -- | Returns a new Worker object. scriptURL will be fetched and executed in the background, 32 | -- | creating a new global environment for which worker represents the communication channel. 33 | -- | options can be used to define the name of that global environment via the name option, 34 | -- | primarily for debugging purposes. It can also ensure this new global environment supports 35 | -- | JavaScript modules (specify type: "module"), and if that is specified, can also be used 36 | -- | to specify how scriptURL is fetched through the credentials option 37 | new' 38 | :: forall e 39 | . String 40 | -> Options 41 | -> Aff (worker :: WORKER | e) Shared 42 | new' url = 43 | liftEff <<< W.new' url 44 | 45 | 46 | -- | Aborts worker’s associated global environment. 47 | port 48 | :: Shared 49 | -> MessagePort 50 | port = 51 | W.port 52 | -------------------------------------------------------------------------------- /src/ApplicationCache.js: -------------------------------------------------------------------------------- 1 | const statusDict = { 2 | 0: 'uncached', 3 | 1: 'idle', 4 | 2: 'checking', 5 | 3: 'downloading', 6 | 4: 'updateready', 7 | 5: 'obsolete', 8 | }; 9 | 10 | exports.abort = function abort(appCache) { 11 | return function eff() { 12 | return appCache.abort(); 13 | }; 14 | }; 15 | 16 | exports._status = function _status(toStatus) { 17 | return function _status2(appCache) { 18 | return toStatus(statusDict[appCache.status]); 19 | }; 20 | }; 21 | 22 | exports.swapCache = function swapCache(appCache) { 23 | return function eff() { 24 | appCache.swapCache(); 25 | }; 26 | }; 27 | 28 | exports.update = function update(appCache) { 29 | return function eff() { 30 | appCache.update(); 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /src/ApplicationCache.purs: -------------------------------------------------------------------------------- 1 | module ApplicationCache 2 | ( APPCACHE 3 | , ApplicationCache 4 | , Status(..) 5 | , abort 6 | , status 7 | , update 8 | , swapCache 9 | ) where 10 | 11 | import Prelude 12 | 13 | import Control.Monad.Eff(kind Effect, Eff) 14 | 15 | 16 | foreign import data APPCACHE :: Effect 17 | 18 | 19 | foreign import data ApplicationCache :: Type 20 | 21 | 22 | data Status 23 | = Uncached 24 | | Idle 25 | | Checking 26 | | Downloading 27 | | UpdateReady 28 | | Obsolete 29 | 30 | 31 | type StatusRec = 32 | { uncached :: Status 33 | , idle :: Status 34 | , checking :: Status 35 | , downloading :: Status 36 | , updateReady :: Status 37 | , obsolete :: Status 38 | } 39 | 40 | 41 | foreign import abort 42 | :: forall e 43 | . ApplicationCache 44 | -> Eff (appcache :: APPCACHE | e) Unit 45 | 46 | 47 | status 48 | :: ApplicationCache 49 | -> Status 50 | status = 51 | _status { uncached : Uncached 52 | , idle : Idle 53 | , checking : Checking 54 | , downloading : Downloading 55 | , updateReady : UpdateReady 56 | , obsolete : Obsolete 57 | } 58 | 59 | 60 | foreign import _status 61 | :: StatusRec 62 | -> ApplicationCache 63 | -> Status 64 | 65 | 66 | foreign import swapCache 67 | :: forall e 68 | . ApplicationCache 69 | -> Eff (appcache :: APPCACHE | e) Unit 70 | 71 | 72 | foreign import update 73 | :: forall e 74 | . ApplicationCache 75 | -> Eff (appcache :: APPCACHE | e) Unit 76 | -------------------------------------------------------------------------------- /src/Cache.js: -------------------------------------------------------------------------------- 1 | /* Cache Storage */ 2 | 3 | exports._deleteCache = function _deleteCache(cacheStorage) { 4 | return function _deleteCache2(cacheName) { 5 | return function aff(success, error) { 6 | try { 7 | cacheStorage.delete(cacheName).then(success, error); 8 | } catch (err) { 9 | error(err); 10 | } 11 | }; 12 | }; 13 | }; 14 | 15 | exports._hasCache = function _hasCache(cacheStorage) { 16 | return function _hasCache2(cacheName) { 17 | return function aff(success, error) { 18 | try { 19 | cacheStorage.has(cacheName).then(success, error); 20 | } catch (err) { 21 | error(err); 22 | } 23 | }; 24 | }; 25 | }; 26 | 27 | exports._openCache = function _openCache(cacheStorage) { 28 | return function _openCache2(cacheName) { 29 | return function aff(success, error) { 30 | try { 31 | cacheStorage.open(cacheName).then(success, error); 32 | } catch (err) { 33 | error(err); 34 | } 35 | }; 36 | }; 37 | }; 38 | 39 | exports._keysCache = function _keysCache(cacheStorage) { 40 | return function aff(success, error) { 41 | try { 42 | cacheStorage.keys() 43 | .then(function onSuccess(xs) { 44 | return Array.prototype.slice.apply(xs); 45 | }) 46 | .then(success, error); 47 | } catch (err) { 48 | error(err); 49 | } 50 | }; 51 | }; 52 | 53 | /* Cache */ 54 | 55 | exports._add = function _add(cache) { 56 | return function _add2(req) { 57 | return function aff(success, error) { 58 | try { 59 | cache.add(req).then(success, error); 60 | } catch (err) { 61 | error(err); 62 | } 63 | }; 64 | }; 65 | }; 66 | 67 | exports._addAll = function _addAll(cache) { 68 | return function _addAll2(xs) { 69 | return function aff(success, error) { 70 | try { 71 | cache.addAll(xs).then(success, error); 72 | } catch (err) { 73 | error(err); 74 | } 75 | }; 76 | }; 77 | }; 78 | 79 | exports._delete = function _delete(cache) { 80 | return function _delete2(req) { 81 | return function _delete3(opts) { 82 | return function aff(success, error) { 83 | try { 84 | cache.delete(req, opts).then(success, error); 85 | } catch (err) { 86 | error(err); 87 | } 88 | }; 89 | }; 90 | }; 91 | }; 92 | 93 | exports._keys = function _keys(cache) { 94 | return function _keys2(req) { 95 | return function _keys3(opts) { 96 | return function aff(success, error) { 97 | try { 98 | cache.keys(req, opts) 99 | .then(function onSuccess(xs) { 100 | return Array.prototype.slice.apply(xs); 101 | }) 102 | .then(success, error); 103 | } catch (err) { 104 | error(err); 105 | } 106 | }; 107 | }; 108 | }; 109 | }; 110 | 111 | exports._match = function _match(cache) { 112 | return function _match2(req) { 113 | return function _match3(opts) { 114 | return function aff(success, error) { 115 | try { 116 | cache.match(req, opts).then(success, error); 117 | } catch (err) { 118 | error(err); 119 | } 120 | }; 121 | }; 122 | }; 123 | }; 124 | 125 | exports._matchAll = function _matchAll(cache) { 126 | return function _matchAll2(xs) { 127 | return function _matchAll3(opts) { 128 | return function aff(success, error) { 129 | try { 130 | cache.matchAll(xs, opts) 131 | .then(function onSuccess(xs_) { 132 | return Array.prototype.slice.apply(xs_); 133 | }) 134 | .then(success, error); 135 | } catch (err) { 136 | error(err); 137 | } 138 | }; 139 | }; 140 | }; 141 | }; 142 | 143 | exports._put = function _put(cache) { 144 | return function _put2(req) { 145 | return function _put3(res) { 146 | return function aff(success, error) { 147 | try { 148 | cache.put(req, res).then(success, error); 149 | } catch (err) { 150 | error(err); 151 | } 152 | }; 153 | }; 154 | }; 155 | }; 156 | -------------------------------------------------------------------------------- /src/Cache.purs: -------------------------------------------------------------------------------- 1 | module Cache 2 | -- * Types 3 | ( CACHE 4 | , Cache 5 | , CacheStorage 6 | , CacheQueryOptions 7 | , defaultCacheQueryOptions 8 | 9 | -- * Cache Storage Manipulations 10 | , deleteCache 11 | , hasCache 12 | , keysCache 13 | , openCache 14 | 15 | -- * Cache Manipulations 16 | , add 17 | , addAll 18 | , delete 19 | , delete' 20 | , keys 21 | , keys' 22 | , match 23 | , match' 24 | , matchAll 25 | , matchAll' 26 | , put 27 | ) where 28 | 29 | import Prelude 30 | 31 | import Control.Monad.Aff (Aff) 32 | import Control.Monad.Eff (kind Effect) 33 | import Data.Maybe (Maybe(..)) 34 | import Data.Nullable (Nullable, toNullable, toMaybe) 35 | 36 | import Fetch (Response, RequestInfo) 37 | 38 | 39 | -------------------- 40 | -- TYPES 41 | -------------------- 42 | 43 | foreign import data CACHE :: Effect 44 | 45 | 46 | foreign import data Cache :: Type 47 | 48 | 49 | foreign import data CacheStorage :: Type 50 | 51 | 52 | 53 | type CacheQueryOptions = 54 | { ignoreSearch :: Boolean 55 | , ignoreMethod :: Boolean 56 | , ignoreVary :: Boolean 57 | } 58 | 59 | 60 | defaultCacheQueryOptions :: CacheQueryOptions 61 | defaultCacheQueryOptions = 62 | { ignoreSearch : false 63 | , ignoreMethod : false 64 | , ignoreVary : false 65 | } 66 | 67 | 68 | -------------------- 69 | -- METHODS 70 | -------------------- 71 | 72 | -- Cache Storage 73 | 74 | deleteCache 75 | :: forall e 76 | . CacheStorage 77 | -> String 78 | -> Aff (cache :: CACHE | e) Boolean 79 | deleteCache = 80 | _deleteCache 81 | 82 | 83 | hasCache 84 | :: forall e 85 | . CacheStorage 86 | -> String 87 | -> Aff (cache :: CACHE | e) Boolean 88 | hasCache = 89 | _hasCache 90 | 91 | 92 | keysCache 93 | :: forall e 94 | . CacheStorage 95 | -> Aff (cache :: CACHE | e) (Array String) 96 | keysCache = 97 | _keysCache 98 | 99 | 100 | openCache 101 | :: forall e 102 | . CacheStorage 103 | -> String 104 | -> Aff (cache :: CACHE | e) Cache 105 | openCache = 106 | _openCache 107 | 108 | -- Cache 109 | 110 | add 111 | :: forall e 112 | . Cache 113 | -> RequestInfo 114 | -> Aff (cache :: CACHE | e) Unit 115 | add = 116 | _add 117 | 118 | 119 | addAll 120 | :: forall e 121 | . Cache 122 | -> Array RequestInfo 123 | -> Aff (cache :: CACHE | e) Unit 124 | addAll = 125 | _addAll 126 | 127 | 128 | delete 129 | :: forall e 130 | . Cache 131 | -> RequestInfo 132 | -> Aff (cache :: CACHE | e) Boolean 133 | delete cache req = 134 | _delete cache req defaultCacheQueryOptions 135 | 136 | 137 | delete' 138 | :: forall e 139 | . Cache 140 | -> RequestInfo 141 | -> CacheQueryOptions 142 | -> Aff (cache :: CACHE | e) Boolean 143 | delete' = 144 | _delete 145 | 146 | 147 | keys 148 | :: forall e 149 | . Cache 150 | -> Aff (cache :: CACHE | e) (Array RequestInfo) 151 | keys cache = 152 | _keys cache (toNullable Nothing) defaultCacheQueryOptions 153 | 154 | 155 | keys' 156 | :: forall e 157 | . Cache 158 | -> Maybe RequestInfo 159 | -> CacheQueryOptions 160 | -> Aff (cache :: CACHE | e) (Array RequestInfo) 161 | keys' cache req opts = 162 | _keys cache (toNullable req) opts 163 | 164 | 165 | match 166 | :: forall e 167 | . Cache 168 | -> RequestInfo 169 | -> Aff (cache :: CACHE | e) (Maybe Response) 170 | match cache req = 171 | toMaybe <$> _match cache req defaultCacheQueryOptions 172 | 173 | 174 | match' 175 | :: forall e 176 | . Cache 177 | -> RequestInfo 178 | -> CacheQueryOptions 179 | -> Aff (cache :: CACHE | e) (Maybe Response) 180 | match' cache req opts = 181 | toMaybe <$>_match cache req opts 182 | 183 | 184 | matchAll 185 | :: forall e 186 | . Cache 187 | -> Aff (cache :: CACHE | e) (Array Response) 188 | matchAll cache = 189 | _matchAll cache (toNullable Nothing) defaultCacheQueryOptions 190 | 191 | 192 | matchAll' 193 | :: forall e 194 | . Cache 195 | -> Maybe RequestInfo 196 | -> CacheQueryOptions 197 | -> Aff (cache :: CACHE | e) (Array Response) 198 | matchAll' cache req opts = 199 | _matchAll cache (toNullable req) opts 200 | 201 | 202 | put 203 | :: forall e 204 | . Cache 205 | -> RequestInfo 206 | -> Response 207 | -> Aff (cache :: CACHE | e) Unit 208 | put = 209 | _put 210 | 211 | 212 | -------------------- 213 | -- FFI 214 | -------------------- 215 | 216 | -- Cache Storage 217 | 218 | foreign import _deleteCache 219 | :: forall e 220 | . CacheStorage 221 | -> String 222 | -> Aff (cache :: CACHE | e) Boolean 223 | 224 | 225 | foreign import _hasCache 226 | :: forall e 227 | . CacheStorage 228 | -> String 229 | -> Aff (cache :: CACHE | e) Boolean 230 | 231 | 232 | foreign import _keysCache 233 | :: forall e 234 | . CacheStorage 235 | -> Aff (cache :: CACHE | e) (Array String) 236 | 237 | 238 | foreign import _openCache 239 | :: forall e 240 | . CacheStorage 241 | -> String 242 | -> Aff (cache :: CACHE | e) Cache 243 | 244 | 245 | -- Cache 246 | 247 | foreign import _add 248 | :: forall e 249 | . Cache 250 | -> String 251 | -> Aff (cache :: CACHE | e) Unit 252 | 253 | 254 | foreign import _addAll 255 | :: forall e 256 | . Cache 257 | -> Array String 258 | -> Aff (cache :: CACHE | e) Unit 259 | 260 | 261 | foreign import _delete 262 | :: forall e 263 | . Cache 264 | -> String 265 | -> CacheQueryOptions 266 | -> Aff (cache :: CACHE | e) Boolean 267 | 268 | 269 | foreign import _keys 270 | :: forall e 271 | . Cache 272 | -> Nullable String 273 | -> CacheQueryOptions 274 | -> Aff (cache :: CACHE | e) (Array String) 275 | 276 | 277 | foreign import _match 278 | :: forall e 279 | . Cache 280 | -> String 281 | -> CacheQueryOptions 282 | -> Aff (cache :: CACHE | e) (Nullable Response) 283 | 284 | 285 | foreign import _matchAll 286 | :: forall e 287 | . Cache 288 | -> Nullable String 289 | -> CacheQueryOptions 290 | -> Aff (cache :: CACHE | e) (Array Response) 291 | 292 | 293 | foreign import _put 294 | :: forall e 295 | . Cache 296 | -> String 297 | -> Response 298 | -> Aff (cache :: CACHE | e) Unit 299 | -------------------------------------------------------------------------------- /src/Fetch.js: -------------------------------------------------------------------------------- 1 | /* Fetch */ 2 | 3 | exports._fetch = function _fetch(req) { 4 | return function aff(success, error) { 5 | try { 6 | fetch(req).then(success, error); 7 | } catch (err) { 8 | error(err); 9 | } 10 | }; 11 | }; 12 | 13 | 14 | /* Clone */ 15 | 16 | exports._clone = function _clone(obj) { 17 | return function eff() { 18 | return obj.clone(); 19 | }; 20 | }; 21 | 22 | 23 | /* Hasbody */ 24 | 25 | exports._text = function _text(body) { 26 | return function aff(success, error) { 27 | try { 28 | body.text().then(success, error); 29 | } catch (err) { 30 | error(err); 31 | } 32 | }; 33 | }; 34 | 35 | 36 | exports._json = function _json(body) { 37 | return function aff(success, error) { 38 | try { 39 | body.json().then(success, error); 40 | } catch (err) { 41 | error(err); 42 | } 43 | }; 44 | }; 45 | 46 | 47 | /* Request */ 48 | 49 | exports._new = function _new(url) { 50 | return new Request(url); 51 | }; 52 | 53 | exports._newPrime = function _newPrime(url, init) { 54 | return new Request(url, init); 55 | }; 56 | 57 | exports._requestCache = function _requestCache(constructor) { 58 | return function _requestCache2(req) { 59 | return constructor(req.cache); 60 | }; 61 | }; 62 | 63 | exports._requestCredentials = function _requestCredentials(constructor) { 64 | return function _requestCredentials2(req) { 65 | return constructor(req.credentials); 66 | }; 67 | }; 68 | 69 | exports._requestDestination = function _requestDestination(constructor) { 70 | return function _requestDestination2(req) { 71 | return constructor(req.destination); 72 | }; 73 | }; 74 | 75 | exports._requestHeaders = function _requestHeaders(Header) { 76 | return function _requestHeaders2(constructor) { 77 | return function _requestHeaders3(req) { 78 | var headers = []; 79 | var gen = req.headers.entries(); 80 | var entry = gen.next(); 81 | while (!entry.done) { 82 | headers.push(constructor(Header(entry.value[0]))(entry.value[1])); 83 | entry = gen.next(); 84 | } 85 | 86 | return headers; 87 | }; 88 | }; 89 | }; 90 | 91 | exports._requestIntegrity = function _requestIntegrity(req) { 92 | return req.integrity; 93 | }; 94 | 95 | exports._requestKeepAlive = function _requestKeepAlive(req) { 96 | return req.keepalive; 97 | }; 98 | 99 | exports._requestMethod = function _requestMethod(constructor) { 100 | return function _requestMethod2(req) { 101 | return constructor(req.method); 102 | }; 103 | }; 104 | 105 | exports._requestMode = function _requestMode(constructor) { 106 | return function _requestMode2(req) { 107 | return constructor(req.mode); 108 | }; 109 | }; 110 | 111 | exports._requestRedirect = function _requestRedirect(constructor) { 112 | return function _requestRedirect2(req) { 113 | return constructor(req.redirect); 114 | }; 115 | }; 116 | 117 | exports._requestReferrer = function _requestReferrer(req) { 118 | return req.referrer; 119 | }; 120 | 121 | exports._requestReferrerPolicy = function _requestReferrerPolicy(constructor) { 122 | return function _requestReferrerPolicy2(req) { 123 | return constructor(req.referrerPolicy); 124 | }; 125 | }; 126 | 127 | exports._requestURL = function _requestURL(req) { 128 | return req.url; 129 | }; 130 | 131 | exports._requestType = function _requestType(constructor) { 132 | return function _requestType2(req) { 133 | return constructor(req.type); 134 | }; 135 | }; 136 | 137 | 138 | /* Response */ 139 | 140 | exports._responseError = function _responseError(res) { 141 | return res.error(); 142 | }; 143 | 144 | exports._responseHeaders = function _responseHeaders(Header) { 145 | return function _responseHeaders2(constructor) { 146 | return function _responseHeaders3(res) { 147 | var headers = []; 148 | var gen = res.headers.entries(); 149 | var entry = gen.next(); 150 | while (!entry.done) { 151 | headers.push(constructor(Header(entry.value[0]))(entry.value[1])); 152 | entry = gen.next(); 153 | } 154 | 155 | return headers; 156 | }; 157 | }; 158 | }; 159 | 160 | exports._responseOk = function _responseOk(res) { 161 | return res.ok; 162 | }; 163 | 164 | exports._responseRedirect = function _responseRedirect(res) { 165 | return function _responseRedirect2(url) { 166 | return function _responseRedirect3(statusCode) { 167 | return res.redirect(url, statusCode || undefined); 168 | }; 169 | }; 170 | }; 171 | 172 | exports._responseRedirected = function _responseRedirected(res) { 173 | return res.redirected; 174 | }; 175 | 176 | exports._responseStatus = function _responseStatus(constructor) { 177 | return function _responseStatus2(res) { 178 | return constructor(res.status); 179 | }; 180 | }; 181 | 182 | exports._responseStatusCode = function _responseStatusCode(res) { 183 | return res.status; 184 | }; 185 | 186 | exports._responseType = function _responseType(constructor) { 187 | return function _responseType2(res) { 188 | return constructor(res.type); 189 | }; 190 | }; 191 | 192 | exports._responseURL = function _responseURL(res) { 193 | return res.url; 194 | }; 195 | -------------------------------------------------------------------------------- /src/Fetch.purs: -------------------------------------------------------------------------------- 1 | module Fetch 2 | -- * Effects & Classes 3 | ( FETCH 4 | , class IsRequest, toRequest 5 | , class HasBody, text, json 6 | , class Clone, clone 7 | 8 | -- * Fetch 9 | , fetch 10 | 11 | -- * Request 12 | , Request 13 | , RequestInfo 14 | , RequestInit 15 | , Credentials 16 | , Destination 17 | , Mode 18 | , Redirect 19 | , ReferrerPolicy 20 | , RequestCache 21 | , RequestType 22 | , new 23 | , new' 24 | , requestCache 25 | , requestCredentials 26 | , requestDestination 27 | , requestHeaders 28 | , requestIntegrity 29 | , requestKeepAlive 30 | , requestMethod 31 | , requestMode 32 | , requestRedirect 33 | , requestReferrer 34 | , requestReferrerPolicy 35 | , requestURL 36 | , requestType 37 | 38 | -- * Response 39 | , Response 40 | , ResponseType 41 | , responseError 42 | , responseHeaders 43 | , responseOk 44 | , responseRedirect 45 | , responseRedirected 46 | , responseStatus 47 | , responseStatusCode 48 | , responseType 49 | , responseURL 50 | ) where 51 | 52 | import Prelude 53 | 54 | import Control.Monad.Aff (Aff) 55 | import Control.Monad.Eff (kind Effect, Eff) 56 | import Control.Monad.Eff.Exception (EXCEPTION) 57 | import Data.Argonaut.Core (Json) 58 | import Data.Maybe (Maybe(..)) 59 | import Data.Nullable (Nullable, toNullable) 60 | import Data.String.Read (class Read, read) 61 | import Network.HTTP (Verb, Header(..), HeaderHead, StatusCode, string2Head, string2Verb, number2Status, status2Number) 62 | 63 | 64 | -------------------- 65 | -- TYPES 66 | -------------------- 67 | 68 | foreign import data FETCH :: Effect 69 | 70 | 71 | foreign import data Response :: Type 72 | 73 | 74 | foreign import data Request :: Type 75 | 76 | 77 | class IsRequest req where 78 | toRequest :: req -> Request 79 | 80 | 81 | class HasBody body where 82 | json :: forall e. body -> Aff (fetch :: FETCH | e) Json 83 | text :: forall e. body -> Aff (fetch :: FETCH | e) String 84 | 85 | 86 | class Clone object where 87 | clone :: forall e. object -> Eff (exception :: EXCEPTION | e) object 88 | 89 | 90 | type RequestInfo = String 91 | 92 | 93 | type RequestInit = 94 | { requestMethod :: Verb 95 | , requestHeaders :: Array Header 96 | , requestMode :: Mode 97 | , requestCache :: RequestCache 98 | , requestKeepAlive :: Boolean 99 | , requestCredentials :: Maybe Credentials 100 | , requestBody :: Maybe String 101 | , requestReferrer :: Maybe String 102 | , requestReferrerPolicy :: Maybe ReferrerPolicy 103 | , requestRedirect :: Maybe Redirect 104 | , requestIntegrity :: Maybe String 105 | } 106 | 107 | 108 | data RequestType 109 | = UnknownType 110 | | Audio 111 | | Font 112 | | Image 113 | | Script 114 | | Style 115 | | Track 116 | | Video 117 | 118 | 119 | data Destination 120 | = UnknownDestination 121 | | Type RequestType 122 | | Document 123 | | Embed 124 | | Manifest 125 | | Object 126 | | Report 127 | | ServiceWorker 128 | | SharedWorker 129 | | DedicatedWorker 130 | | XSLT 131 | 132 | 133 | data Redirect 134 | = Follow 135 | | Error 136 | | Manual 137 | 138 | 139 | data RequestCache 140 | = Default 141 | | NoStore 142 | | Reload 143 | | NoCache 144 | | ForceCache 145 | | OnlyIfCached 146 | 147 | 148 | data Credentials 149 | = Omit 150 | | SameOriginCredentials 151 | | Include 152 | 153 | 154 | data Mode 155 | = Navigate 156 | | SameOrigin 157 | | NoCors 158 | | Cors 159 | 160 | 161 | data ReferrerPolicy 162 | = NoReferrer 163 | | NoReferrerWhenDowngrade 164 | | SameOriginPolicy 165 | | Origin 166 | | StrictOrigin 167 | | OriginWhenCrossOrigin 168 | | StrictOriginWhenCrossOrigin 169 | | UnsafeURL 170 | 171 | 172 | data ResponseType 173 | = Basic 174 | | CorsResponse 175 | | DefaultResponse 176 | | ErrorResponse 177 | | Opaque 178 | | OpaqueRedirect 179 | 180 | 181 | -------------------- 182 | -- METHODS 183 | -------------------- 184 | 185 | -- Fetch 186 | 187 | fetch 188 | :: forall e req. (IsRequest req) 189 | => req 190 | -> Aff (fetch :: FETCH | e) Response 191 | fetch = 192 | toRequest >>> _fetch 193 | 194 | 195 | -- Request 196 | 197 | new 198 | :: String 199 | -> Request 200 | new url = 201 | _new url 202 | 203 | 204 | new' 205 | :: String 206 | -> RequestInit 207 | -> Eff (exception :: EXCEPTION) Request 208 | new' url opts = 209 | _newPrime url opts 210 | 211 | 212 | requestCache 213 | :: Request 214 | -> RequestCache 215 | requestCache = 216 | _requestCache (read >>> toNullable) 217 | 218 | 219 | requestCredentials 220 | :: Request 221 | -> Credentials 222 | requestCredentials = 223 | _requestCredentials (read >>> toNullable) 224 | 225 | 226 | requestDestination 227 | :: Request 228 | -> Destination 229 | requestDestination = 230 | _requestDestination (read >>> toNullable) 231 | 232 | 233 | requestHeaders 234 | :: Request 235 | -> Array Header 236 | requestHeaders = 237 | _requestHeaders string2Head Header 238 | 239 | requestIntegrity 240 | :: Request 241 | -> String 242 | requestIntegrity = 243 | _requestIntegrity 244 | 245 | 246 | requestKeepAlive 247 | :: Request 248 | -> Boolean 249 | requestKeepAlive = 250 | _requestKeepAlive 251 | 252 | 253 | requestMethod 254 | :: Request 255 | -> Verb 256 | requestMethod = 257 | _requestMethod (string2Verb >>> toNullable) 258 | 259 | 260 | requestMode 261 | :: Request 262 | -> Mode 263 | requestMode = 264 | _requestMode (read >>> toNullable) 265 | 266 | 267 | requestRedirect 268 | :: Request 269 | -> Redirect 270 | requestRedirect = 271 | _requestRedirect (read >>> toNullable) 272 | 273 | 274 | requestReferrer 275 | :: Request 276 | -> String 277 | requestReferrer = 278 | _requestReferrer 279 | 280 | 281 | requestReferrerPolicy 282 | :: Request 283 | -> ReferrerPolicy 284 | requestReferrerPolicy = 285 | _requestReferrerPolicy (read >>> toNullable) 286 | 287 | 288 | requestURL 289 | :: Request 290 | -> String 291 | requestURL = 292 | _requestURL 293 | 294 | 295 | requestType 296 | :: Request 297 | -> RequestType 298 | requestType = 299 | _requestType (read >>> toNullable) 300 | 301 | -- Response 302 | 303 | responseError 304 | :: Response 305 | -> Response 306 | responseError = 307 | _responseError 308 | 309 | 310 | responseHeaders 311 | :: Response 312 | -> Array Header 313 | responseHeaders = 314 | _responseHeaders string2Head Header 315 | 316 | 317 | responseOk 318 | :: Response 319 | -> Boolean 320 | responseOk = 321 | _responseOk 322 | 323 | 324 | responseRedirect 325 | :: Response 326 | -> String 327 | -> Maybe StatusCode 328 | -> Response 329 | responseRedirect res url code = 330 | _responseRedirect res url (toNullable (status2Number <$> code)) 331 | 332 | 333 | responseRedirected 334 | :: Response 335 | -> Boolean 336 | responseRedirected = 337 | _responseRedirected 338 | 339 | 340 | responseStatus 341 | :: Response 342 | -> StatusCode 343 | responseStatus = 344 | _responseStatus (number2Status >>> toNullable) 345 | 346 | 347 | responseStatusCode 348 | :: Response 349 | -> Int 350 | responseStatusCode = 351 | _responseStatusCode 352 | 353 | 354 | responseType 355 | :: Response 356 | -> ResponseType 357 | responseType = 358 | _responseType (read >>> toNullable) 359 | 360 | 361 | responseURL 362 | :: Response 363 | -> String 364 | responseURL = 365 | _responseURL 366 | 367 | 368 | -------------------- 369 | -- INSTANCES 370 | -------------------- 371 | 372 | instance isRequestRequest :: IsRequest Request where 373 | toRequest = 374 | id 375 | 376 | 377 | instance isRequestString :: IsRequest String where 378 | toRequest = 379 | new 380 | 381 | 382 | instance hasBodyRequest :: HasBody Request where 383 | text = 384 | _text 385 | 386 | json = 387 | _json 388 | 389 | 390 | instance hasBodyResponse :: HasBody Response where 391 | text = 392 | _text 393 | 394 | json = 395 | _json 396 | 397 | 398 | instance cloneRequest :: Clone Request where 399 | clone = 400 | _clone 401 | 402 | 403 | instance cloneResponse :: Clone Response where 404 | clone = 405 | _clone 406 | 407 | 408 | instance showRequestType :: Show RequestType where 409 | show reqType = 410 | case reqType of 411 | UnknownType -> "" 412 | Audio -> "audio" 413 | Font -> "font" 414 | Image -> "image" 415 | Script -> "script" 416 | Style -> "style" 417 | Track -> "track" 418 | Video -> "video" 419 | 420 | 421 | instance readRequestType :: Read RequestType where 422 | read s = 423 | case s of 424 | "audio" -> pure Audio 425 | "font" -> pure Font 426 | "image" -> pure Image 427 | "script" -> pure Script 428 | "style" -> pure Style 429 | "track" -> pure Track 430 | "video" -> pure Video 431 | "" -> pure UnknownType 432 | _ -> Nothing 433 | 434 | 435 | instance showDestination :: Show Destination where 436 | show destination = 437 | case destination of 438 | UnknownDestination -> "" 439 | Type t -> show t 440 | Document -> "document" 441 | Embed -> "embed" 442 | Manifest -> "manifest" 443 | Object -> "object" 444 | Report -> "report" 445 | ServiceWorker -> "serviceworker" 446 | SharedWorker -> "sharedworker" 447 | DedicatedWorker -> "worker" 448 | XSLT -> "xslt" 449 | 450 | 451 | instance readDestination :: Read Destination where 452 | read s = 453 | case s of 454 | "document" -> pure Document 455 | "embed" -> pure Embed 456 | "manifest" -> pure Manifest 457 | "object" -> pure Object 458 | "report" -> pure Report 459 | "serviceworker" -> pure ServiceWorker 460 | "sharedworker" -> pure SharedWorker 461 | "worker" -> pure DedicatedWorker 462 | "xslt" -> pure XSLT 463 | "" -> pure UnknownDestination 464 | t -> Type <$> read t 465 | 466 | 467 | instance showRedirect :: Show Redirect where 468 | show redirect = 469 | case redirect of 470 | Follow -> "follow" 471 | Error -> "error" 472 | Manual -> "manual" 473 | 474 | 475 | instance readRedirect :: Read Redirect where 476 | read s = 477 | case s of 478 | "follow" -> pure Follow 479 | "error" -> pure Error 480 | "manual" -> pure Manual 481 | _ -> Nothing 482 | 483 | 484 | instance showRequestCache :: Show RequestCache where 485 | show reqCache = 486 | case reqCache of 487 | Default -> "default" 488 | NoStore -> "no-store" 489 | Reload -> "reload" 490 | NoCache -> "no-cache" 491 | ForceCache -> "force-cache" 492 | OnlyIfCached -> "only-if-cached" 493 | 494 | 495 | instance readRequestCache :: Read RequestCache where 496 | read s = 497 | case s of 498 | "default" -> pure Default 499 | "no-store" -> pure NoStore 500 | "reload" -> pure Reload 501 | "no-cache" -> pure NoCache 502 | "force-cache" -> pure ForceCache 503 | "only-if-cached" -> pure OnlyIfCached 504 | _ -> Nothing 505 | 506 | 507 | instance showCredentials :: Show Credentials where 508 | show credentials = 509 | case credentials of 510 | Omit -> "omit" 511 | SameOriginCredentials -> "same-origin" 512 | Include -> "include" 513 | 514 | 515 | instance readCredentials :: Read Credentials where 516 | read credentials = 517 | case credentials of 518 | "omit" -> pure Omit 519 | "same-origin" -> pure SameOriginCredentials 520 | "include" -> pure Include 521 | _ -> Nothing 522 | 523 | 524 | instance showMode :: Show Mode where 525 | show mode = 526 | case mode of 527 | Navigate -> "navigate" 528 | SameOrigin -> "same-origin" 529 | NoCors -> "no-cors" 530 | Cors -> "cors" 531 | 532 | 533 | instance readMode :: Read Mode where 534 | read s = 535 | case s of 536 | "navigate" -> pure Navigate 537 | "same-origin" -> pure SameOrigin 538 | "no-cors" -> pure NoCors 539 | "cors" -> pure Cors 540 | _ -> Nothing 541 | 542 | 543 | instance showReferrerPolicy :: Show ReferrerPolicy where 544 | show policy = 545 | case policy of 546 | NoReferrer -> "no-referrer" 547 | NoReferrerWhenDowngrade -> "no-referrer-when-downgrade" 548 | SameOriginPolicy -> "same-origin" 549 | Origin -> "origin" 550 | StrictOrigin -> "strict-origin" 551 | OriginWhenCrossOrigin -> "origin-when-cross-origin" 552 | StrictOriginWhenCrossOrigin -> "strict-origin-when-cross-origin" 553 | UnsafeURL -> "unsafe-url" 554 | 555 | 556 | instance readReferrerPolicy :: Read ReferrerPolicy where 557 | read s = 558 | case s of 559 | "no-referrer" -> pure NoReferrer 560 | "no-referrer-when-downgrade" -> pure NoReferrerWhenDowngrade 561 | "same-origin" -> pure SameOriginPolicy 562 | "origin" -> pure Origin 563 | "strict-origin" -> pure StrictOrigin 564 | "origin-when-cross-origin" -> pure OriginWhenCrossOrigin 565 | "strict-origin-when-cross-origin" -> pure StrictOriginWhenCrossOrigin 566 | "unsafe-url" -> pure UnsafeURL 567 | _ -> Nothing 568 | 569 | 570 | instance showResponseType :: Show ResponseType where 571 | show resType = 572 | case resType of 573 | Basic -> "basic" 574 | CorsResponse -> "cors" 575 | DefaultResponse -> "default" 576 | ErrorResponse -> "error" 577 | Opaque -> "opaque" 578 | OpaqueRedirect -> "opaqueredirect" 579 | 580 | 581 | instance readResponseType :: Read ResponseType where 582 | read s = 583 | case s of 584 | "basic" -> pure Basic 585 | "cors" -> pure CorsResponse 586 | "default" -> pure DefaultResponse 587 | "error" -> pure ErrorResponse 588 | "opaque" -> pure Opaque 589 | "opaqueredirect" -> pure OpaqueRedirect 590 | _ -> Nothing 591 | 592 | 593 | -------------------- 594 | -- FFI 595 | -------------------- 596 | 597 | -- Fetch 598 | 599 | foreign import _fetch 600 | :: forall e 601 | . Request 602 | -> Aff (fetch :: FETCH | e) Response 603 | 604 | -- Clone 605 | 606 | foreign import _clone 607 | :: forall object e 608 | . object 609 | -> Eff (exception :: EXCEPTION | e) object 610 | 611 | -- HasBody 612 | 613 | foreign import _text 614 | :: forall e body 615 | . body 616 | -> Aff (fetch :: FETCH | e) String 617 | 618 | 619 | foreign import _json 620 | :: forall e body 621 | . body 622 | -> Aff (fetch :: FETCH | e) Json 623 | 624 | 625 | -- Request 626 | 627 | foreign import _new 628 | :: String 629 | -> Request 630 | 631 | 632 | foreign import _newPrime 633 | :: forall e 634 | . String 635 | -> RequestInit 636 | -> Eff (exception :: EXCEPTION | e) Request 637 | 638 | 639 | foreign import _requestCache 640 | :: (String -> Nullable RequestCache) 641 | -> Request 642 | -> RequestCache 643 | 644 | 645 | foreign import _requestCredentials 646 | :: (String -> Nullable Credentials) 647 | -> Request 648 | -> Credentials 649 | 650 | 651 | foreign import _requestDestination 652 | :: (String -> Nullable Destination) 653 | -> Request 654 | -> Destination 655 | 656 | 657 | foreign import _requestHeaders 658 | :: (String -> HeaderHead) 659 | -> (HeaderHead -> String -> Header) 660 | -> Request 661 | -> Array Header 662 | 663 | foreign import _requestIntegrity 664 | :: Request 665 | -> String 666 | 667 | 668 | foreign import _requestKeepAlive 669 | :: Request 670 | -> Boolean 671 | 672 | 673 | foreign import _requestMethod 674 | :: (String -> Nullable Verb) 675 | -> Request 676 | -> Verb 677 | 678 | 679 | foreign import _requestMode 680 | :: (String -> Nullable Mode) 681 | -> Request 682 | -> Mode 683 | 684 | 685 | foreign import _requestRedirect 686 | :: (String -> Nullable Redirect) 687 | -> Request 688 | -> Redirect 689 | 690 | 691 | foreign import _requestReferrer 692 | :: Request 693 | -> String 694 | 695 | 696 | foreign import _requestReferrerPolicy 697 | :: (String -> Nullable ReferrerPolicy) 698 | -> Request 699 | -> ReferrerPolicy 700 | 701 | 702 | foreign import _requestURL 703 | :: Request 704 | -> String 705 | 706 | 707 | foreign import _requestType 708 | :: (String -> Nullable RequestType) 709 | -> Request 710 | -> RequestType 711 | 712 | -- Response 713 | 714 | foreign import _responseError 715 | :: Response 716 | -> Response 717 | 718 | 719 | foreign import _responseHeaders 720 | :: (String -> HeaderHead) 721 | -> (HeaderHead -> String -> Header) 722 | -> Response 723 | -> Array Header 724 | 725 | 726 | foreign import _responseOk 727 | :: Response 728 | -> Boolean 729 | 730 | 731 | foreign import _responseRedirect 732 | :: Response 733 | -> String 734 | -> Nullable Int 735 | -> Response 736 | 737 | 738 | foreign import _responseRedirected 739 | :: Response 740 | -> Boolean 741 | 742 | 743 | foreign import _responseStatus 744 | :: (Int -> Nullable StatusCode) 745 | -> Response 746 | -> StatusCode 747 | 748 | 749 | foreign import _responseStatusCode 750 | :: Response 751 | -> Int 752 | 753 | 754 | foreign import _responseType 755 | :: (String -> Nullable ResponseType) 756 | -> Response 757 | -> ResponseType 758 | 759 | 760 | foreign import _responseURL 761 | :: Response 762 | -> String 763 | -------------------------------------------------------------------------------- /src/GlobalScope.js: -------------------------------------------------------------------------------- 1 | exports._location = function _location(toLocation) { 2 | return function eff() { 3 | // NOTE A Plain JS Object is created because the WorkerLocation object 4 | // can't be serialized and may lead to weird behavior. 5 | return toLocation({ 6 | origin: location.origin || '', 7 | protocol: location.protocol || '', 8 | host: location.host || '', 9 | hostname: location.hostname || '', 10 | port: location.port || '', 11 | pathname: location.pathname || '', 12 | search: location.search || '', 13 | hash: location.hash || '', 14 | }); 15 | }; 16 | }; 17 | 18 | exports._navigator = function _navigator(toNavigator) { 19 | return function eff() { 20 | // NOTE A Plain JS Object is created because the WorkerNavigator object 21 | // can't be serialized and may lead to weird behavior. 22 | return toNavigator({ 23 | appCodeName: navigator.appCodeName || '', 24 | appName: navigator.appName || '', 25 | appVersion: navigator.appVersion || '', 26 | platform: navigator.platform || '', 27 | product: navigator.product || '', 28 | productSub: navigator.productSub || '', 29 | userAgent: navigator.userAgent || '', 30 | vendor: navigator.vendor || '', 31 | vendorSub: navigator.vendorSub || '', 32 | language: navigator.language || '', 33 | languages: Array.prototype.slice.apply(navigator.languages || []), 34 | onLine: navigator.onLine || false, 35 | }); 36 | }; 37 | }; 38 | 39 | exports.close = function eff() { 40 | self.close(); 41 | }; 42 | 43 | exports.onError = function _onError(f) { 44 | return function eff() { 45 | self.onerror = function onerror(msg) { 46 | f(new Error(msg))(); 47 | // NOTE indicates that the error has been handled, 48 | // so it isn't propagated to the parent 49 | return true; 50 | }; 51 | }; 52 | }; 53 | 54 | exports.onLanguageChange = function _onLanguageChange(f) { 55 | return function eff() { 56 | self.onlanguagechange = function onlanguagechange() { 57 | f(); 58 | }; 59 | }; 60 | }; 61 | 62 | exports.onOffline = function _onOffline(f) { 63 | return function eff() { 64 | self.onoffline = function onoffline() { 65 | f(); 66 | }; 67 | }; 68 | }; 69 | 70 | exports.onOnline = function _onOnline(f) { 71 | return function eff() { 72 | self.ononline = function ononline() { 73 | f(); 74 | }; 75 | }; 76 | }; 77 | 78 | exports.onRejectionHandled = function _onRejectionHandled(f) { 79 | return function eff() { 80 | self.onrejectionhandled = function onrejectionhandled() { 81 | f(); 82 | }; 83 | }; 84 | }; 85 | 86 | exports.onUnhandledRejection = function _onUnhandledRejection(f) { 87 | return function eff() { 88 | self.onrejectionunhandled = function onrejectionunhandled() { 89 | f(); 90 | }; 91 | }; 92 | }; 93 | -------------------------------------------------------------------------------- /src/GlobalScope.purs: -------------------------------------------------------------------------------- 1 | module GlobalScope 2 | ( location 3 | , navigator 4 | , close 5 | , onError 6 | , onLanguageChange 7 | , onOffline 8 | , onOnline 9 | , onRejectionHandled 10 | , onUnhandledRejection 11 | ) where 12 | 13 | import Prelude 14 | 15 | import Control.Monad.Eff (Eff) 16 | import Control.Monad.Eff.Exception (Error) 17 | 18 | import Workers (WORKER, Location(..), Navigator(..)) 19 | 20 | 21 | location 22 | :: forall e 23 | . Eff (worker :: WORKER | e) Location 24 | location = 25 | _location Location 26 | 27 | 28 | foreign import _location 29 | :: forall a e 30 | . (a -> Location) 31 | -> Eff (worker :: WORKER | e) Location 32 | 33 | 34 | navigator 35 | :: forall e 36 | . Eff (worker :: WORKER | e) Navigator 37 | navigator = 38 | _navigator Navigator 39 | 40 | 41 | foreign import _navigator 42 | :: forall a e 43 | . (a -> Navigator) 44 | -> Eff (worker :: WORKER | e) Navigator 45 | 46 | 47 | foreign import close 48 | :: forall e 49 | . Eff (worker :: WORKER | e) Unit 50 | 51 | 52 | foreign import onError 53 | :: forall e e' 54 | . (Error -> Eff ( | e') Unit) 55 | -> Eff (worker :: WORKER | e) Unit 56 | 57 | 58 | foreign import onLanguageChange 59 | :: forall e e' 60 | . Eff ( | e') Unit 61 | -> Eff (worker :: WORKER | e) Unit 62 | 63 | 64 | foreign import onOffline 65 | :: forall e e' 66 | . Eff ( | e') Unit 67 | -> Eff (worker :: WORKER | e) Unit 68 | 69 | 70 | foreign import onOnline 71 | :: forall e e' 72 | . Eff ( | e') Unit 73 | -> Eff (worker :: WORKER | e) Unit 74 | 75 | 76 | foreign import onRejectionHandled 77 | :: forall e e' 78 | . Eff ( | e') Unit 79 | -> Eff (worker :: WORKER | e) Unit 80 | 81 | 82 | foreign import onUnhandledRejection 83 | :: forall e e' 84 | . Eff ( | e') Unit 85 | -> Eff (worker :: WORKER | e) Unit 86 | -------------------------------------------------------------------------------- /src/GlobalScope/Dedicated.js: -------------------------------------------------------------------------------- 1 | exports.name = function _name() { 2 | return function eff() { 3 | return self.name; 4 | }; 5 | }; 6 | 7 | exports._postMessage = function _postMessage(msg) { 8 | return function postMessage2(transfer) { 9 | return function eff() { 10 | self.postMessage(msg, transfer.length > 0 ? transfer : undefined); 11 | }; 12 | }; 13 | }; 14 | 15 | exports.onMessage = function _onMessage(f) { 16 | return function eff() { 17 | self.onmessage = function onMessage(e) { 18 | f(e.data)(); 19 | }; 20 | }; 21 | }; 22 | 23 | exports.onMessageError = function _onMessageError(f) { 24 | return function eff() { 25 | self.onmessageerror = function onMessageError(e) { 26 | f(e.target.error); 27 | }; 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /src/GlobalScope/Dedicated.purs: -------------------------------------------------------------------------------- 1 | module GlobalScope.Dedicated 2 | ( name 3 | , postMessage 4 | , postMessage' 5 | , onMessage 6 | , onMessageError 7 | , module GlobalScope 8 | ) where 9 | 10 | import Prelude (Unit) 11 | 12 | import Control.Monad.Eff (Eff) 13 | import Control.Monad.Eff.Exception (EXCEPTION, Error) 14 | 15 | import Workers (WORKER) 16 | import GlobalScope (close, location, navigator, onError, onLanguageChange 17 | ,onOffline, onOnline, onRejectionHandled, onUnhandledRejection) 18 | 19 | 20 | -- | Returns dedicatedWorkerGlobal’s name, i.e. the value given to the Worker constructor. 21 | -- | Primarily useful for debugging. 22 | foreign import name 23 | :: forall e 24 | . Eff (worker :: WORKER | e) String 25 | 26 | 27 | -- | Clones message and transmits it to the Worker object. 28 | postMessage 29 | :: forall e msg 30 | . msg 31 | -> Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 32 | postMessage msg = 33 | _postMessage msg [] 34 | 35 | 36 | -- | Clones message and transmits it to the Worker object associated with 37 | -- | dedicatedWorkerGlobal.transfer can be passed as a list of objects that are to be 38 | -- | transferred rather than cloned. 39 | postMessage' 40 | :: forall e msg transfer 41 | . msg 42 | -> Array transfer 43 | -> Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 44 | postMessage' = 45 | _postMessage 46 | 47 | 48 | foreign import _postMessage 49 | :: forall e msg transfer 50 | . msg 51 | -> Array transfer 52 | -> Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 53 | 54 | 55 | -- | Event handler for the `message` event 56 | foreign import onMessage 57 | :: forall e e' msg 58 | . (msg -> Eff ( | e') Unit) 59 | -> Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 60 | 61 | 62 | -- | Event handler for the `messageError` event 63 | foreign import onMessageError 64 | :: forall e e' 65 | . (Error -> Eff ( | e') Unit) 66 | -> Eff (worker :: WORKER | e) Unit 67 | -------------------------------------------------------------------------------- /src/GlobalScope/Service.js: -------------------------------------------------------------------------------- 1 | /* Global Scope */ 2 | 3 | exports._caches = function eff() { 4 | return self.caches; 5 | }; 6 | 7 | exports._clients = function eff() { 8 | return self.clients; 9 | }; 10 | 11 | exports._registration = function eff() { 12 | return self.registration; 13 | }; 14 | 15 | exports._skipWaiting = function eff() { 16 | self.skipWaiting(); 17 | }; 18 | 19 | exports._onInstall = function _onInstall(f) { 20 | return function eff() { 21 | self.oninstall = function oninstall(e) { 22 | e.waitUntil(new Promise(function waitUntil(resolve, reject) { 23 | try { 24 | f(resolve, reject); 25 | } catch (err) { 26 | reject(err); 27 | } 28 | })); 29 | }; 30 | }; 31 | }; 32 | 33 | exports._onActivate = function _onActivate(f) { 34 | return function eff() { 35 | self.onactivate = function onactivate(e) { 36 | e.waitUntil(new Promise(function waitUntil(resolve, reject) { 37 | try { 38 | f(resolve, reject); 39 | } catch (err) { 40 | reject(err); 41 | } 42 | })); 43 | }; 44 | }; 45 | }; 46 | 47 | exports._onFetch = function _onFetch(toNullable) { 48 | return function _onFetch2(respondWith) { 49 | return function _onFetch3(waitUntil) { 50 | return function eff() { 51 | self.onfetch = function onfetch(e) { 52 | e.respondWith(new Promise(function respondWithCb(resolve, reject) { 53 | try { 54 | respondWith(e.request)(function onSuccess(mres) { 55 | var mres_ = toNullable(mres); 56 | if (mres_ != null) { 57 | resolve(mres_); 58 | return; 59 | } 60 | 61 | self.fetch(e.request).then(resolve, reject); 62 | }, reject); 63 | } catch (err) { 64 | reject(err); 65 | } 66 | })); 67 | 68 | e.waitUntil(new Promise(function waitUntilCb(resolve, reject) { 69 | try { 70 | waitUntil(e.request)(resolve, reject); 71 | } catch (err) { 72 | reject(err); 73 | } 74 | })); 75 | }; 76 | }; 77 | }; 78 | }; 79 | }; 80 | 81 | exports._onMessage = function _onMessage(f) { 82 | return function eff() { 83 | self.onmessage = function onmessage(e) { 84 | e.waitUntil(new Promise(function waitUntil(resolve, reject) { 85 | try { 86 | f(e.source.id)(e.data)(resolve, reject); 87 | } catch (err) { 88 | reject(err); 89 | } 90 | })); 91 | }; 92 | }; 93 | }; 94 | 95 | /* Clients Interface */ 96 | 97 | exports._get = function _get(clients) { 98 | return function _get2(id) { 99 | return function aff(success, error) { 100 | try { 101 | clients.get(id).then(success, error); 102 | } catch (err) { 103 | error(err); 104 | } 105 | }; 106 | }; 107 | }; 108 | 109 | exports._matchAll = function _matchAll(clients) { 110 | return function _matchAll2(opts) { 111 | return function aff(success, error) { 112 | try { 113 | clients.matchAll(opts) 114 | .then(function onSuccess(matches) { 115 | success(Array.prototype.slice.apply(matches)); 116 | }) 117 | .catch(error); 118 | } catch (err) { 119 | error(err); 120 | } 121 | }; 122 | }; 123 | }; 124 | 125 | exports._openWindow = function _openWindow(clients) { 126 | return function _openWindow2(url) { 127 | return function aff(success, error) { 128 | try { 129 | clients.openWindow(url).then(success, error); 130 | } catch (err) { 131 | error(err); 132 | } 133 | }; 134 | }; 135 | }; 136 | 137 | exports._claim = function _claim(clients) { 138 | return function aff(success, error) { 139 | try { 140 | clients.claim().then(success, error); 141 | } catch (err) { 142 | error(err); 143 | } 144 | }; 145 | }; 146 | 147 | /* Client Interface */ 148 | 149 | exports._url = function _url(client) { 150 | return client.url; 151 | }; 152 | 153 | exports._frameType = function _frameType(toFrameType) { 154 | return function _frameType2(client) { 155 | return toFrameType(client.frameType); 156 | }; 157 | }; 158 | 159 | exports._clientId = function _clientId(client) { 160 | return client.id; 161 | }; 162 | 163 | /* Window Client Interface */ 164 | 165 | exports._visibilityState = function _visibilityState(toVisibilityState) { 166 | return function _visibilityState2(client) { 167 | return toVisibilityState(client.visibilityState); 168 | }; 169 | }; 170 | 171 | exports._focused = function _focused(client) { 172 | return client.focused; 173 | }; 174 | 175 | exports._focus = function _focus(client) { 176 | return function aff(success, error) { 177 | try { 178 | client.focus().then(success, error); 179 | } catch (err) { 180 | error(err); 181 | } 182 | }; 183 | }; 184 | 185 | exports._navigate = function _navigate(client) { 186 | return function _navigate2(url) { 187 | return function aff(success, error) { 188 | client.navigate(url).then(success, error); 189 | }; 190 | }; 191 | }; 192 | -------------------------------------------------------------------------------- /src/GlobalScope/Service.purs: -------------------------------------------------------------------------------- 1 | module GlobalScope.Service 2 | -- * Global Scope 3 | ( caches 4 | , clients 5 | , registration 6 | , skipWaiting 7 | , onInstall 8 | , onActivate 9 | , onFetch 10 | , onMessage 11 | 12 | -- * Clients Interface 13 | , Clients 14 | , ClientQueryOptions 15 | , get 16 | , matchAll 17 | , matchAll' 18 | , openWindow 19 | , claim 20 | 21 | 22 | -- * Client Interface 23 | , Client 24 | , ClientId 25 | , ClientType(..) 26 | , url 27 | , frameType 28 | , clientId 29 | 30 | 31 | -- * Window Client Interface 32 | , WindowClient 33 | , FrameType(..) 34 | , VisibilityState(..) 35 | , visibilityState 36 | , focused 37 | , focus 38 | , navigate 39 | ) where 40 | 41 | 42 | import Prelude 43 | 44 | import Control.Monad.Aff (Aff) 45 | import Control.Monad.Eff (Eff) 46 | import Data.Maybe (Maybe(..)) 47 | import Data.Nullable (Nullable, toMaybe, toNullable) 48 | import Data.String.Read (class Read, read) 49 | 50 | import Workers.Service (WORKER, Registration) 51 | import Workers.Class (class Channel) 52 | import Cache (CacheStorage) 53 | import Fetch (Request, Response) 54 | 55 | 56 | -------------------- 57 | -- TYPES 58 | -------------------- 59 | 60 | 61 | foreign import data Clients :: Type 62 | 63 | 64 | foreign import data Client :: Type 65 | 66 | 67 | foreign import data WindowClient :: Type 68 | 69 | 70 | type ClientId = String 71 | 72 | 73 | type ClientQueryOptions = 74 | { includeUncontrolled :: Boolean 75 | , clientType :: ClientType 76 | } 77 | 78 | 79 | data ClientType 80 | = Window 81 | | Worker 82 | | SharedWorker 83 | | All 84 | 85 | 86 | data FrameType 87 | = Auxiliary 88 | | TopLevel 89 | | Nested 90 | | None 91 | 92 | 93 | data VisibilityState 94 | = Hidden 95 | | Visible 96 | | PreRender 97 | | Unloaded 98 | 99 | 100 | -------------------- 101 | -- METHODS 102 | -------------------- 103 | 104 | -- Global Scope 105 | 106 | caches 107 | :: forall e 108 | . Eff (worker :: WORKER | e) CacheStorage 109 | caches = 110 | _caches 111 | 112 | 113 | clients 114 | :: forall e 115 | . Eff (worker :: WORKER | e) Clients 116 | clients = 117 | _clients 118 | 119 | 120 | registration 121 | :: forall e 122 | . Eff (worker :: WORKER | e) Registration 123 | registration = 124 | _registration 125 | 126 | 127 | skipWaiting 128 | :: forall e 129 | . Eff (worker :: WORKER | e) Unit 130 | skipWaiting = 131 | _skipWaiting 132 | 133 | 134 | onInstall 135 | :: forall e e' 136 | . Aff ( | e') Unit 137 | -> Eff (worker :: WORKER | e) Unit 138 | onInstall = 139 | _onInstall 140 | 141 | 142 | onActivate 143 | :: forall e e' 144 | . Aff ( | e') Unit 145 | -> Eff (worker :: WORKER | e) Unit 146 | onActivate = 147 | _onActivate 148 | 149 | 150 | onFetch 151 | :: forall e e' e'' 152 | . (Request -> Aff ( | e') (Maybe Response)) 153 | -> (Request -> Aff ( | e'') Unit) 154 | -> Eff (worker :: WORKER | e) Unit 155 | onFetch f = 156 | _onFetch toNullable f 157 | 158 | 159 | onMessage 160 | :: forall e e' msg 161 | . (ClientId -> msg -> Aff ( | e') Unit) 162 | -> Eff (worker :: WORKER | e) Unit 163 | onMessage = 164 | _onMessage 165 | 166 | -- Clients Interface 167 | 168 | get 169 | :: forall e 170 | . Clients 171 | -> ClientId 172 | -> Aff (worker :: WORKER | e) (Maybe Client) 173 | get cls cid = 174 | toMaybe <$> _get cls cid 175 | 176 | 177 | matchAll 178 | :: forall e 179 | . Clients 180 | -> Aff (worker :: WORKER | e) (Array Client) 181 | matchAll cls = 182 | _matchAll cls { includeUncontrolled: false, clientType: Window } 183 | 184 | 185 | matchAll' 186 | :: forall e 187 | . Clients 188 | -> ClientQueryOptions 189 | -> Aff (worker :: WORKER | e) (Array Client) 190 | matchAll' = 191 | _matchAll 192 | 193 | 194 | openWindow 195 | :: forall e 196 | . Clients 197 | -> String 198 | -> Aff (worker :: WORKER | e) WindowClient 199 | openWindow = 200 | _openWindow 201 | 202 | 203 | claim 204 | :: forall e 205 | . Clients 206 | -> Aff (worker :: WORKER | e) Unit 207 | claim = 208 | _claim 209 | 210 | -- Client Interface 211 | 212 | url 213 | :: Client 214 | -> String 215 | url = 216 | _url 217 | 218 | 219 | frameType 220 | :: Client 221 | -> FrameType 222 | frameType = 223 | _frameType (read >>> toNullable) 224 | 225 | 226 | clientId 227 | :: Client 228 | -> ClientId 229 | clientId = 230 | _clientId 231 | 232 | -- Window Client Interface 233 | 234 | visibilityState 235 | :: WindowClient 236 | -> VisibilityState 237 | visibilityState = 238 | _visibilityState (read >>> toNullable) 239 | 240 | 241 | focused 242 | :: WindowClient 243 | -> Boolean 244 | focused = 245 | _focused 246 | 247 | 248 | focus 249 | :: forall e 250 | . WindowClient 251 | -> Aff (worker :: WORKER | e) WindowClient 252 | focus = 253 | _focus 254 | 255 | 256 | navigate 257 | :: forall e 258 | . WindowClient 259 | -> String 260 | -> Aff (worker :: WORKER | e) WindowClient 261 | navigate = 262 | _navigate 263 | 264 | 265 | -------------------- 266 | -- INSTANCES 267 | -------------------- 268 | 269 | 270 | instance channelClient :: Channel Client where 271 | 272 | 273 | instance showClientType :: Show ClientType where 274 | show x = 275 | case x of 276 | Window -> "window" 277 | Worker -> "worker" 278 | SharedWorker -> "sharedworker" 279 | All -> "all" 280 | 281 | 282 | instance showFrameType :: Show FrameType where 283 | show x = 284 | case x of 285 | Auxiliary -> "auxiliary" 286 | TopLevel -> "top-level" 287 | Nested -> "nested" 288 | None -> "none" 289 | 290 | 291 | instance showVisibilityState :: Show VisibilityState where 292 | show x = 293 | case x of 294 | Hidden -> "hidden" 295 | Visible -> "visible" 296 | PreRender -> "prerender" 297 | Unloaded -> "unloaded" 298 | 299 | 300 | instance readClientType :: Read ClientType where 301 | read s = 302 | case s of 303 | "window" -> pure Window 304 | "worker" -> pure Worker 305 | "sharedworker" -> pure SharedWorker 306 | "all" -> pure All 307 | _ -> Nothing 308 | 309 | 310 | instance readFrameType :: Read FrameType where 311 | read s = 312 | case s of 313 | "auxiliary" -> pure Auxiliary 314 | "top-level" -> pure TopLevel 315 | "nested" -> pure Nested 316 | "none" -> pure None 317 | _ -> Nothing 318 | 319 | 320 | instance readVisibilityState :: Read VisibilityState where 321 | read s = 322 | case s of 323 | "hidden" -> pure Hidden 324 | "visible" -> pure Visible 325 | "prerender" -> pure PreRender 326 | "unloaded" -> pure Unloaded 327 | _ -> Nothing 328 | 329 | 330 | -------------------- 331 | -- FFI 332 | -------------------- 333 | 334 | -- Global Scope 335 | 336 | foreign import _caches 337 | :: forall e 338 | . Eff (worker :: WORKER | e) CacheStorage 339 | 340 | 341 | foreign import _clients 342 | :: forall e 343 | . Eff (worker :: WORKER | e) Clients 344 | 345 | 346 | foreign import _registration 347 | :: forall e 348 | . Eff (worker :: WORKER | e) Registration 349 | 350 | 351 | foreign import _skipWaiting 352 | :: forall e 353 | . Eff (worker :: WORKER | e) Unit 354 | 355 | 356 | foreign import _onInstall 357 | :: forall e e' 358 | . Aff ( | e') Unit 359 | -> Eff (worker :: WORKER | e) Unit 360 | 361 | 362 | foreign import _onActivate 363 | :: forall e e' 364 | . Aff ( | e') Unit 365 | -> Eff (worker :: WORKER | e) Unit 366 | 367 | 368 | foreign import _onFetch 369 | :: forall a e e' e'' 370 | . (Maybe a -> Nullable a) 371 | -> (Request -> Aff ( | e') (Maybe Response)) 372 | -> (Request -> Aff ( | e'') Unit) 373 | -> Eff (worker :: WORKER | e) Unit 374 | 375 | 376 | foreign import _onMessage 377 | :: forall e e' msg 378 | . (ClientId -> msg -> Aff ( | e') Unit) 379 | -> Eff (worker :: WORKER | e) Unit 380 | 381 | -- Clients Interface 382 | 383 | foreign import _get 384 | :: forall e 385 | . Clients 386 | -> ClientId 387 | -> Aff (worker :: WORKER | e) (Nullable Client) 388 | 389 | 390 | foreign import _matchAll 391 | :: forall e 392 | . Clients 393 | -> ClientQueryOptions 394 | -> Aff (worker :: WORKER | e) (Array Client) 395 | 396 | 397 | foreign import _openWindow 398 | :: forall e 399 | . Clients 400 | -> String 401 | -> Aff (worker :: WORKER | e) WindowClient 402 | 403 | 404 | foreign import _claim 405 | :: forall e 406 | . Clients 407 | -> Aff (worker :: WORKER | e) Unit 408 | 409 | -- Client Interface 410 | 411 | foreign import _url 412 | :: Client 413 | -> String 414 | 415 | 416 | -- NOTE The null case is "unsafely" ignored 417 | foreign import _frameType 418 | :: (String -> Nullable FrameType) 419 | -> Client 420 | -> FrameType 421 | 422 | 423 | foreign import _clientId 424 | :: Client 425 | -> ClientId 426 | 427 | -- Window Client Interface 428 | 429 | -- NOTE The null case is "unsafely" ignored 430 | foreign import _visibilityState 431 | :: (String -> Nullable VisibilityState) 432 | -> WindowClient 433 | -> VisibilityState 434 | 435 | 436 | foreign import _focused 437 | :: WindowClient 438 | -> Boolean 439 | 440 | 441 | foreign import _focus 442 | :: forall e 443 | . WindowClient 444 | -> Aff (worker :: WORKER | e) WindowClient 445 | 446 | 447 | foreign import _navigate 448 | :: forall e 449 | . WindowClient 450 | -> String 451 | -> Aff (worker :: WORKER | e) WindowClient 452 | -------------------------------------------------------------------------------- /src/GlobalScope/Shared.js: -------------------------------------------------------------------------------- 1 | exports.name = function eff() { 2 | return self.name; 3 | }; 4 | 5 | exports.applicationCache = function eff() { 6 | return self.applicationCache; 7 | }; 8 | 9 | exports._onConnect = function _onConnect(nonEmpty) { 10 | return function _onConnect2(f) { 11 | return function eff() { 12 | self.onconnect = function onconnect(e) { 13 | const head = e.ports[0]; 14 | const queue = Array.prototype.slice.call(e.ports, 1); 15 | f(nonEmpty(head)(queue))(); 16 | }; 17 | }; 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/GlobalScope/Shared.purs: -------------------------------------------------------------------------------- 1 | module GlobalScope.Shared 2 | ( name 3 | , applicationCache 4 | , onConnect 5 | , module GlobalScope 6 | , module ApplicationCache 7 | ) where 8 | 9 | import Prelude (Unit) 10 | 11 | import Control.Monad.Eff (Eff) 12 | import Data.NonEmpty (NonEmpty(..)) 13 | 14 | import ApplicationCache (APPCACHE, ApplicationCache, Status(..), abort, status, swapCache, update) 15 | import GlobalScope (close, location, navigator, onError, onLanguageChange, onOffline, onOnline, onRejectionHandled, onUnhandledRejection) 16 | import Workers (WORKER) 17 | import MessagePort (MessagePort) 18 | 19 | 20 | -- | Returns sharedWorkerGlobal’s name, i.e. the value given to the SharedWorker constructor. 21 | -- | Multiple SharedWorker objects can correspond to the same shared worker (and 22 | -- | SharedWorkerGlobalScope), by reusing the same name. 23 | foreign import name 24 | :: forall e 25 | . Eff (worker :: WORKER | e) String 26 | 27 | 28 | -- | The applicationCache attribute returns the ApplicationCache object for the worker. 29 | foreign import applicationCache 30 | :: forall e 31 | . Eff (worker :: WORKER | e) ApplicationCache 32 | 33 | 34 | -- | Event handler for the `connect` event 35 | onConnect 36 | :: forall e e' 37 | . (NonEmpty Array MessagePort -> Eff ( | e') Unit) 38 | -> Eff (worker :: WORKER | e) Unit 39 | onConnect = 40 | _onConnect NonEmpty 41 | 42 | 43 | foreign import _onConnect 44 | :: forall e e' 45 | . (MessagePort -> Array MessagePort -> NonEmpty Array MessagePort) 46 | -> (NonEmpty Array MessagePort -> Eff ( | e') Unit) 47 | -> Eff (worker :: WORKER | e) Unit 48 | -------------------------------------------------------------------------------- /src/MessagePort.js: -------------------------------------------------------------------------------- 1 | exports._close = function _close(port) { 2 | return function eff() { 3 | port.close(); 4 | }; 5 | }; 6 | 7 | exports._start = function _start(port) { 8 | return function eff() { 9 | port.start(); 10 | }; 11 | }; 12 | 13 | exports._onMessage = function _onMessage(port) { 14 | return function onMessage2(f) { 15 | return function eff() { 16 | port.onmessage = function onmessage(e) { 17 | f(e.data)(); 18 | }; 19 | }; 20 | }; 21 | }; 22 | 23 | exports._onMessageError = function _onMessageError(port) { 24 | return function onMessageError2(f) { 25 | return function eff() { 26 | port.onmessageerror = function onmessageerror(e) { 27 | f(e.target.error)(); // FIXME 28 | }; 29 | }; 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /src/MessagePort.purs: -------------------------------------------------------------------------------- 1 | module MessagePort 2 | -- * Types 3 | ( MessagePort 4 | 5 | -- * Interaction with MessagePort-like types 6 | , onMessage 7 | , onMessageError 8 | 9 | -- * MessagePort specific manipulations 10 | , close 11 | , start 12 | ) where 13 | 14 | import Prelude 15 | 16 | import Control.Monad.Eff (Eff) 17 | import Control.Monad.Eff.Exception (Error) 18 | 19 | import Workers (WORKER) 20 | import Workers.Class (class Channel) 21 | 22 | 23 | -------------------- 24 | -- TYPES 25 | -------------------- 26 | 27 | 28 | foreign import data MessagePort :: Type 29 | 30 | 31 | -------------------- 32 | -- METHODS 33 | -------------------- 34 | 35 | -- | Event handler for the `message` event 36 | onMessage 37 | :: forall e e' msg 38 | . MessagePort 39 | -> (msg -> Eff ( | e') Unit) 40 | -> Eff (worker :: WORKER | e) Unit 41 | onMessage port = 42 | _onMessage port 43 | 44 | 45 | -- | Event handler for the `messageError` event 46 | onMessageError 47 | :: forall e e' 48 | . MessagePort 49 | -> (Error -> Eff ( | e') Unit) 50 | -> Eff (worker :: WORKER | e) Unit 51 | onMessageError port = 52 | _onMessageError port 53 | 54 | 55 | -- | TODO DOC 56 | close 57 | :: forall e 58 | . MessagePort 59 | -> Eff (worker :: WORKER | e) Unit 60 | close = 61 | _close 62 | 63 | 64 | -- | TODO DOC 65 | start 66 | :: forall e 67 | . MessagePort 68 | -> Eff (worker :: WORKER | e) Unit 69 | start = 70 | _start 71 | 72 | 73 | -------------------- 74 | -- INSTANCES 75 | -------------------- 76 | 77 | 78 | instance channelMessagePort :: Channel MessagePort 79 | 80 | 81 | -------------------- 82 | -- FFI 83 | -------------------- 84 | 85 | 86 | foreign import _onMessage 87 | :: forall e e' msg 88 | . MessagePort 89 | -> (msg -> Eff ( | e') Unit) 90 | -> Eff (worker :: WORKER | e) Unit 91 | 92 | 93 | foreign import _onMessageError 94 | :: forall e e' 95 | . MessagePort 96 | -> (Error -> Eff ( | e') Unit) 97 | -> Eff (worker :: WORKER | e) Unit 98 | 99 | 100 | foreign import _close 101 | :: forall e 102 | . MessagePort 103 | -> Eff (worker :: WORKER | e) Unit 104 | 105 | 106 | foreign import _start 107 | :: forall e 108 | . MessagePort 109 | -> Eff (worker :: WORKER | e) Unit 110 | -------------------------------------------------------------------------------- /src/Workers.js: -------------------------------------------------------------------------------- 1 | exports._onError = function _onError(wrk) { 2 | return function _onError2(f) { 3 | return function eff() { 4 | wrk.onerror = function onerror(err) { 5 | f(err)(); 6 | }; 7 | }; 8 | }; 9 | }; 10 | 11 | exports._postMessage = function postMessage(channel) { 12 | return function postMessage2(msg) { 13 | return function postMessage3(transfer) { 14 | return function eff() { 15 | channel.postMessage(msg, transfer.length > 0 ? transfer : undefined); 16 | }; 17 | }; 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/Workers.purs: -------------------------------------------------------------------------------- 1 | module Workers 2 | ( WORKER 3 | , Location(..) 4 | , Navigator(..) 5 | , WorkerType(..) 6 | , Options 7 | , Credentials(..) 8 | , onError 9 | , postMessage 10 | , postMessage' 11 | ) where 12 | 13 | import Prelude 14 | 15 | import Control.Monad.Eff (kind Effect, Eff) 16 | import Control.Monad.Eff.Exception (EXCEPTION, Error) 17 | import Data.Generic (class Generic, gShow) 18 | import Data.Maybe (Maybe(..)) 19 | import Data.String.Read (class Read) 20 | 21 | import Workers.Class (class AbstractWorker, class Channel) 22 | 23 | 24 | -------------------- 25 | -- TYPES 26 | -------------------- 27 | 28 | 29 | foreign import data WORKER :: Effect 30 | 31 | 32 | newtype Location = Location 33 | { origin :: String 34 | , protocol :: String 35 | , host :: String 36 | , hostname :: String 37 | , port :: String 38 | , pathname :: String 39 | , search :: String 40 | , hash :: String 41 | } 42 | 43 | 44 | -- TODO Add missing worker navigator specific fields 45 | -- https://html.spec.whatwg.org/multipage/workers.html#workernavigator 46 | 47 | newtype Navigator = Navigator 48 | { appCodeName :: String 49 | , appName :: String 50 | , appVersion :: String 51 | , platform :: String 52 | , product :: String 53 | , productSub :: String 54 | , userAgent :: String 55 | , vendor :: String 56 | , vendorSub :: String 57 | , language :: String 58 | , languages :: Array String 59 | , onLine :: Boolean 60 | } 61 | 62 | 63 | data WorkerType 64 | = Classic 65 | | Module 66 | 67 | 68 | data Credentials 69 | = Omit 70 | | SameOrigin 71 | | Include 72 | 73 | 74 | type Options = 75 | { name :: String 76 | , requestCredentials :: Credentials 77 | , workerType :: WorkerType 78 | } 79 | 80 | 81 | -------------------- 82 | -- METHODS 83 | -------------------- 84 | 85 | 86 | -- | Event handler for the `error` event. 87 | onError 88 | :: forall e e' worker. (AbstractWorker worker) 89 | => worker 90 | -> (Error -> Eff ( | e') Unit) 91 | -> Eff (worker :: WORKER | e) Unit 92 | onError = 93 | _onError 94 | 95 | 96 | -- | Clones message and transmits it to the Worker object. 97 | postMessage 98 | :: forall e msg channel. (Channel channel) 99 | => channel 100 | -> msg 101 | -> Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 102 | postMessage p msg = 103 | _postMessage p msg [] 104 | 105 | 106 | -- | Clones message and transmits it to the port object associated with 107 | -- | dedicatedWorker-Global.transfer can be passed as a list of objects 108 | -- | that are to be transferred rather than cloned. 109 | postMessage' 110 | :: forall e msg transfer channel. (Channel channel) 111 | => channel 112 | -> msg 113 | -> Array transfer 114 | -> Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 115 | postMessage' = 116 | _postMessage 117 | 118 | 119 | -------------------- 120 | -- INSTANCES 121 | -------------------- 122 | 123 | 124 | derive instance genericLocation :: Generic Location 125 | 126 | 127 | derive instance genericNavigator :: Generic Navigator 128 | 129 | 130 | instance showLocation :: Show Location where 131 | show = gShow 132 | 133 | 134 | instance showNavigator :: Show Navigator where 135 | show = gShow 136 | 137 | 138 | instance showWorkerType :: Show WorkerType where 139 | show workerType = 140 | case workerType of 141 | Classic -> "classic" 142 | Module -> "module" 143 | 144 | 145 | instance showCredentials :: Show Credentials where 146 | show cred = 147 | case cred of 148 | Omit -> "omit" 149 | SameOrigin -> "same-origin" 150 | Include -> "include" 151 | 152 | 153 | instance readWorkerType :: Read WorkerType where 154 | read s = 155 | case s of 156 | "classic" -> pure Classic 157 | "module" -> pure Module 158 | _ -> Nothing 159 | 160 | 161 | instance readCredentials :: Read Credentials where 162 | read s = 163 | case s of 164 | "omit" -> pure Omit 165 | "same-origin" -> pure SameOrigin 166 | "include" -> pure Include 167 | _ -> Nothing 168 | 169 | 170 | -------------------- 171 | -- FFI 172 | -------------------- 173 | 174 | 175 | foreign import _onError 176 | :: forall e e' worker 177 | . worker 178 | -> (Error -> Eff ( | e') Unit) 179 | -> Eff (worker :: WORKER | e) Unit 180 | 181 | 182 | foreign import _postMessage 183 | :: forall e msg transfer channel 184 | . channel 185 | -> msg 186 | -> Array transfer 187 | -> Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 188 | -------------------------------------------------------------------------------- /src/Workers/Class.purs: -------------------------------------------------------------------------------- 1 | module Workers.Class 2 | ( class AbstractWorker 3 | , class Channel 4 | ) where 5 | 6 | 7 | -- | An opaque class to perform high-level operations on workers 8 | class AbstractWorker worker 9 | 10 | 11 | -- | An opaque class to perform high-level operations on channels 12 | class Channel port 13 | -------------------------------------------------------------------------------- /src/Workers/Dedicated.js: -------------------------------------------------------------------------------- 1 | exports._new = function _new(src) { 2 | return function _new2(opts) { 3 | return function eff() { 4 | return new Worker(src, opts); 5 | }; 6 | }; 7 | }; 8 | 9 | exports._terminate = function _terminate(wrk) { 10 | return function eff() { 11 | wrk.terminate(); 12 | }; 13 | }; 14 | 15 | exports._onMessage = function _onMessage(wrk) { 16 | return function onMessage2(f) { 17 | return function eff() { 18 | wrk.onmessage = function onmessage(e) { 19 | f(e.data)(); 20 | }; 21 | }; 22 | }; 23 | }; 24 | 25 | exports._onMessageError = function _onMessageError(wrk) { 26 | return function onMessageError2(f) { 27 | return function eff() { 28 | wrk.onmessageerror = function onmessageerror(e) { 29 | f(e.target.error)(); // FIXME 30 | }; 31 | }; 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/Workers/Dedicated.purs: -------------------------------------------------------------------------------- 1 | module Workers.Dedicated 2 | -- * Types 3 | ( Dedicated 4 | , terminate 5 | , onMessage 6 | , onMessageError 7 | 8 | -- * Constructors 9 | , new 10 | , new' 11 | 12 | -- * Re-exports 13 | , module Workers 14 | ) where 15 | 16 | import Prelude (Unit, show) 17 | 18 | import Control.Monad.Eff (Eff) 19 | import Control.Monad.Eff.Exception (Error) 20 | 21 | import Workers (WORKER, Credentials(..), Location, Navigator, Options, WorkerType(..), onError, postMessage, postMessage') 22 | import Workers.Class (class AbstractWorker, class Channel) 23 | 24 | 25 | -------------------- 26 | -- TYPES 27 | -------------------- 28 | 29 | 30 | foreign import data Dedicated :: Type 31 | 32 | 33 | -------------------- 34 | -- METHODS 35 | -------------------- 36 | 37 | 38 | -- | Returns a new Worker object. scriptURL will be fetched and executed in the background, 39 | -- | creating a new global environment for which worker represents the communication channel. 40 | new 41 | :: forall e 42 | . String 43 | -> Eff (worker :: WORKER | e) Dedicated 44 | new url = 45 | _new url 46 | { name: "" 47 | , requestCredentials: (show Omit) 48 | , workerType: (show Classic) 49 | } 50 | 51 | 52 | -- | Returns a new Worker object. scriptURL will be fetched and executed in the background, 53 | -- | creating a new global environment for which worker represents the communication channel. 54 | -- | options can be used to define the name of that global environment via the name option, 55 | -- | primarily for debugging purposes. It can also ensure this new global environment supports 56 | -- | JavaScript modules (specify type: "module"), and if that is specified, can also be used 57 | -- | to specify how scriptURL is fetched through the credentials option 58 | new' 59 | :: forall e 60 | . String 61 | -> Options 62 | -> Eff (worker :: WORKER | e) Dedicated 63 | new' url opts = 64 | _new url 65 | { name: opts.name 66 | , requestCredentials: (show opts.requestCredentials) 67 | , workerType: (show opts.workerType) 68 | } 69 | 70 | 71 | -- | Aborts worker’s associated global environment. 72 | terminate 73 | :: forall e 74 | . Dedicated 75 | -> Eff (worker :: WORKER | e) Unit 76 | terminate = 77 | _terminate 78 | 79 | 80 | -- | Event handler for the `message` event 81 | onMessage 82 | :: forall e e' msg 83 | . Dedicated 84 | -> (msg -> Eff ( | e') Unit) 85 | -> Eff (worker :: WORKER | e) Unit 86 | onMessage port = 87 | _onMessage port 88 | 89 | 90 | -- | Event handler for the `messageError` event 91 | onMessageError 92 | :: forall e e' 93 | . Dedicated 94 | -> (Error -> Eff ( | e') Unit) 95 | -> Eff (worker :: WORKER | e) Unit 96 | onMessageError port = 97 | _onMessageError port 98 | 99 | 100 | -------------------- 101 | -- INSTANCES 102 | -------------------- 103 | 104 | 105 | instance abstractWorkerDedicated :: AbstractWorker Dedicated 106 | 107 | 108 | instance channelDedicated :: Channel Dedicated 109 | 110 | 111 | -------------------- 112 | -- FFI 113 | -------------------- 114 | 115 | 116 | foreign import _onMessage 117 | :: forall e e' msg 118 | . Dedicated 119 | -> (msg -> Eff ( | e') Unit) 120 | -> Eff (worker :: WORKER | e) Unit 121 | 122 | 123 | foreign import _onMessageError 124 | :: forall e e' 125 | . Dedicated 126 | -> (Error -> Eff ( | e') Unit) 127 | -> Eff (worker :: WORKER | e) Unit 128 | 129 | 130 | foreign import _new 131 | :: forall e 132 | . String 133 | -> { name :: String, requestCredentials :: String, workerType :: String } 134 | -> Eff (worker :: WORKER | e) Dedicated 135 | 136 | 137 | foreign import _terminate 138 | :: forall e 139 | . Dedicated 140 | -> Eff (worker :: WORKER | e) Unit 141 | -------------------------------------------------------------------------------- /src/Workers/Service.js: -------------------------------------------------------------------------------- 1 | /* SERVICE WORKER CONTAINER */ 2 | 3 | exports._controller = function eff() { 4 | return navigator.serviceWorker.controller; 5 | }; 6 | 7 | exports._getRegistration = function _getRegistration(url) { 8 | return function aff(success, error) { 9 | try { 10 | navigator.serviceWorker 11 | .getRegistration(url || '') 12 | .then(success, error); 13 | } catch (err) { 14 | error(err); 15 | } 16 | }; 17 | }; 18 | 19 | exports._onControllerChange = function _onControllerChange(f) { 20 | return function eff() { 21 | navigator.serviceWorker.oncontrollerchange = function oncontrollerchange() { 22 | f(); 23 | }; 24 | }; 25 | }; 26 | 27 | exports._onMessage = function _onMessage(f) { 28 | return function eff() { 29 | navigator.serviceWorker.onmessage = function onmessage(e) { 30 | f(e.data)(); 31 | }; 32 | }; 33 | }; 34 | 35 | 36 | exports._ready = function aff(success, error) { 37 | try { 38 | navigator.serviceWorker 39 | .ready 40 | .then(success, error); 41 | } catch (err) { 42 | error(err); 43 | } 44 | }; 45 | 46 | exports._register = function _register(url) { 47 | return function _register2(opts) { 48 | return function aff(success, error) { 49 | try { 50 | navigator.serviceWorker 51 | .register(url, opts) 52 | .then(success, error); 53 | } catch (err) { 54 | error(err); 55 | } 56 | }; 57 | }; 58 | }; 59 | 60 | exports._startMessages = function eff() { 61 | navigator.serviceWorker 62 | .startMessages(); 63 | }; 64 | 65 | /* SERVICE WORKER */ 66 | 67 | exports._onStateChange = function _onStateChange(toState) { 68 | return function _onStateChange2(service) { 69 | return function _onStateChange3(f) { 70 | return function eff() { 71 | service.onstatechange = function onstatechange(e) { 72 | f(toState(e.source.state))(); 73 | }; 74 | }; 75 | }; 76 | }; 77 | }; 78 | 79 | exports._scriptURL = function _scriptURL(service) { 80 | return service.scriptURL; 81 | }; 82 | 83 | exports._state = function _state(toState) { 84 | return function _state2(service) { 85 | return toState(service.state); 86 | }; 87 | }; 88 | 89 | /* SERVICE WORKER REGISTRATION */ 90 | 91 | exports._active = function _active(registration) { 92 | return registration.active; 93 | }; 94 | 95 | exports._installing = function _installing(registration) { 96 | return registration.installing; 97 | }; 98 | 99 | exports._waiting = function _waiting(registration) { 100 | return registration.waiting; 101 | }; 102 | 103 | exports._scope = function _scope(registration) { 104 | return registration.scope; 105 | }; 106 | 107 | exports._update = function _update(registration) { 108 | return function aff(success, error) { 109 | try { 110 | registration.update().then(success, error); 111 | } catch (err) { 112 | error(err); 113 | } 114 | }; 115 | }; 116 | 117 | exports._unregister = function _unregister(registration) { 118 | return function aff(success, error) { 119 | try { 120 | registration.update().then(success, error); 121 | } catch (err) { 122 | error(err); 123 | } 124 | }; 125 | }; 126 | 127 | exports._onUpdateFound = function _onUpdateFound(registration) { 128 | return function _onUpdateFound2(f) { 129 | return function eff() { 130 | registration.onupdatefound = function onupdatefound() { 131 | f(); 132 | }; 133 | }; 134 | }; 135 | }; 136 | -------------------------------------------------------------------------------- /src/Workers/Service.purs: -------------------------------------------------------------------------------- 1 | module Workers.Service 2 | -- * Constructors & Setup 3 | ( Registration 4 | , RegistrationOptions 5 | , controller 6 | , getRegistration 7 | , onControllerChange 8 | , onMessage 9 | , ready 10 | , register 11 | , register' 12 | , startMessages 13 | , wait 14 | 15 | -- * Service Worker Manipulations 16 | , State(..) 17 | , Service 18 | , onStateChange 19 | , scriptURL 20 | , state 21 | 22 | -- * Registration Manipulations 23 | , active 24 | , installing 25 | , waiting 26 | , scope 27 | , update 28 | , unregister 29 | , onUpdateFound 30 | 31 | -- * Re-exports 32 | , module Workers 33 | ) where 34 | 35 | import Prelude 36 | 37 | import Control.Monad.Aff (Aff, delay) 38 | import Control.Monad.Eff (Eff) 39 | import Control.Monad.Eff.Class (liftEff) 40 | import Data.Maybe (Maybe(..)) 41 | import Data.Nullable (Nullable, toMaybe, toNullable) 42 | import Data.String.Read (class Read, read) 43 | import Data.Time.Duration (Milliseconds(Milliseconds)) 44 | 45 | import Workers (WORKER, WorkerType(..), onError, postMessage, postMessage') 46 | import Workers.Class (class AbstractWorker, class Channel) 47 | 48 | 49 | -------------------- 50 | -- TYPES 51 | -------------------- 52 | 53 | 54 | foreign import data Service :: Type 55 | 56 | 57 | foreign import data Registration :: Type 58 | 59 | 60 | type RegistrationOptions = 61 | { scope :: String 62 | , workerType :: WorkerType 63 | } 64 | 65 | 66 | data State 67 | = Installing 68 | | Installed 69 | | Activating 70 | | Activated 71 | | Redundant 72 | 73 | 74 | -------------------- 75 | -- METHODS 76 | -------------------- 77 | 78 | -- SERVICE WORKER CONTAINER ~ navigator.serviceWorker globals 79 | 80 | controller 81 | :: forall e 82 | . Eff (worker :: WORKER | e) (Maybe Service) 83 | controller = 84 | toMaybe <$> _controller 85 | 86 | 87 | getRegistration 88 | :: forall e 89 | . Maybe String 90 | -> Aff (worker :: WORKER | e) (Maybe Registration) 91 | getRegistration = 92 | toNullable >>> _getRegistration >=> toPureMaybe 93 | where 94 | toPureMaybe = toMaybe >>> pure 95 | 96 | 97 | onControllerChange 98 | :: forall e e' 99 | . Eff ( | e') Unit 100 | -> Eff (worker :: WORKER | e) Unit 101 | onControllerChange = 102 | _onControllerChange 103 | 104 | 105 | onMessage 106 | :: forall e e' msg 107 | . (msg -> Eff ( | e') Unit) 108 | -> Eff (worker :: WORKER | e) Unit 109 | onMessage = 110 | _onMessage 111 | 112 | 113 | ready 114 | :: forall e 115 | . Aff (worker :: WORKER | e) Registration 116 | ready = 117 | _ready 118 | 119 | 120 | register 121 | :: forall e 122 | . String 123 | -> Aff (worker :: WORKER | e) Registration 124 | register url = 125 | _register url 126 | { scope: "" 127 | , workerType: Classic 128 | } 129 | 130 | 131 | register' 132 | :: forall e 133 | . String 134 | -> RegistrationOptions 135 | -> Aff (worker :: WORKER | e) Registration 136 | register' = 137 | _register 138 | 139 | 140 | startMessages 141 | :: forall e 142 | . Eff (worker :: WORKER | e) Unit 143 | startMessages = 144 | _startMessages 145 | 146 | 147 | wait 148 | :: forall e 149 | . Aff (worker :: WORKER | e) Service 150 | wait = do 151 | mworker <- liftEff controller 152 | case mworker of 153 | Nothing -> do 154 | delay (Milliseconds 50.0) 155 | wait 156 | Just worker -> do 157 | pure worker 158 | 159 | 160 | -- SERVICE WORKER ~ instance methods 161 | 162 | onStateChange 163 | :: forall e e' 164 | . Service 165 | -> (State -> Eff ( | e') Unit) 166 | -> Eff (worker :: WORKER | e) Unit 167 | onStateChange = 168 | _onStateChange (read >>> toNullable) 169 | 170 | 171 | scriptURL 172 | :: Service 173 | -> String 174 | scriptURL = 175 | _scriptURL 176 | 177 | 178 | state 179 | :: Service 180 | -> State 181 | state = 182 | _state (read >>> toNullable) 183 | 184 | 185 | -- SERVICE WORKER REGISTRATION ~ instance method 186 | 187 | active 188 | :: Registration 189 | -> Maybe Service 190 | active = 191 | _active >>> toMaybe 192 | 193 | 194 | installing 195 | :: Registration 196 | -> Maybe Service 197 | installing = 198 | _installing >>> toMaybe 199 | 200 | 201 | waiting 202 | :: Registration 203 | -> Maybe Service 204 | waiting = 205 | _waiting >>> toMaybe 206 | 207 | 208 | scope 209 | :: Registration 210 | -> String 211 | scope = 212 | _scope 213 | 214 | 215 | update 216 | :: forall e 217 | . Registration 218 | -> Aff (worker :: WORKER | e) Unit 219 | update = 220 | _update 221 | 222 | 223 | unregister 224 | :: forall e 225 | . Registration 226 | -> Aff (worker :: WORKER | e) Boolean 227 | unregister = 228 | _unregister 229 | 230 | 231 | onUpdateFound 232 | :: forall e e' 233 | . Registration 234 | -> Eff ( | e') Unit 235 | -> Eff (worker :: WORKER | e) Unit 236 | onUpdateFound = 237 | _onUpdateFound 238 | 239 | 240 | -------------------- 241 | -- INSTANCES 242 | -------------------- 243 | 244 | 245 | instance abstractWorkerService :: AbstractWorker Service 246 | 247 | 248 | instance channelService :: Channel Service 249 | 250 | 251 | instance showState :: Show State where 252 | show s = 253 | case s of 254 | Installing -> "installing" 255 | Installed -> "installed" 256 | Activating -> "activating" 257 | Activated -> "activated" 258 | Redundant -> "redundant" 259 | 260 | 261 | instance readState :: Read State where 262 | read s = 263 | case s of 264 | "installing" -> pure Installing 265 | "installed" -> pure Installed 266 | "activating" -> pure Activating 267 | "activated" -> pure Activated 268 | "redundant" -> pure Redundant 269 | _ -> Nothing 270 | 271 | 272 | -------------------- 273 | -- FFI 274 | -------------------- 275 | 276 | 277 | foreign import _controller 278 | :: forall e 279 | . Eff (worker :: WORKER | e) (Nullable Service) 280 | 281 | 282 | foreign import _getRegistration 283 | :: forall e 284 | . Nullable String 285 | -> Aff (worker :: WORKER | e) (Nullable Registration) 286 | 287 | 288 | foreign import _onControllerChange 289 | :: forall e e' 290 | . Eff ( | e') Unit 291 | -> Eff (worker :: WORKER | e) Unit 292 | 293 | 294 | foreign import _onMessage 295 | :: forall e e' msg 296 | . (msg -> Eff ( | e') Unit) 297 | -> Eff (worker :: WORKER | e) Unit 298 | 299 | 300 | foreign import _ready 301 | :: forall e 302 | . Aff (worker :: WORKER | e) Registration 303 | 304 | 305 | foreign import _register 306 | :: forall e 307 | . String 308 | -> RegistrationOptions 309 | -> Aff (worker :: WORKER | e) Registration 310 | 311 | 312 | foreign import _startMessages 313 | :: forall e 314 | . Eff (worker :: WORKER | e) Unit 315 | 316 | 317 | foreign import _onStateChange 318 | :: forall e e' 319 | . (String -> Nullable State) 320 | -> Service 321 | -> (State -> Eff ( | e') Unit) 322 | -> Eff (worker :: WORKER | e) Unit 323 | 324 | 325 | foreign import _scriptURL 326 | :: Service 327 | -> String 328 | 329 | 330 | foreign import _state 331 | :: (String -> Nullable State) 332 | -> Service 333 | -> State 334 | 335 | 336 | foreign import _active 337 | :: Registration 338 | -> Nullable Service 339 | 340 | 341 | foreign import _installing 342 | :: Registration 343 | -> Nullable Service 344 | 345 | 346 | foreign import _waiting 347 | :: Registration 348 | -> Nullable Service 349 | 350 | 351 | foreign import _scope 352 | :: Registration 353 | -> String 354 | 355 | 356 | foreign import _update 357 | :: forall e 358 | . Registration 359 | -> Aff (worker :: WORKER | e) Unit 360 | 361 | 362 | foreign import _unregister 363 | :: forall e 364 | . Registration 365 | -> Aff (worker :: WORKER | e) Boolean 366 | 367 | 368 | foreign import _onUpdateFound 369 | :: forall e e' 370 | . Registration 371 | -> Eff ( | e') Unit 372 | -> Eff (worker :: WORKER | e) Unit 373 | -------------------------------------------------------------------------------- /src/Workers/Shared.js: -------------------------------------------------------------------------------- 1 | exports._new = function _new(src) { 2 | return function _new2(opts) { 3 | return function eff() { 4 | return new SharedWorker(src, opts); 5 | }; 6 | }; 7 | }; 8 | 9 | exports._port = function _port(wrk) { 10 | return wrk.port; 11 | }; 12 | -------------------------------------------------------------------------------- /src/Workers/Shared.purs: -------------------------------------------------------------------------------- 1 | module Workers.Shared 2 | -- * Types 3 | ( Shared 4 | , port 5 | 6 | -- * Constructors 7 | , new 8 | , new' 9 | 10 | -- * Re-Exports 11 | , module Workers 12 | , module MessagePort 13 | ) where 14 | 15 | import Prelude (show) 16 | 17 | import Control.Monad.Eff (Eff) 18 | 19 | import MessagePort (MessagePort, close, start, onMessage, onMessageError) 20 | import Workers (WORKER, Credentials(..), Location, Navigator, Options, WorkerType(..), onError, postMessage, postMessage') 21 | import Workers.Class (class AbstractWorker) 22 | 23 | 24 | -------------------- 25 | -- TYPES 26 | -------------------- 27 | 28 | 29 | foreign import data Shared :: Type 30 | 31 | 32 | -------------------- 33 | -- METHODS 34 | -------------------- 35 | 36 | 37 | -- | Returns a new Worker object. scriptURL will be fetched and executed in the background, 38 | -- | creating a new global environment for which worker represents the communication channel. 39 | new 40 | :: forall e 41 | . String 42 | -> Eff (worker :: WORKER | e) Shared 43 | new url = 44 | _new url 45 | { name: "" 46 | , requestCredentials: (show Omit) 47 | , workerType: (show Classic) 48 | } 49 | 50 | 51 | -- | Returns a new Worker object. scriptURL will be fetched and executed in the background, 52 | -- | creating a new global environment for which worker represents the communication channel. 53 | -- | options can be used to define the name of that global environment via the name option, 54 | -- | primarily for debugging purposes. It can also ensure this new global environment supports 55 | -- | JavaScript modules (specify type: "module"), and if that is specified, can also be used 56 | -- | to specify how scriptURL is fetched through the credentials option 57 | new' 58 | :: forall e 59 | . String 60 | -> Options 61 | -> Eff (worker :: WORKER | e) Shared 62 | new' url opts = 63 | _new url 64 | { name: opts.name 65 | , requestCredentials: (show opts.requestCredentials) 66 | , workerType: (show opts.workerType) 67 | } 68 | 69 | 70 | -- | Returns sharedWorker’s MessagePort object which can be used 71 | -- | to communicate with the global environment. 72 | port 73 | :: Shared 74 | -> MessagePort 75 | port = 76 | _port 77 | 78 | 79 | -------------------- 80 | -- INSTANCES 81 | -------------------- 82 | 83 | 84 | instance abstractWorkerShared :: AbstractWorker Shared 85 | 86 | 87 | -------------------- 88 | -- FFI 89 | -------------------- 90 | 91 | 92 | foreign import _new 93 | :: forall e 94 | . String 95 | -> { name :: String, requestCredentials :: String, workerType :: String } 96 | -> Eff (worker :: WORKER | e) Shared 97 | 98 | 99 | foreign import _port 100 | :: Shared 101 | -> MessagePort 102 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude (Unit, bind, discard, pure, unit, (<*)) 4 | import Control.Monad.Aff (Aff, launchAff, delay) 5 | import Control.Monad.Aff.AVar (AVAR, makeVar, takeVar, putVar) 6 | import Control.Monad.Eff (Eff) 7 | import Control.Monad.Eff.Class (liftEff) 8 | import Control.Monad.Eff.Exception (EXCEPTION, Error, name) 9 | import Test.Spec (Spec, describe, it) 10 | import Test.Spec.Assertions (shouldEqual, fail) 11 | import Test.Spec.Mocha (MOCHA, runMocha) 12 | import Data.Time.Duration (Milliseconds(..)) 13 | import Data.Maybe (Maybe(..)) 14 | 15 | import Test.Spec(itOnly) 16 | import Control.Monad.Aff.Console(log) 17 | 18 | import Aff.Workers (WORKER, Location(..), Navigator(..), WorkerType(..)) 19 | import Aff.Workers.Dedicated (Dedicated) 20 | import Aff.Workers.Shared (Shared) 21 | 22 | import Aff.MessagePort as MessagePort 23 | import Aff.Workers.Dedicated as DedicatedWorker 24 | import Aff.Workers.Shared as SharedWorker 25 | import Aff.Workers.Service as ServiceWorker 26 | 27 | it' :: forall e. String -> Eff ( | e) Unit -> Spec e Unit 28 | it' str body = 29 | it str (liftEff body) 30 | 31 | 32 | launchAff' :: forall a e. Aff e a -> Eff (exception :: EXCEPTION | e) Unit 33 | launchAff' aff = 34 | pure unit <* (launchAff aff) 35 | 36 | 37 | -- main :: forall e. Eff (mocha :: MOCHA, avar :: AVAR, worker :: WORKER, exception :: EXCEPTION | e) Unit 38 | main = runMocha do 39 | describe "[Dedicated Worker]" do 40 | it "Hello World" do 41 | var <- makeVar 42 | (worker :: Dedicated) <- DedicatedWorker.new "base/worker01.js" 43 | DedicatedWorker.onMessage worker (\msg -> launchAff' do 44 | putVar var msg 45 | ) 46 | DedicatedWorker.postMessage worker "hello" 47 | msg <- takeVar var 48 | msg `shouldEqual` "world" 49 | 50 | it "WorkerLocation object" do 51 | var <- makeVar 52 | (worker :: Dedicated) <- DedicatedWorker.new "base/worker02.js" 53 | DedicatedWorker.onMessage worker (\msg -> launchAff' do 54 | putVar var msg 55 | ) 56 | DedicatedWorker.postMessage worker unit 57 | Location loc <- takeVar var 58 | loc.pathname `shouldEqual` "/base/worker02.js" 59 | 60 | it "WorkerNavigator object" do 61 | var <- makeVar 62 | (worker :: Dedicated) <- DedicatedWorker.new "base/worker03.js" 63 | DedicatedWorker.onMessage worker (\msg -> launchAff' do 64 | putVar var msg 65 | ) 66 | DedicatedWorker.postMessage worker unit 67 | Navigator nav <- takeVar var 68 | nav.onLine `shouldEqual` true 69 | 70 | it "Error Event - Handled by Worker" do 71 | var <- makeVar 72 | (worker :: Dedicated) <- DedicatedWorker.new "base/worker05.js" 73 | DedicatedWorker.onMessage worker (\msg -> launchAff' do 74 | putVar var msg 75 | ) 76 | msg <- takeVar var 77 | msg `shouldEqual` "Error" 78 | 79 | it "Error Event - Bubble to Parent" do 80 | var <- makeVar 81 | (worker :: Dedicated) <- DedicatedWorker.new "base/worker06.js" 82 | DedicatedWorker.onError worker (\err -> launchAff' do 83 | putVar var err 84 | ) 85 | (err :: Error) <- takeVar var 86 | (name err) `shouldEqual` "Error" 87 | 88 | it "Data Clone Error" do 89 | var <- makeVar 90 | (worker :: Dedicated) <- DedicatedWorker.new "base/worker07.js" 91 | DedicatedWorker.onMessage worker (\msg -> launchAff' do 92 | putVar var msg 93 | ) 94 | msg <- takeVar var 95 | msg `shouldEqual` "DataCloneError" 96 | 97 | it "Worker terminate" do 98 | var <- makeVar 99 | (worker :: Dedicated) <- DedicatedWorker.new "base/worker01.js" 100 | DedicatedWorker.onMessage worker (\msg -> launchAff' do 101 | DedicatedWorker.terminate worker 102 | putVar var unit 103 | ) 104 | DedicatedWorker.postMessage worker "patate" 105 | msg <- takeVar var 106 | msg `shouldEqual` unit 107 | 108 | 109 | describe "[Shared Worker]" do 110 | it "Shared Worker Connect" do 111 | var <- makeVar 112 | (worker :: Shared) <- SharedWorker.new "base/worker04.js" 113 | MessagePort.onMessage (SharedWorker.port worker) (\msg -> launchAff' do 114 | putVar var msg 115 | ) 116 | (msg :: Boolean) <- takeVar var 117 | msg `shouldEqual` true 118 | 119 | 120 | describe "[Service Worker]" do 121 | it "Service Worker - using get with source id" do 122 | var <- makeVar 123 | registration <- ServiceWorker.register "base/worker08.js" 124 | ServiceWorker.onMessage (\msg -> launchAff' do 125 | putVar var msg 126 | ) 127 | worker <- ServiceWorker.wait 128 | ServiceWorker.postMessage worker "patate" 129 | msg <- takeVar var 130 | msg `shouldEqual` "PATATE" 131 | 132 | 133 | it "Service Worker - matching all clients" do 134 | var <- makeVar 135 | registration <- ServiceWorker.register "base/worker09.js" 136 | ServiceWorker.onMessage (\msg -> launchAff' do 137 | putVar var msg 138 | ) 139 | worker <- ServiceWorker.wait 140 | ServiceWorker.postMessage worker "patate" 141 | msg <- takeVar var 142 | msg `shouldEqual` "PATATE" 143 | -------------------------------------------------------------------------------- /test/workers/worker01.purs: -------------------------------------------------------------------------------- 1 | module Test.Workers.Worker01 where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Eff (Eff) 6 | import Control.Monad.Eff.Exception (EXCEPTION) 7 | 8 | import Workers (WORKER) 9 | import GlobalScope.Dedicated (onMessage, postMessage) 10 | 11 | 12 | -- | Basic Worker Replying "world" to any message 13 | main :: forall e. Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 14 | main = 15 | onMessage (\_ -> postMessage "world") 16 | -------------------------------------------------------------------------------- /test/workers/worker02.purs: -------------------------------------------------------------------------------- 1 | module Test.Workers.Worker02 where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Eff (Eff) 6 | import Control.Monad.Eff.Exception (EXCEPTION) 7 | 8 | import Workers (WORKER) 9 | import GlobalScope.Dedicated (postMessage, onMessage, location) 10 | 11 | 12 | -- | Worker accessing the location in its global scope 13 | main :: forall e. Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 14 | main = 15 | onMessage (\_ -> location `bind` postMessage) 16 | -------------------------------------------------------------------------------- /test/workers/worker03.purs: -------------------------------------------------------------------------------- 1 | module Test.Workers.Worker03 where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Eff (Eff) 6 | import Control.Monad.Eff.Exception (EXCEPTION) 7 | 8 | import Workers (WORKER) 9 | import GlobalScope.Dedicated (onMessage, postMessage, navigator) 10 | 11 | 12 | -- | Worker accessing the navigator in its global scope 13 | main :: forall e. Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 14 | main = 15 | onMessage (\_ -> navigator `bind` postMessage) 16 | -------------------------------------------------------------------------------- /test/workers/worker04.purs: -------------------------------------------------------------------------------- 1 | module Test.Workers.Worker04 where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Eff (Eff) 6 | import Control.Monad.Eff.Exception (EXCEPTION) 7 | import Data.NonEmpty (head) 8 | 9 | import GlobalScope.Shared (onConnect) 10 | import Workers.Shared (WORKER, postMessage) 11 | 12 | 13 | -- | Shared Worker working through a port 14 | main :: forall e. Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 15 | main = 16 | onConnect $ \ports -> postMessage (head ports) true 17 | -------------------------------------------------------------------------------- /test/workers/worker05.purs: -------------------------------------------------------------------------------- 1 | module Test.Workers.Worker05 where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Eff (Eff) 6 | import Control.Monad.Eff.Exception (EXCEPTION, throwException, error, name) 7 | 8 | import Workers (WORKER) 9 | import GlobalScope.Dedicated (onError, postMessage) 10 | 11 | 12 | -- | Exception handling via onError 13 | main :: forall e. Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 14 | main = do 15 | onError (name >>> postMessage) 16 | throwException (error "patate") 17 | -------------------------------------------------------------------------------- /test/workers/worker06.purs: -------------------------------------------------------------------------------- 1 | module Test.Workers.Worker06 where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Eff (Eff) 6 | import Control.Monad.Eff.Exception (EXCEPTION, throwException, error) 7 | 8 | 9 | -- | Error propagation to parent 10 | main :: forall e. Eff (exception :: EXCEPTION | e) Unit 11 | main = do 12 | throwException (error "patate") 13 | -------------------------------------------------------------------------------- /test/workers/worker07.purs: -------------------------------------------------------------------------------- 1 | module Test.Workers.Worker07 where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Eff (Eff) 6 | import Control.Monad.Eff.Exception (EXCEPTION, name, catchException) 7 | 8 | import Workers (WORKER) 9 | import GlobalScope.Dedicated (postMessage) 10 | 11 | 12 | -- | Catch Data Clone Errors 13 | main :: forall e. Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 14 | main = do 15 | (name >>> postMessage) `catchException` postMessage ((+) 1) 16 | -------------------------------------------------------------------------------- /test/workers/worker08.purs: -------------------------------------------------------------------------------- 1 | module Test.Workers.Worker08 where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Eff (Eff) 6 | import Control.Monad.Eff.Exception (EXCEPTION) 7 | import Control.Monad.Eff.Class (liftEff) 8 | import Control.Monad.Aff (Aff) 9 | import Data.Maybe (Maybe(..)) 10 | import Data.String (toUpper) 11 | 12 | import Workers (WORKER, postMessage) 13 | import GlobalScope.Service (ClientId, clients, claim, get, onActivate, onMessage) 14 | 15 | 16 | main :: forall e. Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 17 | main = do 18 | onMessage onMessageHandler 19 | onActivate onActivateHandler 20 | 21 | 22 | onMessageHandler :: ClientId -> String -> Aff (worker :: WORKER, exception :: EXCEPTION) Unit 23 | onMessageHandler cid msg = do 24 | mclient <- (liftEff clients) >>= flip get cid 25 | case mclient of 26 | Nothing -> pure unit 27 | Just client -> liftEff $ postMessage client (toUpper msg) 28 | 29 | 30 | onActivateHandler :: Aff (worker :: WORKER) Unit 31 | onActivateHandler = 32 | liftEff clients >>= claim 33 | -------------------------------------------------------------------------------- /test/workers/worker09.purs: -------------------------------------------------------------------------------- 1 | module Test.Workers.Worker09 where 2 | 3 | import Prelude 4 | 5 | import Control.Monad.Eff (Eff, foreachE) 6 | import Control.Monad.Eff.Exception (EXCEPTION) 7 | import Control.Monad.Eff.Class (liftEff) 8 | import Control.Monad.Aff (Aff) 9 | import Data.String (toUpper) 10 | 11 | import Workers (WORKER, postMessage) 12 | import GlobalScope.Service (ClientId, clients, claim, matchAll, onActivate, onMessage) 13 | 14 | 15 | main :: forall e. Eff (worker :: WORKER, exception :: EXCEPTION | e) Unit 16 | main = do 17 | onMessage onMessageHandler 18 | onActivate onActivateHandler 19 | 20 | 21 | onMessageHandler :: ClientId -> String -> Aff (worker :: WORKER, exception :: EXCEPTION) Unit 22 | onMessageHandler _ msg = 23 | liftEff clients 24 | >>= matchAll 25 | >>= forEachA (\client -> postMessage client (toUpper msg)) 26 | 27 | 28 | onActivateHandler :: Aff (worker :: WORKER) Unit 29 | onActivateHandler = 30 | liftEff clients >>= claim 31 | 32 | 33 | forEachA :: forall e a. (a -> Eff e Unit) -> (Array a) -> (Aff e Unit) 34 | forEachA f xs = 35 | liftEff (foreachE xs f) 36 | --------------------------------------------------------------------------------