├── .flutter-plugins ├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── demo.gif ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── filegot │ │ │ │ │ └── fluttergundb │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ └── main.dart ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json ├── lib ├── flutter_gundb.dart └── src │ ├── client │ ├── control_flow │ │ ├── gun_event.dart │ │ ├── gun_process_queue.dart │ │ ├── gun_queue.dart │ │ └── middleware_system.dart │ ├── flutter_gun_client.dart │ ├── flutter_gun_link.dart │ ├── graph │ │ ├── gun_graph.dart │ │ ├── gun_graph_node.dart │ │ └── gun_graph_utils.dart │ ├── interfaces.dart │ └── transports │ │ ├── gun_graph_connector.dart │ │ ├── gun_graph_connector_from_adapter.dart │ │ ├── gun_graph_wire_connector.dart │ │ └── web_socket_graph_connector.dart │ ├── crdt │ └── index.dart │ ├── sea │ ├── flutter_gun_sea_client.dart │ └── flutter_gun_user_api.dart │ ├── sear │ ├── authenticate.dart │ ├── base64.dart │ ├── certify.dart │ ├── create_user.dart │ ├── decrypt.dart │ ├── encrypt.dart │ ├── import_aes_key.dart │ ├── index.dart │ ├── pair.dart │ ├── pseudo_random_text.dart │ ├── safe_buffer.dart │ ├── sea_array.dart │ ├── secret.dart │ ├── settings.dart │ ├── sha256.dart │ ├── shims.dart │ ├── sign.dart │ ├── soul.dart │ ├── unpack.dart │ ├── verify.dart │ └── work.dart │ ├── storage │ ├── init.dart │ └── store.dart │ └── types │ ├── enum.dart │ ├── flutter_gun.dart │ ├── generic.dart │ ├── gun.dart │ ├── gun_graph_adapter.dart │ └── sear │ └── types.dart ├── pubspec.yaml └── test └── flutter_gundb_test.dart /.flutter-plugins: -------------------------------------------------------------------------------- 1 | # This is a generated file; do not edit or check into version control. 2 | flutter_secure_storage=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage-8.0.0/ 3 | flutter_secure_storage_linux=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage_linux-1.1.3/ 4 | flutter_secure_storage_macos=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage_macos-3.0.0/ 5 | flutter_secure_storage_web=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage_web-1.1.1/ 6 | flutter_secure_storage_windows=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage_windows-2.0.0/ 7 | path_provider=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/path_provider-2.0.13/ 8 | path_provider_android=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/path_provider_android-2.0.23/ 9 | path_provider_foundation=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/path_provider_foundation-2.1.2/ 10 | path_provider_linux=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.1.9/ 11 | path_provider_windows=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.1.4/ 12 | webcrypto=/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/webcrypto-0.5.3/ 13 | -------------------------------------------------------------------------------- /.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage-8.0.0/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/path_provider_foundation-2.1.2/","native_build":true,"dependencies":[]},{"name":"webcrypto","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/webcrypto-0.5.3/","native_build":true,"dependencies":[]}],"android":[{"name":"flutter_secure_storage","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage-8.0.0/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/path_provider_android-2.0.23/","native_build":true,"dependencies":[]},{"name":"webcrypto","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/webcrypto-0.5.3/","native_build":true,"dependencies":[]}],"macos":[{"name":"flutter_secure_storage_macos","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage_macos-3.0.0/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/path_provider_foundation-2.1.2/","native_build":true,"dependencies":[]}],"linux":[{"name":"flutter_secure_storage_linux","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage_linux-1.1.3/","native_build":true,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-2.1.9/","native_build":false,"dependencies":[]},{"name":"webcrypto","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/webcrypto-0.5.3/","native_build":true,"dependencies":[]}],"windows":[{"name":"flutter_secure_storage_windows","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage_windows-2.0.0/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-2.1.4/","native_build":false,"dependencies":[]}],"web":[{"name":"flutter_secure_storage_web","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/flutter_secure_storage_web-1.1.1/","dependencies":[]},{"name":"webcrypto","path":"/Users/adityapandey/.pub-cache/hosted/pub.dartlang.org/webcrypto-0.5.3/","dependencies":[]}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"webcrypto","dependencies":[]}],"date_created":"2023-03-04 18:14:19.148977","version":"3.3.9"} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | .packages 30 | build/ 31 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.3 2 | 3 | * Added Local Storage for offline use case. 4 | * Add Encryption Sharing between two user with example. 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Flutter GunDB 2 | 3 | This library is a port of GunDB js for the Dart and Flutter. P2P encrypted Communication between multiple users. 4 | GUN is an ecosystem of tools that let you build community run and encrypted applications - like an Open Source Firebase or a Decentralized Dropbox. 5 | 6 | `Note: Some APIs like certify and user, not implemented completely (Trying to do so ;) )` 7 | 8 | ### Demo 9 | 10 | ![Demo Gif](https://raw.githubusercontent.com/adityapandey9/flutter-gun/master/demo.gif) 11 | 12 | ## Features 13 | 14 | 1. Multiplayer by default with realtime p2p state synchronization! 15 | 2. Graph data lets you use key/value, tables, documents, videos, & more! 16 | 3. Local-first, offline, and decentralized with end-to-end encryption. 17 | 18 | Decentralized alternatives to Zoom, Reddit, Instagram, Slack, YouTube, Stripe, Wikipedia, Facebook Horizon and 19 | more have already pushed terabytes of daily P2P traffic on GUN. 20 | 21 | ## Getting started 22 | 23 | Add library to your app. 24 | 25 | ``` 26 | flutter pub add flutter_gundb 27 | ``` 28 | 29 | or 30 | 31 | ```yaml 32 | ..... 33 | dependencies: 34 | flutter_gundb: ^0.0.3 35 | .... 36 | ``` 37 | 38 | ## Usage 39 | 40 | Short example is below. Added longer examples to `/example` folder. 41 | 42 | ```dart 43 | import 'package:flutter_gundb/flutter_gundb.dart'; 44 | 45 | void main() { 46 | final chainGunClient = FlutterGunSeaClient(); 47 | 48 | final getAditya = gun.get('aditya'); 49 | getAditya.put({ 50 | name: "Aditya Kumar Pandey", 51 | email: "janatig@janatig.com", 52 | }); 53 | getAditya.on((a, [b, c]) { 54 | print('Getting Value:: $a'); 55 | }); 56 | } 57 | 58 | ``` 59 | 60 | Example on how to store the data locally. 61 | 62 | ```dart 63 | import 'package:flutter_gundb/flutter_gundb.dart'; 64 | 65 | void main() async { 66 | await initializeFlutterGun(); 67 | final chainGunClient = FlutterGunSeaClient(registerStorage: true); 68 | 69 | final localFirstStorage = gun.get('local-first-storage'); 70 | localFirstStorage.put({ 71 | name: "Testing the Local First", 72 | anime_website: "animeloved.com", 73 | }); 74 | localFirstStorage.on((a, [b, c]) { 75 | print('Getting Value:: $a'); 76 | }); 77 | } 78 | 79 | ``` 80 | 81 | Example How to send other user encrypted data 82 | 83 | ```dart 84 | import 'package:flutter_gundb/flutter_gundb.dart'; 85 | 86 | void main() { 87 | () async { 88 | var aditya = await pair(); 89 | var pandey = await pair(); 90 | 91 | // On Aditya 92 | var shared = await secret(pandey.epub, aditya); 93 | var shared_enc = await encrypt('', shared); 94 | 95 | // Now Share `shared_enc` to your friend pandey (Send shared_enc and aditya's public key) 96 | 97 | // On Pandey Side 98 | 99 | var decryptKey = await secret(aditya.epub, pandey); 100 | var decryptedData = await decrypt(shared_enc, decryptKey); 101 | 102 | // `decryptedData` This is your Decrypted Data 103 | } (); 104 | } 105 | 106 | ``` 107 | 108 | ## Additional information 109 | 110 | Some APIs like `certify` and `user`. If anyone wants to help, kindly send a PR. I would appreciate it. Thank you in advance :) 111 | 112 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/demo.gif -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 17 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 18 | - platform: android 19 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 20 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 21 | - platform: ios 22 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 23 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 24 | - platform: web 25 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 26 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268 27 | 28 | # User provided section 29 | 30 | # List of Local paths (relative to this file) that should be 31 | # ignored by the migrate tool. 32 | # 33 | # Files that are not part of the templates will be ignored by default. 34 | unmanaged_files: 35 | - 'lib/main.dart' 36 | - 'ios/Runner.xcodeproj/project.pbxproj' 37 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A test for Flutter GunDB 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.filegot.fluttergundb.example.example" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/filegot/fluttergundb/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.filegot.fluttergundb.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_gundb/flutter_gundb.dart'; 3 | 4 | void main() async { 5 | 6 | await initializeFlutterGun(); 7 | 8 | runApp(const MyApp()); 9 | } 10 | 11 | class MyApp extends StatelessWidget { 12 | const MyApp({Key? key}) : super(key: key); 13 | 14 | // This widget is the root of your application. 15 | @override 16 | Widget build(BuildContext context) { 17 | return MaterialApp( 18 | title: 'Flutter Demo', 19 | theme: ThemeData( 20 | // This is the theme of your application. 21 | // 22 | // Try running your application with "flutter run". You'll see the 23 | // application has a blue toolbar. Then, without quitting the app, try 24 | // changing the primarySwatch below to Colors.green and then invoke 25 | // "hot reload" (press "r" in the console where you ran "flutter run", 26 | // or simply save your changes to "hot reload" in a Flutter IDE). 27 | // Notice that the counter didn't reset back to zero; the application 28 | // is not restarted. 29 | primarySwatch: Colors.blue, 30 | ), 31 | home: const MyHomePage(title: 'Flutter Demo Home Page'), 32 | ); 33 | } 34 | } 35 | 36 | class MyHomePage extends StatefulWidget { 37 | const MyHomePage({Key? key, required this.title}) : super(key: key); 38 | 39 | // This widget is the home page of your application. It is stateful, meaning 40 | // that it has a State object (defined below) that contains fields that affect 41 | // how it looks. 42 | 43 | // This class is the configuration for the state. It holds the values (in this 44 | // case the title) provided by the parent (in this case the App widget) and 45 | // used by the build method of the State. Fields in a Widget subclass are 46 | // always marked "final". 47 | 48 | final String title; 49 | 50 | @override 51 | State createState() => _MyHomePageState(); 52 | } 53 | 54 | class _MyHomePageState extends State { 55 | String gundbText = ''; 56 | TextEditingController gunDBTestingController = TextEditingController(); 57 | late FlutterGunLink copy; 58 | 59 | @override 60 | void initState() { 61 | super.initState(); 62 | FlutterGunOptions flutterGunOptions = FlutterGunOptions(); 63 | const isLocal = true; 64 | flutterGunOptions.peers = [ isLocal ? 'ws://localhost:8080/gun' : 'wss://gun-manhattan.herokuapp.com/gun']; 65 | final chainGunClient = FlutterGunSeaClient(flutterGunOptions: flutterGunOptions, registerStorage: true); 66 | 67 | copy = chainGunClient.get('filegot2'); 68 | 69 | final pasteJust = copy.get('paste').get('just'); 70 | final doingMaybe = copy.get('doing'); 71 | 72 | pasteJust.on((a, [b, c]) { 73 | print('pasteJust:::: $a'); 74 | setState(() { 75 | if (a != null) { 76 | gundbText = a; 77 | if (gundbText != gunDBTestingController.text) { 78 | gunDBTestingController.text = gundbText; 79 | } 80 | } 81 | }); 82 | }); 83 | 84 | doingMaybe.on((a, [b, c]) async { 85 | print('doingMaybe:: $a'); 86 | 87 | /** Below is just an basic example **/ 88 | 89 | final pairVar = await pair(); 90 | var enc = await encrypt('hello self', pairVar); 91 | print('encrypt:: $enc'); 92 | var data = await sign(enc, pairVar); 93 | print('signed:: $data'); 94 | 95 | var msg = await verify(data, pairVar); 96 | print('verify:: $msg'); 97 | var dec = await decrypt(msg, pairVar); 98 | var proof = await work(dec, pairVar); 99 | var check = await work('hello self', pairVar); 100 | print('Decrypt MSG:: $dec'); 101 | print('Check:: ${proof == check} -- $proof -- $check'); 102 | 103 | /** Below code is for the sharing data encrypted between two users **/ 104 | 105 | var alice = await pair(); 106 | var bob = await pair(); 107 | var shared = await secret(bob.epub, alice); 108 | print('shared secret:: $shared'); 109 | var shared_enc = await encrypt('shared data', shared); 110 | print('shared_enc :: $shared_enc'); 111 | 112 | var decryptKey = await secret(alice.epub, bob); 113 | print('decryptKey:: $decryptKey'); 114 | 115 | var dec2 = await decrypt(shared_enc, decryptKey); 116 | print('Decrypted Data:: $dec2'); 117 | 118 | /** Below Example code not yet done, Currently implementing it **/ 119 | 120 | // var certificate = await certify(alice.pub, ["^AliceOnly.*"], bob); 121 | 122 | // final user = await chainGunClient.user().create(alice.pub, alice.epriv); 123 | 124 | // print('Got User:: ${jsonEncode(user)}'); 125 | 126 | // await chainGunClient.user().auth(alias: alice.pub, password: alice.epriv); 127 | 128 | // final testKey = chainGunClient.get('~${bob.pub}').get('AliceOnly').get('do-not-tell-anyone'); 129 | // 130 | // testKey.put({ 'data': shared_enc, 'cert': certificate }); 131 | // 132 | // testKey.once((a, [b, c]) { 133 | // print('Getting Once the data:: $a, $b, $c'); 134 | // }); 135 | 136 | if (a != null && a['maybe'] == false) { 137 | copy.put({ 138 | 'doing': { 139 | 'maybe': true 140 | } 141 | }); 142 | } 143 | }); 144 | 145 | copy.on((a, [b, c]) { 146 | print('ASD:: $a'); 147 | }); 148 | } 149 | 150 | @override 151 | Widget build(BuildContext context) { 152 | // This method is rerun every time setState is called, for instance as done 153 | // by the _incrementCounter method above. 154 | // 155 | // The Flutter framework has been optimized to make rerunning build methods 156 | // fast, so that you can just rebuild anything that needs updating rather 157 | // than having to individually change instances of widgets. 158 | return Scaffold( 159 | appBar: AppBar( 160 | // Here we take the value from the MyHomePage object that was created by 161 | // the App.build method, and use it to set our appbar title. 162 | title: Text(widget.title), 163 | ), 164 | body: Center( 165 | // Center is a layout widget. It takes a single child and positions it 166 | // in the middle of the parent. 167 | child: Column( 168 | // Column is also a layout widget. It takes a list of children and 169 | // arranges them vertically. By default, it sizes itself to fit its 170 | // children horizontally, and tries to be as tall as its parent. 171 | // 172 | // Invoke "debug painting" (press "p" in the console, choose the 173 | // "Toggle Debug Paint" action from the Flutter Inspector in Android 174 | // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) 175 | // to see the wireframe for each widget. 176 | // 177 | // Column has various properties to control how it sizes itself and 178 | // how it positions its children. Here we use mainAxisAlignment to 179 | // center the children vertically; the main axis here is the vertical 180 | // axis because Columns are vertical (the cross axis would be 181 | // horizontal). 182 | mainAxisAlignment: MainAxisAlignment.center, 183 | children: [ 184 | Text( 185 | gundbText, 186 | style: Theme.of(context).textTheme.headline4, 187 | ), 188 | TextFormField( 189 | controller: gunDBTestingController, 190 | maxLines: 5, 191 | decoration: InputDecoration( 192 | labelText: "Enter text to display", 193 | fillColor: Colors.white, 194 | border: OutlineInputBorder( 195 | borderRadius: BorderRadius.circular(5.0), 196 | borderSide: const BorderSide(), 197 | ), 198 | //fillColor: Colors.green 199 | ), 200 | onChanged: (val) { 201 | copy.put({ 'paste': { 202 | 'just': val, 203 | 'more': { 'no': 2 } 204 | }, 205 | 'doing': { 206 | 'maybe': false 207 | } 208 | }); 209 | }, 210 | ), 211 | ], 212 | ), 213 | ), // This trailing comma makes auto-formatting nicer for build methods. 214 | ); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.9.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.1" 25 | clock: 26 | dependency: transitive 27 | description: 28 | name: clock 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.1" 32 | collection: 33 | dependency: transitive 34 | description: 35 | name: collection 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.16.0" 39 | convert: 40 | dependency: transitive 41 | description: 42 | name: convert 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "3.1.1" 46 | crypto: 47 | dependency: transitive 48 | description: 49 | name: crypto 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "3.0.2" 53 | cupertino_icons: 54 | dependency: "direct main" 55 | description: 56 | name: cupertino_icons 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.0.5" 60 | fake_async: 61 | dependency: transitive 62 | description: 63 | name: fake_async 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.3.1" 67 | ffi: 68 | dependency: transitive 69 | description: 70 | name: ffi 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "2.0.1" 74 | file: 75 | dependency: transitive 76 | description: 77 | name: file 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "6.1.4" 81 | flutter: 82 | dependency: "direct main" 83 | description: flutter 84 | source: sdk 85 | version: "0.0.0" 86 | flutter_gundb: 87 | dependency: "direct main" 88 | description: 89 | path: ".." 90 | relative: true 91 | source: path 92 | version: "0.0.3" 93 | flutter_lints: 94 | dependency: "direct dev" 95 | description: 96 | name: flutter_lints 97 | url: "https://pub.dartlang.org" 98 | source: hosted 99 | version: "2.0.1" 100 | flutter_secure_storage: 101 | dependency: transitive 102 | description: 103 | name: flutter_secure_storage 104 | url: "https://pub.dartlang.org" 105 | source: hosted 106 | version: "8.0.0" 107 | flutter_secure_storage_linux: 108 | dependency: transitive 109 | description: 110 | name: flutter_secure_storage_linux 111 | url: "https://pub.dartlang.org" 112 | source: hosted 113 | version: "1.1.3" 114 | flutter_secure_storage_macos: 115 | dependency: transitive 116 | description: 117 | name: flutter_secure_storage_macos 118 | url: "https://pub.dartlang.org" 119 | source: hosted 120 | version: "3.0.0" 121 | flutter_secure_storage_platform_interface: 122 | dependency: transitive 123 | description: 124 | name: flutter_secure_storage_platform_interface 125 | url: "https://pub.dartlang.org" 126 | source: hosted 127 | version: "1.0.1" 128 | flutter_secure_storage_web: 129 | dependency: transitive 130 | description: 131 | name: flutter_secure_storage_web 132 | url: "https://pub.dartlang.org" 133 | source: hosted 134 | version: "1.1.1" 135 | flutter_secure_storage_windows: 136 | dependency: transitive 137 | description: 138 | name: flutter_secure_storage_windows 139 | url: "https://pub.dartlang.org" 140 | source: hosted 141 | version: "2.0.0" 142 | flutter_test: 143 | dependency: "direct dev" 144 | description: flutter 145 | source: sdk 146 | version: "0.0.0" 147 | flutter_web_plugins: 148 | dependency: transitive 149 | description: flutter 150 | source: sdk 151 | version: "0.0.0" 152 | hive: 153 | dependency: transitive 154 | description: 155 | name: hive 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "2.2.3" 159 | hive_flutter: 160 | dependency: transitive 161 | description: 162 | name: hive_flutter 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "1.1.0" 166 | js: 167 | dependency: transitive 168 | description: 169 | name: js 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "0.6.4" 173 | lints: 174 | dependency: transitive 175 | description: 176 | name: lints 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "2.0.0" 180 | matcher: 181 | dependency: transitive 182 | description: 183 | name: matcher 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "0.12.12" 187 | material_color_utilities: 188 | dependency: transitive 189 | description: 190 | name: material_color_utilities 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "0.1.5" 194 | meta: 195 | dependency: transitive 196 | description: 197 | name: meta 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "1.8.0" 201 | mutex: 202 | dependency: transitive 203 | description: 204 | name: mutex 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "3.0.0" 208 | path: 209 | dependency: transitive 210 | description: 211 | name: path 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "1.8.2" 215 | path_provider: 216 | dependency: transitive 217 | description: 218 | name: path_provider 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "2.0.13" 222 | path_provider_android: 223 | dependency: transitive 224 | description: 225 | name: path_provider_android 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "2.0.23" 229 | path_provider_foundation: 230 | dependency: transitive 231 | description: 232 | name: path_provider_foundation 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "2.1.2" 236 | path_provider_linux: 237 | dependency: transitive 238 | description: 239 | name: path_provider_linux 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "2.1.9" 243 | path_provider_platform_interface: 244 | dependency: transitive 245 | description: 246 | name: path_provider_platform_interface 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "2.0.6" 250 | path_provider_windows: 251 | dependency: transitive 252 | description: 253 | name: path_provider_windows 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "2.1.4" 257 | platform: 258 | dependency: transitive 259 | description: 260 | name: platform 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "3.1.0" 264 | plugin_platform_interface: 265 | dependency: transitive 266 | description: 267 | name: plugin_platform_interface 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "2.1.4" 271 | process: 272 | dependency: transitive 273 | description: 274 | name: process 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "4.2.4" 278 | sky_engine: 279 | dependency: transitive 280 | description: flutter 281 | source: sdk 282 | version: "0.0.99" 283 | source_span: 284 | dependency: transitive 285 | description: 286 | name: source_span 287 | url: "https://pub.dartlang.org" 288 | source: hosted 289 | version: "1.9.0" 290 | stack_trace: 291 | dependency: transitive 292 | description: 293 | name: stack_trace 294 | url: "https://pub.dartlang.org" 295 | source: hosted 296 | version: "1.10.0" 297 | stream_channel: 298 | dependency: transitive 299 | description: 300 | name: stream_channel 301 | url: "https://pub.dartlang.org" 302 | source: hosted 303 | version: "2.1.0" 304 | string_scanner: 305 | dependency: transitive 306 | description: 307 | name: string_scanner 308 | url: "https://pub.dartlang.org" 309 | source: hosted 310 | version: "1.1.1" 311 | term_glyph: 312 | dependency: transitive 313 | description: 314 | name: term_glyph 315 | url: "https://pub.dartlang.org" 316 | source: hosted 317 | version: "1.2.1" 318 | test_api: 319 | dependency: transitive 320 | description: 321 | name: test_api 322 | url: "https://pub.dartlang.org" 323 | source: hosted 324 | version: "0.4.12" 325 | typed_data: 326 | dependency: transitive 327 | description: 328 | name: typed_data 329 | url: "https://pub.dartlang.org" 330 | source: hosted 331 | version: "1.3.1" 332 | uuid: 333 | dependency: transitive 334 | description: 335 | name: uuid 336 | url: "https://pub.dartlang.org" 337 | source: hosted 338 | version: "3.0.6" 339 | vector_math: 340 | dependency: transitive 341 | description: 342 | name: vector_math 343 | url: "https://pub.dartlang.org" 344 | source: hosted 345 | version: "2.1.2" 346 | web_socket_channel: 347 | dependency: transitive 348 | description: 349 | name: web_socket_channel 350 | url: "https://pub.dartlang.org" 351 | source: hosted 352 | version: "2.2.0" 353 | webcrypto: 354 | dependency: transitive 355 | description: 356 | name: webcrypto 357 | url: "https://pub.dartlang.org" 358 | source: hosted 359 | version: "0.5.3" 360 | win32: 361 | dependency: transitive 362 | description: 363 | name: win32 364 | url: "https://pub.dartlang.org" 365 | source: hosted 366 | version: "3.1.3" 367 | xdg_directories: 368 | dependency: transitive 369 | description: 370 | name: xdg_directories 371 | url: "https://pub.dartlang.org" 372 | source: hosted 373 | version: "1.0.0" 374 | sdks: 375 | dart: ">=2.18.0 <3.0.0" 376 | flutter: ">=3.0.0" 377 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A test for Flutter GunDB 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.17.1 <3.0.0" 22 | 23 | # Dependencies specify other packages that your package needs in order to work. 24 | # To automatically upgrade your package dependencies to the latest versions 25 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 26 | # dependencies can be manually updated by changing the version numbers below to 27 | # the latest version available on pub.dev. To see which dependencies have newer 28 | # versions available, run `flutter pub outdated`. 29 | dependencies: 30 | flutter_gundb: 31 | path: ../ 32 | flutter: 33 | sdk: flutter 34 | 35 | 36 | # The following adds the Cupertino Icons font to your application. 37 | # Use with the CupertinoIcons class for iOS style icons. 38 | cupertino_icons: ^1.0.2 39 | 40 | dev_dependencies: 41 | flutter_test: 42 | sdk: flutter 43 | 44 | # The "flutter_lints" package below contains a set of recommended lints to 45 | # encourage good coding practices. The lint set provided by the package is 46 | # activated in the `analysis_options.yaml` file located at the root of your 47 | # package. See that file for information about deactivating specific lint 48 | # rules and activating additional ones. 49 | flutter_lints: ^2.0.0 50 | 51 | # For information on the generic Dart part of this file, see the 52 | # following page: https://dart.dev/tools/pub/pubspec 53 | 54 | # The following section is specific to Flutter packages. 55 | flutter: 56 | 57 | # The following line ensures that the Material Icons font is 58 | # included with your application, so that you can use the icons in 59 | # the material Icons class. 60 | uses-material-design: true 61 | 62 | # To add assets to your application, add an assets section, like this: 63 | # assets: 64 | # - images/a_dot_burr.jpeg 65 | # - images/a_dot_ham.jpeg 66 | 67 | # An image asset can refer to one or more resolution-specific "variants", see 68 | # https://flutter.dev/assets-and-images/#resolution-aware 69 | 70 | # For details regarding adding assets from package dependencies, see 71 | # https://flutter.dev/assets-and-images/#from-packages 72 | 73 | # To add custom fonts to your application, add a fonts section here, 74 | # in this "flutter" section. Each entry in this list should have a 75 | # "family" key with the font family name, and a "fonts" key with a 76 | # list giving the asset and other descriptors for the font. For 77 | # example: 78 | # fonts: 79 | # - family: Schyler 80 | # fonts: 81 | # - asset: fonts/Schyler-Regular.ttf 82 | # - asset: fonts/Schyler-Italic.ttf 83 | # style: italic 84 | # - family: Trajan Pro 85 | # fonts: 86 | # - asset: fonts/TrajanPro.ttf 87 | # - asset: fonts/TrajanPro_Bold.ttf 88 | # weight: 700 89 | # 90 | # For details regarding fonts from package dependencies, 91 | # see https://flutter.dev/custom-fonts/#from-packages 92 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adityapandey9/flutter-gun/a71e1bc382b0cbeb14e32e38708a0f8730686042/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | example 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A test for Flutter GunDB", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /lib/flutter_gundb.dart: -------------------------------------------------------------------------------- 1 | library flutter_gundb; 2 | /// A GunDB. 3 | 4 | export 'src/client/flutter_gun_client.dart' show FlutterGunOptions; 5 | export 'src/client/flutter_gun_link.dart' show FlutterGunLink; 6 | export 'src/sea/flutter_gun_sea_client.dart' show FlutterGunSeaClient; 7 | export 'src/sear/index.dart'; 8 | export 'src/types/sear/types.dart' show KeyPair; 9 | export 'src/storage/init.dart' show initializeFlutterGun; 10 | 11 | -------------------------------------------------------------------------------- /lib/src/client/control_flow/gun_event.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:mutex/mutex.dart'; 4 | 5 | typedef EventCb = FutureOr Function(T a, [U? b, V? c]); 6 | 7 | /// Generic event/listener system 8 | class GunEvent { 9 | late final String name; 10 | final List> _listeners; 11 | final mut = Mutex(); 12 | 13 | GunEvent({required this.name}) : _listeners = []; 14 | 15 | /// @returns number of currently subscribed listeners 16 | num listenerCount() { 17 | return _listeners.length; 18 | } 19 | 20 | void _mutexAcquire(Function fn) { 21 | mut.acquire().then((_) => fn()).then((value) => mut.release()); 22 | } 23 | 24 | /// Register a listener on this event 25 | /// 26 | /// @param cb the callback to subscribe 27 | GunEvent on(EventCb cb) { 28 | _mutexAcquire(() { 29 | if (_listeners.contains(cb)) { 30 | return; 31 | } 32 | _listeners.add(cb); 33 | }); 34 | return this; 35 | } 36 | 37 | /// Unregister a listener on this event 38 | /// @param cb the callback to unsubscribe 39 | GunEvent off(EventCb cb) { 40 | _mutexAcquire(() { 41 | final idx = _listeners.indexOf(cb); 42 | if (idx != -1) { 43 | _listeners.removeAt(idx); 44 | } 45 | }); 46 | return this; 47 | } 48 | 49 | /// Unregister all listeners on this event 50 | GunEvent reset() { 51 | _mutexAcquire(() { 52 | _listeners.clear(); 53 | }); 54 | return this; 55 | } 56 | 57 | /// Trigger this event 58 | GunEvent trigger(T a, [U? b, V? c]) { 59 | _mutexAcquire(() { 60 | Future.forEach(_listeners, (EventCb cb) => cb(a, b, c)); 61 | }); 62 | return this; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/client/control_flow/gun_process_queue.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | import '../../types/gun.dart'; 4 | import 'gun_event.dart'; 5 | import 'gun_queue.dart'; 6 | import 'middleware_system.dart'; 7 | 8 | enum ProcessDupesOptionType { processDupes, dontProcessDupes } 9 | 10 | class GunProcessQueue extends GunQueue { 11 | final String name; 12 | late final MiddlewareSystem middleware; 13 | late bool isProcessing; 14 | late final GunEvent completed; 15 | late final GunEvent emptied; 16 | final ProcessDupesOptionType processDupes; 17 | 18 | late List alreadyProcessed; 19 | 20 | GunProcessQueue( 21 | {this.name = 'GunProcessQueue', 22 | this.processDupes = ProcessDupesOptionType.processDupes}) { 23 | alreadyProcessed = []; 24 | isProcessing = false; 25 | completed = GunEvent(name: '$name.processed'); 26 | emptied = GunEvent(name: '$name.emptied'); 27 | middleware = MiddlewareSystem(name: '$name.middleware'); 28 | } 29 | 30 | @override 31 | bool has(T item) { 32 | return super.has(item) || alreadyProcessed.contains(item); 33 | } 34 | 35 | Future processNext([U? b, V? c]) async { 36 | var item = dequeue(); 37 | final processedItem = item; 38 | 39 | if (item == null) { 40 | return; 41 | } 42 | 43 | item = await middleware.process(item, b, c); 44 | 45 | if (processedItem != null && 46 | processDupes == ProcessDupesOptionType.dontProcessDupes) { 47 | alreadyProcessed.add(processedItem); 48 | } 49 | 50 | if (item != null) { 51 | completed.trigger(item); 52 | } 53 | } 54 | 55 | GunProcessQueue enqueueMany(final List items) { 56 | super.enqueueMany(items); 57 | return this; 58 | } 59 | 60 | Future process() async { 61 | if (isProcessing) { 62 | return; 63 | } 64 | 65 | if (count() == 0) { 66 | return; 67 | } 68 | 69 | isProcessing = true; 70 | while (count() > 0) { 71 | try { 72 | await processNext(); 73 | } catch (e) { 74 | if (kDebugMode) { 75 | print('Process Queue error: ${e.toString()}'); 76 | } 77 | } 78 | } 79 | 80 | emptied.trigger(true); 81 | 82 | isProcessing = false; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/client/control_flow/gun_queue.dart: -------------------------------------------------------------------------------- 1 | import '../../types/gun.dart'; 2 | 3 | class GunQueue { 4 | final String name; 5 | late final List _queue; 6 | 7 | GunQueue({this.name = 'GunQueue'}) : _queue = []; 8 | 9 | num count() { 10 | return _queue.length; 11 | } 12 | 13 | bool has(T item) { 14 | return _queue.contains(item); 15 | } 16 | 17 | GunQueue enqueue(T item) { 18 | if (has(item)) { 19 | return this; 20 | } 21 | 22 | _queue.insert(0, item); 23 | return this; 24 | } 25 | 26 | T? dequeue() { 27 | return _queue.removeLast(); 28 | } 29 | 30 | GunQueue enqueueMany(final List items) { 31 | final filtered = items.where((item) => !has(item)).toList(); 32 | final List filteredListReverse = []; 33 | 34 | for (var i = filtered.length - 1; i >= 0; i--) { 35 | filteredListReverse.add(filtered[i]); 36 | } 37 | if (filtered.isNotEmpty) { 38 | _queue.insertAll(0, filteredListReverse); 39 | } 40 | 41 | return this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/client/control_flow/middleware_system.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class MiddlewareSystem { 4 | final String name; 5 | late final List Function(T a, [U? b, V? c])> _middlewareFunctions; 6 | 7 | MiddlewareSystem({this.name = 'MiddlewareSystem'}) 8 | : _middlewareFunctions = []; 9 | 10 | /// Register middleware function 11 | /// 12 | /// @param middleware The middleware function to add 13 | MiddlewareSystem use( 14 | FutureOr Function(T a, [U? b, V? c]) middleware) { 15 | if (_middlewareFunctions.contains(middleware)) { 16 | return this; 17 | } 18 | 19 | _middlewareFunctions.add(middleware); 20 | return this; 21 | } 22 | 23 | /// Unregister middleware function 24 | /// 25 | /// @param middleware The middleware function to remove 26 | MiddlewareSystem unuse(T? Function(T a, [U? b, V? c]) middleware) { 27 | final idx = _middlewareFunctions.indexOf(middleware); 28 | if (idx != -1) { 29 | _middlewareFunctions.removeAt(idx); 30 | } 31 | 32 | return this; 33 | } 34 | 35 | /// Process values through this middleware 36 | /// @param a Required, this is the value modified/passed through each middleware fn 37 | /// @param b Optional extra argument passed to each middleware function 38 | /// @param c Optional extra argument passed to each middleware function 39 | Future process(T a, [U? b, V? c]) async { 40 | T? val = a; 41 | 42 | for (final fn in _middlewareFunctions) { 43 | if (val == null) { 44 | return null; 45 | } 46 | 47 | val = await fn(val, b, c); 48 | } 49 | 50 | return val; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/client/flutter_gun_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import '../client/transports/web_socket_graph_connector.dart'; 4 | 5 | import '../crdt/index.dart'; 6 | import '../types/flutter_gun.dart'; 7 | import 'flutter_gun_link.dart'; 8 | import 'graph/gun_graph.dart'; 9 | import 'graph/gun_graph_utils.dart'; 10 | import 'interfaces.dart'; 11 | 12 | class FlutterGunOptions { 13 | late final List peers; 14 | GunGraph? graph; 15 | 16 | merge(FlutterGunOptions flutterGunOptions) { 17 | peers = flutterGunOptions.peers; 18 | graph = flutterGunOptions.graph; 19 | // else { 20 | // graph = GunGraph(); 21 | // } 22 | // graph?.use(diffGunCRDT); 23 | // graph?.use(diffGunCRDT, kind: FlutterGunMiddlewareType.write); 24 | } 25 | } 26 | 27 | class FlutterGunClient { 28 | GunGraph? graph; 29 | late FlutterGunOptions _opt; 30 | FlutterGunLink? linkClass; 31 | 32 | FlutterGunClient({this.linkClass, FlutterGunOptions? flutterGunOptions}) { 33 | initializedClient(linkClass: linkClass, flutterGunOptions: flutterGunOptions); 34 | } 35 | 36 | initializedClient({linkClass, FlutterGunOptions? flutterGunOptions}) { 37 | if (flutterGunOptions?.peers == null) { 38 | return; 39 | } 40 | this.linkClass = linkClass; 41 | 42 | if (!isNull(flutterGunOptions) && !isNull(flutterGunOptions?.graph)) { 43 | graph = flutterGunOptions!.graph!; 44 | } else { 45 | graph = GunGraph(); 46 | graph!.use(diffGunCRDT); 47 | graph!.use(diffGunCRDT, kind: FlutterGunMiddlewareType.write); 48 | } 49 | _opt = FlutterGunOptions(); 50 | if (!isNull(flutterGunOptions)) { 51 | opt(flutterGunOptions!); 52 | } 53 | } 54 | 55 | /// Set FlutterGun configuration options 56 | /// 57 | /// @param options 58 | FlutterGunClient opt(FlutterGunOptions options) { 59 | _opt.merge(options); 60 | 61 | if (options.peers.isNotEmpty) { 62 | for (var peer in options.peers) { 63 | final connector = WebSocketGraphConnector(url: peer); 64 | connector.sendPutsFromGraph(graph!); 65 | connector.sendRequestsFromGraph(graph!); 66 | graph!.connect(connector); 67 | } 68 | } 69 | 70 | return this; 71 | } 72 | 73 | /// Traverse a location in the graph 74 | /// 75 | /// @param key Key to read data from 76 | /// @param cb 77 | /// @returns New flutter context corresponding to given key 78 | FlutterGunLink get(String soul, [GunMsgCb? cb]) { 79 | return linkClass = FlutterGunLink(key: soul, flutter: this); 80 | } 81 | 82 | /// Traverse a location in the graph and Return the data 83 | /// 84 | /// @param key Key to read data from 85 | /// @param cb 86 | /// @returns New flutter context corresponding to given key 87 | Future getValue(String soul, [GunMsgCb? cb]) async { 88 | final tempFlutterGunLink = FlutterGunLink(key: soul, flutter: this); 89 | var completer = Completer(); 90 | tempFlutterGunLink.once((a, [b, c]) { 91 | completer.complete(a); 92 | }); 93 | 94 | return completer.future; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/src/client/flutter_gun_link.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import '../types/gun_graph_adapter.dart'; 4 | 5 | import '../types/flutter_gun.dart'; 6 | import '../types/gun.dart'; 7 | import 'flutter_gun_client.dart'; 8 | import 'control_flow/gun_event.dart'; 9 | import 'graph/gun_graph_utils.dart'; 10 | import 'interfaces.dart'; 11 | 12 | class FlutterGunLink { 13 | final String key; 14 | late String? soul; 15 | 16 | late GunFlutterOptions _opt; 17 | late final GunEvent _updateEvent; 18 | late final FlutterGunClient _flutter; 19 | FlutterGunLink? _parent; 20 | VoidCallback? _endQuery; 21 | GunValue? _lastValue; 22 | late bool _hasReceived; 23 | 24 | FlutterGunLink( 25 | {required this.key, 26 | required FlutterGunClient flutter, 27 | FlutterGunLink? parent}) { 28 | if (isNull(parent)) { 29 | soul = key; 30 | } 31 | _opt = GunFlutterOptions(); 32 | _flutter = flutter; 33 | _parent = parent; 34 | _hasReceived = false; 35 | _updateEvent = 36 | GunEvent(name: getPath().join('|')); 37 | } 38 | 39 | /// @returns path of this node 40 | List getPath() { 41 | if (!isNull(_parent)) { 42 | return [...?_parent?.getPath(), key]; 43 | } 44 | 45 | return [key]; 46 | } 47 | 48 | /// Traverse a location in the graph 49 | /// 50 | /// @param key Key to read data from 51 | /// @param cb 52 | /// @returns New flutter context corresponding to given key 53 | FlutterGunLink get(String key, [GunMsgCb? cb]) { 54 | return FlutterGunLink(key: key, flutter: _flutter, parent: this); 55 | } 56 | 57 | /// Move up to the parent context on the flutter. 58 | /// 59 | /// Every time a new flutter is created, a reference to the old context is kept to go back to. 60 | /// 61 | /// @param amount The number of times you want to go back up the flutter. {-1} or {Infinity} will take you to the root. 62 | /// @returns a parent flutter context 63 | dynamic back([amount = 1]) { 64 | if (amount < 0 || amount == double.maxFinite.toInt()) { 65 | return _flutter; 66 | } 67 | if (amount == 1) { 68 | return _parent ?? _flutter; 69 | } 70 | return back(amount - 1); 71 | } 72 | 73 | /// Save data into gun, syncing it with your connected peers. 74 | /// 75 | /// You do not need to re-save the entire object every time, gun will automatically 76 | /// merge your data into what already exists as a "partial" update. 77 | /// 78 | /// @param value the data to save 79 | /// @param cb an optional callback, invoked on each acknowledgment 80 | /// @returns same flutter context 81 | FlutterGunLink put(GunValue value, [GunMsgCb? cb]) { 82 | _flutter.graph!.putPath(getPath(), value, cb, opt().uuid); 83 | return this; 84 | } 85 | 86 | /// Add a unique item to an unordered list. 87 | /// 88 | /// Works like a mathematical set, where each item in the list is unique. 89 | /// If the item is added twice, it will be merged. 90 | /// This means only objects, for now, are supported. 91 | /// 92 | /// @param data should be a gun reference or an object 93 | /// @param cb The callback is invoked exactly the same as .put 94 | /// @returns flutter context for added object 95 | FlutterGunLink set(dynamic data, [GunMsgCb? cb]) { 96 | if (data is FlutterGunLink && !isNull(data.soul)) { 97 | final temp = {}; 98 | temp[data.soul] = {'#': data.soul}; 99 | put(temp, cb); 100 | } else if (data is GunNode) { 101 | final temp = {}; 102 | temp[data.nodeMetaData?.key] = data; 103 | put(temp, cb); 104 | } else { 105 | throw ('set() is only partially supported'); 106 | } 107 | 108 | return this; 109 | } 110 | 111 | /// Register a callback for when it appears a record does not exist 112 | /// 113 | /// If you need to know whether a property or key exists, you can check with .not. 114 | /// It will consult the connected peers and invoke the callback if there's reasonable certainty that none of them have the data available. 115 | /// 116 | /// @param cb If there's reason to believe the data doesn't exist, the callback will be invoked. This can be used as a check to prevent implicitly writing data 117 | /// @returns same flutter context 118 | FlutterGunLink not(void Function(String key) cb) { 119 | promise().then((val) { 120 | if (isNull(val)) { 121 | cb(key); 122 | } 123 | }); 124 | return this; 125 | } 126 | 127 | /// Change the configuration of this flutter link 128 | /// 129 | /// @param options 130 | /// @returns current options 131 | GunFlutterOptions opt([GunFlutterOptions? options]) { 132 | if (!isNull(options)) { 133 | _opt = options!; 134 | } 135 | if (!isNull(_parent)) { 136 | return _opt; 137 | } 138 | return _opt; 139 | } 140 | 141 | /// Get the current data without subscribing to updates. Or undefined if it cannot be found. 142 | /// 143 | /// @param cb The data is the value for that flutter at that given point in time. And the key is the last property name or ID of the node. 144 | /// @returns same flutter context 145 | FlutterGunLink once(GunOnCb cb) { 146 | promise().then((val) => cb(val, key)); 147 | return this; 148 | } 149 | 150 | /// Subscribe to updates and changes on a node or property in realtime. 151 | /// 152 | /// Triggered once initially and whenever the property or node you're focused on changes, 153 | /// Since gun streams data, the callback will probably be called multiple times as new chunk comes in. 154 | /// 155 | /// To remove a listener call .off() on the same property or node. 156 | /// 157 | /// @param cb The callback is immediately fired with the data as it is at that point in time. 158 | /// @returns same flutter context 159 | FlutterGunLink on(GunOnCb cb) { 160 | if (key == '') { 161 | // TODO: "Map logic" 162 | } 163 | 164 | _updateEvent.on(cb); 165 | if (isNull(_endQuery)) { 166 | _endQuery = _flutter.graph!.query(getPath(), _onQueryResponse); 167 | } 168 | if (_hasReceived) { 169 | cb(_lastValue, key); 170 | } 171 | return this; 172 | } 173 | 174 | /// Unsubscribe one or all listeners subscribed with on 175 | /// 176 | /// @returns same flutter context 177 | FlutterGunLink off([GunOnCb? cb]) { 178 | if (!isNull(cb)) { 179 | _updateEvent.off(cb!); 180 | if (!isNull(_endQuery) && _updateEvent.listenerCount() == 0) { 181 | _endQuery!(); 182 | } 183 | } else { 184 | if (!isNull(_endQuery)) { 185 | _endQuery!(); 186 | } 187 | _updateEvent.reset(); 188 | } 189 | return this; 190 | } 191 | 192 | Future promise([timeout = 0]) { 193 | var completer = Completer(); 194 | 195 | cb(GunValue val, [String? _, dynamic __]) { 196 | completer.complete(val); 197 | off(cb); 198 | } 199 | on(cb); 200 | 201 | if (timeout > 0) { 202 | Timer(Duration(milliseconds: timeout), () { completer.complete(null); }); 203 | } 204 | 205 | return completer.future; 206 | } 207 | 208 | Future then(dynamic Function(GunValue gunValue) fn) { 209 | return promise().then(fn); 210 | } 211 | 212 | /// Iterates over each property and item on a node, passing it down the flutter 213 | /// 214 | /// Not yet supported 215 | /// 216 | /// Behaves like a forEach on your data. 217 | /// It also subscribes to every item as well and listens for newly inserted items. 218 | /// 219 | /// @returns a new flutter context holding many flutters simultaneously. 220 | FlutterGunLink map() { 221 | throw ("map() isn't supported yet"); 222 | } 223 | 224 | /// No plans to support this 225 | FlutterGunLink path(String path) { 226 | throw ('No plans to support this'); 227 | } 228 | 229 | /// No plans to support this 230 | FlutterGunLink open(dynamic cb) { 231 | throw ('No plans to support this'); 232 | } 233 | 234 | /// No plans to support this 235 | FlutterGunLink load(dynamic cb) { 236 | throw ('No plans to support this'); 237 | } 238 | 239 | /// No plans to support this 240 | FlutterGunLink bye() { 241 | throw ('No plans to support this'); 242 | } 243 | 244 | /// No plans to support this 245 | FlutterGunLink later() { 246 | throw ('No plans to support this'); 247 | } 248 | 249 | /// No plans to support this 250 | FlutterGunLink unset(GunNode node) { 251 | throw ('No plans to support this'); 252 | } 253 | 254 | void _onQueryResponse(GunValue? value, [String? _, dynamic __]) { 255 | _updateEvent.trigger(value, key); 256 | _lastValue = value; 257 | _hasReceived = true; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /lib/src/client/graph/gun_graph.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:collection'; 3 | 4 | import '../../crdt/index.dart'; 5 | import '../../types/flutter_gun.dart'; 6 | import '../../types/enum.dart'; 7 | import '../../types/generic.dart'; 8 | import '../../types/gun.dart'; 9 | import '../../types/gun_graph_adapter.dart'; 10 | import '../control_flow/gun_event.dart'; 11 | import '../interfaces.dart'; 12 | import '../transports/gun_graph_connector.dart'; 13 | import 'gun_graph_node.dart'; 14 | import 'gun_graph_utils.dart'; 15 | 16 | class GunGraphOptions { 17 | MutableEnum? mutable; 18 | } 19 | 20 | typedef UUIDFuncType = FutureOr Function(List path); 21 | typedef GraphConnectorFuncType = void Function(GunGraphConnector connector); 22 | 23 | class GunGraphEvent { 24 | final GunEvent graphData; 25 | 26 | final GunEvent put; 27 | final GunEvent get; 28 | final GunEvent off; 29 | 30 | GunGraphEvent( 31 | {required this.graphData, 32 | required this.put, 33 | required this.get, 34 | required this.off}); 35 | } 36 | 37 | class GunGraphNodeMap extends GenericCustomValueMap {} 38 | 39 | class GunGraph { 40 | late final String id; 41 | 42 | late final GunGraphEvent events; 43 | 44 | late num activeConnectors; 45 | 46 | late final GunGraphOptions _opt; 47 | 48 | late final List _connectors; 49 | 50 | late final List _readMiddleware; 51 | 52 | late final List _writeMiddleware; 53 | 54 | late final GunGraphData _graph; 55 | 56 | late final GunGraphNodeMap _nodes; 57 | 58 | GunGraph() { 59 | id = generateMessageId(); 60 | activeConnectors = 0; 61 | events = GunGraphEvent( 62 | graphData: GunEvent(name: 'graph data'), 63 | get: GunEvent(name: 'request soul'), 64 | off: GunEvent(name: 'off event'), 65 | put: GunEvent(name: 'put data'), 66 | ); 67 | _opt = GunGraphOptions(); 68 | _opt.mutable = MutableEnum.immutable; 69 | _graph = GunGraphData(); 70 | _nodes = GunGraphNodeMap(); 71 | _connectors = []; 72 | _readMiddleware = []; 73 | _writeMiddleware = []; 74 | } 75 | 76 | /// Configure graph options 77 | /// 78 | /// Currently unused 79 | /// 80 | /// @param options 81 | GunGraph opt(GunGraphOptions options) { 82 | _opt = options; 83 | return this; 84 | } 85 | 86 | GunGraphOptions getOpt() { 87 | return _opt; 88 | } 89 | 90 | /// Connect to a source/destination for graph data 91 | /// 92 | /// @param connector the source or destination for graph data 93 | GunGraph connect(GunGraphConnector connector) { 94 | if (_connectors.contains(connector)) { 95 | return this; 96 | } 97 | _connectors.add(connector.connectToGraph(this)); 98 | 99 | connector.events.connection.on(__onConnectorStatus); 100 | connector.events.graphData.on(_receiveGraphData); 101 | 102 | if (connector.isConnected) { 103 | activeConnectors++; 104 | } 105 | return this; 106 | } 107 | 108 | /// Disconnect from a source/destination for graph data 109 | /// 110 | /// @param connector the source or destination for graph data 111 | GunGraph disconnect(GunGraphConnector connector) { 112 | final idx = _connectors.indexOf(connector); 113 | connector.events.graphData.off(_receiveGraphData); 114 | connector.events.connection.off(__onConnectorStatus); 115 | if (idx != -1) { 116 | _connectors.removeAt(idx); 117 | } 118 | // TODO CHECK IF isConnected is true for the disconnect or not 119 | if (connector.isConnected) { 120 | activeConnectors--; 121 | } 122 | return this; 123 | } 124 | 125 | /// Register graph middleware 126 | /// 127 | /// @param middleware The middleware function to add 128 | /// @param kind Optionaly register write middleware instead of read by passing "write" 129 | GunGraph use(FlutterGunMiddleware middleware, 130 | {FlutterGunMiddlewareType kind = FlutterGunMiddlewareType.read}) { 131 | if (kind == FlutterGunMiddlewareType.read) { 132 | _readMiddleware.add(middleware); 133 | } else if (kind == FlutterGunMiddlewareType.write) { 134 | _writeMiddleware.add(middleware); 135 | } 136 | return this; 137 | } 138 | 139 | /// Unregister graph middleware 140 | /// 141 | /// @param middleware The middleware function to remove 142 | /// @param kind Optionaly unregister write middleware instead of read by passing "write" 143 | GunGraph unuse(FlutterGunMiddleware middleware, 144 | {FlutterGunMiddlewareType kind = FlutterGunMiddlewareType.read}) { 145 | if (kind == FlutterGunMiddlewareType.read) { 146 | final idx = _readMiddleware.indexOf(middleware); 147 | if (idx != -1) { 148 | _readMiddleware.removeAt(idx); 149 | } 150 | } else if (kind == FlutterGunMiddlewareType.write) { 151 | final idx = _writeMiddleware.indexOf(middleware); 152 | if (idx != -1) { 153 | _writeMiddleware.removeAt(idx); 154 | } 155 | } 156 | 157 | return this; 158 | } 159 | 160 | /// Read a potentially multi-level deep path from the graph 161 | /// 162 | /// @param path The path to read 163 | /// @param cb The callback to invoke with results 164 | /// @returns a cleanup function to after done with query 165 | VoidCallback query(List path, GunOnCb cb) { 166 | List lastSouls = []; 167 | GunValue currentValue; 168 | 169 | updateQuery(GunNode? _, [dynamic __, dynamic ___]) { 170 | PathData getPathDateList = getPathData(path, _graph); 171 | 172 | List souls = getPathDateList.souls; 173 | GunValue value = getPathDateList.value; 174 | bool complete = getPathDateList.complete; 175 | 176 | final diffSetsList = diffSets(lastSouls, souls); 177 | 178 | List added = diffSetsList[0]; 179 | List removed = diffSetsList[1]; 180 | 181 | if ((complete && currentValue == null) || 182 | (value != null && value != currentValue)) { 183 | currentValue = value; 184 | cb(value, path[path.length - 1]); 185 | } 186 | 187 | for (final soul in added) { 188 | _requestSoul(soul, updateQuery); 189 | } 190 | 191 | for (final soul in removed) { 192 | _unlistenSoul(soul, updateQuery); 193 | } 194 | 195 | lastSouls = souls; 196 | } 197 | 198 | updateQuery(null); 199 | 200 | return () { 201 | for (final soul in lastSouls) { 202 | _unlistenSoul(soul, updateQuery); 203 | } 204 | }; 205 | } 206 | 207 | FutureOr _internalUUIdFn(List path) { 208 | return path.join('/'); 209 | } 210 | 211 | GunGraphData _getPutPathGunGraph(List souls, GunValue data) { 212 | // Create a new Map for the converted JSON 213 | GunGraphData data2 = GunGraphData(); 214 | var data1 = {}; 215 | var temp = data1; 216 | for (var i = 0; i < souls.length; i++) { 217 | if (i != souls.length - 1) { 218 | temp[souls[i]] = {}; 219 | temp = temp[souls[i]]; 220 | } else { 221 | temp[souls[i]] = data; 222 | } 223 | } 224 | 225 | // Create a queue to store the keys and values that need to be processed 226 | var queue = Queue(); 227 | var pathQueue = Queue(); 228 | 229 | // Add the root data to the queue 230 | queue.addAll(data1.entries); 231 | for (var i = 0; i < data1.entries.length; i++) { 232 | pathQueue.add(""); 233 | } 234 | 235 | // Keep processing the keys and values in the queue until it is empty 236 | while (queue.isNotEmpty) { 237 | // Get the next key and value from the queue 238 | var entry = queue.removeFirst(); 239 | var key = entry.key; 240 | var value = entry.value; 241 | var path = ""; 242 | if (pathQueue.isNotEmpty) { 243 | path = pathQueue.removeFirst(); 244 | } 245 | // Concatenate the current key to the path 246 | var currentPath = path.isEmpty ? key : path.contains("~@") ? key : '$path/$key'; 247 | 248 | // Check if the value is a Map (i.e. another nested dictionary) 249 | if (value is Map) { 250 | // If it is a Map, create a new Map for the converted data 251 | Map currentData2 = {}; 252 | 253 | // Add the metadata to the Map 254 | currentData2['_'] = {'#': currentPath, '>': {}}; 255 | 256 | for (final entry in value.entries) { 257 | currentData2['_']['>'][entry.key] = 258 | DateTime.now().millisecondsSinceEpoch; 259 | if (entry.value is Map) { 260 | currentData2[entry.key] = {"#": currentPath.contains("~@") ? entry.key : "$currentPath/${entry.key}"}; 261 | } else { 262 | currentData2[entry.key] = entry.value; 263 | } 264 | } 265 | 266 | // Add the Map to the converted data 267 | data2[currentPath] = GunNode.fromJson(currentData2); 268 | 269 | // Add the nested data to the queue 270 | queue.addAll(value.entries); 271 | for (var i = 0; i < value.entries.length; i++) { 272 | pathQueue.add(currentPath); 273 | } 274 | } 275 | } 276 | 277 | return data2; 278 | } 279 | 280 | /// Write graph data to a potentially multi-level deep path in the graph 281 | /// 282 | /// @param path The path to read 283 | /// @param data The value to write 284 | /// @param cb Callback function to be invoked for write acks 285 | /// @returns a promise 286 | Future putPath(final List fullPath, GunValue data, [GunMsgCb? cb, 287 | UUIDFuncType? uuidFn]) async { 288 | uuidFn ??= _internalUUIdFn; 289 | if (fullPath.isEmpty) { 290 | throw ("No path specified"); 291 | } 292 | 293 | GunGraphData graph = _getPutPathGunGraph(fullPath, data); 294 | 295 | put(graph, cb); 296 | } 297 | 298 | Future> getPathSouls(List path) async { 299 | var completer = Completer>(); 300 | 301 | if (path.length == 1) { 302 | completer.complete(path); 303 | } 304 | 305 | List lastSouls = []; 306 | 307 | updateQuery(GunNode? _, [dynamic __, dynamic ___]) { 308 | PathData getPathDataList = getPathData(path, _graph); 309 | 310 | List souls = getPathDataList.souls; 311 | bool complete = getPathDataList.complete; 312 | 313 | // print('updateQuery: ${souls.toString()} -- $complete'); 314 | 315 | final diffSetsList = diffSets(lastSouls, souls); 316 | 317 | dynamic added = diffSetsList[0]; 318 | dynamic removed = diffSetsList[1]; 319 | 320 | // print('diffSetsList:: ${added.toString()} -- ${removed.toString()}'); 321 | 322 | end() { 323 | for (final soul in lastSouls) { 324 | _unlistenSoul(soul, updateQuery); 325 | } 326 | lastSouls = []; 327 | } 328 | 329 | if (complete) { 330 | end(); 331 | if (!completer.isCompleted) { 332 | completer.complete(souls); 333 | } 334 | return; 335 | } else { 336 | for (final soul in added) { 337 | _requestSoul(soul, updateQuery); 338 | } 339 | 340 | for (final soul in removed) { 341 | _unlistenSoul(soul, updateQuery); 342 | } 343 | } 344 | 345 | lastSouls = souls; 346 | } 347 | 348 | updateQuery(null); 349 | 350 | return completer.future; 351 | } 352 | 353 | /// Request node data 354 | /// 355 | /// @param soul identifier of node to request 356 | /// @param cb callback for response messages 357 | /// @param msgId optional unique message identifier 358 | /// @returns a function to cleanup listeners when done 359 | VoidCallback get(String soul, [GunMsgCb? cb, String? msgId]) { 360 | String id = msgId ?? generateMessageId(); 361 | 362 | events.get.trigger(FlutterGunGet(cb: cb, msgId: msgId, soul: soul)); 363 | 364 | return () => events.off.trigger(id); 365 | } 366 | 367 | /// Write node data 368 | /// 369 | /// @param data one or more gun nodes keyed by soul 370 | /// @param cb optional callback for response messages 371 | /// @param msgId optional unique message identifier 372 | /// @returns a function to clean up listeners when done 373 | VoidCallback put(GunGraphData data, [GunMsgCb? cb, String? msgId]) { 374 | GunGraphData? diff = flattenGraphData(addMissingState(data)); 375 | 376 | final String id = msgId ?? generateMessageId(); 377 | (() async { 378 | for (final fn in _writeMiddleware) { 379 | if (diff == null) { 380 | return; 381 | } 382 | diff = await fn(diff!, _graph); 383 | } 384 | if (diff == null) { 385 | return; 386 | } 387 | 388 | // print('Data-->Encoded::Sent:: ${jsonEncode(diff)}'); 389 | 390 | events.put.trigger(FlutterGunPut(graph: diff!, cb: cb, msgId: id)); 391 | 392 | _receiveGraphData(diff!); 393 | })(); 394 | 395 | return () => events.off.trigger(id); 396 | } 397 | 398 | /// Synchronously invoke callback function for each connector to this graph 399 | /// 400 | /// @param cb The callback to invoke 401 | GunGraph eachConnector(GraphConnectorFuncType cb) { 402 | for (final connector in _connectors) { 403 | cb(connector); 404 | } 405 | 406 | return this; 407 | } 408 | 409 | /// Update graph data in this flutter from some local or external source 410 | /// 411 | /// @param data node data to include 412 | FutureOr _receiveGraphData(GunGraphData data, [String? id, String? replyToId]) async { 413 | GunGraphData? diff = data; 414 | 415 | for (final fn in _readMiddleware) { 416 | if (diff == null) { 417 | return; 418 | } 419 | diff = await fn(diff, _graph); 420 | } 421 | 422 | if (diff == null) { 423 | return; 424 | } 425 | 426 | for (final soul in diff.keys) { 427 | final node = _nodes[soul]; 428 | if (node == null) { 429 | continue; 430 | } 431 | node.receive((_graph[soul] = 432 | mergeGunNodes(_graph[soul], diff[soul], mut: _opt.mutable!))); 433 | } 434 | 435 | events.graphData.trigger(diff, id, replyToId); 436 | } 437 | 438 | GunGraphNode _node(String soul) { 439 | return (_nodes[soul] = _nodes[soul] ?? 440 | GunGraphNode(graph: this, soul: soul, updateGraph: _receiveGraphData)); 441 | } 442 | 443 | GunGraph _requestSoul(String soul, GunNodeListenCb cb) { 444 | _node(soul).get(cb); 445 | return this; 446 | } 447 | 448 | GunGraph _unlistenSoul(String soul, GunNodeListenCb cb) { 449 | if (!_nodes.containsKey(soul)) { 450 | return this; 451 | } 452 | final node = _nodes[soul]; 453 | if (node == null) { 454 | return this; 455 | } 456 | node.off(cb); 457 | if (node.listenerCount() <= 0) { 458 | node.off(); 459 | _forgetSoul(soul); 460 | } 461 | return this; 462 | } 463 | 464 | GunGraph _forgetSoul(String soul) { 465 | if (!_nodes.containsKey(soul)) { 466 | return this; 467 | } 468 | final node = _nodes[soul]; 469 | if (node != null) { 470 | node.off(); 471 | _nodes.remove(soul); 472 | } 473 | 474 | _graph.remove(soul); 475 | return this; 476 | } 477 | 478 | void __onConnectorStatus(bool connected, [dynamic _, dynamic __]) { 479 | if (connected == true) { 480 | activeConnectors++; 481 | } else { 482 | activeConnectors--; 483 | } 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /lib/src/client/graph/gun_graph_node.dart: -------------------------------------------------------------------------------- 1 | import '../../types/gun_graph_adapter.dart'; 2 | 3 | import '../../types/gun.dart'; 4 | import '../control_flow/gun_event.dart'; 5 | import '../interfaces.dart'; 6 | import 'gun_graph.dart'; 7 | 8 | typedef UpdateGraphFunc = void Function(GunGraphData data, 9 | [String? id, String? replyToId]); 10 | 11 | class GunGraphNode { 12 | final String soul; 13 | 14 | late final GunEvent _data; 15 | late final GunGraph _graph; 16 | VoidCallback? _endCurQuery; 17 | late final UpdateGraphFunc _updateGraph; 18 | 19 | GunGraphNode( 20 | {required this.soul, 21 | required GunGraph graph, 22 | required UpdateGraphFunc updateGraph}) { 23 | _graph = graph; 24 | _updateGraph = updateGraph; 25 | _data = GunEvent(name: ''); 26 | } 27 | 28 | num listenerCount() { 29 | return _data.listenerCount(); 30 | } 31 | 32 | GunGraphNode get(GunNodeListenCb? cb) { 33 | if (cb != null) { 34 | on(cb); 35 | } 36 | _ask(); 37 | return this; 38 | } 39 | 40 | GunGraphNode on(GunNodeListenCb cb) { 41 | _data.on(cb); 42 | return this; 43 | } 44 | 45 | GunGraphNode off([GunNodeListenCb? cb]) { 46 | if (cb != null) { 47 | _data.off(cb); 48 | } else { 49 | _data.reset(); 50 | } 51 | 52 | if (_endCurQuery != null && _data.listenerCount() == 0) { 53 | _endCurQuery!(); 54 | _endCurQuery = null; 55 | } 56 | 57 | return this; 58 | } 59 | 60 | GunGraphNode receive(GunNode? data) { 61 | _data.trigger(data, soul); 62 | return this; 63 | } 64 | 65 | GunGraphNode _ask() { 66 | if (_endCurQuery != null) { 67 | return this; 68 | } 69 | 70 | _graph.get(soul, _onDirectQueryReply); 71 | return this; 72 | } 73 | 74 | void _onDirectQueryReply(GunMsg msg) { 75 | if (msg.put == null) { 76 | GunGraphData gunGraphData = GunGraphData(); 77 | gunGraphData[soul] = null; 78 | _updateGraph(gunGraphData, msg.pos); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/src/client/graph/gun_graph_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:uuid/uuid.dart'; 4 | 5 | import '../../crdt/index.dart'; 6 | import '../../types/gun.dart'; 7 | import '../flutter_gun_link.dart'; 8 | import '../interfaces.dart'; 9 | import 'gun_graph_node.dart'; 10 | 11 | String generateMessageId() { 12 | return const Uuid().v4(); 13 | } 14 | 15 | List> diffSets( 16 | final List initial, final List updated) { 17 | return [ 18 | updated.where((key) => !initial.contains(key)).toList(), 19 | initial.where((key) => !updated.contains(key)).toList() 20 | ]; 21 | } 22 | 23 | bool isObject(Object? node) { 24 | return !(node is int || 25 | node is String || 26 | node is num || 27 | num is double || 28 | node is bool || 29 | node == null); 30 | } 31 | 32 | bool isArray(Object? node) { 33 | return !(node == null || node is! List); 34 | } 35 | 36 | bool isMap(Object? node) { 37 | return !(node == null || node is! Map || node is! MapBase); 38 | } 39 | 40 | bool isNull(Object? node) { 41 | return node == null; 42 | } 43 | 44 | GunGraphData nodeToGraph(GunNode node) { 45 | final modified = {...node}; 46 | GunGraphData nodes = GunGraphData(); 47 | final nodeSoul = node.nodeMetaData?.key; 48 | 49 | for (final key in node.keys) { 50 | final val = node[key]; 51 | if (!isObject(val) || val == null) { 52 | continue; 53 | } 54 | 55 | if (val is GunGraphNode) { 56 | if (val.soul.isNotEmpty) { 57 | final edge = {'#': val.soul}; 58 | modified[key] = edge; 59 | 60 | continue; 61 | } 62 | } 63 | 64 | String soul = ''; 65 | 66 | if (val is GunNode) { 67 | soul = val.nodeMetaData!.key!; 68 | } 69 | 70 | if (val is FlutterGunLink && val.soul != null && val.soul!.isNotEmpty) { 71 | soul = val.soul!; 72 | } 73 | 74 | if (soul.isNotEmpty) { 75 | final edge = {'#': soul}; 76 | modified[key] = edge; 77 | final graph = addMissingState(nodeToGraph(val)); 78 | final diff = diffGunCRDT(graph, nodes); 79 | nodes = !isNull(diff) ? mergeGraph(nodes, diff!) : nodes; 80 | } 81 | } 82 | 83 | // print('SD:: ${modified.toString()} $nodeSoul'); 84 | 85 | GunGraphData raw = GunGraphData(); 86 | raw[nodeSoul!] = GunNode.fromJson(modified); 87 | final withMissingState = addMissingState(raw); 88 | final graphDiff = diffGunCRDT(withMissingState, nodes); 89 | nodes = !isNull(graphDiff) ? mergeGraph(nodes, graphDiff!) : nodes; 90 | 91 | return nodes; 92 | } 93 | 94 | GunGraphData flattenGraphData(GunGraphData data) { 95 | final List graphs = []; 96 | GunGraphData flatGraph = GunGraphData(); 97 | 98 | for (final soul in data.keys) { 99 | final node = data[soul]; 100 | if (!isNull(node)) { 101 | graphs.add(nodeToGraph(node!)); 102 | } 103 | } 104 | 105 | for (final graph in graphs) { 106 | final diff = diffGunCRDT(graph, flatGraph); 107 | flatGraph = !isNull(diff) ? mergeGraph(flatGraph, diff!) : flatGraph; 108 | } 109 | 110 | return flatGraph; 111 | } 112 | 113 | PathData getPathData(List keys, GunGraphData graph) { 114 | final lastKey = keys[keys.length - 1]; 115 | 116 | if (keys.length == 1) { 117 | return PathData( 118 | souls: keys, 119 | complete: graph.containsKey(lastKey), 120 | value: graph[lastKey]); 121 | } 122 | 123 | PathData getPathDataParent = 124 | getPathData(keys.sublist(0, keys.length - 1), graph); 125 | 126 | if (!isObject(getPathDataParent.value)) { 127 | return PathData( 128 | souls: getPathDataParent.souls, 129 | complete: getPathDataParent.complete || !isNull(getPathDataParent.value), 130 | value: null); 131 | } 132 | 133 | final value = getPathDataParent.value[lastKey]; 134 | 135 | if (isNull(value)) { 136 | return PathData( 137 | souls: getPathDataParent.souls, complete: true, value: value); 138 | } 139 | 140 | var edgeSoul; 141 | 142 | if (isObject(value)) { 143 | edgeSoul = value['#']; 144 | } 145 | 146 | if (!isNull(edgeSoul)) { 147 | return PathData( 148 | souls: [...getPathDataParent.souls, edgeSoul], 149 | complete: graph.containsKey(edgeSoul), 150 | value: graph[edgeSoul]); 151 | } 152 | 153 | return PathData(souls: getPathDataParent.souls, complete: true, value: value); 154 | } 155 | 156 | -------------------------------------------------------------------------------- /lib/src/client/interfaces.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import '../types/gun.dart'; 4 | import 'control_flow/gun_event.dart'; 5 | 6 | typedef FutureOrStringFunc = FutureOr Function(List path); 7 | 8 | class GunFlutterOptions { 9 | FutureOrStringFunc? uuid; 10 | } 11 | 12 | typedef GunOnCb = EventCb; 13 | typedef GunNodeListenCb = EventCb; 14 | 15 | class PathData { 16 | final List souls; 17 | final GunValue value; 18 | final bool complete; 19 | 20 | PathData({ required this.souls, this.value, this.complete = false }); 21 | } 22 | 23 | typedef FlutterGunMiddleware = FutureOr Function(GunGraphData updates, GunGraphData existingGraph); 24 | 25 | enum FlutterGunMiddlewareType { 26 | read, 27 | write 28 | } -------------------------------------------------------------------------------- /lib/src/client/transports/gun_graph_connector.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ui'; 3 | 4 | import '../../types/flutter_gun.dart'; 5 | import '../../types/gun.dart'; 6 | import '../control_flow/gun_event.dart'; 7 | import '../control_flow/gun_process_queue.dart'; 8 | import '../graph/gun_graph.dart'; 9 | 10 | class GunGraphConnectorEventType { 11 | final GunEvent graphData; 12 | 13 | final GunEvent receiveMessage; 14 | final GunEvent connection; 15 | 16 | GunGraphConnectorEventType( 17 | {required this.graphData, 18 | required this.receiveMessage, 19 | required this.connection}); 20 | } 21 | 22 | abstract class GunGraphConnector { 23 | final String name; 24 | late bool isConnected; 25 | 26 | late final GunGraphConnectorEventType events; 27 | 28 | late final GunProcessQueue inputQueue; 29 | 30 | late final GunProcessQueue outputQueue; 31 | 32 | GunGraphConnector({ this.name = 'GunGraphConnector' }) { 33 | isConnected = false; 34 | inputQueue = 35 | GunProcessQueue(name: '$name.inputQueue'); 36 | outputQueue = 37 | GunProcessQueue(name: '$name.outputQueue'); 38 | events = GunGraphConnectorEventType( 39 | graphData: GunEvent( 40 | name: '$name.events.graphData'), 41 | receiveMessage: GunEvent( 42 | name: '$name.events.receiveMessage'), 43 | connection: GunEvent(name: '$name.events.connection'), 44 | ); 45 | events.connection.on(__onConnectedChange); 46 | } 47 | 48 | GunGraphConnector off(String msgId, [dynamic _, dynamic __]) { 49 | return this; 50 | } 51 | 52 | GunGraphConnector sendPutsFromGraph(GunGraph graph) { 53 | graph.events.put.on(put); 54 | return this; 55 | } 56 | 57 | GunGraphConnector sendRequestsFromGraph(GunGraph graph) { 58 | graph.events.get.on((req, [dynamic _, dynamic __]) { 59 | get(req); 60 | }); 61 | return this; 62 | } 63 | 64 | FutureOr waitForConnection() { 65 | var completer = Completer(); 66 | 67 | if (isConnected) { 68 | return Future.value(); 69 | } 70 | onConnected(bool? connected, [dynamic _, dynamic __]) { 71 | if (!(connected ?? false)) { 72 | return; 73 | } 74 | completer.complete(); 75 | events.connection.off(onConnected); 76 | } 77 | 78 | events.connection.on(onConnected); 79 | 80 | return completer.future; 81 | } 82 | 83 | /// Send graph data for one or more nodes 84 | /// 85 | /// @returns A function to be called to clean up callback listeners 86 | VoidCallback put(FlutterGunPut params, [dynamic _, dynamic __]) { 87 | return () {}; 88 | } 89 | 90 | /// Request data for a given soul 91 | /// 92 | /// @returns A function to be called to clean up callback listeners 93 | VoidCallback get(FlutterGunGet params, [dynamic _, dynamic __]) { 94 | return () => {}; 95 | } 96 | 97 | /// Queues outgoing messages for sending 98 | /// 99 | /// @param msgs The Gun wire protocol messages to enqueue 100 | GunGraphConnector send(List msgs) { 101 | outputQueue.enqueueMany(msgs); 102 | if (isConnected) { 103 | outputQueue.process(); 104 | } 105 | 106 | return this; 107 | } 108 | 109 | /// Queue incoming messages for processing 110 | /// 111 | /// @param msgs 112 | GunGraphConnector ingest(List msgs) { 113 | inputQueue.enqueueMany(msgs).process(); 114 | 115 | return this; 116 | } 117 | 118 | GunGraphConnector connectToGraph(GunGraph graph) { 119 | graph.events.off.on(off); 120 | return this; 121 | } 122 | 123 | __onConnectedChange(bool connected, [dynamic _, dynamic __]) { 124 | if (connected) { 125 | isConnected = true; 126 | outputQueue.process(); 127 | } else { 128 | isConnected = false; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/src/client/transports/gun_graph_connector_from_adapter.dart: -------------------------------------------------------------------------------- 1 | import '../../types/gun.dart'; 2 | 3 | import '../../types/flutter_gun.dart'; 4 | import '../../types/gun_graph_adapter.dart'; 5 | import '../graph/gun_graph_utils.dart'; 6 | import 'gun_graph_wire_connector.dart'; 7 | 8 | NOOP() => null; 9 | 10 | class GunGraphConnectorFromAdapter extends GunGraphWireConnector { 11 | late final GunGraphAdapter adapter; 12 | final String name; 13 | 14 | GunGraphConnectorFromAdapter( 15 | {required this.adapter, this.name = 'GunGraphConnectorFromAdapter'}); 16 | 17 | @override 18 | VoidCallback get(FlutterGunGet flutterGunGet, [dynamic _, dynamic __]) { 19 | adapter.get(flutterGunGet.soul).then((node) { 20 | GunGraphData gunGraphData = GunGraphData(); 21 | gunGraphData[flutterGunGet.soul] = node; 22 | return GunMsg( 23 | key: generateMessageId(), 24 | pos: flutterGunGet.msgId ?? '', 25 | put: !isNull(node) ? gunGraphData : null); 26 | }).catchError((err) { 27 | print(err); 28 | 29 | return GunMsg( 30 | key: generateMessageId(), 31 | pos: flutterGunGet.msgId ?? '', 32 | err: 'Error fetching node'); 33 | }).then((msg) { 34 | ingest([msg]); 35 | if (!isNull(flutterGunGet.cb)) { 36 | flutterGunGet.cb!(msg); 37 | } 38 | }); 39 | 40 | return NOOP; 41 | } 42 | 43 | @override 44 | VoidCallback put(FlutterGunPut flutterGunPut, [dynamic _, dynamic __]) { 45 | adapter 46 | .put(flutterGunPut.graph) 47 | .then((node) => GunMsg( 48 | key: generateMessageId(), 49 | pos: flutterGunPut.msgId ?? '', 50 | err: null, 51 | ok: true)) 52 | .catchError((err) { 53 | print(err); 54 | 55 | return GunMsg( 56 | key: generateMessageId(), 57 | pos: flutterGunPut.msgId ?? '', 58 | err: 'Error saving put', 59 | ok: false); 60 | }).then((msg) { 61 | ingest([msg]); 62 | if (!isNull(flutterGunPut.cb)) { 63 | flutterGunPut.cb!(msg); 64 | } 65 | }); 66 | 67 | return NOOP; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/client/transports/gun_graph_wire_connector.dart: -------------------------------------------------------------------------------- 1 | import '../../types/flutter_gun.dart'; 2 | import '../../types/generic.dart'; 3 | import '../../types/gun.dart'; 4 | import '../../types/gun_graph_adapter.dart'; 5 | import '../graph/gun_graph_utils.dart'; 6 | import 'gun_graph_connector.dart'; 7 | 8 | class CallBacksMap extends GenericCustomValueMap {} 9 | 10 | abstract class GunGraphWireConnector extends GunGraphConnector { 11 | late final CallBacksMap _callbacks; 12 | final String name; 13 | 14 | GunGraphWireConnector({this.name = 'GunWireProtocol'}) { 15 | _callbacks = CallBacksMap(); 16 | super.inputQueue.completed.on(_onProcessedInput); 17 | } 18 | 19 | @override 20 | GunGraphWireConnector off(String msgId, [dynamic _, dynamic __]) { 21 | super.off(msgId); 22 | _callbacks.remove(msgId); 23 | return this; 24 | } 25 | 26 | /// Send graph data for one or more nodes 27 | /// 28 | /// @returns A function to be called to clean up callback listeners 29 | @override 30 | VoidCallback put(FlutterGunPut flutterGunPut, [dynamic _, dynamic __]) { 31 | final GunMsg msg = GunMsg(put: flutterGunPut.graph); 32 | if (!isNull(flutterGunPut.msgId)) { 33 | msg.key = flutterGunPut.msgId; 34 | } 35 | if (!isNull(flutterGunPut.replyTo)) { 36 | msg.pos = flutterGunPut.replyTo; 37 | } 38 | 39 | return req(msg, flutterGunPut.cb); 40 | } 41 | 42 | /// Request data for a given soul 43 | /// 44 | /// @returns A function to be called to clean up callback listeners 45 | @override 46 | VoidCallback get(FlutterGunGet flutterGunGet, [dynamic _, dynamic __]) { 47 | final GunMsgGet get = GunMsgGet(key: flutterGunGet.soul); 48 | final GunMsg msg = GunMsg(get: get); 49 | if (!isNull(flutterGunGet.msgId)) { 50 | msg.key = flutterGunGet.msgId; 51 | } 52 | 53 | return req(msg, flutterGunGet.cb); 54 | } 55 | 56 | /// Send a message that expects responses via @ 57 | /// 58 | /// @param msg 59 | /// @param cb 60 | VoidCallback req(GunMsg msg, GunMsgCb? cb) { 61 | final String reqId = msg.key = msg.key ?? generateMessageId(); 62 | if (!isNull(cb)) { 63 | _callbacks[reqId] = cb!; 64 | } 65 | send([msg]); 66 | return () { 67 | off(reqId); 68 | }; 69 | } 70 | 71 | void _onProcessedInput(GunMsg? msg, [dynamic _, dynamic __]) { 72 | if (isNull(msg)) { 73 | return; 74 | } 75 | final id = msg?.key; 76 | final replyTo = msg?.pos; 77 | 78 | if (!isNull(msg?.put)) { 79 | events.graphData.trigger(msg!.put!, id, replyTo); 80 | } 81 | 82 | if (!isNull(replyTo)) { 83 | final cb = _callbacks[replyTo]; 84 | if (!isNull(cb)) { 85 | cb!(msg!); 86 | } 87 | } 88 | 89 | events.receiveMessage.trigger(msg!); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/src/client/transports/web_socket_graph_connector.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:web_socket_channel/web_socket_channel.dart'; 4 | 5 | import '../../types/gun.dart'; 6 | import '../graph/gun_graph_utils.dart'; 7 | import 'gun_graph_wire_connector.dart'; 8 | 9 | class WebSocketGraphConnector extends GunGraphWireConnector { 10 | final String url; 11 | late WebSocketChannel _ws; 12 | late bool isLocalConnected; 13 | 14 | WebSocketGraphConnector({required this.url}) { 15 | super.outputQueue.completed.on(_onOutputProcessed); 16 | _ws = _connectWebSocket(); 17 | isLocalConnected = false; 18 | } 19 | 20 | _connectWebSocket() { 21 | final ws = WebSocketChannel.connect(Uri.parse(url)); 22 | 23 | ws.stream.listen((event) { 24 | if (!isLocalConnected) { 25 | events.connection.trigger(true); 26 | } 27 | isLocalConnected=true; 28 | _onReceiveSocketData(event); 29 | }, onError: (_, __) { 30 | events.connection.trigger(false); 31 | }, onDone: () { 32 | isLocalConnected=false; 33 | events.connection.trigger(false); 34 | _ws = _connectWebSocket(); 35 | }); 36 | 37 | return ws; 38 | } 39 | 40 | List _sendToWebsocket(List msgs) { 41 | if (msgs.isEmpty) { 42 | return msgs; 43 | } 44 | // print('Sending It:: ${msgs.length} ${jsonEncode(msgs[0])}'); 45 | if (msgs.length == 1) { 46 | _ws.sink.add(jsonEncode(msgs[0])); 47 | } else if (msgs.length > 1) { 48 | _ws.sink.add(jsonEncode(msgs)); 49 | } 50 | return msgs; 51 | } 52 | 53 | void _onOutputProcessed(GunMsg msg, [dynamic _, dynamic __]) { 54 | if (!isNull(msg)) { 55 | _sendToWebsocket([msg]); 56 | } 57 | } 58 | 59 | void _onReceiveSocketData(dynamic msg) { 60 | // print('Received Msg: $msg'); 61 | final json = jsonDecode(msg); 62 | // print('\n\nReceived Msg:---:: $json'); 63 | 64 | if (isArray(json)) { 65 | ingest((json as List).map((e) => GunMsg.fromJson(e)).toList()); 66 | } else { 67 | ingest([GunMsg.fromJson(json)]); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/src/crdt/index.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import '../types/flutter_gun.dart'; 4 | import '../types/enum.dart'; 5 | import '../types/gun.dart'; 6 | 7 | GunGraphData addMissingState(GunGraphData graphData) { 8 | final updatedGraphData = graphData; 9 | final now = DateTime.now().millisecondsSinceEpoch; 10 | 11 | for (final soul in graphData.entries) { 12 | if (soul.value == null) { 13 | continue; 14 | } 15 | 16 | var node = soul.value; 17 | 18 | var meta = (node?.nodeMetaData = node.nodeMetaData ?? GunNodeMeta()); 19 | meta?.key = soul.key; 20 | var state = (meta?.forward = meta.forward ?? GunNodeState()); 21 | 22 | for (final key in node!.keys) { 23 | if (key == '_') { 24 | continue; 25 | } 26 | state?[key] = state[key] ?? now; 27 | } 28 | updatedGraphData[soul.key] = node; 29 | } 30 | 31 | return updatedGraphData; 32 | } 33 | 34 | GunGraphData? diffGunCRDT(GunGraphData updatedGraph, GunGraphData existingGraph, 35 | {CrdtOption? opts}) { 36 | opts ??= CrdtOption(lexical: jsonEncode, futureGrace: 10 * 60 * 1000); 37 | 38 | var machineState = DateTime.now().millisecondsSinceEpoch, 39 | futureGrace = opts.futureGrace, 40 | lexical = opts.lexical!; 41 | 42 | final maxState = machineState + futureGrace!; 43 | 44 | final GunGraphData allUpdates = GunGraphData(); 45 | 46 | for (final soul in updatedGraph.entries) { 47 | final GunNode? existing = existingGraph[soul.key]; 48 | final GunNode? updated = soul.value; 49 | 50 | final GunNodeState existingState = 51 | existing?.nodeMetaData?.forward ?? GunNodeState(); 52 | final GunNodeState updatedState = 53 | updated?.nodeMetaData?.forward ?? GunNodeState(); 54 | 55 | if (updated == null) { 56 | if (existing == null) { 57 | allUpdates[soul.key] = updated; 58 | } 59 | continue; 60 | } 61 | 62 | var hasUpdates = false; 63 | 64 | final GunNode updates = GunNode( 65 | nodeMetaData: GunNodeMeta(key: soul.key, forward: GunNodeState())); 66 | 67 | for (final key in updatedState.keys) { 68 | final existingKeyState = existingState[key]; 69 | final updatedKeyState = updatedState[key]; 70 | 71 | if (updatedKeyState == null || updatedKeyState > maxState) { 72 | continue; 73 | } 74 | if (existingKeyState != null && existingKeyState >= updatedKeyState) { 75 | continue; 76 | } 77 | 78 | if (existingKeyState == updatedKeyState) { 79 | final existingVal = existing?[key]; 80 | final updatedVal = updated[key]; 81 | // This is based on Gun's logic 82 | if (lexical(updatedVal) <= lexical(existingVal)) { 83 | continue; 84 | } 85 | } 86 | 87 | updates[key] = updated[key]; 88 | updates.nodeMetaData?.forward![key] = updatedKeyState; 89 | hasUpdates = true; 90 | } 91 | 92 | if (hasUpdates) { 93 | allUpdates[soul.key] = updates; 94 | } 95 | } 96 | 97 | return allUpdates.isNotEmpty ? allUpdates : null; 98 | } 99 | 100 | GunNode? mergeGunNodes(GunNode? existing, GunNode? updates, 101 | {MutableEnum mut = MutableEnum.immutable}) { 102 | if (existing == null) { 103 | return updates; 104 | } 105 | if (updates == null) { 106 | return existing; 107 | } 108 | final existingMeta = existing.nodeMetaData ?? GunNodeMeta(); 109 | final existingState = existingMeta.forward ?? GunNodeState(); 110 | final updatedMeta = updates.nodeMetaData ?? GunNodeMeta(); 111 | final updatedState = updatedMeta.forward ?? GunNodeState(); 112 | 113 | if (mut == MutableEnum.mutable) { 114 | existingMeta.forward = existingState; 115 | existing.nodeMetaData = existingMeta; 116 | 117 | for (final key in updatedState.keys) { 118 | existing[key] = updates[key]; 119 | existingState[key] = updatedState[key]!; 120 | } 121 | 122 | return existing; 123 | } 124 | 125 | return GunNode.fromJson({ 126 | ...existing, 127 | ...updates, 128 | "_": { 129 | "#": existingMeta.key, 130 | ">": { 131 | ...?existingMeta.forward, 132 | ...?updates.nodeMetaData?.forward 133 | } 134 | } 135 | }); 136 | } 137 | 138 | GunGraphData mergeGraph(GunGraphData existing, GunGraphData diff, 139 | {MutableEnum mut = MutableEnum.immutable}) { 140 | final GunGraphData result = existing; 141 | for (final soul in diff.keys) { 142 | result[soul] = mergeGunNodes(existing[soul], diff[soul], mut: mut); 143 | } 144 | 145 | return result; 146 | } 147 | -------------------------------------------------------------------------------- /lib/src/sea/flutter_gun_sea_client.dart: -------------------------------------------------------------------------------- 1 | import '../client/flutter_gun_client.dart'; 2 | import '../client/flutter_gun_link.dart'; 3 | import '../client/interfaces.dart'; 4 | import '../sear/unpack.dart'; 5 | import '../storage/store.dart'; 6 | import '../types/gun.dart'; 7 | import 'flutter_gun_user_api.dart'; 8 | 9 | class FlutterGunSeaClient extends FlutterGunClient { 10 | FlutterGunUserApi? _user; 11 | FlutterGunLink? linkClass; 12 | 13 | FlutterGunSeaClient( 14 | {this.linkClass, 15 | FlutterGunOptions? flutterGunOptions, 16 | bool registerStorage = false}) { 17 | if (flutterGunOptions == null) { 18 | var tempFlutterGunOptions = FlutterGunOptions(); 19 | tempFlutterGunOptions.peers = ["wss://gun-manhattan.herokuapp.com/gun"]; 20 | flutterGunOptions = tempFlutterGunOptions; 21 | } 22 | initializedClient( 23 | linkClass: linkClass, flutterGunOptions: flutterGunOptions); 24 | if (registerStorage) { 25 | registerStorageMiddleware(); 26 | } else { 27 | registerSearMiddleware(); 28 | } 29 | } 30 | 31 | FlutterGunUserApi user() { 32 | return (_user ??= FlutterGunUserApi(flutterGunSeaClient: this)); 33 | } 34 | 35 | void registerSearMiddleware() { 36 | graph!.use((GunGraphData updates, GunGraphData existingGraph) => 37 | unpackGraph(updates, graph!.getOpt().mutable!)); 38 | } 39 | 40 | void registerStorageMiddleware() { 41 | // For the Read Use Case 42 | graph!.use((GunGraphData updates, GunGraphData existingGraph) => 43 | getStoreData(unpackGraph(updates, graph!.getOpt().mutable!), graph!.activeConnectors)); 44 | 45 | // For the Write Use Case 46 | graph!.use( 47 | (GunGraphData updates, GunGraphData existingGraph) => 48 | setStoreData(updates), 49 | kind: FlutterGunMiddlewareType.write); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/sea/flutter_gun_user_api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import '../types/sear/types.dart'; 4 | 5 | import '../client/interfaces.dart'; 6 | import '../sear/authenticate.dart'; 7 | import '../sear/create_user.dart'; 8 | import '../sear/sign.dart'; 9 | import '../types/gun.dart'; 10 | import 'flutter_gun_sea_client.dart'; 11 | 12 | class UserReference { 13 | String alias; 14 | String pub; 15 | 16 | UserReference.from({required this.alias, required this.pub}); 17 | 18 | factory UserReference.fromJson(Map parsedJson) { 19 | return UserReference.from( 20 | alias: parsedJson['alias'], 21 | pub: parsedJson['pub'], 22 | ); 23 | } 24 | 25 | Map toJson() => { 26 | 'alias': alias, 27 | 'pub': pub, 28 | }; 29 | } 30 | 31 | class AckErr { 32 | String err; 33 | 34 | AckErr.from({required this.err}); 35 | } 36 | 37 | class UserCredentials { 38 | String alias; 39 | String epub; 40 | String pub; 41 | String epriv; 42 | String priv; 43 | 44 | UserCredentials.from( 45 | {required this.alias, 46 | required this.epub, 47 | required this.pub, 48 | required this.epriv, 49 | required this.priv}); 50 | 51 | Map toJson() => { 52 | 'alias': alias, 53 | 'epub': epub, 54 | 'pub': pub, 55 | 'epriv': epriv, 56 | 'priv': priv, 57 | }; 58 | 59 | factory UserCredentials.fromJson(Map parsedJson) { 60 | return UserCredentials.from( 61 | alias: parsedJson['alias'], 62 | epub: parsedJson['epub'], 63 | pub: parsedJson['pub'], 64 | epriv: parsedJson['epriv'], 65 | priv: parsedJson['priv']); 66 | } 67 | } 68 | 69 | typedef LoginCallback = void Function(dynamic userRef); 70 | 71 | typedef SignMiddleWareFnType = FutureOr Function( 72 | GunGraphData graph, GunGraphData _graph); 73 | 74 | const DEFAULT_CREATE_OPTS = {}; 75 | const DEFAULT_AUTH_OPTS = {}; 76 | 77 | class FlutterGunUserApi { 78 | late FlutterGunSeaClient _gun; 79 | UserReference? isu; 80 | SignMiddleWareFnType? _signMiddleware; 81 | 82 | FlutterGunUserApi({required FlutterGunSeaClient flutterGunSeaClient}) { 83 | _gun = flutterGunSeaClient; 84 | } 85 | 86 | /// 87 | /// https://gun.eco/docs/User#user-create 88 | /// 89 | /// @param alias 90 | /// @param password 91 | /// @param cb 92 | /// @param opt 93 | Future create(String alias, String password, 94 | [LoginCallback? cb, _opt = DEFAULT_CREATE_OPTS]) async { 95 | try { 96 | final user = await createUser(_gun, alias, password); 97 | final ref = useCredentials(UserCredentials.fromJson(user.toJson())); 98 | if (cb != null) { 99 | cb(ref); 100 | } 101 | return ref; 102 | } catch (err) { 103 | if (cb != null) { 104 | cb({err}); 105 | } 106 | throw (err.toString()); 107 | } 108 | } 109 | 110 | /// 111 | /// https://gun.eco/docs/User#user-auth 112 | /// 113 | /// @param alias 114 | /// @param password 115 | /// @param pair 116 | /// @param cb 117 | /// @param opt 118 | Future auth( 119 | {String? alias, 120 | String? password, 121 | PairReturnType? pair, 122 | LoginCallback? cb, 123 | opt = DEFAULT_AUTH_OPTS}) async { 124 | if ((alias == null || password == null) && pair == null) { 125 | throw ("Either Enter Pair or User alias and pass"); 126 | } 127 | 128 | alias ??= pair!.pub; 129 | password ??= pair!.epriv; 130 | 131 | try { 132 | final user = await authenticate(_gun, alias, password); 133 | final ref = useCredentials(UserCredentials.fromJson(user.toJson())); 134 | if (cb != null) { 135 | cb(ref); 136 | } 137 | return ref; 138 | } catch (err) { 139 | if (cb != null) { 140 | cb({err}); 141 | } 142 | throw (err.toString()); 143 | } 144 | } 145 | 146 | /// https://gun.eco/docs/User#user-leave 147 | FlutterGunUserApi leave() { 148 | if (_signMiddleware != null) { 149 | _gun.graph!.unuse(_signMiddleware!, kind: FlutterGunMiddlewareType.write); 150 | _signMiddleware = null; 151 | isu = null; 152 | } 153 | 154 | return this; 155 | } 156 | 157 | UserReference useCredentials(UserCredentials credentials) { 158 | leave(); 159 | _signMiddleware = graphSigner(PairReturnType.from( 160 | pub: credentials.pub, 161 | priv: credentials.priv, 162 | epriv: credentials.epriv, 163 | epub: credentials.epub)); 164 | _gun.graph!.use(_signMiddleware!, kind: FlutterGunMiddlewareType.write); 165 | 166 | return (isu = 167 | UserReference.from(alias: credentials.alias, pub: credentials.pub)); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/src/sear/authenticate.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import '../../flutter_gundb.dart'; 5 | 6 | import '../types/sear/types.dart'; 7 | 8 | const Map DEFAULT_OPTS = {}; 9 | 10 | Future authenticateAccount( 11 | dynamic ident, String password, 12 | [String encoding = 'base64']) async { 13 | if (ident == null || (ident is Map && !ident.containsKey('auth'))) { 14 | return null; 15 | } 16 | 17 | var decrypted; 18 | 19 | try { 20 | final proof = await work(password, ident['auth']['s'], DefaultWorkFn.from(encode: encoding)); 21 | decrypted = await decrypt(ident['auth']['ek'], PairReturnType.from(epriv: proof, epub: "", priv: "", pub: ""), DefaultAESDecryptKey.from(encode: encoding)); 22 | } catch(e) { 23 | final proof = await work(password, ident['auth']['s'], DefaultWorkFn.from(encode: encoding)); 24 | decrypted = await decrypt(ident['auth']['ek'], PairReturnType.from(epriv: proof, epub: "", priv: "", pub: ""), DefaultAESDecryptKey.from(encode: encoding)); 25 | } 26 | 27 | if (decrypted == null) { 28 | return null; 29 | } 30 | 31 | return AuthenticateReturnDataType.from( 32 | alias: ident['alias'], 33 | epriv: decrypted['epriv'], 34 | epub: ident['epub'], 35 | priv: decrypted['priv'], 36 | pub: ident['pub'] 37 | ); 38 | } 39 | 40 | Future authenticateIdentity( 41 | FlutterGunSeaClient fluttergun, 42 | String soul, 43 | String password, 44 | [String encoding = 'base64'] 45 | ) async { 46 | final ident = await fluttergun.getValue(soul); 47 | return authenticateAccount(ident, password, encoding); 48 | } 49 | 50 | Future authenticate( 51 | FlutterGunSeaClient fluttergun, 52 | String alias, 53 | String password, 54 | [Map _opt = DEFAULT_OPTS] 55 | ) async { 56 | final aliasSoul = "~@$alias"; 57 | final idents = await fluttergun.getValue(aliasSoul); 58 | 59 | for (var soul in (idents is Map ? idents : {}).keys) { 60 | if (soul == '_') { 61 | continue; 62 | } 63 | 64 | var pair; 65 | 66 | try { 67 | pair = await authenticateIdentity(fluttergun, soul, password); 68 | } catch (e) { 69 | if (kDebugMode) { 70 | print("Error During authenticate: ${e.toString()}"); 71 | } 72 | } 73 | 74 | if (pair != null) { 75 | return pair; 76 | } 77 | } 78 | 79 | throw ('Wrong alias or password.'); 80 | } -------------------------------------------------------------------------------- /lib/src/sear/base64.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | 4 | import '../types/generic.dart'; 5 | 6 | class SearBase64 extends GenericCustomValueMap { 7 | 8 | static String btob(List any) { 9 | return base64Encode(any); 10 | } 11 | 12 | static Uint8List atob(String base64Data) { 13 | return base64Decode(base64Data); 14 | } 15 | 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/src/sear/certify.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import '../types/sear/types.dart'; 4 | import 'sign.dart' show sign; 5 | 6 | final DefaultCertifyOPTType DEFAULT_OPTS = DefaultCertifyOPTType.from(); 7 | 8 | Future certify(dynamic certificants, dynamic policy, PairReturnType authority, 9 | [DefaultCertifyOPTType? opt]) async { 10 | opt ??= DEFAULT_OPTS; 11 | 12 | certificants = getCertificants(certificants); 13 | 14 | if (certificants == null) { 15 | throw ("No certificant found."); 16 | } 17 | 18 | final expiry = opt.expiry; 19 | 20 | final readPolicy = policy is CertifyPolicyType ? policy.read : null; 21 | 22 | final writePolicy = policy is CertifyPolicyType 23 | ? policy.write 24 | : (policy is String || 25 | policy is List) || (policy.runtimeType == {}.runtimeType && 26 | (policy.containsKey("+") || 27 | policy.containsKey("#") || 28 | policy.containsKey(".") || 29 | policy.containsKey("=") || 30 | policy.containsKey("*") || 31 | policy.containsKey(">") || 32 | policy.containsKey("<"))) 33 | ? policy 34 | : null; 35 | 36 | final block = opt.block; 37 | final readBlock = (block == {}.runtimeType && block!.containsKey('read')) && 38 | (block!['read'] is String || 39 | (block!['read'].runtimeType == {}.runtimeType && 40 | block!['read'].containsKey('#'))) 41 | ? block!['read'] 42 | : null; 43 | 44 | final writeBlock = block is String 45 | ? block 46 | : (block.runtimeType == {}.runtimeType && block!.containsKey('write')) && 47 | (block!['write'] is String || 48 | (block!['write'].runtimeType == {}.runtimeType && 49 | block!['write'].containsKey('#'))) 50 | ? block!['write'] 51 | : null; 52 | 53 | if (readPolicy == null && writePolicy == null) { 54 | throw ("No policy found."); 55 | } 56 | 57 | final data = jsonEncode({ 58 | 'c': certificants, 59 | ...(expiry != null ? {'e': expiry} : {}), 60 | // inject expiry if possible 61 | ...(readPolicy != null ? {'r': readPolicy} : {}), 62 | // "r" stands for read, which means read permission. 63 | ...(writePolicy != null ? {'w': writePolicy} : {}), 64 | // "w" stands for write, which means write permission. 65 | ...(readBlock != null ? {'rb': readBlock} : {}), 66 | // inject READ block if possible 67 | ...(writeBlock != null ? {'wb': writeBlock} : {}), 68 | // inject WRITE block if possible 69 | }); 70 | 71 | final certificate = 72 | await sign(data, authority, DefaultOptSignType.from(raw: true)); 73 | 74 | if (opt.raw != null && opt.raw!) { 75 | return certificate; 76 | } 77 | 78 | return "SEA${jsonEncode(certificate)}"; 79 | } 80 | 81 | dynamic getCertificants(dynamic certificants) { 82 | var data = []; 83 | 84 | if (certificants != null) { 85 | if ((certificants is String || 86 | certificants.runtimeType == [].runtimeType) && 87 | certificants.indexOf("*") > -1) { 88 | return "*"; 89 | } 90 | 91 | if (certificants is String) { 92 | return certificants; 93 | } 94 | 95 | if (certificants is List) { 96 | if (certificants.length == 1 && certificants[0] != null) { 97 | if (certificants[0] is PairReturnType) { 98 | return certificants[0].pub; 99 | } else if (certificants[0] is String) { 100 | return certificants[0]; 101 | } else { 102 | return null; 103 | } 104 | } 105 | 106 | certificants.map((certificant) { 107 | if (certificant is String) { 108 | data.add(certificant); 109 | } else if (certificant is PairReturnType) { 110 | data.add(certificant.pub); 111 | } 112 | 113 | return certificant; 114 | }); 115 | } 116 | 117 | if (certificants is PairReturnType) { 118 | return certificants.pub; 119 | } 120 | } 121 | 122 | return data.isNotEmpty ? data : null; 123 | } 124 | -------------------------------------------------------------------------------- /lib/src/sear/create_user.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import '../../flutter_gundb.dart'; 4 | import 'pair.dart' as create_pair; 5 | 6 | import '../types/gun.dart'; 7 | import '../types/sear/types.dart'; 8 | 9 | Future createUser( 10 | FlutterGunSeaClient fluttergun, String alias, String password) async { 11 | final aliasSoul = "~@$alias"; 12 | 13 | // "pseudo-randomly create a salt, then use PBKDF2 function to extend the password with it." 14 | final salt = pseudoRandomText(64); 15 | 16 | final proof = await work(password, PairReturnType.from(epriv: "", epub: salt, priv: "", pub: "")); 17 | final pair = await create_pair.pair(); 18 | final pubSoul = "~${pair.pub}"; 19 | 20 | final ek = await encrypt( 21 | jsonEncode({'priv': pair.priv, 'epriv': pair.epriv}), 22 | PairReturnType.from(epriv: proof, epub: "", priv: "", pub: ""), 23 | DefaultAESEncryptKey.from(raw: true)); 24 | 25 | final auth = jsonEncode({'ek': ek, 's': salt}); 26 | final data = { 27 | 'alias': alias, 28 | 'auth': auth, 29 | 'epub': pair.epub, 30 | 'pub': pair.pub 31 | }; 32 | 33 | final now = DateTime.now().millisecondsSinceEpoch; 34 | 35 | final GunGraphData tempGraph = GunGraphData(); 36 | final Map tempForwardGraph = {}; 37 | 38 | for (var innerKey in data.keys) { 39 | tempForwardGraph[innerKey] = now; 40 | } 41 | 42 | tempGraph[pubSoul] = GunNode.fromJson({ 43 | '_': {'#': pubSoul, '>': tempForwardGraph}, 44 | ...data 45 | }); 46 | 47 | final graph = await signGraph(tempGraph, pair); 48 | 49 | await () async { 50 | final tempNodePut = fluttergun.get(pubSoul); 51 | tempNodePut.put(graph[pubSoul]); 52 | final tempPut = fluttergun.get(aliasSoul); 53 | tempPut.put({'#': pubSoul}); 54 | }(); 55 | 56 | return CreateUserReturnType.fromJson( 57 | {...data, 'epriv': pair.epriv, 'priv': pair.priv, 'pub': pair.pub}); 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/sear/decrypt.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'shims.dart'; 4 | 5 | import '../types/gun.dart'; 6 | import '../types/sear/types.dart'; 7 | import 'settings.dart' show parse; 8 | 9 | import 'import_aes_key.dart'; 10 | 11 | final DefaultAESDecryptKey DEFAULT_OPTS = 12 | DefaultAESDecryptKey.from(encode: 'base64', name: 'AES-GCM'); 13 | 14 | Future decrypt(dynamic data, dynamic pair, 15 | [DefaultAESDecryptKey? opt]) async { 16 | opt ??= DEFAULT_OPTS; 17 | final json = parse(data); 18 | final encoding = opt.encode ?? DEFAULT_OPTS.encode; 19 | 20 | final key = pair is PairReturnType ? pair.epriv : pair; 21 | 22 | try { 23 | final aeskey = await importAesKey(key, base64Decode(json['s']).buffer, 24 | DefaultAESKey.from(name: opt.name)); 25 | 26 | final encrypted = base64Decode(json['ct']); 27 | 28 | final iv = base64Decode(json['iv']); 29 | final ct = await aeskey.decryptBytes(encrypted, iv, tagLength: 128); 30 | return parse(Shims.textDecoder(ct)); 31 | } catch (e) { 32 | if (kDebugMode) { 33 | print('decrypt error: ${e.toString()}'); 34 | } 35 | if (opt.fallback == null || encoding == opt.fallback) { 36 | throw ('Could not decrypt'); 37 | } 38 | return decrypt( 39 | data, 40 | pair, 41 | DefaultAESDecryptKey.from( 42 | encode: opt.fallback, name: opt.name, fallback: opt.fallback)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/sear/encrypt.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'import_aes_key.dart' show importAesKey; 4 | import 'shims.dart'; 5 | 6 | import '../types/sear/types.dart'; 7 | 8 | final DefaultAESEncryptKey DEFAULT_OPTS = DefaultAESEncryptKey.from(encode: 'base64', name: 'AES-GCM'); 9 | 10 | Future encrypt(dynamic data, dynamic pair, [DefaultAESEncryptKey? opt]) async { 11 | 12 | if (data == null) { 13 | throw ("`null` not allowed."); 14 | } 15 | opt ??= DEFAULT_OPTS; 16 | 17 | final key = pair is PairReturnType ? pair.epriv : pair; 18 | 19 | final msg = data is String ? data : jsonEncode(data); 20 | 21 | final rand = { 's': Shims.random(9), 'iv': Shims.random(15) }; // consider making this 9 and 15 or 18 or 12 to reduce == padding. 22 | 23 | final aesKey = await importAesKey(key, rand['s']?.buffer, DefaultAESKey.from(name: opt.name)); 24 | 25 | final ct = await aesKey.encryptBytes(Shims.textEncoder(msg), rand['iv']!); 26 | 27 | final r = { 28 | 'ct': base64Encode(ct), 29 | 'iv': base64Encode(rand['iv']!), 30 | 's': base64Encode(rand['s']!), 31 | }; 32 | 33 | if (opt.raw != null && opt.raw!) { 34 | return r; 35 | } 36 | 37 | return "SEA${jsonEncode(r)}"; 38 | } -------------------------------------------------------------------------------- /lib/src/sear/import_aes_key.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:webcrypto/webcrypto.dart' as crypto; 3 | 4 | import 'settings.dart' show keyToJwk; 5 | import 'sha256.dart'; 6 | 7 | import 'shims.dart'; 8 | import '../types/sear/types.dart'; 9 | 10 | final DefaultAESKey DEFAULT_OPTS = DefaultAESKey.from(name: 'AES-GCM'); 11 | 12 | Future importAesKey(String key, [ByteBuffer? salt, DefaultAESKey? _opt]) async { 13 | _opt ??= DEFAULT_OPTS; 14 | 15 | final combo = key + (salt?.asUint8List() ?? Shims.random(8)).toString(); 16 | final hash = await sha256(combo); 17 | final jwkKey = keyToJwk(hash); 18 | return crypto.AesGcmSecretKey.importJsonWebKey(jwkKey.toJson()); 19 | } -------------------------------------------------------------------------------- /lib/src/sear/index.dart: -------------------------------------------------------------------------------- 1 | 2 | export 'shims.dart' show Shims; 3 | 4 | export 'settings.dart'; 5 | export 'unpack.dart'; 6 | export 'authenticate.dart' show authenticate, authenticateAccount; 7 | export 'create_user.dart' show createUser; 8 | export 'decrypt.dart' show decrypt; 9 | export 'encrypt.dart' show encrypt; 10 | export 'import_aes_key.dart' show importAesKey; 11 | export 'pair.dart' show pair; 12 | export 'pseudo_random_text.dart' show pseudoRandomText; 13 | export 'sha256.dart' show sha256; 14 | export 'work.dart' show work; 15 | export 'sign.dart'; 16 | export 'soul.dart' show pubFromSoul; 17 | export 'verify.dart' show verify, verifySignature, verifyHashSignature; 18 | export 'secret.dart' show secret; 19 | export 'certify.dart' show certify; 20 | 21 | -------------------------------------------------------------------------------- /lib/src/sear/pair.dart: -------------------------------------------------------------------------------- 1 | import 'package:webcrypto/webcrypto.dart' as crypto; 2 | 3 | import '../types/sear/types.dart'; 4 | 5 | Future pair([opt]) async { 6 | final signKeys = await crypto.EcdsaPrivateKey.generateKey(crypto.EllipticCurve.p256); 7 | 8 | final signPub = await signKeys.publicKey.exportJsonWebKey(); 9 | final signPri = await signKeys.privateKey.exportJsonWebKey(); 10 | 11 | final sa = { 12 | 'priv': signPri['d'], 13 | 'pub': "${signPub["x"]}.${signPub["y"]}", 14 | }; 15 | 16 | final cryptKeys = await crypto.EcdhPrivateKey.generateKey(crypto.EllipticCurve.p256); 17 | 18 | final cryptPub = await cryptKeys.publicKey.exportJsonWebKey(); 19 | final cryptPri = await cryptKeys.privateKey.exportJsonWebKey(); 20 | 21 | final dh = { 22 | 'epriv': cryptPri['d'], 23 | 'epub': "${cryptPub["x"]}.${cryptPub["y"]}", 24 | }; 25 | 26 | return PairReturnType.from( 27 | epriv: dh['epriv'] ?? '', 28 | epub: dh['epub'] ?? '', 29 | priv: sa['priv'] ?? '', 30 | pub: sa['pub'] 31 | ); 32 | } -------------------------------------------------------------------------------- /lib/src/sear/pseudo_random_text.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | String pseudoRandomText([ 4 | l = 24, 5 | c = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXZabcdefghijklmnopqrstuvwxyz' 6 | ]) { 7 | var s = ''; 8 | 9 | while (l > 0) { 10 | s += c[math.Random().nextInt(c.length - 1)]; 11 | l--; 12 | } 13 | 14 | return s; 15 | } -------------------------------------------------------------------------------- /lib/src/sear/safe_buffer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'sea_array.dart'; 4 | 5 | import '../types/generic.dart'; 6 | import 'base64.dart'; 7 | 8 | class SafeBuffer extends GenericCustomList { 9 | static SafeBuffer from(dynamic input, [String? enc]) { 10 | enc ??= 'utf-8'; 11 | SafeBuffer buf = SafeBuffer(); 12 | 13 | if (input is String) { 14 | if (enc == 'hex') { 15 | final bytes = RegExp(r"([\da-fA-F]{2})") 16 | .allMatches(input) 17 | .map((match) => int.parse(match.group(0)!, radix: 16)) 18 | .toList(); 19 | if (bytes.isEmpty) { 20 | throw ("Invalid first argument for type 'hex'."); 21 | } 22 | buf = SeaArray.from(bytes); 23 | } else if (enc == 'utf8') { 24 | final length = input.length; 25 | final words = Uint16List(length); 26 | for (var i = 0; i < length; i++) { 27 | words[i] = input.codeUnitAt(i); 28 | } 29 | buf = SeaArray.from(words); 30 | } else if (enc == 'base64') { 31 | final dec = SearBase64.atob(input); 32 | // final length = dec.length; 33 | // final bytes = Uint8List(length); 34 | // for (var i = 0; i < length; i++) { 35 | // bytes[i] = dec[i]; 36 | // } 37 | buf = SeaArray.from(dec); 38 | } else if (enc == 'binary') { 39 | buf = SeaArray.from(input); 40 | } else { 41 | throw ('SafeBuffer.from unknown encoding: $enc'); 42 | } 43 | return buf; 44 | } 45 | final length = input.byteLength ? input.byteLength : input.length; 46 | if (length > 0) { 47 | var buff; 48 | if (input is ByteBuffer) { 49 | buff = input.asUint8List(); 50 | } 51 | return SeaArray.from(buff ?? input); 52 | } 53 | return buf; 54 | } 55 | 56 | // This is 'safe-buffer.alloc' sans encoding support 57 | static Uint8List alloc(int length, [fill = 0]) { 58 | return Uint8List.fromList(List.filled(length, fill)); 59 | } 60 | 61 | // This is normal UNSAFE 'buffer.alloc' or 'new Buffer(length)' - don't use! 62 | static allocUnsafe(int length) { 63 | return SeaArray.from(Uint8List.fromList(List.filled(length, 0))); 64 | } 65 | 66 | // This puts together array of array like members 67 | static concat(List arr) { 68 | // octet array 69 | // if (!(arr is List)) { 70 | // throw ('First argument must be Array containing ArrayBuffer or Uint8Array instances.'); 71 | // } 72 | return SeaArray.from( 73 | arr.reduce((ret, item) => ret.concat(List.from(item)))); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/sear/sea_array.dart: -------------------------------------------------------------------------------- 1 | import '../types/generic.dart'; 2 | 3 | class SeaArray extends GenericCustomList { 4 | 5 | static from(dynamic input) { 6 | return List.from(input); 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /lib/src/sear/secret.dart: -------------------------------------------------------------------------------- 1 | import 'package:webcrypto/webcrypto.dart' as crypto; 2 | 3 | import '../types/sear/types.dart'; 4 | import 'settings.dart' show jwk; 5 | 6 | final DefaultWorkFn DEFAULT_OPTS = DefaultWorkFn.from(encode: 'base64'); 7 | 8 | Future secret(String key, PairReturnType pair, [DefaultWorkFn? opt]) async { 9 | opt ??= DEFAULT_OPTS; 10 | 11 | final pub = key; 12 | final epub = pair.epub; 13 | final epriv = pair.epriv; 14 | 15 | final pubKeyData = jwk(pub); 16 | 17 | final props = await crypto.EcdhPublicKey.importJsonWebKey(pubKeyData.toJson(), crypto.EllipticCurve.p256); 18 | 19 | final privKeyData = jwk(epub, epriv); 20 | 21 | final privKey = await crypto.EcdhPrivateKey.importJsonWebKey(privKeyData.toJson(), crypto.EllipticCurve.p256); 22 | 23 | final derivedBits = await privKey.deriveBits(256, props); 24 | final derivedKey = await crypto.AesGcmSecretKey.importRawKey(derivedBits); 25 | final derived = await derivedKey.exportJsonWebKey(); 26 | 27 | return derived['k']; 28 | } -------------------------------------------------------------------------------- /lib/src/sear/settings.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | import 'package:webcrypto/webcrypto.dart' as crypto; 4 | 5 | import '../types/sear/types.dart'; 6 | 7 | const shuffleAttackCutoff = 1672511400000; // Jan 1, 2023 8 | 9 | const Map pbkdf2 = { 10 | 'hash': crypto.Hash.sha256, 11 | 'iter': 100000, 12 | 'ks': 64 13 | }; 14 | 15 | const ecdsa = { 16 | 'pair': {'name': 'ECDSA', 'namedCurve': 'P-256'}, 17 | 'sign': { 18 | 'name': 'ECDSA', 19 | 'hash': {'name': 'SHA-256'} 20 | } 21 | }; 22 | 23 | const ecdh = {'name': 'ECDH', 'namedCurve': 'P-256'}; 24 | 25 | JWK jwk(String pub, [String? d]) { 26 | final coords = pub.split('.'); 27 | final data = JWK.from( 28 | crv: 'P-256', 29 | kty: 'EC', 30 | x: coords[0], 31 | y: coords[1], 32 | d: d ?? "", 33 | ext: true, 34 | key_opts: d != null ? ['sign'] : ['verify'], 35 | ); 36 | return data; 37 | } 38 | 39 | KeyToJwk keyToJwk(ByteBuffer keyBytes) { 40 | final keyB64 = base64Encode(keyBytes.asUint8List()); 41 | final k = 42 | keyB64.replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', ''); 43 | return KeyToJwk.from(kty: 'oct', k: k, ext: false, alg: 'A256GCM'); 44 | } 45 | 46 | bool check(dynamic t) { 47 | return t is String && 'SEA{' == t.substring(0, 4); 48 | } 49 | 50 | dynamic parse(dynamic t) { 51 | try { 52 | final yes = t is String; 53 | if (yes && 'SEA{' == t.substring(0, 4)) { 54 | t = t.substring(3); 55 | } 56 | return yes ? jsonDecode(t) : t; 57 | } catch (_e) {} 58 | return t; 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/sear/sha256.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | import 'shims.dart'; 4 | import 'package:webcrypto/webcrypto.dart' as crypto; 5 | 6 | Future sha256(dynamic input, [String name = 'SHA-256']) async { 7 | final inp = input is String ? input : jsonEncode(input); 8 | final encoded = Shims.textEncoder(inp); 9 | final hash = await crypto.Hash.sha256.digestBytes(encoded); 10 | return hash.buffer; 11 | } -------------------------------------------------------------------------------- /lib/src/sear/shims.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert' show utf8; 2 | import 'dart:typed_data'; 3 | import 'package:webcrypto/webcrypto.dart' as crypto; 4 | import 'safe_buffer.dart'; 5 | 6 | class Shims { 7 | 8 | static Uint8List random(int len) { 9 | final bytes = SafeBuffer.alloc(len); 10 | crypto.fillRandomBytes(bytes); 11 | return bytes; 12 | } 13 | 14 | static String textDecoder(List encoded) { 15 | return utf8.decode(encoded); 16 | } 17 | 18 | static List textEncoder(String encoded) { 19 | return utf8.encode(encoded); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /lib/src/sear/sign.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | import 'package:webcrypto/webcrypto.dart' as crypto; 4 | 5 | import 'settings.dart' show jwk, parse; 6 | import 'sha256.dart' show sha256; 7 | import 'soul.dart' show pubFromSoul; 8 | import 'verify.dart' show verify; 9 | import '../types/gun.dart'; 10 | 11 | import '../types/sear/types.dart'; 12 | 13 | final DefaultOptSignType DEFAULT_OPTS = 14 | DefaultOptSignType.from(encode: 'base64'); 15 | 16 | PrepReturnType prep(dynamic val, String key, GunNode node, String soul) { 17 | return PrepReturnType.from( 18 | key: soul, 19 | dot: key, 20 | col: parse(val), 21 | forward: node.nodeMetaData != null 22 | ? node.nodeMetaData!.forward != null 23 | ? node.nodeMetaData!.forward![key] ?? 0 24 | : 0 25 | : 0, 26 | ); 27 | } 28 | 29 | Future hashForSignature(dynamic prepped) async { 30 | final hash = await sha256(prepped); 31 | 32 | return hash.asUint8List(); 33 | } 34 | 35 | Future hashNodeKey(GunNode node, String key) { 36 | final val = node[key]; 37 | 38 | final parsed = parse(val); 39 | final soul = node.nodeMetaData != null ? node.nodeMetaData!.key! : ""; 40 | final prepped = prep(parsed, key, node, soul); 41 | 42 | return hashForSignature(prepped.toJson()); 43 | } 44 | 45 | Future signHash(Uint8List hash, PairReturnType pair, [String? encoding]) async { 46 | encoding ??= DEFAULT_OPTS.encode; 47 | 48 | final token = jwk(pair.pub, pair.priv); 49 | 50 | final signKey = await crypto.EcdsaPrivateKey.importJsonWebKey(token.toJson(), crypto.EllipticCurve.p256); 51 | 52 | final sig = await signKey.signBytes(hash, crypto.Hash.sha256); 53 | return base64Encode(sig); 54 | } 55 | 56 | Future sign(dynamic data, PairReturnType pair, [DefaultOptSignType? opt]) async { 57 | opt ??= DEFAULT_OPTS; 58 | 59 | final json = parse(data); 60 | 61 | final encoding = opt.encode ?? DEFAULT_OPTS.encode; 62 | 63 | final checkData = opt.check ?? json; 64 | 65 | if (json != null && 66 | (json.runtimeType == {}.runtimeType && 67 | ((json.containsKey('s') && json.containsKey('m')) || 68 | (json.containsKey(':') && json.containsKey('~')))) && 69 | (await verify(data, pair) != null) 70 | ) { 71 | final parsed = parse(checkData); 72 | if (opt.raw != null && opt.raw!) { 73 | return parsed; 74 | } 75 | 76 | return 'SEA${jsonEncode(parsed)}'; 77 | } 78 | 79 | final hash = await hashForSignature(json); 80 | 81 | final sig = await signHash(hash, pair, encoding); 82 | 83 | final r = { 84 | 'm': json, 85 | 's': sig 86 | }; 87 | if (opt.raw != null && opt.raw!) { 88 | return r; 89 | } 90 | 91 | return 'SEA${jsonEncode(r)}'; 92 | } 93 | 94 | Future signNodeValue(GunNode node, String key, PairReturnType pair, [String? _encoding]) async { 95 | _encoding ??= DEFAULT_OPTS.encode; 96 | 97 | final data = node[key]; 98 | final json = parse(data); 99 | 100 | if (data != null && json != null && json is Map && ((json.containsKey('s') && json.containsKey('m')) || (json.containsKey(':') && json.containsKey('~')))) { 101 | // already signed 102 | return SignNodeValueReturnType.fromJson(json); 103 | } 104 | 105 | final hash = await hashNodeKey(node, key); 106 | // final hash = await hashForSignature(json); 107 | final sig = await signHash(hash, pair); 108 | 109 | return SignNodeValueReturnType.fromJson({ 110 | ':': json, 111 | '~': sig 112 | }); 113 | } 114 | 115 | Future signNode(GunNode node, PairReturnType pair, [String? encoding]) async { 116 | encoding ??= DEFAULT_OPTS.encode; 117 | 118 | final GunNode signedNode = GunNode.fromJson({ 119 | '_': node.nodeMetaData?.toJson() 120 | }); 121 | 122 | final soul = node.nodeMetaData?.key; 123 | 124 | for (final key in node.keys) { 125 | if (key == 'pub' /*|| key === "alias"*/ && soul == "~${pair.pub}") { 126 | signedNode[key] = node[key]; 127 | continue; 128 | } 129 | 130 | signedNode[key] = jsonEncode(await signNodeValue(node, key, pair, encoding)); 131 | } 132 | 133 | return signedNode; 134 | } 135 | 136 | 137 | Future signGraph(GunGraphData graph, PairReturnType pair, [String? encoding]) async { 138 | 139 | encoding ??= DEFAULT_OPTS.encode; 140 | 141 | final modifiedGraph = graph; 142 | for (final soul in graph.keys) { 143 | final soulPub = pubFromSoul(soul); 144 | 145 | if (soulPub != pair.pub) { 146 | continue; 147 | } 148 | 149 | final node = graph[soul]; 150 | 151 | if (node == null) { 152 | continue; 153 | } 154 | 155 | modifiedGraph[soul] = await signNode(node, pair, encoding); 156 | } 157 | return modifiedGraph; 158 | } 159 | 160 | 161 | GraphSinger graphSigner(PairReturnType pair, [String? encoding]) { 162 | encoding ??= DEFAULT_OPTS.encode; 163 | 164 | return (GunGraphData graph, GunGraphData _) { 165 | return signGraph(graph, pair, encoding); 166 | }; 167 | } 168 | -------------------------------------------------------------------------------- /lib/src/sear/soul.dart: -------------------------------------------------------------------------------- 1 | String pubFromSoul([String? soul]) { 2 | if (soul == null) { 3 | return ''; 4 | } 5 | final tokens = soul.split('~'); 6 | final last = tokens[tokens.length - 1]; 7 | if (last.isEmpty) { 8 | return ''; 9 | } 10 | final coords = last.split('.'); 11 | if (coords.length < 2) { 12 | return ''; 13 | } 14 | return coords.sublist(0, 2).join('.'); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/sear/unpack.dart: -------------------------------------------------------------------------------- 1 | import 'settings.dart' 2 | show shuffleAttackCutoff, check, parse; 3 | import 'soul.dart' show pubFromSoul; 4 | import '../types/enum.dart'; 5 | import '../types/gun.dart'; 6 | 7 | dynamic unpack([dynamic passedValue, String? key, GunNode? node]) { 8 | var value = passedValue; 9 | 10 | if (value == null) { 11 | return; 12 | } 13 | 14 | if (value.runtimeType == {}.runtimeType && value.containsKey(':')) { 15 | final val = value[':']; 16 | if (val != null) { 17 | return val; 18 | } 19 | } 20 | 21 | if (value.runtimeType == {}.runtimeType && value.containsKey('m')) { 22 | final val = value['m']; 23 | if (val != null) { 24 | value = int.parse(val); 25 | } 26 | } 27 | 28 | if (key == null || value == null) { 29 | return; 30 | } 31 | 32 | if (value == node![key]) { 33 | return value; 34 | } 35 | if (!check(node[key])) { 36 | return value; 37 | } 38 | final soul = node.nodeMetaData != null ? node.nodeMetaData!.key : null; 39 | final state = node.nodeMetaData != null 40 | ? node.nodeMetaData!.forward != null 41 | ? node.nodeMetaData!.forward![key] 42 | : null 43 | : null; 44 | 45 | if (value is List && 46 | value.length == 4 && 47 | value[0] == soul && 48 | value[1] == key && 49 | state?.floor() == value[3].floor()) { 50 | return value[2]; 51 | } 52 | 53 | if (state! < shuffleAttackCutoff) { 54 | return value; 55 | } 56 | } 57 | 58 | GunNode unpackNode(GunNode node, [MutableEnum mut = MutableEnum.immutable]) { 59 | final GunNode result = mut == MutableEnum.mutable 60 | ? node 61 | : GunNode.fromJson({'_': node.nodeMetaData?.toJson()}); 62 | 63 | for (final key in node.keys) { 64 | result[key] = unpack(parse(node[key]), key, node); 65 | } 66 | 67 | return result; 68 | } 69 | 70 | GunGraphData unpackGraph(GunGraphData graph, 71 | [MutableEnum mut = MutableEnum.immutable]) { 72 | final GunGraphData unpackedGraph = 73 | mut == MutableEnum.mutable ? graph : GunGraphData(); 74 | 75 | for (final soul in graph.keys) { 76 | final node = graph[soul]; 77 | final pub = pubFromSoul(soul); 78 | 79 | unpackedGraph[soul] = 80 | node != null && pub.isNotEmpty ? unpackNode(node, mut) : node; 81 | } 82 | 83 | return unpackedGraph; 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/sear/verify.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:convert'; 3 | import 'dart:typed_data'; 4 | 5 | import 'settings.dart' show jwk, parse; 6 | import 'sha256.dart' show sha256; 7 | 8 | import 'package:webcrypto/webcrypto.dart' as crypto; 9 | 10 | import '../types/sear/types.dart'; 11 | 12 | 13 | final DefaultOptVerifyType DEFAULT_OPTS = DefaultOptVerifyType.from(encode: 'base64'); 14 | 15 | 16 | Future importKey(String pub, [PairReturnType? d]) { 17 | final token = jwk(pub, d?.priv); 18 | return crypto.EcdsaPublicKey.importJsonWebKey(token.toJson(), crypto.EllipticCurve.p256); 19 | } 20 | 21 | Future verifyHashSignature(Uint8List hash, String signature, String pub, [PairReturnType? d, DefaultOptVerifyType? opt]) async { 22 | opt ??= DEFAULT_OPTS; 23 | 24 | final key = await importKey(pub); 25 | 26 | final sig = base64Decode(signature); 27 | 28 | if (await key.verifyBytes(sig, hash, crypto.Hash.sha256)) { 29 | return true; 30 | } 31 | 32 | return false; 33 | } 34 | 35 | 36 | Future verifySignature(dynamic data, String signature, String pub, [PairReturnType? d, DefaultOptVerifyType? opt]) async { 37 | opt ??= DEFAULT_OPTS; 38 | 39 | final hash = await sha256(data); 40 | 41 | return verifyHashSignature(hash.asUint8List(), signature, pub, d, opt); 42 | } 43 | 44 | Future verify(dynamic data, dynamic pair, [DefaultOptVerifyType? opt]) async { 45 | 46 | if (data == null) { 47 | throw ("data `null` not allowed."); 48 | } 49 | 50 | opt ??= DEFAULT_OPTS; 51 | 52 | final json = parse(data); 53 | 54 | final pub = pair is PairReturnType ? pair.pub : pair; 55 | 56 | if (await verifySignature(json['m'] ?? json[':'], json['s'] ?? json['~'], pub, pair, opt)) { 57 | return json['m'] ?? json[':']; 58 | } 59 | 60 | if (opt.fallback != null && opt.fallback!) { 61 | return oldVerify(data, pub, opt); 62 | } 63 | 64 | return null; 65 | } 66 | 67 | Future oldVerify(dynamic _data, String _pub, [DefaultOptVerifyType? _opt]) async { 68 | throw ('Legacy fallback validation not yet supported'); 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/sear/work.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'shims.dart'; 4 | import 'package:webcrypto/webcrypto.dart' as crypto; 5 | 6 | import 'settings.dart' show pbkdf2; 7 | 8 | import '../types/sear/types.dart'; 9 | 10 | final DefaultWorkFn DEFAULT_OPTS = 11 | DefaultWorkFn.from(encode: 'base64', name: 'PBKDF2', hash: pbkdf2['hash']); 12 | 13 | Future work(String data, PairReturnType pair, [DefaultWorkFn? opt]) async { 14 | opt ??= DEFAULT_OPTS; 15 | 16 | final salt = pair.epub; 17 | 18 | final key = 19 | await crypto.Pbkdf2SecretKey.importRawKey(Shims.textEncoder(data)); 20 | 21 | final res = await key.deriveBits( 22 | opt.length ?? pbkdf2['ks'] * 8, 23 | opt.hash ?? DEFAULT_OPTS.hash!, 24 | Shims.textEncoder(salt), 25 | opt.iterations ?? pbkdf2['iter']); 26 | 27 | return base64Encode(res.toList()); 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/storage/init.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:typed_data'; 3 | import 'package:flutter_secure_storage/flutter_secure_storage.dart'; 4 | import 'package:hive_flutter/adapters.dart'; 5 | 6 | class InitStorage { 7 | static const secureStorage = FlutterSecureStorage(); 8 | static Box? hiveOpenBox; 9 | static const _internalKey = 'secure-encrypted-vaultBox-key'; 10 | static const _internalHiveBoxKey = 'secure-vaultBox'; 11 | 12 | Future _getEncryptedKey([String? key]) async { 13 | // if key not exists return null 14 | final encryptionKeyString = 15 | await secureStorage.read(key: key ?? _internalKey); 16 | 17 | if (encryptionKeyString == null) { 18 | final key = Hive.generateSecureKey(); 19 | await secureStorage.write( 20 | key: _internalKey, 21 | value: base64UrlEncode(key), 22 | ); 23 | } 24 | 25 | final secureKey = await secureStorage.read(key: _internalKey); 26 | return base64Url.decode(secureKey!); 27 | } 28 | 29 | static Future> getHiveBox({Uint8List? encryptionKeyUint8List, String? key}) async { 30 | return (hiveOpenBox = await Hive.openBox(_internalHiveBoxKey, 31 | encryptionCipher: 32 | HiveAesCipher(encryptionKeyUint8List ?? await InitStorage()._getEncryptedKey(key)))); 33 | } 34 | } 35 | 36 | Future initializeFlutterGun({Uint8List? encryptionKeyUint8List, String? key}) async { 37 | await Hive.initFlutter(); 38 | await InitStorage.getHiveBox(encryptionKeyUint8List: encryptionKeyUint8List, key: key); 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/storage/store.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import '../crdt/index.dart' show mergeGunNodes; 4 | 5 | import '../types/gun.dart'; 6 | import 'init.dart'; 7 | 8 | GunGraphData getStoreData(GunGraphData graph, [num activeConnectors = 0]) { 9 | 10 | if (InitStorage.hiveOpenBox == null || InitStorage.hiveOpenBox!.isOpen == false) { 11 | throw ("Initiate Flutter Gun using: `await initializeFlutterGun()`"); 12 | } 13 | 14 | if (activeConnectors > 0) { 15 | return graph; 16 | } 17 | 18 | final GunGraphData unpackedGraph = graph; 19 | 20 | for (final soul in graph.keys) { 21 | GunNode? node; 22 | if (InitStorage.hiveOpenBox!.containsKey(soul)) { 23 | GunNode tempNode = GunNode.fromJson(jsonDecode(InitStorage.hiveOpenBox?.get(soul))); 24 | node = mergeGunNodes(tempNode, graph[soul]); 25 | node?.nodeMetaData = graph[soul]?.nodeMetaData; 26 | } else { 27 | node = graph[soul]; 28 | } 29 | 30 | unpackedGraph[soul] = node; 31 | } 32 | 33 | return unpackedGraph; 34 | } 35 | 36 | 37 | GunGraphData setStoreData(GunGraphData graph) { 38 | 39 | if (InitStorage.hiveOpenBox == null || InitStorage.hiveOpenBox!.isOpen == false) { 40 | throw ("Initiate Flutter Gun using: `await initializeFlutterGun()`"); 41 | } 42 | 43 | final GunGraphData unpackedGraph = graph; 44 | 45 | for (final soul in graph.keys) { 46 | GunNode? node; 47 | if (InitStorage.hiveOpenBox!.containsKey(soul)) { 48 | GunNode tempNode = GunNode.fromJson(jsonDecode(InitStorage.hiveOpenBox?.get(soul))); 49 | node = mergeGunNodes(tempNode, graph[soul]); 50 | node?.nodeMetaData = graph[soul]?.nodeMetaData; 51 | } else { 52 | node = graph[soul]; 53 | } 54 | 55 | InitStorage.hiveOpenBox?.put(soul, jsonEncode(node)); 56 | 57 | unpackedGraph[soul] = node; 58 | } 59 | 60 | return unpackedGraph; 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/types/enum.dart: -------------------------------------------------------------------------------- 1 | enum MutableEnum { 2 | immutable, 3 | mutable 4 | } -------------------------------------------------------------------------------- /lib/src/types/flutter_gun.dart: -------------------------------------------------------------------------------- 1 | import 'gun.dart'; 2 | 3 | typedef GunMsgCb = void Function(GunMsg msg); 4 | typedef LexicalFunc = dynamic Function(GunValue x); 5 | 6 | /// How puts are communicated to FlutterGun connectors 7 | class FlutterGunPut { 8 | late GunGraphData graph; 9 | String? msgId; 10 | String? replyTo; 11 | GunMsgCb? cb; 12 | 13 | FlutterGunPut({ required this.graph, this.msgId, this.replyTo, this.cb }); 14 | } 15 | 16 | /// How gets are communicated to FlutterGun connectors 17 | class FlutterGunGet { 18 | late String soul; 19 | String? msgId; 20 | String? key; 21 | GunMsgCb? cb; 22 | FlutterGunGet({ required this.soul, this.msgId, this.key, this.cb }); 23 | } 24 | 25 | class CrdtOption { 26 | num? machineState; 27 | num? futureGrace; 28 | LexicalFunc? lexical; 29 | 30 | CrdtOption({this.machineState, this.futureGrace, this.lexical}); 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/types/generic.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:typed_data'; 3 | 4 | import '../sear/base64.dart'; 5 | 6 | /// Custom tuple 7 | class Tuple { 8 | final T1 item1; 9 | final T2 item2; 10 | 11 | Tuple({ 12 | required this.item1, 13 | required this.item2, 14 | }); 15 | 16 | factory Tuple.fromJson(Map json) { 17 | return Tuple( 18 | item1: json['item1'], 19 | item2: json['item2'], 20 | ); 21 | } 22 | } 23 | 24 | class GenericCustomList extends ListBase { 25 | @override 26 | late int length; 27 | 28 | @override 29 | T operator [](int index) => this[index]; 30 | 31 | String toCustomString([String? enc, int? start, int? end]) { 32 | enc ??= 'utf8'; 33 | start ??= 0; 34 | final length = this.length; 35 | if (enc == 'hex') { 36 | final buf = Uint8List.fromList(this); 37 | final num = ((end != null ? end + 1 : null) ?? length) - start; 38 | var res = ''; 39 | for (var i = 0; i < num; i++) { 40 | res += buf[i + start].toRadixString(16).padLeft(2, '0'); 41 | } 42 | return res; 43 | } 44 | if (enc == 'utf8') { 45 | final num = (end ?? length) - start; 46 | var res = ''; 47 | for (var i = 0; i < num; i++) { 48 | res += String.fromCharCode(this[i + start]); 49 | } 50 | return res; 51 | } 52 | if (enc == 'base64') { 53 | return SearBase64.btob(this); 54 | } 55 | return ""; 56 | } 57 | 58 | @override 59 | void operator []=(int index, T value) => add(value); 60 | } 61 | 62 | /// Custom Map Base Key, Value 63 | class GenericCustomValueMap extends MapBase { 64 | final Map _map = HashMap.identity(); 65 | 66 | @override 67 | Iterable get keys => _map.keys; 68 | 69 | @override 70 | V? operator [](Object? key) => _map[key]; 71 | 72 | @override 73 | void operator []=(K key, V value) => _map[key] = value; 74 | 75 | @override 76 | void clear() => _map.clear(); 77 | 78 | Map toMap() => _map; 79 | 80 | @override 81 | V? remove(Object? key) => _map.remove(key); 82 | 83 | void merge(dynamic mapData) => _map.addAll(Map.from(mapData)); 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/types/gun.dart: -------------------------------------------------------------------------------- 1 | import 'generic.dart'; 2 | 3 | /// Timestamp of last change for each attribute 4 | class GunNodeState extends GenericCustomValueMap {} 5 | 6 | /// Soul and State of a Gun Node 7 | class GunNodeMeta { 8 | String? key; // # 9 | GunNodeState? forward; // > 10 | 11 | GunNodeMeta({this.key, this.forward}); 12 | 13 | Map toJson() => 14 | {'#': key, '>': forward?.toMap()}; 15 | 16 | factory GunNodeMeta.fromJson(Map parsedJson) { 17 | GunNodeState gunNodeState = GunNodeState(); 18 | if (parsedJson.containsKey('>')) { 19 | gunNodeState.merge(parsedJson['>']); 20 | } 21 | return GunNodeMeta( 22 | key: parsedJson['#'].toString(), 23 | forward: gunNodeState, 24 | ); 25 | } 26 | } 27 | 28 | /// A node (or partial node data) in a Gun Graph 29 | class GunNode extends GenericCustomValueMap { 30 | late GunNodeMeta? nodeMetaData; // _ 31 | 32 | GunNode({this.nodeMetaData}); 33 | 34 | Map toJson() => 35 | {'_': nodeMetaData?.toJson(), ...toMap()}; 36 | 37 | factory GunNode.fromJson(Map parsedJson) { 38 | GunNodeMeta gunNodeMeta = GunNodeMeta(); 39 | if (parsedJson.containsKey('_')) { 40 | gunNodeMeta = GunNodeMeta.fromJson(parsedJson['_']); 41 | parsedJson.remove('_'); 42 | } 43 | GunNode gunNode = GunNode( 44 | nodeMetaData: gunNodeMeta, 45 | ); 46 | gunNode.merge(parsedJson); 47 | return gunNode; 48 | } 49 | } 50 | 51 | /// Gun Graph Data consists of one or more full or partial nodes 52 | class GunGraphData extends GenericCustomValueMap { 53 | GunGraphData(); 54 | 55 | factory GunGraphData.fromJson(Map parsedJson) { 56 | GunGraphData gunGraphData = GunGraphData(); 57 | gunGraphData.addAll(parsedJson.map( 58 | (key, value) => MapEntry(key, GunNode.fromJson(value)))); 59 | return gunGraphData; 60 | } 61 | } 62 | 63 | class GunMsgGet { 64 | String? key; // # 65 | 66 | GunMsgGet({this.key}); 67 | 68 | Map toJson() => {'#': key}; 69 | 70 | factory GunMsgGet.fromJson(Map parsedJson) { 71 | return GunMsgGet( 72 | key: parsedJson['#'].toString(), 73 | ); 74 | } 75 | } 76 | 77 | /// A standard Gun Protocol Message 78 | class GunMsg { 79 | String? key; // # 80 | String? pos; // @ 81 | bool? ack; 82 | dynamic err; 83 | dynamic ok; 84 | GunGraphData? put; 85 | GunMsgGet? get; 86 | 87 | GunMsg({this.key, this.pos, this.put, this.get, this.ack, this.err, this.ok}); 88 | 89 | Map toJson() { 90 | final data = { 91 | '#': key, 92 | '@': pos, 93 | 'ack': ack, 94 | 'err': err, 95 | 'ok': ok, 96 | 'get': get?.toJson(), 97 | 'put': put?.toMap().map( 98 | (key, value) => MapEntry(key, value?.toJson())), 99 | }; 100 | data.removeWhere((key, value) => value == null); 101 | return data; 102 | } 103 | 104 | factory GunMsg.fromJson(Map parsedJson) => GunMsg( 105 | key: parsedJson['#']?.toString(), 106 | pos: parsedJson['@']?.toString(), 107 | ack: parsedJson['ack']?.toString() == 'true', 108 | ok: parsedJson['ok'], 109 | err: parsedJson['err']?.toString(), 110 | get: parsedJson.containsKey('get') 111 | ? GunMsgGet.fromJson(parsedJson['get']) 112 | : null, 113 | put: parsedJson.containsKey('put') 114 | ? GunGraphData.fromJson(parsedJson['put']) 115 | : null); 116 | } 117 | 118 | typedef GunValue = dynamic; 119 | -------------------------------------------------------------------------------- /lib/src/types/gun_graph_adapter.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'generic.dart'; 3 | import 'gun.dart'; 4 | 5 | typedef ChangeSetEntry = Tuple; 6 | typedef ChangeSetEntryFunc = Future Function(); 7 | typedef VoidCallback = void Function(); 8 | typedef SetChangeSetEntryFunc = void Function(ChangeSetEntry change); 9 | 10 | class GunGetOpts { 11 | final String? point; // . 12 | final String? forward; // > 13 | final String? backward; // < 14 | final String? modulo; // % 15 | 16 | GunGetOpts(this.point, this.forward, this.backward, this.modulo); 17 | } 18 | 19 | abstract class GunGraphAdapter { 20 | void close(); 21 | Future get(String soul, [GunGetOpts opts]); 22 | Future getJsonString(String soul, [GunGetOpts opts]); 23 | String getJsonStringSync(String soul, [GunGetOpts opts]); 24 | GunNode? getSync(String soul, [GunGetOpts opts]); 25 | Future put(GunGraphData graphData); 26 | GunGraphData? putSync(GunGraphData graphData); 27 | 28 | Future pruneChangelog(num before); 29 | 30 | ChangeSetEntryFunc getChangesetFeed(String from); 31 | 32 | VoidCallback onChange(SetChangeSetEntryFunc handler, [String from]); 33 | } -------------------------------------------------------------------------------- /lib/src/types/sear/types.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:webcrypto/webcrypto.dart' as crypto; 5 | 6 | import '../gun.dart'; 7 | 8 | class JWK { 9 | String crv; 10 | String? d; 11 | bool? ext = false; 12 | List? key_opts = []; 13 | String kty = ""; 14 | String x = ""; 15 | String y = ""; 16 | 17 | JWK.from({ 18 | required this.kty, 19 | required this.x, 20 | required this.y, 21 | required this.crv, 22 | this.d, 23 | this.ext, 24 | this.key_opts, 25 | }); 26 | 27 | Map toJson() { 28 | final data = { 29 | 'kty': kty, 30 | 'crv': crv, 31 | 'x': x, 32 | 'y': y, 33 | }; 34 | 35 | if (d != null && d!.isNotEmpty) { 36 | data['d'] = d; 37 | } 38 | if (ext != null) { 39 | data['ext'] = ext; 40 | } 41 | if (key_opts != null && key_opts!.isNotEmpty) { 42 | data['key_opts'] = key_opts; 43 | } 44 | 45 | return data; 46 | } 47 | } 48 | 49 | class KeyToJwk { 50 | String k = ""; 51 | String kty = ""; 52 | bool ext = false; 53 | String alg = ""; 54 | 55 | KeyToJwk.from( 56 | {required this.k, 57 | required this.kty, 58 | required this.ext, 59 | required this.alg}); 60 | 61 | Map toJson() => 62 | {'k': k, 'kty': kty, 'ext': ext, 'alg': alg}; 63 | } 64 | 65 | class DefaultAESKey { 66 | String? name; 67 | 68 | DefaultAESKey.from({this.name}); 69 | } 70 | 71 | class DefaultAESDecryptKey { 72 | String? name; 73 | String? encode; 74 | String? fallback; 75 | 76 | DefaultAESDecryptKey.from({this.name, this.encode, this.fallback}); 77 | } 78 | 79 | class DefaultAESWorkKey { 80 | String? name; 81 | String? encode; 82 | Map? hash; 83 | 84 | DefaultAESWorkKey.from({this.name, this.encode, this.hash}); 85 | } 86 | 87 | class DefaultWorkFn { 88 | String? name; 89 | String? encode; 90 | num? iterations; 91 | num? length; 92 | crypto.Hash? hash; 93 | 94 | DefaultWorkFn.from( 95 | {this.name, this.encode, this.hash, this.iterations, this.length}); 96 | } 97 | 98 | class AuthenticateReturnDataType { 99 | String alias; 100 | String epriv; 101 | String epub; 102 | String priv; 103 | String pub; 104 | 105 | AuthenticateReturnDataType.from( 106 | {required this.alias, 107 | required this.epriv, 108 | required this.epub, 109 | required this.priv, 110 | required this.pub}); 111 | 112 | Map toJson() => { 113 | 'alias': alias, 114 | 'epub': epub, 115 | 'pub': pub, 116 | 'epriv': epriv, 117 | 'priv': priv, 118 | }; 119 | 120 | factory AuthenticateReturnDataType.fromJson(Map parsedJson) { 121 | return AuthenticateReturnDataType.from( 122 | alias: parsedJson['alias'], 123 | epub: parsedJson['epub'], 124 | pub: parsedJson['pub'], 125 | epriv: parsedJson['epriv'], 126 | priv: parsedJson['priv'] 127 | ); 128 | } 129 | } 130 | 131 | class DefaultAESEncryptKey { 132 | String? name; 133 | String? encode; 134 | bool? raw; 135 | 136 | DefaultAESEncryptKey.from({this.name, this.encode, this.raw}); 137 | } 138 | 139 | class EncryptFnReturnType { 140 | String ct; 141 | String iv; 142 | String s; 143 | 144 | EncryptFnReturnType.from( 145 | {required this.ct, required this.iv, required this.s}); 146 | } 147 | 148 | class PairReturnType { 149 | String epriv; 150 | String epub; 151 | String priv; 152 | String pub; 153 | 154 | PairReturnType.from( 155 | {required this.epriv, 156 | required this.epub, 157 | required this.priv, 158 | required this.pub}); 159 | } 160 | 161 | class DefaultOptVerifyCheckType { 162 | dynamic m; 163 | String s; 164 | 165 | DefaultOptVerifyCheckType.from({required this.m, required this.s}); 166 | } 167 | 168 | class DefaultOptVerifyType { 169 | bool? fallback; 170 | String? encode; 171 | bool? raw; 172 | DefaultOptVerifyCheckType? check; 173 | 174 | DefaultOptVerifyType.from({this.fallback, this.encode, this.raw, this.check}); 175 | } 176 | 177 | class DefaultOptSignType { 178 | String? encode; 179 | bool? raw; 180 | DefaultOptVerifyCheckType? check; 181 | 182 | DefaultOptSignType.from({this.encode, this.raw, this.check}); 183 | } 184 | 185 | class PrepReturnType { 186 | String key; // # 187 | String dot; // . 188 | dynamic col; // : 189 | num forward; // > 190 | 191 | PrepReturnType.from( 192 | {required this.key, 193 | required this.dot, 194 | required this.col, 195 | required this.forward}); 196 | 197 | Map toJson() => 198 | {'#': key, '.': dot, ':': col, '>': forward}; 199 | } 200 | 201 | class KeyPair { 202 | String pub; 203 | String priv; 204 | 205 | KeyPair.from({required this.pub, required this.priv}); 206 | } 207 | 208 | class SignNodeValueReturnType { 209 | GunValue col; // : 210 | String tilde; // ~ 211 | 212 | SignNodeValueReturnType.from({required this.col, required this.tilde}); 213 | 214 | Map toJson() => { 215 | ':': col, 216 | '~': tilde, 217 | }; 218 | 219 | factory SignNodeValueReturnType.fromJson(Map parsedJson) { 220 | return SignNodeValueReturnType.from( 221 | col: parsedJson[':'] ?? parsedJson['m'], tilde: parsedJson['~'] ?? parsedJson['s']); 222 | } 223 | } 224 | 225 | typedef GraphSinger = FutureOr Function(GunGraphData graph, GunGraphData _graph); 226 | 227 | class CreateUserReturnType { 228 | String alias; 229 | String auth; 230 | String epub; 231 | String pub; 232 | String epriv; 233 | String priv; 234 | 235 | CreateUserReturnType.from( 236 | {required this.alias, 237 | required this.auth, 238 | required this.epub, 239 | required this.pub, 240 | required this.epriv, 241 | required this.priv}); 242 | 243 | Map toJson() => { 244 | 'alias': alias, 245 | 'auth': auth, 246 | 'epub': epub, 247 | 'pub': pub, 248 | 'epriv': epriv, 249 | 'priv': priv, 250 | }; 251 | 252 | factory CreateUserReturnType.fromJson(Map parsedJson) { 253 | return CreateUserReturnType.from( 254 | alias: parsedJson['alias'], 255 | auth: parsedJson['auth'], 256 | epub: parsedJson['epub'], 257 | pub: parsedJson['pub'], 258 | epriv: parsedJson['epriv'], 259 | priv: parsedJson['priv'] 260 | ); 261 | } 262 | } 263 | 264 | class DefaultCertifyOPTType { 265 | num? expiry; 266 | bool? raw; 267 | 268 | dynamic block; 269 | 270 | DefaultCertifyOPTType.from({this.expiry, this.raw, this.block}); 271 | } 272 | 273 | class CertifyPolicyType { 274 | dynamic read; 275 | dynamic write; 276 | 277 | CertifyPolicyType.from({this.read, this.write}); 278 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_gundb 2 | description: A gunDB port P2P encrypted Communication between multiple users. 3 | version: 0.0.3 4 | homepage: https://janatig.com/ 5 | repository: https://github.com/adityapandey9/flutter-gun 6 | issue_tracker: https://github.com/adityapandey9/flutter-gun/issues 7 | 8 | environment: 9 | sdk: ">=2.17.1 <3.0.0" 10 | flutter: ">=1.17.0" 11 | 12 | dependencies: 13 | uuid: ^3.0.6 14 | mutex: ^3.0.0 15 | web_socket_channel: ^2.2.0 16 | webcrypto: ^0.5.3 17 | convert: ^3.1.1 18 | hive: ^2.2.3 19 | hive_flutter: ^1.1.0 20 | flutter_secure_storage: ^8.0.0 21 | flutter: 22 | sdk: flutter 23 | 24 | dev_dependencies: 25 | flutter_test: 26 | sdk: flutter 27 | flutter_lints: ^2.0.0 28 | 29 | # For information on the generic Dart part of this file, see the 30 | # following page: https://dart.dev/tools/pub/pubspec 31 | 32 | # The following section is specific to Flutter packages. 33 | flutter: 34 | 35 | # To add assets to your package, add an assets section, like this: 36 | # assets: 37 | # - images/a_dot_burr.jpeg 38 | # - images/a_dot_ham.jpeg 39 | # 40 | # For details regarding assets in packages, see 41 | # https://flutter.dev/assets-and-images/#from-packages 42 | # 43 | # An image asset can refer to one or more resolution-specific "variants", see 44 | # https://flutter.dev/assets-and-images/#resolution-aware 45 | 46 | # To add custom fonts to your package, add a fonts section here, 47 | # in this "flutter" section. Each entry in this list should have a 48 | # "family" key with the font family name, and a "fonts" key with a 49 | # list giving the asset and other descriptors for the font. For 50 | # example: 51 | # fonts: 52 | # - family: Schyler 53 | # fonts: 54 | # - asset: fonts/Schyler-Regular.ttf 55 | # - asset: fonts/Schyler-Italic.ttf 56 | # style: italic 57 | # - family: Trajan Pro 58 | # fonts: 59 | # - asset: fonts/TrajanPro.ttf 60 | # - asset: fonts/TrajanPro_Bold.ttf 61 | # weight: 700 62 | # 63 | # For details regarding fonts in packages, see 64 | # https://flutter.dev/custom-fonts/#from-packages 65 | -------------------------------------------------------------------------------- /test/flutter_gundb_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'package:flutter_gundb/flutter_gundb.dart'; 4 | 5 | void main() { 6 | test('adds one to input values', () { 7 | final calculator = FlutterGunDB(); 8 | // expect(calculator.addOne(2), 3); 9 | // expect(calculator.addOne(-7), -6); 10 | // expect(calculator.addOne(0), 1); 11 | }); 12 | } 13 | --------------------------------------------------------------------------------