diff --git a/.idea/dictionaries/develar.xml b/.idea/dictionaries/develar.xml index 8f41596..9d252ed 100644 --- a/.idea/dictionaries/develar.xml +++ b/.idea/dictionaries/develar.xml @@ -1,8 +1,13 @@ + forgotpassword icns + keytar onshape + onsubmit + signin + signup \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 9cbe6ef..948417c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -41,13 +41,6 @@ - - - diff --git a/.idea/typescript-compiler.xml b/.idea/typescript-compiler.xml index a3ddd56..88b88fe 100644 --- a/.idea/typescript-compiler.xml +++ b/.idea/typescript-compiler.xml @@ -2,8 +2,7 @@ diff --git a/build/build.js b/build/build.js index 9b9ebe0..ccd78ee 100644 --- a/build/build.js +++ b/build/build.js @@ -1,18 +1,32 @@ "use strict" +let outDir = "dist/Onshape-darwin-x64"; +require("rimraf")(outDir, function (error) { + if (error != null) { + throw new Error(error) + } + + console.log(outDir) +}) + +let packageJson = JSON.parse(require("fs").readFileSync("./package.json")) + let packager = require("electron-packager") -var version = "0.0.2" +var version = packageJson.version packager({ dir: "out", out: "dist", name: "Onshape", platform: "darwin", arch: "x64", - version: "0.36.0", + version: packageJson.devDependencies["electron-prebuilt"].substring(1), icon: "build/icon.icns", asar: true, "app-version": version, "build-version": version, "app-bundle-id": "org.develar.onshape", "app-category-type": "public.app-category.graphics-design", - }, function done (error, appPath) { if (error != null) throw new Error(error) }) \ No newline at end of file + sign: "Vladimir Krivosheev" + }, function (error, appPath) { + if (error != null) throw new Error(error) +}) \ No newline at end of file diff --git a/build/packager.json b/build/packager.json index e532dcc..fa62d67 100644 --- a/build/packager.json +++ b/build/packager.json @@ -20,7 +20,7 @@ }, "win": { "title": "Onshape", - "version": "0.0.1", + "version": "0.2.0", "icon": "build/icon.ico" } } \ No newline at end of file diff --git a/package.json b/package.json index 6b08646..2c63a78 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,19 @@ { "name": "onshape-desktop-shell", - "version": "0.0.2", + "version": "0.2.0", "license": "MIT", "description": "Onshape desktop app (web application shell). Unofficial.", "private": true, "scripts": { + "deps": "npm install && electron-rebuild .", "start": "npm run prepare && electron ./out", "clean": "rimraf dist", "clean:osx": "rimraf dist/Onshape-darwin-x64", "clean:win": "rimraf dist/win", "compile": "rimraf out/*.js && tsc -p src", - "prepare": "npm run compile && cp package.json out/package.json && cd out && npm prune --production && npm install --production && cd ..", - "build:osx": "npm run clean:osx && npm run prepare && node build/build.js && codesign --deep --force --verbose --sign 'Vladimir Krivosheev' dist/Onshape-darwin-x64/Onshape.app", - "build:win": "npm run clean:win && npm run prepare && electron-packager out \"Onshape\" --out=dist/win --platform=win32 --arch=all --version=0.36.0 --icon=build/icon.ico --asar=true", + "prepare": "npm run compile && cp package.json out/package.json && cd out && npm prune --production && npm install --production && electron-rebuild . && cd ..", + "build:osx": "npm run prepare && node build/build.js", + "build:win": "npm run clean:win && npm run prepare && electron-packager out \"Onshape\" --out=dist/win --platform=win32 --arch=all --version=0.36.1 --icon=build/icon.ico --asar=true", "build": "npm run build:osx && npm run build:win", "pack:osx": "npm run build:osx && electron-builder 'dist/Onshape-darwin-x64/Onshape.app' --platform=osx --out='dist/Onshape-darwin-x64' --config=build/packager.json", "pack:win32": "electron-builder 'dist/win/Onshape-win32-x64' --platform=win --out='dist/win' --config=build/packager.json", @@ -30,11 +31,14 @@ "devDependencies": { "electron-builder": "^2.4.0", "electron-packager": "^5.2.0", - "electron-prebuilt": "^0.36.0", + "electron-prebuilt": "^0.36.1", + "electron-rebuild": "^1.0.2", "rimraf": "^2.4.4", - "typescript": "^1.8.0-dev.20151217" + "typescript": "^1.8.0-dev.20151219" }, "dependencies": { - "configstore": "^1.4.0" + "configstore": "^1.4.0", + "electron-debug": "^0.5.1", + "keytar": "^3.0.0" } } diff --git a/src/autoSignIn.ts b/src/autoSignIn.ts new file mode 100644 index 0000000..83e4232 --- /dev/null +++ b/src/autoSignIn.ts @@ -0,0 +1,127 @@ +(function (): void { + "use strict" + + const SERVICE_NAME = "org.develar.onshape" + const LOCAL_STORAGE_LOGIN_KEY = SERVICE_NAME.replace('.', '_') + ".login" + + let passwordToSave: Credentials = null + let maybeUrlChangedTimerId: any = null + let foundFormElementTimerId: number = -1 + let oldUrl: string = null + + require("electron").ipcRenderer.on("maybeUrlChanged", () => { maybeUrlChanged(true) }) + + document.addEventListener("DOMContentLoaded", () => { + checkLocationAndSignInIfNeed() + }) + + class Credentials { + constructor(public login: string, public password: string) { + } + } + + function getInputElement(name: string) { + return document.querySelector('input[name="' + name + '"]'); + } + + function isNotEmpty(string: string) { + // yep, get used to strict Java&Kotlin and cannot see code like (foo) + return string != null && string.length != 0 + } + + function setValue(input: HTMLInputElement, value: string) { + input.value = value + // we must trigger "change" event otherwise form is not submitted on click emulate (it seems, because angular (used in Onshape) doesn't detect changes immediately) + input.dispatchEvent(new Event("change", {"bubbles": true})) + } + + function fillAndSubmit(formElement: HTMLFormElement) { + let login: string = localStorage.getItem(LOCAL_STORAGE_LOGIN_KEY) + if (isNotEmpty(login)) { + setValue(getInputElement("email"), login) + + let password = require("keytar").getPassword(SERVICE_NAME, login) + if (isNotEmpty(password)) { + setValue(getInputElement("password"), password); + (document.querySelector('div.os-form-btn-container > button[type="submit"')).click() + return + } + } + + var superOnSubmit: any = formElement.onsubmit + formElement.onsubmit = () => { + passwordToSave = null + if (superOnSubmit != null) { + superOnSubmit() + } + + let login = getInputElement("email").value + let password = getInputElement("password").value + if (isNotEmpty(login) && isNotEmpty(password)) { + passwordToSave = new Credentials(login, password) + } + } + } + + function fillOrWait() { + let formElement = document.querySelector("form[name='osForm']") + if (formElement != null) { + console.log("form element found") + fillAndSubmit(formElement) + } + else { + console.log("form element not found, schedule") + setTimeout(() => { + checkLocationAndSignInIfNeed() + }) + } + } + + function checkLocationAndSignInIfNeed() { + let location = window.location + if (location.host == "cad.onshape.com" && location.pathname == "/signin") { + fillOrWait() + } + } + + function maybeUrlChanged(checkLater: boolean) { + if (maybeUrlChangedTimerId != null) { + clearTimeout(maybeUrlChangedTimerId) + maybeUrlChangedTimerId = null + } + + let newLocation = window.location + let newUrl = newLocation.href + if (oldUrl != newUrl) { + try { + console.log("url changed:", oldUrl, newUrl) + urlChanged(oldUrl, newLocation) + } + finally { + oldUrl = newUrl + } + } + else if (checkLater) { + // let's reschedule + maybeUrlChangedTimerId = setTimeout(() => { maybeUrlChanged(false) }, 100) + } + } + + function urlChanged(oldUrl: string, newLocation: Location) { + if (foundFormElementTimerId != -1) { + clearTimeout(foundFormElementTimerId) + } + + if (passwordToSave != null) { + if (newLocation.host == "cad.onshape.com") + if (oldUrl.endsWith("/signin") && newLocation.pathname != "/signup/forgotpassword") { + localStorage.setItem(LOCAL_STORAGE_LOGIN_KEY, passwordToSave.login) + require("keytar").replacePassword(SERVICE_NAME, passwordToSave.login, passwordToSave.password) + } + passwordToSave = null + } + else if (document.readyState != "loading") { + checkLocationAndSignInIfNeed() + } + } +}()) \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c4e2a17..130326e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,9 +3,9 @@ import electron = require("electron") import BrowserWindow = GitHubElectron.BrowserWindow import BrowserWindowOptions = GitHubElectron.BrowserWindowOptions -let stateManager = new StateManager() +require('electron-debug')() -electron.crashReporter.start() +let stateManager = new StateManager() let windows: Array = [] let app = electron.app @@ -24,6 +24,30 @@ app.on("window-all-closed", () => { } }) +function registerWindowEventHandlers(window: BrowserWindow, initialUrl: string) { + window.on("closed", () => { + var index = windows.indexOf(window) + console.assert(index >= 0) + windows.splice(index, 1) + }) + + let webContents = window.webContents + // cannot find way to listen url change in pure JS + let frameFinishLoadedId: NodeJS.Timer = null + webContents.on("did-frame-finish-load", (event: any, isMainFrame: boolean) => { + if (frameFinishLoadedId != null) { + clearTimeout(frameFinishLoadedId) + frameFinishLoadedId = null + } + frameFinishLoadedId = setTimeout(() => { + webContents.send("maybeUrlChanged") + }, 300) + }) + webContents.on("will-navigate", (e: any, u: string) => { + console.log("will-navigate", webContents.getURL(), e) + }) +} + function openWindows() { let descriptors = stateManager.getWindows() if (descriptors == null || descriptors.length === 0) { @@ -35,9 +59,10 @@ function openWindows() { let options: BrowserWindowOptions = { // to avoid visible maximizing show: false, + preload: __dirname + "/autoSignIn.js", webPreferences: { // fix jquery issue (https://github.com/atom/electron/issues/254), and in any case node integration is not required - nodeIntegration: false + nodeIntegration: false, } } @@ -56,11 +81,7 @@ function openWindows() { } window.loadURL(descriptor.url) window.show() - window.on("closed", () => { - var index = windows.indexOf(window) - console.assert(index >= 0) - windows.splice(index, 1) - }) + registerWindowEventHandlers(window, descriptor.url) windows.push(window) } } diff --git a/src/tsd.json b/src/tsd.json index 7599541..c5eb1f6 100644 --- a/src/tsd.json +++ b/src/tsd.json @@ -10,6 +10,9 @@ }, "node/node.d.ts": { "commit": "11322524f8db9cdb921427cad84cd22fe2d4f965" + }, + "keytar/keytar.d.ts": { + "commit": "40c60850ad6c8175a62d5ab48c4e016ea5b3dffe" } } } diff --git a/src/typings/configstore.d.ts b/src/typings/configstore.d.ts index feb0d48..7feb69c 100644 --- a/src/typings/configstore.d.ts +++ b/src/typings/configstore.d.ts @@ -18,5 +18,5 @@ declare module 'configstore' { del(key: string): void; } - export = ConfigStore; + export = ConfigStore } \ No newline at end of file diff --git a/src/typings/keytar/keytar.d.ts b/src/typings/keytar/keytar.d.ts new file mode 100644 index 0000000..cee7e71 --- /dev/null +++ b/src/typings/keytar/keytar.d.ts @@ -0,0 +1,60 @@ +// Type definitions for keytar 3.0.0 +// Project: http://atom.github.io/node-keytar/ +// Definitions by: Milan Burda +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +declare module 'keytar' { + /** + * Get the stored password for the service and account. + * + * @param service The string service name. + * @param account The string account name. + * + * @returns the string password or null on failures. + */ + export function getPassword(service: string, account: string): string; + + /** + * Add the password for the service and account to the keychain. + * + * @param service The string service name. + * @param account The string account name. + * @param password The string password. + * + * @returns true on success, false on failure. + */ + export function addPassword(service: string, account: string, password: string): boolean; + + /** + * Delete the stored password for the service and account. + * + * @param service The string service name. + * @param account The string account name. + * + * @returns the string password or null on failures. + */ + export function deletePassword(service: string, account: string): string; + + /** + * Replace the password for the service and account in the keychain. + * + * This is a simple convenience function that internally calls deletePassword(service, account) + * followed by addPassword(service, account, password). + * + * @param service The string service name. + * @param account The string account name. + * @param password The string password. + * + * @returns true on success, false on failure. + */ + export function replacePassword(service: string, account: string, password: string): boolean; + + /** + * Find a password for the service in the keychain. + * + * @param service The string service name. + * + * @returns the string password or null on failures. + */ + export function findPassword(service: string): string; +} diff --git a/src/typings/tsd.d.ts b/src/typings/tsd.d.ts deleted file mode 100644 index ab964ad..0000000 --- a/src/typings/tsd.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/// -///