├── .envrc ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs.json ├── elm.json ├── flake.lock ├── flake.nix ├── nix └── shell.nix ├── package-lock.json ├── package.json ├── scripts └── build-minified.js ├── shell.nix ├── src ├── Webnative.elm ├── Webnative │ ├── AppInfo.elm │ ├── Auth.elm │ ├── CID.elm │ ├── Capabilities.elm │ ├── Configuration.elm │ ├── Error.elm │ ├── FileSystem.elm │ ├── Internal.elm │ ├── Namespace.elm │ ├── Path.elm │ ├── Path │ │ └── Encapsulated.elm │ ├── Permissions.elm │ ├── Program.elm │ ├── Session.elm │ └── Task.elm └── index.ts ├── test-envs ├── commonjs │ ├── index.js │ └── package.json ├── example │ ├── elm.json │ ├── index.html │ ├── package.json │ └── src │ │ ├── Main.elm │ │ ├── Ports.elm │ │ └── index.js └── umd │ └── index.html └── tsconfig.json /.envrc: -------------------------------------------------------------------------------- 1 | use flake -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## 1. Purpose 4 | 5 | A primary goal of `FISSON` is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). 6 | 7 | This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. 8 | 9 | We invite all those who participate in `FISSION` to help us create safe and positive experiences for everyone. 10 | 11 | ## 2. Open Source Citizenship 12 | 13 | A supplemental goal of this Code of Conduct is to increase open source citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. 14 | 15 | Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. 16 | 17 | If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. 18 | 19 | ## 3. Expected Behavior 20 | 21 | The following behaviors are expected and requested of all community members: 22 | 23 | * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. 24 | * Exercise consideration and respect in your speech and actions. 25 | * Attempt collaboration before conflict. 26 | * Refrain from demeaning, discriminatory, or harassing behavior and speech. 27 | * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. 28 | * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. 29 | 30 | ## 4. Unacceptable Behavior 31 | 32 | The following behaviors are considered harassment and are unacceptable within our community: 33 | 34 | * Violence, threats of violence or violent language directed against another person. 35 | * Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. 36 | * Posting or displaying sexually explicit or violent material. 37 | * Posting or threatening to post other people’s personally identifying information ("doxing"). 38 | * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. 39 | * Inappropriate photography or recording. 40 | * Inappropriate physical contact. You should have someone’s consent before touching them. 41 | * Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. 42 | * Deliberate intimidation, stalking or following (online or in person). 43 | * Advocating for, or encouraging, any of the above behavior. 44 | * Sustained disruption of community events, including talks and presentations. 45 | 46 | ## 5. Consequences of Unacceptable Behavior 47 | 48 | Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. 49 | 50 | Anyone asked to stop unacceptable behavior is expected to comply immediately. 51 | 52 | If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). 53 | 54 | ## 6. Reporting Guidelines 55 | 56 | If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. hello@brooklynzelenka.com. 57 | 58 | 59 | 60 | Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. 61 | 62 | ## 7. Addressing Grievances 63 | 64 | If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify the maintainers with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. 65 | 66 | 67 | 68 | ## 8. Scope 69 | 70 | We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues–online and in-person–as well as in all one-on-one communications pertaining to community business. 71 | 72 | This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. 73 | 74 | ## 9. Contact info 75 | 76 | hello@fission.codes 77 | 78 | ## 10. License and attribution 79 | 80 | This Code of Conduct is distributed under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). 81 | 82 | Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). 83 | 84 | Retrieved on November 22, 2016 from [http://citizencodeofconduct.org/](http://citizencodeofconduct.org/) 85 | 86 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: "\U0001F41B bug" 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Summary 11 | 12 | ## Problem 13 | 14 | Describe the immediate problem 15 | 16 | ### Impact 17 | 18 | The impact that this bug has 19 | 20 | ## Solution 21 | 22 | Describe the sort of fix that would solve the issue 23 | 24 | # Detail 25 | 26 | **Describe the bug** 27 | A clear and concise description of what the bug is. 28 | 29 | **To Reproduce** 30 | Steps to reproduce the behavior: 31 | 1. Go to '...' 32 | 2. Click on '....' 33 | 3. Scroll down to '....' 34 | 4. See error 35 | 36 | **Expected behavior** 37 | A clear and concise description of what you expected to happen. 38 | 39 | **Screenshots** 40 | If applicable, add screenshots to help explain your problem. 41 | 42 | **Desktop (please complete the following information):** 43 | - OS: [e.g. iOS] 44 | - Browser [e.g. chrome, safari] 45 | - Version [e.g. 22] 46 | 47 | **Smartphone (please complete the following information):** 48 | - Device: [e.g. iPhone6] 49 | - OS: [e.g. iOS8.1] 50 | - Browser [e.g. stock browser, safari] 51 | - Version [e.g. 22] 52 | 53 | **Additional context** 54 | Add any other context about the problem here. 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: "\U0001F497 enhancement" 6 | assignees: '' 7 | 8 | --- 9 | 10 | NB: Feature requests will only be considered if they solve a pain 11 | 12 | # Summary 13 | 14 | ## Problem 15 | 16 | Describe the pain that this feature will solve 17 | 18 | ### Impact 19 | 20 | The impact of not having this feature 21 | 22 | ## Solution 23 | 24 | Describe the solution 25 | 26 | # Detail 27 | 28 | **Is your feature request related to a problem? Please describe.** 29 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 30 | 31 | **Describe the solution you'd like** 32 | A clear and concise description of what you want to happen. 33 | 34 | **Describe alternatives you've considered** 35 | A clear and concise description of any alternative solutions or features you've considered. 36 | 37 | **Additional context** 38 | Add any other context or screenshots about the feature request here. 39 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | A similar PR may already be submitted! 2 | Please search among the [Pull request](../) before creating one. 3 | 4 | Thanks for submitting a pull request! Please provide enough information so that others can review your pull request: 5 | 6 | For more information, see the `CONTRIBUTING` guide. 7 | 8 | 9 | ## Summary 10 | 11 | 12 | This PR fixes/implements the following **bugs/features** 13 | 14 | * [ ] Bug 1 15 | * [ ] Bug 2 16 | * [ ] Feature 1 17 | * [ ] Feature 2 18 | * [ ] Breaking changes 19 | 20 | 21 | 22 | Explain the **motivation** for making this change. What existing problem does the pull request solve? 23 | 24 | 25 | 26 | ## Test plan (required) 27 | 28 | Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. 29 | 30 | 31 | 32 | 33 | ## Closing issues 34 | 35 | 36 | Fixes # 37 | 38 | ## After Merge 39 | * [ ] Does this change invalidate any docs or tutorials? _If so ensure the changes needed are either made or recorded_ 40 | * [ ] Does this change require a release to be made? Is so please create and deploy the release 41 | 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .direnv 2 | /elm-stuff 3 | /dist 4 | /lib 5 | /node_modules 6 | /test-envs/commonjs/dist/*.js 7 | /test-envs/example/dist 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 3.0.0 Elm / 8.1.0 NPM 4 | 5 | - Removed `Webnative.FileSystem.add` 6 | - Removed `Webnative.FileSystem.cat` 7 | - Updated to Webnative v0.36 8 | 9 | 10 | ### 2.0.0 Elm / 8.0.0 NPM 11 | 12 | Complete rewrite for Webnative v0.35 13 | 14 | 15 | --- 16 | 17 | 18 | ### 7.0.0 19 | 20 | - **Elm**: Fixes issue with decoding the response of `Webnative.leave` and `Webnative.signOut`. 21 | - **Js**: Support CID class instances from the multiformats library, which are converted to strings. 22 | 23 | 24 | ### 6.1.0 25 | 26 | - Adds `Webnative.leave` and `Webnative.signOut` functions 27 | - Fixes issue with loading the filesystem manually. 28 | - Exposes `Webnative.context` and `Wnfs.context`, 29 | so you can more easily build your own `Webnative.Request`s. 30 | 31 | 32 | ### 6.0.0 33 | 34 | __⚠️ Requires webnative 0.24 or later!__ 35 | 36 | - New `Path` type, along with a new `Path` module and helper functions. 37 | - Javascript function params are objects 38 | - Allow setting custom webnative factory 39 | 40 | 41 | ### 5.0.0 42 | 43 | - Fixed typo `loadFilesystem` should be `loadFileSystem` 44 | - Export `defaultInitOptions` 45 | - Show error if webnative failed to load 46 | - Return the `getFs` and `portNames` used in the `setup` and `request` javascript functions. 47 | 48 | 49 | ### 4.0.0-3 (NPM) 50 | 51 | - Don't try to send response to Elm if the port is not defined. 52 | - Rename `processRequest` to `request`. 53 | 54 | 55 | ### 4.0.0-2 (NPM) 56 | 57 | - Expose `processRequest` on the javascript side, in case you want to set up the incoming port yourself. 58 | 59 | 60 | ### 4.0.0 61 | 62 | - Improved the return value of `Webnative.decodeResponse` 63 | - Forgot to expose the new `Error` type 😅 64 | 65 | 66 | ### 3.0.0 67 | 68 | - Fixed issue with `publish` producing an error in `Webnative.decodeResponse` 69 | - Replaced `Maybe Context` with `Context` in the errors returned from `Webnative.decodeResponse` 70 | - Added an `Error` type along side the error message 71 | - Improved documentation 72 | 73 | 74 | ### 2.1.0 75 | 76 | - Expose `Wnfs.Directory` module 77 | - Improve documentation 78 | 79 | 80 | ### 2.0.0 81 | 82 | - **Ability to initialise webnative via Elm** 83 | - **Only one pair of ports instead of two** 84 | - Now works if the ports aren't present (eg. because of Elm dead code elimination) 85 | - Error handling 86 | - Added an example to the tests 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Built by FISSION](https://img.shields.io/badge/⌘-Built_by_FISSION-purple.svg)](https://fission.codes) 4 | [![Discord](https://img.shields.io/discord/478735028319158273.svg)](https://discord.gg/zAQBDEq) 5 | [![Discourse](https://img.shields.io/discourse/https/talk.fission.codes/topics)](https://talk.fission.codes) 6 | 7 | **A thin wrapper around [Webnative](https://github.com/fission-codes/webnative#readme) for Elm.** 8 | 9 | The Webnative SDK empowers developers to build fully distributed web applications without needing a complex back-end. The SDK provides: 10 | 11 | - **User accounts** via the browser's [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) or by using a blockchain wallet as a [webnative plugin](https://github.com/fission-codes/webnative-walletauth). 12 | - **Authorization** using [UCAN](https://ucan.xyz/). 13 | - **Encrypted file storage** via the [Webnative File System](https://guide.fission.codes/developers/webnative/file-system-wnfs) backed by [IPLD](https://ipld.io/). 14 | - **Key management** via websockets and a two-factor auth-like flow. 15 | 16 | Webnative applications work offline and store data encrypted for the user by leveraging the power of the web platform. You can read more about Webnative in Fission's [Webnative Guide](https://guide.fission.codes/developers/webnative). There's also an API reference which can be found at [webnative.fission.app](https://webnative.fission.app) 17 | 18 | 19 | 20 | # QuickStart 21 | 22 | ```shell 23 | elm install fission-codes/webnative-elm 24 | 25 | # requires webnative version 0.36 or later 26 | npm install webnative 27 | npm install webnative-elm 28 | ``` 29 | 30 | Then import the javascript portion of this library and elm-taskport. 31 | We'll need to initialise both of these. 32 | 33 | ```js 34 | import * as TaskPort from "elm-taskport" 35 | import * as WebnativeElm from "webnative-elm" 36 | 37 | TaskPort.install() 38 | WebnativeElm.init({ TaskPort }) 39 | 40 | // elmApp = Elm.Main.init() 41 | ``` 42 | 43 | Once we have that setup, we can write our Webnative Elm code. The following is an entire Webnative app which creates or links a user account, manages user sessions and their file system, and writes to and reads from that file system. 44 | 45 | ```elm 46 | import Task 47 | import Webnative 48 | import Webnative.Auth 49 | import Webnative.Configuration 50 | import Webnative.Error exposing (Error) 51 | import Webnative.FileSystem exposing (Base(..), FileSystem) 52 | import Webnative.Namespace 53 | import Webnative.Path as Path 54 | import Webnative.Program exposing (Program) 55 | import Webnative.Session exposing (Session) 56 | 57 | 58 | -- INIT 59 | 60 | 61 | appInfo : Webnative.AppInfo 62 | appInfo = 63 | { creator = "Webnative", name = "Example" } 64 | 65 | 66 | config : Webnative.Configuration 67 | config = 68 | appInfo 69 | |> Webnative.Namespace.fromAppInfo 70 | |> Webnative.Configuration.fromNamespace 71 | 72 | 73 | type Model 74 | = Unprepared 75 | | NotAuthenticated Program 76 | | Authenticated Program Session FileSystem 77 | 78 | 79 | init : (Model, Cmd Msg) 80 | init = 81 | ( Unprepared 82 | , -- 🚀 83 | config 84 | |> Webnative.program 85 | |> Webnative.attemptTask 86 | { ok = Liftoff 87 | , err = HandleWebnativeError 88 | } 89 | ) 90 | 91 | 92 | 93 | -- UPDATE 94 | 95 | 96 | type Msg 97 | = HandleWebnativeError Error 98 | | GotFileContents String 99 | | GotSession Session 100 | | Liftoff Foundation 101 | | RegisterUser { success : Bool } 102 | 103 | 104 | update : Msg -> Model -> (Model, Cmd Msg) 105 | update msg model = 106 | case msg of 107 | ----------------------------------------- 108 | -- 🚀 109 | ----------------------------------------- 110 | Liftoff foundation -> 111 | let 112 | newModel = 113 | -- Previous authenticated session? 114 | -- Presence of a FileSystem depends on your configuration. 115 | case (foundation.fileSystem, foundation.session) of 116 | (Just fs, Just session) -> Authenticated program session fs 117 | _ -> NotAuthenticated program 118 | in 119 | ( newModel 120 | 121 | -- Next action 122 | -------------- 123 | , case newModel of 124 | NotAuthenticated program -> 125 | -- Option (A), register a new account. 126 | -- We're skipping the username validation and 127 | -- username availability checking here to keep it short. 128 | { email = Nothing 129 | , username = Just "user" 130 | } 131 | |> Webnative.Auth.register program 132 | |> Webnative.attemptTask 133 | { ok = RegisterUser 134 | , error = HandleWebnativeError 135 | } 136 | 137 | -- Option (B), link an existing account. 138 | -- See 'Linking' section below. 139 | 140 | _ -> 141 | Cmd.none 142 | ) 143 | 144 | ----------------------------------------- 145 | -- 🙋 146 | ----------------------------------------- 147 | RegisterUser { success } -> 148 | if success then 149 | ( model 150 | , program 151 | |> Webnative.Auth.sessionWithFileSystem 152 | |> Webnative.attemptTask 153 | { ok = RegisterUser 154 | , error = HandleWebnativeError 155 | } 156 | ) 157 | else 158 | -- Could show message in create-account form. 159 | (model, Cmd.none) 160 | 161 | GotSessionAndFileSystem (Just { fileSystem, session }) -> 162 | ( -- Authenticated 163 | case model of 164 | NotAuthenticated program -> Authenticated program session fileSystem 165 | _ -> model 166 | 167 | -- Next action 168 | -------------- 169 | , let 170 | path = 171 | Path.file [ "Sub Directory", "hello.txt" ] 172 | in 173 | "👋" 174 | |> Webnative.FileSystem.writeUtf8 fileSystem Private path 175 | |> Task.andThen (\_ -> Webnative.FileSystem.publish fileSystem) 176 | |> Task.andThen (\_ -> Webnative.FileSystem.readUtf8 fileSystem Private path) 177 | |> Webnative.attemptTask 178 | { ok = GotFileContents 179 | , error = HandleWebnativeError 180 | } 181 | ) 182 | 183 | ----------------------------------------- 184 | -- 💾 185 | ----------------------------------------- 186 | GotFileContents string -> ... 187 | 188 | ----------------------------------------- 189 | -- 🥵 190 | ----------------------------------------- 191 | HandleWebnativeError UnsupportedBrowser -> -- No indexedDB? Depends on how Webnative is configured. 192 | HandleWebnativeError InsecureContext -> -- Webnative requires a secure context 193 | HandleWebnativeError (JavascriptError string) -> -- Notification.push ("Got JS error: " ++ string) 194 | ``` 195 | 196 | 197 | 198 | # Linking 199 | 200 | When a user has already registered an account, they can link a device instead. 201 | 202 | ```elm 203 | -- TODO: Yet to be implemented 204 | ``` 205 | 206 | 207 | 208 | # Filesystem 209 | 210 | Alternatively you can load the filesystem separately. 211 | You may want to do this when working with a web worker. 212 | 213 | ```elm 214 | import Webnative 215 | 216 | config = 217 | { namespace = ... 218 | 219 | -- 220 | , debug = Nothing 221 | , fileSystem = Just { loadImmediately = Just True, version = Nothing } 222 | , permissions = Nothing 223 | } 224 | 225 | Webnative.program config 226 | ``` 227 | 228 | And then load it either in Elm or in javascript. 229 | 230 | ```elm 231 | Webnative.FileSystem.load program { username = "username" } 232 | ``` 233 | 234 | ```js 235 | const fs = await program.loadFileSystem("username") 236 | webnativeElm.init({ fileSystems: [ fs ] }) 237 | ``` 238 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | [{"name":"Webnative","comment":" Interface for [webnative](https://github.com/fission-suite/webnative#readme), version `0.24` and up.\n\n1. [Getting Started](#getting-started)\n2. [Ports](#ports)\n3. [Authorisation](#authorisation)\n4. [Authentication](#authentication)\n5. [Filesystem](#filesystem)\n6. [Miscellaneous](#miscellaneous)\n\n\n# Getting Started\n\n@docs init, initWithOptions, InitOptions, defaultInitOptions, initialise, initialize\n\n\n# Ports\n\nData flowing through the ports. See `🚀` in the `decodeResponse` example on how to handle the result from `init`.\n\n@docs decodeResponse, DecodedResponse, Artifact, NoArtifact, Request, Response, Error, error, context\n\n\n# Authorisation\n\n@docs redirectToLobby, RedirectTo, AppPermissions, FileSystemPermissions, BranchFileSystemPermissions, Permissions\n\n\n# Authentication\n\n@docs isAuthenticated, leave, signOut, State, AuthSucceededState, AuthCancelledState, ContinuationState\n\n\n# Filesystem\n\n@docs loadFileSystem\n\n","unions":[{"name":"Artifact","comment":" Artifact we receive in the response.\n","args":[],"cases":[["Initialisation",["Webnative.State"]],["NoArtifact",["Webnative.NoArtifact"]]]},{"name":"DecodedResponse","comment":" Request, or response, context.\n","args":["tag"],"cases":[["Webnative",["Webnative.Artifact"]],["WebnativeError",["Webnative.Error"]],["Wnfs",["tag","Wnfs.Artifact"]],["WnfsError",["Wnfs.Error"]]]},{"name":"Error","comment":" Possible errors.\n","args":[],"cases":[["DecodingError",["String.String"]],["InvalidMethod",["String.String"]],["InsecureContext",[]],["JavascriptError",["String.String"]],["UnsupportedBrowser",[]]]},{"name":"NoArtifact","comment":" Not really artifacts, but kind of.\nPart of the `Artifact` type.\n","args":[],"cases":[["LoadedFileSystemManually",[]],["RedirectingToLobby",[]],["SignedOut",[]]]},{"name":"RedirectTo","comment":" Where the authorisation lobby should redirect to after authorisation.\n","args":[],"cases":[["CurrentUrl",[]],["RedirectTo",["Url.Url"]]]},{"name":"State","comment":" Initialisation state, result from `init`.\n","args":[],"cases":[["NotAuthorised",[]],["AuthSucceeded",["Webnative.AuthSucceededState"]],["AuthCancelled",["Webnative.AuthCancelledState"]],["Continuation",["Webnative.ContinuationState"]]]}],"aliases":[{"name":"AppPermissions","comment":" Application permissions.\n","args":[],"type":"{ creator : String.String, name : String.String }"},{"name":"AuthCancelledState","comment":" State attributes when auth was cancelled.\n","args":[],"type":"{ cancellationReason : String.String, throughLobby : Basics.Bool }"},{"name":"AuthSucceededState","comment":" State attributes when auth has succeeded.\n","args":[],"type":"{ newUser : Basics.Bool, throughLobby : Basics.Bool, username : String.String }"},{"name":"BranchFileSystemPermissions","comment":" Filesystem permissions for a branch.\n\nThis is reused for the private and public permissions.\n\n","args":[],"type":"{ directories : List.List (Webnative.Path.Path Webnative.Path.Directory), files : List.List (Webnative.Path.Path Webnative.Path.File) }"},{"name":"ContinuationState","comment":" State attributes when continueing a session.\n","args":[],"type":"{ newUser : Basics.Bool, throughLobby : Basics.Bool, username : String.String }"},{"name":"FileSystemPermissions","comment":" Filesystem permissions.\n\n ```elm\n import Webnative.Path as Path\n\n { private =\n { directories = [ Path.directory [ \"Audio\", \"Mixtapes\" ] ]\n , files = [ Path.file [ \"Audio\", \"Playlists\", \"Jazz.json\" ] ]\n }\n , public =\n { directories = []\n , files = []\n }\n }\n ```\n\n","args":[],"type":"{ private : Webnative.BranchFileSystemPermissions, public : Webnative.BranchFileSystemPermissions }"},{"name":"InitOptions","comment":" Options for `initWithOptions`.\n\nSetting `autoRemoveUrlParams` to `True` causes webnative to automatically remove the query parameters from the lobby.\n\nSetting `loadFileSystem` to `False` disables the automatic loading of the filesystem during `init`.\n\n","args":[],"type":"{ autoRemoveUrlParams : Basics.Bool, loadFileSystem : Basics.Bool }"},{"name":"Permissions","comment":" Permissions to ask the user.\nSee [`AppPermissions`](#AppPermissions) and [`FileSystemPermissions`](#FileSystemPermissions) on how to use these.\n","args":[],"type":"{ app : Maybe.Maybe Webnative.AppPermissions, fs : Maybe.Maybe Webnative.FileSystemPermissions }"},{"name":"Request","comment":" Request from webnative.\n","args":[],"type":"{ context : String.String, tag : String.String, method : String.String, arguments : List.List Json.Encode.Value }"},{"name":"Response","comment":" Response from webnative.\n","args":[],"type":"{ context : String.String, error : Maybe.Maybe String.String, tag : String.String, method : String.String, data : Json.Encode.Value }"}],"values":[{"name":"context","comment":" Request/Response context.\n","type":"String.String"},{"name":"decodeResponse","comment":" Decode the result, the `Response`, from our `Request`.\nConnect this up with `webnativeResponse` subscription port.\n\n subscriptions =\n Ports.webnativeResponse GotWebnativeResponse\n\nAnd then in `update` use this function.\n\n GotWebnativeResponse response ->\n case Webnative.decodeResponse tagFromString response of\n -----------------------------------------\n -- 🚀\n -----------------------------------------\n Webnative ( Initialisation state ) ->\n if Webnative.isAuthenticated state then\n loadUserData\n else\n welcome\n\n -----------------------------------------\n -- 💾\n -----------------------------------------\n Wnfs ReadHelloTxt ( Utf8Content helloContents ) ->\n -- Do something with content from hello.txt\n\n Wnfs Mutation _ ->\n ( model\n , { tag = PointerUpdated }\n |> Wnfs.publish\n |> Ports.webnativeRequest\n )\n\n -----------------------------------------\n -- 🥵\n -----------------------------------------\n -- Do something with the errors,\n -- here we cast them to strings\n WebnativeError err -> Webnative.error err\n WnfsError err -> Wnfs.error err\n\nSee the [README](../latest/) for the full example.\n\n","type":"(String.String -> Result.Result String.String tag) -> Webnative.Response -> Webnative.DecodedResponse tag"},{"name":"defaultInitOptions","comment":" Default `InitOptions`.\n","type":"Webnative.InitOptions"},{"name":"error","comment":" `Error` message.\n","type":"Webnative.Error -> String.String"},{"name":"init","comment":" 🚀 **Start here**\n\nCheck if we're authenticated, process any lobby query-parameters present in the URL, and initiate the user's filesystem if authenticated (can be disabled using `initWithOptions`).\n\nSee `loadFileSystem` if you want to load the user's filesystem yourself.\n**NOTE**, this only works on the main/ui thread, as it uses `window.location`.\n\nSee the [README](../latest/) for an example.\n\n","type":"Webnative.Permissions -> Webnative.Request"},{"name":"initWithOptions","comment":" Initialise webnative, with options.\n","type":"Webnative.InitOptions -> Webnative.Permissions -> Webnative.Request"},{"name":"initialise","comment":" Alias for `init`.\n","type":"Webnative.Permissions -> Webnative.Request"},{"name":"initialize","comment":" Alias for `init`.\n","type":"Webnative.Permissions -> Webnative.Request"},{"name":"isAuthenticated","comment":" Are we authenticated?\n","type":"Webnative.State -> Basics.Bool"},{"name":"leave","comment":" Leave the app and go the lobby.\nRemoves all traces of the user.\nUse `signOut` instead if you don't want the redirect.\n","type":"Webnative.Request"},{"name":"loadFileSystem","comment":" Load in the filesystem manually.\n","type":"Webnative.Permissions -> Webnative.Request"},{"name":"redirectToLobby","comment":" Redirect to the authorisation lobby.\n","type":"Webnative.RedirectTo -> Webnative.Permissions -> Webnative.Request"},{"name":"signOut","comment":" Removes all traces of the user.\nUse `leave` instead if you want to\ngo to the lobby immediately so the user\ncan sign out there as well, if needed.\n","type":"Webnative.Request"}],"binops":[]},{"name":"Webnative.Path","comment":"\n\n\n# Paths\n\n@docs Path, Directory, File, Encapsulated, Kind\n\n\n# Creation\n\n@docs directory, file, root\n\n\n# POSIX\n\n@docs fromPosix, toPosix\n\n\n# Encapsulation\n\n@docs encapsulate\n\n\n# Functions\n\n@docs kind, map, unwrap\n\n\n# Miscellaneous\n\n@docs encode\n\n","unions":[{"name":"Directory","comment":" 👻 Directory\n","args":[],"cases":[]},{"name":"Encapsulated","comment":" 👻 Encapsulated\n","args":[],"cases":[]},{"name":"File","comment":" 👻 File\n","args":[],"cases":[]},{"name":"Kind","comment":" ","args":[],"cases":[["Directory",[]],["File",[]]]},{"name":"Path","comment":" Path type.\n\nThis is used with the [phantom 👻 types](#phantom-types).\n\n directoryPath : Path Directory\n\n filePath : Path File\n\n encapsulatedPath : Path Encapsulated\n\n","args":["t"],"cases":[]}],"aliases":[],"values":[{"name":"directory","comment":" Create a directory path.\n\n directory [ \"Audio\", \"Playlists\" ]\n\n","type":"List.List String.String -> Webnative.Path.Path Webnative.Path.Directory"},{"name":"encapsulate","comment":" Encapsulate a path.\n","type":"Webnative.Path.Path t -> Webnative.Path.Path Webnative.Path.Encapsulated"},{"name":"encode","comment":" Encode to JSON.\n\n >>> import Json.Encode\n\n >>> [ \"foo\" ]\n ..> |> directory\n ..> |> encode\n ..> |> Json.Encode.encode 0\n \"{\\\"directory\\\":[\\\"foo\\\"]}\"\n\n >>> [ \"bar\" ]\n ..> |> file\n ..> |> encode\n ..> |> Json.Encode.encode 0\n \"{\\\"file\\\":[\\\"bar\\\"]}\"\n\n","type":"Webnative.Path.Path t -> Json.Encode.Value"},{"name":"file","comment":" Create a file path.\n\n file [ \"Document\", \"invoice.pdf\" ]\n\n","type":"List.List String.String -> Webnative.Path.Path Webnative.Path.File"},{"name":"fromPosix","comment":" Convert a POSIX formatted string to a path.\n\nThis will return a `Encapsulated` path. To get a path of the type `Path Directory` or `Path File`, use the functions in the `Webnative.Path.Encapsulated` module.\n\n >>> import Webnative.Path.Encapsulated\n\n >>> \"foo/bar/\"\n ..> |> fromPosix\n ..> |> Webnative.Path.Encapsulated.toDirectory\n Just (directory [ \"foo\", \"bar\" ])\n\n >>> \"foo/bar\"\n ..> |> fromPosix\n ..> |> Webnative.Path.Encapsulated.toFile\n Just (file [ \"foo\", \"bar\" ])\n\n","type":"String.String -> Webnative.Path.Path Webnative.Path.Encapsulated"},{"name":"kind","comment":" Get the path kind.\n\n >>> kind (directory [])\n Directory\n\n >>> kind (file [])\n File\n\n","type":"Webnative.Path.Path t -> Webnative.Path.Kind"},{"name":"map","comment":" Map.\n","type":"(List.List String.String -> List.List String.String) -> Webnative.Path.Path t -> Webnative.Path.Path t"},{"name":"root","comment":" Root directory.\n","type":"Webnative.Path.Path Webnative.Path.Directory"},{"name":"toPosix","comment":" Convert a path to the POSIX format.\n\n >>> toPosix (directory [ \"foo\", \"bar\"])\n \"foo/bar/\"\n\n >>> toPosix (file [ \"foo\", \"bar\"])\n \"foo/bar\"\n\n","type":"Webnative.Path.Path t -> String.String"},{"name":"unwrap","comment":" Get the path parts.\n\n >>> unwrap (directory [ \"foo\", \"bar\" ])\n [ \"foo\", \"bar\" ]\n\n >>> unwrap (file [ \"foo\", \"bar\" ])\n [ \"foo\", \"bar\" ]\n\n","type":"Webnative.Path.Path t -> List.List String.String"}],"binops":[]},{"name":"Webnative.Path.Encapsulated","comment":"\n\n\n# Encapsulated Paths\n\n@docs toDirectory, toFile\n\n","unions":[],"aliases":[],"values":[{"name":"toDirectory","comment":" Remove the membrane and extract a `Path Directory`.\n","type":"Webnative.Path.Path Webnative.Path.Encapsulated -> Maybe.Maybe (Webnative.Path.Path Webnative.Path.Directory)"},{"name":"toFile","comment":" Remove the membrane and extract a `Path File`.\n","type":"Webnative.Path.Path Webnative.Path.Encapsulated -> Maybe.Maybe (Webnative.Path.Path Webnative.Path.File)"}],"binops":[]},{"name":"Wnfs","comment":" Interact with your webnative [filesystem](https://guide.fission.codes/developers/webnative#file-system).\n\n\n# Actions\n\n@docs publish\n\n\n## Mutations\n\n@docs mkdir, mv, rm, write, writeUtf8\n\n\n## Queries\n\n@docs exists, ls, read, readUtf8\n\n\n## Aliases\n\n@docs add, cat\n\n\n# Requests & Responses\n\n@docs Base, Attributes, Artifact, Entry, context\n\n\n# Errors\n\n@docs Error, error\n\n","unions":[{"name":"Artifact","comment":" Artifact we receive in the response.\n","args":[],"cases":[["NoArtifact",[]],["Boolean",["Basics.Bool"]],["CID",["String.String"]],["DirectoryContent",["List.List Wnfs.Entry"]],["FileContent",["Bytes.Bytes"]],["Utf8Content",["String.String"]]]},{"name":"Base","comment":" Base of the WNFS action.\n","args":[],"cases":[["AppData",["Wnfs.AppPermissions"]],["Private",[]],["Public",[]]]},{"name":"Error","comment":" Possible errors.\n","args":[],"cases":[["DecodingError",["String.String"]],["InvalidMethod",["String.String"]],["TagParsingError",["String.String"]],["JavascriptError",["String.String"]]]}],"aliases":[{"name":"Attributes","comment":" WNFS action attributes.\n","args":["pathKind"],"type":"{ path : Webnative.Path.Path pathKind, tag : String.String }"},{"name":"Entry","comment":" Directory `Entry`.\n","args":[],"type":"{ cid : String.String, name : String.String, kind : Webnative.Path.Kind, size : Basics.Int }"}],"values":[{"name":"add","comment":" Alias for `write`.\n","type":"Wnfs.Base -> Wnfs.Attributes Webnative.Path.File -> Bytes.Bytes -> Wnfs.Request"},{"name":"cat","comment":" Alias for `read`.\n","type":"Wnfs.Base -> Wnfs.Attributes Webnative.Path.File -> Wnfs.Request"},{"name":"context","comment":" Request/Response context.\n","type":"String.String"},{"name":"error","comment":" `Error` message.\n","type":"Wnfs.Error -> String.String"},{"name":"exists","comment":" Check if something exists.\n","type":"Wnfs.Base -> Wnfs.Attributes a -> Wnfs.Request"},{"name":"ls","comment":" List a directory.\n","type":"Wnfs.Base -> Wnfs.Attributes Webnative.Path.Directory -> Wnfs.Request"},{"name":"mkdir","comment":" Create a directory.\n","type":"Wnfs.Base -> Wnfs.Attributes Webnative.Path.Directory -> Wnfs.Request"},{"name":"mv","comment":" Move something from one location to another.\n","type":"Wnfs.Base -> { from : Webnative.Path.Path t, to : Webnative.Path.Path t, tag : String.String } -> Wnfs.Request"},{"name":"publish","comment":" Publish your changes to your filesystem.\n**📢 You should run this after doing mutations.**\nSee [README](../latest/) examples for more info.\n","type":"{ tag : String.String } -> Wnfs.Request"},{"name":"read","comment":" Read a file from the filesystem in the form of `Bytes`.\n","type":"Wnfs.Base -> Wnfs.Attributes Webnative.Path.File -> Wnfs.Request"},{"name":"readUtf8","comment":" Read a file from the filesystem in the form of a `String`.\n","type":"Wnfs.Base -> Wnfs.Attributes Webnative.Path.File -> Wnfs.Request"},{"name":"rm","comment":" Remove something from the filesystem.\n","type":"Wnfs.Base -> Wnfs.Attributes a -> Wnfs.Request"},{"name":"write","comment":" Write to a file using `Bytes`.\n","type":"Wnfs.Base -> Wnfs.Attributes Webnative.Path.File -> Bytes.Bytes -> Wnfs.Request"},{"name":"writeUtf8","comment":" Write to a file using a `String`.\n","type":"Wnfs.Base -> Wnfs.Attributes Webnative.Path.File -> String.String -> Wnfs.Request"}],"binops":[]}] -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "fission-codes/webnative-elm", 4 | "summary": "Thin wrapper around webnative for Elm", 5 | "license": "Apache-2.0", 6 | "version": "3.0.0", 7 | "exposed-modules": [ 8 | "Webnative", 9 | "Webnative.AppInfo", 10 | "Webnative.Auth", 11 | "Webnative.Capabilities", 12 | "Webnative.CID", 13 | "Webnative.Configuration", 14 | "Webnative.Error", 15 | "Webnative.FileSystem", 16 | "Webnative.Namespace", 17 | "Webnative.Path", 18 | "Webnative.Path.Encapsulated", 19 | "Webnative.Permissions", 20 | "Webnative.Program", 21 | "Webnative.Session", 22 | "Webnative.Task" 23 | ], 24 | "elm-version": "0.19.0 <= v < 0.20.0", 25 | "dependencies": { 26 | "TSFoster/elm-bytes-extra": "1.3.0 <= v < 2.0.0", 27 | "elm/bytes": "1.0.8 <= v < 2.0.0", 28 | "elm/core": "1.0.0 <= v < 2.0.0", 29 | "elm/json": "1.1.3 <= v < 2.0.0", 30 | "elm/url": "1.0.0 <= v < 2.0.0", 31 | "elm-community/maybe-extra": "5.2.0 <= v < 6.0.0", 32 | "lobanov/elm-taskport": "2.0.1 <= v < 3.0.0", 33 | "zwilias/elm-utf-tools": "2.0.1 <= v < 3.0.0" 34 | }, 35 | "test-dependencies": {} 36 | } 37 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "locked": { 5 | "lastModified": 1667395993, 6 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", 7 | "owner": "numtide", 8 | "repo": "flake-utils", 9 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "flake-utils", 15 | "type": "github" 16 | } 17 | }, 18 | "nixpkgs": { 19 | "locked": { 20 | "lastModified": 1669140675, 21 | "narHash": "sha256-npzfyfLECsJWgzK/M4gWhykP2DNAJTYjgY2BWkz/oEQ=", 22 | "owner": "NixOS", 23 | "repo": "nixpkgs", 24 | "rev": "2788904d26dda6cfa1921c5abb7a2466ffe3cb8c", 25 | "type": "github" 26 | }, 27 | "original": { 28 | "owner": "NixOS", 29 | "ref": "nixos-unstable", 30 | "repo": "nixpkgs", 31 | "type": "github" 32 | } 33 | }, 34 | "root": { 35 | "inputs": { 36 | "flake-utils": "flake-utils", 37 | "nixpkgs": "nixpkgs" 38 | } 39 | } 40 | }, 41 | "root": "root", 42 | "version": 7 43 | } 44 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "fission-codes/webnative-elm"; 3 | 4 | 5 | # Inputs 6 | # ====== 7 | 8 | inputs = { 9 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 10 | flake-utils.url = "github:numtide/flake-utils"; 11 | }; 12 | 13 | 14 | # Outputs 15 | # ======= 16 | 17 | outputs = { self, nixpkgs, flake-utils }: 18 | flake-utils.lib.simpleFlake { 19 | inherit self nixpkgs; 20 | name = "fission-codes/webnative-elm"; 21 | shell = ./nix/shell.nix; 22 | }; 23 | } -------------------------------------------------------------------------------- /nix/shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: with pkgs; mkShell { 2 | 3 | buildInputs = [ 4 | elmPackages.elm 5 | elmPackages.elm-format 6 | elmPackages.elm-test 7 | nodejs-18_x 8 | ]; 9 | 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webnative-elm", 3 | "description": "Thin wrapper around webnative for Elm", 4 | "version": "8.1.0", 5 | "author": "Steven Vandevelde ", 6 | "type": "module", 7 | "main": "lib/index.js", 8 | "exports": { 9 | ".": "./lib/index.js", 10 | "./lib/*": [ 11 | "./lib/*", 12 | "./lib/*.js", 13 | "./lib/*/index.js" 14 | ], 15 | "./*": [ 16 | "./lib/*", 17 | "./lib/*.js", 18 | "./lib/*/index.js", 19 | "./*" 20 | ], 21 | "./package.json": "./package.json" 22 | }, 23 | "types": "lib/index.d.ts", 24 | "typesVersions": { 25 | "*": { 26 | "lib/index.d.ts": [ 27 | "lib/index.d.ts" 28 | ], 29 | "*": [ 30 | "lib/*" 31 | ] 32 | } 33 | }, 34 | "scripts": { 35 | "build:js": "rimraf lib dist && tsc && node ./scripts/build-minified.js", 36 | "prepare": "npm run build:js", 37 | "publish-dry": "npm publish --dry-run", 38 | "publish-alpha": "npm publish --tag alpha", 39 | "publish-latest": "npm publish --tag latest" 40 | }, 41 | "peerDependencies": { 42 | "elm-taskport": ">=0.2.0", 43 | "webnative": ">0.35.0" 44 | }, 45 | "devDependencies": { 46 | "elm-taskport": "^2.0.1", 47 | "esbuild": "^0.15.18", 48 | "events": "^3.3.0", 49 | "rimraf": "^3.0.2", 50 | "typescript": "^4.9.3", 51 | "webnative": "^0.36.0-alpha-3" 52 | }, 53 | "files": [ 54 | "dist", 55 | "lib", 56 | "CHANGELOG.md", 57 | "LICENSE", 58 | "README.md", 59 | "package.json" 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /scripts/build-minified.js: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild" 2 | import fs from "fs" 3 | 4 | 5 | const globalName = "webnativeElm" 6 | 7 | 8 | console.log("📦 Bundling & minifying...") 9 | 10 | await esbuild.build({ 11 | entryPoints: [ "src/index.ts" ], 12 | outdir: "dist", 13 | bundle: true, 14 | splitting: true, 15 | minify: true, 16 | sourcemap: true, 17 | platform: "browser", 18 | format: "esm", 19 | target: "es2020", 20 | globalName, 21 | define: { 22 | "global": "globalThis", 23 | "globalThis.process.env.NODE_ENV": "production" 24 | }, 25 | }) 26 | 27 | 28 | fs.renameSync("dist/index.js", "dist/index.esm.min.js") 29 | fs.renameSync("dist/index.js.map", "dist/index.esm.min.js.map") 30 | 31 | 32 | 33 | // UMD 34 | 35 | 36 | const UMD = { 37 | banner: 38 | `(function (root, factory) { 39 | if (typeof define === 'function' && define.amd) { 40 | // AMD. Register as an anonymous module. 41 | define([], factory); 42 | } else if (typeof module === 'object' && module.exports) { 43 | // Node. Does not work with strict CommonJS, but 44 | // only CommonJS-like environments that support module.exports, 45 | // like Node. 46 | module.exports = factory(); 47 | } else { 48 | // Browser globals (root is window) 49 | root.${globalName} = factory(); 50 | } 51 | }(typeof self !== 'undefined' ? self : this, function () { `, 52 | footer: 53 | `return ${globalName}; 54 | }));` 55 | } 56 | 57 | await esbuild.build({ 58 | entryPoints: [ "src/index.ts" ], 59 | outfile: "dist/index.umd.min.js", 60 | bundle: true, 61 | minify: true, 62 | sourcemap: true, 63 | platform: "browser", 64 | format: "iife", 65 | target: "es2020", 66 | globalName, 67 | define: { 68 | "global": "globalThis", 69 | "globalThis.process.env.NODE_ENV": "production" 70 | }, 71 | banner: { js: UMD.banner }, 72 | footer: { js: UMD.footer }, 73 | }) -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import (fetchTarball { 2 | url = "https://github.com/edolstra/flake-compat/archive/b4a34015c698c7793d592d66adbab377907a2be8.tar.gz"; 3 | }) { src = ./.; }).shellNix -------------------------------------------------------------------------------- /src/Webnative.elm: -------------------------------------------------------------------------------- 1 | module Webnative exposing (Foundation, program, attemptTask) 2 | 3 | {-| 4 | 5 | @docs Foundation, program, attemptTask 6 | 7 | -} 8 | 9 | import Json.Decode 10 | import Webnative.Configuration as Configuration exposing (Configuration) 11 | import Webnative.Error as Webnative 12 | import Webnative.FileSystem as FileSystem exposing (FileSystem) 13 | import Webnative.Internal exposing (callTaskPort) 14 | import Webnative.Program as Program exposing (Program) 15 | import Webnative.Session as Session exposing (Session) 16 | import Webnative.Task as Webnative 17 | 18 | 19 | 20 | -- 🚀 21 | 22 | 23 | {-| -} 24 | type alias Foundation = 25 | { fileSystem : Maybe FileSystem 26 | , program : Program 27 | , session : Maybe Session 28 | } 29 | 30 | 31 | {-| -} 32 | program : Configuration -> Webnative.Task Foundation 33 | program = 34 | callTaskPort 35 | { function = "program" 36 | , valueDecoder = 37 | Json.Decode.map3 38 | (\f p s -> { fileSystem = f, program = p, session = s }) 39 | (Json.Decode.field "fs" <| Json.Decode.maybe FileSystem.decoder) 40 | (Json.Decode.field "program" Program.decoder) 41 | (Json.Decode.field "session" <| Json.Decode.maybe Session.decoder) 42 | , argsEncoder = 43 | Configuration.encode 44 | } 45 | 46 | 47 | 48 | -- 🛠 49 | 50 | 51 | {-| Alias for `Webnative.Task.attempt`. 52 | -} 53 | attemptTask : { error : Webnative.Error -> msg, ok : value -> msg } -> Webnative.Task value -> Cmd msg 54 | attemptTask = 55 | Webnative.attempt 56 | -------------------------------------------------------------------------------- /src/Webnative/AppInfo.elm: -------------------------------------------------------------------------------- 1 | module Webnative.AppInfo exposing (AppInfo, appId, encode) 2 | 3 | {-| 4 | 5 | @docs AppInfo, appId, encode 6 | 7 | -} 8 | 9 | import Json.Encode as Json 10 | 11 | 12 | 13 | -- 🌳 14 | 15 | 16 | {-| -} 17 | type alias AppInfo = 18 | { creator : String, name : String } 19 | 20 | 21 | 22 | -- 🛠 23 | 24 | 25 | {-| -} 26 | appId : AppInfo -> String 27 | appId { creator, name } = 28 | creator ++ "/" ++ name 29 | 30 | 31 | {-| -} 32 | encode : AppInfo -> Json.Value 33 | encode { creator, name } = 34 | Json.object 35 | [ ( "creator", Json.string creator ) 36 | , ( "name", Json.string name ) 37 | ] 38 | -------------------------------------------------------------------------------- /src/Webnative/Auth.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Auth exposing (isUsernameAvailable, isUsernameValid, register, session, sessionWithFileSystem) 2 | 3 | {-| 4 | 5 | @docs isUsernameAvailable, isUsernameValid, register, session, sessionWithFileSystem 6 | 7 | -} 8 | 9 | import Json.Decode 10 | import Json.Encode as Json 11 | import Maybe.Extra as Maybe 12 | import Task 13 | import Webnative.FileSystem as FileSystem exposing (FileSystem) 14 | import Webnative.Internal exposing (callTaskPort) 15 | import Webnative.Program as Program exposing (Program) 16 | import Webnative.Session as Session exposing (Session) 17 | import Webnative.Task exposing (Task) 18 | 19 | 20 | 21 | -- 🛠 22 | 23 | 24 | {-| -} 25 | isUsernameAvailable : Program -> String -> Task Bool 26 | isUsernameAvailable program = 27 | callTaskPort 28 | { function = "auth_isUsernameAvailable" 29 | , valueDecoder = Json.Decode.bool 30 | , argsEncoder = Json.string >> Program.withRef program 31 | } 32 | 33 | 34 | {-| -} 35 | isUsernameValid : Program -> String -> Task Bool 36 | isUsernameValid program = 37 | callTaskPort 38 | { function = "auth_isUsernameValid" 39 | , valueDecoder = Json.Decode.bool 40 | , argsEncoder = Json.string >> Program.withRef program 41 | } 42 | 43 | 44 | {-| -} 45 | register : Program -> { email : Maybe String, username : String } -> Task { success : Bool } 46 | register program = 47 | callTaskPort 48 | { function = "auth_register" 49 | , valueDecoder = 50 | Json.Decode.map 51 | (\s -> { success = s }) 52 | (Json.Decode.field "success" Json.Decode.bool) 53 | , argsEncoder = 54 | \{ email, username } -> 55 | [ ( "email", Maybe.unwrap Json.null Json.string email ) 56 | , ( "username", Json.string username ) 57 | ] 58 | |> Json.object 59 | |> Program.withRef program 60 | } 61 | 62 | 63 | {-| -} 64 | session : Program -> Task (Maybe Session) 65 | session = 66 | callTaskPort 67 | { function = "auth_session" 68 | , valueDecoder = Json.Decode.maybe Session.decoder 69 | , argsEncoder = Program.ref 70 | } 71 | 72 | 73 | {-| -} 74 | sessionWithFileSystem : Program -> Task (Maybe { fileSystem : FileSystem, session : Session }) 75 | sessionWithFileSystem program = 76 | Task.andThen 77 | (\maybe -> 78 | case maybe of 79 | Just ses -> 80 | { username = ses.username } 81 | |> FileSystem.load program 82 | |> Task.map (\fs -> Just { fileSystem = fs, session = ses }) 83 | 84 | Nothing -> 85 | Task.succeed Nothing 86 | ) 87 | (session program) 88 | -------------------------------------------------------------------------------- /src/Webnative/CID.elm: -------------------------------------------------------------------------------- 1 | module Webnative.CID exposing (CID, decoder, fromString, toString) 2 | 3 | {-| 4 | 5 | @docs CID, decoder, fromString, toString 6 | 7 | -} 8 | 9 | import Json.Decode exposing (Decoder) 10 | 11 | 12 | 13 | -- 🌳 14 | 15 | 16 | {-| -} 17 | type CID 18 | = CID String 19 | 20 | 21 | 22 | -- 🛠 23 | 24 | 25 | {-| -} 26 | fromString : String -> CID 27 | fromString = 28 | CID 29 | 30 | 31 | {-| -} 32 | decoder : Decoder CID 33 | decoder = 34 | Json.Decode.map CID Json.Decode.string 35 | 36 | 37 | {-| -} 38 | toString : CID -> String 39 | toString (CID string) = 40 | string 41 | -------------------------------------------------------------------------------- /src/Webnative/Capabilities.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Capabilities exposing (collect, request, session) 2 | 3 | {-| 4 | 5 | @docs collect, request, session 6 | 7 | -} 8 | 9 | import Json.Decode 10 | import TaskPort 11 | import Webnative.Internal exposing (callTaskPort) 12 | import Webnative.Program as Program exposing (Program) 13 | import Webnative.Session as Session exposing (Session) 14 | import Webnative.Task exposing (Task) 15 | 16 | 17 | 18 | -- 🛠 19 | 20 | 21 | {-| -} 22 | collect : Program -> Task { username : Maybe String } 23 | collect = 24 | callTaskPort 25 | { function = "capabilities_collect" 26 | , valueDecoder = 27 | Json.Decode.map 28 | (\u -> { username = u }) 29 | (Json.Decode.maybe Json.Decode.string) 30 | , argsEncoder = 31 | Program.ref 32 | } 33 | 34 | 35 | {-| -} 36 | request : Program -> Task () 37 | request = 38 | callTaskPort 39 | { function = "capabilities_request" 40 | , valueDecoder = TaskPort.ignoreValue 41 | , argsEncoder = Program.ref 42 | } 43 | 44 | 45 | {-| -} 46 | session : Program -> Task (Maybe Session) 47 | session = 48 | callTaskPort 49 | { function = "capabilities_session" 50 | , valueDecoder = Json.Decode.maybe Session.decoder 51 | , argsEncoder = Program.ref 52 | } 53 | -------------------------------------------------------------------------------- /src/Webnative/Configuration.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Configuration exposing (Configuration, FileSystemConfiguration, encode, encodeFileSystemConfiguration, fromNamespace) 2 | 3 | {-| 4 | 5 | @docs Configuration, FileSystemConfiguration, encode, encodeFileSystemConfiguration, fromNamespace 6 | 7 | -} 8 | 9 | import Json.Encode as Json 10 | import Maybe.Extra as Maybe 11 | import Webnative.Namespace as Namespace exposing (Namespace) 12 | import Webnative.Permissions as Permissions exposing (Permissions) 13 | 14 | 15 | 16 | -- 🌳 17 | 18 | 19 | {-| -} 20 | type alias Configuration = 21 | { namespace : Namespace 22 | 23 | -- 24 | , debug : Maybe Bool 25 | , fileSystem : Maybe FileSystemConfiguration 26 | , permissions : Maybe Permissions 27 | } 28 | 29 | 30 | {-| -} 31 | type alias FileSystemConfiguration = 32 | { loadImmediately : Maybe Bool 33 | , version : Maybe String 34 | } 35 | 36 | 37 | 38 | -- 🛠 39 | 40 | 41 | {-| -} 42 | encode : Configuration -> Json.Value 43 | encode { namespace, debug, permissions, fileSystem } = 44 | Json.object 45 | [ ( "namespace", Json.string (Namespace.toString namespace) ) 46 | 47 | -- 48 | , ( "debug", Maybe.unwrap Json.null Json.bool debug ) 49 | , ( "fileSystem", Maybe.unwrap Json.null encodeFileSystemConfiguration fileSystem ) 50 | , ( "permissions", Maybe.unwrap Json.null Permissions.encode permissions ) 51 | ] 52 | 53 | 54 | {-| -} 55 | encodeFileSystemConfiguration : FileSystemConfiguration -> Json.Value 56 | encodeFileSystemConfiguration { loadImmediately, version } = 57 | Json.object 58 | [ ( "loadImmediately", Maybe.unwrap Json.null Json.bool loadImmediately ) 59 | , ( "version", Maybe.unwrap Json.null Json.string version ) 60 | ] 61 | 62 | 63 | {-| -} 64 | fromNamespace : Namespace -> Configuration 65 | fromNamespace namespace = 66 | { namespace = namespace 67 | 68 | -- 69 | , debug = Nothing 70 | , permissions = Nothing 71 | 72 | -- 73 | , fileSystem = Nothing 74 | } 75 | -------------------------------------------------------------------------------- /src/Webnative/Error.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Error exposing 2 | ( decoder, fromString, fromTaskPort 3 | , Error(..) 4 | ) 5 | 6 | {-| 7 | 8 | @docs Error, decoder, fromString, fromTaskPort 9 | 10 | -} 11 | 12 | import Json.Decode exposing (Decoder) 13 | import TaskPort 14 | 15 | 16 | 17 | -- 🌳 18 | 19 | 20 | {-| -} 21 | type Error 22 | = InsecureContext 23 | | JavascriptError String 24 | | UnsupportedBrowser 25 | 26 | 27 | 28 | -- 🛠 29 | 30 | 31 | {-| -} 32 | decoder : Decoder Error 33 | decoder = 34 | Json.Decode.map 35 | fromString 36 | Json.Decode.string 37 | 38 | 39 | {-| -} 40 | fromString : String -> Error 41 | fromString string = 42 | case string of 43 | "INSECURE_CONTEXT" -> 44 | InsecureContext 45 | 46 | "UNSUPPORTED_BROWSER" -> 47 | UnsupportedBrowser 48 | 49 | _ -> 50 | JavascriptError string 51 | 52 | 53 | {-| -} 54 | fromTaskPort : TaskPort.Error -> Error 55 | fromTaskPort error = 56 | case error of 57 | TaskPort.InteropError interopError -> 58 | fromString (TaskPort.interopErrorToString interopError) 59 | 60 | TaskPort.JSError (TaskPort.ErrorValue value) -> 61 | value 62 | |> Json.Decode.decodeValue decoder 63 | |> Result.withDefault (fromString <| TaskPort.errorToString error) 64 | 65 | TaskPort.JSError _ -> 66 | fromString (TaskPort.errorToString error) 67 | -------------------------------------------------------------------------------- /src/Webnative/FileSystem.elm: -------------------------------------------------------------------------------- 1 | module Webnative.FileSystem exposing (AssociatedIdentity, Base(..), Entry, FileSystem, acceptShare, account, deactivate, decoder, directoryEntriesDecoder, encode, exists, historyStep, load, ls, mkdir, mv, publish, read, readUtf8, ref, rm, sharePrivate, symlink, withRef, withRefSplat, write, writeUtf8) 2 | 3 | {-| 4 | 5 | @docs AssociatedIdentity, Base, Entry, FileSystem, acceptShare, account, deactivate, decoder, directoryEntriesDecoder, encode, exists, historyStep, load, ls, mkdir, mv, publish, read, readUtf8, ref, rm, sharePrivate, symlink, withRef, withRefSplat, write, writeUtf8 6 | 7 | -} 8 | 9 | import Bytes exposing (Bytes) 10 | import Bytes.Encode 11 | import Dict 12 | import Json.Decode exposing (Decoder) 13 | import Json.Encode as Json 14 | import TaskPort 15 | import Webnative.AppInfo exposing (AppInfo) 16 | import Webnative.CID as CID exposing (CID) 17 | import Webnative.Internal exposing (callTaskPort, encodeBytes, fileContentDecoder, utf8ContentDecoder) 18 | import Webnative.Path as Path exposing (Directory, File, Kind(..), Path) 19 | import Webnative.Program as Program 20 | import Webnative.Task exposing (Task) 21 | 22 | 23 | 24 | -- 🌳 25 | 26 | 27 | {-| -} 28 | type FileSystem 29 | = FileSystemReference String 30 | 31 | 32 | {-| Base of the WNFS action. 33 | -} 34 | type Base 35 | = AppData AppInfo 36 | | Private 37 | | Public 38 | 39 | 40 | {-| -} 41 | type alias AssociatedIdentity = 42 | { rootDID : String 43 | , username : Maybe String 44 | } 45 | 46 | 47 | {-| Directory `Entry`. 48 | -} 49 | type alias Entry = 50 | { cid : CID 51 | , name : String 52 | , kind : Kind 53 | , size : Int 54 | } 55 | 56 | 57 | 58 | -- LOADING 59 | 60 | 61 | {-| -} 62 | load : Program.Program -> { username : String } -> Task FileSystem 63 | load program = 64 | callTaskPort 65 | { function = "loadFileSystem" 66 | , valueDecoder = decoder 67 | , argsEncoder = (\{ username } -> Json.string username) >> Program.withRef program 68 | } 69 | 70 | 71 | 72 | -- POSIX 73 | 74 | 75 | {-| -} 76 | acceptShare : FileSystem -> { shareId : String, sharedBy : String } -> Task () 77 | acceptShare fs { shareId, sharedBy } = 78 | callTaskPort 79 | { function = "fileSystem_acceptShare" 80 | , valueDecoder = TaskPort.ignoreValue 81 | , argsEncoder = Json.object >> withRef fs 82 | } 83 | [ ( "shareId", Json.string shareId ) 84 | , ( "sharedBy", Json.string sharedBy ) 85 | ] 86 | 87 | 88 | {-| -} 89 | account : FileSystem -> Task AssociatedIdentity 90 | account = 91 | callTaskPort 92 | { function = "fileSystem_account" 93 | , valueDecoder = 94 | Json.Decode.map2 95 | (\r u -> { rootDID = r, username = u }) 96 | (Json.Decode.field "rootDID" Json.Decode.string) 97 | (Json.Decode.maybe <| Json.Decode.field "username" Json.Decode.string) 98 | , argsEncoder = ref 99 | } 100 | 101 | 102 | {-| -} 103 | deactivate : FileSystem -> Task () 104 | deactivate = 105 | callTaskPort 106 | { function = "fileSystem_deactivate" 107 | , valueDecoder = TaskPort.ignoreValue 108 | , argsEncoder = ref 109 | } 110 | 111 | 112 | {-| -} 113 | exists : FileSystem -> Base -> Path File -> Task Bool 114 | exists fs base = 115 | callTaskPort 116 | { function = "fileSystem_exists" 117 | , valueDecoder = Json.Decode.bool 118 | , argsEncoder = encodePath base >> withRef fs 119 | } 120 | 121 | 122 | {-| -} 123 | historyStep : FileSystem -> Task () 124 | historyStep = 125 | callTaskPort 126 | { function = "fileSystem_historyStep" 127 | , valueDecoder = TaskPort.ignoreValue 128 | , argsEncoder = ref 129 | } 130 | 131 | 132 | {-| -} 133 | ls : FileSystem -> Base -> Path Directory -> Task (List Entry) 134 | ls fs base = 135 | callTaskPort 136 | { function = "fileSystem_ls" 137 | , valueDecoder = directoryEntriesDecoder 138 | , argsEncoder = encodePath base >> withRef fs 139 | } 140 | 141 | 142 | {-| -} 143 | mkdir : FileSystem -> Base -> Path Directory -> Task () 144 | mkdir fs base = 145 | callTaskPort 146 | { function = "fileSystem_mkdir" 147 | , valueDecoder = TaskPort.ignoreValue 148 | , argsEncoder = encodePath base >> withRef fs 149 | } 150 | 151 | 152 | {-| -} 153 | mv : FileSystem -> Base -> { from : Path k, to : Path k } -> Task () 154 | mv fs base { from, to } = 155 | callTaskPort 156 | { function = "fileSystem_mv" 157 | , valueDecoder = TaskPort.ignoreValue 158 | , argsEncoder = Json.list identity >> withRefSplat fs 159 | } 160 | [ encodePath base from 161 | , encodePath base to 162 | ] 163 | 164 | 165 | {-| -} 166 | publish : FileSystem -> Task CID 167 | publish = 168 | callTaskPort 169 | { function = "fileSystem_publish" 170 | , valueDecoder = CID.decoder 171 | , argsEncoder = ref 172 | } 173 | 174 | 175 | {-| -} 176 | read : FileSystem -> Base -> Path File -> Task Bytes 177 | read fs base = 178 | callTaskPort 179 | { function = "fileSystem_read" 180 | , valueDecoder = fileContentDecoder 181 | , argsEncoder = encodePath base >> withRef fs 182 | } 183 | 184 | 185 | {-| -} 186 | readUtf8 : FileSystem -> Base -> Path File -> Task String 187 | readUtf8 fs base = 188 | callTaskPort 189 | { function = "fileSystem_read" 190 | , valueDecoder = utf8ContentDecoder 191 | , argsEncoder = encodePath base >> withRef fs 192 | } 193 | 194 | 195 | {-| -} 196 | rm : FileSystem -> Base -> Path k -> Task () 197 | rm fs base = 198 | callTaskPort 199 | { function = "fileSystem_rm" 200 | , valueDecoder = TaskPort.ignoreValue 201 | , argsEncoder = encodePath base >> withRef fs 202 | } 203 | 204 | 205 | {-| -} 206 | sharePrivate : FileSystem -> List (Path k) -> { shareWith : List String } -> Task () 207 | sharePrivate fs paths { shareWith } = 208 | callTaskPort 209 | { function = "fileSystem_sharePrivate" 210 | , valueDecoder = TaskPort.ignoreValue 211 | , argsEncoder = Json.object >> withRef fs 212 | } 213 | [ ( "at", Json.list (encodePath Private) paths ) 214 | , ( "shareWith", Json.list Json.string shareWith ) 215 | ] 216 | 217 | 218 | {-| -} 219 | symlink : FileSystem -> Base -> { at : Path Directory, name : String, referringTo : Path k } -> Task () 220 | symlink fs base { at, name, referringTo } = 221 | callTaskPort 222 | { function = "fileSystem_symlink" 223 | , valueDecoder = TaskPort.ignoreValue 224 | , argsEncoder = Json.object >> withRef fs 225 | } 226 | [ ( "at", encodePath base at ) 227 | , ( "name", Json.string name ) 228 | , ( "referringTo", encodePath base referringTo ) 229 | ] 230 | 231 | 232 | {-| -} 233 | write : FileSystem -> Base -> Path File -> Bytes -> Task () 234 | write fs base path content = 235 | callTaskPort 236 | { function = "fileSystem_write" 237 | , valueDecoder = TaskPort.ignoreValue 238 | , argsEncoder = Json.list identity >> withRefSplat fs 239 | } 240 | [ encodePath base path 241 | , encodeBytes content 242 | ] 243 | 244 | 245 | {-| -} 246 | writeUtf8 : FileSystem -> Base -> Path File -> String -> Task () 247 | writeUtf8 fs base path content = 248 | callTaskPort 249 | { function = "fileSystem_write" 250 | , valueDecoder = TaskPort.ignoreValue 251 | , argsEncoder = Json.list identity >> withRefSplat fs 252 | } 253 | [ encodePath base path 254 | , encodeBytes (Bytes.Encode.encode <| Bytes.Encode.string content) 255 | ] 256 | 257 | 258 | 259 | -- REFERENCE 260 | 261 | 262 | {-| -} 263 | ref : FileSystem -> Json.Value 264 | ref fileSystem = 265 | Json.object 266 | [ ( "fileSystemRef", encode fileSystem ) ] 267 | 268 | 269 | {-| -} 270 | withRef : FileSystem -> Json.Value -> Json.Value 271 | withRef fileSystem arg = 272 | Json.object 273 | [ ( "fileSystemRef", encode fileSystem ) 274 | , ( "arg", arg ) 275 | ] 276 | 277 | 278 | {-| -} 279 | withRefSplat : FileSystem -> Json.Value -> Json.Value 280 | withRefSplat fileSystem arg = 281 | Json.object 282 | [ ( "fileSystemRef", encode fileSystem ) 283 | , ( "arg", arg ) 284 | , ( "useSplat", Json.bool True ) 285 | ] 286 | 287 | 288 | 289 | -- 🛠 290 | 291 | 292 | {-| -} 293 | decoder : Decoder FileSystem 294 | decoder = 295 | Json.Decode.map FileSystemReference Json.Decode.string 296 | 297 | 298 | {-| -} 299 | directoryEntriesDecoder : Decoder (List Entry) 300 | directoryEntriesDecoder = 301 | Json.Decode.map3 302 | (\cid isFile size -> 303 | { cid = cid 304 | , size = size 305 | , kind = 306 | if isFile then 307 | File 308 | 309 | else 310 | Directory 311 | } 312 | ) 313 | (Json.Decode.oneOf 314 | [ Json.Decode.field "cid" CID.decoder 315 | , Json.Decode.field "pointer" CID.decoder 316 | ] 317 | ) 318 | (Json.Decode.field "isFile" Json.Decode.bool) 319 | (Json.Decode.field "size" Json.Decode.int) 320 | |> Json.Decode.dict 321 | |> Json.Decode.map 322 | (\dict -> 323 | dict 324 | |> Dict.toList 325 | |> List.map 326 | (\( name, { cid, kind, size } ) -> 327 | { cid = cid 328 | , kind = kind 329 | , name = name 330 | , size = size 331 | } 332 | ) 333 | ) 334 | 335 | 336 | {-| -} 337 | encode : FileSystem -> Json.Value 338 | encode (FileSystemReference r) = 339 | Json.string r 340 | 341 | 342 | 343 | -- ㊙️ 344 | 345 | 346 | encodePath : Base -> Path k -> Json.Value 347 | encodePath base path = 348 | path 349 | |> Path.map 350 | (\parts -> 351 | case base of 352 | AppData { creator, name } -> 353 | [ "private", "Apps", creator, name ] ++ parts 354 | 355 | Private -> 356 | "private" :: parts 357 | 358 | Public -> 359 | "public" :: parts 360 | ) 361 | |> Path.encode 362 | -------------------------------------------------------------------------------- /src/Webnative/Internal.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Internal exposing (..) 2 | 3 | import Bytes exposing (Bytes) 4 | import Bytes.Decode 5 | import Bytes.Decode.Extra 6 | import Bytes.Encode 7 | import Bytes.Encode.Extra 8 | import Json.Decode exposing (Decoder, Value) 9 | import Json.Encode as Json 10 | import String.UTF8 as UTF8 11 | import Task exposing (Task) 12 | import TaskPort 13 | import Webnative.Error as Error exposing (Error) 14 | 15 | 16 | 17 | -- 🏔 18 | 19 | 20 | qualifiedTaskPortFunctionName : TaskPort.FunctionName -> TaskPort.QualifiedName 21 | qualifiedTaskPortFunctionName = 22 | TaskPort.inNamespace "fission-codes/webnative" "8.1.0" 23 | 24 | 25 | 26 | -- BYTES 27 | 28 | 29 | encodeBytes : Bytes -> Json.Value 30 | encodeBytes bytes = 31 | bytes 32 | |> Bytes.Decode.decode 33 | (bytes 34 | |> Bytes.width 35 | |> Bytes.Decode.Extra.byteValues 36 | ) 37 | |> Maybe.withDefault [] 38 | |> Json.list Json.int 39 | 40 | 41 | toBytes : List Int -> Bytes 42 | toBytes list = 43 | list 44 | |> Bytes.Encode.Extra.byteValues 45 | |> Bytes.Encode.encode 46 | 47 | 48 | 49 | -- FILE SYSTEM 50 | 51 | 52 | cidDecoder : Json.Decode.Decoder String 53 | cidDecoder = 54 | Json.Decode.string 55 | 56 | 57 | fileContentDecoder : Json.Decode.Decoder Bytes 58 | fileContentDecoder = 59 | Json.Decode.int 60 | |> Json.Decode.list 61 | |> Json.Decode.map toBytes 62 | 63 | 64 | utf8ContentDecoder : Json.Decode.Decoder String 65 | utf8ContentDecoder = 66 | Json.Decode.int 67 | |> Json.Decode.list 68 | |> Json.Decode.andThen 69 | (\list -> 70 | case UTF8.toString list of 71 | Ok string -> 72 | Json.Decode.succeed string 73 | 74 | Err err -> 75 | Json.Decode.fail err 76 | ) 77 | 78 | 79 | 80 | -- TASKPORTS 81 | 82 | 83 | callTaskPort : 84 | { function : String 85 | , valueDecoder : Decoder value 86 | , argsEncoder : args -> Value 87 | } 88 | -> args 89 | -> Task Error value 90 | callTaskPort opts args = 91 | args 92 | |> TaskPort.callNS 93 | { function = qualifiedTaskPortFunctionName opts.function 94 | , valueDecoder = Json.Decode.oneOf [ taskPortResultDecoder opts.valueDecoder, opts.valueDecoder ] 95 | , argsEncoder = opts.argsEncoder 96 | } 97 | |> Task.mapError Error.fromTaskPort 98 | 99 | 100 | callTaskPortWithoutArgs : 101 | { function : String 102 | , valueDecoder : Decoder value 103 | } 104 | -> Task Error value 105 | callTaskPortWithoutArgs opts = 106 | { function = qualifiedTaskPortFunctionName opts.function 107 | , valueDecoder = Json.Decode.oneOf [ taskPortResultDecoder opts.valueDecoder, opts.valueDecoder ] 108 | } 109 | |> TaskPort.callNoArgsNS 110 | |> Task.mapError Error.fromTaskPort 111 | 112 | 113 | taskPortResultDecoder : Decoder a -> Decoder a 114 | taskPortResultDecoder valueDecoder = 115 | Json.Decode.oneOf 116 | [ Json.Decode.field "ok" valueDecoder 117 | , Json.Decode.andThen Json.Decode.fail (Json.Decode.field "error" Json.Decode.string) 118 | ] 119 | -------------------------------------------------------------------------------- /src/Webnative/Namespace.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Namespace exposing (Namespace, fromAppInfo, fromString, toString) 2 | 3 | {-| 4 | 5 | @docs Namespace, fromAppInfo, fromString, toString 6 | 7 | -} 8 | 9 | import Webnative.AppInfo as AppInfo exposing (AppInfo) 10 | 11 | 12 | 13 | -- 🌳 14 | 15 | 16 | {-| -} 17 | type Namespace 18 | = NsAppInfo AppInfo 19 | | NsString String 20 | 21 | 22 | 23 | -- 🛠 24 | 25 | 26 | {-| -} 27 | fromAppInfo : AppInfo -> Namespace 28 | fromAppInfo = 29 | NsAppInfo 30 | 31 | 32 | {-| -} 33 | fromString : String -> Namespace 34 | fromString = 35 | NsString 36 | 37 | 38 | {-| -} 39 | toString : Namespace -> String 40 | toString namespace = 41 | case namespace of 42 | NsAppInfo appInfo -> 43 | AppInfo.appId appInfo 44 | 45 | NsString string -> 46 | string 47 | -------------------------------------------------------------------------------- /src/Webnative/Path.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Path exposing 2 | ( Path, Directory, File, Encapsulated, Kind(..) 3 | , directory, file, root 4 | , fromPosix, toPosix 5 | , encapsulate 6 | , kind, map, unwrap 7 | , encode 8 | ) 9 | 10 | {-| 11 | 12 | 13 | # Paths 14 | 15 | @docs Path, Directory, File, Encapsulated, Kind 16 | 17 | 18 | # Creation 19 | 20 | @docs directory, file, root 21 | 22 | 23 | # POSIX 24 | 25 | @docs fromPosix, toPosix 26 | 27 | 28 | # Encapsulation 29 | 30 | @docs encapsulate 31 | 32 | 33 | # Functions 34 | 35 | @docs kind, map, unwrap 36 | 37 | 38 | # Miscellaneous 39 | 40 | @docs encode 41 | 42 | -} 43 | 44 | import Json.Encode as Json 45 | 46 | 47 | 48 | -- 🌳 49 | 50 | 51 | {-| Path type. 52 | 53 | This is used with the [phantom 👻 types](#phantom-types). 54 | 55 | directoryPath : Path Directory 56 | 57 | filePath : Path File 58 | 59 | encapsulatedPath : Path Encapsulated 60 | 61 | -} 62 | type Path t 63 | = Path Kind (List String) 64 | 65 | 66 | {-| -} 67 | type Kind 68 | = Directory 69 | | File 70 | 71 | 72 | 73 | -- PHANTOM TYPES 74 | 75 | 76 | {-| 👻 Directory 77 | -} 78 | type Directory 79 | = Directory_ 80 | 81 | 82 | {-| 👻 File 83 | -} 84 | type File 85 | = File_ 86 | 87 | 88 | {-| 👻 Encapsulated 89 | -} 90 | type Encapsulated 91 | = Encapsulated_ 92 | 93 | 94 | 95 | -- CREATION 96 | 97 | 98 | {-| Create a directory path. 99 | 100 | directory [ "Audio", "Playlists" ] 101 | 102 | -} 103 | directory : List String -> Path Directory 104 | directory = 105 | Path Directory 106 | 107 | 108 | {-| Create a file path. 109 | 110 | file [ "Document", "invoice.pdf" ] 111 | 112 | -} 113 | file : List String -> Path File 114 | file = 115 | Path File 116 | 117 | 118 | {-| Root directory. 119 | -} 120 | root : Path Directory 121 | root = 122 | directory [] 123 | 124 | 125 | 126 | -- POSIX 127 | 128 | 129 | {-| Convert a POSIX formatted string to a path. 130 | 131 | This will return a `Encapsulated` path. To get a path of the type `Path Directory` or `Path File`, use the functions in the `Webnative.Path.Encapsulated` module. 132 | 133 | >>> import Webnative.Path.Encapsulated 134 | 135 | >>> "foo/bar/" 136 | ..> |> fromPosix 137 | ..> |> Webnative.Path.Encapsulated.toDirectory 138 | Just (directory [ "foo", "bar" ]) 139 | 140 | >>> "foo/bar" 141 | ..> |> fromPosix 142 | ..> |> Webnative.Path.Encapsulated.toFile 143 | Just (file [ "foo", "bar" ]) 144 | 145 | -} 146 | fromPosix : String -> Path Encapsulated 147 | fromPosix string = 148 | string 149 | |> (\s -> 150 | if String.startsWith "/" s then 151 | String.dropLeft 1 s 152 | 153 | else 154 | s 155 | ) 156 | |> (\s -> 157 | if String.endsWith "/" s then 158 | Path Directory (String.split "/" <| String.dropRight 1 s) 159 | 160 | else 161 | Path File (String.split "/" s) 162 | ) 163 | 164 | 165 | {-| Convert a path to the POSIX format. 166 | 167 | >>> toPosix (directory [ "foo", "bar"]) 168 | "foo/bar/" 169 | 170 | >>> toPosix (file [ "foo", "bar"]) 171 | "foo/bar" 172 | 173 | -} 174 | toPosix : Path t -> String 175 | toPosix (Path k parts) = 176 | let 177 | joined = 178 | String.join "/" parts 179 | in 180 | case k of 181 | Directory -> 182 | joined ++ "/" 183 | 184 | File -> 185 | joined 186 | 187 | 188 | 189 | -- ENCAPSULATE 190 | 191 | 192 | {-| Encapsulate a path. 193 | -} 194 | encapsulate : Path t -> Path Encapsulated 195 | encapsulate (Path k p) = 196 | Path k p 197 | 198 | 199 | 200 | -- 🛠 201 | 202 | 203 | {-| Get the path kind. 204 | 205 | >>> kind (directory []) 206 | Directory 207 | 208 | >>> kind (file []) 209 | File 210 | 211 | Even if a path is encapsulated, 212 | you can still check the kind of path it is. 213 | 214 | >>> kind (encapsulate <| directory []) 215 | Directory 216 | 217 | >>> kind (encapsulate <| file []) 218 | File 219 | 220 | -} 221 | kind : Path t -> Kind 222 | kind (Path k _) = 223 | k 224 | 225 | 226 | {-| Map. 227 | -} 228 | map : (List String -> List String) -> Path t -> Path t 229 | map fn (Path k parts) = 230 | Path k (fn parts) 231 | 232 | 233 | {-| Get the path parts. 234 | 235 | >>> unwrap (directory [ "foo", "bar" ]) 236 | [ "foo", "bar" ] 237 | 238 | >>> unwrap (file [ "foo", "bar" ]) 239 | [ "foo", "bar" ] 240 | 241 | -} 242 | unwrap : Path t -> List String 243 | unwrap (Path _ parts) = 244 | parts 245 | 246 | 247 | 248 | -- MISCELLANEOUS 249 | 250 | 251 | {-| Encode to JSON. 252 | 253 | >>> import Json.Encode 254 | 255 | >>> [ "foo" ] 256 | ..> |> directory 257 | ..> |> encode 258 | ..> |> Json.Encode.encode 0 259 | "{\"directory\":[\"foo\"]}" 260 | 261 | >>> [ "bar" ] 262 | ..> |> file 263 | ..> |> encode 264 | ..> |> Json.Encode.encode 0 265 | "{\"file\":[\"bar\"]}" 266 | 267 | -} 268 | encode : Path t -> Json.Value 269 | encode (Path k p) = 270 | Json.object 271 | [ ( case k of 272 | Directory -> 273 | "directory" 274 | 275 | File -> 276 | "file" 277 | , Json.list Json.string p 278 | ) 279 | ] 280 | -------------------------------------------------------------------------------- /src/Webnative/Path/Encapsulated.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Path.Encapsulated exposing (toDirectory, toFile) 2 | 3 | {-| 4 | 5 | 6 | # Encapsulated Paths 7 | 8 | @docs toDirectory, toFile 9 | 10 | -} 11 | 12 | import Webnative.Path as Path exposing (Directory, Encapsulated, File, Kind(..), Path, directory, file) 13 | 14 | 15 | 16 | -- EXTRACTION 17 | 18 | 19 | {-| Remove the membrane and extract a `Path Directory`. 20 | -} 21 | toDirectory : Path Encapsulated -> Maybe (Path Directory) 22 | toDirectory path = 23 | case Path.kind path of 24 | Directory -> 25 | path 26 | |> Path.unwrap 27 | |> directory 28 | |> Just 29 | 30 | File -> 31 | Nothing 32 | 33 | 34 | {-| Remove the membrane and extract a `Path File`. 35 | -} 36 | toFile : Path Encapsulated -> Maybe (Path File) 37 | toFile path = 38 | case Path.kind path of 39 | Directory -> 40 | Nothing 41 | 42 | File -> 43 | path 44 | |> Path.unwrap 45 | |> file 46 | |> Just 47 | -------------------------------------------------------------------------------- /src/Webnative/Permissions.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Permissions exposing (BranchFileSystemPermissions, FileSystemPermissions, Permissions, encode, encodeFileSystemPermissions, flattenPermissions) 2 | 3 | {-| 4 | 5 | @docs BranchFileSystemPermissions, FileSystemPermissions, Permissions, encode, encodeFileSystemPermissions, flattenPermissions 6 | 7 | -} 8 | 9 | import Json.Encode as Json 10 | import Maybe.Extra as Maybe 11 | import Webnative.AppInfo as AppInfo exposing (AppInfo) 12 | import Webnative.Path as Path exposing (Kind(..), Path) 13 | 14 | 15 | 16 | -- 🌳 17 | 18 | 19 | {-| Filesystem permissions for a branch. 20 | 21 | This is reused for the private and public permissions. 22 | 23 | -} 24 | type alias BranchFileSystemPermissions = 25 | { directories : List (Path Path.Directory) 26 | , files : List (Path Path.File) 27 | } 28 | 29 | 30 | {-| Filesystem permissions. 31 | 32 | ```elm 33 | import Webnative.Path as Path 34 | 35 | { private = 36 | { directories = [ Path.directory [ "Audio", "Mixtapes" ] ] 37 | , files = [ Path.file [ "Audio", "Playlists", "Jazz.json" ] ] 38 | } 39 | , public = 40 | { directories = [] 41 | , files = [] 42 | } 43 | } 44 | ``` 45 | 46 | -} 47 | type alias FileSystemPermissions = 48 | { private : BranchFileSystemPermissions 49 | , public : BranchFileSystemPermissions 50 | } 51 | 52 | 53 | {-| Permissions to ask the user. 54 | See [`AppPermissions`](#AppPermissions) and [`FileSystemPermissions`](#FileSystemPermissions) on how to use these. 55 | -} 56 | type alias Permissions = 57 | { app : Maybe AppInfo 58 | , fs : Maybe FileSystemPermissions 59 | } 60 | 61 | 62 | 63 | -- 🛠 64 | 65 | 66 | {-| -} 67 | encode : Permissions -> Json.Value 68 | encode { app, fs } = 69 | Json.object 70 | [ ( "app", Maybe.unwrap Json.null AppInfo.encode app ) 71 | , ( "fs", Maybe.unwrap Json.null encodeFileSystemPermissions fs ) 72 | ] 73 | 74 | 75 | {-| -} 76 | encodeFileSystemPermissions : FileSystemPermissions -> Json.Value 77 | encodeFileSystemPermissions { private, public } = 78 | let 79 | encodeBranch branch = 80 | List.append 81 | (List.map Path.encode branch.directories) 82 | (List.map Path.encode branch.files) 83 | in 84 | Json.object 85 | [ ( "private", Json.list identity (encodeBranch private) ) 86 | , ( "public", Json.list identity (encodeBranch public) ) 87 | ] 88 | 89 | 90 | {-| -} 91 | flattenPermissions : Permissions -> Maybe Permissions 92 | flattenPermissions permissions = 93 | case ( permissions.app, permissions.fs ) of 94 | ( Nothing, Nothing ) -> 95 | Nothing 96 | 97 | _ -> 98 | Just permissions 99 | -------------------------------------------------------------------------------- /src/Webnative/Program.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Program exposing (Program, decoder, encode, ref, withRef, withRefSplat) 2 | 3 | {-| 4 | 5 | @docs Program, decoder, encode, ref, withRef, withRefSplat 6 | 7 | -} 8 | 9 | import Json.Decode exposing (Decoder) 10 | import Json.Encode as Json 11 | 12 | 13 | 14 | -- 🌳 15 | 16 | 17 | {-| -} 18 | type Program 19 | = ProgramReference String 20 | 21 | 22 | 23 | -- REFERENCE 24 | 25 | 26 | {-| -} 27 | ref : Program -> Json.Value 28 | ref program = 29 | Json.object 30 | [ ( "programRef", encode program ) ] 31 | 32 | 33 | {-| -} 34 | withRef : Program -> Json.Value -> Json.Value 35 | withRef program arg = 36 | Json.object 37 | [ ( "programRef", encode program ) 38 | , ( "arg", arg ) 39 | ] 40 | 41 | 42 | {-| -} 43 | withRefSplat : Program -> Json.Value -> Json.Value 44 | withRefSplat program arg = 45 | Json.object 46 | [ ( "programRef", encode program ) 47 | , ( "arg", arg ) 48 | , ( "useSplat", Json.bool True ) 49 | ] 50 | 51 | 52 | 53 | -- 🛠 54 | 55 | 56 | {-| -} 57 | decoder : Decoder Program 58 | decoder = 59 | Json.Decode.map ProgramReference Json.Decode.string 60 | 61 | 62 | {-| -} 63 | encode : Program -> Json.Value 64 | encode (ProgramReference r) = 65 | Json.string r 66 | -------------------------------------------------------------------------------- /src/Webnative/Session.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Session exposing (Session, decoder) 2 | 3 | {-| 4 | 5 | @docs Session, decoder 6 | 7 | -} 8 | 9 | import Json.Decode exposing (Decoder) 10 | 11 | 12 | 13 | -- 🌳 14 | 15 | 16 | {-| -} 17 | type alias Session = 18 | { kind : String 19 | , username : String 20 | } 21 | 22 | 23 | 24 | -- 🛠 25 | 26 | 27 | {-| -} 28 | decoder : Decoder Session 29 | decoder = 30 | Json.Decode.map2 31 | Session 32 | (Json.Decode.field "type" Json.Decode.string) 33 | (Json.Decode.field "username" Json.Decode.string) 34 | -------------------------------------------------------------------------------- /src/Webnative/Task.elm: -------------------------------------------------------------------------------- 1 | module Webnative.Task exposing (Task, attempt) 2 | 3 | {-| 4 | 5 | @docs Task, attempt 6 | 7 | -} 8 | 9 | import Task 10 | import Webnative.Error as Webnative 11 | 12 | 13 | 14 | -- 🌳 15 | 16 | 17 | {-| -} 18 | type alias Task a = 19 | Task.Task Webnative.Error a 20 | 21 | 22 | 23 | -- 🛠 24 | 25 | 26 | {-| -} 27 | attempt : { error : Webnative.Error -> msg, ok : value -> msg } -> Task value -> Cmd msg 28 | attempt { error, ok } = 29 | Task.attempt 30 | (\result -> 31 | case result of 32 | Err e -> 33 | error e 34 | 35 | Ok o -> 36 | ok o 37 | ) 38 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Webnative from "webnative" 2 | import { Maybe } from "webnative" 3 | import { hasProp } from "webnative/common/index" 4 | import { DistinctivePath, Partition, PartitionedNonEmpty } from "webnative/path/index" 5 | 6 | 7 | export type Reference = string 8 | 9 | 10 | /** 11 | * Create TaskPort namespace. 12 | */ 13 | export function createTaskPortNamespace(TaskPort) { 14 | return TaskPort.createNamespace("fission-codes/webnative", "8.1.0") 15 | } 16 | 17 | 18 | /** 19 | * Setup the ports for our Elm app. 20 | * 21 | * @param program Existing programs to 22 | */ 23 | export function init(options: { 24 | fileSystems?: Webnative.FileSystem[] 25 | programs?: Webnative.Program[], 26 | TaskPort: any 27 | }): { taskPortNamespace } { 28 | const fileSystems: Record = Object.fromEntries( 29 | (options.fileSystems || []).map(fs => [ fileSystemRef(fs), fs ]) 30 | ) 31 | 32 | const programs: Record = Object.fromEntries( 33 | (options.programs || []).map(program => [ programRef(program), program ]) 34 | ) 35 | 36 | const ns = createTaskPortNamespace(options.TaskPort) 37 | 38 | const withProg = fn => withProgram(programs, fn) 39 | const withFs = fn => withFileSystem(fileSystems, fn) 40 | 41 | ns.register("program", args => createProgram(programs, fileSystems, args)) 42 | ns.register("loadFileSystem", withProg(p => args => loadFileSystem(fileSystems, p, args))) 43 | 44 | ns.register("auth_isUsernameAvailable", withProg(p => p.auth.isUsernameAvailable)) 45 | ns.register("auth_isUsernameValid", withProg(p => p.auth.isUsernameValid)) 46 | ns.register("auth_register", withProg(p => p.auth.register)) 47 | ns.register("auth_session", withProg(p => p.auth.session)) 48 | 49 | ns.register("capabilities_collect", withProg(p => p.capabilities.collect)) 50 | ns.register("capabilities_request", withProg(p => p.capabilities.request)) 51 | ns.register("capabilities_session", withProg(p => p.capabilities.session)) 52 | 53 | ns.register("fileSystem_acceptShare", withFs(f => f.acceptShare)) 54 | ns.register("fileSystem_account", withFs(f => f.account)) 55 | ns.register("fileSystem_deactivate", withFs(f => f.deactivate)) 56 | ns.register("fileSystem_exists", withFs(f => f.exists)) 57 | ns.register("fileSystem_get", withFs(f => f.get)) 58 | ns.register("fileSystem_historyStep", withFs(f => f.historyStep)) 59 | ns.register("fileSystem_loadShare", withFs(f => f.loadShare)) 60 | ns.register("fileSystem_ls", withFs(f => f.ls)) 61 | ns.register("fileSystem_mkdir", withFs(f => f.mkdir)) 62 | ns.register("fileSystem_mv", withFs(f => f.mv)) 63 | ns.register("fileSystem_publish", withFs(f => f.publish)) 64 | ns.register("fileSystem_read", withFs(f => f.read)) 65 | ns.register("fileSystem_resolveSymlink", withFs(f => f.resolveSymlink)) 66 | ns.register("fileSystem_rm", withFs(f => f.rm)) 67 | ns.register("fileSystem_sharePrivate", withFs(f => f.sharePrivate)) 68 | ns.register("fileSystem_symlink", withFs(f => f.symlink)) 69 | ns.register("fileSystem_write", withFs(addToFileSystem)) 70 | 71 | return { taskPortNamespace: ns } 72 | } 73 | 74 | 75 | 76 | // TASKS 77 | 78 | 79 | function addToFileSystem(fs: Webnative.FileSystem) { 80 | return (path: DistinctivePath>, bytes: number[]) => fs.write( 81 | path, Uint8Array.from(bytes) 82 | ) 83 | } 84 | 85 | 86 | function createProgram( 87 | programs: Record, 88 | fileSystems: Record, 89 | config: Webnative.Configuration 90 | ): Promise< 91 | { ok: { fs: string | null, program: string, session: Webnative.Session | null } } | { err: string } 92 | > { 93 | return Webnative.program(config).then( 94 | program => { 95 | const programRef = Webnative.namespace(config) 96 | programs[ programRef ] = program 97 | 98 | const fsRef = program.session?.fs ? fileSystemRef(program.session.fs) : null 99 | if (fsRef && program.session?.fs) fileSystems[ fsRef ] = program.session.fs 100 | 101 | return { ok: encodeProgram(program) } 102 | }, 103 | error => { 104 | return { err: typeof error === "string" ? error : error.message || "Unknown error" } 105 | } 106 | ) 107 | } 108 | 109 | 110 | async function loadFileSystem( 111 | fileSystems: Record, 112 | program: Webnative.Program, 113 | username: string 114 | ) { 115 | const fs = await program.fileSystem.load(username) 116 | const fsRef = fileSystemRef(fs) 117 | fileSystems[ fsRef ] = fs 118 | return fsRef 119 | } 120 | 121 | 122 | 123 | // 🛠 124 | 125 | 126 | export function encodeProgram(program: Webnative.Program): { 127 | fs: Maybe, 128 | program: Reference, 129 | session: Maybe 130 | } { 131 | const fs = program.session && program.session.fs ? fileSystemRef(program.session.fs) : null 132 | return { fs, program: programRef(program), session: program.session } 133 | } 134 | 135 | 136 | export function fileSystemRef(fs: Webnative.FileSystem): string { 137 | return fs.account.rootDID 138 | } 139 | 140 | 141 | export function programRef(program: Webnative.Program): Reference { 142 | return Webnative.namespace(program.configuration) 143 | } 144 | 145 | 146 | function withFileSystem( 147 | fileSystems: Record, 148 | fn: (fs: Webnative.FileSystem) => unknown 149 | ) { 150 | return async ({ arg, fileSystemRef, useSplat }) => { 151 | const fs = fileSystems[ fileSystemRef ] 152 | const innerValue = fn(fs) 153 | if (typeof innerValue !== "function") return innerValue 154 | const result = useSplat ? await innerValue.apply(fs, arg) : await innerValue.call(fs, arg) 155 | 156 | if (hasProp(result, "account")) return null // FileSystem instance 157 | if (hasProp(result, "code")) return result.toString() 158 | if (hasProp(result, "buffer")) return Array.from(result as Uint8Array) 159 | return result 160 | } 161 | } 162 | 163 | 164 | function withProgram( 165 | programs: Record, 166 | fn: (program: Webnative.Program) => unknown 167 | ) { 168 | return ({ arg, programRef, useSplat }) => { 169 | const program = programs[ programRef ] 170 | const innerValue = fn(program) 171 | if (typeof innerValue !== "function") return innerValue 172 | return useSplat ? innerValue.apply(program, arg) : innerValue.call(program, arg) 173 | } 174 | } -------------------------------------------------------------------------------- /test-envs/commonjs/index.js: -------------------------------------------------------------------------------- 1 | import * as webnative from "webnative" 2 | import * as webnativeElm from "webnative-elm" 3 | 4 | console.log(webnativeElm) 5 | 6 | document.body.innerHTML = typeof webnativeElm.setup === "function" 7 | ? "Success" 8 | : "Failed" 9 | -------------------------------------------------------------------------------- /test-envs/commonjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "esbuild": "^0.8.30" 5 | }, 6 | "dependencies": { 7 | "webnative": "^0.24.0", 8 | "webnative-elm": "file:../../" 9 | }, 10 | "scripts": { 11 | "build": "esbuild --bundle index.js --outfile=dist/index.js" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-envs/example/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src", 5 | "../../src" 6 | ], 7 | "elm-version": "0.19.1", 8 | "dependencies": { 9 | "direct": { 10 | "TSFoster/elm-bytes-extra": "1.3.0", 11 | "elm/browser": "1.0.2", 12 | "elm/bytes": "1.0.8", 13 | "elm/core": "1.0.5", 14 | "elm/html": "1.0.0", 15 | "elm/json": "1.1.3", 16 | "elm/url": "1.0.0", 17 | "elm-community/maybe-extra": "5.2.0", 18 | "zwilias/elm-utf-tools": "2.0.1" 19 | }, 20 | "indirect": { 21 | "elm/time": "1.0.0", 22 | "elm/virtual-dom": "1.0.2" 23 | } 24 | }, 25 | "test-dependencies": { 26 | "direct": {}, 27 | "indirect": {} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test-envs/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test-envs/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "webnative": "0.24.0", 5 | "webnative-elm": "file:../../" 6 | }, 7 | "devDependencies": { 8 | "esbuild": "^0.8.34" 9 | }, 10 | "scripts": { 11 | "build": "elm make src/Main.elm --output=dist/application.js --debug && esbuild --bundle src/index.js --outfile=dist/index.js" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-envs/example/src/Main.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (main) 2 | 3 | import Browser 4 | import Html 5 | import Html.Events 6 | import Ports 7 | import Webnative exposing (Artifact(..), DecodedResponse(..), State(..)) 8 | import Wnfs exposing (Artifact(..)) 9 | 10 | 11 | main : Program () Model Msg 12 | main = 13 | Browser.element 14 | { init = 15 | \_ -> 16 | permissions 17 | |> Webnative.init 18 | |> Ports.webnativeRequest 19 | |> Tuple.pair { authenticated = False, loading = True } 20 | , update = update 21 | , subscriptions = subscriptions 22 | , view = 23 | \model -> 24 | if model.loading then 25 | Html.text "Loading 👀" 26 | 27 | else if not model.authenticated then 28 | Html.button 29 | [ Html.Events.onClick RedirectToAuth ] 30 | [ Html.text "Authenticate" ] 31 | 32 | else 33 | Html.text "Logged in! 👋" 34 | } 35 | 36 | 37 | 38 | -- 📰 39 | 40 | 41 | subscriptions : Model -> Sub Msg 42 | subscriptions _ = 43 | Ports.webnativeResponse GotWebnativeResponse 44 | 45 | 46 | 47 | -- 🌳 48 | 49 | 50 | type alias Model = 51 | { authenticated : Bool 52 | , loading : Bool 53 | } 54 | 55 | 56 | type Tag 57 | = Query 58 | 59 | 60 | permissions : Webnative.Permissions 61 | permissions = 62 | { app = Nothing 63 | , fs = Nothing 64 | } 65 | 66 | 67 | 68 | -- 📣 69 | 70 | 71 | type Msg 72 | = RedirectToAuth 73 | | GotWebnativeResponse Webnative.Response 74 | 75 | 76 | update : Msg -> Model -> ( Model, Cmd Msg ) 77 | update msg model = 78 | case msg of 79 | RedirectToAuth -> 80 | ( model 81 | , Ports.webnativeRequest <| 82 | Webnative.redirectToLobby 83 | Webnative.CurrentUrl 84 | permissions 85 | ) 86 | 87 | GotWebnativeResponse response -> 88 | let 89 | result = 90 | Webnative.decodeResponse 91 | tagFromString 92 | response 93 | 94 | _ = 95 | Debug.log "" result 96 | 97 | m = 98 | { model | loading = False } 99 | in 100 | case result of 101 | ----------------------------------------- 102 | -- 🌏 103 | ----------------------------------------- 104 | Webnative (Initialisation state) -> 105 | ( { m | authenticated = Webnative.isAuthenticated state }, Cmd.none ) 106 | 107 | Webnative (Webnative.NoArtifact _) -> 108 | ( m, Cmd.none ) 109 | 110 | ----------------------------------------- 111 | -- 💾 112 | ----------------------------------------- 113 | Wnfs Query (Utf8Content string) -> 114 | ( m, Cmd.none ) 115 | 116 | Wnfs Query _ -> 117 | ( m, Cmd.none ) 118 | 119 | ----------------------------------------- 120 | -- 🥵 121 | ----------------------------------------- 122 | WnfsError err -> 123 | let 124 | _ = 125 | Debug.todo (Wnfs.error err) 126 | in 127 | ( m 128 | , Cmd.none 129 | ) 130 | 131 | WebnativeError err -> 132 | let 133 | _ = 134 | Debug.todo (Webnative.error err) 135 | in 136 | ( m 137 | , Cmd.none 138 | ) 139 | 140 | 141 | 142 | -- TAG ENCODING/DECODING 143 | 144 | 145 | tagToString : Tag -> String 146 | tagToString tag = 147 | case tag of 148 | Query -> 149 | "Query" 150 | 151 | 152 | tagFromString : String -> Result String Tag 153 | tagFromString string = 154 | case string of 155 | "Query" -> 156 | Ok Query 157 | 158 | _ -> 159 | Err "Invalid tag" 160 | -------------------------------------------------------------------------------- /test-envs/example/src/Ports.elm: -------------------------------------------------------------------------------- 1 | port module Ports exposing (..) 2 | 3 | import Webnative 4 | 5 | 6 | port webnativeRequest : Webnative.Request -> Cmd msg 7 | 8 | 9 | port webnativeResponse : (Webnative.Response -> msg) -> Sub msg 10 | -------------------------------------------------------------------------------- /test-envs/example/src/index.js: -------------------------------------------------------------------------------- 1 | import * as webnative from "webnative" 2 | import * as webnativeElm from "webnative-elm" 3 | 4 | 5 | const elmApp = Elm.Main.init({ 6 | node: document.body.querySelector("#replaceme") 7 | }) 8 | 9 | 10 | webnativeElm.setup({ app: elmApp }) 11 | -------------------------------------------------------------------------------- /test-envs/umd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "ES2020", 5 | "module": "ES2020", 6 | "sourceMap": true, 7 | "declaration": true, 8 | "skipLibCheck": true, 9 | "declarationDir": "lib", 10 | "outDir": "lib" 11 | }, 12 | "include": [ 13 | "src/**/*" 14 | ] 15 | } --------------------------------------------------------------------------------