Commit 7ef9e631f17e55dbcf91b3a35eba946d3f88ccf3
1 parent
6b261ad25f
Exists in
master
and in
1 other branch
allow static dir
Showing 7 changed files with 522 additions and 40 deletions Inline Diff
.gitignore
View file @
7ef9e63
*~ | 1 | 1 | *~ | |
venv* | 2 | 2 | venv* | |
static* | 3 | |||
*.pyc | 4 | 3 | *.pyc | |
.idea* | 5 | 4 | .idea* | |
.*.swp | 6 | 5 | .*.swp | |
.*.swo | 7 | 6 | .*.swo | |
.*.swn | 8 | 7 | .*.swn | |
*.sqlite3 | 9 | 8 | *.sqlite3 | |
secrets | 10 | 9 | secrets | |
.coverage | 11 | 10 | .coverage | |
htmlcov* | 12 | 11 | htmlcov* | |
sass/*.css | 13 | 12 | sass/*.css |
Makefile
View file @
7ef9e63
all: styles/materialize.css styles/flashier.css fixjsstyle | 1 | 1 | all: styles/materialize.css styles/flashier.css fixjsstyle | |
2 | 2 | |||
JS_SOURCES = config.js $(wildcard scripts/*{Controller,Service,Directive}.js) | 3 | 3 | JS_SOURCES = config.js $(wildcard scripts/*Controller.js) $(wildcard scripts/*Directive.js) $(wildcard scripts/*Service.js) | |
4 | 4 | |||
5 | print-%: | |||
6 | @echo '$*=$($*)' | |||
7 | ||||
styles/materialize.css: .PHONY | 5 | 8 | styles/materialize.css: .PHONY | |
sassc sass/materialize.scss styles/materialize.css | 6 | 9 | sassc sass/materialize.scss styles/materialize.css | |
7 | 10 | |||
styles/flashier.css: sass/flashier.scss | 8 | 11 | styles/flashier.css: sass/flashier.scss | |
sassc sass/flashier.scss styles/flashier.css | 9 | 12 | sassc sass/flashier.scss styles/flashier.css | |
10 | 13 | |||
fixjsstyle: .PHONY | 11 | 14 | fixjsstyle: .PHONY | |
$(foreach sourcefile,$(JS_SOURCES),expand $(sourcefile) | sponge > $(sourcefile);) | 12 | 15 | $(foreach sourcefile,$(JS_SOURCES),expand $(sourcefile) | sponge $(sourcefile);) |
scripts/ResetPasswordController.js
View file @
7ef9e63
angular.module('flashy.ResetPasswordController', ['ui.router']). | 1 | 1 | angular.module('flashy.ResetPasswordController', ['ui.router']). | |
2 | 2 | |||
controller('ResetPasswordController', ['$scope', '$state', '$http', '$timeout', | 3 | 3 | controller('ResetPasswordController', ['$scope', '$state', '$http', '$timeout', | |
function($scope, $state, $http, $timeout) { | 4 | 4 | function($scope, $state, $http, $timeout) { | |
'use strict'; | 5 | 5 | 'use strict'; | |
var url = document.location.href.split('/'); | 6 | 6 | var url = document.location.href.split('/'); | |
var token = url[url.length - 1]; | 7 | 7 | var token = url[url.length - 1]; | |
var uid = url[url.length - 2]; | 8 | 8 | var uid = url[url.length - 2]; | |
$scope.error = false; | 9 | 9 | $scope.error = false; | |
$scope.success = false; | 10 | 10 | $scope.success = false; | |
$scope.mismatch = false; | 11 | 11 | $scope.mismatch = false; | |
$scope.unacceptable = false; | 12 | 12 | $scope.unacceptable = false; | |
/*if(token == 'resetpassword') { | 13 | 13 | /*if(token == 'resetpassword') { | |
$state.go('login'); | 14 | 14 | $state.go('login'); | |
}*/ | 15 | 15 | }*/ | |
console.log('RESETTING'); | 16 | 16 | console.log('RESETTING'); | |
$scope.confirmResetPass = function() { | 17 | 17 | $scope.confirmResetPass = function() { | |
if ($scope.newPassword.length < 8) { | 18 | 18 | if ($scope.newPassword.length < 8) { | |
$scope.unacceptable = true; | 19 | 19 | $scope.unacceptable = true; | |
return; | 20 | 20 | return; | |
} | 21 | 21 | } | |
if ($scope.newPassword != $scope.confirmPassword) { | 22 | 22 | if ($scope.newPassword != $scope.confirmPassword) { | |
$scope.mismatch = true; | 23 | 23 | $scope.mismatch = true; | |
$scope.confirmPassword.$setPristine(); | 24 | 24 | $scope.confirmPassword.$setPristine(); | |
console.log('mismatch'); | 25 | 25 | console.log('mismatch'); | |
return; | 26 | 26 | return; | |
} | 27 | 27 | } | |
/*password passes local tests*/ | 28 | 28 | /*password passes local tests*/ | |
$http.post('/api/reset_password/', JSON.stringify({ | 29 | 29 | $http.post('/api/reset_password/', JSON.stringify({ | |
'uid': uid, | 30 | 30 | 'uid': uid, | |
'token': token, | 31 | 31 | 'token': token, | |
'new_password': $scope.newPassword | 32 | 32 | 'new_password': $scope.newPassword | |
})) | 33 | 33 | })) | |
.success(function(data) { | 34 | 34 | .success(function(data) { | |
$scope.error = false; | 35 | 35 | $scope.error = false; | |
$scope.success = true; | 36 | 36 | $scope.success = true; | |
//$state.go('resetpasssuccess'); | 37 | 37 | //$state.go('resetpasssuccess'); | |
$timeout(function($state) { | 38 | 38 | $timeout(function($state) { | |
$state.go('login'); | 39 | 39 | $state.go('login'); | |
}, 1000); | 40 | 40 | }, 1000); | |
console.log(data); | 41 | 41 | console.log(data); | |
}) | 42 | 42 | }) | |
.error(function(data, status, header, config) { | 43 | 43 | .error(function(data, status, header, config) { | |
$scope.error = true; | 44 | 44 | $scope.error = true; | |
$scope.success = false; | 45 | 45 | $scope.success = false; | |
$scope.mismatch = false; | 46 | 46 | $scope.mismatch = false; | |
$scope.unacceptable = false; | 47 | 47 | $scope.unacceptable = false; | |
console.log(data); | 48 | 48 | console.log(data); | |
}); | 49 | 49 | }); | |
}; | 50 | 50 | }; | |
$scope.cancelReset = function() { | 51 | 51 | $scope.cancelReset = function() { | |
$state.go('login'); | 52 | 52 | $state.go('login'); | |
}; | 53 | 53 | }; | |
}]); | 54 | 54 | }]); | |
55 | 55 | |||
scripts/SettingsController.js
View file @
7ef9e63
angular.module('flashy.SettingsController', ['ui.router']). | 1 | 1 | angular.module('flashy.SettingsController', ['ui.router']). | |
2 | 2 | |||
controller('SettingsController', function($scope) { | 3 | 3 | controller('SettingsController', function($scope) { | |
4 | 4 | |||
5 | 5 | |||
$scope.changePassword = function(oldPassword, newPassword, confirmedNewPassword) { | 6 | 6 | $scope.changePassword = function(oldPassword, newPassword, confirmedNewPassword) { | |
7 | 7 | |||
8 | 8 | |||
9 | 9 |
scripts/StudyController.js
View file @
7ef9e63
angular.module('flashy.StudyController', ['ui.router']). | 1 | 1 | angular.module('flashy.StudyController', ['ui.router']). | |
2 | 2 | |||
controller('StudyController', ['$scope', '$stateParams', '$state', '$http', 'UserService', | 3 | 3 | controller('StudyController', ['$scope', '$stateParams', '$state', '$http', 'UserService', | |
function($scope, $stateParams, $state, $http, UserService) { | 4 | 4 | function($scope, $stateParams, $state, $http, UserService) { | |
console.log('Flashy study controller content in this file. also hell0'); | 5 | 5 | console.log('Flashy study controller content in this file. also hell0'); | |
sectionId = $stateParams.sectionId; | 6 | 6 | sectionId = $stateParams.sectionId; | |
$scope.isParamOpen = true; | 7 | 7 | $scope.isParamOpen = true; | |
8 | 8 | |||
$(document).ready(function() { | 9 | 9 | $(document).ready(function() { | |
$('.datepicker').pickadate({ | 10 | 10 | $('.datepicker').pickadate({ | |
selectMonths: true, // Creates a dropdown to control month | 11 | 11 | selectMonths: true, // Creates a dropdown to control month | |
selectYears: 15 // Creates a dropdown of 15 years to control year | 12 | 12 | selectYears: 15 // Creates a dropdown of 15 years to control year | |
}); | 13 | 13 | }); | |
14 | 14 | |||
$('select').material_select(); | 15 | 15 | $('select').material_select(); | |
}); | 16 | 16 | }); | |
17 | 17 | |||
$scope.UserService = UserService; | 18 | 18 | $scope.UserService = UserService; | |
$scope.isParamOpen = true; | 19 | 19 | $scope.isParamOpen = true; | |
20 | 20 | |||
console.log($scope.UserService.getUserData().sections); | 21 | 21 | console.log($scope.UserService.getUserData().sections); | |
22 | 22 | |||
$scope.toggleSectionToStudy = function(id) { | 23 | 23 | $scope.toggleSectionToStudy = function(id) { | |
console.log('toggle sect', id); | 24 | 24 | console.log('toggle sect', id); | |
$scope.sectionToStudy = id; | 25 | 25 | $scope.sectionToStudy = id; | |
}; | 26 | 26 | }; | |
27 | 27 | |||
$scope.openParams = function() { | 28 | 28 | $scope.openParams = function() { | |
$scope.isParamOpen = !$scope.isParamOpen; | 29 | 29 | $scope.isParamOpen = !$scope.isParamOpen; | |
}; | 30 | 30 | }; | |
31 | 31 | |||
$scope.fetchQuiz = function(a, b) { | 32 | 32 | $scope.fetchQuiz = function(a, b) { | |
//console.log($scope.startDate, $scope.endDate); | 33 | 33 | //console.log($scope.startDate, $scope.endDate); | |
console.log(a, b); | 34 | 34 | console.log(a, b); | |
}; | 35 | 35 | }; | |
36 | 36 | |||
/* | 37 | 37 | /* | |
$scope.fetchQuiz = function() { | 38 | 38 | $scope.fetchQuiz = function() { | |
console.log('fetching quiz...'); | 39 | 39 | console.log('fetching quiz...'); | |
var studyRequest = { | 40 | 40 | var studyRequest = { | |
'sections': ($scope.sectionToStudy == null) ? [] : [$scope.sectionToStudy], | 41 | 41 | 'sections': ($scope.sectionToStudy == null) ? [] : [$scope.sectionToStudy], | |
'material_date_begin':, | 42 | 42 | 'material_date_begin':, | |
'material_date_end': | 43 | 43 | 'material_date_end': | |
}; | 44 | 44 | }; | |
45 | 45 | |||
// $http.post('/api/study/', studyRequest). | 46 | 46 | // $http.post('/api/study/', studyRequest). | |
//TODO | 47 | 47 | //TODO | |
}; | 48 | 48 | }; | |
*/ | 49 | 49 | */ | |
50 | 50 | |||
/* OLD STUFF :in case you still neeed it */ | 51 | 51 | /* OLD STUFF :in case you still neeed it */ | |
// Flashcard content | 52 | 52 | // Flashcard content | |
$scope.htmlContent = 'sample text here longwordddddddddddddddddddddddddddd hello there from js review ctrl alwkejflakewjflk awjkefjkwefjlkea jfkewjaweajkakwef jk fjeawkafj kaewjf jawekfj akwejfk '; | 53 | 53 | $scope.htmlContent = 'sample text here longwordddddddddddddddddddddddddddd hello there from js review ctrl alwkejflakewjflk awjkefjkwefjlkea jfkewjaweajkakwef jk fjeawkafj kaewjf jawekfj akwejfk '; | |
//single card | 54 | 54 | //single card | |
$scope.samples = | 55 | 55 | $scope.samples = | |
{ | 56 | 56 | { | |
'name': 'lol', | 57 | 57 | 'name': 'lol', | |
'text': 'sample text here 111111 woo hoo I think it works', | 58 | 58 | 'text': 'sample text here 111111 woo hoo I think it works', | |
'mask': [[0, 6], [16, 23]] | 59 | 59 | 'mask': [[0, 6], [16, 23]] | |
}; | 60 | 60 | }; | |
61 | 61 | |||
// get text to display as array | 62 | 62 | // get text to display as array | |
$scope.displayText = []; | 63 | 63 | $scope.displayText = []; | |
// get answers to blanks as array | 64 | 64 | // get answers to blanks as array | |
$scope.blankText = []; | 65 | 65 | $scope.blankText = []; | |
var start = 0; // where to start next string break | 66 | 66 | var start = 0; // where to start next string break | |
for (var i = 0; i < $scope.samples.mask.length; i++) { | 67 | 67 | for (var i = 0; i < $scope.samples.mask.length; i++) { | |
$scope.displayText.push($scope.samples.text.substring(start, $scope.samples.mask[i][0])); | 68 | 68 | $scope.displayText.push($scope.samples.text.substring(start, $scope.samples.mask[i][0])); | |
$scope.blankText.push($scope.samples.text.substring($scope.samples.mask[i][0], $scope.samples.mask[i][1])); | 69 | 69 | $scope.blankText.push($scope.samples.text.substring($scope.samples.mask[i][0], $scope.samples.mask[i][1])); | |
start = $scope.samples.mask[i][1]; | 70 | 70 | start = $scope.samples.mask[i][1]; | |
} | 71 | 71 | } | |
if (start != $scope.samples.mask.length - 1) | 72 | 72 | if (start != $scope.samples.mask.length - 1) | |
$scope.displayText.push($scope.samples.text.substring(start)); | 73 | 73 | $scope.displayText.push($scope.samples.text.substring(start)); |
static/js/angular-contenteditable.js
View file @
7ef9e63
File was created | 1 | /** | ||
2 | * @see http://docs.angularjs.org/guide/concepts | |||
3 | * @see http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController | |||
4 | * @see https://github.com/angular/angular.js/issues/528#issuecomment-7573166 | |||
5 | */ | |||
6 | ||||
7 | angular.module('contenteditable', []) | |||
8 | .directive('contenteditable', ['$timeout', function($timeout) { return { | |||
9 | restrict: 'A', | |||
10 | require: '?ngModel', | |||
11 | link: function(scope, element, attrs, ngModel) { | |||
12 | // don't do anything unless this is actually bound to a model | |||
13 | if (!ngModel) { | |||
14 | return | |||
15 | } | |||
16 | ||||
17 | // options | |||
18 | var opts = {} | |||
19 | angular.forEach([ | |||
20 | 'stripBr', | |||
21 | 'noLineBreaks', | |||
22 | 'selectNonEditable', | |||
23 | 'moveCaretToEndOnChange', | |||
24 | ], function(opt) { | |||
25 | var o = attrs[opt] | |||
26 | opts[opt] = o && o !== 'false' | |||
27 | }) | |||
28 | ||||
29 | // view -> model | |||
30 | element.bind('input', function(e) { | |||
31 | scope.$apply(function() { | |||
32 | var html, html2, rerender | |||
33 | html = element.html() | |||
34 | rerender = false | |||
35 | if (opts.stripBr) { | |||
36 | html = html.replace(/<br>$/, '') | |||
37 | } | |||
38 | if (opts.noLineBreaks) { | |||
39 | html2 = html.replace(/<div>/g, '').replace(/<br>/g, '').replace(/<\/div>/g, '') | |||
40 | if (html2 !== html) { | |||
41 | rerender = true | |||
42 | html = html2 | |||
43 | } | |||
44 | } | |||
45 | ngModel.$setViewValue(html) | |||
46 | if (rerender) { | |||
47 | ngModel.$render() | |||
48 | } | |||
49 | if (html === '') { | |||
50 | // the cursor disappears if the contents is empty | |||
51 | // so we need to refocus | |||
52 | $timeout(function(){ | |||
53 | element[0].blur() | |||
54 | element[0].focus() | |||
55 | }) | |||
56 | } | |||
57 | }) | |||
58 | }) | |||
59 | ||||
60 | // model -> view | |||
61 | var oldRender = ngModel.$render | |||
62 | ngModel.$render = function() { | |||
63 | var el, el2, range, sel | |||
64 | if (!!oldRender) { | |||
65 | oldRender() | |||
66 | } | |||
67 | element.html(ngModel.$viewValue || '') | |||
68 | if (opts.moveCaretToEndOnChange) { | |||
69 | el = element[0] | |||
70 | range = document.createRange() | |||
71 | sel = window.getSelection() | |||
72 | if (el.childNodes.length > 0) { | |||
73 | el2 = el.childNodes[el.childNodes.length - 1] | |||
74 | range.setStartAfter(el2) | |||
75 | } else { | |||
76 | range.setStartAfter(el) | |||
77 | } | |||
78 | range.collapse(true) | |||
79 | sel.removeAllRanges() | |||
80 | sel.addRange(range) | |||
81 | } | |||
82 | } | |||
83 | if (opts.selectNonEditable) { | |||
84 | element.bind('click', function(e) { | |||
85 | var range, sel, target | |||
86 | target = e.toElement | |||
87 | if (target !== this && angular.element(target).attr('contenteditable') === 'false') { | |||
88 | range = document.createRange() | |||
89 | sel = window.getSelection() | |||
90 | range.setStartBefore(target) | |||
91 | range.setEndAfter(target) | |||
92 | sel.removeAllRanges() | |||
93 | sel.addRange(range) | |||
94 | } |
static/js/angular-websocket.js
View file @
7ef9e63
File was created | 1 | (function() { | ||
2 | 'use strict'; | |||
3 | ||||
4 | var noop = angular.noop; | |||
5 | var objectFreeze = (Object.freeze) ? Object.freeze : noop; | |||
6 | var objectDefineProperty = Object.defineProperty; | |||
7 | var isString = angular.isString; | |||
8 | var isFunction = angular.isFunction; | |||
9 | var isDefined = angular.isDefined; | |||
10 | var isObject = angular.isObject; | |||
11 | var isArray = angular.isArray; | |||
12 | var forEach = angular.forEach; | |||
13 | var arraySlice = Array.prototype.slice; | |||
14 | // ie8 wat | |||
15 | if (!Array.prototype.indexOf) { | |||
16 | Array.prototype.indexOf = function(elt /*, from*/) { | |||
17 | var len = this.length >>> 0; | |||
18 | var from = Number(arguments[1]) || 0; | |||
19 | from = (from < 0) ? Math.ceil(from) : Math.floor(from); | |||
20 | if (from < 0) { | |||
21 | from += len; | |||
22 | } | |||
23 | ||||
24 | for (; from < len; from++) { | |||
25 | if (from in this && this[from] === elt) { return from; } | |||
26 | } | |||
27 | return -1; | |||
28 | }; | |||
29 | } | |||
30 | ||||
31 | // $WebSocketProvider.$inject = ['$rootScope', '$q', '$timeout', '$websocketBackend']; | |||
32 | function $WebSocketProvider($rootScope, $q, $timeout, $websocketBackend) { | |||
33 | ||||
34 | function $WebSocket(url, protocols, options) { | |||
35 | if (!options && isObject(protocols) && !isArray(protocols)) { | |||
36 | options = protocols; | |||
37 | protocols = undefined; | |||
38 | } | |||
39 | ||||
40 | this.protocols = protocols; | |||
41 | this.url = url || 'Missing URL'; | |||
42 | this.ssl = /(wss)/i.test(this.url); | |||
43 | ||||
44 | // this.binaryType = ''; | |||
45 | // this.extensions = ''; | |||
46 | // this.bufferedAmount = 0; | |||
47 | // this.trasnmitting = false; | |||
48 | // this.buffer = []; | |||
49 | ||||
50 | // TODO: refactor options to use isDefined | |||
51 | this.scope = options && options.scope || $rootScope; | |||
52 | this.rootScopeFailover = options && options.rootScopeFailover && true; | |||
53 | this.useApplyAsync = options && options.useApplyAsync || false; | |||
54 | this.initialTimeout = options && options.initialTimeout || 500; // 500ms | |||
55 | this.maxTimeout = options && options.maxTimeout || 5 * 60 * 1000; // 5 minutes | |||
56 | this.reconnectIfNotNormalClose = options && options.reconnectIfNotNormalClose || false; | |||
57 | ||||
58 | this._reconnectAttempts = 0; | |||
59 | this.sendQueue = []; | |||
60 | this.onOpenCallbacks = []; | |||
61 | this.onMessageCallbacks = []; | |||
62 | this.onErrorCallbacks = []; | |||
63 | this.onCloseCallbacks = []; | |||
64 | ||||
65 | objectFreeze(this._readyStateConstants); | |||
66 | ||||
67 | if (url) { | |||
68 | this._connect(); | |||
69 | } else { | |||
70 | this._setInternalState(0); | |||
71 | } | |||
72 | ||||
73 | } | |||
74 | ||||
75 | ||||
76 | $WebSocket.prototype._readyStateConstants = { | |||
77 | 'CONNECTING': 0, | |||
78 | 'OPEN': 1, | |||
79 | 'CLOSING': 2, | |||
80 | 'CLOSED': 3, | |||
81 | 'RECONNECT_ABORTED': 4 | |||
82 | }; | |||
83 | ||||
84 | $WebSocket.prototype._normalCloseCode = 1000; | |||
85 | ||||
86 | $WebSocket.prototype._reconnectableStatusCodes = [ | |||
87 | 4000 | |||
88 | ]; | |||
89 | ||||
90 | $WebSocket.prototype.safeDigest = function safeDigest(autoApply) { | |||
91 | if (autoApply && !this.scope.$$phase) { | |||
92 | this.scope.$digest(); | |||
93 | } | |||
94 | }; | |||
95 | ||||
96 | $WebSocket.prototype.bindToScope = function bindToScope(scope) { | |||
97 | var self = this; | |||
98 | if (scope) { | |||
99 | this.scope = scope; | |||
100 | if (this.rootScopeFailover) { | |||
101 | this.scope.$on('$destroy', function() { | |||
102 | self.scope = $rootScope; | |||
103 | }); | |||
104 | } | |||
105 | } | |||
106 | return self; | |||
107 | }; | |||
108 | ||||
109 | $WebSocket.prototype._connect = function _connect(force) { | |||
110 | if (force || !this.socket || this.socket.readyState !== this._readyStateConstants.OPEN) { | |||
111 | this.socket = $websocketBackend.create(this.url, this.protocols); | |||
112 | this.socket.onmessage = angular.bind(this, this._onMessageHandler); | |||
113 | this.socket.onopen = angular.bind(this, this._onOpenHandler); | |||
114 | this.socket.onerror = angular.bind(this, this._onErrorHandler); | |||
115 | this.socket.onclose = angular.bind(this, this._onCloseHandler); | |||
116 | } | |||
117 | }; | |||
118 | ||||
119 | $WebSocket.prototype.fireQueue = function fireQueue() { | |||
120 | while (this.sendQueue.length && this.socket.readyState === this._readyStateConstants.OPEN) { | |||
121 | var data = this.sendQueue.shift(); | |||
122 | ||||
123 | this.socket.send( | |||
124 | isString(data.message) ? data.message : JSON.stringify(data.message) | |||
125 | ); | |||
126 | data.deferred.resolve(); | |||
127 | } | |||
128 | }; | |||
129 | ||||
130 | $WebSocket.prototype.notifyOpenCallbacks = function notifyOpenCallbacks(event) { | |||
131 | for (var i = 0; i < this.onOpenCallbacks.length; i++) { | |||
132 | this.onOpenCallbacks[i].call(this, event); | |||
133 | } | |||
134 | }; | |||
135 | ||||
136 | $WebSocket.prototype.notifyCloseCallbacks = function notifyCloseCallbacks(event) { | |||
137 | for (var i = 0; i < this.onCloseCallbacks.length; i++) { | |||
138 | this.onCloseCallbacks[i].call(this, event); | |||
139 | } | |||
140 | }; | |||
141 | ||||
142 | $WebSocket.prototype.notifyErrorCallbacks = function notifyErrorCallbacks(event) { | |||
143 | for (var i = 0; i < this.onErrorCallbacks.length; i++) { | |||
144 | this.onErrorCallbacks[i].call(this, event); | |||
145 | } | |||
146 | }; | |||
147 | ||||
148 | $WebSocket.prototype.onOpen = function onOpen(cb) { | |||
149 | this.onOpenCallbacks.push(cb); | |||
150 | return this; | |||
151 | }; | |||
152 | ||||
153 | $WebSocket.prototype.onClose = function onClose(cb) { | |||
154 | this.onCloseCallbacks.push(cb); | |||
155 | return this; | |||
156 | }; | |||
157 | ||||
158 | $WebSocket.prototype.onError = function onError(cb) { | |||
159 | this.onErrorCallbacks.push(cb); | |||
160 | return this; | |||
161 | }; | |||
162 | ||||
163 | ||||
164 | $WebSocket.prototype.onMessage = function onMessage(callback, options) { | |||
165 | if (!isFunction(callback)) { | |||
166 | throw new Error('Callback must be a function'); | |||
167 | } | |||
168 | ||||
169 | if (options && isDefined(options.filter) && !isString(options.filter) && !(options.filter instanceof RegExp)) { | |||
170 | throw new Error('Pattern must be a string or regular expression'); | |||
171 | } | |||
172 | ||||
173 | this.onMessageCallbacks.push({ | |||
174 | fn: callback, | |||
175 | pattern: options ? options.filter : undefined, | |||
176 | autoApply: options ? options.autoApply : true | |||
177 | }); | |||
178 | return this; | |||
179 | }; | |||
180 | ||||
181 | $WebSocket.prototype._onOpenHandler = function _onOpenHandler(event) { | |||
182 | this._reconnectAttempts = 0; | |||
183 | this.notifyOpenCallbacks(event); | |||
184 | this.fireQueue(); | |||
185 | }; | |||
186 | ||||
187 | $WebSocket.prototype._onCloseHandler = function _onCloseHandler(event) { | |||
188 | this.notifyCloseCallbacks(event); | |||
189 | if ((this.reconnectIfNotNormalClose && event.code !== this._normalCloseCode) || this._reconnectableStatusCodes.indexOf(event.code) > -1) { | |||
190 | this.reconnect(); | |||
191 | } | |||
192 | }; | |||
193 | ||||
194 | $WebSocket.prototype._onErrorHandler = function _onErrorHandler(event) { | |||
195 | this.notifyErrorCallbacks(event); | |||
196 | }; | |||
197 | ||||
198 | $WebSocket.prototype._onMessageHandler = function _onMessageHandler(message) { | |||
199 | var pattern; | |||
200 | var self = this; | |||
201 | var currentCallback; | |||
202 | for (var i = 0; i < self.onMessageCallbacks.length; i++) { | |||
203 | currentCallback = self.onMessageCallbacks[i]; | |||
204 | pattern = currentCallback.pattern; | |||
205 | if (pattern) { | |||
206 | if (isString(pattern) && message.data === pattern) { | |||
207 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); | |||
208 | } | |||
209 | else if (pattern instanceof RegExp && pattern.exec(message.data)) { | |||
210 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); | |||
211 | } | |||
212 | } | |||
213 | else { | |||
214 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); | |||
215 | } | |||
216 | } | |||
217 | ||||
218 | function applyAsyncOrDigest(callback, autoApply, args) { | |||
219 | args = arraySlice.call(arguments, 2); | |||
220 | if (self.useApplyAsync) { | |||
221 | self.scope.$applyAsync(function() { | |||
222 | callback.apply(self, args); | |||
223 | }); | |||
224 | } else { | |||
225 | callback.apply(self, args); | |||
226 | self.safeDigest(autoApply); | |||
227 | } | |||
228 | } | |||
229 | ||||
230 | }; | |||
231 | ||||
232 | $WebSocket.prototype.close = function close(force) { | |||
233 | if (force || !this.socket.bufferedAmount) { | |||
234 | this.socket.close(); | |||
235 | } | |||
236 | return this; | |||
237 | }; | |||
238 | ||||
239 | $WebSocket.prototype.send = function send(data) { | |||
240 | var deferred = $q.defer(); | |||
241 | var self = this; | |||
242 | var promise = cancelableify(deferred.promise); | |||
243 | ||||
244 | if (self.readyState === self._readyStateConstants.RECONNECT_ABORTED) { | |||
245 | deferred.reject('Socket connection has been closed'); | |||
246 | } | |||
247 | else { | |||
248 | self.sendQueue.push({ | |||
249 | message: data, | |||
250 | deferred: deferred | |||
251 | }); | |||
252 | self.fireQueue(); | |||
253 | } | |||
254 | ||||
255 | // Credit goes to @btford | |||
256 | function cancelableify(promise) { | |||
257 | promise.cancel = cancel; | |||
258 | var then = promise.then; | |||
259 | promise.then = function() { | |||
260 | var newPromise = then.apply(this, arguments); | |||
261 | return cancelableify(newPromise); | |||
262 | }; | |||
263 | return promise; | |||
264 | } | |||
265 | ||||
266 | function cancel(reason) { | |||
267 | self.sendQueue.splice(self.sendQueue.indexOf(data), 1); | |||
268 | deferred.reject(reason); | |||
269 | return self; | |||
270 | } | |||
271 | ||||
272 | if ($websocketBackend.isMocked && $websocketBackend.isMocked() && | |||
273 | $websocketBackend.isConnected(this.url)) { | |||
274 | this._onMessageHandler($websocketBackend.mockSend()); | |||
275 | } | |||
276 | ||||
277 | return promise; | |||
278 | }; | |||
279 | ||||
280 | $WebSocket.prototype.reconnect = function reconnect() { | |||
281 | this.close(); | |||
282 | ||||
283 | var backoffDelay = this._getBackoffDelay(++this._reconnectAttempts); | |||
284 | ||||
285 | var backoffDelaySeconds = backoffDelay / 1000; | |||
286 | console.log('Reconnecting in ' + backoffDelaySeconds + ' seconds'); | |||
287 | ||||
288 | $timeout(angular.bind(this, this._connect), backoffDelay); | |||
289 | ||||
290 | return this; | |||
291 | }; | |||
292 | // Exponential Backoff Formula by Prof. Douglas Thain | |||
293 | // http://dthain.blogspot.co.uk/2009/02/exponential-backoff-in-distributed.html | |||
294 | $WebSocket.prototype._getBackoffDelay = function _getBackoffDelay(attempt) { | |||
295 | var R = Math.random() + 1; | |||
296 | var T = this.initialTimeout; | |||
297 | var F = 2; | |||
298 | var N = attempt; | |||
299 | var M = this.maxTimeout; | |||
300 | ||||
301 | return Math.floor(Math.min(R * T * Math.pow(F, N), M)); | |||
302 | }; | |||
303 | ||||
304 | $WebSocket.prototype._setInternalState = function _setInternalState(state) { | |||
305 | if (Math.floor(state) !== state || state < 0 || state > 4) { | |||
306 | throw new Error('state must be an integer between 0 and 4, got: ' + state); | |||
307 | } | |||
308 | ||||
309 | // ie8 wat | |||
310 | if (!objectDefineProperty) { | |||
311 | this.readyState = state || this.socket.readyState; | |||
312 | } | |||
313 | this._internalConnectionState = state; | |||
314 | ||||
315 | ||||
316 | forEach(this.sendQueue, function(pending) { | |||
317 | pending.deferred.reject('Message cancelled due to closed socket connection'); | |||
318 | }); |