├── .gitattributes ├── .gitignore ├── README.md ├── icons ├── big.png ├── icon.png ├── medium.png └── small.png ├── join.js ├── js ├── auth.js ├── base │ ├── constants.js │ ├── contextmenu.js │ ├── deviceIdsAndDirectDevices.js │ ├── extensions.js │ └── gcm.js ├── chromenotifications.js ├── extensions.js ├── init.js ├── joindevices.js ├── joinwebapi.js ├── utils.js └── windowmanagement.js ├── manifest.json └── options.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Join Firefox Extension 2 | Firefox Add-on for Join 3 | 4 | ## What it does 5 | 6 | For now the extension only allows you to log-in and use the right click menu to push stuff to your devices 7 | 8 | 9 | ## Compatibility 10 | 11 | This add-on has been tested on Firefox 49.0. (As of writing this is the [Nightly](https://nightly.mozilla.org/) version) 12 | It needs some features that have been recently added so it may not work at all on lower versions. 13 | 14 | ## How to install and use 15 | 16 | - In Firefox navigate to [about:debugging#addons](about:debugging#addons) 17 | - Click "Load Temporary Add-on" 18 | - Find the JoinFirefox folder 19 | - Select the manifest.json file 20 | - Log-in to your google account in the tab that pops-up 21 | - Right-click any page to see the Join options 22 | -------------------------------------------------------------------------------- /icons/big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomgcd/JoinFirefox/6b51de104fb6e07ca9e40a3dfa00acd1c29f7875/icons/big.png -------------------------------------------------------------------------------- /icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomgcd/JoinFirefox/6b51de104fb6e07ca9e40a3dfa00acd1c29f7875/icons/icon.png -------------------------------------------------------------------------------- /icons/medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomgcd/JoinFirefox/6b51de104fb6e07ca9e40a3dfa00acd1c29f7875/icons/medium.png -------------------------------------------------------------------------------- /icons/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joaomgcd/JoinFirefox/6b51de104fb6e07ca9e40a3dfa00acd1c29f7875/icons/small.png -------------------------------------------------------------------------------- /join.js: -------------------------------------------------------------------------------- 1 | console.log("olaa!"); 2 | joinWebApi.devices(function(devices){ 3 | contextMenu.update(devices); 4 | }); 5 | contextMenu.update(joindevices.storedDevices); 6 | /*authentication.getToken(function(token){ 7 | 8 | },true);*/ 9 | -------------------------------------------------------------------------------- /js/auth.js: -------------------------------------------------------------------------------- 1 | console.log("olaa Auth!!"); 2 | var Authentication = function(){ 3 | var AUTH_CALLBACK_URL = "https://joinjoaomgcd.appspot.com/authorize.html"; 4 | 5 | var me = this; 6 | var getCliendId = function(){ 7 | return chrome.runtime.getManifest().oauth2.client_id_web; 8 | } 9 | var removeAuthToken = function(callback){ 10 | delete localStorage.accessToken; 11 | delete localStorage.authExpires; 12 | delete localStorage.userinfo; 13 | } 14 | var isLocalAccessTokenValid = function(){ 15 | //console.log(localStorage.accessToken + " - " + localStorage.authExpires + " - " + new Date(new Number(localStorage.authExpires))); 16 | return localStorage.accessToken && localStorage.authExpires && new Date(new Number(localStorage.authExpires)) > new Date(); 17 | } 18 | this.getUserInfo = function(callback,force,token){ 19 | if(!localStorage){ 20 | return; 21 | } 22 | if(localStorage.userinfo && !force){ 23 | callback(JSON.parse(localStorage.userinfo)); 24 | return; 25 | } 26 | me.doGetWithAuth("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", function(result){ 27 | localStorage.userinfo = JSON.stringify(result); 28 | callback(result); 29 | console.log("Got user info"); 30 | console.log(result); 31 | },function(error){ 32 | console.log("Error: " + error); 33 | },token); 34 | 35 | } 36 | this.doPostWithAuth = function(url,content, callback, callbackError) { 37 | doRequestWithAuth("POST",url,content,callback,callbackError); 38 | } 39 | this.doGetWithAuth = function(url, callback, callbackError,token) { 40 | me.doRequestWithAuth("GET",url,null,callback,callbackError,false, token); 41 | } 42 | this.doRequestWithAuth = function(method, url,content, callback, callbackError, isRetry, token) { 43 | me.getToken(function(token) { 44 | if(token == null){ 45 | if (callbackError != null) { 46 | callbackError("noauth"); 47 | } 48 | }else{ 49 | 50 | var contentClass = content == null ? null : content.toClass(); 51 | var isFileOrForm = contentClass && (contentClass == "[object File]" || contentClass == "[object FormData]"); 52 | var authHeader = "Bearer " + token; 53 | //console.log("authHeader: " + authHeader); 54 | console.log("Posting to: " + url); 55 | var req = new XMLHttpRequest(); 56 | req.open(method, url, true); 57 | req.setRequestHeader("authorization", authHeader); 58 | if(content){ 59 | if(!isFileOrForm){ 60 | req.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); 61 | } 62 | } 63 | req.onload = function() { 64 | console.log("POST status: " + this.status); 65 | var result = {}; 66 | if(this.responseText){ 67 | result = JSON.parse(this.responseText) 68 | } 69 | if(!isRetry && result.userAuthError){ 70 | console.log("Retrying with new token..."); 71 | removeCachedAuthToken(function(){ 72 | doRequestWithAuth(method, url,content, callback, callbackError, true); 73 | }) 74 | }else{ 75 | if (callback != null) { 76 | callback(result); 77 | } 78 | } 79 | } 80 | req.onerror = function(e) { 81 | if (callbackError != null) { 82 | callbackError(e.currentTarget); 83 | } 84 | } 85 | var contentString = null; 86 | if(content){ 87 | if(isFileOrForm){ 88 | contentString = content; 89 | }else{ 90 | contentString = JSON.stringify(content); 91 | } 92 | } 93 | req.send(contentString); 94 | } 95 | },false,token); 96 | } 97 | this.getAuthUrl = function(selectAccount,background){ 98 | var manifest = chrome.runtime.getManifest(); 99 | var url = "https://accounts.google.com/o/oauth2/v2/auth?response_type=token"; 100 | url += "&client_id=" + getCliendId(); 101 | if(!background){ 102 | url += "&redirect_uri=" + encodeURIComponent(AUTH_CALLBACK_URL); 103 | }else{ 104 | url += "&redirect_uri=postmessage"; 105 | url += "&origin=" + encodeURIComponent("https://joinjoaomgcd.appspot.com"); 106 | } 107 | url += "&scope=" + encodeURIComponent(manifest.oauth2.scopes.joinJoaomgcd(" ")); 108 | if(selectAccount){ 109 | url += "&prompt=select_account"; 110 | } 111 | return url; 112 | } 113 | var getAuthTokenFromUrl =function(url){ 114 | if(url.indexOf("#access_token=")>0){ 115 | return url.substring(url.indexOf("#")+"#access_token=".length,url.indexOf("&")); 116 | } 117 | } 118 | var setLocalAccessToken = function(token, expiresIn){ 119 | localStorage.authExpires = new Date().getTime() + ((expiresIn - 120) * 1000); 120 | localStorage.accessToken = token; 121 | console.log("Setting local token: " + localStorage.accessToken + " - " + localStorage.authExpires); 122 | } 123 | var isDoingAuth = false; 124 | var waitingForAuthCallbacks = []; 125 | this.getToken = function(callback,selectAccount,token){ 126 | if(token){ 127 | if(callback){ 128 | callback(token); 129 | } 130 | return; 131 | } 132 | if(selectAccount){ 133 | removeAuthToken(); 134 | } 135 | //removeAuthToken(); 136 | if(isLocalAccessTokenValid()){ 137 | if(callback){ 138 | callback(localStorage.accessToken); 139 | } 140 | }else{ 141 | 142 | console.log("Access token not valid"); 143 | var focusOnAuthTabId = function(){ 144 | if(authTabId){ 145 | chrome.tabs.update(authTabId,{"active":true}); 146 | if(!localStorage.warnedLogin){ 147 | localStorage.warnedLogin = true; 148 | //alert("Please login to use Join"); 149 | } 150 | }else{ 151 | //alert("Something went wrong. Please reload the Join extension."); 152 | } 153 | } 154 | if(!isDoingAuth){ 155 | isDoingAuth = true; 156 | var url = me.getAuthUrl(selectAccount); 157 | 158 | if(localStorage.userinfo){ 159 | var userinfo = JSON.parse(localStorage.userinfo); 160 | if(userinfo.email){ 161 | url += "&login_hint="+ userinfo.email; 162 | } 163 | } 164 | var closeListener = function(tabId,removeInfo){ 165 | if(authTabId && tabId == authTabId){ 166 | finisher(tabId); 167 | } 168 | } 169 | var authListener = function(tabId,changeInfo,tab){ 170 | if(tab.url && tab.url.indexOf(getCliendId())>0){ 171 | authTabId = tabId; 172 | focusOnAuthTabId(); 173 | } 174 | if(tab && tab.url && tab.url.indexOf(AUTH_CALLBACK_URL) == 0){ 175 | var redirect_url = tab.url; 176 | var token = getAuthTokenFromUrl(redirect_url); 177 | finisher(tabId,token,redirect_url); 178 | } 179 | } 180 | var finisher = function(tabId,token,redirect_url){ 181 | authTabId = null; 182 | chrome.tabs.onUpdated.removeListener(authListener); 183 | chrome.tabs.onRemoved.removeListener(closeListener); 184 | //console.log("Auth token found from tab: " + token); 185 | chrome.tabs.remove(tabId); 186 | var finshCallback = function(token){ 187 | if(callback){ 188 | callback(token); 189 | } 190 | waitingForAuthCallbacks.doForAll(function(waitingCallback){ 191 | waitingCallback(token) 192 | }); 193 | waitingForAuthCallbacks = []; 194 | isDoingAuth = false; 195 | } 196 | if(token && redirect_url){ 197 | //console.log("Redirect Url: " + redirect_url); 198 | var expiresParameter = UrlUtils.getURLParameter(redirect_url,"expires_in"); 199 | //console.log("Expires parameter: " + expiresParameter); 200 | var expiresIn = new Number(expiresParameter); 201 | setLocalAccessToken(token,expiresIn); 202 | //console.log("Token expires in " + expiresIn + " seconds"); 203 | me.getUserInfo(function(userInfoFromStorage){ 204 | console.log("Logged in with: " + userInfoFromStorage.email); 205 | finshCallback(token); 206 | },true,token); 207 | }else{ 208 | finshCallback(null); 209 | } 210 | 211 | } 212 | chrome.tabs.onUpdated.addListener(authListener); 213 | chrome.tabs.onRemoved.addListener(closeListener) 214 | windowManagement.openTab( url ,{active:false},function(tab){ 215 | console.log("Tab auth created"); 216 | console.log(tab); 217 | }); 218 | }else{ 219 | if(callback){ 220 | waitingForAuthCallbacks.push(callback); 221 | focusOnAuthTabId(); 222 | } 223 | } 224 | } 225 | } 226 | } -------------------------------------------------------------------------------- /js/base/constants.js: -------------------------------------------------------------------------------- 1 | var Constants = { 2 | "REPLY_DIRECTLY": "Reply Directly" 3 | }; -------------------------------------------------------------------------------- /js/base/contextmenu.js: -------------------------------------------------------------------------------- 1 | 2 | var ContextMenu = function(){ 3 | var OPEN = "Open"; 4 | var PASTE = "Paste"; 5 | var CREATE_NOTIFICATION = "Create Notification"; 6 | var SET_AS_WALLPAPER = "Set As Wallpaper"; 7 | var SEND_TASKER_COMMAND = "Send Tasker Command"; 8 | var WITH = "With"; 9 | var DOWNLOAD = "Download"; 10 | var me = this; 11 | var push = function(device,data){ 12 | var gcmPush = new GCMPush(); 13 | gcmPush.applyProps(data); 14 | gcmPush.send(device.deviceId,function(result){ 15 | console.log("Pushed"); 16 | console.log(result); 17 | },function(result){ 18 | console.log("Not Pushed!"); 19 | console.log(result); 20 | }); 21 | } 22 | //Opens 23 | var openUrl = function(device, url){ 24 | push(device, {"url": info.pageUrl}); 25 | } 26 | var openPage = function(device, info, tab){ 27 | openUrl(device, info.pageUrl); 28 | } 29 | var openLink = function(device, info, tab){ 30 | openUrl(device, info.pageUrl); 31 | } 32 | 33 | //Pastes 34 | var pasteText = function(device, text){ 35 | push(device, {"clipboard": text}); 36 | } 37 | var pastePage = function(device, info, tab){ 38 | pasteText(device, info.pageUrl); 39 | } 40 | var pasteLink = function(device, info, tab){ 41 | pasteText(device, info.linkUrl); 42 | } 43 | var pasteSelection = function(device, info, tab){ 44 | pasteText(device, info.selectionText); 45 | } 46 | 47 | //Notifications 48 | var notificationForUrl = function(device, info, tab, url, title){ 49 | if(!title){ 50 | title = "Saved Page"; 51 | } 52 | push(device, {"url": url,"text": tab.title,"title": title}); 53 | } 54 | var notificationPage = function(device, info, tab){ 55 | notificationForUrl(device, info, tab, info.pageUrl); 56 | } 57 | var notificationLink = function(device, info, tab){ 58 | notificationForUrl(device, info, tab, info.linkUrl); 59 | } 60 | var notificationSelection = function(device, info, tab){ 61 | notificationForUrl(device, info, tab, info.selectionText, "Note To Self"); 62 | } 63 | 64 | //Downloads 65 | var downloadUrl = function(device, url){ 66 | push(device, {"files": [url]}); 67 | } 68 | var downloadLink = function(device, info, tab){ 69 | downloadUrl(device, info.linkUrl); 70 | } 71 | var downloadSourceUrl = function(device, info, tab){ 72 | downloadUrl(device, info.srcUrl); 73 | } 74 | 75 | //Wallpapers 76 | var setWallpaper = function(device, url){ 77 | push(device, {"wallpaper": url}); 78 | } 79 | var setWallpaperSourceUrl = function(device, info, tab){ 80 | setWallpaper(device, info.srcUrl); 81 | } 82 | 83 | //Tasker Commands 84 | var sendTaskerCommand = function(device, text){ 85 | var prefix = prompt("Enter Tasker command prefix.\n\nSent command will be 'prefix=:=selection'"); 86 | if(!prefix){ 87 | return; 88 | } 89 | text = prefix + "=:=" + text; 90 | push(device, {"text": text}); 91 | } 92 | var sendTaskerCommandLink = function(device, info, tab){ 93 | sendTaskerCommand(device, info.linkUrl); 94 | } 95 | var sendTaskerCommandSelection = function(device, info, tab){ 96 | sendTaskerCommand(device, info.selectionText); 97 | } 98 | var sendTaskerCommandPage = function(device, info, tab){ 99 | sendTaskerCommand(device, info.pageUrl); 100 | } 101 | var sendTaskerCommandSourceUrl = function(device, info, tab){ 102 | sendTaskerCommand(device, info.srcUrl); 103 | } 104 | this.update = function(devices){ 105 | chrome.contextMenus.removeAll(); 106 | var contexts = { 107 | "page":[ 108 | new ContextMenuItem(OPEN,openPage), 109 | new ContextMenuItem(PASTE,pastePage), 110 | new ContextMenuItem(CREATE_NOTIFICATION,notificationPage, WITH), 111 | new ContextMenuItem(SEND_TASKER_COMMAND,sendTaskerCommandPage, WITH), 112 | ], 113 | "selection":[ 114 | new ContextMenuItem(PASTE,pasteSelection), 115 | new ContextMenuItem(CREATE_NOTIFICATION,notificationSelection, WITH), 116 | new ContextMenuItem(SEND_TASKER_COMMAND,sendTaskerCommandSelection, WITH), 117 | ], 118 | "link":[ 119 | new ContextMenuItem(OPEN,openLink), 120 | new ContextMenuItem(PASTE,pasteLink), 121 | new ContextMenuItem(CREATE_NOTIFICATION,notificationLink, WITH), 122 | new ContextMenuItem(DOWNLOAD,downloadLink), 123 | new ContextMenuItem(SEND_TASKER_COMMAND,sendTaskerCommandLink, WITH), 124 | ], 125 | "editable":[], 126 | "image":[ 127 | new ContextMenuItem(SET_AS_WALLPAPER,setWallpaperSourceUrl), 128 | new ContextMenuItem(DOWNLOAD,downloadSourceUrl), 129 | new ContextMenuItem(SEND_TASKER_COMMAND,sendTaskerCommandSourceUrl, WITH), 130 | ], 131 | "video":[ 132 | new ContextMenuItem(DOWNLOAD,downloadSourceUrl), 133 | new ContextMenuItem(SEND_TASKER_COMMAND,sendTaskerCommandSourceUrl, WITH), 134 | ], 135 | "audio":[ 136 | new ContextMenuItem(DOWNLOAD,downloadSourceUrl), 137 | new ContextMenuItem(SEND_TASKER_COMMAND,sendTaskerCommandSourceUrl, WITH), 138 | ] 139 | }; 140 | 141 | if(!devices){ 142 | return; 143 | } 144 | devices.doForAll(function(device){ 145 | chrome.contextMenus.create({ 146 | "id": device.deviceId, 147 | "title": device.deviceName, 148 | "contexts": ["all"] 149 | }); 150 | 151 | for(var contextId in contexts){ 152 | var context = contexts[contextId]; 153 | if(!context.isArray()){ 154 | continue; 155 | } 156 | if(context.length > 0){ 157 | var contextForDevice = contextId + device.deviceId; 158 | var contextName = contextId.substring(0,1).toUpperCase() + contextId.substring(1); 159 | var multipleFuncsForContext = context.length > 1; 160 | if(multipleFuncsForContext){ 161 | chrome.contextMenus.create({ 162 | "id": contextForDevice, 163 | "parentId": device.deviceId, 164 | "contexts": [contextId], 165 | "title": contextName 166 | }); 167 | }else{ 168 | contextForDevice = device.deviceId; 169 | } 170 | 171 | context.doForAll(function(contextMenuItem){ 172 | var actionTitle = contextMenuItem.title; 173 | if(!multipleFuncsForContext){ 174 | var joiner = " "; 175 | if(contextMenuItem.joiner){ 176 | joiner = " " + contextMenuItem.joiner + " "; 177 | } 178 | actionTitle = actionTitle + joiner + contextName; 179 | } 180 | chrome.contextMenus.create({ 181 | "parentId": contextForDevice, 182 | "contexts": [contextId], 183 | "onclick": function(info, tab){ 184 | contextMenuItem.handler(device, info, tab); 185 | }, 186 | "title": actionTitle 187 | }); 188 | }); 189 | } 190 | } 191 | }); 192 | } 193 | var ContextMenuItem = function(title,handler, joiner){ 194 | this.title = title; 195 | this.handler = handler; 196 | this.joiner = joiner; 197 | } 198 | } -------------------------------------------------------------------------------- /js/base/deviceIdsAndDirectDevices.js: -------------------------------------------------------------------------------- 1 | 2 | var DeviceIdsAndDirectDevices = function(deviceIds,allDevices, showNotificationFunc){ 3 | 4 | var GCM_PARAM_TIME_TO_LIVE = "time_to_live"; 5 | var GCM_PARAM_PRIORITY = "priority"; 6 | var GCM_MESSAGE_PRIORITY_HIGH = "high"; 7 | var GCM_PARAM_DELAY_WHILE_IDLE = "delay_while_idle"; 8 | var doDirectGCMRequest = function(regId, gcmString, gcmType, gcmParams, callback, callbackError){ 9 | var req = new XMLHttpRequest(); 10 | req.open("POST", "https://gcm-http.googleapis.com/gcm/send", true); 11 | req.setRequestHeader("Authorization", "key=AIzaSyDvDS_KGPYTBrCG7tppCyq9P3_iVju9UkA"); 12 | req.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); 13 | req.onload = function() { 14 | console.log("POST status: " + this.status); 15 | var result = {}; 16 | if(this.status != 200){ 17 | if (callbackError != null) { 18 | callbackError(this.responseText); 19 | } 20 | return; 21 | } 22 | if(this.responseText){ 23 | result = JSON.parse(this.responseText) 24 | } 25 | if (callback != null) { 26 | callback(result); 27 | } 28 | } 29 | req.onerror = function(e) { 30 | if (callbackError != null) { 31 | callbackError(e.currentTarget); 32 | } 33 | } 34 | if(typeof regId == "string"){ 35 | regId = [regId]; 36 | } 37 | var content = { 38 | "data": { 39 | "json": gcmString, 40 | "type": gcmType 41 | }, 42 | "registration_ids": regId 43 | } 44 | content.applyProps(gcmParams); 45 | content[GCM_PARAM_PRIORITY] = GCM_MESSAGE_PRIORITY_HIGH; 46 | content[GCM_PARAM_DELAY_WHILE_IDLE] = false; 47 | var contentString = JSON.stringify(content); 48 | req.send(contentString); 49 | }; 50 | var me = this; 51 | if(!allDevices){ 52 | allDevices = devices; 53 | } 54 | if(!showNotificationFunc){ 55 | showNotificationFunc = showNotification; 56 | } 57 | if(typeof deviceIds == "string"){ 58 | deviceIds = deviceIds.split(","); 59 | } 60 | this.deviceIds = deviceIds; 61 | this.serverDevices = []; 62 | this.directDevices = []; 63 | 64 | this.convertGroupToDeviceIds = function(device){ 65 | var devicesResult = []; 66 | if(device.deviceId.indexOf("group." == 0)){ 67 | var devicesForGroup = joindevices.groups.deviceGroups.getGroupDevices(allDevices, device.deviceId); 68 | if(devicesForGroup && devicesForGroup.length > 0){ 69 | for (var i = 0; i < devicesForGroup.length; i++) { 70 | var deviceForGroup = devicesForGroup[i]; 71 | devicesResult.push(deviceForGroup); 72 | } 73 | }else{ 74 | devicesResult.push(device); 75 | } 76 | }else{ 77 | devicesResult.push(device); 78 | } 79 | return devicesResult; 80 | } 81 | var devicesForId = allDevices.where(function(device){ 82 | return deviceIds.indexOf(device.deviceId) >= 0; 83 | }); 84 | if(devicesForId.length == 0){ 85 | if(deviceIds.length>0){ 86 | me.serverDevices.push({"deviceId":deviceIds[0]}); 87 | } 88 | }else{ 89 | devicesForId.doForAll(function(deviceForId){ 90 | var devicesExpanded = me.convertGroupToDeviceIds(deviceForId); 91 | devicesExpanded.doForAll(function(deviceForId){ 92 | if(deviceForId.regId2){ 93 | me.directDevices.removeIf(function(device){ 94 | return device.deviceId == deviceForId.deviceId; 95 | }); 96 | me.directDevices.push(deviceForId); 97 | }else{ 98 | me.serverDevices.removeIf(function(device){ 99 | return device.deviceId == deviceForId.deviceId; 100 | }); 101 | me.serverDevices.push(deviceForId); 102 | } 103 | }); 104 | }); 105 | } 106 | 107 | this.callCallback = function(callback,data){ 108 | if(callback){ 109 | callback(data); 110 | } 111 | } 112 | this.send = function(sendThroughServer, gcm, gcmParams,callback, callbackError){ 113 | if(!gcm){ 114 | me.callCallback(callbackError,"No message to push"); 115 | return; 116 | } 117 | var serverDevices = me.serverDevices; 118 | var directDevices = me.directDevices; 119 | var gcmString = JSON.stringify(gcm); 120 | if(gcmString.length > 3500){ 121 | serverDevices = serverDevices.concat(directDevices); 122 | directDevices = []; 123 | } 124 | if(directDevices.length > 0){ 125 | var regIds = directDevices.select(function(device){return device.regId2;}); 126 | if(!gcmParams){ 127 | gcmParams = {}; 128 | } 129 | doDirectGCMRequest(regIds,gcmString,gcm.getCommunicationType(),gcmParams,function(multicastResult){ 130 | for (var i = 0; i < directDevices.length; i++) { 131 | var device = directDevices[i]; 132 | var result = multicastResult.results[i]; 133 | me.handleGcmResult(device, result); 134 | } 135 | 136 | if(serverDevices.length == 0){ 137 | me.callCallback(callback,result); 138 | } 139 | 140 | console.log("Posted direct GCM"); 141 | console.log(gcmString); 142 | }, 143 | function(error){ 144 | var title = "Direct GCM error"; 145 | console.log(title); 146 | console.log(error); 147 | showNotificationFunc(title, "Error: " + error.responseText); 148 | 149 | if(serverDevices.length == 0){ 150 | me.callCallback(callbackError,error.toString()); 151 | } 152 | }); 153 | } 154 | if(serverDevices.length > 0){ 155 | sendThroughServer(serverDevices.select(function(device){return device.deviceId;}),callback,callbackError); 156 | } 157 | } 158 | this.handleGcmResult = function(device, result){ 159 | console.log("Direct GCM result"); 160 | console.log(result); 161 | if(result.message_id){ 162 | if(result.registration_id){ 163 | device.regId2 = result.registration_id; 164 | console.log("RegId2 changed for " + device.deviceName); 165 | setDevices(allDevices); 166 | } 167 | }else{ 168 | var error = result.error; 169 | var errorMessage = error; 170 | if(error == "NotRegistered"){ 171 | errorMessage = "Device not registered!"; 172 | } 173 | if(!errorMessage){ 174 | errorMessage = "Unknown Error"; 175 | } 176 | console.log(errorMessage); 177 | showNotificationFunc("Error Direct Send", errorMessage); 178 | } 179 | } 180 | } -------------------------------------------------------------------------------- /js/base/extensions.js: -------------------------------------------------------------------------------- 1 | String.prototype.replaceAll = function(search, replacement) { 2 | var target = this; 3 | return target.replace(new RegExp(search, 'g'), replacement); 4 | }; 5 | Array.prototype.removeIf = function(callback) { 6 | var removed = 0; 7 | var i = 0; 8 | while (i < this.length) { 9 | if (callback(this[i], i)) { 10 | this.splice(i, 1); 11 | removed++; 12 | } 13 | else { 14 | ++i; 15 | } 16 | } 17 | return removed; 18 | }; 19 | Array.prototype.select = function(func) { 20 | var result = []; 21 | for (var i = 0; i < this.length; i++) { 22 | var item = this[i]; 23 | result.push(func(item)); 24 | }; 25 | return result; 26 | }; 27 | Array.prototype.doForAll = function(func) { 28 | for (var i = 0; i < this.length; i++) { 29 | var item = this[i]; 30 | func(item,i); 31 | }; 32 | }; 33 | Array.prototype.where = function(func) { 34 | var result = []; 35 | for (var i = 0; i < this.length; i++) { 36 | var item = this[i]; 37 | if(func(item)){ 38 | result.push(item); 39 | } 40 | }; 41 | return result; 42 | }; 43 | Array.prototype.equalsArray = function(arr2) { 44 | if(this.length !== arr2.length) 45 | return false; 46 | for(var i = this.length; i--;) { 47 | if(this[i] !== arr2[i]) 48 | return false; 49 | } 50 | 51 | return true; 52 | } 53 | Array.prototype.equalsArrayAnyOrder = function(arr2) { 54 | if(this.length !== arr2.length) 55 | return false; 56 | for(var i = this.length; i--;) { 57 | if(arr2.indexOf(this[i])<0){ 58 | return false; 59 | } 60 | } 61 | for(var i = arr2.length; i--;) { 62 | if(this.indexOf(arr2[i])<0){ 63 | return false; 64 | } 65 | } 66 | 67 | return true; 68 | } 69 | Array.prototype.first = function(func) { 70 | for (var i = 0; i < this.length; i++) { 71 | var item = this[i]; 72 | if(func(item)){ 73 | return item; 74 | } 75 | }; 76 | return null; 77 | }; 78 | Array.prototype.joinJoaomgcd = function(joiner) { 79 | if(!joiner){ 80 | joiner=","; 81 | } 82 | var joined = ""; 83 | for (var i = 0; i < this.length; i++) { 84 | var item = this[i]; 85 | if(i>0){ 86 | joined += joiner; 87 | } 88 | joined += item; 89 | }; 90 | return joined; 91 | }; 92 | Array.prototype.doForAllAsync = function(func, callbackFinal, shouldProcessItem, callbackEach) { 93 | var me = this; 94 | var count = -1; 95 | var results = []; 96 | var doAll = function(callback){ 97 | count++; 98 | if(count == me.length){ 99 | callback(results); 100 | }else{ 101 | var item = me[count]; 102 | if(!shouldProcessItem || shouldProcessItem(item)){ 103 | func(item,function(result){ 104 | results.push(result); 105 | if(callbackEach){ 106 | callbackEach(result, count); 107 | } 108 | doAll(callback); 109 | }); 110 | }else{ 111 | results.push(null); 112 | doAll(callback); 113 | } 114 | 115 | } 116 | }; 117 | doAll(callbackFinal); 118 | }; 119 | Array.prototype.doForChain = function(func, callbackFinal) { 120 | var me = this; 121 | var count = -1; 122 | var preivousResult = null; 123 | var doAll = function(callback){ 124 | count++; 125 | if(count == me.length){ 126 | callback(preivousResult); 127 | }else{ 128 | var item = me[count]; 129 | func(item,preivousResult,function(result){ 130 | preivousResult = result; 131 | doAll(callback); 132 | }); 133 | 134 | 135 | } 136 | }; 137 | doAll(callbackFinal); 138 | }; 139 | Object.prototype.applyProps = function(objToApply){ 140 | if(!objToApply){ 141 | return; 142 | } 143 | for(var prop in objToApply){ 144 | var value = objToApply[prop]; 145 | if(value && value.toClass() != "[object Function]"){ 146 | this[prop] = value; 147 | } 148 | } 149 | return this; 150 | }; 151 | Object.prototype.toClass = function(){ 152 | return {}.toString.call(this); 153 | }; 154 | Object.prototype.isString = function(){ 155 | return this.toClass() == "[object String]"; 156 | }; 157 | Object.prototype.isArray = function(){ 158 | return this.toClass() == "[object Array]"; 159 | }; 160 | -------------------------------------------------------------------------------- /js/base/gcm.js: -------------------------------------------------------------------------------- 1 | var Request = function(){ 2 | this.getParams = function() { 3 | var json = new Object(); 4 | for (prop in this) { 5 | json[prop] = this[prop]; 6 | } 7 | return json; 8 | } 9 | } 10 | var GCM = function(){ 11 | this.execute = function() { 12 | console.log(this); 13 | } 14 | this.fromJson = function(json) { 15 | this.applyProps(json); 16 | } 17 | this.fromJsonString = function(str) { 18 | str = decryptString(str); 19 | if(str.indexOf("{") < 0){ 20 | showNotification("Decryption Error","Please check that your encryption password matches the one on other devices.") 21 | return; 22 | } 23 | var json = JSON.parse(str); 24 | this.fromJson(json); 25 | } 26 | this.getCommunicationType = function() { 27 | return "GCM"; 28 | } 29 | this.encrypt = function(params){ 30 | var password = localStorage.encryptionPassword; 31 | if(!password){ 32 | return; 33 | } 34 | this.encryptSpecific(params,password); 35 | } 36 | } 37 | GCM.prototype = new Request(); 38 | var GCMPush = function(){ 39 | this.getCommunicationType = function() { 40 | return "GCMPush"; 41 | } 42 | this.send = function(deviceIds,callback,callbackError){ 43 | var push = {}.applyProps(this); 44 | if(deviceIds.isString()){ 45 | push.deviceIds = [deviceIds]; 46 | }else{ 47 | push.deviceIds = deviceIds; 48 | } 49 | joinWebApi.push(push,callback,callbackError); 50 | } 51 | } 52 | GCMPush.prototype = new GCM(); -------------------------------------------------------------------------------- /js/chromenotifications.js: -------------------------------------------------------------------------------- 1 | var ChromeNotifications = function(){ 2 | this.showNotification = function(title, message, timeout, notificationId){ 3 | if(!timeout)timeout = 3000; 4 | var options = { 5 | "type":"basic", 6 | "iconUrl":"icons/big.png", 7 | "title": title, 8 | "message": message 9 | }; 10 | if(!notificationId){ 11 | notificationId = StringUtils.guid(); 12 | } 13 | chrome.notifications.create(notificationId, options,function(){ 14 | setInterval(function() { 15 | chrome.notifications.clear(notificationId, function() {}) 16 | }, timeout); 17 | }); 18 | } 19 | } -------------------------------------------------------------------------------- /js/extensions.js: -------------------------------------------------------------------------------- 1 | Array.prototype.removeIf = function(callback) { 2 | var removed = 0; 3 | var i = 0; 4 | while (i < this.length) { 5 | if (callback(this[i], i)) { 6 | this.splice(i, 1); 7 | removed++; 8 | } 9 | else { 10 | ++i; 11 | } 12 | } 13 | return removed; 14 | }; 15 | Array.prototype.select = function(func) { 16 | var result = []; 17 | for (var i = 0; i < this.length; i++) { 18 | var item = this[i]; 19 | result.push(func(item)); 20 | }; 21 | return result; 22 | }; 23 | Array.prototype.doForAll = function(func) { 24 | for (var i = 0; i < this.length; i++) { 25 | var item = this[i]; 26 | func(item,i); 27 | }; 28 | }; 29 | Array.prototype.where = function(func) { 30 | var result = []; 31 | for (var i = 0; i < this.length; i++) { 32 | var item = this[i]; 33 | if(func(item)){ 34 | result.push(item); 35 | } 36 | }; 37 | return result; 38 | }; 39 | Array.prototype.equalsArray = function(arr2) { 40 | if(this.length !== arr2.length) 41 | return false; 42 | for(var i = this.length; i--;) { 43 | if(this[i] !== arr2[i]) 44 | return false; 45 | } 46 | 47 | return true; 48 | } 49 | Array.prototype.equalsArrayAnyOrder = function(arr2) { 50 | if(this.length !== arr2.length) 51 | return false; 52 | for(var i = this.length; i--;) { 53 | if(arr2.indexOf(this[i])<0){ 54 | return false; 55 | } 56 | } 57 | for(var i = arr2.length; i--;) { 58 | if(this.indexOf(arr2[i])<0){ 59 | return false; 60 | } 61 | } 62 | 63 | return true; 64 | } 65 | Array.prototype.first = function(func) { 66 | for (var i = 0; i < this.length; i++) { 67 | var item = this[i]; 68 | if(func(item)){ 69 | return item; 70 | } 71 | }; 72 | return null; 73 | }; 74 | Array.prototype.joinJoaomgcd = function(joiner) { 75 | if(!joiner){ 76 | joiner=","; 77 | } 78 | var joined = ""; 79 | for (var i = 0; i < this.length; i++) { 80 | var item = this[i]; 81 | if(i>0){ 82 | joined += joiner; 83 | } 84 | joined += item; 85 | }; 86 | return joined; 87 | }; 88 | Array.prototype.doForAllAsync = function(func, callbackFinal, shouldProcessItem, callbackEach) { 89 | var me = this; 90 | var count = -1; 91 | var results = []; 92 | var doAll = function(callback){ 93 | count++; 94 | if(count == me.length){ 95 | callback(results); 96 | }else{ 97 | var item = me[count]; 98 | if(!shouldProcessItem || shouldProcessItem(item)){ 99 | func(item,function(result){ 100 | results.push(result); 101 | if(callbackEach){ 102 | callbackEach(result, count); 103 | } 104 | doAll(callback); 105 | }); 106 | }else{ 107 | results.push(null); 108 | doAll(callback); 109 | } 110 | 111 | } 112 | }; 113 | doAll(callbackFinal); 114 | }; 115 | Array.prototype.doForChain = function(func, callbackFinal) { 116 | var me = this; 117 | var count = -1; 118 | var preivousResult = null; 119 | var doAll = function(callback){ 120 | count++; 121 | if(count == me.length){ 122 | callback(preivousResult); 123 | }else{ 124 | var item = me[count]; 125 | func(item,preivousResult,function(result){ 126 | preivousResult = result; 127 | doAll(callback); 128 | }); 129 | 130 | 131 | } 132 | }; 133 | doAll(callbackFinal); 134 | }; 135 | Object.prototype.applyProps = function(objToApply){ 136 | for(var prop in objToApply){ 137 | var value = objToApply[prop]; 138 | if(value.toClass() != "[object Function]"){ 139 | this[prop] = value; 140 | } 141 | } 142 | return this; 143 | }; 144 | Object.prototype.toClass = function(){ 145 | return {}.toString.call(this); 146 | }; 147 | Object.prototype.isString = function(){ 148 | return this.toClass() == "[object String]"; 149 | }; 150 | Object.prototype.isArray = function(){ 151 | return this.toClass() == "[object Array]"; 152 | }; -------------------------------------------------------------------------------- /js/init.js: -------------------------------------------------------------------------------- 1 | var windowManagement = new WindowManagement(); 2 | var authentication = new Authentication(); 3 | var joinWebApi = new JoinWebApi(); 4 | var chromeNotifications = new ChromeNotifications(); 5 | var contextMenu = new ContextMenu(); -------------------------------------------------------------------------------- /js/joindevices.js: -------------------------------------------------------------------------------- 1 | var joindevices = { 2 | "groups":{ 3 | "DeviceGroup" : function(id, name){ 4 | this.id = id; 5 | this.name = name; 6 | this.devices = []; 7 | }, 8 | "DeviceGroups" : function(){ 9 | var me = this; 10 | this.allDeviceGroups = [joindevices.DEVICE_GROUP_ALL,joindevices.DEVICE_GROUP_ANDROID,joindevices.DEVICE_GROUP_CHROME,joindevices.DEVICE_GROUP_WINDOWS10,joindevices.DEVICE_GROUP_FIREFOX,joindevices.DEVICE_GROUP_PHONE,joindevices.DEVICE_GROUP_TABLET,joindevices.DEVICE_GROUP_PC]; 11 | this.androidGroups = [joindevices.DEVICE_GROUP_ANDROID,joindevices.DEVICE_GROUP_PHONE,joindevices.DEVICE_GROUP_TABLET]; 12 | this.deviceTypeGroups = {}; 13 | 14 | this.deviceTypeGroups[joindevices.DEVICE_TYPE_ANDROID_PHONE] = [joindevices.DEVICE_GROUP_ALL,joindevices.DEVICE_GROUP_ANDROID,joindevices.DEVICE_GROUP_PHONE]; 15 | this.deviceTypeGroups[joindevices.DEVICE_TYPE_ANDROID_TABLET] = [joindevices.DEVICE_GROUP_ALL,joindevices.DEVICE_GROUP_ANDROID,joindevices.DEVICE_GROUP_TABLET]; 16 | this.deviceTypeGroups[joindevices.DEVICE_TYPE_CHROME_BROWSER] = [joindevices.DEVICE_GROUP_ALL,joindevices.DEVICE_GROUP_CHROME,joindevices.DEVICE_GROUP_PC]; 17 | this.deviceTypeGroups[joindevices.DEVICE_TYPE_WIDNOWS_PC] = [joindevices.DEVICE_GROUP_ALL,joindevices.DEVICE_GROUP_WINDOWS10,joindevices.DEVICE_GROUP_PC]; 18 | this.deviceTypeGroups[joindevices.DEVICE_TYPE_FIREFOX] = [joindevices.DEVICE_GROUP_ALL,joindevices.DEVICE_GROUP_FIREFOX,joindevices.DEVICE_GROUP_PC]; 19 | this.deviceTypeGroups[joindevices.DEVICE_TYPE_ANDROID_TV] = [joindevices.DEVICE_GROUP_ALL,joindevices.DEVICE_GROUP_ANDROID]; 20 | this.putDevicesIntoGroups = function(devices){ 21 | //Group devices into groups 22 | for (var i = 0; i < this.allDeviceGroups.length; i++) { 23 | var deviceGroup = this.allDeviceGroups[i]; 24 | deviceGroup.devices = devices.where(function(device){ 25 | return device.deviceType != joindevices.DEVICE_TYPE_GROUP && me.deviceTypeGroups[device.deviceType].indexOf(deviceGroup) >=0; 26 | }); 27 | } 28 | //Check equal groups and remove devices from them 29 | for (var i = 0; i < this.allDeviceGroups.length; i++) { 30 | var deviceGroup = this.allDeviceGroups[i]; 31 | var otherGroupsThatAreTheSame = this.allDeviceGroups.where(function(deviceGroupToSearch){ 32 | return deviceGroupToSearch != deviceGroup && deviceGroupToSearch.devices.equalsArrayAnyOrder(deviceGroup.devices); 33 | }); 34 | otherGroupsThatAreTheSame.doForAll(function(groupToRemoveDevices){ 35 | groupToRemoveDevices.devices = []; 36 | }); 37 | } 38 | //console.log(me.allDeviceGroups); 39 | // 40 | /*for (var groupId in deviceGroupsToCreate) { 41 | var devicesForGroup = deviceGroupsToCreate[groupId]; 42 | }*/ 43 | } 44 | this.getGroups = function(devices){ 45 | this.putDevicesIntoGroups(devices); 46 | return this.allDeviceGroups.where(function(deviceGroup){ 47 | return deviceGroup.devices.length > 1; 48 | }); 49 | } 50 | this.GROUP_PREFIX = "group."; 51 | this.getGroupDevices = function(devices, groupId){ 52 | if(groupId == null){ 53 | return []; 54 | } 55 | if(groupId.indexOf(this.GROUP_PREFIX)==0){ 56 | groupId = groupId.substring(this.GROUP_PREFIX.length); 57 | } 58 | this.putDevicesIntoGroups(devices); 59 | var group = this.allDeviceGroups.first(function(deviceGroup){ 60 | return deviceGroup.id == groupId; 61 | }); 62 | if(!group){ 63 | return []; 64 | } 65 | return group.devices; 66 | } 67 | 68 | } 69 | } 70 | }; 71 | joindevices.DEVICE_TYPE_ANDROID_PHONE = 1; 72 | joindevices.DEVICE_TYPE_ANDROID_TABLET = 2; 73 | joindevices.DEVICE_TYPE_CHROME_BROWSER = 3; 74 | joindevices.DEVICE_TYPE_WIDNOWS_PC = 4; 75 | joindevices.DEVICE_TYPE_FIREFOX = 6; 76 | joindevices.DEVICE_TYPE_GROUP = 7; 77 | joindevices.DEVICE_TYPE_ANDROID_TV = 8; 78 | joindevices.DEVICE_GROUP_ALL = new joindevices.groups.DeviceGroup("all","All"); 79 | joindevices.DEVICE_GROUP_ANDROID = new joindevices.groups.DeviceGroup("android","Androids"); 80 | joindevices.DEVICE_GROUP_CHROME = new joindevices.groups.DeviceGroup("chrome","Chromes"); 81 | joindevices.DEVICE_GROUP_WINDOWS10 = new joindevices.groups.DeviceGroup("windows10", "Windows 10s"); 82 | joindevices.DEVICE_GROUP_FIREFOX = new joindevices.groups.DeviceGroup("firefox", "Firefoxes"); 83 | joindevices.DEVICE_GROUP_PHONE = new joindevices.groups.DeviceGroup("phone","Phones"); 84 | joindevices.DEVICE_GROUP_TABLET = new joindevices.groups.DeviceGroup("tablet","Tablets"); 85 | joindevices.DEVICE_GROUP_PC = new joindevices.groups.DeviceGroup("pc","PCs"); 86 | joindevices.groups.deviceGroups = new joindevices.groups.DeviceGroups(); -------------------------------------------------------------------------------- /js/joinwebapi.js: -------------------------------------------------------------------------------- 1 | var JoinWebApi = function(){ 2 | 3 | var me = this; 4 | var devicesJson = localStorage.devices; 5 | joindevices.storedDevices = null; 6 | if(devicesJson){ 7 | joindevices.storedDevices = JSON.parse(devicesJson); 8 | } 9 | var joinserverBase = "https://joinjoaomgcd.appspot.com/"; 10 | var joinserver = joinserverBase + "_ah/api/"; 11 | 12 | this.devices = function(callback){ 13 | authentication.doGetWithAuth(joinserver + "registration/v1/listDevices/", function(result){ 14 | console.log(result); 15 | joindevices.storedDevices = result.records; 16 | localStorage.devices = JSON.stringify(joindevices.storedDevices); 17 | if(callback != null){ 18 | callback(result.records); 19 | } 20 | },function(error){ 21 | console.log("Error getting devices: " + error); 22 | if(callback != null){ 23 | callback(null); 24 | } 25 | }); 26 | } 27 | this.push = function(push, callback, callbackError){ 28 | var deviceIds = push.deviceIds; 29 | push.deviceId = null; 30 | push.deviceIds = deviceIds.join(); 31 | var sender = new DeviceIdsAndDirectDevices(push.deviceIds,joindevices.storedDevices,chromeNotifications.showNotification); 32 | var gcmParams = {}; 33 | if(push.clipboard){ 34 | gcmParams[sender.GCM_PARAM_TIME_TO_LIVE] = 0; 35 | } 36 | var gcm = {"push":push}; 37 | gcm.getCommunicationType = function(){return "GCMPush"}; 38 | sender.send(function(deviceIds,callback,callbackError){ 39 | push.deviceId = null; 40 | push.deviceIds = deviceIds.join(); 41 | doPostWithAuth(joinserver + "messaging/v1/sendPush/",push,callback,callbackError); 42 | },gcm,gcmParams, function(result){ 43 | console.log("Sent push: " + JSON.stringify(result)); 44 | if(callback){ 45 | callback(result); 46 | } 47 | },function(error){ 48 | console.log("Error: " + error.er); 49 | if(callbackError){ 50 | callbackError(error); 51 | } 52 | }); 53 | } 54 | } -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | var UrlUtils = { 2 | "getURLParameter": function(url,name) { 3 | if (!url) url = location.href; 4 | name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]"); 5 | var regexS = "[\\?&]"+name+"=([^]*)"; 6 | var regex = new RegExp( regexS ); 7 | var results = regex.exec( url ); 8 | return results == null ? null : results[1]; 9 | } 10 | } 11 | var StringUtils ={ 12 | "guid": function(){ 13 | function s4() { 14 | return Math.floor((1 + Math.random()) * 0x10000) 15 | .toString(16) 16 | .substring(1); 17 | } 18 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + 19 | s4() + '-' + s4() + s4() + s4(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /js/windowmanagement.js: -------------------------------------------------------------------------------- 1 | var WindowManagement = function(){ 2 | var me = this; 3 | this.openNewTab = function(url,options, callback){ 4 | chrome.windows.getCurrent({ 'populate': false }, function(current) { 5 | if (current) { 6 | if(!options){ 7 | options = {}; 8 | } 9 | options.url = url; 10 | chrome.tabs.create(options,callback); 11 | } else { 12 | var finalOptions = { 'url': url, 'type': 'normal', 'focused': true }; 13 | if(options){ 14 | for(var prop in options){ 15 | finalOptions[prop] = options[prop]; 16 | } 17 | } 18 | chrome.windows.create(finalOptions,callback); 19 | } 20 | }); 21 | } 22 | this.openTab = function(url,options,callback){ 23 | chrome.tabs.query({},function(result){ 24 | var correctTab = result.first(function(tab){ 25 | return tab.url == url; 26 | }); 27 | if(correctTab){ 28 | var finalOptions = {active: true}; 29 | if(options){ 30 | for(var prop in options){ 31 | finalOptions[prop] = options[prop]; 32 | } 33 | } 34 | chrome.tabs.update(correctTab.id, finalOptions,callback); 35 | }else{ 36 | me.openNewTab(url,options,callback); 37 | } 38 | }); 39 | } 40 | } -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Join by joaoapps", 3 | "short_name": "Join", 4 | "description": "Bring your devices together", 5 | "version": "1.0.18", 6 | "permissions": [ 7 | "storage", 8 | "webRequest", 9 | "webRequestBlocking", 10 | "notifications", 11 | "tabs", 12 | "https://localhost:8080/", 13 | "http://localhost/", 14 | "https://joinjoaomgcd.appspot.com/", 15 | "https://accounts.google.com/o/oauth2/v2/auth/", 16 | "https://www.googleapis.com/oauth2/v1/userinfo/", 17 | "https://gcm-http.googleapis.com/gcm/send", 18 | "contextMenus", 19 | "activeTab" 20 | ], 21 | "externally_connectable": { 22 | "matches": [ 23 | "*://joinjoaomgcd.appspot.com/*", 24 | "http://192.168.12.100/*" 25 | ] 26 | }, 27 | "icons": { 28 | "16": "icons/small.png", 29 | "48": "icons/medium.png", 30 | "128": "icons/big.png" 31 | }, 32 | "browser_action": { 33 | "browser_style": true, 34 | "default_icon": "icon/con.png", 35 | "default_popup": "devices.html" 36 | }, 37 | "web_accessible_resources": [ 38 | "icons/medium.png" 39 | ], 40 | "background": { 41 | "scripts": [ 42 | "js/base/extensions.js", 43 | "js/utils.js", 44 | "js/joindevices.js", 45 | "js/windowmanagement.js", 46 | "js/auth.js", 47 | "js/base/deviceIdsAndDirectDevices.js", 48 | "js/joinwebapi.js", 49 | "js/chromenotifications.js", 50 | "js/base/contextmenu.js", 51 | "js/base/gcm.js", 52 | "js/init.js", 53 | "join.js" 54 | ] 55 | }, 56 | "oauth2": { 57 | "client_id": "596310809542-5tfd4b5u90jmh0r664mdl73cnct5m0ea.apps.googleusercontent.com", 58 | "client_id_web": "596310809542-c2bg952rtmf05el5kouqlcf0ajqnfpdl.apps.googleusercontent.com", 59 | "scopes": [ 60 | "https://www.googleapis.com/auth/userinfo.email", 61 | "https://www.googleapis.com/auth/userinfo.profile", 62 | "https://www.googleapis.com/auth/drive.appfolder", 63 | "https://www.googleapis.com/auth/drive", 64 | "https://www.googleapis.com/auth/drive.file" 65 | ] 66 | }, 67 | "manifest_version": 2 68 | } -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |