├── .gitattributes ├── .gitignore ├── .packages ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── example ├── bin │ ├── file_watcher_server.dart │ ├── receivers │ │ └── justareceiver.dart │ └── server.dart ├── pubspec.yaml ├── store │ └── todo.txt └── web │ ├── dartforcetodo.css │ ├── dartforcetodo.dart │ └── dartforcetodo.html ├── lib ├── cargo │ ├── cargo_holder.dart │ ├── cargo_holder_client.dart │ ├── cargo_holder_server.dart │ ├── cargo_package.dart │ ├── cargo_package_dispatcher.dart │ ├── cargo_protocol.dart │ ├── data_changeable.dart │ └── view_collection.dart ├── client │ ├── browser_messenger.dart │ ├── connect_event.dart │ └── force_client.dart ├── clientsocket │ ├── abstract_socket.dart │ ├── polling_socket.dart │ └── websocket_wrapper.dart ├── common │ ├── base_force_client.dart │ ├── basic_sendable.dart │ ├── basic_sender.dart │ ├── client_context.dart │ ├── client_sendable.dart │ ├── package.dart │ └── protocol.dart ├── connectors │ ├── connector.dart │ └── server_socket_connector.dart ├── force_browser.dart ├── force_common.dart ├── force_serverside.dart ├── message │ ├── base_message.dart │ ├── message_dispatcher.dart │ ├── message_package.dart │ ├── message_security.dart │ ├── message_type.dart │ └── messages_construct_helper.dart ├── server │ ├── force.dart │ ├── force_context.dart │ ├── force_server.dart │ ├── metadata.dart │ ├── polling_server.dart │ ├── profile_event.dart │ ├── serveable.dart │ ├── server_protocol.dart │ └── socket_event.dart ├── serverclient │ ├── force_client.dart │ └── server_messenger.dart ├── serversocket │ ├── abstract_socket.dart │ ├── polling_socket.dart │ ├── server_socket.dart │ ├── stream_socket.dart │ └── websocket_wrapper.dart └── test.dart ├── pubspec.yaml ├── readme.md ├── resources └── dart_force_logo.jpg ├── test ├── cargo_tests.dart ├── flow_tests.dart ├── pollingserver_tests.dart └── serverside_tests.dart └── walkthrough.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dart excludes 2 | packages 3 | .project 4 | .children 5 | *.js 6 | pubspec.lock 7 | .idea 8 | .pub 9 | build 10 | *.packages 11 | -------------------------------------------------------------------------------- /.packages: -------------------------------------------------------------------------------- 1 | # Generated by pub on 2016-02-22 12:12:19.147077. 2 | analyzer:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/analyzer-0.25.0+1/lib/ 3 | args:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/args-0.13.2/lib/ 4 | async:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/async-1.2.0/lib/ 5 | barback:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/barback-0.15.2+4/lib/ 6 | browser:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/browser-0.10.0+2/lib/ 7 | cargo:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/cargo-0.8.0/lib/ 8 | collection:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/collection-1.1.1/lib/ 9 | crypto:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/crypto-0.9.0/lib/ 10 | forcemvc:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/forcemvc-0.8.0/lib/ 11 | glob:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/glob-1.0.4/lib/ 12 | http_multi_server:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/http_multi_server-1.3.2/lib/ 13 | http_parser:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/http_parser-0.0.2+7/lib/ 14 | http_server:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/http_server-0.9.5+1/lib/ 15 | jsonify:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/jsonify-0.0.1/lib/ 16 | locale:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/locale-0.1.1+2/lib/ 17 | logging:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/logging-0.11.2/lib/ 18 | matcher:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/matcher-0.12.0+1/lib/ 19 | mime:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/mime-0.9.3/lib/ 20 | mirrorme:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/mirrorme-0.2.2/lib/ 21 | mock:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/mock-0.11.0+4/lib/ 22 | mustache4dart:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/mustache4dart-1.0.10/lib/ 23 | path:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/path-1.3.5/lib/ 24 | pool:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/pool-1.1.0/lib/ 25 | pub_semver:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/pub_semver-1.2.1/lib/ 26 | route:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/route-0.4.6/lib/ 27 | shelf:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/shelf-0.6.1+2/lib/ 28 | shelf_static:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/shelf_static-0.2.2/lib/ 29 | shelf_web_socket:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/shelf_web_socket-0.0.1+2/lib/ 30 | source_map_stack_trace:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/source_map_stack_trace-1.0.4/lib/ 31 | source_maps:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/source_maps-0.10.1/lib/ 32 | source_span:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/source_span-1.1.2/lib/ 33 | stack_trace:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/stack_trace-1.3.4/lib/ 34 | string_scanner:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/string_scanner-0.1.3+1/lib/ 35 | test:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/test-0.12.3+4/lib/ 36 | unittest:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/unittest-0.11.6+1/lib/ 37 | uuid:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/uuid-0.5.0/lib/ 38 | watcher:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/watcher-0.9.6/lib/ 39 | wired:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/wired-0.4.4/lib/ 40 | yaml:file:///C:/Users/joris.hermans/AppData/Roaming/Pub/Cache/hosted/pub.dartlang.org/yaml-2.1.3/lib/ 41 | force:lib/ 42 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Originally written by: 2 | Joris Hermans - http://github.com/jorishermans 3 | 4 | With contributions by: 5 | Robert Åkerblom-Andersson - https://github.com/Scorpiion -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Changelog ### 2 | 3 | This file contains highlights of what changes on each version of the force package. 4 | 5 | #### Pub version 0.7.1 #### 6 | 7 | - use the latest forcemvc 8 | 9 | #### Pub version 0.7.0 #### 10 | 11 | - upgrade packages in pubspec.yaml 12 | - use the latest forcemvc 13 | 14 | #### Pub version 0.6.5+1 #### 15 | 16 | - improve the test package of force 17 | 18 | #### Pub version 0.6.5 #### 19 | 20 | - adding more conditional logic when dispatching a protocol 21 | - add a test library so you can test your protocol flows better 22 | 23 | #### Pub version 0.6.4 #### 24 | 25 | - add your custom protocols on the force stack, providing an easy way to extends the management of websockets 26 | - Remove pingpong protocol (there is still a keepalive possibility on websockets) 27 | 28 | #### Pub version 0.6.3+4 #### 29 | 30 | - Fix a bug in view_collection.dart on the revert flow 31 | 32 | #### Pub version 0.6.3+3 #### 33 | 34 | - improve testing and code quality 35 | - update uuid to 0.5.0 36 | - fix issue with posting from IE9 to the polling server 37 | 38 | #### Pub version 0.6.3+1 & 0.6.3+2 #### 39 | 40 | - add the parsing of a Blob in a websocket that is been send by the server. 41 | - clean up on the long polling implementation 42 | - change the implementation of StreamSocket to work well with shelf_web_socket 43 | 44 | #### Pub version 0.6.3 #### 45 | 46 | - add onChange listener on viewCollection 47 | - change logic of keep alives to native keep alive implementation of a websocket 48 | 49 | #### Pub version 0.6.2+2 #### 50 | 51 | - add some logging about the keep alive 52 | 53 | #### Pub version 0.6.2 & 0.6.2+1 #### 54 | 55 | - keep a state of message when the client is offline, when it is back online we send these messages to the server 56 | - add a ping / pong protocol 57 | - add a keep alive timer, that send every x seconds ping signals to the clients 58 | 59 | #### Pub version 0.6.1 #### 60 | 61 | - add options possibility of cargo into the db client api of force 62 | - easier transform your clientside objects with serialize function 63 | - upgrade to forcemvc 0.7.0 and wired 0.4.3 64 | 65 | #### Pub version 0.6.0 #### 66 | 67 | - designing clientside db api 68 | - ForceClient: BC BREAK - port should be int instead of String 69 | - adding protocols wiring into force, having 2 clean api's next to eachother, db & communication api. 70 | - improvements on forceclient 71 | 72 | #### Pub version 0.5.7 #### 73 | 74 | - adapting the start method so it is compliant with the latest ForceMVC release! 75 | Future start({FallbackStart fallback}) 76 | 77 | #### Pub version 0.5.6+1 #### 78 | 79 | - exporting underlying packages! 80 | 81 | #### Pub version 0.5.6 #### 82 | 83 | - Add 'reply' method to sender 84 | - It works in IE9 85 | - Upgrade to the latest ForceMVC implementations 86 | 87 | #### Pub version 0.5.5 #### 88 | 89 | - Server to server communication, based upon ServerSockets 90 | - Introducing connectors 91 | 92 | #### Pub version 0.5.4 #### 93 | 94 | - Prepare code for use on appengine 95 | 96 | #### Pub version 0.5.3 #### 97 | 98 | - Add new annotation @ClosedConnection and event stream for closedConnection 99 | - Add more documentation in the code 100 | 101 | #### Pub version 0.5.2 #### 102 | 103 | fix dependencies on 'wired' 104 | 105 | #### Pub version 0.5.1 & pub version 0.5.1+1 #### 106 | 107 | introduce @NewConnection 108 | send a message 'ack' to the new client on connection 109 | 110 | #### Pub version 0.5.0+1 #### 111 | 112 | Improve internal working. 113 | 114 | #### Pub version 0.5.0 #### 115 | 116 | Remove parentheses from annotations. 117 | Upgrade to latest forcemvc version (0.5.0+1). 118 | Expanding functionality for authentication with roles, like in forcemvc. 119 | Adding broadcast functionality 120 | 121 | #### Pub version 0.4.2+1 #### 122 | 123 | Moving this framework to an organisation repo in github 124 | 125 | #### Pub version 0.4.2 #### 126 | 127 | When a new Socket is been created a new SocketEvent will be added. 128 | 129 | #### Pub version 0.4.1+1 #### 130 | 131 | Small changes in the Force class, renaming of the handle method. 132 | 133 | #### Pub version 0.4.1 #### 134 | 135 | Making the force logic abstract, independent of the serverside http logic. 136 | 137 | #### Pub version 0.4.0+2 #### 138 | 139 | Adding more tests for polling implementation, small improvements to the polling implementation. 140 | 141 | #### Pub version 0.4.0 & 0.4.0+1 #### 142 | 143 | Make it possible to add @Autowired and @Value into the classes with @Receivable annotations. 144 | 145 | #### Pub version 0.3.7 #### 146 | 147 | Making the setup of a project easier by creating a logging function. 148 | 149 | #### Pub version 0.3.6 #### 150 | 151 | Add authentication rules into the framework. 152 | 153 | #### Pub version 0.3.5 #### 154 | 155 | New structure! Look at the examples. 156 | 157 | #### Pub version 0.3.4 #### 158 | 159 | Add host and port parameters to force client. 160 | 161 | #### Pub version 0.3.3+10 #### 162 | 163 | Update dependency forcemvc. 164 | 165 | #### Pub version 0.3.3+9 #### 166 | 167 | Improve the working of the polling mechanics into the right direction. 168 | 169 | #### Pub version 0.3.3+8 #### 170 | 171 | Updating uuid package dependencies 172 | 173 | #### Pub version 0.3.3+7 #### 174 | 175 | Adding option for static folder of force mvc, staticDir. 176 | 177 | #### Pub version 0.3.3+6 #### 178 | 179 | Updating dependencies. 180 | 181 | #### Pub version 0.3.3+5 #### 182 | 183 | Solving issue with startpage rendering! 184 | 185 | #### Pub version 0.3.3+4 #### 186 | 187 | Updated this buildPath: '../build/web' to get it working in Dart 1.2 188 | 189 | #### Pub version 0.3.3+2 & 0.3.3+3 #### 190 | 191 | Updating dependencies. 192 | 193 | #### Pub version 0.3.3+1 #### 194 | 195 | Changing force_serveable with serve(name) with typing it with String. 196 | 197 | #### Pub version 0.3.3 #### 198 | 199 | Introducing @Receivable so you can annotate a class that has receiver methods. 200 | 201 | #### Pub version 0.3.2+4 #### 202 | 203 | Updating dependencies and force mirrors code. 204 | 205 | #### Pub version 0.3.2+2 #### 206 | 207 | Solving the initialization of the client socket. 208 | 209 | #### Pub version 0.3.2 #### 210 | 211 | Solving issue #12 and more toolable way to handle connecting. 212 | So now you need to implement onConnected and onDisconnected. 213 | 214 | #### Pub version 0.3.1+3 #### 215 | 216 | You have access to the webserver through server property of the force server implementation. 217 | 218 | #### Pub version 0.3.1+2 #### 219 | 220 | Update to the new force mvc package, update polling server code. 221 | 222 | #### Pub version 0.3.1+1 #### 223 | 224 | Iterate over all the annotations at a method until you found the Receiver annotation. 225 | 226 | #### Pub version 0.3.1 #### 227 | 228 | Extract webServer code and put it into forcemvc package 229 | 230 | #### Pub version 0.3.0+5 & 0.3.0+6 #### 231 | 232 | Some small changes in logging and an update to the documentation. 233 | 234 | #### Pub version 0.3.0+4 #### 235 | 236 | Adding a generateId method to the forceclient class. So you can use this unique id in the client to start something, for example a gamesession! 237 | 238 | #### Pub version 0.3.0+3 #### 239 | 240 | Added an optional parameter url to forceclient so you can set the url to another hosted force server endpoint. 241 | Added an optional parameter heartbeat, to specify in milliseconds the heartbeat duration. 242 | 243 | #### Pub version 0.3.0+2 #### 244 | 245 | Refactor the code so it uses a factory instead of a static method to choose the socket implementation clientside. 246 | Fixed an small issue when sending characters through polling and receiving it again. 247 | 248 | #### Pub version 0.3.0+1 #### 249 | 250 | Sending the old property values in the profile changed event. So you can use the old value and look at the new value in profileInfo field. 251 | 252 | #### Pub version 0.3.0 #### 253 | 254 | Adding socket abstraction to the dart force framework and add also the long polling mechanism as an alternative for websockets. 255 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Joris Hermans 2 | 3 | Joris: https://twitter.com/jorishermans 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. -------------------------------------------------------------------------------- /example/bin/file_watcher_server.dart: -------------------------------------------------------------------------------- 1 | library dart_force_todo_server2; 2 | 3 | import "package:force/force_serverside.dart"; 4 | 5 | import "dart:io"; 6 | 7 | import 'package:logging/logging.dart' show Logger, Level, LogRecord; 8 | 9 | void main() { 10 | 11 | Logger.root.level = Level.INFO; 12 | Logger.root.onRecord.listen((LogRecord rec) { 13 | if (rec.level >= Level.SEVERE) { 14 | var stack = rec.stackTrace != null ? rec.stackTrace : ""; 15 | print('${rec.level.name}: ${rec.time}: ${rec.message} - ${rec.error} $stack'); 16 | } else { 17 | print('${rec.level.name}: ${rec.time}: ${rec.message}'); 18 | } 19 | }); 20 | 21 | ForceClient fc = new ForceClient(); 22 | 23 | fc.on("update", (fme, sender) { 24 | print("todo: ${fme.json["todo"]}"); 25 | }); 26 | 27 | var pathTo = Platform.script.resolve("../store/").toFilePath(); 28 | var uriKey = new Uri.file(pathTo).resolve("todo.txt"); 29 | File file = new File(uriKey.toFilePath()); 30 | Directory dir = new Directory(pathTo); 31 | 32 | String content = ""; 33 | 34 | fc.connect().then((_) { 35 | // readlines 36 | dir.watch().listen((FileSystemEvent fse) { 37 | if (fse.type == FileSystemEvent.MODIFY && !fse.isDirectory) { 38 | String newContent = file.readAsStringSync(); 39 | if (newContent != content) { 40 | List lines = newContent.split("\n"); 41 | for (var line in lines) { 42 | var data = {"todo": line}; 43 | fc.send("add", data); 44 | } 45 | } 46 | content = newContent; 47 | } 48 | }); 49 | }); 50 | 51 | } 52 | -------------------------------------------------------------------------------- /example/bin/receivers/justareceiver.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_todo; 2 | 3 | @Receivable 4 | class JustAReceiver { 5 | 6 | @NewConnection 7 | void connection(socketId, ForceSocket socket) { 8 | print("new connection created for $socketId"); 9 | } 10 | 11 | @Receiver("help") 12 | void help(fme, sender) { 13 | sender.send("options", {"values": ["a", "b", "c"]}); 14 | 15 | // do more with this! 16 | } 17 | 18 | @ClosedConnection 19 | void closedConnection(socketId, ForceSocket socket) { 20 | print("connection closed for $socketId"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/bin/server.dart: -------------------------------------------------------------------------------- 1 | library dart_force_todo; 2 | 3 | import "package:force/force_serverside.dart"; 4 | 5 | part 'receivers/justareceiver.dart'; 6 | 7 | main() async { 8 | 9 | ForceServer fs = new ForceServer(port: 3030, 10 | clientFiles: "../build/web/", 11 | startPage: "dartforcetodo.html"); 12 | 13 | fs.setupConsoleLog(); 14 | 15 | await fs.start(); 16 | 17 | fs.on("add", (message, sender) { 18 | fs.send("update", message.json); 19 | }); 20 | 21 | 22 | // add serversocket implementation into the game! server 2 server communication 23 | Connector connector = new ServerSocketConnector(); 24 | fs.addConnector(connector); 25 | 26 | connector.start(); 27 | 28 | } -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dartforcetodo 2 | description: A sample web application 3 | dependencies: 4 | browser: any 5 | force: 6 | path: ../ 7 | -------------------------------------------------------------------------------- /example/store/todo.txt: -------------------------------------------------------------------------------- 1 | drink a beer 2 | run for an hour 3 | watching dart talk 4 | -------------------------------------------------------------------------------- /example/web/dartforcetodo.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #F8F8F8; 4 | font-family: 'Open Sans', sans-serif; 5 | font-size: 14px; 6 | font-weight: normal; 7 | line-height: 1.2em; 8 | margin: 15px; 9 | } 10 | 11 | h1, p { 12 | color: #333; 13 | } 14 | 15 | #sample_container_id { 16 | width: 100%; 17 | height: 400px; 18 | position: relative; 19 | border: 1px solid #ccc; 20 | background-color: #fff; 21 | } 22 | 23 | #sample_text_id { 24 | font-size: 24pt; 25 | text-align: center; 26 | margin-top: 140px; 27 | -webkit-user-select: none; 28 | user-select: none; 29 | } 30 | -------------------------------------------------------------------------------- /example/web/dartforcetodo.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | import 'package:force/force_browser.dart'; 3 | 4 | ForceClient fc; 5 | 6 | main() async { 7 | fc = new ForceClient(); 8 | fc.connect(); 9 | 10 | await fc.onConnected; 11 | 12 | querySelector("#input").onKeyPress.listen(handleKeyEvent); 13 | 14 | querySelector("#btn") 15 | ..text = "GO" 16 | ..onClick.listen(broadcast); 17 | 18 | fc.on("update", (fme, sender) => 19 | querySelector("#list").appendHtml("
${fme.json["todo"]}
")); 20 | } 21 | 22 | void handleKeyEvent(KeyboardEvent event) { 23 | KeyEvent keyEvent = new KeyEvent.wrap(event); 24 | if (keyEvent.keyCode == KeyCode.ENTER) { 25 | handleInput(); 26 | } 27 | } 28 | 29 | void broadcast(MouseEvent event) { 30 | handleInput(); 31 | } 32 | 33 | void handleInput() { 34 | InputElement input = querySelector("#input"); 35 | 36 | fc.send("add", {"todo": input.value}); 37 | 38 | input.value = ""; 39 | } -------------------------------------------------------------------------------- /example/web/dartforcetodo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Dartforcetodo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

