├── syweb ├── webclient │ ├── VERSION │ ├── favicon.ico │ ├── img │ │ ├── close.png │ │ ├── logo.png │ │ ├── p │ │ │ ├── p0.png │ │ │ ├── p1.png │ │ │ ├── p10.png │ │ │ ├── p11.png │ │ │ ├── p12.png │ │ │ ├── p13.png │ │ │ ├── p14.png │ │ │ ├── p15.png │ │ │ ├── p16.png │ │ │ ├── p17.png │ │ │ ├── p18.png │ │ │ ├── p19.png │ │ │ ├── p2.png │ │ │ ├── p20.png │ │ │ ├── p21.png │ │ │ ├── p22.png │ │ │ ├── p23.png │ │ │ ├── p24.png │ │ │ ├── p25.png │ │ │ ├── p3.png │ │ │ ├── p4.png │ │ │ ├── p5.png │ │ │ ├── p6.png │ │ │ ├── p7.png │ │ │ ├── p8.png │ │ │ └── p9.png │ │ ├── pause.png │ │ ├── play.png │ │ ├── video.png │ │ ├── voice.png │ │ ├── attach.png │ │ ├── delete.png │ │ ├── spinner.gif │ │ ├── gradient.png │ │ ├── keyboard.png │ │ ├── logo-small.png │ │ ├── settings.png │ │ ├── green_phone.png │ │ ├── default-profile.png │ │ └── icons │ │ │ ├── filetype-audio.png │ │ │ ├── filetype-gif.png │ │ │ ├── filetype-image.png │ │ │ ├── filetype-text.png │ │ │ ├── filetype-video.png │ │ │ ├── filetype-message.png │ │ │ └── filetype-attachment.png │ ├── media │ │ ├── busy.mp3 │ │ ├── busy.ogg │ │ ├── ring.mp3 │ │ ├── ring.ogg │ │ ├── callend.mp3 │ │ ├── callend.ogg │ │ ├── message.mp3 │ │ ├── message.ogg │ │ ├── ringback.mp3 │ │ └── ringback.ogg │ ├── config.sample.js │ ├── README │ ├── lib │ │ ├── angular-dialog-service-5.2.6 │ │ │ ├── dialogs.min.css │ │ │ └── dialogs-default-translations.min.js │ │ ├── angular-spinner.min.js │ │ ├── angular-spinner.min.js.map │ │ ├── angular-peity.js │ │ ├── ng-infinite-scroll-matrix.js │ │ ├── jquery.peity.min.js │ │ ├── autofill-event.js │ │ ├── spin.min.js │ │ ├── angular-route.min.js │ │ ├── angular-sanitize.min.js │ │ └── angular-file-upload.min.js │ ├── app │ │ ├── payment │ │ │ ├── state.html │ │ │ ├── payment.html │ │ │ ├── payment-controller.js │ │ │ └── payment-service.js │ │ ├── user │ │ │ ├── user.html │ │ │ └── user-controller.js │ │ ├── components │ │ │ ├── matrix │ │ │ │ ├── version-service.js │ │ │ │ ├── presence-service.js │ │ │ │ ├── event-reaper-service.js │ │ │ │ ├── typing-service.js │ │ │ │ ├── recents-service.js │ │ │ │ └── commands-service.js │ │ │ ├── fileInput │ │ │ │ └── file-input-directive.js │ │ │ ├── dialogs │ │ │ │ └── dialog-service.js │ │ │ └── utilities │ │ │ │ └── utilities-service.js │ │ ├── login │ │ │ ├── login.html │ │ │ ├── reset-password.html │ │ │ ├── register.html │ │ │ ├── reset-password-controller.js │ │ │ └── login-controller.js │ │ ├── mobile.css │ │ ├── recents │ │ │ ├── recents-controller.js │ │ │ └── recents-filter.js │ │ ├── app-directive.js │ │ ├── home │ │ │ ├── home.html │ │ │ └── home-controller.js │ │ ├── app-filter.js │ │ └── app.js │ └── test │ │ ├── README │ │ ├── unit │ │ ├── typing-service.spec.js │ │ ├── reset-password-controller.spec.js │ │ ├── recents-controller.spec.js │ │ ├── presence-service.spec.js │ │ ├── dialog-service.spec.js │ │ ├── user-controller.spec.js │ │ ├── login-controller.spec.js │ │ ├── commands-service.spec.js │ │ ├── event-reaper-service.spec.js │ │ └── register-controller.spec.js │ │ └── karma.conf.js └── __init__.py ├── .gitignore ├── MANIFEST.in ├── AUTHORS.rst ├── package.json ├── setup.py └── README.rst /syweb/webclient/VERSION: -------------------------------------------------------------------------------- 1 | 0.6.8 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test-results.xml 2 | **/test/coverage/ 3 | **/webclient/config.js 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include syweb/webclient * 2 | include *.json 3 | include *.rst 4 | include LICENSE 5 | -------------------------------------------------------------------------------- /syweb/webclient/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/favicon.ico -------------------------------------------------------------------------------- /syweb/webclient/img/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/close.png -------------------------------------------------------------------------------- /syweb/webclient/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/logo.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p0.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p1.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p10.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p11.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p12.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p13.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p14.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p15.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p16.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p17.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p18.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p19.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p2.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p20.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p21.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p22.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p23.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p24.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p25.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p3.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p4.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p5.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p6.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p7.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p8.png -------------------------------------------------------------------------------- /syweb/webclient/img/p/p9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/p/p9.png -------------------------------------------------------------------------------- /syweb/webclient/img/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/pause.png -------------------------------------------------------------------------------- /syweb/webclient/img/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/play.png -------------------------------------------------------------------------------- /syweb/webclient/img/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/video.png -------------------------------------------------------------------------------- /syweb/webclient/img/voice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/voice.png -------------------------------------------------------------------------------- /syweb/webclient/img/attach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/attach.png -------------------------------------------------------------------------------- /syweb/webclient/img/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/delete.png -------------------------------------------------------------------------------- /syweb/webclient/img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/spinner.gif -------------------------------------------------------------------------------- /syweb/webclient/media/busy.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/media/busy.mp3 -------------------------------------------------------------------------------- /syweb/webclient/media/busy.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/media/busy.ogg -------------------------------------------------------------------------------- /syweb/webclient/media/ring.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/media/ring.mp3 -------------------------------------------------------------------------------- /syweb/webclient/media/ring.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/media/ring.ogg -------------------------------------------------------------------------------- /syweb/webclient/img/gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/gradient.png -------------------------------------------------------------------------------- /syweb/webclient/img/keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/keyboard.png -------------------------------------------------------------------------------- /syweb/webclient/img/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/logo-small.png -------------------------------------------------------------------------------- /syweb/webclient/img/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/settings.png -------------------------------------------------------------------------------- /syweb/webclient/media/callend.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/media/callend.mp3 -------------------------------------------------------------------------------- /syweb/webclient/media/callend.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/media/callend.ogg -------------------------------------------------------------------------------- /syweb/webclient/media/message.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/media/message.mp3 -------------------------------------------------------------------------------- /syweb/webclient/media/message.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/media/message.ogg -------------------------------------------------------------------------------- /syweb/webclient/media/ringback.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/media/ringback.mp3 -------------------------------------------------------------------------------- /syweb/webclient/media/ringback.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/media/ringback.ogg -------------------------------------------------------------------------------- /syweb/webclient/img/green_phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/green_phone.png -------------------------------------------------------------------------------- /syweb/webclient/img/default-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/default-profile.png -------------------------------------------------------------------------------- /syweb/webclient/img/icons/filetype-audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/icons/filetype-audio.png -------------------------------------------------------------------------------- /syweb/webclient/img/icons/filetype-gif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/icons/filetype-gif.png -------------------------------------------------------------------------------- /syweb/webclient/img/icons/filetype-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/icons/filetype-image.png -------------------------------------------------------------------------------- /syweb/webclient/img/icons/filetype-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/icons/filetype-text.png -------------------------------------------------------------------------------- /syweb/webclient/img/icons/filetype-video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/icons/filetype-video.png -------------------------------------------------------------------------------- /syweb/webclient/img/icons/filetype-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/icons/filetype-message.png -------------------------------------------------------------------------------- /syweb/webclient/img/icons/filetype-attachment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matrix-org/matrix-angular-sdk/HEAD/syweb/webclient/img/icons/filetype-attachment.png -------------------------------------------------------------------------------- /syweb/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def installed_location(): 4 | return os.path.dirname(__file__) 5 | 6 | with open(os.path.join(installed_location(), "webclient/VERSION")) as f: 7 | __version__ = f.read().strip() 8 | -------------------------------------------------------------------------------- /syweb/webclient/config.sample.js: -------------------------------------------------------------------------------- 1 | webClientConfig = { 2 | paymentUrl: "https://some_payment_url.com/checkout", // url to do a payment 3 | paymentEulaUrl: "http://some_eula_url.com/eula" // url to obtain the EULA 4 | }; 5 | -------------------------------------------------------------------------------- /syweb/webclient/README: -------------------------------------------------------------------------------- 1 | Basic Usage 2 | ----------- 3 | 4 | The web client should automatically run when running the home server. 5 | Alternatively, you can run it stand-alone: 6 | 7 | $ python -m SimpleHTTPServer 8 | 9 | Then, open this URL in a WEB browser:: 10 | 11 | http://127.0.0.1:8000/ 12 | 13 | 14 | -------------------------------------------------------------------------------- /syweb/webclient/lib/angular-dialog-service-5.2.6/dialogs.min.css: -------------------------------------------------------------------------------- 1 | .dialog-header-error{background-color:#d2322d}.dialog-header-wait{background-color:#428bca}.dialog-header-notify{background-color:#eee}.dialog-header-confirm{background-color:#333}.dialog-header-confirm h4,.dialog-header-confirm span,.dialog-header-error h4,.dialog-header-error span,.dialog-header-wait h4,.dialog-header-wait span{color:#fff} -------------------------------------------------------------------------------- /syweb/webclient/app/payment/state.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | [matrix] 6 |
7 | 8 |

OpenMarket Matrix Gateway Login: {{status.title}}

9 |

10 | {{status.description}} 11 |

12 |
13 |
14 | -------------------------------------------------------------------------------- /syweb/webclient/test/README: -------------------------------------------------------------------------------- 1 | Testing is done using Karma and Jasmine 2.x 2 | 3 | 4 | UNIT TESTING 5 | ============ 6 | 7 | Requires the following: 8 | - npm/nodejs 9 | - phantomjs 10 | 11 | Requires the following node packages: 12 | - npm install jasmine 13 | - npm install karma 14 | - npm install karma-jasmine 15 | - npm install karma-phantomjs-launcher 16 | - npm install karma-junit-reporter 17 | - npm install karma-coverage 18 | 19 | Make sure you're in this directory so it can find the config file and run: 20 | karma start 21 | 22 | You should see all the tests pass. 23 | 24 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Kegan Dougal 2 | * Core 3 | 4 | Emmanuel Rohee 5 | * Early development 6 | 7 | Dave Baker 8 | * VoIP 9 | * Push API impl. 10 | * v2 Auth API impl. 11 | 12 | Matthew Hodgson 13 | * Layout improvements. 14 | * Bug fixes. 15 | 16 | Nolan Darilek 17 | * PR #2: Add ARIA to room messages wrapper so screen readers speak incoming 18 | messages. 19 | 20 | Brennan Novak 21 | * PR #7: Improve layout density of Name, Avatar, and Date, and Actions. 22 | * PR #7: Added npm package.json file. 23 | 24 | Andreas Ekeroth 25 | * PR #9: Add broadcast of incoming state events. 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matrix-angular-sdk", 3 | "version": "0.6.8", 4 | "description": "AngularJS SDK and example front-end client for the Matrix.org decentralised communications platform", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/matrix-org/matrix-angular-sdk.git" 12 | }, 13 | "keywords": [ 14 | "angular", 15 | "css", 16 | "html", 17 | "web", 18 | "client", 19 | "matrix-org", 20 | "chat", 21 | "webrtc", 22 | "voip", 23 | "im", 24 | "iot" 25 | ], 26 | "author": "Matrix.org", 27 | "license": "Apache-2.0", 28 | "bugs": { 29 | "url": "https://matrix.org/jira/browse/SYWEB" 30 | }, 31 | "homepage": "https://github.com/matrix-org/matrix-angular-sdk" 32 | } 33 | -------------------------------------------------------------------------------- /syweb/webclient/app/user/user.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | [matrix] 7 |
8 | 9 |

{{ user.displayname || user.id }}

10 | 11 |
12 |
13 | 14 |
15 |
16 |
{{ user.id }}
17 |
18 |
19 | 20 | 21 |
22 | 23 | 24 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /syweb/webclient/lib/angular-dialog-service-5.2.6/dialogs-default-translations.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-dialog-service - A service to handle common dialog types in a web application. Built on top of Angular-Bootstrap's modal 3 | * @version v5.2.5 4 | * @author Michael Conroy, michael.e.conroy@gmail.com 5 | * @license MIT, http://www.opensource.org/licenses/MIT 6 | */ 7 | angular.module("dialogs.default-translations",["pascalprecht.translate"]).config(["$translateProvider",function(n){n.translations("en-US",{DIALOGS_ERROR:"Error",DIALOGS_ERROR_MSG:"An unknown error has occurred.",DIALOGS_CLOSE:"Close",DIALOGS_PLEASE_WAIT:"Please Wait",DIALOGS_PLEASE_WAIT_ELIPS:"Please Wait...",DIALOGS_PLEASE_WAIT_MSG:"Waiting on operation to complete.",DIALOGS_PERCENT_COMPLETE:"% Complete",DIALOGS_NOTIFICATION:"Notification",DIALOGS_NOTIFICATION_MSG:"Unknown application notification.",DIALOGS_CONFIRMATION:"Confirmation",DIALOGS_CONFIRMATION_MSG:"Confirmation required.",DIALOGS_OK:"OK",DIALOGS_YES:"Yes",DIALOGS_NO:"No"}),n.preferredLanguage("en-US")}]); -------------------------------------------------------------------------------- /syweb/webclient/lib/angular-spinner.min.js: -------------------------------------------------------------------------------- 1 | !function(a){"use strict";function b(a,b){a.module("angularSpinner",[]).factory("usSpinnerService",["$rootScope",function(a){var b={};return b.spin=function(b){a.$broadcast("us-spinner:spin",b)},b.stop=function(b){a.$broadcast("us-spinner:stop",b)},b}]).directive("usSpinner",["$window",function(c){return{scope:!0,link:function(d,e,f){function g(){d.spinner&&d.spinner.stop()}var h=b||c.Spinner;d.spinner=null,d.key=a.isDefined(f.spinnerKey)?f.spinnerKey:!1,d.startActive=a.isDefined(f.spinnerStartActive)?f.spinnerStartActive:d.key?!1:!0,d.spin=function(){d.spinner&&d.spinner.spin(e[0])},d.stop=function(){d.startActive=!1,g()},d.$watch(f.usSpinner,function(a){g(),d.spinner=new h(a),(!d.key||d.startActive)&&d.spinner.spin(e[0])},!0),d.$on("us-spinner:spin",function(a,b){b===d.key&&d.spin()}),d.$on("us-spinner:stop",function(a,b){b===d.key&&d.stop()}),d.$on("$destroy",function(){d.stop(),d.spinner=null})}}}])}"function"==typeof define&&define.amd?define(["angular","spin"],b):b(a.angular)}(window); 2 | //# sourceMappingURL=angular-spinner.min.js.map -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2014 OpenMarket Ltd 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. 16 | 17 | import os 18 | from setuptools import setup, find_packages 19 | 20 | 21 | # Utility function to read the README file. 22 | # Used for the long_description. It's nice, because now 1) we have a top level 23 | # README file and 2) it's easier to type in the README file than to put a raw 24 | # string in below ... 25 | def read(fname): 26 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 27 | 28 | setup( 29 | name="matrix-angular-sdk", 30 | version=read("syweb/webclient/VERSION").strip(), 31 | packages=find_packages(exclude=["tests", "tests.*"]), 32 | description="Matrix Angular Sdk", 33 | include_package_data=True, 34 | zip_safe=False, 35 | long_description=read("README.rst"), 36 | entry_points="""""" 37 | ) 38 | -------------------------------------------------------------------------------- /syweb/webclient/lib/angular-spinner.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"angular-spinner.min.js","sources":["angular-spinner.js"],"names":["root","factory","angular","Spinner","module","$rootScope","config","spin","key","$broadcast","stop","directive","$window","scope","link","element","attr","stopSpinner","spinner","SpinnerConstructor","isDefined","spinnerKey","startActive","spinnerStartActive","$watch","usSpinner","options","$on","event","define","amd","window"],"mappings":"CAMC,SAAUA,GACV,YAEA,SAASC,GAAQC,EAASC,GAEzBD,EACEE,OAAO,qBAEPH,QAAQ,oBAAqB,aAAc,SAAUI,GACrD,GAAIC,KAUJ,OARAA,GAAOC,KAAO,SAAUC,GACvBH,EAAWI,WAAW,kBAAmBD,IAG1CF,EAAOI,KAAO,SAAUF,GACvBH,EAAWI,WAAW,kBAAmBD,IAGnCF,KAGPK,UAAU,aAAc,UAAW,SAAUC,GAC7C,OACCC,OAAO,EACPC,KAAM,SAAUD,EAAOE,EAASC,GAW/B,QAASC,KACJJ,EAAMK,SACTL,EAAMK,QAAQR,OAZhB,GAAIS,GAAqBhB,GAAWS,EAAQT,OAE5CU,GAAMK,QAAU,KAEhBL,EAAML,IAAMN,EAAQkB,UAAUJ,EAAKK,YAAcL,EAAKK,YAAa,EAEnER,EAAMS,YAAcpB,EAAQkB,UAAUJ,EAAKO,oBAC1CP,EAAKO,mBAAqBV,EAAML,KAChC,GAAQ,EAQTK,EAAMN,KAAO,WACRM,EAAMK,SACTL,EAAMK,QAAQX,KAAKQ,EAAQ,KAI7BF,EAAMH,KAAO,WACZG,EAAMS,aAAc,EACpBL,KAGDJ,EAAMW,OAAOR,EAAKS,UAAW,SAAUC,GACtCT,IACAJ,EAAMK,QAAU,GAAIC,GAAmBO,KAClCb,EAAML,KAAOK,EAAMS,cACvBT,EAAMK,QAAQX,KAAKQ,EAAQ,MAE1B,GAEHF,EAAMc,IAAI,kBAAmB,SAAUC,EAAOpB,GACzCA,IAAQK,EAAML,KACjBK,EAAMN,SAIRM,EAAMc,IAAI,kBAAmB,SAAUC,EAAOpB,GACzCA,IAAQK,EAAML,KACjBK,EAAMH,SAIRG,EAAMc,IAAI,WAAY,WACrBd,EAAMH,OACNG,EAAMK,QAAU,YAOA,kBAAXW,SAAyBA,OAAOC,IAE1CD,QAAQ,UAAW,QAAS5B,GAG5BA,EAAQD,EAAKE,UAEb6B"} -------------------------------------------------------------------------------- /syweb/webclient/app/components/matrix/version-service.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | angular.module('versionService', []) 20 | .service('versionService', ['$http', '$q', 21 | function ($http, $q) { 22 | var pendingVersionDefer = $q.defer(); 23 | var versionService = this; 24 | versionService.version = "unknown"; 25 | versionService.getVersion = function() { 26 | return pendingVersionDefer.promise; 27 | }; 28 | 29 | $http.get("VERSION").then(function(response) { 30 | console.log("Version %s", response.data); 31 | versionService.version = response.data; 32 | pendingVersionDefer.resolve(); 33 | }, 34 | function(err) { 35 | console.error("Failed to get web client version."); 36 | pendingVersionDefer.resolve(); 37 | }); 38 | 39 | }]); 40 | 41 | 42 | -------------------------------------------------------------------------------- /syweb/webclient/app/payment/payment.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | [matrix] 6 |
7 | 8 |

