A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Integrate webnative-elm (#285)

authored by

Steven Vandevelde and committed by
GitHub
20ecc544 1d6c507f

+1004 -323
+5 -1
Justfile
··· 180 180 cp {{NPM_DIR}}/ipfs-message-port-client/dist/index.min.js {{BUILD_DIR}}/vendor/ipfs-message-port-client.min.js 181 181 cp ./vendor/pep.js {{BUILD_DIR}}/vendor/pep.js 182 182 183 - {{NPM_DIR}}/.bin/terser {{NPM_DIR}}/webnative/index.umd.js \ 183 + {{NPM_DIR}}/.bin/terser {{NPM_DIR}}/webnative/dist/index.umd.js \ 184 184 --output {{BUILD_DIR}}/vendor/webnative.min.js \ 185 + --compress --mangle 186 + 187 + {{NPM_DIR}}/.bin/terser {{SRC_DIR}}/Static/webnative-elm.js \ 188 + --output {{BUILD_DIR}}/vendor/webnative-elm.min.js \ 185 189 --compress --mangle 186 190 187 191
+6 -3
elm.json
··· 13 13 "Gizra/elm-debouncer": "2.0.0", 14 14 "Herteby/enum": "1.0.1", 15 15 "NoRedInk/elm-json-decode-pipeline": "1.0.0", 16 - "robinheghan/murmur3": "1.0.0", 16 + "TSFoster/elm-tuple-extra": "2.0.0", 17 17 "arturopala/elm-monocle": "2.2.0", 18 18 "avh4/elm-color": "1.0.0", 19 19 "elm/browser": "1.0.2", ··· 30 30 "elm/virtual-dom": "1.0.2", 31 31 "elm-community/dict-extra": "2.4.0", 32 32 "elm-community/html-extra": "3.4.0", 33 - "elm-community/list-extra": "8.2.4", 33 + "elm-community/list-extra": "8.3.0", 34 34 "elm-community/maybe-extra": "5.2.0", 35 35 "elm-explorations/markdown": "1.0.0", 36 + "fission-suite/webnative-elm": "6.0.0", 36 37 "icidasset/elm-binary": "2.1.0", 37 - "icidasset/elm-material-icons": "4.0.0", 38 + "icidasset/elm-material-icons": "8.0.0", 38 39 "icidasset/elm-sha": "2.0.2", 39 40 "mpizenberg/elm-pointer-events": "4.0.2", 40 41 "newlandsvalley/elm-binary-base64": "1.0.3", 41 42 "noahzgordon/elm-color-extra": "1.0.2", 42 43 "ohanhi/keyboard": "2.0.1", 44 + "robinheghan/murmur3": "1.0.0", 43 45 "rtfeldman/elm-hex": "1.0.0", 44 46 "ryannhg/date-format": "2.3.0", 45 47 "truqu/elm-base64": "2.0.4", ··· 48 50 "ymtszw/elm-xml-decode": "3.2.1" 49 51 }, 50 52 "indirect": { 53 + "TSFoster/elm-bytes-extra": "1.3.0", 51 54 "elm/bytes": "1.0.8", 52 55 "elm/parser": "1.1.0", 53 56 "fredcy/elm-parseint": "2.0.1",
+3 -2
package.json
··· 1 1 { 2 2 "name": "Diffuse", 3 3 "description": "A music player that connects to your cloud/distributed storage", 4 - "version": "3.0.0-beta-2", 4 + "version": "3.0.0-beta-3", 5 5 "author": "Steven Vandevelde <icid.asset@gmail.com>", 6 6 "homepage": "https://diffuse.sh", 7 7 "repository": "github:icidasset/diffuse", ··· 35 35 "subworkers": "^1.0.1", 36 36 "timer.js": "^1.0.4", 37 37 "tocca": "^2.0.9", 38 - "webnative": "0.21.4" 38 + "webnative": "0.25.2", 39 + "webnative-elm": "6.0.0" 39 40 }, 40 41 "pnpm": { 41 42 "overrides": {
+73 -27
pnpm-lock.yaml
··· 14 14 subworkers: 1.0.1 15 15 timer.js: 1.0.4 16 16 tocca: 2.0.9 17 - webnative: 0.21.4 17 + webnative: 0.25.2 18 + webnative-elm: 6.0.0_webnative@0.25.2 18 19 devDependencies: 19 20 elm-review: 2.3.3 20 21 elm-tailwind-css: 1.0.1_tailwindcss@1.9.6 ··· 1046 1047 node: '>=6.0' 1047 1048 resolution: 1048 1049 integrity: sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== 1049 - /cids/1.1.5: 1050 + /cids/1.1.7: 1050 1051 dependencies: 1051 - multibase: 3.1.0 1052 - multicodec: 2.1.0 1053 - multihashes: 3.1.0 1054 - uint8arrays: 2.0.5 1052 + multibase: 4.0.4 1053 + multicodec: 3.1.0 1054 + multihashes: 4.0.2 1055 + uint8arrays: 2.1.7 1055 1056 dev: false 1056 1057 engines: 1057 1058 node: '>=4.0.0' 1058 1059 npm: '>=3.0.0' 1059 1060 resolution: 1060 - integrity: sha512-i0V7tF2Jf78BKXyy2rpy1H/ozaJEP8b3Z7ZcHe9J86RRvJZ4e7daaJP3xwL09e14/Bl/mYX5WVc36fbQtjH7Sg== 1061 + integrity: sha512-dlh+K0hMwFAFFjWQ2ZzxOhgGVNVREPdmk8cqHFui2U4sOodcemLMxdE5Ujga4cDcDQhWfldEPThkfu6KWBt1eA== 1061 1062 /cipher-base/1.0.4: 1062 1063 dependencies: 1063 1064 inherits: 2.0.4 ··· 2047 2048 node: '>= 0.10' 2048 2049 resolution: 2049 2050 integrity: sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== 2050 - /fission-bloom-filters/1.4.0: 2051 + /fission-bloom-filters/1.6.0: 2051 2052 dependencies: 2052 2053 is-buffer: 2.0.5 2053 2054 lodash: 4.17.20 ··· 2058 2059 xxhashjs: 0.2.2 2059 2060 dev: false 2060 2061 resolution: 2061 - integrity: sha512-7wif9/Uj7IKqTUQhk/KlWVh9fRin65UQSz2BCx+7myG67sgS2ETpyu8iV/yNou7M2KcrZhZd94vn4a54L6T70Q== 2062 + integrity: sha512-pJWpxdU7RqcuES0PvlHAeoW3+meW0/cf4/9KrnQN41We+9ulSyD5VauyeLqobQ6uQvDhOjymE/7Gq2nzAkOhjA== 2062 2063 /flat-cache/3.0.4: 2063 2064 dependencies: 2064 2065 flatted: 3.1.0 ··· 2594 2595 integrity: sha512-n5cDDeTWWRwK1EBoWwRti+8nP4NbytBBY0pldmnIkq6Z55KNFmWofh4rl9dPZpj+U/nVq7gweR3ylrvMt4YZ5A== 2595 2596 /ipfs-core-types/0.2.1: 2596 2597 dependencies: 2597 - cids: 1.1.5 2598 + cids: 1.1.7 2598 2599 multiaddr: 8.1.2 2599 2600 peer-id: 0.14.2 2600 2601 dev: false ··· 2602 2603 integrity: sha512-q93+93qSybku6woZaajE9mCrHeVoMzNtZ7S5m/zx0+xHRhnoLlg8QNnGGsb5/+uFQt/RiBArsIw/Q61K9Jwkzw== 2603 2604 /ipld-block/0.11.0: 2604 2605 dependencies: 2605 - cids: 1.1.5 2606 + cids: 1.1.7 2606 2607 dev: false 2607 2608 engines: 2608 2609 node: '>=6.0.0' ··· 2611 2612 integrity: sha512-Kk56OOPmlWAjXfBJXvx2jX5RA6R9qUrcc2JXwF7Y4IL9mlmxcxTNkgcsJYR78DbyMllQbi7yreghjGjtCTYKaw== 2612 2613 /ipld-dag-pb/0.20.0: 2613 2614 dependencies: 2614 - cids: 1.1.5 2615 + cids: 1.1.7 2615 2616 class-is: 1.1.0 2616 2617 multicodec: 2.1.0 2617 2618 multihashing-async: 2.0.1 ··· 3035 3036 dev: false 3036 3037 resolution: 3037 3038 integrity: sha1-dgNxknCvtlZO04oiCHoG/Jqk6hs= 3038 - /keystore-idb/0.14.0: 3039 + /keystore-idb/0.14.1: 3039 3040 dependencies: 3040 3041 '@ungap/global-this': 0.4.3 3042 + buffer: 6.0.3 3041 3043 localforage: 1.9.0 3042 3044 dev: false 3043 3045 engines: 3044 3046 node: '>=10.21.0' 3045 3047 resolution: 3046 - integrity: sha512-LX6BipvB4t90XZgD/6yYg6/7Avv9V+yEg6j48Vds2T5QRd82De03ApKlCbkYYGl3G2odwY28odL7ES6+eKcglg== 3048 + integrity: sha512-oyymnax18EycBDlS8Leg9WmJJ39J3fITDXUWd+he8gkq44hgnEDjjgfiGWuCBLlB9/3E3AmLWvpXFPlPAG6jWQ== 3047 3049 /keyv/4.0.3: 3048 3050 dependencies: 3049 3051 json-buffer: 3.0.1 ··· 3474 3476 integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 3475 3477 /multiaddr/8.1.2: 3476 3478 dependencies: 3477 - cids: 1.1.5 3479 + cids: 1.1.7 3478 3480 class-is: 1.1.0 3479 3481 dns-over-http-resolver: 1.2.1 3480 3482 err-code: 2.0.3 ··· 3495 3497 npm: '>=6.0.0' 3496 3498 resolution: 3497 3499 integrity: sha512-Z+pThrpbS7ckQ2DwW5mPiwCGe1a94f8DWi/OxmbyeRednVOyUKmLSE+60kL/WHFYwWnaD1OakXGk3PYI1NkMFw== 3500 + /multibase/4.0.4: 3501 + dependencies: 3502 + '@multiformats/base-x': 4.0.1 3503 + dev: false 3504 + engines: 3505 + node: '>=12.0.0' 3506 + npm: '>=6.0.0' 3507 + resolution: 3508 + integrity: sha512-8/JmrdSGzlw6KTgAJCOqUBSGd1V6186i/X8dDCGy/lbCKrQ+1QB6f3HE+wPr7Tpdj4U3gutaj9jG2rNX6UpiJg== 3498 3509 /multicodec/2.1.0: 3499 3510 dependencies: 3500 3511 uint8arrays: 1.1.0 ··· 3502 3513 dev: false 3503 3514 resolution: 3504 3515 integrity: sha512-7AYpK/avswOWvnqQ9/jOkQCS7Fp4aKxw5ojvn5gyK2VQTZz3YVXeLMzoIZDBy745JSfJMXkTS0ptnHci5Mt1mA== 3516 + /multicodec/3.1.0: 3517 + dependencies: 3518 + uint8arrays: 2.1.7 3519 + varint: 6.0.0 3520 + dev: false 3521 + resolution: 3522 + integrity: sha512-f6d4DhbQ9a8WiJ/wpbKgeJSeR0/juP/1wnjbKdZ0KAWDkC/z7Lb3xOegMUG+uTcfwSYf6j1eTvFf8HDgqPRGmQ== 3523 + /multiformats/9.4.3: 3524 + dev: false 3525 + resolution: 3526 + integrity: sha512-sCNjBP/NPCeQu83Mst8IQZq9+HuR7Catvk/m7CeH0r/nupsU6gM7GINf5E1HCDRxDeU+Cgda/WPmcwQhYs3dyA== 3505 3527 /multihashes/3.1.0: 3506 3528 dependencies: 3507 3529 multibase: 3.1.0 ··· 3513 3535 npm: '>=6.0.0' 3514 3536 resolution: 3515 3537 integrity: sha512-snU+w6aZy5bTrrqIHW3wkT0MfHmxcpOsaVNJt0NzUnseksbjFDVUZjSmhDMAVOVnIdLMS7xHjo55pKlBIGmC3g== 3538 + /multihashes/4.0.2: 3539 + dependencies: 3540 + multibase: 4.0.4 3541 + uint8arrays: 2.1.7 3542 + varint: 5.0.2 3543 + dev: false 3544 + engines: 3545 + node: '>=12.0.0' 3546 + npm: '>=6.0.0' 3547 + resolution: 3548 + integrity: sha512-xpx++1iZr4ZQHjN1mcrXS6904R36LWLxX/CBifczjtmrtCXEX623DMWOF1eiNSg+pFpiZDFVBgou/4v6ayCHSQ== 3516 3549 /multihashing-async/2.0.1: 3517 3550 dependencies: 3518 3551 blakejs: 1.1.0 ··· 3618 3651 dev: true 3619 3652 resolution: 3620 3653 integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== 3654 + /noble-ed25519/1.2.5: 3655 + dev: false 3656 + resolution: 3657 + integrity: sha512-7vst+4UhM5QU3jJ3pUqPMKBCOePrxBojmoQa59qcSnYvjFF/T4jqb4WISlfslcWyBw7G5H9V/acpcAxMd8DzUQ== 3621 3658 /node-addon-api/2.0.2: 3622 3659 dev: false 3623 3660 resolution: ··· 4038 4075 integrity: sha512-QHJag0oYYPVkx6rVPEgCLEUMo6VRYbV3GUrqy00lxXJBEIw9LhPCP5MQI6mEfahJO9KYUP8W8qD8kC0V9RyZFQ== 4039 4076 /peer-id/0.14.2: 4040 4077 dependencies: 4041 - cids: 1.1.5 4078 + cids: 1.1.7 4042 4079 class-is: 1.1.0 4043 4080 libp2p-crypto: 0.18.0 4044 4081 minimist: 1.2.5 ··· 5517 5554 dev: false 5518 5555 resolution: 5519 5556 integrity: sha512-cLdlZ6jnFczsKf5IH1gPHTtcHtPGho5r4CvctohmQjw8K7Q3gFdfIGHxSTdTaCKrL4w09SsPRJTqRS0drYeszA== 5520 - /uint8arrays/2.0.5: 5557 + /uint8arrays/2.1.7: 5521 5558 dependencies: 5522 - multibase: 3.1.0 5523 - web-encoding: 1.0.6 5559 + multiformats: 9.4.3 5524 5560 dev: false 5525 5561 resolution: 5526 - integrity: sha512-1HSktgwqtYIwVn1mg3GcnqKhHH9oC4kVgdD/43cxMWwhP8rihKcFPmToDzS1XtbvVvlR8XxTk/DUBf0C83qNIg== 5562 + integrity: sha512-k+yuEWEHQG/TuRaxL+JVEe8IBqyU5dhDkw+CISCDccOcW90dIju0A6i0Iwav0MK7kg73FZpowqOByS5e/B6GYA== 5527 5563 /union-value/1.0.1: 5528 5564 dependencies: 5529 5565 arr-union: 3.1.0 ··· 5736 5772 dev: false 5737 5773 resolution: 5738 5774 integrity: sha512-l+UtsuV4zrBKyVAj9VCtwWgscTgadCsdGgL1OvbV102cvydWwJCGXlFIXauzWLzfheIDHfPNRWfgMuwyC6ZfIA== 5739 - /webnative/0.21.4: 5775 + /webnative-elm/6.0.0_webnative@0.25.2: 5776 + dependencies: 5777 + webnative: 0.25.2 5778 + dev: false 5779 + peerDependencies: 5780 + webnative: '> 0.24.0' 5781 + resolution: 5782 + integrity: sha512-J3awU3m6LsGDhWg0MGVe8WltQhcRSdz57sMYtoIgDteDePhfG1l7B0HUnzWCotyXs8j7HDVHCGlR+B33vEW/fQ== 5783 + /webnative/0.25.2: 5740 5784 dependencies: 5741 5785 base58-universal: 1.0.0 5742 5786 borc: 2.1.2 5743 5787 buffer: 6.0.3 5744 - cids: 1.1.5 5745 - fission-bloom-filters: 1.4.0 5788 + cids: 1.1.7 5789 + fission-bloom-filters: 1.6.0 5746 5790 ipfs-message-port-client: '@ipfs.runfission.com/ipfs/bafybeigx6q4aezve7my76s5vvfuiinbxtepapqvmjf2jbgrozrut6cjape/p/ipfs-message-port-client.tar.gz' 5747 5791 ipfs-message-port-protocol: '@ipfs.runfission.com/ipfs/bafybeigx6q4aezve7my76s5vvfuiinbxtepapqvmjf2jbgrozrut6cjape/p/ipfs-message-port-protocol.tar.gz' 5748 5792 ipld-dag-pb: 0.20.0 5749 - keystore-idb: 0.14.0 5793 + keystore-idb: 0.14.1 5750 5794 localforage: 1.9.0 5751 5795 make-error: 1.3.6 5796 + noble-ed25519: 1.2.5 5752 5797 throttle-debounce: 2.3.0 5753 5798 dev: false 5754 5799 engines: 5755 5800 node: '>=10.21.0' 5756 5801 resolution: 5757 - integrity: sha512-4fozhQN5QboZRV+lzx5muLuQyeEsjeyrs3XH+cQhlon2wl2M2d5DgcZlyyaTUv9CBckoyNbVRtlUrMo2dw5OqA== 5802 + integrity: sha512-KRyvtdtsWj6tlji8qbXa6fJKCq/jThUamIFbSbr/+/2E1kSxhlR+cxqLKnQR8drl/tcfeHunc8Aj27gTtHDp0g== 5758 5803 /webpack-cli/3.3.12_webpack@4.45.0: 5759 5804 dependencies: 5760 5805 chalk: 2.4.2 ··· 5976 6021 version: 0.5.0-fission 5977 6022 '@ipfs.runfission.com/ipfs/bafybeigx6q4aezve7my76s5vvfuiinbxtepapqvmjf2jbgrozrut6cjape/p/ipfs-message-port-protocol.tar.gz': 5978 6023 dependencies: 5979 - cids: 1.1.5 6024 + cids: 1.1.7 5980 6025 ipld-block: 0.11.0 5981 6026 dev: false 5982 6027 engines: ··· 6011 6056 terser: ^5.0.0 6012 6057 timer.js: ^1.0.4 6013 6058 tocca: ^2.0.9 6014 - webnative: 0.21.4 6059 + webnative: 0.25.2 6060 + webnative-elm: 6.0.0 6015 6061 webpack: ^4.44.1 6016 6062 webpack-cli: ^3.3.12
+1
src/Applications/Brain.elm
··· 167 167 , Ports.receiveTags (ProcessingMsg << Processing.TagsStep) 168 168 , Ports.replaceTags ReplaceTrackTags 169 169 , Ports.savedHypaethralBit (\_ -> UserMsg User.SaveNextHypaethralBit) 170 + , Ports.webnativeResponse (UserMsg << User.GotWebnativeResponse) 170 171 171 172 -- 172 173 , Time.every (60 * 1000) SetCurrentTime
+7 -6
src/Applications/Brain/Ports.elm
··· 3 3 import Alien 4 4 import Json.Encode as Json 5 5 import Sources.Processing exposing (ContextForTags, ContextForTagsSync) 6 + import Webnative 6 7 7 8 8 9 ··· 64 65 port requestDropbox : Alien.Event -> Cmd msg 65 66 66 67 67 - port requestFission : Alien.Event -> Cmd msg 68 - 69 - 70 68 port requestIpfs : Alien.Event -> Cmd msg 71 69 72 70 ··· 79 77 port toDropbox : Alien.Event -> Cmd msg 80 78 81 79 82 - port toFission : Alien.Event -> Cmd msg 83 - 84 - 85 80 port toIpfs : Alien.Event -> Cmd msg 86 81 87 82 88 83 port toRemoteStorage : Alien.Event -> Cmd msg 84 + 85 + 86 + port webnativeRequest : Webnative.Request -> Cmd msg 89 87 90 88 91 89 ··· 108 106 109 107 110 108 port savedHypaethralBit : (Json.Value -> msg) -> Sub msg 109 + 110 + 111 + port webnativeResponse : (Webnative.Response -> msg) -> Sub msg
+4 -3
src/Applications/Brain/Types.elm
··· 3 3 import Brain.Sources.Processing.Types as Processing 4 4 import Brain.User.Types as User 5 5 import Debouncer.Basic as Debouncer exposing (Debouncer) 6 + import Dict exposing (Dict) 6 7 import Json.Decode as Json 7 8 import List.Zipper exposing (Zipper) 8 9 import Management 9 10 import Sources.Processing as Processing 10 11 import Time 11 - import User.Layer as User exposing (HypaethralBit(..)) 12 + import User.Layer as User exposing (HypaethralBaggage, HypaethralBit(..)) 12 13 13 14 14 15 ··· 27 28 { authMethod : Maybe User.Method 28 29 , currentTime : Time.Posix 29 30 , hypaethralDebouncer : Debouncer HypaethralBit (List HypaethralBit) 30 - , hypaethralRetrieval : Maybe (Zipper ( HypaethralBit, Json.Value )) 31 - , hypaethralStorage : List HypaethralBit 31 + , hypaethralRetrieval : Maybe (Zipper ( HypaethralBit, Json.Value, HypaethralBaggage )) 32 + , hypaethralStorage : List { bit : HypaethralBit, saving : Bool } 32 33 , hypaethralUserData : User.HypaethralData 33 34 , legacyMode : Bool 34 35 , migratingData : Bool
+104 -39
src/Applications/Brain/User/State.elm
··· 11 11 import Json.Decode as Decode 12 12 import Json.Encode as Json 13 13 import List.Zipper as Zipper 14 + import Maybe.Extra as Maybe 14 15 import Playlists.Encoding as Playlists 15 16 import Return exposing (andThen, return) 16 17 import Return.Ext as Return ··· 19 20 import Task.Extra exposing (do) 20 21 import Tracks exposing (Track) 21 22 import Tracks.Encoding as Tracks 23 + import Tuple3 22 24 import Url exposing (Url) 23 25 import Url.Ext as Url 24 26 import User.Layer as User exposing (..) 27 + import User.Layer.Methods.Fission as Fission 28 + import Webnative 25 29 26 30 27 31 ··· 32 36 initialCommand uiUrl = 33 37 case Url.action uiUrl of 34 38 [ "authenticate", "fission" ] -> 35 - Cmd.batch 36 - [ do (UserMsg RetrieveEnclosedData) 37 - ] 39 + Cmd.none 38 40 39 41 _ -> 40 42 Cmd.batch ··· 101 103 SaveEnclosedData a -> 102 104 saveEnclosedData a 103 105 104 - SaveHypaethralData a b -> 105 - saveHypaethralData a b 106 - 107 106 ----------------------------------------- 108 107 -- y. Data 109 108 ----------------------------------------- ··· 137 136 ----------------------------------------- 138 137 -- z. Data 139 138 ----------------------------------------- 139 + GotWebnativeResponse a -> 140 + gotWebnativeResponse a 141 + 140 142 SaveAllHypaethralData -> 141 143 saveAllHypaethralData 142 144 ··· 163 165 -- 🔱 164 166 165 167 168 + gotWebnativeResponse : Webnative.Response -> Manager 169 + gotWebnativeResponse response model = 170 + let 171 + isQuery = 172 + Maybe.isJust model.hypaethralRetrieval 173 + 174 + baggage = 175 + model.hypaethralRetrieval 176 + |> Maybe.map (Zipper.current >> Tuple3.third) 177 + |> Maybe.withDefault BaggageClaimed 178 + in 179 + case Fission.proceed response baggage of 180 + Fission.Error err -> 181 + Common.reportUI Alien.ReportError err model 182 + 183 + Fission.Hypaethral data -> 184 + hypaethralDataRetrieved data model 185 + 186 + Fission.LoadedFileSystem -> 187 + -- Had to load the filesystem first, please continue. 188 + let 189 + authMethod = 190 + Fission { initialised = True } 191 + in 192 + model.authMethod 193 + |> Maybe.map (\_ -> authMethod) 194 + |> (\a -> { model | authMethod = a }) 195 + |> retrieveAllHypaethralData 196 + 197 + Fission.Ongoing newBaggage request -> 198 + model.hypaethralRetrieval 199 + |> Maybe.map 200 + (newBaggage 201 + |> always 202 + |> Tuple3.mapThird 203 + |> Zipper.map 204 + ) 205 + |> (\h -> { model | hypaethralRetrieval = h }) 206 + |> Return.communicate (Ports.webnativeRequest request) 207 + 208 + Fission.OtherRequest request -> 209 + request 210 + |> Ports.webnativeRequest 211 + |> return model 212 + 213 + Fission.SaveNextHypaethralBit -> 214 + saveNextHypaethralBit model 215 + 216 + Fission.Stopping -> 217 + Return.singleton model 218 + 219 + 166 220 signIn : Json.Value -> Manager 167 221 signIn json model = 168 222 -- 🐤 ··· 222 276 Just (Dropbox _) -> 223 277 Cmd.none 224 278 225 - Just Fission -> 279 + Just (Fission _) -> 226 280 Ports.deconstructFission () 227 281 228 282 Just (Ipfs _) -> ··· 299 353 let 300 354 retrieval = 301 355 Maybe.map 302 - (Zipper.mapCurrent <| Tuple.mapSecond <| always encodedData) 356 + (Zipper.mapCurrent <| Tuple3.mapSecond <| always encodedData) 303 357 model.hypaethralRetrieval 304 358 in 305 359 case Maybe.andThen Zipper.next retrieval of 306 360 Just nextRetrieval -> 307 361 retrieveHypaethralData 308 - (Tuple.first <| Zipper.current nextRetrieval) 362 + (Tuple3.first <| Zipper.current nextRetrieval) 309 363 { model | hypaethralRetrieval = Just nextRetrieval } 310 364 311 365 Nothing -> ··· 346 400 let 347 401 maybeZipper = 348 402 hypaethralBit.list 349 - |> List.map (\( _, b ) -> ( b, Json.null )) 403 + |> List.map (\( _, b ) -> ( b, Json.null, BaggageClaimed )) 350 404 |> Zipper.fromList 351 405 in 352 406 case maybeZipper of 353 407 Just zipper -> 354 408 retrieveHypaethralData 355 - (Tuple.first <| Zipper.current zipper) 409 + (Tuple3.first <| Zipper.current zipper) 356 410 { model | hypaethralRetrieval = Just zipper } 357 411 358 412 Nothing -> ··· 363 417 retrieveHypaethralData : HypaethralBit -> Manager 364 418 retrieveHypaethralData bit model = 365 419 let 420 + filename = 421 + hypaethralBitFileName bit 422 + 366 423 file = 367 - Json.string (hypaethralBitFileName bit) 424 + Json.string filename 368 425 in 369 426 case model.authMethod of 370 427 -- 🚀 ··· 377 434 |> Ports.requestDropbox 378 435 |> return model 379 436 380 - Just Fission -> 381 - [ ( "file", file ) 382 - ] 383 - |> Json.object 384 - |> Alien.broadcast Alien.AuthFission 385 - |> Ports.requestFission 437 + Just (Fission params) -> 438 + filename 439 + |> Fission.retrieve params bit 440 + |> Ports.webnativeRequest 386 441 |> return model 387 442 388 443 Just (Ipfs { apiOrigin }) -> ··· 452 507 |> saveHypaethralDataBits 453 508 454 509 455 - saveHypaethralData : HypaethralBit -> Json.Value -> Manager 456 - saveHypaethralData bit json model = 510 + saveHypaethralData : HypaethralBit -> Manager 511 + saveHypaethralData bit model = 457 512 let 513 + filename = 514 + hypaethralBitFileName bit 515 + 458 516 file = 459 - Json.string (hypaethralBitFileName bit) 517 + Json.string filename 518 + 519 + json = 520 + encodeHypaethralBit bit model.hypaethralUserData 460 521 in 461 522 case model.authMethod of 462 523 -- 🚀 ··· 470 531 |> Ports.toDropbox 471 532 |> return model 472 533 473 - Just Fission -> 474 - [ ( "data", json ) 475 - , ( "file", file ) 476 - ] 477 - |> Json.object 478 - |> Alien.broadcast Alien.AuthFission 479 - |> Ports.toFission 534 + Just (Fission params) -> 535 + json 536 + |> Fission.save params bit filename 537 + |> List.map Ports.webnativeRequest 538 + |> Cmd.batch 480 539 |> return model 481 540 482 541 Just (Ipfs { apiOrigin }) -> ··· 519 578 -} 520 579 saveHypaethralDataBits : List HypaethralBit -> Manager 521 580 saveHypaethralDataBits bits model = 522 - case model.hypaethralStorage ++ bits of 523 - bit :: rest -> 524 - saveHypaethralData 525 - bit 526 - (encodeHypaethralBit bit model.hypaethralUserData) 527 - { model | hypaethralStorage = rest } 581 + let 582 + newItems = 583 + List.map (\b -> { bit = b, saving = False }) bits 584 + in 585 + case model.hypaethralStorage ++ newItems of 586 + item :: rest -> 587 + if item.saving then 588 + Return.singleton model 589 + 590 + else 591 + saveHypaethralData 592 + item.bit 593 + { model | hypaethralStorage = { item | saving = True } :: rest } 528 594 529 595 _ -> 530 596 Return.singleton model ··· 562 628 saveNextHypaethralBit : Manager 563 629 saveNextHypaethralBit model = 564 630 case model.hypaethralStorage of 565 - bit :: rest -> 631 + _ :: item :: rest -> 566 632 saveHypaethralData 567 - bit 568 - (encodeHypaethralBit bit model.hypaethralUserData) 569 - { model | hypaethralStorage = rest } 633 + item.bit 634 + { model | hypaethralStorage = { item | saving = True } :: rest } 570 635 571 636 _ -> 572 - Return.singleton model 637 + Return.singleton { model | hypaethralStorage = [] } 573 638 574 639 575 640 sendHypaethralDataToUI : Json.Value -> HypaethralData -> Manager
+2 -1
src/Applications/Brain/User/Types.elm
··· 3 3 import Debouncer.Basic as Debouncer 4 4 import Json.Decode as Json 5 5 import User.Layer as User exposing (HypaethralBit(..)) 6 + import Webnative 6 7 7 8 8 9 ··· 37 38 | RetrieveEnclosedData 38 39 | EnclosedDataRetrieved Json.Value 39 40 | SaveEnclosedData Json.Value 40 - | SaveHypaethralData HypaethralBit Json.Value 41 41 ----------------------------------------- 42 42 -- y. Data 43 43 ----------------------------------------- ··· 50 50 ----------------------------------------- 51 51 -- z. Data 52 52 ----------------------------------------- 53 + | GotWebnativeResponse Webnative.Response 53 54 | SaveAllHypaethralData 54 55 | SaveHypaethralDataBits (List HypaethralBit) 55 56 | SaveHypaethralDataSlowly (Debouncer.Msg HypaethralBit)
+5 -1
src/Applications/UI.elm
··· 460 460 Export -> 461 461 User.export 462 462 463 + GotWebnativeResponse a -> 464 + User.gotWebnativeResponse a 465 + 463 466 ImportFile a -> 464 467 User.importFile a 465 468 ··· 577 580 -- 📭 Other 578 581 ----------------------------------------- 579 582 , Ports.setIsOnline SetIsOnline 583 + , Ports.webnativeResponse GotWebnativeResponse 580 584 , Sub.map KeyboardMsg Keyboard.subscriptions 581 585 , Time.every (60 * 1000) SetCurrentTime 582 586 ] ··· 679 683 TracksMsg (Tracks.StoredInCache data <| Just err) 680 684 681 685 _ -> 682 - ShowNotification (Notifications.error err) 686 + ShowNotification (Notifications.stickyError err)
+19 -4
src/Applications/UI/Authentication/State.elm
··· 37 37 import Url.Ext as Url 38 38 import User.Layer exposing (..) 39 39 import User.Layer.Methods.RemoteStorage as RemoteStorage 40 + import Webnative 41 + import Webnative.Constants as Webnative 40 42 41 43 42 44 ··· 99 101 initialCommand url = 100 102 case Url.action url of 101 103 [ "authenticate", "fission" ] -> 102 - Ports.authenticateWithFission () 104 + Webnative.permissions 105 + |> Webnative.initWithOptions 106 + { autoRemoveUrlParams = True 107 + , loadFileSystem = False 108 + } 109 + |> Ports.webnativeRequest 103 110 104 111 _ -> 105 112 Cmd.none ··· 265 272 |> Nav.load 266 273 |> return model 267 274 268 - Fission -> 269 - () 270 - |> Ports.redirectToFissionForAuth 275 + Fission _ -> 276 + let 277 + url = 278 + model.url 279 + 280 + redirectTo = 281 + { url | query = Just "action=authenticate/fission" } 282 + in 283 + Webnative.permissions 284 + |> Webnative.redirectToLobby (Webnative.RedirectTo redirectTo) 285 + |> Ports.webnativeRequest 271 286 |> return model 272 287 273 288 RemoteStorage _ ->
+1 -1
src/Applications/UI/Authentication/View.elm
··· 253 253 , outOfOrder = False 254 254 } 255 255 , choiceButton 256 - { action = TriggerExternalAuth Fission "" 256 + { action = TriggerExternalAuth (Fission { initialised = False }) "" 257 257 , icon = \_ _ -> Svg.map never UI.Svg.Elements.fissionLogo 258 258 , infoLink = Just "https://fission.codes/" 259 259 , label = "Fission"
+7 -6
src/Applications/UI/Ports.elm
··· 3 3 import Alien 4 4 import Json.Encode as Json 5 5 import Queue 6 + import Webnative 6 7 7 8 8 9 ··· 13 14 14 15 15 16 port adjustEqualizerSetting : { knob : String, value : Float } -> Cmd msg 16 - 17 - 18 - port authenticateWithFission : () -> Cmd msg 19 17 20 18 21 19 port copyToClipboard : String -> Cmd msg ··· 36 34 port preloadAudio : Queue.EngineItem -> Cmd msg 37 35 38 36 39 - port redirectToFissionForAuth : () -> Cmd msg 40 - 41 - 42 37 port seek : Float -> Cmd msg 43 38 44 39 ··· 46 41 47 42 48 43 port toBrain : Alien.Event -> Cmd msg 44 + 45 + 46 + port webnativeRequest : Webnative.Request -> Cmd msg 49 47 50 48 51 49 ··· 119 117 120 118 121 119 port showStickyErrorNotification : (String -> msg) -> Sub msg 120 + 121 + 122 + port webnativeResponse : (Webnative.Response -> msg) -> Sub msg 122 123 123 124 124 125
+2 -2
src/Applications/UI/Settings.elm
··· 100 100 Just (Dropbox _) -> 101 101 text "on Dropbox." 102 102 103 - Just Fission -> 103 + Just (Fission _) -> 104 104 text "on Fission." 105 105 106 106 Just (Ipfs _) -> ··· 120 120 Just (Dropbox d) -> 121 121 changePassphrase (Dropbox d) 122 122 123 - Just Fission -> 123 + Just (Fission _) -> 124 124 nothing 125 125 126 126 Just (Ipfs i) ->
+2
src/Applications/UI/Types.elm
··· 37 37 import UI.Tracks.Types as Tracks 38 38 import Url exposing (Protocol(..), Url) 39 39 import User.Layer exposing (..) 40 + import Webnative 40 41 41 42 42 43 ··· 275 276 -- User 276 277 ----------------------------------------- 277 278 | Export 279 + | GotWebnativeResponse Webnative.Response 278 280 | ImportFile File 279 281 | ImportJson String 280 282 | ImportLegacyData
+22
src/Applications/UI/User/State.elm
··· 1 1 module UI.User.State exposing (..) 2 2 3 + import Alien 4 + import Return exposing (return) 3 5 import UI.Authentication.State as Authentication 6 + import UI.Ports as Ports 4 7 import UI.Types as UI exposing (..) 8 + import User.Layer as User 9 + import Webnative exposing (Artifact(..), DecodedResponse(..)) 10 + import Webnative.Tag as Tag 5 11 6 12 7 13 8 14 -- 🔱 15 + 16 + 17 + gotWebnativeResponse : Webnative.Response -> Manager 18 + gotWebnativeResponse response model = 19 + case Webnative.decodeResponse Tag.fromString response of 20 + Webnative (Initialisation state) -> 21 + if Webnative.isAuthenticated state then 22 + Authentication.signIn 23 + (User.Fission { initialised = False }) 24 + model 25 + 26 + else 27 + Return.singleton model 28 + 29 + _ -> 30 + Return.singleton model 9 31 10 32 11 33 migrateHypaethralUserData : Manager
+6 -2
src/Javascript/Brain/index.js
··· 10 10 import * as processing from "../processing" 11 11 import * as user from "./user" 12 12 13 - import { fromCache, removeCache, reportError } from "./common" 13 + import { fromCache, identity, removeCache, reportError } from "./common" 14 14 import { sendData, storageCallback, toCache } from "./common" 15 15 16 16 importScripts("brain.elm.js") ··· 87 87 : event.tag 88 88 89 89 toCache(key, event.data.data || event.data) 90 - .then( storageCallback(app, event) ) 90 + .then( 91 + event.tag === "AUTH_ANONYMOUS" 92 + ? storageCallback(app, event) 93 + : identity 94 + ) 91 95 .catch( reportError(app, event) ) 92 96 }) 93 97
+34 -162
src/Javascript/Brain/user.js
··· 6 6 7 7 8 8 import * as crypto from "../crypto" 9 - import { WEBNATIVE_PERMISSIONS, WEBNATIVE_STAGING_MODE, identity } from "../common" 9 + import { WEBNATIVE_STAGING_ENV, WEBNATIVE_STAGING_MODE, identity } from "../common" 10 10 11 11 import { SECRET_KEY_LOCATION } from "./common" 12 12 import { decryptIfNeeded, encryptWithSecretKey } from "./common" ··· 98 98 // ------- 99 99 100 100 let wn 101 - let wnfs 102 - 103 - const PLAYLISTS_PATH = "private/Audio/Music/Playlists/" 104 101 105 102 106 - function fission() { 107 - if (!wn) { 108 - importScripts("vendor/webnative.min.js") 109 - importScripts("vendor/ipfs-message-port-client.min.js") 110 - 111 - wn = self.webnative 112 - 113 - if ([ "localhost", "nightly.diffuse.sh" ].includes(location.hostname)) { 114 - wn.setup.debug({ enabled: true }) 115 - } 116 - 117 - if (WEBNATIVE_STAGING_MODE) { 118 - wn.setup.endpoints({ 119 - api: "https://runfission.net", 120 - lobby: "http://auth.runfission.net", 121 - user: "fissionuser.net" 122 - }) 123 - } 103 + ports.webnativeRequest = app => request => { 104 + constructFission().then(() => { 105 + webnativeElm.request({ app: app, request: request }) 106 + }) 107 + } 124 108 125 - return ( 126 - new Promise((resolve) => { 127 - const channel = new MessageChannel() 128 109 129 - channel.port1.onmessage = ({ ports }) => { 130 - resolve(ports[0]) 131 - } 110 + function constructFission() { 111 + if (wn) return Promise.resolve() 132 112 133 - self.postMessage( 134 - { action: "SETUP_WEBNATIVE" }, 135 - [ channel.port2 ] 136 - ) 137 - }) 138 - ) 139 - .then(port => self.IpfsMessagePortClient.from(port)) 140 - .then(ipfs => wn.ipfs.set(ipfs)) 141 - .then(_ => wn.loadFileSystem(WEBNATIVE_PERMISSIONS)) 142 - .then(fs => wnfs = fs) 113 + importScripts("vendor/webnative.min.js") 114 + importScripts("vendor/webnative-elm.min.js") 115 + importScripts("vendor/ipfs-message-port-client.min.js") 143 116 144 - } else { 145 - return Promise.resolve() 117 + // Environment setup 118 + wn = self.webnative 146 119 120 + if ([ "localhost", "nightly.diffuse.sh" ].includes(location.hostname)) { 121 + wn.setup.debug({ enabled: true }) 147 122 } 148 - } 149 123 150 - 151 - ports.deconstructFission = _app => _ => { 152 - wn.leave({ withoutRedirect: true }) 153 - wn = null 154 - wnfs = null 155 - } 156 - 157 - 158 - ports.requestFission = app => event => { 159 - fission() 160 - .then(() => { 161 - switch (event.data.file) { 162 - 163 - case "playlists.json": 164 - return wnfs.exists(PLAYLISTS_PATH) 124 + if (WEBNATIVE_STAGING_MODE) { 125 + wn.setup.endpoints(WEBNATIVE_STAGING_ENV) 126 + } 165 127 166 - default: 167 - return wnfs.exists(wnfs.appPath([ event.data.file ])) 128 + // Connect IPFS 129 + return ( 130 + new Promise((resolve) => { 131 + const channel = new MessageChannel() 168 132 133 + channel.port1.onmessage = ({ ports }) => { 134 + resolve(ports[0]) 169 135 } 170 - }) 171 - .then(exists => { 172 - const sendJsonData_ = sendJsonData(app, event) 173 - if (!exists) return sendJsonData_(null) 174 136 175 - switch (event.data.file) { 176 - 177 - case "playlists.json": 178 - return (() => { 179 - if (!exists) return wnfs 180 - .read(wnfs.appPath([ event.data.file ])) 181 - .then(a => a ? new TextDecoder().decode(a) : null) 182 - .then(sendJsonData_) 183 - 184 - return wnfs.ls(PLAYLISTS_PATH).then(result => { 185 - return Promise.all(Object.values(result).map(r => 186 - wnfs 187 - .read(PLAYLISTS_PATH + r.name) 188 - .then(p => new TextDecoder().decode(p)) 189 - .then(j => JSON.parse(j)) 190 - )) 191 - }) 192 - })() 193 - .then(playlists => { 194 - sendData(app, event)(playlists) 195 - }) 196 - 197 - default: 198 - return wnfs 199 - .read(wnfs.appPath([ event.data.file ])) 200 - .then(a => a ? new TextDecoder().decode(a) : null) 201 - .then(sendJsonData_) 202 - 203 - } 137 + self.postMessage( 138 + { action: "SETUP_WEBNATIVE_IFRAME" }, 139 + [ channel.port2 ] 140 + ) 204 141 }) 205 - .catch( reportError(app, event) ) 142 + ) 143 + .then(port => self.IpfsMessagePortClient.from(port)) 144 + .then(ipfs => wn.ipfs.set(ipfs)) 206 145 } 207 146 208 147 209 - ports.toFission = app => event => { 210 - fission() 211 - .then(() => { 212 - switch (event.data.file) { 213 - 214 - case "playlists.json": 215 - return wnfs.exists(PLAYLISTS_PATH).then(exists => { 216 - if (exists) return null 217 - return wnfs.mkdir(PLAYLISTS_PATH) 218 - }) 219 - 220 - default: 221 - return null 222 - 223 - } 224 - }) 225 - .then(() => { 226 - let playlistFilenames 227 - 228 - switch (event.data.file) { 229 - 230 - case "playlists.json": 231 - playlistFilenames = event.data.data.map(playlist => 232 - `${playlist.name}.json` 233 - ) 234 - 235 - return wnfs.exists(PLAYLISTS_PATH).then(exists => { 236 - if (exists) return true 237 - return wnfs.mkdir(PLAYLISTS_PATH) 238 - 239 - }).then(_ => 240 - wnfs.ls(PLAYLISTS_PATH) 241 - 242 - ).then(list => 243 - // delete playlists that are no longer in the catalog 244 - Object.values(list).map(l => l.name).filter(name => 245 - !playlistFilenames.includes(name) 246 - ) 247 - 248 - ).then(playlistsToRemove => 249 - Promise.all(playlistsToRemove.map(name => 250 - wnfs.rm(`${PLAYLISTS_PATH}/${name}`) 251 - )) 252 - 253 - ).then(() => 254 - // create/update playlists 255 - Promise.all(event.data.data.map(playlist => wnfs.write( 256 - `${PLAYLISTS_PATH}/${playlist.name}.json`, 257 - new Blob( 258 - [ JSON.stringify(playlist) ], 259 - { type: "text/plain" } 260 - ) 261 - ))) 262 - 263 - ) 264 - 265 - default: 266 - return wnfs.write( 267 - wnfs.appPath([ event.data.file ]), 268 - new Blob( 269 - [ JSON.stringify(event.data.data) ], 270 - { type: "text/plain" } 271 - ) 272 - ) 273 - 274 - } 275 - }) 276 - .then(() => wnfs.publish()) 277 - .then( storageCallback(app, event) ) 278 - .catch( reportError(app, event) ) 148 + ports.deconstructFission = _app => _ => { 149 + wn.leave({ withoutRedirect: true }) 150 + wn = null 279 151 } 280 152 281 153
+7 -12
src/Javascript/common.js
··· 3 3 // ʕ•ᴥ•ʔ 4 4 5 5 6 - export const WEBNATIVE_PERMISSIONS = { 7 - app: { 8 - name: "Diffuse", 9 - creator: "icidasset" 10 - }, 11 - fs: { 12 - privatePaths: ["/Audio/Music/Playlists"], 13 - publicPaths: ["/Audio/Music/Playlists"] 14 - } 15 - } 16 - 17 - 18 6 export const WEBNATIVE_STAGING_MODE = ( 19 7 location.host === "localhost:5000" 20 8 ) 9 + 10 + 11 + export const WEBNATIVE_STAGING_ENV = { 12 + api: "https://runfission.net", 13 + lobby: "https://auth.runfission.net", 14 + user: "fissionuser.net" 15 + } 21 16 22 17 23 18
+17 -44
src/Javascript/index.js
··· 15 15 16 16 import * as audioEngine from "./audio-engine" 17 17 import * as db from "./indexed-db" 18 - import { WEBNATIVE_PERMISSIONS, WEBNATIVE_STAGING_MODE, debounce, fileExtension } from "./common" 18 + import { WEBNATIVE_STAGING_ENV, WEBNATIVE_STAGING_MODE, debounce, fileExtension } from "./common" 19 19 20 20 21 21 // 🔐 ··· 130 130 131 131 function handleAction(action, data, ports) { switch (action) { 132 132 case "DOWNLOAD_TRACKS": return downloadTracks(data) 133 - case "SETUP_WEBNATIVE": return setupWebnative(ports) 133 + case "SETUP_WEBNATIVE_IFRAME": return setupWebnativeIframe(ports) 134 134 }} 135 135 136 136 ··· 264 264 265 265 266 266 wire.webnative = () => { 267 - app.ports.authenticateWithFission.subscribe(authenticateWithFission) 268 - app.ports.redirectToFissionForAuth.subscribe(redirectToFissionForAuth) 269 - } 270 - 271 - 272 - function authenticateWithFission() { 273 - loadWebnative().then(() => { 274 - return wn.initialise({ 275 - permissions: WEBNATIVE_PERMISSIONS, 276 - loadFileSystem: false 277 - }) 278 - 279 - }).then(state => { 280 - if (state.authenticated) brain.postMessage({ 281 - tag: "SIGN_IN", 282 - data: { method: "FISSION", migratingData: false, passphrase: null }, 283 - error: null 267 + app.ports.webnativeRequest.subscribe(request => { 268 + loadWebnative().then(() => { 269 + webnativeElm.request({ app: app, request: request }) 284 270 }) 285 - 286 - }) 287 - } 288 - 289 - 290 - function redirectToFissionForAuth() { 291 - loadWebnative().then(() => { 292 - wn.redirectToLobby( 293 - WEBNATIVE_PERMISSIONS, 294 - `${location.origin}${location.pathname}?action=authenticate/fission` 295 - ) 296 271 }) 297 272 } 298 273 ··· 300 275 function loadWebnative() { 301 276 if (wn) return Promise.resolve() 302 277 303 - return loadScript("vendor/webnative.min.js").then(() => { 304 - wn = window.webnative 278 + return loadScript("vendor/webnative.min.js") 279 + .then(() => loadScript("vendor/webnative-elm.min.js")) 280 + .then(() => { 281 + wn = window.webnative 305 282 306 - if ([ "localhost", "nightly.diffuse.sh" ].includes(location.hostname)) { 307 - wn.setup.debug({ enabled: true }) 308 - } 283 + if ([ "localhost", "nightly.diffuse.sh" ].includes(location.hostname)) { 284 + wn.setup.debug({ enabled: true }) 285 + } 309 286 310 - if (WEBNATIVE_STAGING_MODE) { 311 - wn.setup.endpoints({ 312 - api: "https://runfission.net", 313 - lobby: "http://auth.runfission.net", 314 - user: "fissionuser.net" 315 - }) 316 - } 317 - }) 287 + if (WEBNATIVE_STAGING_MODE) { 288 + wn.setup.endpoints(WEBNATIVE_STAGING_ENV) 289 + } 290 + }) 318 291 } 319 292 320 293 321 - function setupWebnative(ports) { 294 + function setupWebnativeIframe(ports) { 322 295 loadWebnative() 323 296 .then(_ => wn.ipfs.iframe()) 324 297 .then(iframePort => ports[0].postMessage("connect", [ iframePort ]))
+2
src/Library/Alien.elm
··· 72 72 | NotAuthenticated 73 73 | ReloadTracks 74 74 | RemoveTracksByPath 75 + | ReportError 75 76 | ReportProcessingError 76 77 | ReportProcessingProgress 77 78 | UpdateSourceData ··· 130 131 , ( "NOT_AUTHENTICATED", NotAuthenticated ) 131 132 , ( "RELOAD_TRACKS", ReloadTracks ) 132 133 , ( "REMOVE_TRACKS_BY_PATH", RemoveTracksByPath ) 134 + , ( "REPORT_ERROR", ReportError ) 133 135 , ( "REPORT_PROCESSING_ERROR", ReportProcessingError ) 134 136 , ( "REPORT_PROCESSING_PROGRESS", ReportProcessingProgress ) 135 137 , ( "UPDATE_SOURCE_DATA", UpdateSourceData )
+37 -7
src/Library/User/Layer.elm
··· 38 38 39 39 type Method 40 40 = Dropbox { token : String } 41 - | Fission 41 + | Fission { initialised : Bool } 42 42 | Ipfs { apiOrigin : String } 43 43 | Local 44 44 | RemoteStorage { userAddress : String, token : String } ··· 50 50 Dropbox _ -> 51 51 False 52 52 53 - Fission -> 54 - True 53 + Fission _ -> 54 + -- NOTE: Temporarily disabled, 55 + -- since we don't actually support public playlists yet. 56 + False 55 57 56 58 Ipfs _ -> 57 59 False ··· 96 98 | Tracks 97 99 98 100 101 + type HypaethralBaggage 102 + = BaggageClaimed 103 + -- 104 + | PlaylistsBaggage PlaylistsBaggageAttributes 105 + 106 + 99 107 type alias HypaethralData = 100 108 { favourites : List Tracks.Favourite 101 109 , playlists : List Playlists.Playlist ··· 106 114 } 107 115 108 116 117 + type alias PlaylistsBaggageAttributes = 118 + { publicPlaylistsRead : List Json.Value 119 + , publicPlaylistsTodo : List String 120 + , privatePlaylistsRead : List Json.Value 121 + , privatePlaylistsTodo : List String 122 + } 123 + 124 + 109 125 110 126 -- 🔱 ░░ METHOD 111 127 ··· 127 143 Just (Dropbox { token = t }) 128 144 129 145 [ "FISSION" ] -> 130 - Just Fission 146 + Just (Fission { initialised = False }) 131 147 132 148 [ "IPFS", a ] -> 133 149 Just (Ipfs { apiOrigin = a }) ··· 152 168 , token 153 169 ] 154 170 155 - Fission -> 171 + Fission _ -> 156 172 "FISSION" 157 173 158 174 Ipfs { apiOrigin } -> ··· 327 343 |> optional (hypaethralBitKey Tracks) (Json.listIgnore Tracks.trackDecoder) [] 328 344 329 345 330 - putHypaethralJsonBitsTogether : List ( HypaethralBit, Json.Value ) -> Json.Value 346 + putHypaethralJsonBitsTogether : List ( HypaethralBit, Json.Value, HypaethralBaggage ) -> Json.Value 331 347 putHypaethralJsonBitsTogether bits = 332 348 bits 333 - |> List.map (Tuple.mapFirst hypaethralBitKey) 349 + |> List.map (\( a, b, _ ) -> ( hypaethralBitKey a, b )) 334 350 |> Json.Encode.object 351 + 352 + 353 + 354 + -- 🔱 ░░ BAGGAGE 355 + 356 + 357 + mapPlaylistsBaggage : (PlaylistsBaggageAttributes -> PlaylistsBaggageAttributes) -> HypaethralBaggage -> HypaethralBaggage 358 + mapPlaylistsBaggage fn baggage = 359 + case baggage of 360 + PlaylistsBaggage p -> 361 + PlaylistsBaggage (fn p) 362 + 363 + b -> 364 + b
+342
src/Library/User/Layer/Methods/Fission.elm
··· 1 + module User.Layer.Methods.Fission exposing (..) 2 + 3 + import Json.Decode as Decode 4 + import Json.Encode as Json 5 + import List.Zipper as Zipper exposing (Zipper) 6 + import Playlists exposing (Playlist) 7 + import Return 8 + import User.Layer exposing (HypaethralBaggage(..), HypaethralBit(..), mapPlaylistsBaggage) 9 + import Webnative exposing (Artifact(..), DecodedResponse(..), NoArtifact(..)) 10 + import Webnative.Constants exposing (..) 11 + import Webnative.Path as Path exposing (Directory, File, Path) 12 + import Webnative.Tag as Tag exposing (Step(..), Tag(..)) 13 + import Wnfs exposing (Artifact(..)) 14 + 15 + 16 + 17 + -- 🌳 18 + 19 + 20 + type Proceedings 21 + = Error String 22 + | Hypaethral Json.Value 23 + | LoadedFileSystem 24 + | Ongoing HypaethralBaggage Webnative.Request 25 + | OtherRequest Webnative.Request 26 + | SaveNextHypaethralBit 27 + | Stopping 28 + 29 + 30 + 31 + -- ⛰ 32 + 33 + 34 + playlistPath : String -> Path File 35 + playlistPath name = 36 + playlistsPath 37 + |> Path.unwrap 38 + |> (\p -> p ++ [ name ++ ".json" ]) 39 + |> Path.file 40 + 41 + 42 + 43 + -- ⛵️ 44 + 45 + 46 + proceed : Webnative.Response -> HypaethralBaggage -> Proceedings 47 + proceed response baggage = 48 + case Webnative.decodeResponse Tag.fromString response of 49 + Webnative (Webnative.NoArtifact LoadedFileSystemManually) -> 50 + LoadedFileSystem 51 + 52 + ----------------------------------------- 53 + -- (1) Public Playlists 54 + ----------------------------------------- 55 + --------------------- 56 + -- Directory Exists - 57 + --------------------- 58 + Wnfs (LoadPlaylists PublicPlaylistsDirectoryExists) (Boolean True) -> 59 + { path = playlistsPath 60 + , tag = Tag.toString (LoadPlaylists PublicPlaylistsDirectoryListed) 61 + } 62 + |> Wnfs.ls Wnfs.Public 63 + |> Ongoing baggage 64 + 65 + Wnfs (LoadPlaylists PublicPlaylistsDirectoryExists) (Boolean False) -> 66 + { path = playlistsPath 67 + , tag = Tag.toString (LoadPlaylists PrivatePlaylistsDirectoryListed) 68 + } 69 + |> Wnfs.ls Wnfs.Private 70 + |> Ongoing baggage 71 + 72 + --------- 73 + -- List - 74 + --------- 75 + Wnfs (LoadPlaylists PublicPlaylistsDirectoryListed) (DirectoryContent listing) -> 76 + baggage 77 + |> ensurePlaylistsBaggage 78 + |> mapPlaylistsBaggage 79 + (\b -> { b | publicPlaylistsTodo = List.map .name listing }) 80 + |> readPublicPlaylistOrMoveOn 81 + 82 + --------- 83 + -- Read - 84 + --------- 85 + Wnfs (LoadPlaylists PublicPlaylistRead) (Utf8Content json) -> 86 + baggage 87 + |> mapPlaylistsBaggage 88 + (\b -> 89 + case Decode.decodeString Decode.value json of 90 + Ok value -> 91 + { b | publicPlaylistsRead = value :: b.publicPlaylistsRead } 92 + 93 + Err _ -> 94 + b 95 + ) 96 + |> readPublicPlaylistOrMoveOn 97 + 98 + ----------------------------------------- 99 + -- (2) Private Playlists 100 + ----------------------------------------- 101 + --------------------- 102 + -- Directory Exists - 103 + --------------------- 104 + Wnfs (LoadPlaylists PrivatePlaylistsDirectoryExists) (Boolean True) -> 105 + { path = playlistsPath 106 + , tag = Tag.toString (LoadPlaylists PrivatePlaylistsDirectoryListed) 107 + } 108 + |> Wnfs.ls Wnfs.Private 109 + |> Ongoing baggage 110 + 111 + Wnfs (LoadPlaylists PrivatePlaylistsDirectoryExists) (Boolean False) -> 112 + finalisePlaylists baggage 113 + 114 + --------- 115 + -- List - 116 + --------- 117 + Wnfs (LoadPlaylists PrivatePlaylistsDirectoryListed) (DirectoryContent listing) -> 118 + baggage 119 + |> ensurePlaylistsBaggage 120 + |> mapPlaylistsBaggage 121 + (\b -> { b | privatePlaylistsTodo = List.map .name listing }) 122 + |> readPrivatePlaylistOrMoveOn 123 + 124 + --------- 125 + -- Read - 126 + --------- 127 + Wnfs (LoadPlaylists PrivatePlaylistRead) (Utf8Content json) -> 128 + baggage 129 + |> mapPlaylistsBaggage 130 + (\b -> 131 + case Decode.decodeString Decode.value json of 132 + Ok value -> 133 + { b | privatePlaylistsRead = value :: b.privatePlaylistsRead } 134 + 135 + Err _ -> 136 + b 137 + ) 138 + |> readPrivatePlaylistOrMoveOn 139 + 140 + ----------------------------------------- 141 + -- Other 142 + ----------------------------------------- 143 + WnfsError (Wnfs.JavascriptError "Path does not exist") -> 144 + Hypaethral Json.null 145 + 146 + WnfsError err -> 147 + Error (Wnfs.error err) 148 + 149 + WebnativeError err -> 150 + Error (Webnative.error err) 151 + 152 + Wnfs GotHypaethralData (Utf8Content json) -> 153 + json 154 + |> Decode.decodeString Decode.value 155 + |> Result.map Hypaethral 156 + |> Result.withDefault Stopping 157 + 158 + Wnfs PublishedHypaethralData _ -> 159 + SaveNextHypaethralBit 160 + 161 + Wnfs WroteHypaethralData Wnfs.NoArtifact -> 162 + { tag = Tag.toString PublishedHypaethralData } 163 + |> Wnfs.publish 164 + |> OtherRequest 165 + 166 + _ -> 167 + Stopping 168 + 169 + 170 + 171 + -- 👀 172 + 173 + 174 + retrieve : { initialised : Bool } -> HypaethralBit -> String -> Webnative.Request 175 + retrieve { initialised } bit filename = 176 + if initialised then 177 + case bit of 178 + -- NOTE: Currently no custom logic for playlists, 179 + -- too many unsolved variables. 180 + -- 181 + -- Playlists -> 182 + -- Wnfs.exists 183 + -- Wnfs.Public 184 + -- { path = playlistsPath 185 + -- , tag = Tag.toString (LoadPlaylists PublicPlaylistsDirectoryExists) 186 + -- } 187 + _ -> 188 + Wnfs.readUtf8 189 + (Wnfs.AppData app) 190 + { path = Path.file [ filename ] 191 + , tag = Tag.toString GotHypaethralData 192 + } 193 + 194 + else 195 + Webnative.loadFileSystem permissions 196 + 197 + 198 + save : { initialised : Bool } -> HypaethralBit -> String -> Json.Value -> List Webnative.Request 199 + save { initialised } bit filename dataCollection = 200 + if initialised then 201 + case bit of 202 + -- TODO: Write each playlist to a file 203 + -- Not in use, because of concurrency issues with publish. 204 + -- https://github.com/fission-suite/webnative/issues/267 205 + -- Playlists -> 206 + -- dataCollection 207 + -- |> Decode.decodeValue (Decode.list Decode.value) 208 + -- |> Result.withDefault [] 209 + -- |> List.filterMap 210 + -- (\playlist -> 211 + -- playlist 212 + -- |> Decode.decodeValue 213 + -- (Decode.map2 214 + -- Tuple.pair 215 + -- (Decode.field "name" Decode.string) 216 + -- (Decode.field "public" Decode.bool) 217 + -- ) 218 + -- |> Result.toMaybe 219 + -- |> Maybe.map (Tuple.pair playlist) 220 + -- ) 221 + -- |> List.map 222 + -- (\( playlist, ( name, public ) ) -> 223 + -- Wnfs.writeUtf8 224 + -- (if public then 225 + -- Wnfs.Public 226 + -- 227 + -- else 228 + -- Wnfs.Private 229 + -- ) 230 + -- { path = playlistPath name 231 + -- , tag = Tag.toString WrotePlaylist 232 + -- } 233 + -- (Json.encode 0 playlist) 234 + -- ) 235 + -- |> (\list -> 236 + -- case list of 237 + -- [] -> 238 + -- [ Wnfs.publish { tag = Tag.toString PublishedHypaethralData } 239 + -- ] 240 + -- 241 + -- l -> 242 + -- l 243 + -- ) 244 + _ -> 245 + [ Wnfs.writeUtf8 246 + (Wnfs.AppData app) 247 + { path = Path.file [ filename ] 248 + , tag = Tag.toString WroteHypaethralData 249 + } 250 + (Json.encode 0 dataCollection) 251 + ] 252 + 253 + else 254 + [ Webnative.loadFileSystem permissions ] 255 + 256 + 257 + 258 + -- PLAYLISTS 259 + 260 + 261 + ensurePlaylistsBaggage : HypaethralBaggage -> HypaethralBaggage 262 + ensurePlaylistsBaggage baggage = 263 + case baggage of 264 + BaggageClaimed -> 265 + PlaylistsBaggage 266 + { publicPlaylistsRead = [] 267 + , publicPlaylistsTodo = [] 268 + , privatePlaylistsRead = [] 269 + , privatePlaylistsTodo = [] 270 + } 271 + 272 + b -> 273 + b 274 + 275 + 276 + finalisePlaylists : HypaethralBaggage -> Proceedings 277 + finalisePlaylists baggage = 278 + case baggage of 279 + PlaylistsBaggage { publicPlaylistsRead, privatePlaylistsRead } -> 280 + (publicPlaylistsRead ++ privatePlaylistsRead) 281 + |> Json.list identity 282 + |> Hypaethral 283 + 284 + _ -> 285 + Hypaethral Json.null 286 + 287 + 288 + 289 + -- PLAYLISTS ░░ PUBLIC 290 + 291 + 292 + readPublicPlaylistOrMoveOn : HypaethralBaggage -> Proceedings 293 + readPublicPlaylistOrMoveOn baggage = 294 + case baggage of 295 + PlaylistsBaggage b -> 296 + case b.publicPlaylistsTodo of 297 + [] -> 298 + checkPrivatePlaylistsDir baggage 299 + 300 + name :: rest -> 301 + { path = playlistPath (String.dropRight 5 name) 302 + , tag = Tag.toString (LoadPlaylists PublicPlaylistRead) 303 + } 304 + |> Wnfs.readUtf8 Wnfs.Public 305 + |> Ongoing 306 + (PlaylistsBaggage { b | publicPlaylistsTodo = rest }) 307 + 308 + _ -> 309 + checkPrivatePlaylistsDir baggage 310 + 311 + 312 + 313 + -- PLAYLISTS ░░ PRIVATE 314 + 315 + 316 + checkPrivatePlaylistsDir : HypaethralBaggage -> Proceedings 317 + checkPrivatePlaylistsDir baggage = 318 + { path = playlistsPath 319 + , tag = Tag.toString (LoadPlaylists PrivatePlaylistsDirectoryExists) 320 + } 321 + |> Wnfs.exists Wnfs.Private 322 + |> Ongoing baggage 323 + 324 + 325 + readPrivatePlaylistOrMoveOn : HypaethralBaggage -> Proceedings 326 + readPrivatePlaylistOrMoveOn baggage = 327 + case baggage of 328 + PlaylistsBaggage b -> 329 + case b.privatePlaylistsTodo of 330 + [] -> 331 + finalisePlaylists baggage 332 + 333 + name :: rest -> 334 + { path = playlistPath (String.dropRight 5 name) 335 + , tag = Tag.toString (LoadPlaylists PrivatePlaylistRead) 336 + } 337 + |> Wnfs.readUtf8 Wnfs.Private 338 + |> Ongoing 339 + (PlaylistsBaggage { b | privatePlaylistsTodo = rest }) 340 + 341 + _ -> 342 + finalisePlaylists baggage
+30
src/Library/Webnative/Constants.elm
··· 1 + module Webnative.Constants exposing (..) 2 + 3 + import Webnative 4 + import Webnative.Path as Path exposing (Directory, Path) 5 + 6 + 7 + permissions : Webnative.Permissions 8 + permissions = 9 + { app = Just app 10 + , fs = Just fs 11 + } 12 + 13 + 14 + app : Webnative.AppPermissions 15 + app = 16 + { creator = "icidasset" 17 + , name = "Diffuse" 18 + } 19 + 20 + 21 + fs : Webnative.FileSystemPermissions 22 + fs = 23 + { private = { directories = [ playlistsPath ], files = [] } 24 + , public = { directories = [ playlistsPath ], files = [] } 25 + } 26 + 27 + 28 + playlistsPath : Path Directory 29 + playlistsPath = 30 + Path.directory [ "Audio", "Music", "Playlists" ]
+111
src/Library/Webnative/Tag.elm
··· 1 + module Webnative.Tag exposing (..) 2 + 3 + import Enum exposing (Enum) 4 + import List.Zipper as Zipper exposing (Zipper) 5 + import Playlists exposing (Playlist) 6 + 7 + 8 + 9 + -- 🌳 10 + 11 + 12 + type Tag 13 + = GotHypaethralData 14 + | PublishedHypaethralData 15 + | WroteHypaethralData 16 + ----------------------------------------- 17 + -- Flows 18 + ----------------------------------------- 19 + | LoadPlaylists Step 20 + 21 + 22 + type Step 23 + = ----------------------------------------- 24 + -- Playlists 25 + ----------------------------------------- 26 + PlaylistAdded 27 + | PlaylistRemoved 28 + | PrivatePlaylistsDirectoryCreated 29 + | PrivatePlaylistsDirectoryExists 30 + | PrivatePlaylistsDirectoryListed 31 + | PrivatePlaylistRead 32 + | PublicPlaylistsDirectoryCreated 33 + | PublicPlaylistsDirectoryExists 34 + | PublicPlaylistsDirectoryListed 35 + | PublicPlaylistRead 36 + 37 + 38 + 39 + -- 🛠 40 + 41 + 42 + fromString : String -> Result String Tag 43 + fromString = 44 + tagEnum.fromString >> Result.fromMaybe "Unknown tag" 45 + 46 + 47 + toString : Tag -> String 48 + toString = 49 + tagEnum.toString 50 + 51 + 52 + 53 + -- ENUMS 54 + 55 + 56 + tagEnum = 57 + [ GotHypaethralData 58 + , PublishedHypaethralData 59 + , WroteHypaethralData 60 + 61 + ----------------------------------------- 62 + -- Playlists 63 + ----------------------------------------- 64 + , LoadPlaylists PlaylistAdded 65 + , LoadPlaylists PlaylistRemoved 66 + , LoadPlaylists PrivatePlaylistsDirectoryCreated 67 + , LoadPlaylists PrivatePlaylistsDirectoryExists 68 + , LoadPlaylists PrivatePlaylistsDirectoryListed 69 + , LoadPlaylists PrivatePlaylistRead 70 + , LoadPlaylists PublicPlaylistsDirectoryCreated 71 + , LoadPlaylists PublicPlaylistsDirectoryExists 72 + , LoadPlaylists PublicPlaylistsDirectoryListed 73 + , LoadPlaylists PublicPlaylistRead 74 + ] 75 + |> List.map tagIterator 76 + |> Enum.create 77 + 78 + 79 + tagIterator tag = 80 + case tag of 81 + GotHypaethralData -> 82 + ( "GotHypaethralData", GotHypaethralData ) 83 + 84 + PublishedHypaethralData -> 85 + ( "PublishedHypaethralData", PublishedHypaethralData ) 86 + 87 + WroteHypaethralData -> 88 + ( "WroteHypaethralData", WroteHypaethralData ) 89 + 90 + ----------------------------------------- 91 + -- Flows 92 + ----------------------------------------- 93 + LoadPlaylists step -> 94 + ( "LoadPlaylists" ++ "_" ++ stepEnum.toString step 95 + , LoadPlaylists step 96 + ) 97 + 98 + 99 + stepEnum = 100 + Enum.create 101 + [ ( "PlaylistAdded", PlaylistAdded ) 102 + , ( "PlaylistRemoved", PlaylistRemoved ) 103 + , ( "PrivatePlaylistsDirectoryCreated", PrivatePlaylistsDirectoryCreated ) 104 + , ( "PrivatePlaylistsDirectoryExists", PrivatePlaylistsDirectoryExists ) 105 + , ( "PrivatePlaylistsDirectoryListed", PrivatePlaylistsDirectoryListed ) 106 + , ( "PrivatePlaylistRead", PrivatePlaylistRead ) 107 + , ( "PublicPlaylistsDirectoryCreated", PublicPlaylistsDirectoryCreated ) 108 + , ( "PublicPlaylistsDirectoryExists", PublicPlaylistsDirectoryExists ) 109 + , ( "PublicPlaylistsDirectoryListed", PublicPlaylistsDirectoryListed ) 110 + , ( "PublicPlaylistRead", PublicPlaylistRead ) 111 + ]
+155
src/Static/webnative-elm.js
··· 1 + // TODO: Remove once fixed in webnative-elm 2 + // https://github.com/fission-suite/webnative-elm/issues/17 3 + 4 + 5 + const DEFAULT_PORT_NAMES = { 6 + incoming: "webnativeRequest", 7 + outgoing: "webnativeResponse" 8 + } 9 + 10 + 11 + ;(function (root, factory) { 12 + if (typeof exports === "object" && typeof exports.nodeName !== "string") { 13 + // CommonJS 14 + factory(exports, require("webnative")) 15 + } else { 16 + // Browser globals 17 + factory((root.webnativeElm = {}), root.webnative) 18 + } 19 + 20 + }(typeof self !== "undefined" ? self : this, function (exports, wn) { 21 + 22 + let fs 23 + const builtInGetFs = () => fs 24 + 25 + 26 + /** 27 + * Handle request. 28 + */ 29 + exports.request = function({ 30 + app, 31 + request, 32 + getFs = builtInGetFs, 33 + portNames = DEFAULT_PORT_NAMES 34 + }) { 35 + switch (request.context) { 36 + case "WEBNATIVE": return webnativeRequest({ app, portNames, request }) 37 + case "WNFS": return wnfsRequest({ app, getFs, portNames, request }) 38 + } 39 + 40 + return { getFs: getFs, portNames: portNames } 41 + } 42 + 43 + 44 + /** 45 + * Setup the ports for our Elm app. 46 + */ 47 + exports.setup = function ({ 48 + app, 49 + getFs = builtInGetFs, 50 + portNames = DEFAULT_PORT_NAMES, 51 + webnative 52 + }) { 53 + if (webnative) { 54 + wn = webnative 55 + } 56 + 57 + if (!wn) { 58 + throw new Error("Failed to load webnative") 59 + } 60 + 61 + if (!app.ports || !app.ports[portNames.incoming]) { 62 + console.warn(`Couldn't find the incoming Elm port for webnative named "${portNames.incoming}". Could be that you haven't used the port yet, dead code elimination.`) 63 + return 64 + } 65 + 66 + if (!app.ports[portNames.outgoing]) { 67 + console.warn(`Not sending webnative responses back to your Elm app, because the outgoing port named "${portNames.outgoing}" was not found. Could be that you haven't used the port yet, dead code elimination.`) 68 + } 69 + 70 + app.ports[portNames.incoming].subscribe(request => { 71 + exports.request({ request, app, getFs, portNames }) 72 + }) 73 + 74 + return { getFs: getFs, portNames: portNames } 75 + } 76 + 77 + 78 + /** 79 + * Handle webnative request. 80 + */ 81 + function webnativeRequest({ 82 + app, portNames, request 83 + }) { 84 + Promise.resolve(wn[request.method]( 85 + ...request.arguments 86 + 87 + )).then(result => { 88 + switch (request.method) { 89 + case "redirectToLobby": return; 90 + case "loadFileSystem": fs = result; break; 91 + case "initialise": fs = result.fs; break; 92 + } 93 + 94 + // Report back to Elm 95 + if (app.ports[portNames.outgoing]) app.ports[portNames.outgoing].send({ 96 + tag: request.tag, 97 + error: null, 98 + method: request.method, 99 + data: result, 100 + context: request.context 101 + }) 102 + 103 + }).catch(err => { 104 + if (app.ports[portNames.outgoing]) app.ports[portNames.outgoing].send({ 105 + tag: request.tag, 106 + error: err.message || err, 107 + method: request.method, 108 + data: null, 109 + context: request.context 110 + }) 111 + 112 + }) 113 + } 114 + 115 + 116 + /** 117 + * Handle WNFS request. 118 + */ 119 + function wnfsRequest({ 120 + app, getFs, portNames, request 121 + }) { 122 + const method = request.method.replace(/_utf8$/, "") 123 + 124 + if (request.method === "write") { 125 + request.arguments = [ 126 + request.arguments[0], 127 + Uint8Array.from(request.arguments[1]) 128 + ] 129 + } 130 + 131 + Promise.resolve(getFs()).then(fs => fs[method]( 132 + ...request.arguments 133 + 134 + )).then(data => { 135 + if (app.ports[portNames.outgoing]) app.ports[portNames.outgoing].send({ 136 + tag: request.tag, 137 + error: null, 138 + method: request.method, 139 + data: data.root ? null : (data.buffer ? Array.from(data) : data), 140 + context: request.context 141 + }) 142 + 143 + }).catch(err => { 144 + if (app.ports[portNames.outgoing]) app.ports[portNames.outgoing].send({ 145 + tag: request.tag, 146 + error: err.message || err, 147 + method: request.method, 148 + data: null, 149 + context: request.context 150 + }) 151 + 152 + }) 153 + } 154 + 155 + }))