git.iko.soy/rowtype-yoga/purescript-webextension-polyfill
on main help sign in
8 files changed+327
since splitsnapshots
unifiedsplit
.gitignore+10 LICENCE+7 bower.json+30 packages.dhall+105 spago.dhall+32 src/WebExtension/Polyfill.js+22 src/WebExtension/Polyfill.purs+111 src/WebExtension/Polyfill/Types.purs+10
.gitignorenew file+10
@@ -0,0 +1,10 @@
1+/bower_components/
2+/node_modules/
3+/.pulp-cache/
4+/output/
5+/generated-docs/
6+/.psc-package/
7+/.psc*
8+/.purs*
9+/.psa*
10+/.spago
LICENCEnew file+7
@@ -0,0 +1,7 @@
1+Copyright 2022 Mark Eibes
2+
3+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
bower.jsonnew file+30
@@ -0,0 +1,30 @@
1+{
2+ "name": "purescript-webextension-polyfill",
3+ "license": [
4+ "MIT"
5+ ],
6+ "repository": {
7+ "type": "git",
8+ "url": "https://github.com/rowtype-yoga/purescript-webextension-polyfill.git"
9+ },
10+ "ignore": [
11+ "**/.*",
12+ "node_modules",
13+ "bower_components",
14+ "output"
15+ ],
16+ "dependencies": {
17+ "purescript-aff": "^v7.1.0",
18+ "purescript-aff-promise": "^v4.0.0",
19+ "purescript-console": "^v6.0.0",
20+ "purescript-effect": "^v4.0.0",
21+ "purescript-either": "^v6.1.0",
22+ "purescript-foldable-traversable": "^v6.0.0",
23+ "purescript-foreign": "^v7.0.0",
24+ "purescript-foreign-object": "^v4.1.0",
25+ "purescript-maybe": "^v6.0.0",
26+ "purescript-newtype": "^v5.0.0",
27+ "purescript-prelude": "^v6.0.1",
28+ "purescript-yoga-json": "https://github.com/rowtype-yoga/purescript-yoga-json.git#v4.0.1"
29+ }
30+}
packages.dhallnew file+105
@@ -0,0 +1,105 @@
1+{-
2+Welcome to your new Dhall package-set!
3+
4+Below are instructions for how to edit this file for most use
5+cases, so that you don't need to know Dhall to use it.
6+
7+## Use Cases
8+
9+Most will want to do one or both of these options:
10+1. Override/Patch a package's dependency
11+2. Add a package not already in the default package set
12+
13+This file will continue to work whether you use one or both options.
14+Instructions for each option are explained below.
15+
16+### Overriding/Patching a package
17+
18+Purpose:
19+- Change a package's dependency to a newer/older release than the
20+ default package set's release
21+- Use your own modified version of some dependency that may
22+ include new API, changed API, removed API by
23+ using your custom git repo of the library rather than
24+ the package set's repo
25+
26+Syntax:
27+where `entityName` is one of the following:
28+- dependencies
29+- repo
30+- version
31+-------------------------------
32+let upstream = --
33+in upstream
34+ with packageName.entityName = "new value"
35+-------------------------------
36+
37+Example:
38+-------------------------------
39+let upstream = --
40+in upstream
41+ with halogen.version = "master"
42+ with halogen.repo = "https://example.com/path/to/git/repo.git"
43+
44+ with halogen-vdom.version = "v4.0.0"
45+ with halogen-vdom.dependencies = [ "extra-dependency" ] # halogen-vdom.dependencies
46+-------------------------------
47+
48+### Additions
49+
50+Purpose:
51+- Add packages that aren't already included in the default package set
52+
53+Syntax:
54+where `<version>` is:
55+- a tag (i.e. "v4.0.0")
56+- a branch (i.e. "master")
57+- commit hash (i.e. "701f3e44aafb1a6459281714858fadf2c4c2a977")
58+-------------------------------
59+let upstream = --
60+in upstream
61+ with new-package-name =
62+ { dependencies =
63+ [ "dependency1"
64+ , "dependency2"
65+ ]
66+ , repo =
67+ "https://example.com/path/to/git/repo.git"
68+ , version =
69+ "<version>"
70+ }
71+-------------------------------
72+
73+Example:
74+-------------------------------
75+let upstream = --
76+in upstream
77+ with benchotron =
78+ { dependencies =
79+ [ "arrays"
80+ , "exists"
81+ , "profunctor"
82+ , "strings"
83+ , "quickcheck"
84+ , "lcg"
85+ , "transformers"
86+ , "foldable-traversable"
87+ , "exceptions"
88+ , "node-fs"
89+ , "node-buffer"
90+ , "node-readline"
91+ , "datetime"
92+ , "now"
93+ ]
94+ , repo =
95+ "https://github.com/hdgarrood/purescript-benchotron.git"
96+ , version =
97+ "v7.0.0"
98+ }
99+-------------------------------
100+-}
101+let upstream =
102+ https://github.com/purescript/package-sets/releases/download/psc-0.15.7-20230219/packages.dhall
103+ sha256:2ba900b6b1cdeb3ad8f1554d1ceaafede6a9451dc009f96ef45754476d00c900
104+
105+in upstream
spago.dhallnew file+32
@@ -0,0 +1,32 @@
1+{-
2+Welcome to a Spago project!
3+You can edit this file as you like.
4+
5+Need help? See the following resources:
6+- Spago documentation: https://github.com/purescript/spago
7+- Dhall language tour: https://docs.dhall-lang.org/tutorials/Language-Tour.html
8+
9+When creating a new Spago project, you can use
10+`spago init --no-comments` or `spago init -C`
11+to generate this file without the comments in this block.
12+-}
13+{ name = "webextension-polyfill"
14+, repository = "https://github.com/rowtype-yoga/purescript-webextension-polyfill.git"
15+, license = "MIT"
16+, dependencies =
17+ [ "aff"
18+ , "aff-promise"
19+ , "console"
20+ , "effect"
21+ , "either"
22+ , "foldable-traversable"
23+ , "foreign"
24+ , "foreign-object"
25+ , "maybe"
26+ , "newtype"
27+ , "prelude"
28+ , "yoga-json"
29+ ]
30+, packages = ./packages.dhall
31+, sources = [ "src/**/*.purs", "test/**/*.purs" ]
32+}
src/WebExtension/Polyfill.jsnew file+22
@@ -0,0 +1,22 @@
1+import browser, { storage, history as _history, runtime, windows } from 'webextension-polyfill'
2+
3+export const browserImpl = browser
4+export function loadFromLocalStorageImpl(key) { return () => storage.local.get(key)}
5+export function saveInLocalStorageImpl(obj) { return () => storage.local.set(obj)}
6+export function removeFromLocalStorageImpl(key) { return () => storage.local.remove(key)}
7+
8+export function history() { return _history}
9+
10+export function addOnVisitedListenerImpl(listener) { return history => () => history.onVisited.addListener(listener)}
11+
12+export function sendMessageViaPortImpl(message, port) { return port.postMessage(message)}
13+export function addOnPortMessageListenerImpl(listener, port) { return port.onMessage.addListener(listener)}
14+
15+export function onContentScriptConnectedImpl(listener) { return () =>
16+ runtime.onConnect.addListener(port => listener(port)())}
17+
18+export const connectToBackgroundScriptImpl = runtime.connect
19+
20+export function openNewWindowImpl(options) { return () => windows.create(options)}
21+
22+export const reload = runtime.reload
src/WebExtension/Polyfill.pursnew file+111
@@ -0,0 +1,111 @@
1+module WebExtension.Polyfill where
2+
3+import Prelude
4+import Control.Promise (Promise, toAffE)
5+import Data.Either (Either(..))
6+import Data.Maybe (Maybe(..))
7+import Data.Semigroup.Foldable (intercalateMap)
8+import Effect (Effect)
9+import Effect.Aff (Aff)
10+import Effect.Class.Console as Console
11+import Effect.Uncurried (EffectFn1, EffectFn2, mkEffectFn1, runEffectFn1, runEffectFn2)
12+import Foreign (Foreign, renderForeignError)
13+import Foreign.Object (Object)
14+import Foreign.Object as Object
15+import Prim.Row (class Union)
16+import Yoga.JSON (class ReadForeign, class WriteForeign, read)
17+import Yoga.JSON as JSON
18+import WebExtension.Polyfill.Types (StorageKey(..))
19+
20+foreign import browserImpl ∷ Type
21+
22+-- Unusal API. The keys could also be an array in which case there'd be an object
23+-- with keys for the array of strings passed in originally values. Maybe this is
24+-- for efficiency reasons when using the "sync" API which is apparently kind of
25+-- throttled. Until then, we store keys and values.
26+foreign import data LocalStorage ∷ Type
27+
28+foreign import loadFromLocalStorageImpl ∷ String -> Effect (Promise (Object Foreign))
29+
30+foreign import saveInLocalStorageImpl ∷ Object Foreign -> Effect (Promise Unit)
31+
32+foreign import removeFromLocalStorageImpl ∷ String -> Effect (Promise Unit)
33+
34+loadFromLocalStorage ∷ ∀ a. ReadForeign a => StorageKey -> Aff (Maybe a)
35+loadFromLocalStorage (StorageKey key) = do
36+ Console.info $ "Loading from Local Storage " <> key
37+ result <- toAffE $ loadFromLocalStorageImpl key
38+ pure case Object.lookup key result of
39+ Nothing -> Nothing
40+ Just raw -> JSON.read_ raw
41+
42+saveInLocalStorage ∷ ∀ a. WriteForeign a => StorageKey -> a -> Aff Unit
43+saveInLocalStorage (StorageKey key) value = do
44+ Console.info $ "Saving in Local Storage " <> key <> " : " <> JSON.writeJSON value
45+ saveInLocalStorageImpl (Object.singleton key $ JSON.write value) # toAffE
46+
47+removeFromLocalStorage ∷ StorageKey -> Aff Unit
48+removeFromLocalStorage (StorageKey key) = do
49+ Console.info $ "Removing from Local Storage " <> key
50+ removeFromLocalStorageImpl key # toAffE
51+
52+foreign import data Port ∷ Type
53+
54+foreign import sendMessageViaPortImpl ∷ EffectFn2 Foreign Port Unit
55+
56+sendMessageViaPort ∷ ∀ msg. WriteForeign msg => msg -> Port -> Effect Unit
57+sendMessageViaPort m = runEffectFn2 sendMessageViaPortImpl (JSON.write m)
58+
59+foreign import addOnPortMessageListenerImpl ∷ EffectFn2 (EffectFn1 Foreign Unit) Port Unit
60+
61+addOnPortMessageListener ∷ (Foreign -> Effect Unit) -> Port -> Effect Unit
62+addOnPortMessageListener listener = runEffectFn2 addOnPortMessageListenerImpl (mkEffectFn1 listener)
63+
64+foreign import onContentScriptConnectedImpl ∷ (Port -> Effect Unit) -> Effect Unit
65+
66+onContentScriptConnected ∷ (Port -> Effect Unit) -> Effect Unit
67+onContentScriptConnected = onContentScriptConnectedImpl
68+
69+type ConnectOptions =
70+ (name ∷ String)
71+
72+foreign import connectToBackgroundScriptImpl ∷ ∀ opts. EffectFn1 (Record opts) Port
73+
74+connectToBackgroundScript ∷ ∀ opts missing. Union opts missing ConnectOptions => Record opts -> Effect Port
75+connectToBackgroundScript = runEffectFn1 connectToBackgroundScriptImpl
76+
77+foreign import openNewWindowImpl ∷ ∀ opts. Record opts -> Effect Unit
78+
79+openNewWindow ∷ ∀ opts. Record opts -> Effect Unit
80+openNewWindow = openNewWindowImpl
81+
82+foreign import reload ∷ Effect Unit
83+
84+foreign import data BrowserHistory ∷ Type
85+
86+foreign import history ∷ Effect BrowserHistory
87+
88+foreign import addOnVisitedListenerImpl ∷ (EffectFn1 Foreign Unit) -> BrowserHistory -> Effect Unit
89+
90+addOnVisitedListener
91+ ∷ (HistoryItem -> Effect Unit)
92+ -> BrowserHistory
93+ -> Effect Unit
94+addOnVisitedListener callback = addOnVisitedListenerImpl (mkEffectFn1 rawCallback)
95+ where
96+ rawCallback f = do
97+ let parsed = read f
98+ case parsed of
99+ Left err ->
100+ Console.error
101+ $ "Could not parse history item "
102+ <> intercalateMap "\n" renderForeignError err
103+ Right ok -> callback ok
104+
105+type HistoryItem =
106+ { id ∷ String
107+ , url ∷ Maybe String
108+ , title ∷ Maybe String
109+ , lastVisitTime ∷ Maybe Number -- really an instant
110+ , visitCount ∷ Maybe Int
111+ }
src/WebExtension/Polyfill/Types.pursnew file+10
@@ -0,0 +1,10 @@
1+module WebExtension.Polyfill.Types where
2+
3+import Prelude
4+import Data.Newtype (class Newtype)
5+
6+newtype StorageKey = StorageKey String
7+
8+derive instance newtypeStorageKey ∷ Newtype StorageKey _
9+derive newtype instance eqStorageKey ∷ Eq StorageKey
10+derive newtype instance ordStorageKey ∷ Ord StorageKey