New Plugin: Sync
Use Google Realtime API, store key value pair in CollaborativeMap. Allow plugin to sync data between client.
This commit is contained in:
parent
13d1ff9aaa
commit
bf9fcd29e4
7
external/gapi.js
vendored
Normal file
7
external/gapi.js
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
var gapi=window.gapi=window.gapi||{};gapi._bs=new Date().getTime();(function(){var f=null,g=encodeURIComponent,k=window,m=decodeURIComponent,n="push",r="test",t="shift",u="replace",y="length",B="split",C="join";var D=k,E=document,aa=D.location,ba=function(){},ca=/\[native code\]/,G=function(a,b,c){return a[b]=a[b]||c},da=function(a){for(var b=0;b<this[y];b++)if(this[b]===a)return b;return-1},ea=function(a){a=a.sort();for(var b=[],c=void 0,d=0;d<a[y];d++){var e=a[d];e!=c&&b[n](e);c=e}return b},H=function(){var a;if((a=Object.create)&&ca[r](a))a=a(f);else{a={};for(var b in a)a[b]=void 0}return a},I=G(D,"gapi",{});var J;J=G(D,"___jsl",H());G(J,"I",0);G(J,"hel",10);var K=function(){var a=aa.href,b;if(J.dpo)b=J.h;else{b=J.h;var c=RegExp("([#].*&|[#])jsh=([^&#]*)","g"),d=RegExp("([?#].*&|[?#])jsh=([^&#]*)","g");if(a=a&&(c.exec(a)||d.exec(a)))try{b=m(a[2])}catch(e){}}return b},fa=function(a){var b=G(J,"PQ",[]);J.PQ=[];var c=b[y];if(0===c)a();else for(var d=0,e=function(){++d===c&&a()},h=0;h<c;h++)b[h](e)},L=function(a){return G(G(J,"H",H()),a,H())};var M=G(J,"perf",H()),N=G(M,"g",H()),ga=G(M,"i",H());G(M,"r",[]);H();H();var O=function(a,b,c){var d=M.r;"function"===typeof d?d(a,b,c):d[n]([a,b,c])},Q=function(a,b,c){b&&0<b[y]&&(b=P(b),c&&0<c[y]&&(b+="___"+P(c)),28<b[y]&&(b=b.substr(0,28)+(b[y]-28)),c=b,b=G(ga,"_p",H()),G(b,c,H())[a]=(new Date).getTime(),O(a,"_p",c))},P=function(a){return a[C]("__")[u](/\./g,"_")[u](/\-/g,"_")[u](/\,/g,"_")};var S=H(),T=[],U=function(a){throw Error("Bad hint"+(a?": "+a:""));};T[n](["jsl",function(a){for(var b in a)if(Object.prototype.hasOwnProperty.call(a,b)){var c=a[b];"object"==typeof c?J[b]=G(J,b,[]).concat(c):G(J,b,c)}if(b=a.u)a=G(J,"us",[]),a[n](b),(b=/^https:(.*)$/.exec(b))&&a[n]("http:"+b[1])}]);var ha=/^(\/[a-zA-Z0-9_\-]+)+$/,ia=/^[a-zA-Z0-9\-_\.!]+$/,ja=/^gapi\.loaded_[0-9]+$/,ka=/^[a-zA-Z0-9,._-]+$/,oa=function(a,b,c,d){var e=a[B](";"),h=S[e[t]()],l=f;h&&(l=h(e,b,c,d));if(!(b=!l))b=l,c=b.match(la),d=b.match(ma),b=!(d&&1===d[y]&&na[r](b)&&c&&1===c[y]);b&&U(a);return l},qa=function(a,b,c,d){a=pa(a);ja[r](c)||U("invalid_callback");b=V(b);d=d&&d[y]?V(d):f;var e=function(a){return g(a)[u](/%2C/g,",")};return[g(a.d)[u](/%2C/g,",")[u](/%2F/g,"/"),"/k=",e(a.version),"/m=",e(b),d?"/exm="+e(d):
|
||||
"","/rt=j/sv=1/d=1/ed=1",a.a?"/am="+e(a.a):"",a.b?"/rs="+e(a.b):"","/cb=",e(c)][C]("")},pa=function(a){"/"!==a.charAt(0)&&U("relative path");for(var b=a.substring(1)[B]("/"),c=[];b[y];){a=b[t]();if(!a[y]||0==a.indexOf("."))U("empty/relative directory");else if(0<a.indexOf("=")){b.unshift(a);break}c[n](a)}a={};for(var d=0,e=b[y];d<e;++d){var h=b[d][B]("="),l=m(h[0]),p=m(h[1]);2!=h[y]||(!l||!p)||(a[l]=a[l]||p)}b="/"+c[C]("/");ha[r](b)||U("invalid_prefix");c=W(a,"k",!0);d=W(a,"am");a=W(a,"rs");return{d:b,
|
||||
version:c,a:d,b:a}},V=function(a){for(var b=[],c=0,d=a[y];c<d;++c){var e=a[c][u](/\./g,"_")[u](/-/g,"_");ka[r](e)&&b[n](e)}return b[C](",")},W=function(a,b,c){a=a[b];!a&&c&&U("missing: "+b);if(a){if(ia[r](a))return a;U("invalid: "+b)}return f},na=/^https?:\/\/[a-z0-9_.-]+\.google\.com(:\d+)?\/[a-zA-Z0-9_.,!=\-\/]+$/,ma=/\/cb=/g,la=/\/\//g,ra=function(){var a=K();if(!a)throw Error("Bad hint");return a};S.m=function(a,b,c,d){(a=a[0])||U("missing_hint");return"https://apis.google.com"+qa(a,b,c,d)};var X=decodeURI("%73cript"),Y=function(a,b){for(var c=[],d=0;d<a[y];++d){var e=a[d];e&&0>da.call(b,e)&&c[n](e)}return c},sa=function(a){"loading"!=E.readyState?Z(a):E.write("<"+X+' src="'+encodeURI(a)+'"></'+X+">")},Z=function(a){var b=E.createElement(X);b.setAttribute("src",a);b.async="true";(a=E.getElementsByTagName(X)[0])?a.parentNode.insertBefore(b,a):(E.head||E.body||E.documentElement).appendChild(b)},ta=function(a,b){var c=b&&b._c;if(c)for(var d=0;d<T[y];d++){var e=T[d][0],h=T[d][1];h&&Object.prototype.hasOwnProperty.call(c,
|
||||
e)&&h(c[e],a,b)}},ua=function(a,b){$(function(){var c;c=b===K()?G(I,"_",H()):H();c=G(L(b),"_",c);a(c)})},wa=function(a,b){var c=b||{};"function"==typeof b&&(c={},c.callback=b);ta(a,c);var d=a?a[B](":"):[],e=c.h||ra(),h=G(J,"ah",H());if(!h["::"]||!d[y])va(d||[],c,e);else{for(var l=[],p=f;p=d[t]();){var v=p[B]("."),v=h[p]||h[v[1]&&"ns:"+v[0]||""]||e,s=l[y]&&l[l[y]-1]||f,z=s;if(!s||s.hint!=v)z={hint:v,c:[]},l[n](z);z.c[n](p)}var A=l[y];if(1<A){var F=c.callback;F&&(c.callback=function(){0==--A&&F()})}for(;d=
|
||||
l[t]();)va(d.c,c,d.hint)}},va=function(a,b,c){a=ea(a)||[];var d=b.callback,e=b.config,h=b.timeout,l=b.ontimeout,p=f,v=!1;if(h&&!l||!h&&l)throw"Timeout requires both the timeout parameter and ontimeout parameter to be set";var s=G(L(c),"r",[]).sort(),z=G(L(c),"L",[]).sort(),A=[].concat(s),F=function(a,b){if(v)return 0;D.clearTimeout(p);z[n].apply(z,q);var d=((I||{}).config||{}).update;d?d(e):e&&G(J,"cu",[])[n](e);if(b){Q("me0",a,A);try{ua(b,c)}finally{Q("me1",a,A)}}return 1};0<h&&(p=D.setTimeout(function(){v=
|
||||
!0;l()},h));var q=Y(a,z);if(q[y]){var q=Y(a,s),w=G(J,"CP",[]),x=w[y];w[x]=function(a){if(!a)return 0;Q("ml1",q,A);var b=function(b){w[x]=f;F(q,a)&&fa(function(){d&&d();b()})},c=function(){var a=w[x+1];a&&a()};0<x&&w[x-1]?w[x]=function(){b(c)}:b(c)};if(q[y]){var R="loaded_"+J.I++;I[R]=function(a){w[x](a);I[R]=f};a=oa(c,q,"gapi."+R,s);s[n].apply(s,q);Q("ml0",q,A);b.sync||D.___gapisync?sa(a):Z(a)}else w[x](ba)}else F(q)&&d&&d()};var $=function(a){if(J.hee&&0<J.hel)try{return a()}catch(b){J.hel--,wa("debug_error",function(){k.___jsl.hefn(b)})}else return a()};I.load=function(a,b){return $(function(){return wa(a,b)})};N.bs0=k.gapi._bs||(new Date).getTime();O("bs0");N.bs1=(new Date).getTime();O("bs1");delete k.gapi._bs;})();
|
||||
gapi.load("",{callback:window["gapi_onload"],_c:{"jsl":{"ci":{"services":{},"deviceType":"desktop","lexps":[102,103,100,71,98,96,110,108,79,106,45,17,86,81,61,30],"inline":{"css":1},"report":{},"oauth-flow":{"disableOpt":true,"authUrl":"https://accounts.google.com/o/oauth2/auth","proxyUrl":"https://accounts.google.com/o/oauth2/postmessageRelay","persist":true},"isLoggedIn":false,"isPlusUser":false,"iframes":{"additnow":{"methods":["launchurl"],"url":"https://apis.google.com/additnow/additnow.html?bsv"},"shortlists":{"url":"?bsv"},"plus":{"methods":["onauth"],"url":":socialhost:/u/:session_index:/_/pages/badge?bsv"},":socialhost:":"https://plusone.google.com","recobox":{"params":{"url":""},"url":":socialhost:/:session_prefix:_/widget/render/recobox?bsv"},"plus_followers":{"params":{"url":""},"url":":socialhost:/_/im/_/widget/render/plus/followers?bsv"},"autocomplete":{"params":{"url":""},"url":":socialhost:/:session_prefix:_/widget/render/autocomplete?bsv"},"plus_share":{"params":{"url":""},"url":":socialhost:/:session_prefix:_/+1/sharebutton?plusShare\u003dtrue\u0026bsv"},"savetowallet":{"url":"https://clients5.google.com/s2w/o/savetowallet?bsv"},"panoembed":{"url":"https://ssl.gstatic.com/pano/embed/?bsv"},"signin":{"methods":["onauth"],"params":{"url":""},"url":":socialhost:/:session_prefix:_/widget/render/signin?bsv"},"appcirclepicker":{"url":":socialhost:/:session_prefix:_/widget/render/appcirclepicker?bsv"},"commentcount":{"url":":socialhost:/:session_prefix:_/widget/render/commentcount?bsv"},"hangout":{"url":"https://talkgadget.google.com/:session_prefix:talkgadget/_/widget?bsv"},"plus_circle":{"params":{"url":""},"url":":socialhost:/:session_prefix:_/widget/plus/circle?bsv"},"savetodrive":{"methods":["save"],"url":"https://drive.google.com/savetodrivebutton?usegapi\u003d1\u0026bsv"},"card":{"url":":socialhost:/:session_prefix:_/hovercard/card?bsv"},"evwidget":{"params":{"url":""},"url":":socialhost:/:session_prefix:_/events/widget?bsv"},"zoomableimage":{"url":"https://ssl.gstatic.com/microscope/embed/?bsv"},":signuphost:":"https://plus.google.com","plusone":{"preloadUrl":["https://ssl.gstatic.com/s2/oz/images/stars/po/Publisher/sprite4-a67f741843ffc4220554c34bd01bb0bb.png"],"params":{"count":"","size":"","url":""},"url":":socialhost:/:session_prefix:_/+1/fastbutton?bsv"},"comments":{"methods":["scroll","openwindow"],"params":{"location":["search","hash"]},"url":":socialhost:/:session_prefix:_/widget/render/comments?bsv"}},"debug":{"host":"https://plusone.google.com","reportExceptionRate":0.05,"rethrowException":true},"csi":{"rate":0.01},"googleapis.config":{"mobilesignupurl":"https://m.google.com/app/plus/oob?"}},"h":"m;/_/scs/apps-static/_/js/k\u003doz.gapi.zh_TW.xyvCdLj6RQA.O/m\u003d__features__/am\u003dUQ/rt\u003dj/d\u003d1/rs\u003dAItRSTO4MhJ2bKBUNwOawgunNqLfYJSZ-w","u":"https://apis.google.com/js/api.js","hee":true,"fp":"25aeff8fd7610bb7da2b71719ff2ece079a140b7","dpo":false},"fp":"25aeff8fd7610bb7da2b71719ff2ece079a140b7","annotation":["autocomplete","profile","interactivepost"],"bimodal":["signin"]}});
|
536
plugins/sync.user.js
Normal file
536
plugins/sync.user.js
Normal file
@ -0,0 +1,536 @@
|
||||
// ==UserScript==
|
||||
// @id iitc-plugin-sync@xelio
|
||||
// @name IITC plugin: Sync
|
||||
// @version 0.1.0.@@DATETIMEVERSION@@
|
||||
// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
|
||||
// @updateURL @@UPDATEURL@@
|
||||
// @downloadURL @@DOWNLOADURL@@
|
||||
// @description [@@BUILDNAME@@-@@BUILDDATE@@] Sync data between clients
|
||||
// @include https://www.ingress.com/intel*
|
||||
// @include http://www.ingress.com/intel*
|
||||
// @match https://www.ingress.com/intel*
|
||||
// @match http://www.ingress.com/intel*
|
||||
// ==/UserScript==
|
||||
|
||||
function wrapper() {
|
||||
// ensure plugin framework is there, even if iitc is not yet loaded
|
||||
if(typeof window.plugin !== 'function') window.plugin = function() {};
|
||||
|
||||
|
||||
// PLUGIN START ////////////////////////////////////////////////////////
|
||||
|
||||
// use own namespace for plugin
|
||||
window.plugin.sync = function() {};
|
||||
|
||||
window.plugin.sync.KEY_UUID = {key: 'plugin-sync-data-uuid', field: 'uuid'};
|
||||
|
||||
// Each client has an unique UUID, to identify remote data is udpated by other clients or not
|
||||
window.plugin.sync.uuid = null;
|
||||
|
||||
window.plugin.sync.dialogHTML = null;
|
||||
window.plugin.sync.authorizer = null;
|
||||
|
||||
// Store registered CollaborativeMap
|
||||
window.plugin.sync.registerdPluginsFields = null;
|
||||
|
||||
// Other plugin call this function to push update to Google Realtime API
|
||||
// example:
|
||||
// plugin.sync.updateMap('keys', 'keysdata', ['guid1', 'guid2', 'guid3'])
|
||||
// Which will push plugin.keys.keysdata['guid1'] etc. to Google Realtime API
|
||||
window.plugin.sync.updateMap = function(pluginName, fieldName, keyArray) {
|
||||
var registeredMap = plugin.sync.registerdPluginsFields.get(pluginName, fieldName);
|
||||
if(!registeredMap) return false;
|
||||
registeredMap.updateMap(keyArray);
|
||||
}
|
||||
|
||||
// Other plugin call this to register a field as CollaborativeMap to sync with Google Realtime API
|
||||
// example: plugin.sync.registerMapForSync('keys', 'keysdata', plugin.keys.updateCallback, plugin.keys.initializedCallback)
|
||||
// which register plugin.keys.keysdata
|
||||
//
|
||||
// updateCallback function format: function(pluginName, fieldName, eventObejct)
|
||||
// updateCallback will be fired when local or remote pushed update to Google Realtime API
|
||||
// eventObject is a ValueChangedEvent, refer to following url
|
||||
// https://developers.google.com/drive/realtime/reference/gapi.drive.realtime.ValueChangedEvent
|
||||
//
|
||||
// initializedCallback funciton format: function(pluginName, fieldName)
|
||||
// initializedCallback will be fired when the CollaborativeMap finished initialize and good to use
|
||||
window.plugin.sync.registerMapForSync = function(pluginName, fieldName, callback, initializedCallback) {
|
||||
var options, registeredMap;
|
||||
options = {'pluginName': pluginName,
|
||||
'fieldName': fieldName,
|
||||
'callback': callback,
|
||||
'initializedCallback': initializedCallback,
|
||||
'authorizer': plugin.sync.authorizer,
|
||||
'uuid': plugin.sync.uuid};
|
||||
registeredMap = new plugin.sync.RegisteredMap(options);
|
||||
plugin.sync.registerdPluginsFields.add(registeredMap);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//// RegisteredMap
|
||||
// Create a file named pluginName[fieldName] in folder specified by authorizer
|
||||
// The file use as realtime document with CollaborativeMap to store the data and a
|
||||
// CollaborativeString to store uuid of last update client
|
||||
// callback will called when any local/remote update happen
|
||||
// initializedCallback will called when RegisteredMap initialized and good to use.
|
||||
window.plugin.sync.RegisteredMap = function(options) {
|
||||
this.pluginName = options['pluginName'];
|
||||
this.fieldName = options['fieldName'];
|
||||
this.callback = options['callback'];
|
||||
this.initializedCallback = options['initializedCallback'];
|
||||
this.authorizer = options['authorizer'];
|
||||
this.uuid = options['uuid'];
|
||||
this.fileId = null;
|
||||
this.doc = null;
|
||||
this.model = null;
|
||||
this.map = null;
|
||||
this.lastUpdateUUID = null;
|
||||
this.initializing = false;
|
||||
this.initialized = false;
|
||||
this.updateListener = this.updateListener.bind(this);
|
||||
this.initialize = this.initialize.bind(this);
|
||||
}
|
||||
|
||||
window.plugin.sync.RegisteredMap.prototype.updateMap = function(keyArray) {
|
||||
var _this = this;
|
||||
// Use compound operation to ensure update pushed as a batch
|
||||
this.model.beginCompoundOperation();
|
||||
// Remove before set text to ensure full text change
|
||||
this.lastUpdateUUID.removeRange(0, this.lastUpdateUUID.length);
|
||||
this.lastUpdateUUID.setText(this.uuid);
|
||||
|
||||
$.each(keyArray, function(ind, key) {
|
||||
var value = window.plugin[_this.pluginName][_this.fieldName][key];
|
||||
if(typeof(value) !== 'undefined') {
|
||||
_this.map.set(key, value);
|
||||
} else {
|
||||
_this.map.delete(key);
|
||||
}
|
||||
});
|
||||
this.model.endCompoundOperation();
|
||||
}
|
||||
|
||||
window.plugin.sync.RegisteredMap.prototype.isUpdatedByOthers = function() {
|
||||
return this.lastUpdateUUID.toString() !== this.uuid;
|
||||
}
|
||||
|
||||
window.plugin.sync.RegisteredMap.prototype.getFileName = function() {
|
||||
return this.pluginName + '[' + this.fieldName + ']'
|
||||
}
|
||||
|
||||
window.plugin.sync.RegisteredMap.prototype.searchOrCreateFile = function(callback) {
|
||||
var queryOption, createOption, searchCallBack, _this;
|
||||
_this = this;
|
||||
|
||||
queryOption = 'title = "' + this.getFileName() +'" and "' + this.authorizer.folderId + '" in parents and trashed = false';
|
||||
createOption = {'convert': 'false'
|
||||
, 'ocr': 'false'
|
||||
, 'resource': {'title': this.getFileName(),
|
||||
'description': 'IITC plugin data for ' + this.getFileName(),
|
||||
'mimeType': 'application/vnd.google-apps.drive-sdk',
|
||||
'parents': [{'id': this.authorizer.folderId}]
|
||||
}
|
||||
};
|
||||
searchCallBack = function(resp) {
|
||||
if(resp.items) {
|
||||
_this.fileId = resp.items[0].id;
|
||||
if(callback) callback();
|
||||
} else {
|
||||
plugin.sync.createFileOrFolder(createOption, function(resp) {
|
||||
if (resp.id) {
|
||||
_this.fileId = resp.id;
|
||||
if(callback) callback();
|
||||
} else {
|
||||
_this.initializing = false;
|
||||
console.log('Plugin Sync: Could not create file ' + _this.getFileName());
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
plugin.sync.searchFileOrFolder(queryOption, searchCallBack);
|
||||
}
|
||||
|
||||
window.plugin.sync.RegisteredMap.prototype.updateListener = function(e) {
|
||||
console.log(e);
|
||||
if(!e.isLocal) {
|
||||
if(!window.plugin[this.pluginName][this.fieldName]) {
|
||||
window.plugin[this.pluginName][this.fieldName] = {};
|
||||
}
|
||||
if(typeof(e.newValue) !== 'undefined' && e.newValue !== null) {
|
||||
window.plugin[this.pluginName][this.fieldName][e.property] = e.newValue;
|
||||
} else {
|
||||
delete window.plugin[this.pluginName][this.fieldName][e.property];
|
||||
}
|
||||
}
|
||||
if(this.callback) this.callback(this.pluginName, this.fieldName, e);
|
||||
}
|
||||
|
||||
window.plugin.sync.RegisteredMap.prototype.initialize = function(callback) {
|
||||
this.initializing = true;
|
||||
var initRealtime, initializeModel, onFileLoaded, handleError, _this;
|
||||
_this = this;
|
||||
|
||||
initRealtime = function() {
|
||||
gapi.drive.realtime.load(_this.fileId, onFileLoaded, initializeModel, handleError);
|
||||
};
|
||||
|
||||
// this function called when the document is created first time
|
||||
// and the CollaborativeMap is populated with data in plugin field
|
||||
initializeModel = function(model) {
|
||||
var map = model.createMap();
|
||||
var lastUpdateUUID = model.createString();
|
||||
|
||||
// Init the map values if this map is first created
|
||||
$.each(window.plugin[_this.pluginName][_this.fieldName], function(key, val) {
|
||||
map.set(key, val);
|
||||
});
|
||||
lastUpdateUUID.setText(_this.uuid);
|
||||
|
||||
model.getRoot().set('map', map);
|
||||
model.getRoot().set('last-udpate-uuid', lastUpdateUUID);
|
||||
console.log(_this.pluginName + '[' + _this.fieldName + ']' + ': model initialized');
|
||||
};
|
||||
|
||||
// this function called when the document is loaded
|
||||
// update local data if the document is updated by other
|
||||
// and add update listener to CollaborativeMap
|
||||
onFileLoaded = function(doc) {
|
||||
_this.doc = doc;
|
||||
_this.model = doc.getModel();
|
||||
_this.map = doc.getModel().getRoot().get("map");
|
||||
_this.lastUpdateUUID = doc.getModel().getRoot().get("last-udpate-uuid");
|
||||
_this.map.addEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, _this.updateListener);
|
||||
|
||||
// Replace local value if data is changed by others
|
||||
if(_this.isUpdatedByOthers()) {
|
||||
console.log(_this.pluginName + '[' + _this.fieldName + ']' + ': updated by others, replacing content.');
|
||||
window.plugin[_this.pluginName][_this.fieldName] = {};
|
||||
$.each(_this.map.keys(), function(ind, key) {
|
||||
window.plugin[_this.pluginName][_this.fieldName][key] = _this.map.get(key);
|
||||
});
|
||||
}
|
||||
|
||||
_this.initialized = true;
|
||||
_this.initializing = false;
|
||||
console.log(_this.pluginName + '[' + _this.fieldName + ']' + ': data loaded');
|
||||
if(callback) callback();
|
||||
if(_this.initializedCallback) _this.initializedCallback(_this.pluginName, _this.fieldName);
|
||||
};
|
||||
|
||||
// Stop the sync if any error occur and try to re-authorize
|
||||
handleError = function(e) {
|
||||
_this.initializing = false;
|
||||
console.log('handle error');
|
||||
_this.stopSync();
|
||||
_this.authorizer.authorize();
|
||||
console.log('Realtime API Error: ' + e.type);
|
||||
};
|
||||
|
||||
this.searchOrCreateFile(initRealtime);
|
||||
}
|
||||
|
||||
window.plugin.sync.RegisteredMap.prototype.stopSync = function() {
|
||||
this.map.removeEventListener(gapi.drive.realtime.EventType.VALUE_CHANGED, this.updateListener);
|
||||
this.fileId = null;
|
||||
this.doc = null;
|
||||
this.model = null;
|
||||
this.map = null;
|
||||
this.lastUpdateUUID = null;
|
||||
this.initializing = false;
|
||||
this.initialized = false;
|
||||
plugin.sync.registerdPluginsFields.addToWaitingInitialize(this.pluginName, this.fieldName);
|
||||
}
|
||||
//// end RegisteredMap
|
||||
|
||||
|
||||
|
||||
|
||||
//// registerdPluginsFields
|
||||
// Store RegisteredMap and handle initialization of RegisteredMap
|
||||
window.plugin.sync.registerdPluginsFields = function(options) {
|
||||
this.authorizer = options['authorizer'];
|
||||
this.pluginsfields = {};
|
||||
this.waitingInitialize = [];
|
||||
this.initializeRegistered = this.initializeRegistered.bind(this);
|
||||
this.cleanWaitingInitialize = this.cleanWaitingInitialize.bind(this);
|
||||
this.initializeWorker = this.initializeWorker.bind(this);
|
||||
this.authorizer.addAuthCallback(this.initializeRegistered);
|
||||
}
|
||||
|
||||
window.plugin.sync.registerdPluginsFields.prototype.add = function(registeredMap) {
|
||||
var pluginName, fieldName;
|
||||
pluginName = registeredMap.pluginName;
|
||||
fieldName = registeredMap.fieldName;
|
||||
this.pluginsfields[pluginName] = this.pluginsfields[pluginName] || {};
|
||||
|
||||
if(this.pluginsfields[pluginName][fieldName]) return false;
|
||||
|
||||
this.pluginsfields[pluginName][fieldName] = registeredMap;
|
||||
this.waitingInitialize.push(registeredMap);
|
||||
|
||||
this.initializeWorker();
|
||||
}
|
||||
|
||||
window.plugin.sync.registerdPluginsFields.prototype.addToWaitingInitialize = function(pluginName, fieldName) {
|
||||
var registeredMap = this.get(pluginName, fieldName);
|
||||
if(!registeredMap) return;
|
||||
this.waitingInitialize.push(registeredMap);
|
||||
|
||||
this.initializeWorker();
|
||||
}
|
||||
|
||||
window.plugin.sync.registerdPluginsFields.prototype.get = function(pluginName, fieldName) {
|
||||
if(!this.pluginsfields[pluginName]) return;
|
||||
return this.pluginsfields[pluginName][fieldName];
|
||||
}
|
||||
|
||||
window.plugin.sync.registerdPluginsFields.prototype.initializeRegistered = function() {
|
||||
var _this = this;
|
||||
if(this.authorizer.isAuthed()) {
|
||||
$.each(this.waitingInitialize, function(ind, map) {
|
||||
if(!map.initializing && !map.initialized) {
|
||||
map.initialize(_this.cleanWaitingInitialize);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.plugin.sync.registerdPluginsFields.prototype.cleanWaitingInitialize = function() {
|
||||
this.waitingInitialize = $.grep(this.waitingInitialize, function(map, ind) {return !map.initialized;});
|
||||
}
|
||||
|
||||
window.plugin.sync.registerdPluginsFields.prototype.initializeWorker = function() {
|
||||
var _this = this;
|
||||
|
||||
if(this.authorizer.isAuthed()) {
|
||||
this.initializeRegistered();
|
||||
}
|
||||
|
||||
clearTimeout(this.timer);
|
||||
if(this.waitingInitialize.length > 0) {
|
||||
this.timer = setTimeout(function() {_this.initializeWorker()}, 10000);
|
||||
}
|
||||
}
|
||||
//// end registerdPluginsFields
|
||||
|
||||
|
||||
|
||||
|
||||
//// Authorizer
|
||||
// authorize user google account and create a folder 'IITC-SYNC-DATA' to store Realtime document
|
||||
window.plugin.sync.Authorizer = function(options) {
|
||||
this.authCallback = options['authCallback'];
|
||||
this.folderId = null;
|
||||
this.authorize = this.authorize.bind(this);
|
||||
}
|
||||
|
||||
window.plugin.sync.Authorizer.prototype.CLIENT_ID = '686674438052.apps.googleusercontent.com';
|
||||
window.plugin.sync.Authorizer.prototype.SCOPES = ['https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive.metadata.readonly'];
|
||||
|
||||
window.plugin.sync.Authorizer.prototype.isAuthed = function() {
|
||||
return this.folderId !== null;
|
||||
}
|
||||
|
||||
window.plugin.sync.Authorizer.prototype.addAuthCallback = function(callback) {
|
||||
if(typeof(this.authCallback) === 'function') this.authCallback = [this.authCallback];
|
||||
this.authCallback.push(callback);
|
||||
}
|
||||
|
||||
window.plugin.sync.Authorizer.prototype.authComplete = function() {
|
||||
if(this.authCallback) {
|
||||
if(typeof(this.authCallback) === 'function') this.authCallback();
|
||||
if(this.authCallback instanceof Array && this.authCallback.length > 0) {
|
||||
$.each(this.authCallback, function(ind, func) {
|
||||
func();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.plugin.sync.Authorizer.prototype.initFolder = function(callback) {
|
||||
var queryOption, createOption, searchCallBack, _this;
|
||||
_this = this;
|
||||
|
||||
queryOption = 'title = "IITC-SYNC-DATA" and mimeType = "application/vnd.google-apps.folder" and trashed = false';
|
||||
createOption = {'convert': 'false'
|
||||
, 'ocr': 'false'
|
||||
, 'resource': {'title': 'IITC-SYNC-DATA',
|
||||
'description': 'Store IITC sync data',
|
||||
'mimeType': 'application/vnd.google-apps.folder'
|
||||
}
|
||||
};
|
||||
searchCallBack = function(resp) {
|
||||
if(resp.items) {
|
||||
_this.folderId = resp.items[0].id;
|
||||
if(callback) callback();
|
||||
_this.authComplete();
|
||||
} else {
|
||||
plugin.sync.createFileOrFolder(createOption, function(resp) {
|
||||
if (resp.id) {
|
||||
_this.folderId = resp.id;
|
||||
if(callback) callback();
|
||||
} else {
|
||||
console.log('Plugin Sync: Could not create folder "IITC-SYNC-DATA"');
|
||||
}
|
||||
_this.authComplete();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
plugin.sync.searchFileOrFolder(queryOption, searchCallBack);
|
||||
}
|
||||
|
||||
window.plugin.sync.Authorizer.prototype.authorize = function(popup, callback) {
|
||||
var handleAuthResult, _this;
|
||||
_this = this;
|
||||
|
||||
handleAuthResult = function(authResult) {
|
||||
if(authResult && !authResult.error) {
|
||||
_this.initFolder(callback);
|
||||
} else {
|
||||
_this.folderId = null;
|
||||
_this.authComplete();
|
||||
console.log('Plugin Sync: Authorization error.');
|
||||
}
|
||||
};
|
||||
|
||||
gapi.auth.authorize({'client_id': this.CLIENT_ID, 'scope': this.SCOPES, 'immediate': !popup}
|
||||
, handleAuthResult);
|
||||
}
|
||||
//// end Authorizer
|
||||
|
||||
window.plugin.sync.createFileOrFolder = function(option, callback) {
|
||||
gapi.client.load('drive', 'v2', function() {
|
||||
gapi.client.drive.files.insert(option).execute(callback);
|
||||
});
|
||||
}
|
||||
|
||||
window.plugin.sync.searchFileOrFolder = function(queryOption, callback) {
|
||||
gapi.client.load('drive', 'v2', function() {
|
||||
var option = {'q': queryOption};
|
||||
gapi.client.drive.files.list(option).execute(callback);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// http://stackoverflow.com/a/8809472/2322660
|
||||
// http://stackoverflow.com/a/7221797/2322660
|
||||
// With format fixing: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx where y in [8,9,a,b]
|
||||
window.plugin.sync.generateUUID = function() {
|
||||
if(window.crypto) {
|
||||
var buf = new Uint16Array(8);
|
||||
window.crypto.getRandomValues(buf);
|
||||
var S4 = function(num) {
|
||||
var ret = num.toString(16);
|
||||
return "000".substring(0, 4-ret.length) + ret;
|
||||
};
|
||||
var yxxx = function(num) {
|
||||
return num&0x3fff|0x8000;
|
||||
}
|
||||
return (S4(buf[0])+S4(buf[1])+"-"+S4(buf[2])+"-4"+S4(buf[3]).substring(1)+"-"+S4(yxxx(buf[4]))+"-"+S4(buf[5])+S4(buf[6])+S4(buf[7]));
|
||||
} else {
|
||||
var d = new Date().getTime();
|
||||
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = (d + Math.random()*16)%16 | 0;
|
||||
d = Math.floor(d/16);
|
||||
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
|
||||
});
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
||||
window.plugin.sync.storeLocal = function(mapping) {
|
||||
if(typeof(plugin.sync[mapping.field]) !== 'undefined' && plugin.sync[mapping.field] !== null) {
|
||||
localStorage[mapping.key] = JSON.stringify(plugin.sync[mapping.field]);
|
||||
} else {
|
||||
localStorage.removeItem(mapping.key);
|
||||
}
|
||||
}
|
||||
|
||||
window.plugin.sync.loadLocal = function(mapping) {
|
||||
var objectJSON = localStorage[mapping.key];
|
||||
if(!objectJSON) return;
|
||||
plugin.sync[mapping.field] = mapping.convertFunc
|
||||
? mapping.convertFunc(JSON.parse(objectJSON))
|
||||
: JSON.parse(objectJSON);
|
||||
}
|
||||
|
||||
window.plugin.sync.loadUUID = function() {
|
||||
plugin.sync.loadLocal(plugin.sync.KEY_UUID);
|
||||
if(!plugin.sync.uuid) {
|
||||
plugin.sync.uuid = plugin.sync.generateUUID();
|
||||
plugin.sync.storeLocal(plugin.sync.KEY_UUID);
|
||||
}
|
||||
}
|
||||
|
||||
window.plugin.sync.loadGapi = function() {
|
||||
try { console.log('Loading Gapi JS now'); } catch(e) {}
|
||||
@@INCLUDERAW:external/gapi.js@@
|
||||
try { console.log('done loading delaunay JS'); } catch(e) {}
|
||||
}
|
||||
|
||||
window.plugin.sync.toggleAuthButton = function() {
|
||||
var authed = plugin.sync.authorizer.isAuthed();
|
||||
$('#sync-authButton').attr('disabled', authed);
|
||||
$('#sync-authButton').html(authed ? 'Authorized' : 'Authorize');
|
||||
if(authed) {
|
||||
$('#sync-authButton').addClass('sync-authButton-authed');
|
||||
$('#sync-show-dialog').removeClass('sync-show-dialog-error');
|
||||
} else {
|
||||
$('#sync-authButton').removeClass('sync-authButton-authed');
|
||||
$('#sync-show-dialog').addClass('sync-show-dialog-error');
|
||||
}
|
||||
}
|
||||
|
||||
window.plugin.sync.showDialog = function() {
|
||||
alert(plugin.sync.dialogHTML);
|
||||
plugin.sync.toggleAuthButton();
|
||||
}
|
||||
|
||||
window.plugin.sync.setupDialog = function() {
|
||||
plugin.sync.dialogHTML = '<div id="sync-dialog">'
|
||||
+ '<button id="sync-authButton" class="sync-authButton-authed" onclick="setTimeout(function(){window.plugin.sync.authorizer.authorize(true)}, 1)" disabled="disabled">Authorize</button>'
|
||||
+ '</div>';
|
||||
$('#toolbox').append('<a id="sync-show-dialog" onclick="window.plugin.sync.showDialog();">Sync</a> ');
|
||||
}
|
||||
|
||||
window.plugin.sync.setupCSS = function() {
|
||||
$("<style>")
|
||||
.prop("type", "text/css")
|
||||
.html(".sync-authButton-authed {\
|
||||
opacity: 0.5;\
|
||||
}\
|
||||
.sync-show-dialog-error {\
|
||||
color: #FF2222;\
|
||||
}")
|
||||
.appendTo("head");
|
||||
}
|
||||
|
||||
var setup = function() {
|
||||
window.plugin.sync.loadUUID();
|
||||
window.plugin.sync.loadGapi();
|
||||
window.plugin.sync.setupCSS();
|
||||
window.plugin.sync.setupDialog();
|
||||
|
||||
window.plugin.sync.authorizer = new window.plugin.sync.Authorizer({'authCallback': [plugin.sync.toggleAuthButton]});
|
||||
window.plugin.sync.registerdPluginsFields = new window.plugin.sync.registerdPluginsFields({'authorizer': window.plugin.sync.authorizer});
|
||||
gapi.load('auth:client,drive-realtime,drive-share', window.plugin.sync.authorizer.authorize);
|
||||
}
|
||||
|
||||
// PLUGIN END //////////////////////////////////////////////////////////
|
||||
|
||||
if(window.iitcLoaded && typeof setup === 'function') {
|
||||
setup();
|
||||
} else {
|
||||
if(window.bootPlugins)
|
||||
window.bootPlugins.push(setup);
|
||||
else
|
||||
window.bootPlugins = [setup];
|
||||
}
|
||||
} // wrapper end
|
||||
// inject code into site context
|
||||
var script = document.createElement('script');
|
||||
script.appendChild(document.createTextNode('('+ wrapper +')();'));
|
||||
(document.body || document.head || document.documentElement).appendChild(script);
|
Loading…
x
Reference in New Issue
Block a user