Initial snapshot.
This commit is contained in:
211
static/cosmopolite.js
Normal file
211
static/cosmopolite.js
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
Copyright 2014, Ian Gulliver
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
var cosmopolite = {};
|
||||
|
||||
cosmopolite.Client = function(opt_callbacks, opt_urlPrefix, opt_namespace) {
|
||||
this.callbacks_ = opt_callbacks || {};
|
||||
this.urlPrefix_ = opt_urlPrefix || '/cosmopolite';
|
||||
this.namespace_ = opt_namespace || 'cosmopolite';
|
||||
|
||||
this.stateCache_ = {};
|
||||
|
||||
var scripts = [
|
||||
'https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js',
|
||||
'/_ah/channel/jsapi',
|
||||
];
|
||||
this.numScriptsToLoad_ = scripts.length;
|
||||
for (var i = 0; i < scripts.length; i++) {
|
||||
var script = document.createElement('script');
|
||||
script.src = scripts[i];
|
||||
script.onload = this.onLoad_.bind(this);
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.onLoad_ = function() {
|
||||
if (--this.numScriptsToLoad_ > 0) {
|
||||
return;
|
||||
}
|
||||
this.$ = jQuery.noConflict(true);
|
||||
this.registerMessageHandlers_();
|
||||
this.getUser_();
|
||||
this.createChannel_();
|
||||
};
|
||||
|
||||
// Message from another browser window
|
||||
cosmopolite.Client.prototype.onReceiveMessage_ = function(data) {
|
||||
switch (data) {
|
||||
case 'login_complete':
|
||||
this.getUser_();
|
||||
break;
|
||||
case 'logout_complete':
|
||||
localStorage.removeItem(this.namespace_ + ':client_id');
|
||||
localStorage.removeItem(this.namespace_ + ':google_user_id');
|
||||
this.$('#google_user').empty();
|
||||
this.getUser_();
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown message type');
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.registerMessageHandlers_ = function() {
|
||||
this.$(window).on('message', this.$.proxy(function(e) {
|
||||
if (e.originalEvent.origin != window.location.origin) {
|
||||
console.log(
|
||||
'Received message from bad origin: ' + e.originalEvent.origin);
|
||||
return;
|
||||
}
|
||||
console.log('Received message: ' + e.originalEvent.data);
|
||||
this.onReceiveMessage_(e.originalEvent.data);
|
||||
}, this));
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.sendRPC_ = function(command, data, onSuccess, delay) {
|
||||
if (this.namespace_ + ':client_id' in localStorage) {
|
||||
data['client_id'] = localStorage[this.namespace_ + ':client_id'];
|
||||
}
|
||||
if (this.namespace_ + ':google_user_id' in localStorage) {
|
||||
data['google_user_id'] = localStorage[this.namespace_ + ':google_user_id'];
|
||||
}
|
||||
this.$.ajax({
|
||||
url: this.urlPrefix_ + '/api/' + command,
|
||||
type: 'post',
|
||||
data: data,
|
||||
dataType: 'json',
|
||||
context: this,
|
||||
})
|
||||
.done(function(data, stat, xhr) {
|
||||
if ('google_user_id' in data) {
|
||||
localStorage[this.namespace_ + ':google_user_id'] =
|
||||
data['google_user_id'];
|
||||
}
|
||||
if ('client_id' in data) {
|
||||
localStorage[this.namespace_ + ':client_id'] = data['client_id'];
|
||||
}
|
||||
if (data['status'] == 'retry') {
|
||||
// Discard delay
|
||||
this.sendRPC_(command, data, onSuccess);
|
||||
return;
|
||||
}
|
||||
if (data['status'] != 'ok') {
|
||||
console.log(
|
||||
'Server returned unknown status (' + data['status'] + ') for RPC '
|
||||
+ command);
|
||||
// TODO(flamingcow): Refresh the page? Show an alert?
|
||||
return;
|
||||
}
|
||||
if (onSuccess) {
|
||||
this.$.proxy(onSuccess, this)(data.response);
|
||||
}
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
var intDelay =
|
||||
xhr.getResponseHeader('Retry-After') ||
|
||||
Math.min(32, Math.max(2, delay || 2));
|
||||
console.log(
|
||||
'RPC ' + command + ' failed. Will retry in ' + intDelay + ' seconds');
|
||||
function retry() {
|
||||
this.sendRPC_(command, data, onSuccess, Math.pow(intDelay, 2));
|
||||
}
|
||||
window.setTimeout(this.$.proxy(retry, this), intDelay * 1000);
|
||||
});
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.getUser_ = function() {
|
||||
this.sendRPC_('getUser', {}, function(data) {
|
||||
if ('google_user' in data) {
|
||||
if ('onLogin' in this.callbacks_) {
|
||||
this.callbacks_['onLogin'](
|
||||
data['google_user'],
|
||||
this.urlPrefix_ + '/auth/logout');
|
||||
}
|
||||
} else {
|
||||
if ('onLogout' in this.callbacks_) {
|
||||
this.callbacks_['onLogout'](
|
||||
this.urlPrefix_ + '/auth/login');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.setValue = function(key, value) {
|
||||
this.sendRPC_('setValue', {
|
||||
'key': key,
|
||||
'value': value,
|
||||
})
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.getValue = function(key) {
|
||||
return this.stateCache_[key];
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.createChannel_ = function() {
|
||||
this.sendRPC_('createChannel', {}, this.onCreateChannel_);
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.onCreateChannel_ = function(data) {
|
||||
var channel = new goog.appengine.Channel(data['token']);
|
||||
console.log('Opening channel...');
|
||||
this.socket = channel.open({
|
||||
onopen: this.$.proxy(this.onSocketOpen_, this),
|
||||
onclose: this.$.proxy(this.onSocketClose_, this),
|
||||
onmessage: this.$.proxy(this.onSocketMessage_, this),
|
||||
onerror: this.$.proxy(this.onSocketError_, this),
|
||||
});
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.onSocketOpen_ = function() {
|
||||
console.log('Channel opened');
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.onSocketClose_ = function() {
|
||||
if (!this.socket) {
|
||||
return;
|
||||
}
|
||||
console.log('Channel closed');
|
||||
this.socket = null;
|
||||
this.createChannel_();
|
||||
};
|
||||
|
||||
cosmopolite.Client.prototype.onSocketMessage_ = function(msg) {
|
||||
console.log('message: ' + msg.data);
|
||||
var parsed = JSON.parse(msg.data);
|
||||
switch (parsed.message_type) {
|
||||
case 'state':
|
||||
var key = parsed['key'];
|
||||
var value = parsed['value'];
|
||||
if (this.stateCache_[key] == value) {
|
||||
// Duplicate message.
|
||||
break;
|
||||
}
|
||||
this.stateCache_[key] = value;
|
||||
if ('onStateChange' in this.callbacks_) {
|
||||
this.callbacks_['onStateChange'](key, value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown message type: ' + parsed.message_type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cosmopolite.Client.prototype.onSocketError_ = function(msg) {
|
||||
console.log('Socket error: ' + msg);
|
||||
this.socket.close();
|
||||
};
|
||||
79
static/debug.html
Normal file
79
static/debug.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="/cosmopolite/static/cosmopolite.js" charset="UTF-8"></script>
|
||||
<style>
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>Google user: <span id="google_user"></span></div>
|
||||
|
||||
<div>
|
||||
Key:
|
||||
<select id="keys">
|
||||
<option></option>
|
||||
<option>(add new)</option>
|
||||
</select>
|
||||
<input type="button" id="set" value="Set">
|
||||
<br />
|
||||
<textarea id="value" rows="10" cols="40"></textarea>
|
||||
</div>
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
var googleUser = document.getElementById('google_user');
|
||||
var keys = document.getElementById('keys');
|
||||
|
||||
var callbacks = {
|
||||
onLogin: function(username, logout_url) {
|
||||
googleUser.innerHTML =
|
||||
username +
|
||||
' <a href="' + logout_url + '" target="_blank">(log out)</a>';
|
||||
},
|
||||
onLogout: function(login_url) {
|
||||
googleUser.innerHTML =
|
||||
'<a href="' + login_url + '" target="_blank">(log in)</a>';
|
||||
},
|
||||
onStateChange: function(key, value) {
|
||||
for (var i = 0; i < keys.options.length; ++i) {
|
||||
if (keys.options[i].value == key) {
|
||||
break;
|
||||
};
|
||||
if (keys.options[i].value > key) {
|
||||
var option = document.createElement('option');
|
||||
option.text = key;
|
||||
keys.options.add(option, i);
|
||||
break;
|
||||
};
|
||||
};
|
||||
if (i == keys.options.length) {
|
||||
var option = document.createElement('option');
|
||||
option.text = key;
|
||||
keys.options.add(option);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var debug = new cosmopolite.Client(callbacks);
|
||||
|
||||
document.getElementById('set').addEventListener('click', function() {
|
||||
debug.setValue(
|
||||
document.getElementById('keys').value,
|
||||
document.getElementById('value').value);
|
||||
});
|
||||
|
||||
document.getElementById('keys').addEventListener('change', function() {
|
||||
if (this.value == '(add new)') {
|
||||
var new_key = prompt('New key name:');
|
||||
callbacks['onStateChange'](new_key, '');
|
||||
this.value = new_key;
|
||||
return;
|
||||
}
|
||||
var value = document.getElementById('value');
|
||||
value.value = debug.getValue(this.value);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
8
static/login_complete.html
Normal file
8
static/login_complete.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
window.opener.postMessage('login_complete', window.location.origin);
|
||||
window.close();
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
||||
8
static/logout_complete.html
Normal file
8
static/logout_complete.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript">
|
||||
window.opener.postMessage('logout_complete', window.location.origin);
|
||||
window.close();
|
||||
</script>
|
||||
</head>
|
||||
</html>
|
||||
Reference in New Issue
Block a user