OpenMarket Matrix Gateway

9 | 10 |
11 | 12 | Link your PayPal account with {{purchase.user}} in order to send SMS. During beta testing, messages incur no cost (with a per-user fair usage limit), but you must link your account to PayPal as a security measure. Your account will never be charged without your consent. 13 |
14 | 15 | 16 | 22 |

23 | 24 |

25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /syweb/webclient/lib/angular-peity.js: -------------------------------------------------------------------------------- 1 | var angularPeity = angular.module( 'angular-peity', [] ); 2 | 3 | $.fn.peity.defaults.pie = { 4 | fill: ["#ff0000", "#aaaaaa"], 5 | radius: 4, 6 | } 7 | 8 | var buildChartDirective = function ( chartType ) { 9 | return { 10 | restrict: 'E', 11 | scope: { 12 | data: "=", 13 | options: "=" 14 | }, 15 | link: function ( scope, element, attrs ) { 16 | 17 | var options = {}; 18 | if ( scope.options ) { 19 | options = scope.options; 20 | } 21 | 22 | // N.B. live-binding to data by Matthew 23 | scope.$watch('data', function () { 24 | var span = document.createElement( 'span' ); 25 | span.textContent = scope.data.join(); 26 | 27 | if ( !attrs.class ) { 28 | span.className = ""; 29 | } else { 30 | span.className = attrs.class; 31 | } 32 | 33 | if (element[0].nodeType === 8) { 34 | element.replaceWith( span ); 35 | } 36 | else if (element[0].firstChild) { 37 | element.empty(); 38 | element[0].appendChild( span ); 39 | } 40 | else { 41 | element[0].appendChild( span ); 42 | } 43 | 44 | jQuery( span ).peity( chartType, options ); 45 | }); 46 | } 47 | }; 48 | }; 49 | 50 | 51 | angularPeity.directive( 'pieChart', function () { 52 | 53 | return buildChartDirective( "pie" ); 54 | 55 | } ); 56 | 57 | 58 | angularPeity.directive( 'barChart', function () { 59 | 60 | return buildChartDirective( "bar" ); 61 | 62 | } ); 63 | 64 | 65 | angularPeity.directive( 'lineChart', function () { 66 | 67 | return buildChartDirective( "line" ); 68 | 69 | } ); 70 | -------------------------------------------------------------------------------- /syweb/webclient/test/unit/typing-service.spec.js: -------------------------------------------------------------------------------- 1 | describe('TypingService', function() { 2 | var $interval, $q, $rootScope; 3 | 4 | var matrixService = { 5 | setTyping: function(){} 6 | }; 7 | 8 | beforeEach(function() { 9 | module(function ($provide) { 10 | $provide.value('matrixService', matrixService); 11 | }); 12 | module('typingService'); 13 | }); 14 | 15 | beforeEach(inject(function(_$interval_, _$q_, _$rootScope_) { 16 | $interval = _$interval_; 17 | $q = _$q_; 18 | $rootScope = _$rootScope_; 19 | })); 20 | 21 | it('should be able to start sending typing notifications.', inject( 22 | function(typingService) { 23 | var roomId = "!foo:bar"; 24 | var defer = $q.defer(); 25 | spyOn(matrixService, "setTyping").and.returnValue(defer.promise); 26 | 27 | typingService.setTyping(roomId, true); 28 | expect(matrixService.setTyping).toHaveBeenCalledWith(roomId, true, typingService.SERVER_SPECIFIED_TIMEOUT_MS); 29 | })); 30 | 31 | it('should be able to explicitly stop sending typing notifications.', inject( 32 | function(typingService) { 33 | var roomId = "!foo:bar"; 34 | var defer = $q.defer(); 35 | spyOn(matrixService, "setTyping").and.returnValue(defer.promise); 36 | 37 | typingService.setTyping(roomId, true); 38 | defer.resolve({}); 39 | $rootScope.$digest(); 40 | 41 | typingService.setTyping(roomId, false); 42 | expect(matrixService.setTyping).toHaveBeenCalledWith(roomId, false); 43 | })); 44 | 45 | it('should timeout and send a stop typing notification if it expires.', inject( 46 | function(typingService) { 47 | var roomId = "!foo:bar"; 48 | var defer = $q.defer(); 49 | spyOn(matrixService, "setTyping").and.returnValue(defer.promise); 50 | 51 | typingService.setTyping(roomId, true); 52 | defer.resolve({}); 53 | $rootScope.$digest(); 54 | 55 | $interval.flush(typingService.USER_TIMEOUT_MS); // annoyingly this flushes ALL THE TIMEOUTS :( 56 | 57 | expect(matrixService.setTyping).toHaveBeenCalledWith(roomId, false); 58 | })); 59 | }); 60 | -------------------------------------------------------------------------------- /syweb/webclient/lib/ng-infinite-scroll-matrix.js: -------------------------------------------------------------------------------- 1 | /* ng-infinite-scroll - v1.0.0 - 2013-02-23 2 | Matrix: Modified to support scrolling UP to get infinite loading and to listen 3 | to scroll events on the PARENT element, not the window. 4 | */ 5 | var mod; 6 | 7 | mod = angular.module('infinite-scroll', []); 8 | 9 | mod.directive('infiniteScroll', [ 10 | '$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) { 11 | return { 12 | link: function(scope, elem, attrs) { 13 | var checkWhenEnabled, handler, scrollDistance, scrollEnabled; 14 | $window = angular.element($window); 15 | scrollDistance = 0; 16 | if (attrs.infiniteScrollDistance != null) { 17 | scope.$watch(attrs.infiniteScrollDistance, function(value) { 18 | return scrollDistance = parseInt(value, 10); 19 | }); 20 | } 21 | scrollEnabled = true; 22 | checkWhenEnabled = false; 23 | if (attrs.infiniteScrollDisabled != null) { 24 | scope.$watch(attrs.infiniteScrollDisabled, function(value) { 25 | scrollEnabled = !value; 26 | if (scrollEnabled && checkWhenEnabled) { 27 | checkWhenEnabled = false; 28 | return handler(); 29 | } 30 | }); 31 | } 32 | handler = function() { 33 | var elementTop, remaining, shouldScroll, windowTop; 34 | windowTop = 0; 35 | elementTop = elem.offset().top; 36 | shouldScroll = elementTop >= 0; // top of list is at the top of the window or further down the page 37 | if (shouldScroll && scrollEnabled) { 38 | if ($rootScope.$$phase) { 39 | return scope.$eval(attrs.infiniteScroll); 40 | } else { 41 | return scope.$apply(attrs.infiniteScroll); 42 | } 43 | } else if (shouldScroll) { 44 | return checkWhenEnabled = true; 45 | } 46 | }; 47 | elem.parent().on('scroll', handler); 48 | scope.$on('$destroy', function() { 49 | return elem.parent().off('scroll', handler); 50 | }); 51 | return $timeout((function() { 52 | if (attrs.infiniteScrollImmediateCheck) { 53 | if (scope.$eval(attrs.infiniteScrollImmediateCheck)) { 54 | return handler(); 55 | } 56 | } else { 57 | return handler(); 58 | } 59 | }), 0); 60 | } 61 | }; 62 | } 63 | ]); 64 | -------------------------------------------------------------------------------- /syweb/webclient/app/user/user-controller.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | angular.module('UserController', ['matrixService']) 20 | .controller('UserController', ['$scope', '$rootScope', '$routeParams', 'matrixService', 'dialogService', 'eventHandlerService', 21 | function($scope, $rootScope, $routeParams, matrixService, dialogService, eventHandlerService) { 22 | $scope.httpUri = matrixService.getHttpUriForMxc; 23 | $scope.onInit = function() { 24 | $scope.user = { 25 | id: $routeParams.user_matrix_id, 26 | displayname: "", 27 | avatar_url: undefined 28 | }; 29 | 30 | $scope.user_id = matrixService.config().user_id; 31 | 32 | matrixService.getDisplayName($scope.user.id).then( 33 | function(response) { 34 | $scope.user.displayname = response.data.displayname; 35 | } 36 | ); 37 | 38 | matrixService.getProfilePictureUrl($scope.user.id).then( 39 | function(response) { 40 | $scope.user.avatar_url = response.data.avatar_url; 41 | } 42 | ); 43 | }; 44 | 45 | $scope.messageUser = function() { 46 | // FIXME: create a new room every time, for now 47 | dialogService.showProgress($scope.user.id, "Sending invite...", 100); 48 | eventHandlerService.createRoom(undefined, "private", [$scope.user.id]).then( 49 | function(room_id) { 50 | $rootScope.$broadcast('dialogs.wait.complete'); 51 | $scope.feedback = "Invite sent successfully"; 52 | $rootScope.goToPage("/room/" + room_id); 53 | }, 54 | function(error) { 55 | $rootScope.$broadcast('dialogs.wait.complete'); 56 | dialogService.showError(error); 57 | }); 58 | }; 59 | 60 | }]); 61 | -------------------------------------------------------------------------------- /syweb/webclient/app/components/fileInput/file-input-directive.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /* 20 | * Transform an element into an image file input button. 21 | * Watch to the passed variable change. It will contain the selected HTML5 file object. 22 | */ 23 | angular.module('mFileInput', []) 24 | .directive('mFileInput', function() { 25 | return { 26 | restrict: 'A', 27 | transclude: 'true', 28 | // FIXME: add back in accept="image/*" when needed - e.g. for avatars 29 | template: '
', 30 | scope: { 31 | selectedFile: '=mFileInput' 32 | }, 33 | 34 | link: function(scope, element, attrs, ctrl) { 35 | 36 | // Check if HTML5 file selection is supported 37 | if (window.FileList) { 38 | element.on("click", function() { 39 | element.find("input")[0].click(); 40 | element.find("input").on("change", function(e) { 41 | scope.selectedFile = this.files[0]; 42 | scope.$apply(); 43 | // clear the input so on.change fires again if they choose the same file... 44 | element.find("input").val(""); 45 | }); 46 | }); 47 | scope.$on('$destroy', function() { 48 | element.off("click"); 49 | element.find("input").off("change"); 50 | }); 51 | } 52 | else { 53 | setTimeout(function() { 54 | element.attr("disabled", true); 55 | element.attr("title", "The app uses the HTML5 File API to send files. Your browser does not support it."); 56 | }, 1); 57 | } 58 | 59 | // Change the mouse icon on mouseover on this element 60 | element.css("cursor", "pointer"); 61 | } 62 | }; 63 | }); 64 | -------------------------------------------------------------------------------- /syweb/webclient/app/login/login.html: -------------------------------------------------------------------------------- 1 | 48 | 49 | -------------------------------------------------------------------------------- /syweb/webclient/app/mobile.css: -------------------------------------------------------------------------------- 1 | /*** Mobile voodoo ***/ 2 | 3 | /** iPads **/ 4 | @media all and (max-device-width: 768px) { 5 | #roomRecentsTableWrapper, 6 | #sidebar-resizer { 7 | display: none; 8 | } 9 | } 10 | 11 | /** iPhones **/ 12 | @media all and (max-device-width: 640px) { 13 | 14 | #messageTableWrapper { 15 | margin-right: 0px ! important; 16 | } 17 | 18 | .leftBlock { 19 | width: 8em ! important; 20 | font-size: 8px ! important; 21 | } 22 | 23 | .rightBlock { 24 | width: 0px ! important; 25 | display: none ! important; 26 | } 27 | 28 | .avatar { 29 | width: 36px ! important; 30 | } 31 | 32 | #header, .goodConnection { 33 | background-color: transparent; 34 | } 35 | 36 | #headerContent { 37 | padding-right: 5px; 38 | height: auto; 39 | line-height: normal; 40 | } 41 | 42 | #headerContent button { 43 | font-size: 8px; 44 | } 45 | 46 | #messageTable, 47 | #wrapper, 48 | #controls { 49 | max-width: 640px ! important; 50 | } 51 | 52 | #controls { 53 | padding: 0px; 54 | } 55 | 56 | #headerUserId, 57 | #roomHeader img, 58 | #userIdCell, 59 | #roomRecentsTableWrapper, 60 | #usersTableWrapper, 61 | #controlButtons, 62 | #sidebar-resizer, 63 | #callbar, 64 | .extraControls { 65 | display: none; 66 | } 67 | 68 | #buttonsCell { 69 | width: 60px ! important; 70 | padding-left: 20px ! important; 71 | } 72 | 73 | #roomLogo { 74 | display: none; 75 | } 76 | 77 | .bubble { 78 | font-size: 12px ! important; 79 | min-height: 20px ! important; 80 | } 81 | 82 | #roomHeader { 83 | padding-top: 20px; 84 | } 85 | 86 | .roomHeaderInfo { 87 | margin-right: 0px; 88 | } 89 | 90 | #roomName { 91 | font-size: 12px ! important; 92 | margin-top: 0px ! important; 93 | } 94 | 95 | .roomTopicSection { 96 | display: none; 97 | } 98 | 99 | #roomPage { 100 | top: 40px ! important; 101 | left: 5px ! important; 102 | right: 5px ! important; 103 | bottom: 40px ! important; 104 | } 105 | 106 | #controlPanel { 107 | height: 31px; 108 | } 109 | 110 | /* stop zoom on select */ 111 | select:focus, 112 | textarea, 113 | input 114 | { 115 | font-size: 16px ! important; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /syweb/webclient/app/payment/payment-controller.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | angular.module('PaymentController', []) 20 | .controller('PaymentController', 21 | ['$scope', '$sce', '$location', '$routeParams', 'matrixService', 'dialogService', 'paymentService', 22 | function($scope, $sce, $location, $routeParams, matrixService, dialogService, paymentService) { 23 | if (!paymentService.hasAcceptedEula()) { 24 | console.error("Has not accepted EULA"); 25 | $location.url("/"); 26 | return; 27 | } 28 | 29 | $scope.purchase = { 30 | user: matrixService.config().user_id, 31 | amount: 5.00, 32 | url: $sce.trustAsResourceUrl(webClientConfig.paymentUrl), 33 | submitted: false 34 | }; 35 | 36 | if ($routeParams.payment_state) { 37 | $scope.status = {}; 38 | // the user has been redirected back here after payment 39 | if ($routeParams.payment_state === "success") { 40 | $scope.status.title = "Success"; 41 | $scope.status.description = "You have successfully been logged in."; 42 | } 43 | else if ($routeParams.payment_state === "fail") { 44 | $scope.status.title = "Failed"; 45 | $scope.status.description = "Your have failed to log in."; 46 | } 47 | else if ($routeParams.payment_state === "cancel") { 48 | $scope.status.title = "Cancelled"; 49 | $scope.status.description = "Your payment was cancelled."; 50 | } 51 | else { 52 | console.error("Unknown payment state"); 53 | $location.url("/"); 54 | } 55 | } 56 | 57 | $scope.onSubmit = function($event) { 58 | if($scope.purchase.amount <= 0) { 59 | dialogService.showError("Must have a positive amount."); 60 | $event.preventDefault(); 61 | return; 62 | } 63 | // prevent multiple clicks 64 | if ($scope.purchase.submitted) { 65 | $event.preventDefault(); 66 | return; 67 | } 68 | $scope.purchase.submitted = true; 69 | }; 70 | 71 | $scope.onInit = function() { 72 | if (!$scope.purchase.url) { 73 | console.error("No configured payment URL!"); 74 | $location.url("/"); 75 | } 76 | }; 77 | }]); -------------------------------------------------------------------------------- /syweb/webclient/test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Sep 18 2014 14:25:57 GMT+0100 (BST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | // XXX: Order is important; doing /js/angular* makes the tests not run, 18 | // hence angular.js THEN angular-*.js. Also, this is also why we import 19 | // everything but app*, then app.js, then app*.js else you get 20 | // "Module 'matrixWebClient' is not available!" errors. 21 | files: [ 22 | '../lib/jquery*', 23 | '../lib/angular.js', 24 | '../lib/angular-*.js', 25 | '../lib/jquery.peity.min.js', 26 | '../lib/ng-infinite-scroll-matrix.js', 27 | '../lib/ui-bootstrap*', 28 | '../lib/elastic.js', 29 | '../lib/angular-dialog-service-5.2.6/dialogs.min.js', 30 | '../lib/matrix.js', // until matrix-js-sdk has its own tests 31 | '../app/**/!(app*).js', 32 | '../app/app.js', 33 | '../app/app*', 34 | './unit/**/*.js' 35 | ], 36 | 37 | plugins: [ 38 | 'karma-*', 39 | ], 40 | 41 | 42 | // list of files to exclude 43 | exclude: [ 44 | ], 45 | 46 | 47 | // preprocess matching files before serving them to the browser 48 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 49 | preprocessors: { 50 | '../app/**/*.js': 'coverage' 51 | }, 52 | 53 | 54 | // test results reporter to use 55 | // possible values: 'dots', 'progress' 56 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 57 | reporters: ['progress', 'junit', 'coverage'], 58 | junitReporter: { 59 | outputFile: 'test-results.xml', 60 | suite: '' 61 | }, 62 | 63 | coverageReporter: { 64 | type: 'cobertura', 65 | dir: 'coverage/', 66 | file: 'coverage.xml' 67 | }, 68 | 69 | // web server port 70 | port: 9876, 71 | 72 | 73 | // enable / disable colors in the output (reporters and logs) 74 | colors: true, 75 | 76 | 77 | // level of logging 78 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 79 | logLevel: config.LOG_DEBUG, 80 | 81 | 82 | // enable / disable watching file and executing tests whenever any file changes 83 | autoWatch: true, 84 | 85 | 86 | // start these browsers 87 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 88 | browsers: ['PhantomJS'], 89 | 90 | 91 | // Continuous Integration mode 92 | // if true, Karma captures browsers, runs the tests and exits 93 | singleRun: true 94 | }); 95 | }; 96 | -------------------------------------------------------------------------------- /syweb/webclient/test/unit/reset-password-controller.spec.js: -------------------------------------------------------------------------------- 1 | describe("ResetPasswordController ", function() { 2 | var rootScope, scope, ctrl, $q, $timeout; 3 | var testSid; 4 | 5 | // mock services 6 | var matrixService = { 7 | config: function() { 8 | return { 9 | user_id: userId 10 | } 11 | }, 12 | setConfig: function(){}, 13 | saveConfig: function(){}, 14 | linkEmail: function(){ 15 | var d = $q.defer(); 16 | d.resolve({ 17 | data: {sid: testSid} 18 | }); 19 | return d.promise; 20 | }, 21 | setPassword: function(newPassword, authDict) { 22 | var d = $q.defer(); 23 | d.resolve({ 24 | data: {} 25 | }); 26 | return d.promise; 27 | } 28 | }; 29 | 30 | var eventStreamService = { 31 | resume: function(){} 32 | }; 33 | 34 | var dialogService = { 35 | showError: function(err){} 36 | }; 37 | 38 | beforeEach(function() { 39 | module('ResetPasswordController'); 40 | }); 41 | 42 | beforeEach(inject(function($rootScope, $injector, $location, $controller, _$q_, _$timeout_) { 43 | $q = _$q_; 44 | $timeout = _$timeout_; 45 | scope = $rootScope.$new(); 46 | rootScope = $rootScope; 47 | ctrl = $controller('ResetPasswordController', { 48 | '$scope': scope, 49 | '$rootScope': $rootScope, 50 | '$location': $location, 51 | 'matrixService': matrixService, 52 | 'eventStreamService': eventStreamService, 53 | 'dialogService': dialogService 54 | }); 55 | })); 56 | 57 | it('should be able to trigger password reset via email auth.', function() { 58 | var prevFeedback = angular.copy(scope.feedback); 59 | spyOn(matrixService, "saveConfig"); 60 | spyOn(matrixService, "linkEmail").and.callThrough(); 61 | spyOn(matrixService, "setPassword").and.callThrough(); 62 | 63 | testSid = 'thetestsid'; 64 | 65 | scope.account.pwd1 = "password"; 66 | scope.account.pwd2 = "password"; 67 | scope.account.email = "me@example.com"; 68 | scope.account.desired_user_id = "bob"; 69 | scope.account.homeserver = "http://example.com"; 70 | scope.account.identityServer = "http://example.com"; 71 | scope.reset_password(); 72 | rootScope.$digest(); 73 | 74 | expect(matrixService.linkEmail).toHaveBeenCalledWith("me@example.com", scope.clientSecret, 1); 75 | 76 | scope.verifyToken(); 77 | 78 | expect(matrixService.setPassword).toHaveBeenCalledWith("password", { 79 | type: 'm.login.email.identity', 80 | threepid_creds: jasmine.objectContaining({ 81 | sid: 'thetestsid', 82 | id_server: 'example.com' 83 | }) 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /syweb/webclient/test/unit/recents-controller.spec.js: -------------------------------------------------------------------------------- 1 | describe("RecentsController ", function() { 2 | var rootScope, scope, ctrl, $q; 3 | 4 | // test vars 5 | var testInitialSyncDefer, testSelectedRoomId, testRooms; 6 | 7 | 8 | var modelService = { 9 | getRooms: function(){ 10 | return testRooms; 11 | } 12 | }; 13 | 14 | var recentsService = { 15 | getSelectedRoomId: function() { 16 | return testSelectedRoomId; 17 | }, 18 | getUnreadMessages: function() { 19 | 20 | }, 21 | getUnreadBingMessages: function() { 22 | 23 | }, 24 | markAsRead: function(roomId) {}, 25 | 26 | BROADCAST_UNREAD_MESSAGES: "BROADCAST_UNREAD_MESSAGES", 27 | BROADCAST_SELECTED_ROOM_ID: "BROADCAST_SELECTED_ROOM_ID", 28 | BROADCAST_UNREAD_BING_MESSAGES: "BROADCAST_UNREAD_BING_MESSAGES" 29 | 30 | }; 31 | 32 | var eventHandlerService = { 33 | waitForInitialSyncCompletion: function(){ 34 | return testInitialSyncDefer.promise; 35 | } 36 | }; 37 | 38 | 39 | beforeEach(function() { 40 | module('matrixWebClient'); 41 | }); 42 | 43 | beforeEach(inject(function($rootScope, $injector, $controller, _$q_) { 44 | $q = _$q_; 45 | scope = $rootScope.$new(); 46 | rootScope = $rootScope; 47 | rootScope.goToPage = function(){} 48 | 49 | // reset test vars 50 | testInitialSyncDefer = $q.defer(); 51 | testSelectedRoomId = undefined; 52 | testRooms = { 53 | "!aaa:localhost": {}, 54 | "!bbb:localhost": {} 55 | }; 56 | 57 | ctrl = $controller('RecentsController', { 58 | '$scope': scope, 59 | 'modelService': modelService, 60 | 'recentsService': recentsService, 61 | 'eventHandlerService': eventHandlerService 62 | }); 63 | }) 64 | ); 65 | 66 | it('should flag when initialSync is complete so the spinner can be dismissed.', function() { 67 | expect(scope.doneInitialSync).toBe(false); 68 | testInitialSyncDefer.resolve(""); 69 | scope.$digest(); 70 | expect(scope.doneInitialSync).toBe(true); 71 | }); 72 | 73 | it('should track the selected room ID so it can be highlighted correctly.', function() { 74 | expect(scope.recentsSelectedRoomID).toEqual(testSelectedRoomId); 75 | var roomId = "!aaa:localhost"; 76 | scope.$broadcast(recentsService.BROADCAST_SELECTED_ROOM_ID, roomId); 77 | expect(scope.recentsSelectedRoomID).toEqual(roomId); 78 | }); 79 | 80 | it('should mark a room as read when it is selected.', function() { 81 | spyOn(recentsService, "markAsRead"); 82 | expect(recentsService.markAsRead).not.toHaveBeenCalled(); 83 | var roomId = "!bbb:localhost"; 84 | scope.selectRoom({}, { 85 | room_id: roomId 86 | }); 87 | expect(recentsService.markAsRead).toHaveBeenCalledWith(roomId); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /syweb/webclient/app/recents/recents-controller.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | angular.module('RecentsController', ['matrixService', 'matrixFilter']) 20 | .controller('RecentsController', ['$rootScope', '$scope', 'modelService', 'recentsService', 'eventHandlerService', 'dialogService', '$window', 21 | function($rootScope, $scope, modelService, recentsService, eventHandlerService, dialogService, $window) { 22 | 23 | $scope.doneInitialSync = false; 24 | eventHandlerService.waitForInitialSyncCompletion().then(function() { 25 | $scope.doneInitialSync = true; 26 | }); 27 | 28 | // Expose the service to the view 29 | $scope.modelService = modelService; 30 | 31 | // retrieve all rooms and expose them 32 | $scope.rooms = modelService.getRooms(); 33 | 34 | $scope.$on("$destroy", function() { 35 | $scope.rooms = null; 36 | }); 37 | 38 | // track the selected room ID: the html will use this 39 | $scope.recentsSelectedRoomID = recentsService.getSelectedRoomId(); 40 | $scope.$on(recentsService.BROADCAST_SELECTED_ROOM_ID, function(ngEvent, room_id) { 41 | $scope.recentsSelectedRoomID = room_id; 42 | }); 43 | 44 | // track the list of unread messages: the html will use this 45 | $scope.unreadMessages = recentsService.getUnreadMessages(); 46 | $scope.$on(recentsService.BROADCAST_UNREAD_MESSAGES, function(ngEvent, room_id, unreadCount) { 47 | $scope.unreadMessages = recentsService.getUnreadMessages(); 48 | }); 49 | 50 | // track the list of unread BING messages: the html will use this 51 | $scope.unreadBings = recentsService.getUnreadBingMessages(); 52 | $scope.$on(recentsService.BROADCAST_UNREAD_BING_MESSAGES, function(ngEvent, room_id, event) { 53 | $scope.unreadBings = recentsService.getUnreadBingMessages(); 54 | }); 55 | 56 | $scope.selectRoom = function($event, room) { 57 | recentsService.markAsRead(room.room_id); 58 | var url = encodeURIComponent("room/"+(room.room_alias ? room.room_alias : room.room_id)); 59 | if ($event.ctrlKey) { 60 | $window.open("#/" + url); 61 | } 62 | else { 63 | $rootScope.goToPage(url); 64 | } 65 | }; 66 | 67 | $scope.leave = function(roomId) { 68 | eventHandlerService.leaveRoom(roomId).then(function(response) { 69 | // refresh room list 70 | $scope.rooms = modelService.getRooms(); 71 | }, 72 | function(error) { 73 | dialogService.showError(error); 74 | }); 75 | }; 76 | 77 | }]); 78 | 79 | -------------------------------------------------------------------------------- /syweb/webclient/app/login/reset-password.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | [matrix] 5 |
6 | 7 |
8 |
9 | Reset Password:
10 |
11 |
12 | 13 |
14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 | 22 |

23 |
24 | 25 | 26 | 27 |
28 | 29 | 30 |
Your home server stores all your conversation and account data.
31 | 32 | 33 |
Matrix provides identity servers to track which emails etc. belong to which Matrix IDs.
34 | Only http://matrix.org currently exists.
35 |
36 |
37 |
38 |
39 | An email has been sent to {{ account.email }}. Once you've followed the link it contains either continue registration there or click below
40 | 41 |
42 |
43 |
44 |
45 |

Your password has been reset.

46 |

You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, re-log in on each device.

47 |

Please Log In

48 |
49 |
50 |
51 | 52 |
53 |
54 | 55 | -------------------------------------------------------------------------------- /syweb/webclient/app/recents/recents-filter.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | angular.module('RecentsFilter', []) 20 | .filter('orderRecents', ["matrixService", "modelService", "paymentService", function(matrixService, modelService, paymentService) { 21 | return function(rooms) { 22 | var user_id = matrixService.config().user_id; 23 | 24 | // Transform the dict into an array 25 | // The key, room_id, is already in value objects 26 | var filtered = []; 27 | angular.forEach(rooms, function(room, room_id) { 28 | if (paymentService.isAccountRoom(room)) { 29 | return; 30 | } 31 | 32 | room.recent = {}; 33 | var meEvent = room.current_room_state.state("m.room.member", user_id); 34 | // Show the room only if the user has joined it or has been invited 35 | // (ie, do not show it if he has been banned) 36 | var member = modelService.getMember(room_id, user_id); 37 | if (member) { 38 | member = member.event; 39 | } 40 | room.recent.me = member; 41 | if (member && ("invite" === member.content.membership || "join" === member.content.membership)) { 42 | if ("invite" === member.content.membership) { 43 | room.recent.inviter = member.user_id; 44 | } 45 | // Count users here 46 | // TODO: Compute it directly in modelService 47 | room.recent.numUsersInRoom = modelService.getUserCountInRoom(room_id); 48 | 49 | filtered.push(room); 50 | } 51 | else if (meEvent && "invite" === meEvent.content.membership) { 52 | // The only information we have about the room is that the user has been invited 53 | filtered.push(room); 54 | } 55 | }); 56 | 57 | // And time sort them 58 | // The room with the latest message at first 59 | filtered.sort(function (roomA, roomB) { 60 | 61 | var lastMsgRoomA = roomA.lastAnnotatedEvent; 62 | var lastMsgRoomB = roomB.lastAnnotatedEvent; 63 | 64 | // Invite message does not have a body message nor ts 65 | // Puth them at the top of the list 66 | if (undefined === lastMsgRoomA) { 67 | return -1; 68 | } 69 | else if (undefined === lastMsgRoomB) { 70 | return 1; 71 | } 72 | else { 73 | return lastMsgRoomB.event.origin_server_ts - lastMsgRoomA.event.origin_server_ts; 74 | } 75 | }); 76 | return filtered; 77 | }; 78 | }]); 79 | -------------------------------------------------------------------------------- /syweb/webclient/lib/jquery.peity.min.js: -------------------------------------------------------------------------------- 1 | // Peity jQuery plugin version 3.0.2 2 | // (c) 2014 Ben Pickles 3 | // 4 | // http://benpickles.github.io/peity 5 | // 6 | // Released under MIT license. 7 | (function(h,w,i,v){var p=function(a,b){var d=w.createElementNS("http://www.w3.org/2000/svg",a);h(d).attr(b);return d},y="createElementNS"in w&&p("svg",{}).createSVGRect,e=h.fn.peity=function(a,b){y&&this.each(function(){var d=h(this),c=d.data("peity");c?(a&&(c.type=a),h.extend(c.opts,b)):(c=new x(d,a,h.extend({},e.defaults[a],b)),d.change(function(){c.draw()}).data("peity",c));c.draw()});return this},x=function(a,b,d){this.$el=a;this.type=b;this.opts=d},r=x.prototype;r.draw=function(){e.graphers[this.type].call(this, 8 | this.opts)};r.fill=function(){var a=this.opts.fill;return h.isFunction(a)?a:function(b,d){return a[d%a.length]}};r.prepare=function(a,b){this.svg||this.$el.hide().after(this.svg=p("svg",{"class":"peity"}));return h(this.svg).empty().data("peity",this).attr({height:b,width:a})};r.values=function(){return h.map(this.$el.text().split(this.opts.delimiter),function(a){return parseFloat(a)})};e.defaults={};e.graphers={};e.register=function(a,b,d){this.defaults[a]=b;this.graphers[a]=d};e.register("pie", 9 | {fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(a){if(!a.delimiter){var b=this.$el.text().match(/[^0-9\.]/);a.delimiter=b?b[0]:","}b=this.values();if("/"==a.delimiter)var d=b[0],b=[d,i.max(0,b[1]-d)];for(var c=0,d=b.length,n=0;cj?m=q(i.min(d,0)):o=q(i.max(c,0)):u=1;u=o-m;0==u&&(u=1,0 0) { 70 | forEach(this, function(el) { 71 | listener(el, newValue); 72 | }); 73 | } 74 | return res; 75 | } 76 | } 77 | 78 | function addGlobalEventListener(eventName, listener) { 79 | // Use a capturing event listener so that 80 | // we also get the event when it's stopped! 81 | // Also, the blur event does not bubble. 82 | rootElement.addEventListener(eventName, onEvent, true); 83 | 84 | function onEvent(event) { 85 | var target = event.target; 86 | listener(target); 87 | } 88 | } 89 | 90 | function findParentForm(el) { 91 | while (el) { 92 | if (el.nodeName === 'FORM') { 93 | return $(el); 94 | } 95 | el = el.parentNode; 96 | } 97 | return $(); 98 | } 99 | 100 | function forEach(arr, listener) { 101 | if (arr.forEach) { 102 | return arr.forEach(listener); 103 | } 104 | var i; 105 | for (i=0; i 2 |
3 | 4 | [matrix] 5 |
6 | 7 |
8 | 9 |
10 | Create account:
11 | 12 |
13 |
14 |
15 | 16 |
Use this account when people search for my email address on Matrix

17 | 18 |
19 | 20 |
21 | 22 |
23 | 24 |
25 |
26 | 27 | 28 |
29 | 30 |

31 |
32 | 33 | 34 | 35 |
36 | 37 | 38 |
Your home server stores all your conversation and account data.
39 | 40 | 41 |
Matrix provides identity servers to track which emails etc. belong to which Matrix IDs.
42 | Only https://matrix.org currently exists.
43 |
44 |
45 |
46 |

This Home Server only allows humans to create accounts.

47 |

Please verify that you're not a robot.

48 |
49 | 50 |
51 |
52 |
53 | An email has been sent to {{ account.email }}. Once you've followed the link it contains either continue registration there or click below
54 | 55 |
56 |
57 | 58 | 59 |
60 |
61 | 62 | -------------------------------------------------------------------------------- /syweb/webclient/app/components/matrix/presence-service.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /* 20 | * This service tracks user activity on the page to determine his presence state. 21 | * Any state change will be sent to the Home Server. 22 | */ 23 | angular.module('mPresence', []) 24 | .service('mPresence', ['$document', 'matrixService', '$interval', 25 | function ($document, matrixService, $interval) { 26 | 27 | // Time in ms after that a user is considered as unavailable/away 28 | var UNAVAILABLE_TIME = 3 * 60000; // 3 mins 29 | 30 | // The current presence state 31 | var state = undefined; 32 | 33 | var self = this; 34 | var timer; 35 | 36 | this.UNAVAILABLE_TIME = UNAVAILABLE_TIME; // Expose for unit testing 37 | 38 | /** 39 | * Start listening the user activity to evaluate his presence state. 40 | * Any state change will be sent to the Home Server. 41 | */ 42 | this.start = function() { 43 | if (undefined === state) { 44 | // The user is online if he moves the mouser or press a key 45 | $document[0].onmousemove = resetTimer; 46 | $document[0].onkeypress = resetTimer; 47 | 48 | resetTimer(); 49 | } 50 | }; 51 | 52 | /** 53 | * Stop tracking user activity 54 | */ 55 | this.stop = function() { 56 | if (timer) { 57 | $interval.cancel(timer); 58 | timer = undefined; 59 | } 60 | state = undefined; 61 | }; 62 | 63 | /** 64 | * Get the current presence state. 65 | * @returns {matrixService.presence} the presence state 66 | */ 67 | this.getState = function() { 68 | return state; 69 | }; 70 | 71 | /** 72 | * Set the presence state. 73 | * If the state has changed, the Home Server will be notified. 74 | * @param {matrixService.presence} newState the new presence state 75 | */ 76 | this.setState = function(newState) { 77 | if (newState !== state) { 78 | console.log("mPresence - New state: " + newState); 79 | 80 | state = newState; 81 | 82 | // Inform the HS on the new user state 83 | matrixService.setUserPresence(state).then( 84 | function() { 85 | 86 | }, 87 | function(error) { 88 | console.log("mPresence - Failed to send new presence state: " + JSON.stringify(error)); 89 | }); 90 | } 91 | }; 92 | 93 | /** 94 | * Callback called when the user made no action on the page for UNAVAILABLE_TIME ms. 95 | * @private 96 | */ 97 | function onUnvailableTimerFire() { 98 | self.setState(matrixService.presence.unavailable); 99 | } 100 | 101 | /** 102 | * Callback called when the user made an action on the page 103 | * @private 104 | */ 105 | function resetTimer() { 106 | // User is still here 107 | self.setState(matrixService.presence.online); 108 | 109 | // Re-arm the timer 110 | $interval.cancel(timer); 111 | timer = $interval(onUnvailableTimerFire, UNAVAILABLE_TIME, 1); 112 | } 113 | 114 | }]); 115 | 116 | 117 | -------------------------------------------------------------------------------- /syweb/webclient/app/components/dialogs/dialog-service.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /* 20 | This service contains logic for showing error, notification and confirmation 21 | dialogs. 22 | */ 23 | angular.module('dialogService', []) 24 | .factory('dialogService', ['$q', 'dialogs', function($q, dialogs) { 25 | 26 | // create a rejected promise with the given message 27 | var showDialog = function(kind, title, body) { 28 | var dialog; 29 | title = escapeHtml(title); 30 | body = escapeHtml(body); 31 | 32 | if (kind === "error") { 33 | dialog = dialogs.error(title, body); 34 | } 35 | else if (kind === "success") { 36 | dialog = dialogs.notify(title, body); 37 | } 38 | else { 39 | console.error("Unknown kind of dialog: " + kind); 40 | } 41 | 42 | if (dialog) { 43 | return dialog.result; 44 | } 45 | else { 46 | var defer = $q.defer(); 47 | defer.reject("Unable to display dialog"); 48 | return defer.promise; 49 | } 50 | }; 51 | 52 | var escapeHtml = function(str) { 53 | var div = document.createElement('div'); 54 | div.appendChild(document.createTextNode(str)); 55 | return div.innerHTML; 56 | }; 57 | 58 | return { 59 | 60 | showConfirm: function(title, someHtml) { 61 | var d = dialogs.confirm(title, someHtml); 62 | return d.result; 63 | }, 64 | 65 | showProgress: function(title, body, progress) { 66 | var d = dialogs.wait(escapeHtml(title), escapeHtml(body), progress); 67 | return d.result; 68 | }, 69 | 70 | showSuccess: function(title, body) { 71 | return showDialog("success", title, body); 72 | }, 73 | 74 | showMatrixError: function(title, error) { 75 | return showDialog("error", title, 76 | error.error + " ("+error.errcode+")"); 77 | }, 78 | 79 | showError: function(error) { 80 | if (typeof(error) === "string") { 81 | return showDialog("error", "Error", error); 82 | } 83 | else if (error.data && error.data.errcode && error.data.error) { 84 | error = error.data; 85 | return this.showMatrixError("Error", error); 86 | } 87 | else if (error.errcode && error.error) { 88 | return this.showMatrixError("Error", error); 89 | } 90 | else if (error.status === 0) { 91 | return showDialog("error", "Network Error", "Unable to complete your request."); 92 | } 93 | else if (error.status > 0) { 94 | return showDialog("error", "HTTP Error", "Unable to complete your request. ("+error.status+")"); 95 | } 96 | else { 97 | console.error("Unknown error: "+JSON.stringify(error)); 98 | } 99 | // fallback: always return a promise but reject it. 100 | var defer = $q.defer(); 101 | defer.reject(error); 102 | return defer.promise; 103 | } 104 | 105 | }; 106 | }]); 107 | -------------------------------------------------------------------------------- /syweb/webclient/lib/spin.min.js: -------------------------------------------------------------------------------- 1 | //fgnass.github.com/spin.js#v2.0.1 2 | !function(a,b){"object"==typeof exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return l[e]||(m.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",m.cssRules.length),l[e]=1),e}function d(a,b){var c,d,e=a.style;for(b=b.charAt(0).toUpperCase()+b.slice(1),d=0;d',c)}m.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.width,left:d.radius,top:-d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.length+d.width,k=2*j,l=2*-(d.width+d.length)+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k 2 | 3 | 21 | 22 |
23 | 24 |
25 | [matrix] 26 |
27 | 28 |

Welcome to homeserver {{ config.user_domain }}

29 | 30 |
31 |
32 | 33 |
34 |
35 |
{{ profile.displayName }}
36 |
{{ config.user_id }}
37 |
38 |
39 |
40 |
41 |
42 | 45 | 46 | 47 |
48 |
49 |
50 |
51 | 54 | 55 | 56 |
57 |
58 |
59 |

60 | 61 |

62 |
63 | 64 | 65 |

Recent conversations

66 |
67 |
68 | 69 |

Public rooms

70 |
71 | 72 | 75 | 76 | 81 | 87 | 88 | 89 | 92 | 93 | 94 |
77 | 78 | {{ room.room_display_name }} 79 | 80 | 82 |
84 | {{ room.num_joined_members }} {{ room.num_joined_members == 1 ? 'user' : 'users' }} 85 |
86 |
90 | {{ room.topic }} 91 |
95 |
96 | 97 |
98 | 99 | -------------------------------------------------------------------------------- /syweb/webclient/test/unit/dialog-service.spec.js: -------------------------------------------------------------------------------- 1 | describe('DialogService', function() { 2 | var q; 3 | 4 | // mocked dependency 5 | var dialogs = { 6 | error: function(title, body){ 7 | 8 | }, 9 | notify: function(title, body){ 10 | 11 | } 12 | }; 13 | 14 | beforeEach(function() { 15 | module(function ($provide) { 16 | $provide.value('dialogs', dialogs); 17 | }); 18 | module('dialogService'); 19 | }); 20 | 21 | beforeEach(inject(function($q) { 22 | q = $q; 23 | })); 24 | 25 | it('should display a "Network Error" for a failed HTTP request (no connection).', inject( 26 | function(dialogService) { 27 | var err = { 28 | status: 0, 29 | data: {} 30 | }; 31 | spyOn(dialogs, "error"); 32 | dialogService.showError(err); 33 | expect(dialogs.error).toHaveBeenCalledWith("Network Error", jasmine.any(String)); 34 | })); 35 | 36 | it('should display an "HTTP Error" for a failed HTTP request (non 2xx response).', inject( 37 | function(dialogService) { 38 | var err = { 39 | status: 404, 40 | data: {} 41 | }; 42 | spyOn(dialogs, "error"); 43 | dialogService.showError(err); 44 | expect(dialogs.error).toHaveBeenCalledWith("HTTP Error", jasmine.any(String)); 45 | })); 46 | 47 | it('should display the value of the HS "error" and "errcode" if one is provided.', inject( 48 | function(dialogService) { 49 | var errcode = "M_SOMETHING"; 50 | var error = "A message is you"; 51 | var err = { 52 | status: 200, 53 | data: { 54 | errcode: errcode, 55 | error: error 56 | } 57 | }; 58 | spyOn(dialogs, "error"); 59 | dialogService.showError(err); 60 | expect(dialogs.error).toHaveBeenCalled(); 61 | var args = dialogs.error.calls.argsFor(0); 62 | var errcodeExists = args[0].indexOf(errcode) >= 0 || args[1].indexOf(errcode) >= 0; 63 | var errorExists = args[0].indexOf(error) >= 0 || args[1].indexOf(error) >= 0; 64 | expect(errorExists).toBe(true); 65 | expect(errcodeExists).toBe(true); 66 | })); 67 | 68 | it('should be able to display errors for just data responses.', inject( 69 | function(dialogService) { 70 | var errcode = "M_SOMETHING"; 71 | var error = "A message is you"; 72 | var err = { 73 | errcode: errcode, 74 | error: error 75 | }; 76 | spyOn(dialogs, "error"); 77 | dialogService.showError(err); 78 | expect(dialogs.error).toHaveBeenCalled(); 79 | var args = dialogs.error.calls.argsFor(0); 80 | var errcodeExists = args[0].indexOf(errcode) >= 0 || args[1].indexOf(errcode) >= 0; 81 | var errorExists = args[0].indexOf(error) >= 0 || args[1].indexOf(error) >= 0; 82 | expect(errorExists).toBe(true); 83 | expect(errcodeExists).toBe(true); 84 | })); 85 | 86 | it('should display success messages.', inject( 87 | function(dialogService) { 88 | var title = "title"; 89 | var body = "A body"; 90 | spyOn(dialogs, "notify"); 91 | dialogService.showSuccess(title, body); 92 | expect(dialogs.notify).toHaveBeenCalledWith(title, body); 93 | })); 94 | 95 | it('should display raw error strings.', inject( 96 | function(dialogService) { 97 | var body = "A body is you"; 98 | spyOn(dialogs, "error"); 99 | dialogService.showError(body); 100 | expect(dialogs.error).toHaveBeenCalledWith(jasmine.any(String), body); 101 | })); 102 | 103 | it('should escape HTML in the error.', inject( 104 | function(dialogService) { 105 | var body = "bar"; 106 | var escapedBody = "<html>bar</html>" 107 | spyOn(dialogs, "error"); 108 | dialogService.showError(body); 109 | expect(dialogs.error).toHaveBeenCalledWith(jasmine.any(String), escapedBody); 110 | })); 111 | }); 112 | -------------------------------------------------------------------------------- /syweb/webclient/lib/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.13 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(q,d,C){'use strict';function v(r,k,h){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,f,b,c,y){function z(){l&&(h.cancel(l),l=null);m&&(m.$destroy(),m=null);n&&(l=h.leave(n),l.then(function(){l=null}),n=null)}function x(){var b=r.current&&r.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),c=r.current;n=y(b,function(b){h.enter(b,null,n||f).then(function(){!d.isDefined(t)||t&&!a.$eval(t)||k()});z()});m=c.scope=b;m.$emit("$viewContentLoaded"); 7 | m.$eval(w)}else z()}var m,n,l,t=b.autoscroll,w=b.onload||"";a.$on("$routeChangeSuccess",x);x()}}}function A(d,k,h){return{restrict:"ECA",priority:-400,link:function(a,f){var b=h.current,c=b.locals;f.html(c.$template);var y=d(f.contents());b.controller&&(c.$scope=a,c=k(b.controller,c),b.controllerAs&&(a[b.controllerAs]=c),f.data("$ngControllerController",c),f.children().data("$ngControllerController",c));y(a)}}}q=d.module("ngRoute",["ng"]).provider("$route",function(){function r(a,f){return d.extend(Object.create(a), 8 | f)}function k(a,d){var b=d.caseInsensitiveMatch,c={originalPath:a,regexp:a},h=c.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");c.regexp=new RegExp("^"+a+"$",b?"i":"");return c}var h={};this.when=function(a,f){var b=d.copy(f);d.isUndefined(b.reloadOnSearch)&&(b.reloadOnSearch=!0); 9 | d.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);h[a]=d.extend(b,a&&k(a,b));if(a){var c="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";h[c]=d.extend({redirectTo:a},k(c,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,f,b,c,k,q,x){function m(b){var e=s.current; 10 | (v=(p=l())&&e&&p.$$route===e.$$route&&d.equals(p.pathParams,e.pathParams)&&!p.reloadOnSearch&&!w)||!e&&!p||a.$broadcast("$routeChangeStart",p,e).defaultPrevented&&b&&b.preventDefault()}function n(){var u=s.current,e=p;if(v)u.params=e.params,d.copy(u.params,b),a.$broadcast("$routeUpdate",u);else if(e||u)w=!1,(s.current=e)&&e.redirectTo&&(d.isString(e.redirectTo)?f.path(t(e.redirectTo,e.params)).search(e.params).replace():f.url(e.redirectTo(e.pathParams,f.path(),f.search())).replace()),c.when(e).then(function(){if(e){var a= 11 | d.extend({},e.resolve),b,g;d.forEach(a,function(b,e){a[e]=d.isString(b)?k.get(b):k.invoke(b,null,null,e)});d.isDefined(b=e.template)?d.isFunction(b)&&(b=b(e.params)):d.isDefined(g=e.templateUrl)&&(d.isFunction(g)&&(g=g(e.params)),g=x.getTrustedResourceUrl(g),d.isDefined(g)&&(e.loadedTemplateUrl=g,b=q(g)));d.isDefined(b)&&(a.$template=b);return c.all(a)}}).then(function(c){e==s.current&&(e&&(e.locals=c,d.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,u))},function(b){e==s.current&&a.$broadcast("$routeChangeError", 12 | e,u,b)})}function l(){var a,b;d.forEach(h,function(c,h){var g;if(g=!b){var k=f.path();g=c.keys;var m={};if(c.regexp)if(k=c.regexp.exec(k)){for(var l=1,n=k.length;l " + roomId); 97 | }); 98 | 99 | $rootScope.$on(eventHandlerService.MSG_EVENT, 100 | function(ngEvent, event, isLive) { 101 | if (!enabled || event.room_id === viewingRoom || !isLive) { 102 | return; 103 | } 104 | 105 | var room = modelService.getRoom(event.room_id); 106 | if (room.aevents.length > MAX_EVENTS) { 107 | reapRoom(event.room_id); 108 | } 109 | }); 110 | 111 | return { 112 | MAX_EVENTS: MAX_EVENTS, 113 | 114 | setEnabled: function(isEnabled) { 115 | enabled = isEnabled; 116 | }, 117 | 118 | reap: function(roomId) { 119 | reapRoom(roomId); 120 | } 121 | }; 122 | 123 | }]); 124 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Matrix Angular SDK 2 | ================== 3 | 4 | .. DANGER:: 5 | **matrix-angular-sdk is not currently being maintained or developed by the core 6 | team, and whilst stable it has some serious performance issues; Angular makes it 7 | a bit too easy to shoot yourself in the foot and doesn't help you escape when 8 | you do so. All of our current focus is going into the 9 | https://github.com/matrix-org/matrix-js-sdk, https://github.com/matrix-org/matrix-react-sdk 10 | and https://github.com/matrix-org/matrix-react-skin stack instead - please use 11 | those rather than this if you want support from the core team. Thanks!** 12 | 13 | .. image:: http://matrix.org/jenkins/buildStatus/icon?job=SynapseWebClient 14 | :target: http://matrix.org/jenkins/job/SynapseWebClient/ 15 | 16 | This project provides AngularJS services for implementing the `Client-Server API`_ 17 | on Matrix_ : an open standard for interoperable Instant Messaging and VoIP. It 18 | comes shipped with Synapse_ : a home server reference implementation. 19 | 20 | This project also provides a complete, stand-alone client which can communicate 21 | with Matrix home servers using a web browser. 22 | 23 | The Synapse_ homeserver ships the latest stable version of this library. If you 24 | wish it to serve up a development copy instead, then you must configure this 25 | checkout to be picked up by synapse:: 26 | 27 | $ python setup.py develop --user 28 | 29 | Running 30 | ======= 31 | To run the stand-alone client, the ``syweb/webclient`` folder must be hosted. 32 | This can most easily be achieved by:: 33 | 34 | cd syweb/webclient 35 | python -m SimpleHTTPServer 36 | 37 | Navigate to ``http://localhost:8000`` to see the client. 38 | 39 | Bugs / Feature Requests 40 | ======================= 41 | Think you've found a bug? Want a new feature on the client? Please open an issue 42 | on JIRA: 43 | 44 | - Create an account and login to https://matrix.org/jira 45 | - Navigate to the ``SYWEB`` project. 46 | - Click **Create Issue** - Please be as descriptive as possible, with reproduction 47 | steps if possible. 48 | 49 | All issues in JIRA are **public**. 50 | 51 | Contributing 52 | ============ 53 | Want to fix a bug or add a new feature? Check JIRA first to see if someone else is 54 | handling this issue. If no one is actively working on the issue, then please fork 55 | the ``develop`` branch when writing your fix, and open a pull request when you're 56 | ready. Do not base your pull requests off ``master``. 57 | 58 | Configuration 59 | ============= 60 | The web client can be configured by adding a ``config.js`` file in the 61 | ``syweb/webclient`` directory. This includes configuration for setting up ReCaptcha. 62 | An example file can be found at ``syweb/webclient/config.sample.js``. 63 | 64 | Structure 65 | ========= 66 | The ``app`` directory contains the SDK, which is split up into subfolders depending 67 | on the logical scope of the code. The ``components`` directory contains reusable 68 | components which are used in many places. More specific directories such as ``home`` 69 | and ``settings`` contain code specific to that part of the app: e.g. the home screen 70 | and settings page respectively. 71 | 72 | The `Client-Server API`_ is encapsulated as an AngularJS service called ``matrixService``. 73 | There are also complementary services such as ``eventStreamService`` which handle more 74 | complex non-HTTP client logic. 75 | 76 | Services can be used independently provided their dependencies are satisfied. 77 | 78 | * ``matrixService`` is provided at the lowest level, as it just wraps the raw HTTP calls. 79 | * ``modelService`` allows models of matrix objects to be accessed, such as ``User``, 80 | ``Room``, ``RoomState`` and ``RoomMember``, and provides convenience functions to perform 81 | HTTP calls on these objects (e.g. ``Room.leave``). 82 | * ``eventHandlerService`` interprets raw Matrix events and determines what needs to be 83 | stored with the ``modelService``. 84 | * ``eventStreamService`` controls long-polling behaviour on the ``/events`` HTTP call. 85 | * ``typingService`` controls the submission of typing events into a room. 86 | * ``presenceService`` controls the submission of presence events. 87 | 88 | Alternatively, you can use different controllers and html templates and leave the services 89 | to work together as is. 90 | 91 | Tests 92 | ===== 93 | Tests are contained in the `test directory`_. They require 94 | Karma (running PhantomJS) and Jasmine 2.x+ in order to run. Assuming you have the 95 | required karma plugins, you can run the tests by running ``karma start`` in the 96 | test directory. 97 | 98 | Attributions 99 | ============ 100 | File icons are taken from http://medialoot.com/item/free-flat-filetype-icons/ and 101 | distributed under the terms of the Paid License (invoice #7355) 102 | 103 | Keyboard and GIF icon from icons8: http://icons8.com/ 104 | 105 | .. _Synapse: https://github.com/matrix-org/synapse/ 106 | .. _Matrix: http://www.matrix.org 107 | .. _Client-Server API: http://matrix.org/docs/api/client-server/ 108 | .. _test directory: syweb/webclient/test 109 | -------------------------------------------------------------------------------- /syweb/webclient/app/app-filter.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | angular.module('matrixWebClient') 20 | .filter('duration', function() { 21 | return function(time) { 22 | if (!time) return; 23 | var t = parseInt(time / 1000); 24 | var s = t % 60; 25 | var m = parseInt(t / 60) % 60; 26 | var h = parseInt(t / (60 * 60)) % 24; 27 | var d = parseInt(t / (60 * 60 * 24)); 28 | if (t < 60) { 29 | if (t < 0) { 30 | return "0s"; 31 | } 32 | return s + "s"; 33 | } 34 | if (t < 60 * 60) { 35 | return m + "m"; // + s + "s"; 36 | } 37 | if (t < 24 * 60 * 60) { 38 | return h + "h"; // + m + "m"; 39 | } 40 | return d + "d "; // + h + "h"; 41 | }; 42 | }) 43 | .filter('orderMembersList', function($sce) { 44 | return function(members) { 45 | var filtered = []; 46 | 47 | angular.forEach(members, function(member, key) { 48 | member["id"] = key; 49 | 50 | // do not add members who have left. 51 | var ignoreList = ["leave", "kick", "ban"]; 52 | if (ignoreList.indexOf(member.event.content.membership) != -1) { 53 | return; 54 | } 55 | 56 | filtered.push(member); 57 | }); 58 | 59 | filtered.sort(function (a, b) { 60 | // Sort members on their last_active absolute time 61 | a = a.user; 62 | b = b.user; 63 | 64 | var aLastActiveTS = 0, bLastActiveTS = 0; 65 | if (a && a.event && a.event.content && a.event.content.last_active_ago !== undefined) { 66 | aLastActiveTS = a.last_updated - a.event.content.last_active_ago; 67 | } 68 | if (b && b.event && b.event.content && b.event.content.last_active_ago !== undefined) { 69 | bLastActiveTS = b.last_updated - b.event.content.last_active_ago; 70 | } 71 | if (aLastActiveTS || bLastActiveTS) { 72 | return bLastActiveTS - aLastActiveTS; 73 | } 74 | else { 75 | // If they do not have last_active_ago, sort them according to their presence state 76 | // Online users go first amongs members who do not have last_active_ago 77 | var presenceLevels = { 78 | offline: 1, 79 | unavailable: 2, 80 | online: 4, 81 | free_for_chat: 3 82 | }; 83 | var aPresence = (a && a.event && a.event.content.presence in presenceLevels) ? presenceLevels[a.event.content.presence] : 0; 84 | var bPresence = (b && b.event && b.event.content.presence in presenceLevels) ? presenceLevels[b.event.content.presence] : 0; 85 | return bPresence - aPresence; 86 | } 87 | }); 88 | return filtered; 89 | }; 90 | }) 91 | .filter('unsafe', ['$sce', function($sce) { 92 | return function(text) { 93 | return $sce.trustAsHtml(text); 94 | }; 95 | }]) 96 | .filter('escapeHTML', function() { 97 | return function(text) { 98 | if (text) { 99 | return text. 100 | replace(/&/g, '&'). 101 | replace(//g, '>'). 103 | replace(/'/g, '''). 104 | replace(/"/g, '"'); 105 | } 106 | return ''; 107 | }; 108 | }) 109 | // Exactly the same as ngSanitize's linky but instead of pushing sanitized 110 | // text in the addText function, we just push the raw text. 111 | .filter('unsanitizedLinky', ['$sanitize', function($sanitize) { 112 | var LINKY_URL_REGEXP = 113 | /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)[^<>\s]*[^\s.;,(){}<>"]/, 114 | MAILTO_REGEXP = /^mailto:/; 115 | 116 | return function(text, target) { 117 | if (!text) return text; 118 | var match; 119 | var raw = text; 120 | var html = []; 121 | var url; 122 | var i; 123 | while ((match = raw.match(LINKY_URL_REGEXP))) { 124 | // We can not end in these as they are sometimes found at the end of the sentence 125 | url = match[0]; 126 | // if we did not match ftp/http/mailto then assume mailto 127 | if (match[2] == match[3]) url = 'mailto:' + url; 128 | i = match.index; 129 | addText(raw.substr(0, i)); 130 | addLink(url, match[0].replace(MAILTO_REGEXP, '')); 131 | raw = raw.substring(i + match[0].length); 132 | } 133 | addText(raw); 134 | return $sanitize(html.join('')); 135 | 136 | function addText(text) { 137 | if (!text) { 138 | return; 139 | } 140 | html.push(text); 141 | } 142 | 143 | function addLink(url, text) { 144 | html.push(''); 153 | addText(text); 154 | html.push(''); 155 | } 156 | }; 157 | }]); 158 | -------------------------------------------------------------------------------- /syweb/webclient/app/components/matrix/typing-service.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /* 20 | * This service controls the sending and timers for typing notifications. 21 | * It needs to maintain 2 timers: one for the user input (a timer which 22 | * when it times out means they are no longer typing), and one for the 23 | * server poke (which when it times out means you need to re-poke). 24 | */ 25 | angular.module('typingService', []) 26 | .factory('typingService', [ 'matrixService', '$interval', 27 | function(matrixService, $interval) { 28 | var typingService = {}; 29 | 30 | // canonical source for rooms typing in 31 | var roomsTyping = { 32 | // room_id : true|false 33 | }; 34 | 35 | 36 | // The "user timeout" is the time taken from the last character entered until they are treated 37 | // as having stopped typing. 38 | typingService.USER_TIMEOUT_MS = 5000; 39 | var userTimeouts = { 40 | // room_id: timeout 41 | }; 42 | var cancelUserTimeout = function(roomId) { 43 | var timerPromise = userTimeouts[roomId]; 44 | if (timerPromise) { 45 | $interval.cancel(timerPromise); 46 | } 47 | }; 48 | var startUserTimeout = function(roomId) { 49 | userTimeouts[roomId] = $interval(function() { 50 | if (roomsTyping[roomId]) { 51 | console.log("[typing] user-timeout: expired "+roomId); 52 | roomsTyping[roomId] = false; 53 | stopTyping(roomId); 54 | } 55 | }, typingService.USER_TIMEOUT_MS, 1); 56 | }; 57 | 58 | // The "server timeout" is the time taken from the last server poke until another poke is required if they are still 59 | // typing. This is slightly less than the server-specified timeout because of propagation times. 60 | typingService.SERVER_TIMEOUT_MS = 25000; 61 | typingService.SERVER_SPECIFIED_TIMEOUT_MS = 30000; 62 | var serverTimeouts = { 63 | // room_id: timeout 64 | }; 65 | var cancelServerTimeout = function(roomId) { 66 | var timerPromise = serverTimeouts[roomId]; 67 | if (timerPromise) { 68 | $interval.cancel(timerPromise); 69 | } 70 | }; 71 | var startServerTimeout = function(roomId) { 72 | serverTimeouts[roomId] = $interval(function() { 73 | console.log("[typing] server-timeout: expired. Still typing: "+roomsTyping[roomId]+" in room "+roomId); 74 | if (roomsTyping[roomId]) { 75 | startServerTimeout(roomId); 76 | // re-poke the server 77 | matrixService.setTyping(roomId, true, typingService.SERVER_SPECIFIED_TIMEOUT_MS).then(function() { 78 | console.log("[typing] server-timeout: Repoked."); 79 | }, 80 | function(error) { 81 | console.error("[typing] server-timeout: Unable to re-poke typing notification."); 82 | }); 83 | } 84 | }, typingService.SERVER_TIMEOUT_MS, 1); 85 | }; 86 | 87 | 88 | 89 | var stopTyping = function(roomId) { 90 | cancelUserTimeout(roomId); 91 | cancelServerTimeout(roomId); 92 | 93 | matrixService.setTyping(roomId, false).then(function() { 94 | console.log("[typing] sent stopped typing."); 95 | }, 96 | function(error) { 97 | console.error("[typing] Unable to send stop typing in room "+roomId); 98 | }); 99 | }; 100 | 101 | var startTyping = function(roomId) { 102 | matrixService.setTyping(roomId, true, typingService.SERVER_SPECIFIED_TIMEOUT_MS).then(function() { 103 | console.log("[typing] sent started typing. starting typing timeouts in room "+roomId); 104 | cancelUserTimeout(roomId); 105 | cancelServerTimeout(roomId); 106 | startServerTimeout(roomId); 107 | startUserTimeout(roomId); 108 | }, 109 | function(error) { 110 | console.error("[typing] Unable to send typing in room "+roomId); 111 | }); 112 | }; 113 | 114 | 115 | /* 116 | * Announce that you are typing (or not) in a room. This can be called 117 | * as often as you like, so attaching it to onchange events on an input 118 | * box is encouraged. 119 | * @param The room you are typing in. 120 | * @param Truthy if you are typing. 121 | */ 122 | typingService.setTyping = function(roomId, isTyping) { 123 | if (isTyping && !roomsTyping[roomId]) { 124 | // state change -> typing 125 | roomsTyping[roomId] = true; 126 | console.log("[typing] explicit start typing in room "+roomId); 127 | startTyping(roomId); 128 | } 129 | else if (!isTyping && roomsTyping[roomId]) { 130 | // state change -> not typing 131 | roomsTyping[roomId] = false; 132 | console.log("[typing] explicit stop typing in room "+roomId); 133 | stopTyping(roomId); 134 | } 135 | 136 | if (isTyping) { 137 | // restart the user timeout 138 | cancelUserTimeout(roomId); 139 | startUserTimeout(roomId); 140 | } 141 | } 142 | 143 | return typingService; 144 | 145 | }]); 146 | -------------------------------------------------------------------------------- /syweb/webclient/app/components/utilities/utilities-service.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /* 20 | * This service contains multipurpose helper functions. 21 | */ 22 | angular.module('mUtilities', []) 23 | .service('mUtilities', ['$q', function ($q) { 24 | /* 25 | * Get the size of an image 26 | * @param {File|Blob} imageFile the file containing the image 27 | * @returns {promise} A promise that will be resolved by an object with 2 members: 28 | * width & height 29 | */ 30 | this.getImageSize = function(imageFile) { 31 | var deferred = $q.defer(); 32 | 33 | // Load the file into an html element 34 | var img = document.createElement("img"); 35 | 36 | var reader = new FileReader(); 37 | reader.onload = function(e) { 38 | img.src = e.target.result; 39 | 40 | // Once ready, returns its size 41 | img.onload = function() { 42 | deferred.resolve({ 43 | width: img.width, 44 | height: img.height 45 | }); 46 | }; 47 | img.onerror = function(e) { 48 | deferred.reject(e); 49 | }; 50 | }; 51 | reader.onerror = function(e) { 52 | deferred.reject(e); 53 | }; 54 | reader.readAsDataURL(imageFile); 55 | 56 | return deferred.promise; 57 | }; 58 | 59 | /* 60 | * Resize the image to fit in a square of the side maxSize. 61 | * The aspect ratio is kept. The returned image data uses JPEG compression. 62 | * Source: http://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/ 63 | * @param {File} imageFile the file containing the image 64 | * @param {Integer} maxSize the max side size 65 | * @returns {promise} A promise that will be resolved by a Blob object containing 66 | * the resized image data 67 | */ 68 | this.resizeImage = function(imageFile, maxSize) { 69 | var self = this; 70 | var deferred = $q.defer(); 71 | 72 | var canvas = document.createElement("canvas"); 73 | 74 | var img = document.createElement("img"); 75 | var reader = new FileReader(); 76 | reader.onload = function(e) { 77 | 78 | img.src = e.target.result; 79 | 80 | // Once ready, returns its size 81 | img.onload = function() { 82 | var ctx = canvas.getContext("2d"); 83 | ctx.drawImage(img, 0, 0); 84 | 85 | var MAX_WIDTH = maxSize; 86 | var MAX_HEIGHT = maxSize; 87 | var width = img.width; 88 | var height = img.height; 89 | 90 | if (width > height) { 91 | if (width > MAX_WIDTH) { 92 | height *= MAX_WIDTH / width; 93 | width = MAX_WIDTH; 94 | } 95 | } else { 96 | if (height > MAX_HEIGHT) { 97 | width *= MAX_HEIGHT / height; 98 | height = MAX_HEIGHT; 99 | } 100 | } 101 | canvas.width = width; 102 | canvas.height = height; 103 | var ctx = canvas.getContext("2d"); 104 | ctx.drawImage(img, 0, 0, width, height); 105 | 106 | // Extract image data in the same format as the original one. 107 | // The 0.7 compression value will work with formats that supports it like JPEG. 108 | var dataUrl = canvas.toDataURL(imageFile.type, 0.7); 109 | deferred.resolve(self.dataURItoBlob(dataUrl)); 110 | }; 111 | img.onerror = function(e) { 112 | deferred.reject(e); 113 | }; 114 | }; 115 | reader.onerror = function(e) { 116 | deferred.reject(e); 117 | }; 118 | reader.readAsDataURL(imageFile); 119 | 120 | return deferred.promise; 121 | }; 122 | 123 | /* 124 | * Convert a dataURI string to a blob 125 | * Source: http://stackoverflow.com/a/17682951 126 | * @param {String} dataURI the dataURI can be a base64 encoded string or an URL encoded string. 127 | * @returns {Blob} the blob 128 | */ 129 | this.dataURItoBlob = function(dataURI) { 130 | // convert base64 to raw binary data held in a string 131 | // doesn't handle URLEncoded DataURIs 132 | var byteString; 133 | if (dataURI.split(',')[0].indexOf('base64') >= 0) 134 | byteString = atob(dataURI.split(',')[1]); 135 | else 136 | byteString = unescape(dataURI.split(',')[1]); 137 | // separate out the mime component 138 | var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 139 | 140 | // write the bytes of the string to an ArrayBuffer 141 | var ab = new ArrayBuffer(byteString.length); 142 | var ia = new Uint8Array(ab); 143 | for (var i = 0; i < byteString.length; i++) { 144 | ia[i] = byteString.charCodeAt(i); 145 | } 146 | 147 | // write the ArrayBuffer to a blob, and you're done 148 | return new Blob([ab],{type: mimeString}); 149 | }; 150 | 151 | }]); -------------------------------------------------------------------------------- /syweb/webclient/lib/angular-sanitize.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.13 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(n,h,p){'use strict';function E(a){var d=[];s(d,h.noop).chars(a);return d.join("")}function g(a){var d={};a=a.split(",");var c;for(c=0;c=c;e--)d.end&&d.end(f[e]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,k,f=[],m=a,l;for(f.last=function(){return f[f.length-1]};a;){l="";k=!0;if(f.last()&&x[f.last()])a=a.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(H,"$1").replace(I,"$1");d.chars&&d.chars(r(b));return""}),e("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(d.comment&& 8 | d.comment(a.substring(4,b)),a=a.substring(b+3),k=!1);else if(y.test(a)){if(b=a.match(y))a=a.replace(b[0],""),k=!1}else if(J.test(a)){if(b=a.match(z))a=a.substring(b[0].length),b[0].replace(z,e),k=!1}else K.test(a)&&((b=a.match(A))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(A,c)),k=!1):(l+="<",a=a.substring(1)));k&&(b=a.indexOf("<"),l+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),d.chars&&d.chars(r(l)))}if(a==m)throw L("badparse",a);m=a}e()}function r(a){if(!a)return"";var d=M.exec(a);a=d[1]; 9 | var c=d[3];if(d=d[2])q.innerHTML=d.replace(//g,">")}function s(a,d){var c=!1,e=h.bind(a,a.push);return{start:function(a,k,f){a=h.lowercase(a);!c&&x[a]&&(c=a);c||!0!==C[a]||(e("<"),e(a), 10 | h.forEach(k,function(c,f){var k=h.lowercase(f),g="img"===a&&"src"===k||"background"===k;!0!==P[k]||!0===D[k]&&!d(c,g)||(e(" "),e(f),e('="'),e(B(c)),e('"'))}),e(f?"/>":">"))},end:function(a){a=h.lowercase(a);c||!0!==C[a]||(e(""));a==c&&(c=!1)},chars:function(a){c||e(B(a))}}}var L=h.$$minErr("$sanitize"),A=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,z=/^<\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, 11 | K=/^]*?)>/i,I=/"\u201d\u2019]/,c=/^mailto:/;return function(e,b){function k(a){a&&g.push(E(a))} 15 | function f(a,c){g.push("');k(c);g.push("")}if(!e)return e;for(var m,l=e,g=[],n,p;m=l.match(d);)n=m[0],m[2]||m[4]||(n=(m[3]?"http://":"mailto:")+n),p=m.index,k(l.substr(0,p)),f(n,m[0].replace(c,"")),l=l.substring(p+m[0].length);k(l);return a(g.join(""))}}])})(window,window.angular); 16 | //# sourceMappingURL=angular-sanitize.min.js.map 17 | -------------------------------------------------------------------------------- /syweb/webclient/app/components/matrix/recents-service.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /* 20 | This service manages shared state between *instances* of recent lists. The 21 | recents controller will hook into this central service to get things like: 22 | - which rooms should be highlighted 23 | - which rooms have been binged 24 | - which room is currently selected 25 | - etc. 26 | This is preferable to polluting the $rootScope with recents specific info, and 27 | makes the dependency on this shared state *explicit*. 28 | */ 29 | angular.module('recentsService', []) 30 | .factory('recentsService', ['$rootScope', '$document', 'eventHandlerService', 'matrixService', 'modelService', 31 | 'notificationService', 32 | function($rootScope, $document, eventHandlerService, matrixService, modelService, notificationService) { 33 | // notify listeners when variables in the service are updated. We need to do 34 | // this since we do not tie them to any scope. 35 | var BROADCAST_SELECTED_ROOM_ID = "recentsService:BROADCAST_SELECTED_ROOM_ID(room_id)"; 36 | var selectedRoomId = undefined; 37 | 38 | var showCountInTitle = true; 39 | var titleCount = 0; 40 | var originalTitle = $document[0].title; 41 | 42 | var BROADCAST_UNREAD_MESSAGES = "recentsService:BROADCAST_UNREAD_MESSAGES(room_id, unreadCount)"; 43 | var unreadMessages = { 44 | // room_id: 45 | }; 46 | 47 | var BROADCAST_UNREAD_BING_MESSAGES = "recentsService:BROADCAST_UNREAD_BING_MESSAGES(room_id, event)"; 48 | var unreadBingMessages = { 49 | // room_id: bingEvent 50 | }; 51 | 52 | var addUnreadBing = function(event) { 53 | if (!unreadBingMessages[event.room_id]) { 54 | unreadBingMessages[event.room_id] = {}; 55 | } 56 | unreadBingMessages[event.room_id] = event; 57 | $rootScope.$broadcast(BROADCAST_UNREAD_BING_MESSAGES, event.room_id, event); 58 | }; 59 | 60 | // listen for new unread messages 61 | $rootScope.$on(eventHandlerService.MSG_EVENT, function(ngEvent, event, isLive) { 62 | if (isLive && event.room_id !== selectedRoomId && matrixService.config().user_id !== event.user_id) { 63 | notificationService.ifShouldHighlightEvent(event).then(function() { 64 | addUnreadBing(event); 65 | }); 66 | 67 | if (!unreadMessages[event.room_id]) { 68 | unreadMessages[event.room_id] = 0; 69 | } 70 | unreadMessages[event.room_id] += 1; 71 | titleCount += 1; 72 | updateTitleCount(); 73 | $rootScope.$broadcast(BROADCAST_UNREAD_MESSAGES, event.room_id, unreadMessages[event.room_id]); 74 | } 75 | }); 76 | 77 | // bing for new invites 78 | $rootScope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) { 79 | if (isLive && event.room_id !== selectedRoomId) { 80 | if (matrixService.config().user_id === event.state_key && 81 | event.content.membership === "invite") { 82 | addUnreadBing(event); 83 | } 84 | } 85 | }); 86 | // bing for invites from initialsync 87 | eventHandlerService.waitForInitialSyncCompletion().then(function() { 88 | var rooms = modelService.getRooms(); 89 | var me = matrixService.config().user_id; 90 | Object.keys(rooms).forEach(function (room_id) { 91 | var room = rooms[room_id]; 92 | if (room.getMembershipState(me) === "invite") { 93 | addUnreadBing(room.now.state("m.room.member", me)); 94 | } 95 | }); 96 | }); 97 | 98 | var updateTitleCount = function() { 99 | if (!showCountInTitle) { 100 | return; 101 | } 102 | 103 | if (titleCount <= 0) { 104 | $document[0].title = originalTitle; 105 | } 106 | else { 107 | $document[0].title = "("+titleCount+") " + originalTitle; 108 | } 109 | }; 110 | 111 | return { 112 | BROADCAST_SELECTED_ROOM_ID: BROADCAST_SELECTED_ROOM_ID, 113 | BROADCAST_UNREAD_MESSAGES: BROADCAST_UNREAD_MESSAGES, 114 | 115 | showUnreadMessagesInTitle: function(showMessages) { 116 | showCountInTitle = showMessages; 117 | }, 118 | 119 | getSelectedRoomId: function() { 120 | return selectedRoomId; 121 | }, 122 | 123 | setSelectedRoomId: function(room_id) { 124 | selectedRoomId = room_id; 125 | $rootScope.$broadcast(BROADCAST_SELECTED_ROOM_ID, room_id); 126 | }, 127 | 128 | getUnreadMessages: function() { 129 | return unreadMessages; 130 | }, 131 | 132 | getUnreadBingMessages: function() { 133 | return unreadBingMessages; 134 | }, 135 | 136 | markAsRead: function(room_id) { 137 | if (unreadMessages[room_id]) { 138 | titleCount -= unreadMessages[room_id]; 139 | unreadMessages[room_id] = 0; 140 | } 141 | if (unreadBingMessages[room_id]) { 142 | unreadBingMessages[room_id] = undefined; 143 | } 144 | $rootScope.$broadcast(BROADCAST_UNREAD_MESSAGES, room_id, 0); 145 | $rootScope.$broadcast(BROADCAST_UNREAD_BING_MESSAGES, room_id, undefined); 146 | updateTitleCount(); 147 | } 148 | 149 | }; 150 | 151 | }]); 152 | -------------------------------------------------------------------------------- /syweb/webclient/app/app.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | var matrixWebClient = angular.module('matrixWebClient', [ 18 | 'ngRoute', 19 | 'MatrixWebClientController', 20 | 'LoginController', 21 | 'ResetPasswordController', 22 | 'RegisterController', 23 | 'RoomController', 24 | 'HomeController', 25 | 'RecentsController', 26 | 'RecentsFilter', 27 | 'SettingsController', 28 | 'PaymentController', 29 | 'UserController', 30 | 'paymentService', 31 | 'matrixService', 32 | 'webRtcService', 33 | 'matrixPhoneService', 34 | 'MatrixCall', 35 | 'eventStreamService', 36 | 'eventHandlerService', 37 | 'eventReaperService', 38 | 'notificationService', 39 | 'dialogService', 40 | 'recentsService', 41 | 'modelService', 42 | 'commandsService', 43 | 'typingService', 44 | 'versionService', 45 | 'infinite-scroll', 46 | 'ui.bootstrap', 47 | 'dialogs.main', 48 | 'angularSpinner', 49 | 'monospaced.elastic' 50 | ]); 51 | 52 | matrixWebClient.config(['$routeProvider', '$provide', '$httpProvider', 53 | function($routeProvider, $provide, $httpProvider) { 54 | $routeProvider. 55 | when('/login', { 56 | templateUrl: 'app/login/login.html' 57 | }). 58 | when('/register', { 59 | templateUrl: 'app/login/register.html' 60 | }). 61 | when('/reset-password', { 62 | templateUrl: 'app/login/reset-password.html' 63 | }). 64 | when('/room/:room_id_or_alias', { 65 | templateUrl: 'app/room/room.html' 66 | }). 67 | when('/room/', { // room URL with room alias in it (ex: http://127.0.0.1:8000/#/room/#public:localhost:8080) will come here. 68 | // The reason is that 2nd hash key breaks routeProvider parameters cutting so that the URL will not match with 69 | // the previous '/room/:room_id_or_alias' URL rule 70 | templateUrl: 'app/room/room.html' 71 | }). 72 | when('/', { 73 | templateUrl: 'app/home/home.html' 74 | }). 75 | when('/settings', { 76 | templateUrl: 'app/settings/settings.html' 77 | }). 78 | when('/payment', { 79 | templateUrl: 'app/payment/payment.html' 80 | }). 81 | when('/payment/:payment_state', { 82 | templateUrl: 'app/payment/state.html' 83 | }). 84 | when('/user/:user_matrix_id', { 85 | templateUrl: 'app/user/user.html' 86 | }). 87 | otherwise({ 88 | redirectTo: '/' 89 | }); 90 | 91 | $provide.factory('AccessTokenInterceptor', ['$q', '$rootScope', 92 | function ($q, $rootScope) { 93 | return { 94 | responseError: function(rejection) { 95 | if ("data" in rejection && rejection.data && typeof rejection.data == 'object' && 96 | "errcode" in rejection.data && 97 | rejection.data.errcode === "M_UNKNOWN_TOKEN") { 98 | console.log("Got a 403 with an unknown token. Logging out.") 99 | $rootScope.$broadcast("M_UNKNOWN_TOKEN"); 100 | } 101 | return $q.reject(rejection); 102 | } 103 | }; 104 | }]); 105 | $httpProvider.interceptors.push('AccessTokenInterceptor'); 106 | }]); 107 | 108 | matrixWebClient.run(['$location', '$rootScope', 'matrixService', function($location, $rootScope, matrixService) { 109 | $rootScope.httpUri = matrixService.getHttpUriForMxc; 110 | 111 | // Source: https://msdn.microsoft.com/en-us/library/cc817582.aspx 112 | // Returns the version of Windows Internet Explorer or a -1 113 | // (indicating the use of another browser). 114 | function getInternetExplorerVersion() { 115 | var rv = -1; // Return value assumes failure. 116 | if (!navigator) { 117 | return rv; 118 | } 119 | if (navigator.appName == 'Microsoft Internet Explorer') { 120 | var ua = navigator.userAgent; 121 | var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); 122 | if (re.exec(ua) != null) { 123 | rv = parseFloat( RegExp.$1 ); 124 | } 125 | } 126 | return rv; 127 | } 128 | 129 | // Check browser support. Fail gracefully and display an error message 130 | // if running on IE8 and below. 131 | var ieVersion = getInternetExplorerVersion(); 132 | if (ieVersion > -1 && ieVersion < 9.0) { 133 | $rootScope.unsupportedBrowser = { 134 | browser: navigator.userAgent, 135 | reason: "Internet Explorer is supported from version 9." 136 | }; 137 | } 138 | // The app requires localStorage 139 | if(typeof(Storage) === "undefined") { 140 | $rootScope.unsupportedBrowser = { 141 | browser: navigator.userAgent, 142 | reason: "Your browser does not support HTML local storage." 143 | }; 144 | } 145 | 146 | // If user auth details are not in cache, go to the login page 147 | $rootScope.$on("$routeChangeStart", function(event, next, current) { 148 | var unauthenticatedPages = ["/register", "/reset-password", "/login"]; 149 | if (!matrixService.isUserLoggedIn() && unauthenticatedPages.indexOf($location.path()) === -1) { 150 | $location.path("login"); 151 | } 152 | }); 153 | 154 | }]); 155 | -------------------------------------------------------------------------------- /syweb/webclient/app/components/matrix/commands-service.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /* 20 | This service contains logic for parsing and performing IRC style commands. 21 | */ 22 | angular.module('commandsService', []) 23 | .factory('commandsService', ['$q', '$location', 'matrixService', 'modelService', function($q, $location, matrixService, modelService) { 24 | 25 | // create a rejected promise with the given message 26 | var reject = function(msg) { 27 | var deferred = $q.defer(); 28 | deferred.reject({ 29 | data: { 30 | error: msg 31 | } 32 | }); 33 | return deferred.promise; 34 | }; 35 | 36 | // Change your nickname 37 | var doNick = function(room_id, args) { 38 | if (args) { 39 | return matrixService.setDisplayName(args); 40 | } 41 | return reject("Usage: /nick "); 42 | }; 43 | 44 | // Join a room 45 | var doJoin = function(room_id, args) { 46 | if (args) { 47 | var matches = args.match(/^(\S+)$/); 48 | if (matches) { 49 | var room_alias = matches[1]; 50 | $location.url("room/" + room_alias); 51 | // NB: We don't need to actually do the join, since that happens 52 | // automatically if we are not joined onto a room already when 53 | // the page loads. 54 | return reject("Joining "+room_alias); 55 | } 56 | } 57 | return reject("Usage: /join "); 58 | }; 59 | 60 | // Kick a user from the room with an optional reason 61 | var doKick = function(room_id, args) { 62 | if (args) { 63 | var matches = args.match(/^(\S+?)( +(.*))?$/); 64 | if (matches) { 65 | return matrixService.kick(room_id, matches[1], matches[3]); 66 | } 67 | } 68 | return reject("Usage: /kick []"); 69 | }; 70 | 71 | // Ban a user from the room with an optional reason 72 | var doBan = function(room_id, args) { 73 | if (args) { 74 | var matches = args.match(/^(\S+?)( +(.*))?$/); 75 | if (matches) { 76 | return matrixService.ban(room_id, matches[1], matches[3]); 77 | } 78 | } 79 | return reject("Usage: /ban []"); 80 | }; 81 | 82 | // Unban a user from the room 83 | var doUnban = function(room_id, args) { 84 | if (args) { 85 | var matches = args.match(/^(\S+)$/); 86 | if (matches) { 87 | // Reset the user membership to "leave" to unban him 88 | return matrixService.unban(room_id, matches[1]); 89 | } 90 | } 91 | return reject("Usage: /unban "); 92 | }; 93 | 94 | // Define the power level of a user 95 | var doOp = function(room_id, args) { 96 | if (args) { 97 | var matches = args.match(/^(\S+?)( +(\d+))?$/); 98 | var powerLevel = 50; // default power level for op 99 | if (matches) { 100 | var user_id = matches[1]; 101 | if (matches.length === 4 && undefined !== matches[3]) { 102 | powerLevel = parseInt(matches[3]); 103 | } 104 | if (powerLevel !== NaN) { 105 | var powerLevelEvent = modelService.getRoom(room_id).current_room_state.state("m.room.power_levels"); 106 | return matrixService.setUserPowerLevel(room_id, user_id, powerLevel, powerLevelEvent); 107 | } 108 | } 109 | } 110 | return reject("Usage: /op []"); 111 | }; 112 | 113 | // Reset the power level of a user 114 | var doDeop = function(room_id, args) { 115 | if (args) { 116 | var matches = args.match(/^(\S+)$/); 117 | if (matches) { 118 | var powerLevelEvent = modelService.getRoom(room_id).current_room_state.state("m.room.power_levels"); 119 | return matrixService.setUserPowerLevel(room_id, args, undefined, powerLevelEvent); 120 | } 121 | } 122 | return reject("Usage: /deop "); 123 | }; 124 | 125 | 126 | var commands = { 127 | "nick": doNick, 128 | "join": doJoin, 129 | "kick": doKick, 130 | "ban": doBan, 131 | "unban": doUnban, 132 | "op": doOp, 133 | "deop": doDeop 134 | }; 135 | 136 | return { 137 | 138 | /** 139 | * Process the given text for commands and perform them. 140 | * @param {String} roomId The room in which the input was performed. 141 | * @param {String} input The raw text input by the user. 142 | * @return {Promise} A promise of the pending command, or null if the 143 | * input is not a command. 144 | */ 145 | processInput: function(roomId, input) { 146 | // trim any trailing whitespace, as it can confuse the parser for 147 | // IRC-style commands 148 | input = input.replace(/\s+$/, ""); 149 | if (input[0] === "/" && input[1] !== "/") { 150 | var bits = input.match(/^(\S+?)( +(.*))?$/); 151 | var cmd = bits[1].substring(1).toLowerCase(); 152 | var args = bits[3]; 153 | if (commands[cmd]) { 154 | return commands[cmd](roomId, args); 155 | } 156 | return reject("Unrecognised IRC-style command: " + cmd); 157 | } 158 | return null; // not a command 159 | } 160 | 161 | }; 162 | 163 | }]); 164 | 165 | -------------------------------------------------------------------------------- /syweb/webclient/test/unit/commands-service.spec.js: -------------------------------------------------------------------------------- 1 | describe('CommandsService', function() { 2 | var scope; 3 | var roomId = "!dlwifhweu:localhost"; 4 | 5 | var testPowerLevelsEvent, testMatrixServicePromise; 6 | 7 | var matrixService = { // these will be spyed on by jasmine, hence stub methods 8 | setDisplayName: function(args){}, 9 | kick: function(args){}, 10 | ban: function(args){}, 11 | unban: function(args){}, 12 | setUserPowerLevel: function(args){} 13 | }; 14 | 15 | var modelService = { 16 | getRoom: function(roomId) { 17 | return { 18 | room_id: roomId, 19 | current_room_state: { 20 | events: { 21 | "m.room.power_levels": testPowerLevelsEvent 22 | }, 23 | state: function(type, key) { 24 | return key ? this.events[type+key] : this.events[type]; 25 | } 26 | } 27 | }; 28 | } 29 | }; 30 | 31 | 32 | // helper function for asserting promise outcomes 33 | NOTHING = "[Promise]"; 34 | RESOLVED = "[Resolved promise]"; 35 | REJECTED = "[Rejected promise]"; 36 | var expectPromise = function(promise, expects) { 37 | var value = NOTHING; 38 | promise.then(function(result) { 39 | value = RESOLVED; 40 | }, function(fail) { 41 | value = REJECTED; 42 | }); 43 | scope.$apply(); 44 | expect(value).toEqual(expects); 45 | }; 46 | 47 | // setup the service and mocked dependencies 48 | beforeEach(function() { 49 | 50 | // set default mock values 51 | testPowerLevelsEvent = { 52 | content: { 53 | default: 50 54 | }, 55 | user_id: "@foo:bar", 56 | room_id: roomId 57 | } 58 | 59 | // mocked dependencies 60 | module(function ($provide) { 61 | $provide.value('matrixService', matrixService); 62 | $provide.value('modelService', modelService); 63 | }); 64 | 65 | // tested service 66 | module('commandsService'); 67 | }); 68 | 69 | beforeEach(inject(function($rootScope, $q) { 70 | scope = $rootScope; 71 | testMatrixServicePromise = $q.defer(); 72 | })); 73 | 74 | it('should reject a no-arg "/nick".', inject( 75 | function(commandsService) { 76 | var promise = commandsService.processInput(roomId, "/nick"); 77 | expectPromise(promise, REJECTED); 78 | })); 79 | 80 | it('should be able to set a /nick with multiple words.', inject( 81 | function(commandsService) { 82 | spyOn(matrixService, 'setDisplayName').and.returnValue(testMatrixServicePromise); 83 | var promise = commandsService.processInput(roomId, "/nick Bob Smith"); 84 | expect(matrixService.setDisplayName).toHaveBeenCalledWith("Bob Smith"); 85 | expect(promise).toBe(testMatrixServicePromise); 86 | })); 87 | 88 | it('should be able to /kick a user without a reason.', inject( 89 | function(commandsService) { 90 | spyOn(matrixService, 'kick').and.returnValue(testMatrixServicePromise); 91 | var promise = commandsService.processInput(roomId, "/kick @bob:matrix.org"); 92 | expect(matrixService.kick).toHaveBeenCalledWith(roomId, "@bob:matrix.org", undefined); 93 | expect(promise).toBe(testMatrixServicePromise); 94 | })); 95 | 96 | it('should be able to /kick a user with a reason.', inject( 97 | function(commandsService) { 98 | spyOn(matrixService, 'kick').and.returnValue(testMatrixServicePromise); 99 | var promise = commandsService.processInput(roomId, "/kick @bob:matrix.org he smells"); 100 | expect(matrixService.kick).toHaveBeenCalledWith(roomId, "@bob:matrix.org", "he smells"); 101 | expect(promise).toBe(testMatrixServicePromise); 102 | })); 103 | 104 | it('should be able to /ban a user without a reason.', inject( 105 | function(commandsService) { 106 | spyOn(matrixService, 'ban').and.returnValue(testMatrixServicePromise); 107 | var promise = commandsService.processInput(roomId, "/ban @bob:matrix.org"); 108 | expect(matrixService.ban).toHaveBeenCalledWith(roomId, "@bob:matrix.org", undefined); 109 | expect(promise).toBe(testMatrixServicePromise); 110 | })); 111 | 112 | it('should be able to /ban a user with a reason.', inject( 113 | function(commandsService) { 114 | spyOn(matrixService, 'ban').and.returnValue(testMatrixServicePromise); 115 | var promise = commandsService.processInput(roomId, "/ban @bob:matrix.org he smells"); 116 | expect(matrixService.ban).toHaveBeenCalledWith(roomId, "@bob:matrix.org", "he smells"); 117 | expect(promise).toBe(testMatrixServicePromise); 118 | })); 119 | 120 | it('should be able to /unban a user.', inject( 121 | function(commandsService) { 122 | spyOn(matrixService, 'unban').and.returnValue(testMatrixServicePromise); 123 | var promise = commandsService.processInput(roomId, "/unban @bob:matrix.org"); 124 | expect(matrixService.unban).toHaveBeenCalledWith(roomId, "@bob:matrix.org"); 125 | expect(promise).toBe(testMatrixServicePromise); 126 | })); 127 | 128 | it('should be able to /op a user.', inject( 129 | function(commandsService) { 130 | spyOn(matrixService, 'setUserPowerLevel').and.returnValue(testMatrixServicePromise); 131 | var promise = commandsService.processInput(roomId, "/op @bob:matrix.org 50"); 132 | expect(matrixService.setUserPowerLevel).toHaveBeenCalledWith(roomId, "@bob:matrix.org", 50, testPowerLevelsEvent); 133 | expect(promise).toBe(testMatrixServicePromise); 134 | })); 135 | 136 | it('should be able to /deop a user.', inject( 137 | function(commandsService) { 138 | spyOn(matrixService, 'setUserPowerLevel').and.returnValue(testMatrixServicePromise); 139 | var promise = commandsService.processInput(roomId, "/deop @bob:matrix.org"); 140 | expect(matrixService.setUserPowerLevel).toHaveBeenCalledWith(roomId, "@bob:matrix.org", undefined, testPowerLevelsEvent); 141 | expect(promise).toBe(testMatrixServicePromise); 142 | })); 143 | 144 | // SYWEB-100 145 | it('should treat /commands as case-insensitive.', inject( 146 | function(commandsService) { 147 | spyOn(matrixService, 'kick').and.returnValue(testMatrixServicePromise); 148 | var promise = commandsService.processInput(roomId, "/KicK @bob:matrix.org"); 149 | expect(matrixService.kick).toHaveBeenCalledWith(roomId, "@bob:matrix.org", undefined); 150 | expect(promise).toBe(testMatrixServicePromise); 151 | })); 152 | }); 153 | -------------------------------------------------------------------------------- /syweb/webclient/test/unit/event-reaper-service.spec.js: -------------------------------------------------------------------------------- 1 | describe('EventReaperService', function() { 2 | var q, scope; 3 | 4 | // mock dependencies 5 | var matrixService = { 6 | roomInitialSync: function(roomId, limit) { 7 | return {}; 8 | } 9 | }; 10 | 11 | var modelService = { 12 | removeRoom: function(roomId){}, 13 | getRoom: function(roomId){return {};} 14 | }; 15 | 16 | var eventHandlerService = { 17 | wipeDuplicateDetection: function(roomId) {}, 18 | handleRoomInitialSync: function(room, response) {} 19 | }; 20 | 21 | var recentsService = { 22 | BROADCAST_SELECTED_ROOM_ID: "BROADCAST_SELECTED_ROOM_ID" 23 | }; 24 | 25 | // setup the dependencies 26 | beforeEach(function() { 27 | 28 | // reset test data 29 | 30 | 31 | // dependencies 32 | module(function ($provide) { 33 | $provide.value('matrixService', matrixService); 34 | $provide.value('modelService', modelService); 35 | $provide.value('recentsService', recentsService); 36 | $provide.value('eventHandlerService', eventHandlerService); 37 | }); 38 | 39 | // tested service 40 | module('eventReaperService'); 41 | }); 42 | 43 | beforeEach(inject(function($q, $rootScope) { 44 | q = $q; 45 | scope = $rootScope; 46 | })); 47 | 48 | it('should not reap rooms if it is not enabled.', inject( 49 | function(eventReaperService) { 50 | eventReaperService.setEnabled(false); 51 | 52 | var roomId = "!reaper:matrix.org"; 53 | 54 | var event = { 55 | content: {}, 56 | room_id: roomId, 57 | event_id: "f", 58 | type: "m.room.message" 59 | }; 60 | 61 | var eventsArray = [event]; 62 | spyOn(modelService, "getRoom").and.callFake(function(roomId) { 63 | return { 64 | events: eventsArray 65 | } 66 | }); 67 | 68 | spyOn(matrixService, "roomInitialSync").and.callFake(function() { 69 | return q.defer().promise; 70 | }); 71 | 72 | for (var i=0; i< eventReaperService.MAX_EVENTS + 1; i++) { 73 | eventsArray.push(event); 74 | scope.$broadcast(eventHandlerService.MSG_EVENT, event, true); 75 | scope.$digest(); 76 | } 77 | 78 | expect(matrixService.roomInitialSync).not.toHaveBeenCalled(); 79 | })); 80 | 81 | it('should be able to force reap a room, even if it is not enabled.', inject( 82 | function(eventReaperService) { 83 | eventReaperService.setEnabled(false); 84 | 85 | var roomId = "!reaper:matrix.org"; 86 | spyOn(modelService, "removeRoom"); 87 | spyOn(eventHandlerService, "wipeDuplicateDetection"); 88 | spyOn(eventHandlerService, "handleRoomInitialSync"); 89 | spyOn(matrixService, "roomInitialSync").and.callFake(function() { 90 | var defer = q.defer(); 91 | defer.resolve({ 92 | data: { 93 | state: [], 94 | messages: [], 95 | presence: [] 96 | } 97 | }); 98 | return defer.promise; 99 | }); 100 | 101 | eventReaperService.reap(roomId); 102 | scope.$digest(); 103 | 104 | expect(modelService.removeRoom).toHaveBeenCalledWith(roomId); 105 | expect(eventHandlerService.wipeDuplicateDetection).toHaveBeenCalledWith(roomId); 106 | expect(eventHandlerService.handleRoomInitialSync).toHaveBeenCalled(); 107 | })); 108 | 109 | it('should not reap the room being viewed currently.', inject( 110 | function(eventReaperService) { 111 | eventReaperService.setEnabled(true); 112 | 113 | var roomId = "!reaper:matrix.org"; 114 | scope.$broadcast(recentsService.BROADCAST_SELECTED_ROOM_ID, roomId); 115 | scope.$digest(); 116 | 117 | var event = { 118 | content: {}, 119 | room_id: roomId, 120 | event_id: "f", 121 | type: "m.room.message" 122 | }; 123 | 124 | var eventsArray = [event]; 125 | spyOn(modelService, "getRoom").and.callFake(function(roomId) { 126 | return { 127 | events: eventsArray 128 | } 129 | }); 130 | 131 | spyOn(matrixService, "roomInitialSync").and.callFake(function() { 132 | return q.defer().promise; 133 | }); 134 | 135 | for (var i=0; i< eventReaperService.MAX_EVENTS + 1; i++) { 136 | eventsArray.push(event); 137 | scope.$broadcast(eventHandlerService.MSG_EVENT, event, true); 138 | scope.$digest(); 139 | } 140 | 141 | expect(matrixService.roomInitialSync).not.toHaveBeenCalled(); 142 | })); 143 | 144 | it('should reap rooms with more than MAX_EVENTS events.', inject( 145 | function(eventReaperService) { 146 | eventReaperService.setEnabled(true); 147 | 148 | var roomId = "!reaper:matrix.org"; 149 | scope.$broadcast(recentsService.BROADCAST_SELECTED_ROOM_ID, "!a:b.com"); 150 | scope.$digest(); 151 | 152 | var aevent = { 153 | event: { 154 | content: {}, 155 | room_id: roomId, 156 | event_id: "f", 157 | type: "m.room.message" 158 | } 159 | }; 160 | 161 | var aeventsArray = [aevent]; 162 | spyOn(modelService, "getRoom").and.callFake(function(roomId) { 163 | return { 164 | aevents: aeventsArray 165 | } 166 | }); 167 | 168 | spyOn(matrixService, "roomInitialSync").and.callFake(function() { 169 | return q.defer().promise; 170 | }); 171 | 172 | for (var i=0; i< eventReaperService.MAX_EVENTS + 1; i++) { 173 | aeventsArray.push(aevent); 174 | scope.$broadcast(eventHandlerService.MSG_EVENT, aevent.event, true); 175 | scope.$digest(); 176 | } 177 | 178 | expect(matrixService.roomInitialSync).toHaveBeenCalled(); 179 | })); 180 | 181 | it('should not reap a room whilst a reap of that room is ongoing.', inject( 182 | function(eventReaperService) { 183 | var roomId = "!foo:bar"; 184 | spyOn(matrixService, "roomInitialSync").and.callFake(function() { 185 | return q.defer().promise; 186 | }); 187 | 188 | eventReaperService.reap(roomId); 189 | expect(matrixService.roomInitialSync).toHaveBeenCalled(); 190 | eventReaperService.reap(roomId); 191 | expect(matrixService.roomInitialSync.calls.count()).toEqual(1); 192 | })); 193 | }); 194 | -------------------------------------------------------------------------------- /syweb/webclient/test/unit/register-controller.spec.js: -------------------------------------------------------------------------------- 1 | describe("RegisterController ", function() { 2 | var rootScope, scope, ctrl, $q, $timeout; 3 | var userId = "@foo:bar"; 4 | var displayName = "Foo"; 5 | var avatarUrl = "avatar.url"; 6 | 7 | window.webClientConfig = { 8 | useCaptcha: false 9 | }; 10 | 11 | var dialogService = { 12 | showError: function(err){} // will be spyed 13 | }; 14 | 15 | // test vars 16 | var testRegisterData, testFailRegisterData; 17 | var testEmailLinkData, testFailEmailLinkData; 18 | var testEmailAuthData, testFailEmailAuthData 19 | 20 | 21 | // mock services 22 | var matrixService = { 23 | config: function() { 24 | return { 25 | user_id: userId 26 | } 27 | }, 28 | setConfig: function(){}, 29 | saveConfig: function(){}, 30 | linkEmail: function(){ 31 | var d = $q.defer(); 32 | if (testFailEmailLinkData) { 33 | d.reject({ 34 | data: testFailEmailLinkData 35 | }); 36 | } 37 | else { 38 | d.resolve({ 39 | data: testEmailLinkData 40 | }); 41 | } 42 | return d.promise; 43 | }, 44 | authEmail: function() { 45 | var d = $q.defer(); 46 | if (testFailEmailAuthData) { 47 | d.reject({ 48 | data: testFailEmailAuthData 49 | }); 50 | } 51 | else { 52 | d.resolve({ 53 | data: testEmailAuthData 54 | }); 55 | } 56 | return d.promise; 57 | }, 58 | register: function(mxid, password, threepidCreds, captchaResponse, sessionId, bind_email) { 59 | var d = $q.defer(); 60 | if (threepidCreds === true) d.reject({data: {}}); 61 | if (testFailRegisterData) { 62 | d.reject({ 63 | data: testFailRegisterData 64 | }); 65 | } 66 | else { 67 | d.resolve({ 68 | data: testRegisterData 69 | }); 70 | } 71 | return d.promise; 72 | } 73 | }; 74 | 75 | var eventStreamService = { 76 | resume: function(){} 77 | }; 78 | 79 | beforeEach(function() { 80 | module('matrixWebClient'); 81 | 82 | // reset test vars 83 | testRegisterData = undefined; 84 | testFailRegisterData = undefined; 85 | }); 86 | 87 | beforeEach(inject(function($rootScope, $injector, $location, $controller, _$q_, _$timeout_) { 88 | $q = _$q_; 89 | $timeout = _$timeout_; 90 | scope = $rootScope.$new(); 91 | rootScope = $rootScope; 92 | rootScope.onLoggedIn = function(){}; 93 | routeParams = { 94 | user_matrix_id: userId 95 | }; 96 | ctrl = $controller('RegisterController', { 97 | '$scope': scope, 98 | '$rootScope': $rootScope, 99 | '$location': $location, 100 | 'matrixService': matrixService, 101 | 'eventStreamService': eventStreamService, 102 | 'dialogService': dialogService 103 | }); 104 | }) 105 | ); 106 | 107 | // SYWEB-109 108 | it('should display an error if the HS rejects the username on registration', function() { 109 | var prevFeedback = angular.copy(scope.feedback); 110 | spyOn(dialogService, "showError"); 111 | 112 | testFailRegisterData = { 113 | errcode: "M_UNKNOWN", 114 | error: "I am rejecting you." 115 | }; 116 | 117 | scope.account.pwd1 = "password"; 118 | scope.account.pwd2 = "password"; 119 | scope.account.desired_user_id = "bob"; 120 | scope.register(); // this depends on the result of a deferred 121 | rootScope.$digest(); // which is delivered after the digest 122 | 123 | expect(dialogService.showError).toHaveBeenCalled(); 124 | }); 125 | 126 | it('should be able to register with just a user ID and password and save the response.', function() { 127 | var prevFeedback = angular.copy(scope.feedback); 128 | spyOn(matrixService, "register").and.callThrough(); 129 | spyOn(matrixService, "saveConfig"); 130 | 131 | testRegisterData = { 132 | user_id: "@bob:localhost", 133 | access_token: "abc123" 134 | }; 135 | 136 | scope.account.pwd1 = "password"; 137 | scope.account.pwd2 = "password"; 138 | scope.account.desired_user_id = "bob"; 139 | scope.register(); // this depends on the result of a deferred 140 | rootScope.$digest(); // which is delivered after the digest 141 | 142 | expect(matrixService.register).toHaveBeenCalledWith("bob", "password", undefined, undefined, undefined, true); 143 | expect(matrixService.saveConfig).toHaveBeenCalled(); 144 | }); 145 | 146 | it('should be able to register with an email and a user ID and password and save the response.', function() { 147 | var prevFeedback = angular.copy(scope.feedback); 148 | spyOn(matrixService, "register").and.callThrough(); 149 | spyOn(matrixService, "authEmail").and.callThrough(); 150 | spyOn(matrixService, "linkEmail").and.callThrough(); 151 | spyOn(matrixService, "saveConfig"); 152 | 153 | testEmailLinkData = { 154 | sid: "session_id" 155 | }; 156 | 157 | testEmailAuthData = { 158 | success: true 159 | }; 160 | 161 | testRegisterData = { 162 | user_id: "@bob:localhost", 163 | access_token: "abc123" 164 | }; 165 | 166 | // registration request 167 | scope.account.pwd1 = "password"; 168 | scope.account.pwd2 = "password"; 169 | scope.account.desired_user_id = "bob"; 170 | scope.account.email = "foo@bar.com"; 171 | scope.register(); // this depends on the result of a deferred 172 | rootScope.$digest(); // which is delivered after the digest 173 | 174 | expect(matrixService.register).toHaveBeenCalledWith("bob", "password", true); 175 | matrixService.register.calls.reset(); 176 | 177 | expect(scope.clientSecret).toBeDefined(); 178 | expect(matrixService.linkEmail).toHaveBeenCalledWith("foo@bar.com", scope.clientSecret, 1); // XXX what is sendAttempt? 179 | 180 | // token entry 181 | scope.verifyToken(); 182 | rootScope.$digest(); 183 | 184 | expect(matrixService.register).toHaveBeenCalledWith("bob", "password", 185 | jasmine.objectContaining({ 186 | sid: testEmailLinkData.sid, 187 | client_secret: scope.clientSecret 188 | }), undefined, undefined, true); 189 | expect(matrixService.saveConfig).toHaveBeenCalled(); 190 | }); 191 | }); 192 | -------------------------------------------------------------------------------- /syweb/webclient/app/home/home-controller.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 OpenMarket Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | angular.module('HomeController', ['matrixService', 'eventHandlerService', 'RecentsController']) 20 | .controller('HomeController', ['$scope', '$rootScope', '$location', 'matrixService', 'eventHandlerService', 'recentsService', 'dialogService', '$modal', 21 | function($scope, $rootScope, $location, matrixService, eventHandlerService, recentsService, dialogService, $modal) { 22 | 23 | $scope.config = matrixService.config(); 24 | $scope.httpUri = matrixService.getHttpUriForMxc; 25 | $scope.public_rooms = undefined; 26 | $scope.favourite_rooms = []; 27 | $scope.newRoomId = ""; 28 | $scope.feedback = ""; 29 | 30 | $scope.newRoom = { 31 | room_id: "", 32 | private: false 33 | }; 34 | 35 | $scope.joinAlias = { 36 | room_alias: "" 37 | }; 38 | 39 | $scope.profile = { 40 | displayName: "", 41 | avatarUrl: "" 42 | }; 43 | 44 | $scope.newChat = { 45 | user: "" 46 | }; 47 | 48 | recentsService.setSelectedRoomId(undefined); 49 | 50 | var refresh = function() { 51 | 52 | matrixService.publicRooms().then( 53 | function(response) { 54 | $scope.public_rooms = response.data.chunk; 55 | if (!$scope.public_rooms) return; 56 | for (var i = 0; i < $scope.public_rooms.length; i++) { 57 | var room = $scope.public_rooms[i]; 58 | 59 | if (room.aliases && room.aliases.length > 0) { 60 | room.room_display_name = room.aliases[0]; 61 | room.room_alias = room.aliases[0]; 62 | 63 | if (room.room_alias == "#matrix:matrix.org" || 64 | room.room_alias == "#matrix-dev:matrix.org" || 65 | room.room_alias == "#matrix-fr:matrix.org") 66 | { 67 | room.is_favourite = true; 68 | } 69 | } 70 | else if (room.name) { 71 | room.room_display_name = room.name; 72 | } 73 | else { 74 | room.room_display_name = room.room_id; 75 | } 76 | } 77 | } 78 | ); 79 | }; 80 | 81 | $scope.joinAlias = function(room_alias) { 82 | dialogService.showProgress(room_alias, "Joining room...", 100); 83 | eventHandlerService.joinRoom(room_alias).then(function(roomId) { 84 | $rootScope.$broadcast('dialogs.wait.complete'); 85 | $location.url("/room/" + room_alias); 86 | }, 87 | function(err) { 88 | $rootScope.$broadcast('dialogs.wait.complete'); 89 | dialogService.showError(err); 90 | }); 91 | 92 | }; 93 | 94 | // FIXME: factor this out between user-controller and home-controller etc. 95 | $scope.messageUser = function() { 96 | // FIXME: create a new room every time, for now 97 | 98 | eventHandlerService.createRoom(null, 'private', [$scope.newChat.user]).then( 99 | function(room_id) { 100 | console.log("Created room with id: "+ room_id); 101 | $location.url("/room/" + room_id); 102 | }, 103 | function(error) { 104 | dialogService.showError(error); 105 | } 106 | ); 107 | }; 108 | 109 | $scope.showCreateRoomDialog = function() { 110 | var modalInstance = $modal.open({ 111 | templateUrl: 'createRoomTemplate.html', 112 | controller: 'CreateRoomController', 113 | size: 'lg', 114 | scope: $scope 115 | }); 116 | }; 117 | 118 | 119 | $scope.onInit = function() { 120 | // Load profile data 121 | // Display name 122 | matrixService.getDisplayName($scope.config.user_id).then( 123 | function(response) { 124 | $scope.profile.displayName = response.data.displayname; 125 | var config = matrixService.config(); 126 | config.display_name = response.data.displayname; 127 | matrixService.setConfig(config); 128 | matrixService.saveConfig(); 129 | }, 130 | function(error) { 131 | $scope.feedback = "Can't load display name"; 132 | } 133 | ); 134 | // Avatar 135 | matrixService.getProfilePictureUrl($scope.config.user_id).then( 136 | function(response) { 137 | $scope.profile.avatarUrl = response.data.avatar_url; 138 | }, 139 | function(error) { 140 | $scope.feedback = "Can't load avatar URL"; 141 | } 142 | ); 143 | 144 | // Listen to room creation event in order to update the public rooms list 145 | $scope.$on(eventHandlerService.ROOM_CREATE_EVENT, function(ngEvent, event, isLive) { 146 | if (isLive) { 147 | // As we do not know if this room is public, do a full list refresh 148 | refresh(); 149 | } 150 | }); 151 | 152 | refresh(); 153 | }; 154 | 155 | // Clean data when user logs out 156 | $scope.$on(eventHandlerService.RESET_EVENT, function() { 157 | $scope.public_rooms = []; 158 | }); 159 | }]) 160 | .controller('CreateRoomController', ['$scope', '$location', '$modalInstance', 'eventHandlerService', 'dialogService', 161 | function($scope, $location, $modalInstance, eventHandlerService, dialogService) { 162 | $scope.newRoom = { 163 | isPublic: false, 164 | alias: "" 165 | }; 166 | 167 | $scope.create = function() { 168 | var isPublic = $scope.newRoom.isPublic ? "public" : "private"; 169 | var alias = $scope.newRoom.alias; 170 | if (alias.trim().length == 0) { 171 | alias = undefined; 172 | } 173 | if (alias) { 174 | var colonIndex = alias.indexOf(":"); 175 | if (colonIndex != -1) { 176 | alias = alias.substr(0, colonIndex); 177 | } 178 | } 179 | eventHandlerService.createRoom(alias, isPublic).then( 180 | function(roomId) { 181 | console.log("Created room with id: "+ roomId); 182 | $modalInstance.dismiss(); 183 | $location.url("/room/" + roomId); 184 | }, 185 | function(error) { 186 | $modalInstance.dismiss(); 187 | dialogService.showError(error); 188 | } 189 | ); 190 | }; 191 | }]); 192 | -------------------------------------------------------------------------------- /syweb/webclient/lib/angular-file-upload.min.js: -------------------------------------------------------------------------------- 1 | /*! 2.0.4 */ 2 | !function(){function a(a,b){window.XMLHttpRequest.prototype[a]=b(window.XMLHttpRequest.prototype[a])}function b(a,b,c,d,e,f){function g(a,b,c,d,e){f(function(){for(var b=[],g=0;g');c.multiple&&i.attr("multiple",c.multiple),c.accept&&i.attr("accept",c.accept),i.css("width","1px").css("height","1px").css("opacity",0).css("position","absolute").css("filter","alpha(opacity=0)").css("padding",0).css("margin",0).css("overflow","hidden").attr("tabindex","-1").attr("ng-file-generated-elem",!0),b.append(i),b.__afu_fileClickDelegate__=function(){i[0].click()},b.bind("click",b.__afu_fileClickDelegate__),b.css("overflow","hidden"),b=i}0!=a.resetOnClick()&&b.bind("click",function(e){b[0].value&&g([],c,d,a,e),b[0].value=null}),d&&a.$parent.$watch(c.ngModel,function(a){null==a&&(b[0].value=null)}),""!=c.ngFileSelect&&(a.change=a.select),b.bind("change",function(b){var e;e=b.__files_||b.target.files,g(e,c,d,a,b)})}function c(a,b,c,g,h,i,j){function k(a,b,c){var d=!0;if(r){var e=c.dataTransfer.items;if(null!=e)for(var f=0;f0&&"file"!=j.protocol())for(var m=0;m0)break}else{var p=a.dataTransfer.files;if(null!=p)for(var m=0;m0));m++);}var q=0;!function t(a){i(function(){if(s)10*q++<2e4&&t(10);else{if(!d&&h.length>1){for(var a=0;"directory"==h[a].type;)a++;h=[h[a]]}b(h,k)}},a||0)}();var s=0}var m=d();if(c.dropAvailable&&i(function(){a.dropAvailable=m}),!m)return 0!=a.hideOnDropNotAvailable()&&b.css("display","none"),void 0;var n=null,o=a.stopPropagation(),p=1,q=a.accept()||c.accept||c.ngAccept,r=q?new RegExp(f(q)):null;b[0].addEventListener("dragover",function(d){d.preventDefault(),o&&d.stopPropagation(),i.cancel(n),a.actualDragOverClass||(a.actualDragOverClass=k(a,c,d)),b.addClass(a.actualDragOverClass)},!1),b[0].addEventListener("dragenter",function(a){a.preventDefault(),o&&a.stopPropagation()},!1),b[0].addEventListener("dragleave",function(){n=i(function(){b.removeClass(a.actualDragOverClass),a.actualDragOverClass=null},p||1)},!1),""!=c.ngFileDrop&&(a.change=a.drop),b[0].addEventListener("drop",function(d){d.preventDefault(),o&&d.stopPropagation(),b.removeClass(a.actualDragOverClass),a.actualDragOverClass=null,l(d,function(b,e){g&&(a.fileModel=b,g&&g.$setViewValue(null!=b&&0==b.length?"":b)),c.ngFileRejectedModel&&(a.fileRejectedModel=e),i(function(){a.change({$files:b,$rejectedFiles:e,$event:d})})},0!=a.allowDir(),c.multiple||a.multiple()||"true"==c.ngMultiple)},!1)}function d(){var a=document.createElement("div");return"draggable"in a&&"ondrop"in a}function e(a){return/^[\000-\177]*$/.test(a)}function f(a){if(a.length>2&&"/"===a[0]&&"/"===a[a.length-1])return a.substring(1,a.length-1);var b=a.split(","),c="";if(b.length>1)for(var d=0;d|:\\-]","g"),"\\$&")+"$",c=c.replace(/\\\*/g,".*").replace(/\\\?/g,".");return c}window.XMLHttpRequest&&!window.XMLHttpRequest.__isFileAPIShim&&a("setRequestHeader",function(a){return function(b,c){if("__setXHR_"===b){var d=c(this);d instanceof Function&&d(this)}else a.apply(this,arguments)}});var g=angular.module("angularFileUpload",[]);g.version="2.0.4",g.service("$upload",["$http","$q","$timeout",function(a,b,c){function d(d){d.method=d.method||"POST",d.headers=d.headers||{},d.transformRequest=d.transformRequest||function(b,c){return window.ArrayBuffer&&b instanceof window.ArrayBuffer?b:a.defaults.transformRequest[0](b,c)};var e=b.defer(),f=e.promise;return d.headers.__setXHR_=function(){return function(a){a&&(d.__XHR=a,d.xhrFn&&d.xhrFn(a),a.upload.addEventListener("progress",function(a){a.config=d,e.notify?e.notify(a):f.progress_fn&&c(function(){f.progress_fn(a)})},!1),a.upload.addEventListener("load",function(a){a.lengthComputable&&(a.config=d,e.notify?e.notify(a):f.progress_fn&&c(function(){f.progress_fn(a)}))},!1))}},a(d).then(function(a){e.resolve(a)},function(a){e.reject(a)},function(a){e.notify(a)}),f.success=function(a){return f.then(function(b){a(b.data,b.status,b.headers,d)}),f},f.error=function(a){return f.then(null,function(b){a(b.data,b.status,b.headers,d)}),f},f.progress=function(a){return f.progress_fn=a,f.then(null,null,function(b){a(b)}),f},f.abort=function(){return d.__XHR&&c(function(){d.__XHR.abort()}),f},f.xhr=function(a){return d.xhrFn=function(b){return function(){b&&b.apply(f,arguments),a.apply(f,arguments)}}(d.xhrFn),f},f}this.upload=function(b){b.headers=b.headers||{},b.headers["Content-Type"]=void 0,b.transformRequest=b.transformRequest||a.defaults.transformRequest;var c=new FormData,e=b.transformRequest,f=b.data;return b.transformRequest=function(a,c){if(f)if(b.formDataAppender)for(var d in f){var g=f[d];b.formDataAppender(a,d,g)}else for(var d in f){var g=f[d];if("function"==typeof e)g=e(g,c);else for(var h=0;h