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 @@
-///
-///