Dart force todo

16 | 17 |

Hello from Dart Force!

18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/cargo/cargo_holder.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | /** 4 | * Holds all interactions with the cargo instances, abstraction for the implementations on the server and on the client 5 | */ 6 | abstract class CargoHolder { 7 | 8 | Map _cargos = new Map(); 9 | 10 | DataChangeable dataChangeable; 11 | 12 | CargoHolder(this.dataChangeable); 13 | 14 | void publish(String collection, CargoBase cargoBase); 15 | 16 | bool subscribe(String collection, params, Options options, String id); 17 | 18 | bool exist(String collection); 19 | 20 | bool add(String collection, key, data); 21 | 22 | bool update(String collection, key, data); 23 | 24 | bool set(String collection, data); 25 | 26 | bool remove(String collection, key); 27 | 28 | } -------------------------------------------------------------------------------- /lib/cargo/cargo_holder_client.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | /** 4 | * Holds all interactions with the cargo instances on the client! 5 | */ 6 | class CargoHolderClient implements CargoHolder { 7 | 8 | Map _cargos = new Map(); 9 | 10 | DataChangeable dataChangeable; 11 | 12 | CargoHolderClient(this.dataChangeable); 13 | 14 | // publish a cargo collection 15 | void publish(String collection, CargoBase cargoBase) { 16 | _cargos[collection] = cargoBase; 17 | } 18 | 19 | // subscribe to changes of a cargo collection 20 | bool subscribe(String collection, params, Options options, String id) { 21 | bool colExist = exist(collection); 22 | // Don't need todo anything on the client 23 | return colExist; 24 | } 25 | 26 | bool exist(String collection) { 27 | return _cargos[collection]!=null; 28 | } 29 | 30 | bool add(String collection, key, data) { 31 | bool colExist = exist(collection); 32 | if (colExist) { 33 | _cargos[collection].add(key, data); 34 | } 35 | return colExist; 36 | } 37 | 38 | bool update(String collection, key, data) { 39 | bool colExist = exist(collection); 40 | if (colExist) { 41 | _cargos[collection].setItem(key, data); 42 | } 43 | return colExist; 44 | } 45 | 46 | bool remove(String collection, key) { 47 | bool colExist = exist(collection); 48 | if (colExist) { 49 | _cargos[collection].removeItem(key); 50 | } 51 | return colExist; 52 | } 53 | 54 | bool set(String collection, data) { 55 | throw new UnsupportedError('Is not supported on the client!'); 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /lib/cargo/cargo_holder_server.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | /** 4 | * Holds all interactions with the cargo instances on the server! 5 | */ 6 | class CargoHolderServer implements CargoHolder { 7 | 8 | Map _cargos = new Map(); 9 | Map> _subscribers = new Map>(); 10 | 11 | Map _parameters = new Map(); 12 | 13 | DataChangeable dataChangeable; 14 | 15 | var _uuid = new Uuid(); 16 | 17 | CargoHolderServer(this.dataChangeable); 18 | 19 | void publish(String collection, CargoBase cargoBase) { 20 | _cargos[collection] = cargoBase; 21 | 22 | cargoBase.onAll((de) { 23 | // inform all subscribers for this change! 24 | if (de.type==DataType.CHANGED) { 25 | //before that 26 | _sendTo(collection, de.key, de.data); 27 | } else { 28 | _removePush(collection, de.key, de.data); 29 | } 30 | }); 31 | } 32 | 33 | void _sendTo(collection, key, data) { 34 | // inform all subscribers for this change! 35 | List ids = _subscribers[collection]; 36 | 37 | if (ids != null) { 38 | for (var id in ids) { 39 | Map params = _parameters["${collection}_${id}"]; 40 | 41 | bool sendIt = (data is Map ? containsByOverlay(data, params) : true); 42 | if (sendIt) { 43 | this._sendToId(collection, key, data, id); 44 | } 45 | } 46 | } 47 | } 48 | 49 | void _sendToId(collection, key, data, id) { 50 | dataChangeable.update(collection, key, data, id: id); 51 | } 52 | 53 | void _removePush(collection, key, data) { 54 | // inform all subscribers for this change! 55 | List ids = _subscribers[collection]; 56 | 57 | for (var id in ids) { 58 | Map params = _parameters["${collection}_${id}"]; 59 | if (containsByOverlay(data, params)) { 60 | dataChangeable.remove(collection, key, id: id); 61 | } 62 | } 63 | } 64 | 65 | bool subscribe(String collection, params, Options options, String id) { 66 | bool colExist = exist(collection); 67 | if (colExist) { 68 | List ids = new List(); 69 | if (_subscribers[collection] != null) { 70 | ids = _subscribers[collection]; 71 | } 72 | ids.add(id); 73 | // send data if necessary 74 | _subscribers[collection] = ids; 75 | _parameters["${collection}_${id}"] = params; 76 | 77 | // send the collection to the clients 78 | _cargos[collection].export(params: params, options: options).then((Map values) { 79 | // if revert send it revert to the client 80 | if (options.revert) values = revertMap(values); 81 | 82 | values.forEach((key, value) => _sendToId(collection, key, value, id)); 83 | }); 84 | } 85 | return colExist; 86 | } 87 | 88 | bool exist(String collection) { 89 | return _cargos[collection]!=null; 90 | } 91 | 92 | bool add(String collection, key, data) { 93 | bool colExist = exist(collection); 94 | if (colExist) { 95 | _cargos[collection].add(key, data); 96 | } 97 | return colExist; 98 | } 99 | 100 | bool update(String collection, key, data) { 101 | bool colExist = exist(collection); 102 | if (colExist) { 103 | _cargos[collection].setItem(key, data); 104 | } 105 | return colExist; 106 | } 107 | 108 | bool remove(String collection, key) { 109 | bool colExist = exist(collection); 110 | if (colExist) { 111 | _cargos[collection].removeItem(key); 112 | } 113 | return colExist; 114 | } 115 | 116 | bool set(String collection, data) { 117 | bool colExist = exist(collection); 118 | if (colExist) { 119 | _cargos[collection].setItem(_uuid.v4(), data); 120 | } 121 | return colExist; 122 | } 123 | 124 | generateKey(key) { 125 | return key; 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /lib/cargo/cargo_package.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | /** 4 | * A db, cargo action that needs to happen 5 | */ 6 | class CargoAction { 7 | 8 | static const SUBSCRIBE = 'db.subscribe'; 9 | static const ADD = 'db.add'; 10 | static const SET = 'db.set'; 11 | static const UPDATE = 'db.update'; 12 | static const REMOVE = 'db.remove'; 13 | static const CANCEL = 'db.cancel'; 14 | 15 | String type; 16 | 17 | CargoAction(this.type); 18 | 19 | CargoAction.fromJson(json) { 20 | if (json!=null) { 21 | type = json["name"]; 22 | } 23 | } 24 | 25 | Map toJson() { 26 | Map json = new Map(); 27 | json["name"] = type; 28 | return json; 29 | } 30 | } 31 | 32 | /** 33 | * Cargo package exists out of info what we will do with the data in Cargo 34 | */ 35 | class CargoPackage extends Package { 36 | 37 | String wsId; 38 | String collection; 39 | String key; 40 | dynamic profile; 41 | dynamic json; 42 | dynamic params; 43 | 44 | Options options; 45 | CargoAction action; 46 | 47 | CargoPackage(this.collection, this.action, this.profile, { this.key, this.json, this.params, this.options, this.wsId: "-"}); 48 | 49 | CargoPackage.fromJson(json, {this.wsId}) { 50 | if (json!=null) { 51 | this.key = json["key"]; 52 | this.json = json["data"]; 53 | this.profile = json["profile"]; 54 | this.collection = json["collection"]; 55 | this.params = json["params"]; 56 | 57 | if (json["options"] != null) { 58 | this.options = new Options(limit: json["options"]["limit"], revert: json["options"]["revert"]); 59 | } else { 60 | this.options = new Options(); 61 | } 62 | 63 | this.action = new CargoAction.fromJson(json["action"]); 64 | } 65 | } 66 | 67 | Map toJson() { 68 | Map json = new Map(); 69 | if (this.key != null) json["key"] = this.key; 70 | if (this.json!= null) json["data"] = this.json; 71 | if (this.params != null) json["params"] = this.params; 72 | if (this.profile!= null) json["profile"] = this.profile; 73 | if (this.collection != null) json["collection"] = this.collection; 74 | if (this.action != null) json["action"] = this.action.toJson(); 75 | if (this.options != null) { 76 | json["options"] = { "limit" : options.limit, "revert" : options.revert }; 77 | } 78 | return json; 79 | } 80 | 81 | /** 82 | * Cancel a cargo interaction 83 | */ 84 | void cancel() { 85 | this.action = new CargoAction(CargoAction.CANCEL); 86 | } 87 | } -------------------------------------------------------------------------------- /lib/cargo/cargo_package_dispatcher.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | typedef ValidateCargoPackage(CargoPackage fcp, Sender sender); 4 | 5 | /** 6 | * Dispatch all the cargo packages to the correct methods in the cargo holder class 7 | * Before dispatching it he will execute the validator method corresponding a collection 8 | */ 9 | class CargoPackageDispatcher extends ProtocolDispatch { 10 | 11 | CargoHolder cargoHolder; 12 | 13 | Map _validators = new Map(); 14 | 15 | CargoPackageDispatcher(this.cargoHolder); 16 | 17 | void publish(String collection, CargoBase cargo, {ValidateCargoPackage filter}) { 18 | _validators[collection] = filter; 19 | cargoHolder.publish(collection, cargo); 20 | } 21 | 22 | /** 23 | * Dispatch a CargoPackage 24 | */ 25 | void dispatch(CargoPackage fcp) { 26 | var collection = fcp.collection; 27 | 28 | // before dispatch evaluate ... 29 | ValidateCargoPackage validator = _validators[collection]; 30 | if (validator != null) { 31 | validator(fcp, new Sender(sendable, fcp.wsId)); 32 | } 33 | 34 | if (fcp.action.type == CargoAction.SUBSCRIBE) { 35 | cargoHolder.subscribe(fcp.collection, fcp.params, fcp.options, fcp.wsId); 36 | } else if (fcp.action.type == CargoAction.ADD) { 37 | cargoHolder.add(fcp.collection, fcp.collection, fcp.json); 38 | } else if (fcp.action.type == CargoAction.UPDATE) { 39 | cargoHolder.update(fcp.collection, fcp.key, fcp.json); 40 | } else if (fcp.action.type == CargoAction.REMOVE) { 41 | cargoHolder.remove(fcp.collection, fcp.key); 42 | } else if (fcp.action.type == CargoAction.SET) { 43 | cargoHolder.set(fcp.collection, fcp.json); 44 | } 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /lib/cargo/cargo_protocol.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | /** 4 | * Arrange the Cargo protocol, should the package be dispatched, 5 | * how converting the raw data into a cargo package, expose a stream, ... 6 | */ 7 | class ForceCargoProtocol extends Protocol { 8 | 9 | StreamController _controller; 10 | ProtocolDispatch dispatcher; 11 | 12 | ForceCargoProtocol(this.dispatcher) { 13 | _controller = new StreamController(); 14 | } 15 | 16 | /** 17 | * Should the data be dispatch as a cargo package ... look at the data here ... 18 | */ 19 | bool shouldDispatch(data) { 20 | return data.toString().contains("collection"); 21 | } 22 | 23 | /** 24 | * Converting the raw data into a cargo package 25 | */ 26 | CargoPackage onConvert(data, {wsId: "-"}) { 27 | CargoPackage fcp = new CargoPackage.fromJson(JSON.decode(data), wsId: wsId); 28 | addPackage(fcp); 29 | 30 | return fcp; 31 | } 32 | 33 | CargoPackage addPackage(CargoPackage fcp) { 34 | _controller.add(fcp); 35 | return fcp; 36 | } 37 | 38 | Stream get onPackage => _controller.stream; 39 | } -------------------------------------------------------------------------------- /lib/cargo/data_changeable.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | abstract class DataChangeable { 4 | 5 | void add(collection, key, data, {id}); 6 | 7 | void set(collection, data, {id}); 8 | 9 | void update(collection, key, data, {id}); 10 | 11 | void remove(collection, key, {id}); 12 | 13 | } -------------------------------------------------------------------------------- /lib/cargo/view_collection.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | /** transform json objects into real objects by the user 4 | * until better ways in dart this will be the way to transform our data 5 | **/ 6 | typedef Object deserializeData(Map json); 7 | 8 | /** 9 | * Is a memory wrapper arround cargo, so we can add this to our view! 10 | * Ideal class to use it in Angular or Polymer. 11 | */ 12 | class ViewCollection extends Object with IterableMixin { 13 | 14 | CargoBase cargo; 15 | DataChangeable _changeable; 16 | String _collection; 17 | 18 | Options options; 19 | 20 | deserializeData deserialize; 21 | 22 | Map _all = new Map(); 23 | 24 | /// put the data raw in a map, make the map only available as a getter 25 | Map _raw = new Map(); 26 | Map get data => _raw; 27 | 28 | /// Follow changes in view collection 29 | DataChangeListener _cargoDataChange; 30 | onChange(DataChangeListener cargoDataChange) => this._cargoDataChange = cargoDataChange; 31 | 32 | ViewCollection(this._collection, this.cargo, this.options, this._changeable, {this.deserialize}) { 33 | this.cargo.onAll((DataEvent de) { 34 | if (de.type==DataType.CHANGED) { 35 | var data = de.data; 36 | if (data is Map && deserialize != null) { 37 | data = deserialize(data); 38 | } 39 | 40 | _all = _addNewValue(_all, de.key, new EncapsulatedValue(de.key, data)); 41 | _raw = _addNewValue(_raw, de.key, data); 42 | if (_cargoDataChange!=null) _cargoDataChange(new DataEvent(de.key, data, de.type)); 43 | } 44 | if (de.type==DataType.REMOVED) { 45 | _all.remove(de.key); 46 | _raw.remove(de.key); 47 | if (_cargoDataChange!=null) _cargoDataChange(de); 48 | } 49 | }); 50 | } 51 | 52 | Map _addNewValue(Map values, key, data) { 53 | // check on the limit option and limit the map 54 | if (options != null && options.hasLimit() && !values.containsKey(key)) { 55 | if (options.limit == values.length) { 56 | var removableKey; 57 | if(options.revert) { 58 | removableKey = values.keys.elementAt(values.keys.length-1); 59 | } else { 60 | removableKey = values.keys.elementAt(0); 61 | } 62 | values.remove(removableKey); 63 | } 64 | } 65 | // if we need to revert our results 66 | if (options != null && options.revert && !values.containsKey(key)) { 67 | Map tempMap = new Map(); 68 | 69 | tempMap[key] = data; 70 | tempMap.addAll(values); 71 | 72 | values = tempMap; 73 | } else { 74 | values[key] = data; 75 | } 76 | return values; 77 | } 78 | 79 | void update(key, value) { 80 | this.cargo.setItem(key, value); 81 | this._changeable.update(_collection, key, value); 82 | } 83 | 84 | void remove(id) { 85 | this.cargo.removeItem(id); 86 | this._changeable.remove(_collection, id); 87 | } 88 | 89 | void set(value) { 90 | this._changeable.set(_collection, value); 91 | } 92 | 93 | Iterator get iterator => _all.values.iterator; 94 | 95 | } 96 | 97 | class EncapsulatedValue { 98 | String key; 99 | var value; 100 | 101 | EncapsulatedValue(this.key, this.value); 102 | } -------------------------------------------------------------------------------- /lib/client/browser_messenger.dart: -------------------------------------------------------------------------------- 1 | part of force.client; 2 | 3 | class BrowserMessenger extends Messenger { 4 | Socket socket; 5 | 6 | BrowserMessenger(this.socket); 7 | 8 | void send(sendingPackage) { 9 | if (socket != null && socket.isOpen()) { 10 | socket.send(JSON.encode(sendingPackage)); 11 | } else { 12 | this.offline(sendingPackage); 13 | } 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /lib/client/connect_event.dart: -------------------------------------------------------------------------------- 1 | part of force.client; 2 | 3 | class ForceConnectEvent { 4 | 5 | String type; 6 | 7 | ForceConnectEvent(this.type); 8 | 9 | } -------------------------------------------------------------------------------- /lib/client/force_client.dart: -------------------------------------------------------------------------------- 1 | part of force.client; 2 | 3 | class ForceClient extends BaseForceClient with ClientSendable { 4 | Socket socket; 5 | 6 | String wsPath; 7 | 8 | ForceClient({String wsPath: "/ws", String url: null, String host: null, int port: null, int heartbeat: 500, bool usePolling: false}) { 9 | print("create a forceclient"); 10 | 11 | clientContext = new ForceClientContext(this); 12 | 13 | this.wsPath = wsPath; 14 | if (host==null) { 15 | host = '${Uri.base.host}'; 16 | } 17 | if (port==null) { 18 | port = Uri.base.port; 19 | } 20 | if (url==null) { 21 | url = '$host:$port'; 22 | } 23 | 24 | this.socket = new Socket('$url$wsPath', usePolling: usePolling, heartbeat: heartbeat); 25 | this.messenger = new BrowserMessenger(socket); 26 | 27 | onConnected.listen((ConnectEvent) { 28 | this.messenger.online(); 29 | }); 30 | } 31 | 32 | void connect() { 33 | this.socket.connect(); 34 | this.socket.onMessage.listen((e) { 35 | clientContext.protocolDispatchers.dispatch_raw(e.data); 36 | }); 37 | } 38 | 39 | dynamic generateId() { 40 | var rng = new Random(); 41 | return rng.nextInt(10000000); 42 | } 43 | 44 | Stream get onConnected => this.socket.onConnecting; 45 | Stream get onDisconnected => this.socket.onDisconnecting; 46 | } -------------------------------------------------------------------------------- /lib/clientsocket/abstract_socket.dart: -------------------------------------------------------------------------------- 1 | part of force.client; 2 | 3 | class SocketEvent { 4 | var data; 5 | SocketEvent(this.data); 6 | } 7 | 8 | class ConnectEvent {} 9 | 10 | abstract class Socket { 11 | 12 | // For subclasses 13 | Socket._(); 14 | 15 | factory Socket(String url, {bool usePolling: false, int heartbeat: 2000}) { 16 | print("chosing a socket implementation!"); 17 | if (usePolling || !WebSocket.supported) { 18 | return new PollingSocket(url, heartbeat); 19 | } else { 20 | return new WebSocketWrapper(url); 21 | } 22 | } 23 | 24 | StreamController _connectController; 25 | StreamController _disconnectController; 26 | StreamController _messageController; 27 | 28 | Stream _onMessage; 29 | Stream _onConnecting; 30 | Stream _onDisconnecting; 31 | 32 | void connect(); 33 | 34 | void send(data); 35 | 36 | bool isOpen(); 37 | 38 | Stream get onMessage => _onMessage = _stream_resolving(_messageController, _onMessage); 39 | Stream get onConnecting => _onConnecting = _stream_resolving(_connectController, _onConnecting); 40 | Stream get onDisconnecting => _onDisconnecting = _stream_resolving(_disconnectController, _onDisconnecting); 41 | 42 | Stream _stream_resolving(StreamController controller, Stream stream) { 43 | if (stream==null) { 44 | stream = controller.stream.asBroadcastStream(); 45 | } 46 | return stream; 47 | } 48 | } -------------------------------------------------------------------------------- /lib/clientsocket/polling_socket.dart: -------------------------------------------------------------------------------- 1 | part of force.client; 2 | 3 | class PollingSocket extends Socket { 4 | Duration _heartbeat = new Duration(milliseconds: 2000); 5 | 6 | String _url; 7 | bool _alreadyConnected = false; 8 | 9 | String _uuid; 10 | 11 | int count = 0; 12 | 13 | PollingSocket(this._url, heartbeat_ms) : super._() { 14 | _connectController = new StreamController(); 15 | _disconnectController = new StreamController(); 16 | _messageController = new StreamController(); 17 | 18 | _heartbeat = new Duration(milliseconds : heartbeat_ms); 19 | 20 | print('polling socket is created'); 21 | } 22 | 23 | void connect() { 24 | HttpRequest.getString('http://$_url/uuid/').then(procces_id).catchError((error) { 25 | print('no support for long polling ... available on the server'); 26 | }); 27 | } 28 | 29 | void procces_id(String value) { 30 | var json = JSON.decode(value); 31 | 32 | print(json); 33 | 34 | _uuid = json["id"]; 35 | 36 | new Timer(_heartbeat, polling); 37 | } 38 | 39 | void polling() { 40 | count++; 41 | print('polling to ... http://$_url/polling/?pid=$_uuid&count=$count'); 42 | HttpRequest.getString('http://$_url/polling/?pid=$_uuid&count=$count').then(processString).catchError((error) { 43 | print('no support for long polling at this moment ... problems with the server???'); 44 | }); 45 | } 46 | 47 | void processString(String values) { 48 | print('process return from polling ...$values'); 49 | var messages = JSON.decode(values); 50 | if (!_alreadyConnected) { 51 | _connectController.add(new ConnectEvent()); 52 | _alreadyConnected = true; 53 | } 54 | if (messages!=null) { 55 | for (var value in messages) { 56 | print('individual value -> $value'); 57 | _messageController.add(new SocketEvent(value)); 58 | } 59 | } 60 | new Timer(_heartbeat, polling); 61 | } 62 | 63 | void send(data) { 64 | if (_uuid!=null) { 65 | var package = JSON.encode({ 66 | "pid" : _uuid, 67 | "data" : data 68 | }); 69 | print('sending data to the post http://$_url/polling/'); 70 | var httpRequest = new HttpRequest(); 71 | httpRequest.open('POST', 'http://$_url/polling/'); 72 | httpRequest.setRequestHeader('Content-type', 73 | 'application/x-www-form-urlencoded'); 74 | httpRequest.onLoadEnd.listen((e) => loadEnd(httpRequest)); 75 | httpRequest.send(package); 76 | } 77 | } 78 | 79 | void loadEnd(HttpRequest request) { 80 | if (request.status != 200) { 81 | print('Uh oh, there was an error of ${request.status}'); 82 | } else { 83 | print('Data has been posted'); 84 | } 85 | } 86 | 87 | bool isOpen() => true; 88 | } -------------------------------------------------------------------------------- /lib/clientsocket/websocket_wrapper.dart: -------------------------------------------------------------------------------- 1 | part of force.client; 2 | 3 | class WebSocketWrapper extends Socket { 4 | static const Duration RECONNECT_DELAY = const Duration(milliseconds: 500); 5 | 6 | bool _connectPending = false; 7 | WebSocket webSocket; 8 | 9 | String _url; 10 | 11 | WebSocketWrapper(this._url) : super._() { 12 | _connectController = new StreamController(); 13 | _disconnectController = new StreamController(); 14 | _messageController = new StreamController(); 15 | } 16 | 17 | void connect() { 18 | _connectPending = false; 19 | //print("try to connect to this url -> $_url"); 20 | webSocket = new WebSocket('ws://$_url?t=${_timeStamp()}'); 21 | webSocket.onOpen.first.then((_) { 22 | _onConnected(); 23 | webSocket.onClose.first.then((_) { 24 | print("Connection disconnected to ${webSocket.url}"); 25 | _onDisconnected(); 26 | }); 27 | }); 28 | webSocket.onError.first.then((_) { 29 | print("Failed to connect to ${webSocket.url}. " 30 | "Please run bin/server.dart and try again."); 31 | _onDisconnected(); 32 | }); 33 | } 34 | 35 | void _onConnected() { 36 | print("connected!"); 37 | _connectController.add(new ConnectEvent()); 38 | webSocket.onMessage.listen((e) { 39 | if (e.data is Blob) { 40 | FileReader fileReader = new FileReader(); 41 | fileReader.onLoadEnd.listen((_) => 42 | _messageController.add(new SocketEvent(fileReader.result))); 43 | 44 | fileReader.readAsText(e.data); 45 | } else { 46 | _messageController.add(new SocketEvent(e.data)); 47 | } 48 | }); 49 | } 50 | 51 | void _onDisconnected() { 52 | print("disconnected!"); 53 | _disconnectController.add(new ConnectEvent()); 54 | if (_connectPending) return; 55 | _connectPending = true; 56 | new Timer(RECONNECT_DELAY, connect); 57 | } 58 | 59 | void send(data) { 60 | webSocket.send(data); 61 | } 62 | 63 | bool isOpen() => webSocket.readyState == WebSocket.OPEN; 64 | 65 | _timeStamp() => new DateTime.now().millisecondsSinceEpoch; 66 | } -------------------------------------------------------------------------------- /lib/common/base_force_client.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | /// This is the base class of all force clients with all the acces 4 | abstract class BaseForceClient { 5 | 6 | /// context of a client and his protocols 7 | ForceClientContext clientContext; 8 | 9 | /// a stream of message packages 10 | Stream get onMessage => clientContext.onMessage; 11 | 12 | /// register on a collection of data, to get updates and send updates back to the root database / persistance layer 13 | ViewCollection register(String collection, CargoBase cargo, {Map params, Options options, deserializeData deserialize}) 14 | => clientContext.register(collection, cargo, params: params, options: options, deserialize: deserialize); 15 | 16 | /// add a new protocol into force 17 | void addProtocol(Protocol protocol) { 18 | clientContext.protocolDispatchers.addProtocol(protocol); 19 | } 20 | 21 | /// listen to new requests for new data packages 22 | void on(String request, MessageReceiver forceMessageController) { 23 | clientContext.on(request, forceMessageController); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /lib/common/basic_sendable.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | abstract class Sendable extends SendablePackage { 4 | 5 | void send(request, data); 6 | 7 | void sendTo(id, request, data); 8 | 9 | void sendToProfile(key, value, request, data); 10 | 11 | } -------------------------------------------------------------------------------- /lib/common/basic_sender.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | class Sender { 4 | 5 | Sendable sendable; 6 | String _reply_id; 7 | 8 | Sender(this.sendable, this._reply_id); 9 | 10 | void send(request, data) { 11 | sendable.send(request, data); 12 | } 13 | 14 | void reply(request, data) { 15 | sendable.sendTo(this._reply_id, request, data); 16 | } 17 | 18 | void sendTo(id, request, data) { 19 | sendable.sendTo(id, request, data); 20 | } 21 | 22 | void sendToProfile(key, value, request, data) { 23 | sendable.sendToProfile(key, value, request, data); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /lib/common/client_context.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | class ForceClientContext { 4 | 5 | ProtocolDispatchers protocolDispatchers; 6 | CargoHolder _cargoHolder; 7 | ForceMessageDispatcher _forceMessageDispatcher; 8 | ForceMessageProtocol _forceMessageProtocol; 9 | 10 | ClientSendable clientSendable; 11 | 12 | ForceClientContext(this.clientSendable) { 13 | _setupProtocols(); 14 | } 15 | 16 | void _setupProtocols() { 17 | protocolDispatchers = new ProtocolDispatchers(this.clientSendable); 18 | _cargoHolder = new CargoHolderClient(this.clientSendable); 19 | _forceMessageDispatcher = new ForceMessageDispatcher(); 20 | _forceMessageProtocol = new ForceMessageProtocol(_forceMessageDispatcher); 21 | protocolDispatchers.addProtocol(_forceMessageProtocol); 22 | // add Cargo 23 | CargoPackageDispatcher cargoPacakgeDispatcher = new CargoPackageDispatcher(_cargoHolder); 24 | ForceCargoProtocol forceCargoProtocol = new ForceCargoProtocol(cargoPacakgeDispatcher); 25 | protocolDispatchers.addProtocol(forceCargoProtocol); 26 | } 27 | 28 | Stream get onMessage => _forceMessageProtocol.onMessage; 29 | 30 | ViewCollection register(String collection, CargoBase cargo, {Map params, Options options, deserializeData deserialize}) { 31 | CargoBase cargoWithCollection = cargo.instanceWithCollection(collection); 32 | _cargoHolder.publish(collection, cargoWithCollection); 33 | this.clientSendable.subscribe(collection, params: params, options: options); 34 | 35 | return new ViewCollection(collection, cargoWithCollection, options, this.clientSendable, deserialize: deserialize); 36 | } 37 | 38 | void on(String request, MessageReceiver forceMessageController) { 39 | _forceMessageDispatcher.register(request, forceMessageController); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /lib/common/client_sendable.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | class ClientSendable implements Sendable, DataChangeable { 4 | 5 | Messenger messenger; 6 | 7 | var _profileInfo; 8 | 9 | MessagesConstructHelper _messagesConstructHelper = new MessagesConstructHelper(); 10 | 11 | void initProfileInfo(profileInfo) { 12 | _messagesConstructHelper.initProfileInfo(profileInfo); 13 | send('profileInfo', {}); 14 | } 15 | 16 | // send it to the server 17 | void send(request, data) { 18 | this.sendPackage(_messagesConstructHelper.send(request, data)); 19 | } 20 | 21 | // broadcast it directly to all the clients 22 | void broadcast(request, data) { 23 | this.sendPackage(_messagesConstructHelper.broadcast(request, data)); 24 | } 25 | 26 | // send to a specific socket with an id 27 | void sendTo(id, request, data) { 28 | this.sendPackage(_messagesConstructHelper.sendTo(id, request, data)); 29 | } 30 | 31 | // send to a profile with specific values 32 | void sendToProfile(key, value, request, data) { 33 | this.sendPackage(_messagesConstructHelper.sendToProfile(key, value, request, data)); 34 | } 35 | 36 | // DB SENDABLE METHODS 37 | void subscribe(collection, {params, Options options}) { 38 | this.sendPackage(_messagesConstructHelper.subscribe(collection, params: params, options: options)); 39 | } 40 | 41 | void add(collection, key, value, {id}) { 42 | this.sendPackage(_messagesConstructHelper.add(collection, key, value)); 43 | } 44 | 45 | void update(collection, key, value, {id}) { 46 | this.sendPackage(_messagesConstructHelper.update(collection, key, value)); 47 | } 48 | 49 | void remove(collection, key, {id}) { 50 | this.sendPackage(_messagesConstructHelper.remove(collection, key)); 51 | } 52 | 53 | void set(collection, value, {id}) { 54 | this.sendPackage(_messagesConstructHelper.set(collection, value)); 55 | } 56 | 57 | void sendPackage(sendingPackage) { 58 | messenger.send(sendingPackage); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /lib/common/package.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | /** 4 | * Represents a package that will be send through a socket 5 | */ 6 | abstract class SendablePackage { 7 | void sendPackage(package); 8 | } 9 | 10 | /** 11 | * Represents a package that will be send through a socket 12 | */ 13 | abstract class Package { 14 | dynamic profile; 15 | } -------------------------------------------------------------------------------- /lib/common/protocol.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | /** 4 | * Represents a protocol, this will be used in the communication flow and in the client db api flow 5 | * or you can use this class when you want to define your own protocol 6 | */ 7 | abstract class Protocol { 8 | 9 | ProtocolDispatch dispatcher; 10 | 11 | // sendable add this to dispatcher 12 | void set sendable(Sendable sendable) { 13 | dispatcher.sendable = sendable; 14 | } 15 | 16 | T onConvert(data, {wsId: "-"}); 17 | 18 | bool shouldDispatch(data); 19 | 20 | List convertPackages(data, {wsId: "-"}) { 21 | List packages = new List(); 22 | List data_lines = removeEmptyLines(data.split("\n")); 23 | for (var line in data_lines) { 24 | if (shouldDispatch(line)) { 25 | packages.add(onConvert(line, wsId: wsId)); 26 | } 27 | } 28 | return packages; 29 | } 30 | 31 | void dispatchRaw(data, {wsId: "-"}) { 32 | List data_lines = removeEmptyLines(data.split("\n")); 33 | for (var line in data_lines) { 34 | if (shouldDispatch(line)) { 35 | dispatcher.dispatch(onConvert(line, wsId: wsId)); 36 | } 37 | } 38 | } 39 | 40 | void dispatch(data) { 41 | if (data is T) { 42 | dispatcher.dispatch(data); 43 | } 44 | } 45 | 46 | List removeEmptyLines(List lines) { 47 | List notEmptyLines = new List(); 48 | for (String line in lines) { 49 | if (!line.trim().isEmpty) { 50 | notEmptyLines.add(line); 51 | } 52 | } 53 | return notEmptyLines; 54 | } 55 | } 56 | 57 | /** 58 | * It dispatches the protocol and handles the protocol by doing something with his commands. 59 | */ 60 | abstract class ProtocolDispatch { 61 | 62 | Sendable sendable; 63 | 64 | void dispatch(T protocolMessages); 65 | 66 | } 67 | 68 | /** 69 | * It analyzes all the protocols and then dispatch it on there working half. 70 | */ 71 | class ProtocolDispatchers { 72 | 73 | Sendable sendable; 74 | 75 | ProtocolDispatchers(this.sendable); 76 | 77 | List _protocols = new List(); 78 | 79 | List convertPackages(data, {wsId: "-"}) { 80 | List packages = new List(); 81 | for (var protocol in _protocols) { 82 | packages.addAll(protocol.convertPackages(data, wsId: wsId)); 83 | } 84 | return packages; 85 | } 86 | 87 | void dispatch_raw(data, {wsId: "-"}) { 88 | for (var protocol in _protocols) { 89 | protocol.dispatchRaw(data, wsId: wsId); 90 | } 91 | } 92 | 93 | void dispatch(data) { 94 | for (var protocol in _protocols) { 95 | protocol.dispatch(data); 96 | } 97 | } 98 | 99 | void addProtocol(Protocol protocol) { 100 | protocol.sendable = sendable; 101 | _protocols.add(protocol); 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /lib/connectors/connector.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | abstract class Connector { 4 | 5 | StreamController _controller; 6 | Completer _completer = new Completer(); 7 | 8 | Future start(); 9 | 10 | Stream wire() => _controller.stream; 11 | 12 | void complete() { 13 | if (!_completer.isCompleted) _completer.complete(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /lib/connectors/server_socket_connector.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class ServerSocketConnector extends Connector { 4 | 5 | int port; 6 | String address; 7 | 8 | StreamController _controller; 9 | 10 | ServerSocketConnector({this.address: '127.0.0.1', this.port: 4041}) { 11 | _controller = new StreamController(); 12 | } 13 | 14 | Future start() { 15 | ServerSocket.bind(this.address, this.port) 16 | .then((serverSocket) { 17 | serverSocket.listen((socket) { 18 | _controller.add(new ServerSocketWrapper(socket)); 19 | 20 | }); 21 | }); 22 | return _completer.future; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lib/force_browser.dart: -------------------------------------------------------------------------------- 1 | library force.client; 2 | 3 | import 'dart:async'; 4 | import 'dart:convert' show JSON; 5 | import 'dart:html'; 6 | 7 | import 'dart:math'; 8 | // import 'package:uuid/uuid_client.dart'; 9 | 10 | import 'force_common.dart'; 11 | export 'force_common.dart'; 12 | 13 | part 'client/force_client.dart'; 14 | part 'client/connect_event.dart'; 15 | part 'client/browser_messenger.dart'; 16 | 17 | part 'clientsocket/abstract_socket.dart'; 18 | part 'clientsocket/websocket_wrapper.dart'; 19 | part 'clientsocket/polling_socket.dart'; 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /lib/force_common.dart: -------------------------------------------------------------------------------- 1 | library force.common; 2 | 3 | import 'dart:async'; 4 | import 'dart:convert' show JSON; 5 | 6 | import 'dart:collection' show IterableMixin, IterableBase; 7 | 8 | import 'package:cargo/cargo_base.dart'; 9 | export 'package:cargo/cargo_base.dart'; 10 | 11 | part 'message/base_message.dart'; 12 | part 'message/message_package.dart'; 13 | part 'message/message_type.dart'; 14 | 15 | part 'common/basic_sender.dart'; 16 | part 'common/basic_sendable.dart'; 17 | part 'message/message_dispatcher.dart'; 18 | 19 | part 'common/client_sendable.dart'; 20 | 21 | part 'message/messages_construct_helper.dart'; 22 | 23 | part 'common/protocol.dart'; 24 | part 'common/package.dart'; 25 | part 'common/client_context.dart'; 26 | part 'common/base_force_client.dart'; 27 | 28 | part 'cargo/cargo_package_dispatcher.dart'; 29 | part 'cargo/cargo_protocol.dart'; 30 | 31 | part 'cargo/cargo_holder.dart'; 32 | part 'cargo/data_changeable.dart'; 33 | part 'cargo/cargo_holder_client.dart'; 34 | 35 | part 'cargo/cargo_package.dart'; 36 | part 'cargo/view_collection.dart'; 37 | -------------------------------------------------------------------------------- /lib/force_serverside.dart: -------------------------------------------------------------------------------- 1 | library force.server; 2 | 3 | import 'dart:async'; 4 | import 'dart:convert'; 5 | import 'dart:io'; 6 | import 'dart:mirrors'; 7 | 8 | import 'package:logging/logging.dart' show Logger, Level, LogRecord; 9 | import 'package:uuid/uuid.dart'; 10 | import 'package:forcemvc/force_mvc.dart'; 11 | import 'package:wired/wired.dart'; 12 | 13 | export 'package:forcemvc/force_mvc.dart'; 14 | export 'package:wired/wired.dart'; 15 | 16 | import 'package:mirrorme/mirrorme.dart'; 17 | 18 | import 'force_common.dart'; 19 | export 'force_common.dart'; 20 | 21 | part 'server/force.dart'; 22 | part 'server/force_context.dart'; 23 | part 'server/force_server.dart'; 24 | 25 | part 'server/metadata.dart'; 26 | part 'server/profile_event.dart'; 27 | part 'server/polling_server.dart'; 28 | 29 | part 'message/message_security.dart'; 30 | 31 | part 'serversocket/abstract_socket.dart'; 32 | part 'serversocket/websocket_wrapper.dart'; 33 | part 'serversocket/polling_socket.dart'; 34 | part 'serversocket/stream_socket.dart'; 35 | 36 | part 'serversocket/server_socket.dart'; 37 | 38 | // custom connectors to the force system 39 | part 'connectors/connector.dart'; 40 | part 'connectors/server_socket_connector.dart'; 41 | 42 | // server socket client 43 | part 'serverclient/force_client.dart'; 44 | part 'serverclient/server_messenger.dart'; 45 | 46 | //mixins 47 | part 'server/serveable.dart'; 48 | part 'server/server_protocol.dart'; 49 | 50 | part 'server/socket_event.dart'; 51 | 52 | //cargo db api 53 | part 'cargo/cargo_holder_server.dart'; 54 | -------------------------------------------------------------------------------- /lib/message/base_message.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | class ForceMessageProtocol extends Protocol { 4 | 5 | StreamController _controller; 6 | ProtocolDispatch dispatcher; 7 | 8 | ForceMessageProtocol(this.dispatcher) { 9 | _controller = new StreamController(); 10 | } 11 | 12 | bool shouldDispatch(data) { 13 | // Test what is typical for this protocol 14 | return data.toString().contains("request"); 15 | } 16 | 17 | MessagePackage onConvert(data, {wsId: "-"}) { 18 | MessagePackage fme = new MessagePackage.fromJson(JSON.decode(data), wsId: wsId); 19 | 20 | addMessage(fme); 21 | 22 | return fme; 23 | } 24 | 25 | MessagePackage addMessage(MessagePackage vme) { 26 | _controller.add(vme); 27 | return vme; 28 | } 29 | 30 | Stream get onMessage => _controller.stream; 31 | } -------------------------------------------------------------------------------- /lib/message/message_dispatcher.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | typedef MessageReceiver(MessagePackage fme, Sender sender); 4 | 5 | class ForceMessageDispatcher extends ProtocolDispatch { 6 | 7 | List beforeMapping = new List(); 8 | Map mapping = new Map(); 9 | 10 | ForceMessageDispatcher(); 11 | 12 | void before(MessageReceiver messageController) { 13 | beforeMapping.add(messageController); 14 | } 15 | 16 | void register(String request, MessageReceiver messageController) { 17 | mapping[request] = messageController; 18 | } 19 | 20 | void dispatch(MessagePackage fme) { 21 | var key = fme.request; 22 | 23 | for (MessageReceiver messageReceiver in beforeMapping) { 24 | _executeMessageReceiver(fme, messageReceiver, sendable); 25 | } 26 | if (fme.messageType.type == MessageType.NORMAL) { 27 | _executeMessageReceiver(fme, mapping[key], sendable); 28 | } else if (fme.messageType.type == MessageType.BROADCAST) { 29 | sendable.send(fme.request, fme.json); 30 | _executeMessageReceiver(fme, mapping[key], sendable); 31 | } else { 32 | // DIRECTLY SEND THIS TO THE CORRECT CLIENT 33 | if (fme.messageType.type == MessageType.ID) { 34 | sendable.sendTo(fme.messageType.id, fme.request, fme.json); 35 | } 36 | if (fme.messageType.type == MessageType.PROFILE) { 37 | sendable.sendToProfile(fme.messageType.key, fme.messageType.value, fme.request, fme.json); 38 | } 39 | 40 | } 41 | } 42 | 43 | void _executeMessageReceiver(MessagePackage fme, MessageReceiver messageReceiver, Sendable sendable) { 44 | if (messageReceiver!=null) { 45 | messageReceiver(fme, new Sender(sendable, fme.wsId)); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /lib/message/message_package.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | class MessagePackage extends Package { 4 | 5 | String wsId; 6 | String request; 7 | dynamic profile; 8 | dynamic json; 9 | 10 | MessageType messageType; 11 | 12 | MessagePackage(this.request, this.messageType, this.json, this.profile, { this.wsId: "-"}); 13 | 14 | MessagePackage.fromJson(json, {this.wsId}) { 15 | if (json!=null) { 16 | this.json = json["data"]; 17 | this.profile = json["profile"]; 18 | this.request = json["request"]; 19 | 20 | this.messageType = new MessageType.fromJson(json["type"]); 21 | } 22 | } 23 | 24 | Map toJson() { 25 | Map json = new Map(); 26 | if (json != null) json["data"] = this.json; 27 | if (json != null) json["profile"] = this.profile; 28 | if (json != null) json["request"] = this.request; 29 | if (json != null) json["type"] = this.messageType.toJson(); 30 | return json; 31 | } 32 | } -------------------------------------------------------------------------------- /lib/message/message_security.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class ForceMessageSecurity { 4 | 5 | Map requestList = new Map(); 6 | SecurityContextHolder securityContextHolder; 7 | 8 | ForceMessageSecurity(this.securityContextHolder); 9 | 10 | void register(String request, List roles) { 11 | requestList[request] = roles; 12 | } 13 | 14 | /** 15 | * This method is doing security checks on top of ForceMessagePackage protocol 16 | */ 17 | bool isAuthorized(HttpRequest req, package) { 18 | if (package is MessagePackage) { 19 | MessagePackage fmp = package; 20 | if (requestList[fmp.request] != null && requestList[fmp.request].isNotEmpty) { 21 | // check if you are logged in against correct credentials 22 | return this.securityContextHolder.checkAuthorization(req, requestList[fmp.request], data: fmp); 23 | } 24 | } 25 | return true; 26 | } 27 | } -------------------------------------------------------------------------------- /lib/message/message_type.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | class MessageType { 4 | 5 | static const NORMAL = 'normal'; 6 | static const BROADCAST = 'broadcast'; 7 | static const ID = 'id'; 8 | static const PROFILE = 'profile'; 9 | 10 | String type; 11 | String id; 12 | String key; 13 | String value; 14 | String collection; 15 | 16 | MessageType(this.type); 17 | 18 | MessageType.fromJson(json) { 19 | if (json!=null) { 20 | type = json["name"]; 21 | if (type == ID) { 22 | id = json['id']; 23 | } else if (type == PROFILE) { 24 | key = json['key']; 25 | value = json['value']; 26 | } 27 | } 28 | } 29 | 30 | Map toJson() { 31 | Map json = new Map(); 32 | json["name"] = type; 33 | if (type == ID) { 34 | json['id'] = id; 35 | } else if (type == PROFILE) { 36 | json['key'] = key; 37 | json['value'] = value; 38 | } 39 | return json; 40 | } 41 | } -------------------------------------------------------------------------------- /lib/message/messages_construct_helper.dart: -------------------------------------------------------------------------------- 1 | part of force.common; 2 | 3 | abstract class Messenger { 4 | List notSendedPackages = new List(); 5 | 6 | void send(sendingPackage); 7 | 8 | void offline(sendingPackage) { 9 | print('Socket not connected, message $sendingPackage not sent'); 10 | notSendedPackages.add(sendingPackage); 11 | } 12 | 13 | void online() { 14 | List packages = new List(); 15 | packages.addAll(notSendedPackages); 16 | for (var package in notSendedPackages) { 17 | notSendedPackages.remove(package); 18 | 19 | send(JSON.encode(package)); 20 | } 21 | } 22 | } 23 | 24 | 25 | class MessagesConstructHelper { 26 | 27 | var _profileInfo; 28 | 29 | void initProfileInfo(profileInfo) { 30 | _profileInfo = profileInfo; 31 | } 32 | 33 | dynamic send(request, data) { 34 | MessagePackage fme = new MessagePackage(request,new MessageType(MessageType.NORMAL), data,_profileInfo); 35 | return fme; 36 | } 37 | 38 | // broadcast it directly to all the clients 39 | dynamic broadcast(request, data) { 40 | MessagePackage fme = new MessagePackage(request,new MessageType(MessageType.BROADCAST), data,_profileInfo); 41 | return fme; 42 | } 43 | 44 | // broadcast it directly to all the clients 45 | dynamic subscribe(collection, {params, Options options}) { 46 | //return _collection(ForceMessageType.SUBSCRIBE, collection, "", {}); 47 | return new CargoPackage(collection, new CargoAction(CargoAction.SUBSCRIBE), _profileInfo, params: params, options: options); 48 | } 49 | 50 | // broadcast it directly to all the clients 51 | dynamic add(collection, key, value) { 52 | return new CargoPackage(collection, new CargoAction(CargoAction.ADD), _profileInfo, key: key, json: value); 53 | } 54 | 55 | // broadcast it directly to all the clients 56 | dynamic update(collection, key, value) { 57 | return new CargoPackage(collection, new CargoAction(CargoAction.UPDATE), _profileInfo, key: key, json: value); 58 | } 59 | 60 | // broadcast it directly to all the clients 61 | dynamic remove(collection, key) { 62 | return new CargoPackage(collection, new CargoAction(CargoAction.REMOVE), _profileInfo, key: key); 63 | } 64 | 65 | // broadcast it directly to all the clients 66 | dynamic set(collection, value) { 67 | return new CargoPackage(collection, new CargoAction(CargoAction.SET), _profileInfo, json: value); 68 | } 69 | 70 | // send to a specific socket with an id 71 | dynamic sendTo(id, request, data) { 72 | MessagePackage fme = new MessagePackage(request,new MessageType(MessageType.ID), data,_profileInfo); 73 | fme.messageType.id = id; 74 | return fme; 75 | } 76 | 77 | // send to a profile with specific values 78 | dynamic sendToProfile(key, value, request, data) { 79 | MessagePackage fme = new MessagePackage(request,new MessageType(MessageType.PROFILE), data,_profileInfo); 80 | fme.messageType.key = key; 81 | fme.messageType.value = value; 82 | return fme; 83 | } 84 | } -------------------------------------------------------------------------------- /lib/server/force.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class Force extends Object with ServerSendable { 4 | 5 | final Logger log = new Logger('Force'); 6 | 7 | static SecurityContextHolder _securityContext = new SecurityContextHolder(new NoSecurityStrategy()); 8 | 9 | var uuid = new Uuid(); 10 | // ForceMessageDispatcher _messageDispatcherInternal; 11 | 12 | ForceMessageSecurity messageSecurity = new ForceMessageSecurity(_securityContext); 13 | StreamController _profileController = new StreamController(); 14 | 15 | PollingServer pollingServer = new PollingServer(); 16 | 17 | /// When a new Socket is been created a new [SocketEvent] will be added. 18 | StreamController _onSocket = new StreamController.broadcast(); 19 | 20 | /// When a Socket connection is been closed a new [SocketEvent] will be added. 21 | StreamController _onSocketClosed = new StreamController.broadcast(); 22 | 23 | /// List of special connectors 24 | List connectors = new List(); 25 | 26 | ForceContext _forceContext; 27 | 28 | Duration _keepAliveTime; 29 | 30 | /** 31 | * The register method provides a way to add objects that contain the [Receiver] annotation, 32 | * these methods are then executed when the request value match an incoming message. 33 | * 34 | **/ 35 | void scan() { 36 | Scanner<_Receivable> classesHelper = new Scanner<_Receivable>(); 37 | 38 | List classes = ApplicationContext.addComponents(classesHelper.scan()); 39 | 40 | for (var obj in classes) { 41 | this.register(obj); 42 | } 43 | } 44 | 45 | /** 46 | * Add a keep alive to the Socket lifecycle of a WebSocket 47 | */ 48 | void activateKeepAlive({int seconds: 40}) { 49 | _keepAliveTime = new Duration(seconds: seconds); 50 | log.info("Keep alive is been activated with a ping pong at ${seconds} seconds."); 51 | } 52 | 53 | /** 54 | * The register method provides a way to add objects that contain the [Receiver] annotation, 55 | * these methods are then executed when the request value match an incoming message. 56 | * 57 | * So this will turn 58 | * 59 | * @Receiver("request") 60 | * void whenRequest(e, sendable) {} 61 | * 62 | * into 63 | * 64 | * on("request", (e, sendable) {} 65 | * 66 | * 67 | **/ 68 | void register(Object obj) { 69 | MetaDataHelper metaDataHelper = new MetaDataHelper(); 70 | List> metaDataValues = metaDataHelper.from(obj); 71 | 72 | var auth = MVCAnnotationHelper.getAuthentication(obj); 73 | 74 | var _ref; //Variable to check null values 75 | var roles = (auth != null ? auth.roles : null); 76 | 77 | // then look at Pr eAuthorizeRoles, when they are defined 78 | roles = (_ref = new AnnotationScanner().instanceFrom(obj))== null ? null : _ref.roles; 79 | 80 | for (MetaDataValue mdv in metaDataValues) { 81 | on(mdv.object.request, (e, sendable) { 82 | log.info("execute this please!"); 83 | mdv.invoke([e, sendable]); 84 | }, roles: roles); 85 | } 86 | 87 | // Look for new connection annotations 88 | MetaDataHelper<_NewConnection, MethodMirror> newConnectionMetaDataHelper = new MetaDataHelper<_NewConnection, MethodMirror>(); 89 | List> ncMetaData = newConnectionMetaDataHelper.from(obj); 90 | 91 | _invokeMetaDataSocketEvent(_onSocket.stream, ncMetaData); 92 | 93 | // Look for close connection annotations 94 | MetaDataHelper<_ClosedConnection, MethodMirror> closedConnectionMetaDataHelper = new MetaDataHelper<_ClosedConnection, MethodMirror>(); 95 | List> ccMetaData = closedConnectionMetaDataHelper.from(obj); 96 | 97 | _invokeMetaDataSocketEvent(_onSocketClosed.stream, ccMetaData); 98 | } 99 | 100 | void _invokeMetaDataSocketEvent(Stream stream, List metaData) { 101 | stream.listen((SocketEvent se) { 102 | for (MetaDataValue mdv in metaData) { 103 | mdv.invoke([se.wsId, se.socket]); 104 | } 105 | }); 106 | } 107 | 108 | /** 109 | * Handles the abstract Socket implementation of Force, so we can wrap any kind of Socket into this abstract class. 110 | * 111 | **/ 112 | void handle(ForceSocket socket) { 113 | String id = uuid.v4(); 114 | log.info("register id $id"); 115 | 116 | this.forceSockets[id] = socket; 117 | this.forceSockets[id].onMessage.listen((e) { 118 | handleMessages(e.request, id, e.data); 119 | }); 120 | this.forceSockets[id].done().then((e) { 121 | log.info("socket ended"); 122 | checkConnections(); 123 | }); 124 | _startNewConnection(id, socket); 125 | } 126 | 127 | void _startNewConnection(String socketId, ForceSocket socket) { 128 | checkConnections(); 129 | _onSocket.add(new SocketEvent(socketId, socket)); 130 | // send a first message to the newly connected socket 131 | this.sendTo(socketId, "ack", "ack"); 132 | } 133 | 134 | /** 135 | * Handles the messages that are coming into the server, regulator. 136 | */ 137 | void handleMessages(HttpRequest req, String id, data) { 138 | List packages = _lazyContext().protocolDispatchers().convertPackages(data, wsId: id); 139 | for(var package in packages) { 140 | if (messageSecurity.isAuthorized(req, package)) { 141 | _lazyContext().protocolDispatchers().dispatch(package); 142 | } else { 143 | sendTo(id, "unauthorized", data); 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * This will be executed before the on method. 150 | * 151 | * It will be executed before every message that is been send. This method can help with intercept the message before it goes into the loop. 152 | * 153 | **/ 154 | void before(MessageReceiver messageController) { 155 | _lazyContext().messageDispatch().before(messageController); 156 | } 157 | 158 | /** 159 | * You can use this method when you want to do something when a message comes in for a certain request. 160 | * 161 | * So imagine you want to do something when a message with request 'info' comes in. Then you can do it like this. 162 | * 163 | * on('info', (e, sendable) { 164 | * sendable.send("received", { "data": "ok" }); 165 | * }); 166 | * 167 | **/ 168 | void on(String request, MessageReceiver messageController, {List roles}) { 169 | messageSecurity.register(request, roles); 170 | _lazyContext().messageDispatch().register(request, messageController); 171 | } 172 | 173 | /** 174 | * You can publish a collection so it is been know in the system and also filter CargoPackages before it gets dispatched 175 | * 176 | * So when bad data comes in you can anticipate before adding it into the Cargo Persistent Store (Mongo, Memory, File, ...) 177 | * 178 | * publish('todos', cargoInstance, (ForceCargoPackage fcp) { 179 | * if (fcp.action = ActionType.ADD) { 180 | * 181 | * } 182 | * }); 183 | */ 184 | CargoBase publish(String collection, CargoBase cargo, {ValidateCargoPackage validate}) { 185 | CargoBase cargoWithCollection = cargo.instanceWithCollection(collection); 186 | _lazyContext().cargoPacakgeDispatcher().publish(collection, cargoWithCollection, filter: validate); 187 | return cargoWithCollection; 188 | 189 | } 190 | 191 | /** 192 | * Close a specific websocket connection. 193 | * 194 | **/ 195 | void close(String id) { 196 | if (forceSockets.containsKey(id)) { 197 | this.forceSockets[id].close(); 198 | } 199 | checkConnections(); 200 | } 201 | 202 | /** 203 | * check all the connections if they are still all active, otherwise these connections will be closed and removed from the websockets list. 204 | * 205 | **/ 206 | void checkConnections() { 207 | List removeWs = new List(); 208 | this.forceSockets.forEach((String key, ForceSocket ws) { 209 | if (ws.isClosed()) { 210 | removeWs.add(key); 211 | } 212 | }); 213 | 214 | _removeWsConnections(removeWs); 215 | } 216 | 217 | void _removeWsConnections(List removeWs) { 218 | printAmountOfConnections(); 219 | 220 | for (String wsId in removeWs) { 221 | _onSocketClosed.add(new SocketEvent(wsId, this.forceSockets[wsId])); 222 | this.forceSockets.remove(wsId); 223 | if (this.profiles.containsKey(wsId)) { 224 | _profileController.add(new ForceProfileEvent(ForceProfileType.Removed, wsId, this.profiles[wsId])); 225 | 226 | this.profiles.remove(wsId); 227 | } 228 | } 229 | } 230 | 231 | void addConnector(Connector connector) { 232 | connectors.add(connector); 233 | 234 | connector.wire().listen((ForceSocket forceSocket) { 235 | handle(forceSocket); 236 | 237 | connector.complete(); 238 | }); 239 | } 240 | 241 | void addProtocol(Protocol protocol) { 242 | _lazyContext().protocolDispatchers().addProtocol(protocol); 243 | } 244 | 245 | void _checkProfiles(e, sendable) { 246 | if (e.profile != null) { 247 | if (!profiles.containsKey(e.wsId)) { 248 | _profileController.add(new ForceProfileEvent(ForceProfileType.New, e.wsId, e.profile)); 249 | } else { 250 | // look at the difference with current profile 251 | Map oldProfile = profiles[e.wsId]; 252 | Map newProfile = e.profile; 253 | newProfile.forEach((key, value) { 254 | if (oldProfile.containsKey(key)) { 255 | if (oldProfile[key]!=value) { 256 | _profileController.add(new ForceProfileEvent(ForceProfileType.ChangedProperty, e.wsId, e.profile, property: new ForceProperty(key, oldProfile[key]))); 257 | } 258 | } else { 259 | _profileController.add(new ForceProfileEvent(ForceProfileType.NewProperty, e.wsId, e.profile, property: new ForceProperty(key, value))); 260 | } 261 | }); 262 | } 263 | profiles[e.wsId] = e.profile; 264 | } 265 | } 266 | 267 | ForceContext _lazyContext() { 268 | if (_forceContext==null) { 269 | _forceContext = new ForceContext(this); 270 | } 271 | return _forceContext; 272 | } 273 | 274 | Stream get onProfileChanged => _profileController.stream; 275 | 276 | /// When a new socket is been created 277 | Stream get onSocket => _onSocket.stream; 278 | 279 | /// When a socket connection is been closed 280 | Stream get onClosed => _onSocketClosed.stream; 281 | 282 | } 283 | -------------------------------------------------------------------------------- /lib/server/force_context.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class ForceContext { 4 | 5 | ProtocolDispatchers _protocolDispatchers; 6 | CargoHolder _cargoHolder; 7 | ForceMessageDispatcher _forceMessageDispatcher; 8 | CargoPackageDispatcher _cargoPackageDispatcher; 9 | 10 | Force force; 11 | 12 | ForceContext(this.force); 13 | 14 | ProtocolDispatchers protocolDispatchers() { 15 | if (_protocolDispatchers == null) { 16 | _protocolDispatchers = new ProtocolDispatchers(this.force); 17 | ForceMessageProtocol forceMessageProtocol = new ForceMessageProtocol(messageDispatch()); 18 | _protocolDispatchers.addProtocol(forceMessageProtocol); 19 | 20 | // add Cargo 21 | ForceCargoProtocol forceCargoProtocol = new ForceCargoProtocol(cargoPacakgeDispatcher()); 22 | _protocolDispatchers.addProtocol(forceCargoProtocol); 23 | } 24 | return _protocolDispatchers; 25 | } 26 | 27 | ForceMessageDispatcher messageDispatch() { 28 | if (_forceMessageDispatcher == null) { 29 | _forceMessageDispatcher = new ForceMessageDispatcher(); 30 | } 31 | return _forceMessageDispatcher; 32 | } 33 | 34 | CargoHolder _innerCargoHolder() { 35 | if (_cargoHolder == null) { 36 | _cargoHolder = new CargoHolderServer(this.force); 37 | } 38 | return _cargoHolder; 39 | } 40 | 41 | CargoPackageDispatcher cargoPacakgeDispatcher() { 42 | if (_cargoPackageDispatcher==null) { 43 | _cargoPackageDispatcher = new CargoPackageDispatcher(_innerCargoHolder()); 44 | } 45 | return _cargoPackageDispatcher; 46 | } 47 | } -------------------------------------------------------------------------------- /lib/server/force_server.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | typedef WebSocketHandler(Socket socket); 4 | 5 | class ForceServer extends Force with Serveable { 6 | 7 | final Logger log = new Logger('ForceServer'); 8 | 9 | WebApplication _basicServer; 10 | 11 | ForceServer({host: "127.0.0.1", 12 | port: 8080, 13 | wsPath: "/ws", 14 | clientFiles: '../build/web/', 15 | clientServe: true, 16 | keepAlive: false, 17 | startPage: 'index.html'}) { 18 | _basicServer = new WebApplication(host: host, 19 | port: port, 20 | wsPath: wsPath, 21 | clientFiles: clientFiles, 22 | startPage: startPage, 23 | clientServe: clientServe); 24 | messageSecurity = new ForceMessageSecurity(_basicServer.securityContext); 25 | 26 | scan(); 27 | 28 | // listen on info from the client 29 | this.before(_checkProfiles); 30 | 31 | // start pollingServer 32 | pollingServer.onConnection.listen((PollingSocket socket) { 33 | handle(socket); 34 | }); 35 | 36 | if (keepAlive) _keepAliveTime = new Duration(seconds: 40); 37 | 38 | this.server.use('$wsPath/uuid/', pollingServer.retrieveUuid, method: "GET"); 39 | this.server.use(PollingServer.pollingPath(wsPath), pollingServer.polling, method: "GET"); 40 | this.server.use(PollingServer.pollingPath(wsPath), pollingServer.sendedData, method: "POST"); 41 | } 42 | 43 | /** 44 | * This method will start the server. 45 | * @param FallbackStart when a start fails, you can implement what the application needs todo to restart randomPortFallback is an example. 46 | * 47 | * @return a future when the server is been started. 48 | */ 49 | Future start({FallbackStart fallback}) { 50 | return _basicServer.start(handleWs: this._socketsHandler, fallback: fallback); 51 | } 52 | 53 | void _socketsHandler(WebSocket ws, HttpRequest req) { 54 | if (this._keepAliveTime!=null) ws.pingInterval = new Duration(seconds: 45); 55 | handle(new WebSocketWrapper(ws, req)); 56 | } 57 | 58 | /** 59 | * This requestHandler can be used to hook into the system without having to start a server. 60 | * You need to use this method for example with Google App Engine runtime. 61 | * 62 | * @param request is the current HttpRequest that needs to be handled by the system. 63 | */ 64 | void requestHandler(HttpRequest request) { 65 | _basicServer.requestHandler(request, this._socketsHandler); 66 | } 67 | 68 | /** 69 | * Activate server logging in forcemvc 70 | * 71 | * @param the level of login 72 | */ 73 | void setupConsoleLog([Level level = Level.INFO]) { 74 | _basicServer.setupConsoleLog(level); 75 | } 76 | 77 | WebApplication get server => _basicServer; 78 | 79 | } 80 | 81 | -------------------------------------------------------------------------------- /lib/server/metadata.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | /** 4 | * This annotation can be added to a method, 5 | * this method will be able to listen to messages coming in on a certain message request 6 | * 7 | * @Receiver("add") 8 | * void add(vme, sender) { 9 | * sender.send("update", vme.json); 10 | * } 11 | * 12 | * It is the same as: 13 | * fs.on("add", (vme, sender) { 14 | sender.send("update", vme.json); 15 | }); 16 | */ 17 | class Receiver { 18 | 19 | final String request; 20 | const Receiver(this.request); 21 | 22 | String toString() => request; 23 | 24 | } 25 | 26 | /** 27 | * This annotation can be added to a class, this will indicate that this class can have receivable methods, 28 | * that can capture messages on a certain request. 29 | */ 30 | const Receivable = const _Receivable(); 31 | 32 | class _Receivable { 33 | 34 | const _Receivable(); 35 | 36 | } 37 | 38 | /** 39 | * This annotation can be used when you want to catch new connections 40 | * 41 | * @NewConnection 42 | * void someNewConnection(socketId, Socket socket) { 43 | * ... 44 | * } 45 | */ 46 | const NewConnection = const _NewConnection(); 47 | 48 | class _NewConnection { 49 | 50 | const _NewConnection(); 51 | 52 | } 53 | 54 | /** 55 | * This annotation can be used when you want to catch closed connections, 56 | * when somebody closed his browser 57 | * 58 | * @ClosedConnection 59 | * void someNewConnection(socketId, Socket socket) { 60 | * ... 61 | * } 62 | */ 63 | const ClosedConnection = const _ClosedConnection(); 64 | 65 | class _ClosedConnection { 66 | 67 | const _ClosedConnection(); 68 | 69 | } -------------------------------------------------------------------------------- /lib/server/polling_server.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class PollingServer { 4 | 5 | final Logger log = new Logger('PollingServer'); 6 | 7 | static String pollingPath(String wsPath) => '$wsPath/polling/'; 8 | 9 | Map connections = new Map(); 10 | StreamController _socketController; 11 | 12 | PollingServer() { 13 | _socketController = new StreamController(); 14 | } 15 | 16 | void retrieveUuid(ForceRequest forceRequest, Model model) { 17 | model.addAttribute("id", new Uuid().v4()); 18 | } 19 | 20 | void polling(ForceRequest req, Model model) { 21 | String pid = req.request.uri.queryParameters['pid']; 22 | 23 | checkMessages(req, model, pid); 24 | } 25 | 26 | void checkMessages(ForceRequest req, Model model, pid) { 27 | PollingSocket pollingSocket = retrieveSocket(pid, req.request); 28 | 29 | List messages = new List(); 30 | for (var message in pollingSocket.messages) { 31 | messages.add(message); 32 | } 33 | 34 | model.addAttributeObject(messages); 35 | 36 | pollingSocket.messages.clear(); 37 | } 38 | 39 | Future sendedData(ForceRequest req, Model model) async { 40 | var package = await req.getPostData(); 41 | var pid = package["pid"]; 42 | 43 | PollingSocket pollingSocket = retrieveSocket(pid, req.request); 44 | if (pollingSocket != null) { 45 | pollingSocket.sendedData(package["data"]); 46 | } 47 | 48 | return {"status" : "ok"}; 49 | } 50 | 51 | PollingSocket retrieveSocket(pid, HttpRequest req) { 52 | PollingSocket pollingSocket; 53 | if (connections.containsKey(pid)) { 54 | pollingSocket = connections[pid]; 55 | pollingSocket.request = req; 56 | } else if (pid!=null) { 57 | log.info("new polling connection! $pid"); 58 | 59 | pollingSocket = new PollingSocket(req); 60 | connections[pid] = pollingSocket; 61 | _socketController.add(pollingSocket); 62 | } 63 | return pollingSocket; 64 | } 65 | 66 | Stream get onConnection => _socketController.stream; 67 | } -------------------------------------------------------------------------------- /lib/server/profile_event.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class ForceProfileEvent { 4 | 5 | ForceProfileType type; 6 | String wsId; 7 | dynamic profileInfo; 8 | ForceProperty property; 9 | 10 | ForceProfileEvent(this.type, this.wsId, this.profileInfo, {property: null}) { 11 | this.property = property; 12 | } 13 | 14 | toString() => "[$wsId] $type $profileInfo"; 15 | } 16 | 17 | class ForceProperty { 18 | 19 | String key; 20 | var value; 21 | 22 | ForceProperty(this.key, this.value); 23 | 24 | toString() => "$key - $value"; 25 | } 26 | 27 | class ForceProfileType { 28 | final String _type; 29 | 30 | const ForceProfileType(this._type); 31 | 32 | static const New = const ForceProfileType('New'); 33 | static const Removed = const ForceProfileType('Removed'); 34 | static const NewProperty = const ForceProfileType('NewProperty'); 35 | static const ChangedProperty = const ForceProfileType('ChangedProperty'); 36 | 37 | toString() => "$_type"; 38 | } -------------------------------------------------------------------------------- /lib/server/serveable.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | /** 4 | * This class makes it possible to easily serve files to a client. 5 | * 6 | */ 7 | class Serveable { 8 | 9 | WebApplication _basicServer; 10 | 11 | Stream serve(name) { 12 | return _basicServer.serve(name); 13 | } 14 | 15 | void serveFile(HttpRequest request, String root, String fileName) { 16 | _basicServer.serveFile(request, root, fileName); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /lib/server/server_protocol.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class ServerSendable implements Sendable, DataChangeable { 4 | 5 | final Logger log = new Logger('Sendable'); 6 | 7 | Map forceSockets = new Map(); 8 | Map profiles = new Map(); 9 | 10 | MessagesConstructHelper _messagesConstructHelper = new MessagesConstructHelper(); 11 | 12 | void send(request, data) { 13 | printAmountOfConnections(); 14 | 15 | var sendingPackage = _messagesConstructHelper.send(request, data); 16 | sendPackage(sendingPackage); 17 | } 18 | 19 | void broadcast(request, data) { 20 | printAmountOfConnections(); 21 | 22 | var sendingPackage = _messagesConstructHelper.broadcast(request, data); 23 | sendPackage(sendingPackage); 24 | } 25 | 26 | void sendTo(id, request, data) { 27 | log.info("*** send to $id"); 28 | 29 | var sendingPackage = _messagesConstructHelper.send(request, data); 30 | _sendPackageToId(id, sendingPackage); 31 | } 32 | 33 | void sendToProfile(key, value, request, data) { 34 | List ids = new List(); 35 | profiles.forEach((String id, profile_data) { 36 | if (profile_data[key] == value) { 37 | ids.add(id); 38 | } 39 | }); 40 | if (ids.isNotEmpty) { 41 | for (String id in ids) { 42 | sendTo(id, request, data); 43 | } 44 | } 45 | } 46 | 47 | // DATA PROTOCOL 48 | void add(collection, key, data, {id}) { 49 | var sendingPackage = _messagesConstructHelper.add(collection, key, data); 50 | _sendPackageToId(id, sendingPackage); 51 | } 52 | 53 | void set(collection, data, {id}) { 54 | var sendingPackage = _messagesConstructHelper.set(collection, data); 55 | _sendPackageToId(id, sendingPackage); 56 | } 57 | 58 | void update(collection, key, data, {id}) { 59 | var sendingPackage = _messagesConstructHelper.update(collection, key, data); 60 | _sendPackageToId(id, sendingPackage); 61 | } 62 | 63 | void remove(collection, key, {id}) { 64 | var sendingPackage = _messagesConstructHelper.remove(collection, key); 65 | _sendPackageToId(id, sendingPackage); 66 | } 67 | 68 | // OVERALL METHODS 69 | void sendPackage(sendingPackage) { 70 | forceSockets.forEach((String key, ForceSocket ws) { 71 | log.info("sending package ... to $key"); 72 | ws.add(JSON.encode(sendingPackage)); 73 | }); 74 | } 75 | 76 | void _sendPackageToId(id, sendingPackage) { 77 | ForceSocket ws = forceSockets[id]; 78 | if (ws != null) { 79 | ws.add(JSON.encode(sendingPackage)); 80 | } 81 | } 82 | 83 | void printAmountOfConnections() { 84 | int size = this.forceSockets.length; 85 | log.info("*** total amount of sockets: $size"); 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /lib/server/socket_event.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | /** 4 | * SocketEvent happens when a new socket is been created. 5 | * 6 | **/ 7 | class SocketEvent { 8 | 9 | String wsId; 10 | ForceSocket socket; 11 | 12 | SocketEvent(this.wsId, this.socket); 13 | 14 | String toString() => "Socket with id $wsId"; 15 | } -------------------------------------------------------------------------------- /lib/serverclient/force_client.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class ForceClient extends BaseForceClient with ClientSendable { 4 | ForceSocket socket; 5 | 6 | String host; 7 | int port; 8 | String url; 9 | 10 | ForceClient({this.host: '127.0.0.1', this.port: 4041, this.url: null}) { 11 | clientContext = new ForceClientContext(this); 12 | 13 | this.messenger = new ServerMessenger(socket); 14 | } 15 | 16 | connect() async { 17 | Socket serverSocket = await Socket.connect(this.host, this.port); 18 | this.socket = new ServerSocketWrapper(serverSocket); 19 | this.messenger = new ServerMessenger(socket); 20 | 21 | socket.onMessage.listen((e) { 22 | clientContext.protocolDispatchers.dispatch_raw(e.data); 23 | }); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /lib/serverclient/server_messenger.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class ServerMessenger extends Messenger { 4 | 5 | final Logger log = new Logger('ServerMessenger'); 6 | 7 | ForceSocket socket; 8 | 9 | ServerMessenger(this.socket); 10 | 11 | void send(sendingPackage) { 12 | if (socket != null && !socket.isClosed()) { 13 | log.info('send package to the server $sendingPackage'); 14 | socket.add(JSON.encode(sendingPackage)); 15 | } else { 16 | this.offline(sendingPackage); 17 | } 18 | } 19 | 20 | void online() { 21 | for (var package in notSendedPackages) { 22 | socket.add(JSON.encode(package)); 23 | } 24 | } 25 | 26 | 27 | } -------------------------------------------------------------------------------- /lib/serversocket/abstract_socket.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class MessageEvent { 4 | var data; 5 | HttpRequest request; 6 | 7 | MessageEvent(this.request, this.data); 8 | } 9 | 10 | abstract class ForceSocket { 11 | StreamController _messageController; 12 | 13 | HttpRequest request; 14 | 15 | Stream get onMessage => _messageController.stream; 16 | Future done(); 17 | 18 | bool isClosed(); 19 | 20 | void close(); 21 | 22 | void add(data); 23 | } -------------------------------------------------------------------------------- /lib/serversocket/polling_socket.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class PollingSocket extends ForceSocket { 4 | 5 | List messages = new List(); 6 | Completer completer = new Completer.sync(); 7 | HttpRequest request; 8 | 9 | PollingSocket(this.request) { 10 | _messageController = new StreamController(); 11 | } 12 | 13 | Future done() { 14 | return completer.future; 15 | } 16 | 17 | bool isClosed() { 18 | return false; 19 | } 20 | 21 | void close() { 22 | completer.complete(const []); 23 | } 24 | 25 | void sendedData(data) { 26 | _messageController.add(new MessageEvent(request, data)); 27 | } 28 | 29 | void add(data) { 30 | messages.add(data); 31 | } 32 | } -------------------------------------------------------------------------------- /lib/serversocket/server_socket.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | /** 4 | * 5 | * A wrapper class for the ServerSocket implementation! 6 | * 7 | */ 8 | class ServerSocketWrapper extends ForceSocket { 9 | 10 | Socket socket; 11 | bool closed = false; 12 | 13 | ServerSocketWrapper(this.socket, [request]){ 14 | _messageController = new StreamController(); 15 | 16 | this.socket.transform(UTF8.decoder).listen((data) { 17 | _messageController.add(new MessageEvent(request, data)); 18 | }).onDone(() { 19 | closed = true; 20 | }); 21 | } 22 | 23 | Future done() => this.socket.done; 24 | 25 | bool isClosed() { 26 | return closed; 27 | } 28 | 29 | void close() { 30 | socket.close(); 31 | } 32 | 33 | void add(data) { 34 | this.socket.writeln(data); 35 | } 36 | } -------------------------------------------------------------------------------- /lib/serversocket/stream_socket.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class StreamSocket extends ForceSocket { 4 | 5 | Stream stream; 6 | StreamSink sink; 7 | StreamSubscription subscription; 8 | StreamController _controller; 9 | 10 | bool closed = false; 11 | 12 | StreamSocket.fromController(StreamController streamController) { 13 | this.stream = streamController.stream; 14 | this.sink = streamController; 15 | 16 | _init(); 17 | } 18 | 19 | StreamSocket.fromStreamAndSink(this.stream, this.sink) { 20 | _init(); 21 | } 22 | 23 | StreamSocket(stream) { 24 | this.stream = stream; 25 | this.sink = stream; 26 | 27 | _init(); 28 | } 29 | 30 | void _init() { 31 | _messageController = new StreamController(); 32 | 33 | subscription = this.stream.listen((data) { 34 | _messageController.add(new MessageEvent(request, data)); 35 | }); 36 | subscription.onDone(() { 37 | closed = true; 38 | }); 39 | } 40 | 41 | Future done() => this.sink.done; 42 | 43 | bool isClosed() { 44 | return closed; 45 | } 46 | 47 | void close() { 48 | sink.close(); 49 | } 50 | 51 | void add(data) { 52 | this.sink.add(data); 53 | } 54 | } -------------------------------------------------------------------------------- /lib/serversocket/websocket_wrapper.dart: -------------------------------------------------------------------------------- 1 | part of force.server; 2 | 3 | class WebSocketWrapper extends ForceSocket { 4 | 5 | WebSocket webSocket; 6 | HttpRequest request; 7 | 8 | WebSocketWrapper(this.webSocket, [this.request]) { 9 | _messageController = new StreamController(); 10 | 11 | this.webSocket.listen((data) { 12 | _messageController.add(new MessageEvent(request, data)); 13 | }); 14 | } 15 | 16 | Future done() => this.webSocket.done; 17 | 18 | bool isClosed() { 19 | return webSocket.readyState == WebSocket.CLOSED; 20 | } 21 | 22 | void close() { 23 | this.webSocket.close(); 24 | } 25 | 26 | void add(data) { 27 | this.webSocket.add(data); 28 | } 29 | } -------------------------------------------------------------------------------- /lib/test.dart: -------------------------------------------------------------------------------- 1 | library force.test; 2 | 3 | import 'package:force/force_serverside.dart'; 4 | import 'dart:async'; 5 | 6 | /// A Testing helper class to construct everything you need to test end2end 7 | class TestForce { 8 | TwoWaySocket _twoWaySocket = new TwoWaySocket(); 9 | BaseForceClient forceClient; 10 | 11 | TestForce(Force force) { 12 | forceClient = new TestForceClient(_twoWaySocket.clientSocket); 13 | force.handle(_twoWaySocket.serverSocket); 14 | } 15 | } 16 | 17 | /// Test interface for ForceClient 18 | class TestForceClient extends BaseForceClient with ClientSendable { 19 | ForceSocket socket; 20 | 21 | TestForceClient(this.socket) { 22 | clientContext = new ForceClientContext(this); 23 | 24 | this.messenger = new ServerMessenger(socket); 25 | } 26 | 27 | void connect() { 28 | socket.onMessage.listen((e) { 29 | clientContext.protocolDispatchers.dispatch_raw(e.data); 30 | }); 31 | } 32 | 33 | } 34 | 35 | /// Two way socket implementation, to mock the connection between to sockets 36 | class TwoWaySocket { 37 | 38 | ForceSocket clientSocket; 39 | ForceSocket serverSocket; 40 | 41 | TwoWaySocket() { 42 | StreamController clientStream = new StreamController.broadcast(); 43 | StreamController serverStream = new StreamController.broadcast(); 44 | 45 | serverSocket = new StreamSocket.fromStreamAndSink(serverStream.stream, clientStream); 46 | clientSocket = new StreamSocket.fromStreamAndSink(clientStream.stream, serverStream); 47 | 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: force 2 | version: 0.7.1 3 | author: Joris Hermans 4 | description: A real time web framework for dart, embracing websockets, making communication even better 5 | homepage: https://github.com/ForceUniverse/dart-force 6 | dependencies: 7 | logging: '>=0.11.0 <0.12.0' 8 | uuid: any 9 | forcemvc: '>=0.8.5 <0.9.0' 10 | cargo: '>=0.8.0 <0.9.0' 11 | dev_dependencies: 12 | test: ">=0.12.13 <0.13.0" 13 | mock: '>=0.11.0 <0.11.1' 14 | environment: 15 | sdk: '>=1.9.0 <2.0.0' 16 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![VERSION!](https://img.shields.io/pub/v/force.svg) 2 | 3 | ### Dart Force Framework ### 4 | 5 | ![LOGO!](https://raw.github.com/ForceUniverse/dart-force/master/resources/dart_force_logo.jpg) 6 | 7 | A realtime web framework for dart. 8 | 9 | With this framework communication between client and server becomes easy, without any boilerplate code. 10 | 11 | #### Introduction #### 12 | 13 | Dart Force is a Realtime web framework for [Dart](http://www.dartlang.org). We will make it easy for you to create realtime applications with it in [Dart](http://www.dartlang.org), like a chat, interactive dashboard, multiplayer games, ... 14 | 15 | #### How does it work? #### 16 | 17 | ##### Serverside ##### 18 | 19 | First of all you need a server to handle incoming messages and dispatch or handle this messages correctly. 20 | 21 | ```dart 22 | import "package:force/force_serverside.dart"; 23 | 24 | ForceServer fs = new ForceServer(); 25 | 26 | main() async{ 27 | fs.server.use("/", (req, model) => "dartforcetodo"); 28 | await fs.start(); 29 | 30 | fs.on("add", (vme, sender) { 31 | fs.send("update", vme.json); 32 | }); 33 | 34 | } 35 | ``` 36 | 37 | ##### Clientside ##### 38 | 39 | The client can listen to messages: 40 | 41 | ```dart 42 | ForceClient fc; 43 | void main() { 44 | fc = new ForceClient(); 45 | fc.connect(); 46 | 47 | fc.onConnected.listen((e) { 48 | fc.on("update", (fme, sender) { 49 | querySelector("#list").appendHtml("
${fme.json["todo"]}
"); 50 | }); 51 | }); 52 | } 53 | ``` 54 | 55 | You can also send messages: 56 | ```dart 57 | InputElement input = querySelector("#input"); 58 | var data = {"todo": input.value}; 59 | fc.send("add", data); 60 | ``` 61 | 62 | It is a little bit inspired by [socket.io](http://socket.io) for the communication flow. 63 | 64 | ##### Dart Force mvc access (routing) ##### 65 | 66 | You have access to the force mvc webserver if you do the following: 67 | ```dart 68 | forceServer.server.on(url, controllerHandler, method: 'GET'); 69 | ``` 70 | 71 | or just create a controller class. For more info go to the project page of [force mvc](https://github.com/ForceUniverse/dart-force/wiki/ForceMVC%3A-Serverside-routing) 72 | 73 | #### Shelf integration #### 74 | 75 | You can very easily use the power of Force into the shelf stack by using shelf_web_socket package and then use the following code, so that force can interpret the websocket stream of shelf. 76 | 77 | ```dart 78 | Force force = new Force(); 79 | var _handlerws = webSocketHandler((webSocket) => force.handle(new StreamSocket(webSocket))); 80 | ``` 81 | 82 | More info on the [wiki page](https://github.com/ForceUniverse/dart-force/wiki/Shelf) 83 | 84 | #### Quick starter guide #### 85 | 86 | This guide can help you to get you started! [Getting started](https://github.com/ForceUniverse/dart-force/wiki/Getting-started) 87 | 88 | * Reference 89 | * [Long polling](https://github.com/ForceUniverse/dart-force/wiki/Long-polling) 90 | * [Communication flow](https://github.com/ForceUniverse/dart-force/wiki/Communication-flow) 91 | * [Profile management](https://github.com/ForceUniverse/dart-force/wiki/Profile-management) 92 | * [Annotations](https://github.com/ForceUniverse/dart-force/wiki/Annotations) 93 | * [ForceMVC: Serverside routing, similar too spring mvc](https://github.com/ForceUniverse/dart-force/wiki/ForceMVC%3A-Serverside-routing) 94 | * [Authentication](https://github.com/ForceUniverse/dart-force/wiki/Authentication) 95 | * [Google AppEngine](https://github.com/ForceUniverse/dart-force/wiki/Google-AppEngine) 96 | * [Connectors](https://github.com/ForceUniverse/dart-force/wiki/Connectors) 97 | * [Server 2 Server Communication](https://github.com/ForceUniverse/dart-force/wiki/server-2-server) 98 | * [Custom protocols](https://github.com/ForceUniverse/dart-force/wiki/Custom-protocols) 99 | * [Clientside DB API](https://github.com/ForceUniverse/dart-force/wiki/Clientside-DB-API) 100 | 101 | Look at our wiki for more [info](https://github.com/ForceUniverse/dart-force/wiki) or this info below. 102 | 103 | #### Examples #### 104 | 105 | You can find a lot of examples in the force examples [organisation](https://github.com/ForceExamples) 106 | 107 | Links to some examples that I made with this framework. 108 | 109 | [chat](http://forcechat.herokuapp.com/) - [source code](https://github.com/ForceExamples/dart-force-chat-example) 110 | 111 | [polymer example](http://polymerforce.herokuapp.com) - [source code](https://github.com/jorishermans/dart-force-polymer-example) 112 | 113 | #### Development trick #### 114 | 115 | Following the next steps will make it easier for you to develop, this allows you to adapt clientside files and immidiatly see results without doing a pub build. 116 | 117 | pub serve web --hostname 0.0.0.0 --port 7777 && 118 | export DART_PUB_SERVE="http://localhost:7777" && 119 | pub run bin/server.dart 120 | 121 | #### Server 2 Server #### 122 | 123 | It is also possible to do server 2 server communication. You can find the more info [here](https://github.com/ForceUniverse/dart-force/wiki/server-2-server) 124 | 125 | or you can watch the [video](https://www.youtube.com/watch?v=4J33_60Bf3I) 126 | 127 | ### Notes to Contributors ### 128 | 129 | #### Fork Dart Force #### 130 | 131 | If you'd like to contribute back to the core, you can [fork this repository](https://help.github.com/articles/fork-a-repo) and send us a pull request, when it is ready. 132 | 133 | If you are new to Git or GitHub, please read [this guide](https://help.github.com/) first. 134 | 135 | #### Twitter #### 136 | 137 | Follow us on twitter https://twitter.com/usethedartforce 138 | 139 | #### Google+ #### 140 | 141 | Follow us on [google+](https://plus.google.com/111406188246677273707) 142 | 143 | or join our [G+ Community](https://plus.google.com/u/0/communities/109050716913955926616) 144 | 145 | #### Screencast tutorial #### 146 | 147 | Screencast todo tutorial about the dart force realtime functionality on [youtube](http://youtu.be/FZr75CsBNag) 148 | 149 | #### Join our discussion group #### 150 | 151 | [Google group](https://groups.google.com/forum/#!forum/dart-force) 152 | -------------------------------------------------------------------------------- /resources/dart_force_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForceUniverse/dart-force/ffff42951a8404121dc78013a9617287c75df232/resources/dart_force_logo.jpg -------------------------------------------------------------------------------- /test/cargo_tests.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:force/force_serverside.dart'; 3 | import 'package:cargo/cargo_base.dart'; 4 | import 'package:cargo/cargo_server.dart'; 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | 8 | import 'package:force/test.dart'; 9 | 10 | void main() { 11 | var collection = "posts"; 12 | var profileName = 'chatName'; 13 | HttpRequest req; 14 | 15 | var data = { 'key' : 'value', 'key2' : 'value2' }; 16 | var profileInfo = {'name' : profileName}; 17 | 18 | test('force cargo package test', () { 19 | ForceServer fs = new ForceServer(); 20 | Cargo cargo = new Cargo(); 21 | fs.publish(collection, cargo); 22 | 23 | var sendingPackage = new CargoPackage(collection, new CargoAction(CargoAction.ADD), profileInfo, json: data); 24 | 25 | cargo.onAll((DataEvent de) { 26 | expect(data, de.data[0]); 27 | }); 28 | 29 | fs.handleMessages(req, "id:bla", JSON.encode(sendingPackage)); 30 | }); 31 | 32 | test('force cargo publish/register test', () { 33 | Force force = new Force(); 34 | // setup client 35 | TestForce tf = new TestForce(force); 36 | TestForceClient fc = tf.forceClient; 37 | fc.connect(); 38 | 39 | Cargo cargo = new Cargo(MODE: CargoMode.MEMORY); 40 | force.publish("hunters", cargo, validate: (CargoPackage fcp, Sender sender) {}); 41 | 42 | fc.initProfileInfo({'name' : profileName}); 43 | 44 | Cargo cargo2 = new Cargo(MODE: CargoMode.MEMORY); 45 | fc.register("hunters", cargo2); 46 | }); 47 | 48 | test('force cargo publish/register test', () async { 49 | Force force = new Force(); 50 | // setup client 51 | TestForce tf = new TestForce(force); 52 | TestForceClient fc = tf.forceClient; 53 | fc.connect(); 54 | 55 | CargoBase newCargo = force.publish("hunters", new Cargo(MODE: CargoMode.MEMORY), validate: (CargoPackage fcp, Sender sender) {}); 56 | 57 | newCargo.onAll((de) async { 58 | int length = await newCargo.length(); 59 | expect(length, 1); 60 | }); 61 | 62 | // fc.initProfileInfo({'name' : profileName}); 63 | 64 | Cargo cargo2 = new Cargo(MODE: CargoMode.MEMORY); 65 | ViewCollection vc = fc.register("hunters", cargo2); 66 | vc.update("value", "gogo"); 67 | 68 | }); 69 | 70 | } -------------------------------------------------------------------------------- /test/flow_tests.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:force/force_serverside.dart'; 3 | import 'package:force/test.dart'; 4 | import 'dart:convert'; 5 | import 'dart:async'; 6 | 7 | void main() { 8 | var request = "req:"; 9 | var profileName = 'chatName'; 10 | var data = { 'key' : 'value', 'key2' : 'value2' }; 11 | 12 | test('force basic messageDispatcher test', () { 13 | Force force = new Force(); 14 | 15 | var sendingPackage = new MessagePackage(request, new MessageType(MessageType.NORMAL), data, {'name' : profileName}); 16 | 17 | force.on(request, expectAsync((e, sendable) { 18 | expect(e.profile['name'], profileName); 19 | expect(e.json['key'], 'value'); 20 | expect(e.json['key2'], 'value2'); 21 | })); 22 | 23 | StreamSocket streamSocket = new StreamSocket.fromController(new StreamController.broadcast()); 24 | streamSocket.add(JSON.encode(sendingPackage)); 25 | 26 | force.handle(streamSocket); 27 | }); 28 | 29 | test('sending data from client, the socket chain', () { 30 | Force force = new Force(); 31 | // setup client 32 | TestForce tf = new TestForce(force); 33 | TestForceClient fc = tf.forceClient; 34 | fc.connect(); 35 | 36 | force.on(request, expectAsync((e, sendable) { 37 | expect(e.profile['name'], profileName); 38 | expect(e.json['key'], 'value'); 39 | expect(e.json['key2'], 'value2'); 40 | })); 41 | fc.initProfileInfo({'name' : profileName}); 42 | fc.send(request, data); 43 | }); 44 | 45 | test('sending data from the server, the socket chain', () { 46 | Force force = new Force(); 47 | // setup client 48 | TestForce tf = new TestForce(force); 49 | TestForceClient fc = tf.forceClient; 50 | fc.connect(); 51 | 52 | fc.on(request, expectAsync((e, sendable) { 53 | expect(e.json['key'], 'value'); 54 | expect(e.json['key2'], 'value2'); 55 | })); 56 | 57 | force.send(request, data); 58 | }); 59 | 60 | } -------------------------------------------------------------------------------- /test/pollingserver_tests.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:forcemvc/force_mvc.dart'; 3 | import 'package:force/force_serverside.dart'; 4 | import 'package:forcemvc/test.dart'; 5 | 6 | main() { 7 | // First tests! 8 | 9 | Model model = new Model(); 10 | MockForceRequest req = new MockForceRequest(); 11 | 12 | var request = "poll"; 13 | var profileName = "jef"; 14 | var package = {'request': request, 15 | 'type': { 'name' : 'normal'}, 16 | 'profile': {'name' : profileName}, 17 | 'data': { 'key' : 'value', 'key2' : 'value2' }}; 18 | 19 | var uuid = "@pid@"; 20 | var send_package = { 21 | "pid" : uuid, 22 | "data" : package 23 | }; 24 | 25 | test('testing the polling server', () { 26 | 27 | PollingServer pollingServer = new PollingServer(); 28 | pollingServer.onConnection.listen(expectAsync((PollingSocket socket) { 29 | socket.onMessage.listen(expectAsync((MessageEvent event) { 30 | expect(event.data["request"], request); 31 | })); 32 | })); 33 | 34 | req.postData = send_package; 35 | 36 | pollingServer.sendedData(req, model); 37 | pollingServer.checkMessages(req, model, uuid); 38 | 39 | }); 40 | 41 | 42 | } -------------------------------------------------------------------------------- /test/serverside_tests.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:force/force_serverside.dart'; 3 | import 'dart:convert'; 4 | import 'dart:io'; 5 | 6 | void main() { 7 | var request = "req:"; 8 | var profileName = 'chatName'; 9 | HttpRequest req; 10 | 11 | var data = { 'key' : 'value', 'key2' : 'value2' }; 12 | var profileInfo = {'name' : profileName}; 13 | 14 | test('force basic messageDispatcher test', () { 15 | ForceServer fs = new ForceServer(); 16 | var sendingPackage = new MessagePackage(request, new MessageType(MessageType.NORMAL), data, profileInfo); 17 | 18 | fs.on(request, expectAsync((e, sendable) { 19 | expect(e.profile['name'], profileName); 20 | expect(e.json['key'], 'value'); 21 | expect(e.json['key2'], 'value2'); 22 | })); 23 | 24 | fs.handleMessages(req, "id:bla", JSON.encode(sendingPackage)); 25 | }); 26 | 27 | test('force id messageDispatcher test', () { 28 | ForceServer fs = new ForceServer(); 29 | MessageType fmt = new MessageType(MessageType.ID); 30 | fmt.id = 'aefed'; 31 | 32 | var sendingPackage = new MessagePackage(request, fmt, data, profileInfo); 33 | 34 | fs.on(request, expectAsync((e, sendable) { 35 | print('Should not be reached'); }, count: 0)); 36 | 37 | 38 | fs.handleMessages(req, "id:bla", JSON.encode(sendingPackage)); 39 | }); 40 | 41 | test('force profile messageDispatcher test', () { 42 | ForceServer fs = new ForceServer(); 43 | 44 | MessageType fmt = new MessageType(MessageType.PROFILE); 45 | fmt.key = 'key'; 46 | fmt.value = 'value'; 47 | var sendingPackage = new MessagePackage(request, fmt, data, profileInfo); 48 | 49 | fs.on(request, expectAsync((e, sendable) { 50 | print('Should not be reached'); }, count: 0)); 51 | 52 | fs.handleMessages(req, "id:bla", JSON.encode(sendingPackage)); 53 | }); 54 | 55 | test('force profile changing test', () { 56 | ForceServer fs = new ForceServer(); 57 | 58 | MessageType fmt = new MessageType(MessageType.NORMAL); 59 | var sendingPackage = new MessagePackage(request, fmt, data, profileInfo); 60 | 61 | fs.onProfileChanged.listen(expectAsync((e) { 62 | String name = e.profileInfo['name']; 63 | 64 | expect(name, profileName); 65 | })); 66 | 67 | fs.handleMessages(req, "id:bla", JSON.encode(sendingPackage)); 68 | }); 69 | 70 | test('force new property profile changing test', () { 71 | ForceServer fs = new ForceServer(); 72 | var channelName = "chnnl"; 73 | var sendingPackage = new MessagePackage(request, 74 | new MessageType(MessageType.NORMAL), 75 | { 'key' : 'value', 'key2' : 'value2' }, 76 | {'name' : profileName}).toJson(); 77 | 78 | fs.handleMessages(req, "id:bla", JSON.encode(sendingPackage)); 79 | 80 | fs.onProfileChanged.listen(expectAsync((e) { 81 | if (e.type == ForceProfileType.NewProperty) { 82 | expect(e.property.key, 'channel'); 83 | expect(e.property.value, channelName); 84 | } 85 | }, count: 2)); 86 | 87 | sendingPackage['profile'] = {'name' : profileName, 'channel' : channelName}; 88 | fs.handleMessages(req, "id:bla", JSON.encode(sendingPackage)); 89 | }); 90 | 91 | test('force changed property profile changing test', () { 92 | ForceServer fs = new ForceServer(); 93 | var channelName = "chnnl"; 94 | var sendingPackage = {'request': request, 95 | 'type': { 'name' : 'normal'}, 96 | 'profile': {'name' : profileName}, 97 | 'data': { 'key' : 'value', 'key2' : 'value2' }}; 98 | 99 | fs.handleMessages(req, "id:bla", JSON.encode(sendingPackage)); 100 | 101 | fs.onProfileChanged.listen(expectAsync((e) { 102 | if (e.type == ForceProfileType.ChangedProperty) { 103 | expect(e.property.key, 'name'); 104 | expect(e.property.value, profileName); 105 | expect(e.profileInfo['name'], channelName); 106 | } 107 | }, count: 2)); 108 | 109 | sendingPackage['profile'] = {'name' : channelName}; 110 | fs.handleMessages(req, "id:bla", JSON.encode(sendingPackage)); 111 | }); 112 | } -------------------------------------------------------------------------------- /walkthrough.md: -------------------------------------------------------------------------------- 1 | ### Dart Force Framework ### 2 | 3 | ![LOGO!](https://raw.github.com/ForceUniverse/dart-force/master/resources/dart_force_logo.jpg) 4 | 5 | A realtime web framework for dart. 6 | 7 | With this framework communication between client and server becomes easy, without any boilerplate code. 8 | 9 | Look at our wiki for more [info](https://github.com/ForceUniverse/dart-force/wiki) or this info below. 10 | 11 | #### Walkthrough #### 12 | 13 | ##### Client side ##### 14 | 15 | Import the client side library for dart force. 16 | 17 | import 'package:force/force_browser.dart'; 18 | 19 | First create a client. 20 | 21 | ForceClient forceClient = new ForceClient(); 22 | forceClient.connect(); 23 | 24 | Listen on the connection, when it is established. 25 | 26 | forceClient.onConnected.listen((e) { 27 | 28 | }); 29 | 30 | Listen on the connection, when it is been broken. 31 | 32 | forceClient.onDisconnected.listen((e) { 33 | 34 | }); 35 | 36 | Listen on messages with the request of text. 37 | 38 | forceClient.on("text", (e, sender) { 39 | ... 40 | }); 41 | 42 | You can also send messages to the server. 43 | 44 | forceClient.send('text', data); 45 | 46 | When you are in the need to reply on a message, you can use the 'reply' method of sender. 47 | 48 | forceClient.on("text", (e, sender) { 49 | sender.reply("received", ok_data); 50 | }); 51 | 52 | ##### Server Side ##### 53 | 54 | Import Serverside code for dart force. 55 | 56 | import 'package:force/force_serverside.dart'; 57 | 58 | Instantiate a forceserver. 59 | 60 | ForceServer fs = new ForceServer( port: 9223, startPage: 'start.html' ); 61 | 62 | Other optional properties that are possible on ForceServer: 63 | 64 | wsPath: is the websocket path of the server 65 | host: is the domain name of your application, by default to localhost 66 | port: is the adres port of the application 67 | buildPath: is the build path of the application by default this is ../build/web/ 68 | startPage: the startpage of the application, the html name that the app needs to use as default root page 69 | staticDir: is the public directory where you can put your stylesheets and images 70 | 71 | Listen on messages of type text and react upon that. 72 | 73 | fs.on('text', (e, sendable) { 74 | var json = e.json; 75 | var line = json['line']; 76 | sendable.send('text', { 'line': line }); 77 | }); 78 | 79 | You can also serve files from the server part. 80 | 81 | fs.start().then((_) { 82 | fs.serve("/client.dart").listen((request) { 83 | fs.serveFile("../web/client.dart", request); 84 | }); 85 | }); 86 | 87 | You can listen when a new Socket connection is been created. 88 | 89 | fs.onSocket.listen((SocketEvent se) { 90 | // socket event 91 | }); 92 | 93 | #### Other features #### 94 | 95 | ##### Profile info & client to client communication ##### 96 | 97 | Adding profile data on a connection, this will make it easy to send a message to a certain profile group or sending messages to an individual, without knowing his websocket id. 98 | 99 | On the client you can set the current browser user his profile data as follow. 100 | 101 | var profileInfo = { 'name' : chatName}; 102 | forceClient.initProfileInfo(profileInfo); 103 | 104 | On the server you can send something to a profile or a profile group by the following method in sendable. 105 | 106 | sendable.sendToProfile('name', name, 'private', message); 107 | 108 | You can also listen to profileChanges by using the following method on the forceServer. 109 | 110 | fs.onProfileChanged().listen((e) => print("$e")); 111 | 112 | Now you can send directly from the client to another client, the server notice the message type and forward it directly to the corresponding client. 113 | No coding on server required todo this! 114 | 115 | Just add the following code in your client side code. 116 | 117 | forceClient.sendToProfile(key, value, request, data); 118 | 119 | ##### Long polling ##### 120 | 121 | You can easily use long polling as follow! 122 | 123 | forceClient = new ForceClient(usePolling: true, heartbeat: 200); 124 | 125 | ##### Serverside Classes with Receiver method annotations ##### 126 | 127 | On the server you can use @Receiver on a method to define that this is a receiver method. 128 | It is the same as forceServer.on("play", (e, sendable) { 129 | 130 | @Receiver("play") 131 | void onGamePlay(ForceMessageEvent vme, Sender sender) { 132 | 133 | You can register this class with the register method of a ForceServer object. 134 | 135 | forceServer.register(new GameReceiver()); 136 | 137 | You can also annotate a class with the @Receivable annotation, so the system can pick up this class and automatically register these classes. 138 | 139 | ##### Dart Force mvc access ##### 140 | 141 | You have access to the force mvc webserver if you do the following: 142 | 143 | forceServer.server.on(url, controllerHandler, method: 'GET'); 144 | 145 | ##### Authentication ##### 146 | 147 | You can now add the annotation @Authentication to a receiver class. 148 | 149 | You can also do the following. 150 | 151 | forceServer.on("examplerequest", (e, sendable) { 152 | // do something 153 | }, roles: ["ADMIN", "BASIC"]); 154 | 155 | An authentication in force is following a strategy. 156 | You can set a strategy by extending the class SecurityStrategy. 157 | 158 | class SessionStrategy extends SecurityStrategy { 159 | 160 | bool checkAuthorization(HttpRequest req) { 161 | HttpSession session = req.session; 162 | return (session["user"]!=null); 163 | } 164 | 165 | Uri getRedirectUri(HttpRequest req) { 166 | var referer = req.uri.toString(); 167 | return Uri.parse("/login/?referer=$referer"); 168 | } 169 | } 170 | 171 | And then add this strategy to the webserver. 172 | 173 | forceServer.server.strategy = new SessionStrategy(); 174 | 175 | When you are not authorized, the system sends the following message back: 176 | "unauthorized" with the data you send over the system. 177 | So you can also listen to the message "unauthorized" in your client, then you can inform the user he need to login. 178 | 179 | #### Logging #### 180 | 181 | You can easily boostrap logging. 182 | 183 | server.setupConsoleLog(); 184 | 185 | #### Other Annotations #### 186 | 187 | In the @Receivable classes you can also use: 188 | 189 | @NewConnection : when a new socket connection is established 190 | 191 | @NewConnection 192 | void connection(socketId, Socket socket) { 193 | print("new connection created for $socketId"); 194 | } 195 | 196 | @ClosedConnection : when a socket connection is been closed 197 | 198 | @ClosedConnection 199 | void closedConnection(socketId, Socket socket) { 200 | print("connection closed for $socketId"); 201 | } 202 | 203 | #### Server 2 Server Communication #### 204 | 205 | With the implementation of ServerSockets it is possible to use a ForceClient on the server. 206 | 207 | This allows you todo server 2 server communication. 208 | 209 | On the Force Server you implements: 210 | 211 | Connector connector = new ServerSocketConnector(); 212 | fs.addConnector(connector); 213 | 214 | connector.start(); 215 | 216 | On the other server where you want to sent messages to the Force Server you use: 217 | 218 | ForceClient fc = new ForceClient(); 219 | 220 | fc.on("update", (fme, sender) { 221 | print("todo: ${fme.json["todo"]}"); 222 | }); 223 | 224 | Under the hood it will establish a connection to the server socket. 225 | 226 | #### Development trick #### 227 | 228 | Following the next steps will make it easier for you to develop, this allows you to adapt clientside files and immidiatly see results without doing a pub build. 229 | 230 | pub serve web --hostname 0.0.0.0 --port 7777 && 231 | export DART_PUB_SERVE="http://localhost:7777" && 232 | pub run bin/server.dart 233 | 234 | #### GAE #### 235 | 236 | You can now easily run your Force apps on a Google App Engine infrastructure by the following code! The rest is the same as a normal dart force app. 237 | 238 | ```dart 239 | ForceServer forceServer = new ForceServer(); 240 | 241 | runAppEngine(forceServer.requestHandler).then((_) { 242 | // Server running. and you can do all the stuff you want! 243 | }); 244 | ``` 245 | 246 | You don't need to start ForceServer anymore, start of the server will be done by AppEngine! 247 | 248 | More info about [GAE overall](https://www.dartlang.org/cloud/) 249 | 250 | ### Notes to Contributors ### 251 | 252 | #### Fork Dart Force #### 253 | 254 | If you'd like to contribute back to the core, you can [fork this repository](https://help.github.com/articles/fork-a-repo) and send us a pull request, when it is ready. 255 | 256 | If you are new to Git or GitHub, please read [this guide](https://help.github.com/) first. 257 | 258 | #### Twitter #### 259 | 260 | Follow us on twitter https://twitter.com/usethedartforce 261 | 262 | #### Google+ #### 263 | 264 | Follow us on [google+](https://plus.google.com/111406188246677273707) 265 | 266 | or join our [G+ Community](https://plus.google.com/u/0/communities/109050716913955926616) 267 | 268 | #### Screencast tutorial #### 269 | 270 | Screencast todo tutorial about the dart force realtime functionality on [youtube](http://youtu.be/FZr75CsBNag) 271 | 272 | #### Join our discussion group #### 273 | 274 | [Google group](https://groups.google.com/forum/#!forum/dart-force) 275 | 276 | #### examples #### 277 | 278 | Links to some examples that I made with this framework. 279 | 280 | [chat](http://forcechat.herokuapp.com/) - [source code](https://github.com/jorishermans/dart-force-chat-example) 281 | 282 | [polymer example](http://polymerforce.herokuapp.com) - [source code](https://github.com/jorishermans/dart-force-polymer-example) 283 | --------------------------------------------------------------------------------