From 3d3f73c2592ff3c1e10552b8e68b6966d5f25efc Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Mon, 10 Jun 2024 15:52:45 -0500 Subject: [PATCH] added row level AES encryption. --- package-lock.json | 29 ++++ package.json | 16 +-- public/styles.css | 5 + src/controllers/rigplatform.ts | 5 - src/diagram/diagramManager.ts | 16 ++- src/diagram/diagramMenuManager.ts | 4 +- src/diagram/diagramObject.ts | 47 +++++-- src/information/inputTextView.ts | 36 ++--- src/integration/encryption.ts | 90 ++++++++++++ src/integration/functions/hexFunctions.ts | 4 +- src/integration/functions/syncDoc.ts | 40 ++++-- src/integration/newRelicQuery.ts | 2 +- src/integration/pouchdbPersistenceManager.ts | 136 +++++++++++++++---- src/maps/functions/tileFunctions.ts | 2 +- src/menus/configMenu.ts | 18 +-- src/newrelic/newRelicMenu.ts | 8 +- src/objects/Button.ts | 4 +- src/objects/handle.ts | 4 +- src/react/webApp.tsx | 35 +++++ src/toolbox/toolbox.ts | 10 +- src/util/functions/sceneInspector.ts | 5 + vite.config.ts | 28 +++- 22 files changed, 421 insertions(+), 123 deletions(-) create mode 100644 src/integration/encryption.ts diff --git a/package-lock.json b/package-lock.json index 4f9ac40..53ba8eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,9 @@ "earcut": "^2.2.4", "events": "^3.3.0", "file-saver": "^2.0.5", + "hash-wasm": "4.11.0", "hls.js": "^1.1.4", + "js-crypto-aes": "1.0.6", "loglevel": "^1.9.1", "niceware": "^4.0.0", "peer-lite": "2.0.2", @@ -43,6 +45,7 @@ "recordrtc": "^5.6.0", "rfc4648": "^1.5.3", "round": "^2.0.1", + "uint8-to-b64": "^1.0.2", "uuid": "^9.0.1" }, "devDependencies": { @@ -1702,6 +1705,11 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/hash-wasm": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.11.0.tgz", + "integrity": "sha512-HVusNXlVqHe0fzIzdQOGolnFN6mX/fqcrSAOcTBXdvzrXVHwTz11vXeKRmkR5gTuwVpvHZEIyKoePDvuAR+XwQ==" + }, "node_modules/hls.js": { "version": "1.5.8", "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.8.tgz", @@ -1826,6 +1834,19 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/js-crypto-aes": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/js-crypto-aes/-/js-crypto-aes-1.0.6.tgz", + "integrity": "sha512-E2hu9z5+YtpDg9Un/bDfmH+I5dv/8aN+ozxv9L0ybZldcQ9T5iYDbBKdlKGBUKI3IvzoWSBSdnZnhwZaRIN46w==", + "dependencies": { + "js-crypto-env": "^1.0.5" + } + }, + "node_modules/js-crypto-env": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/js-crypto-env/-/js-crypto-env-1.0.5.tgz", + "integrity": "sha512-8/UNN3sG8J+yMzqwSNVaobaWhIz4MqZFoOg5OB0DFXqS8eFjj2YvdmLJqIWXPl57Yw10SvYx0DQOtkfsWIV9Aw==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3029,6 +3050,14 @@ "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", "dev": true }, + "node_modules/uint8-to-b64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uint8-to-b64/-/uint8-to-b64-1.0.2.tgz", + "integrity": "sha512-V2mVc9Mu398c1aXz55K3NHMFyLk8g0AP/tUNXqv0SGrSn9nCzkiUlNWFH5H0yJkH4Me/RAX7+u9xZuXgp/YV2A==", + "dependencies": { + "base64-js": "1.5.1" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/package.json b/package.json index 9fe94e8..2b3f70e 100644 --- a/package.json +++ b/package.json @@ -22,36 +22,28 @@ "@babylonjs/inspector": "^7.9.0", "@babylonjs/loaders": "^7.9.0", "@babylonjs/materials": "^7.9.0", - "@babylonjs/procedural-textures": "^7.9.0", "@babylonjs/serializers": "^7.9.0", "@maptiler/client": "1.8.1", "@picovoice/cobra-web": "^2.0.3", "@picovoice/eagle-web": "^1.0.0", "@picovoice/web-voice-processor": "^4.0.9", - "@typed-mxgraph/typed-mxgraph": "^1.0.8", - "@types/dom-to-image": "^2.6.7", - "@types/file-saver": "^2.0.6", "@types/node": "^18.14.0", "@types/react": "^18.2.72", "@types/react-dom": "^18.2.22", "axios": "^1.6.8", "canvas-hypertxt": "1.0.3", - "dom-to-image-more": "^3.3.0", - "earcut": "^2.2.4", - "events": "^3.3.0", - "file-saver": "^2.0.5", "hls.js": "^1.1.4", "loglevel": "^1.9.1", - "niceware": "^4.0.0", "peer-lite": "2.0.2", "pouchdb": "^8.0.1", - "pouchdb-find": "^8.0.1", - "query-string": "^8.1.0", "react-dom": "^18.2.0", "recordrtc": "^5.6.0", "rfc4648": "^1.5.3", "round": "^2.0.1", - "uuid": "^9.0.1" + "uuid": "^9.0.1", + "js-crypto-aes": "1.0.6", + "hash-wasm": "4.11.0", + "uint8-to-b64": "^1.0.2" }, "devDependencies": { "@types/dom-to-image": "^2.6.7", diff --git a/public/styles.css b/public/styles.css index 61f2c78..4b63413 100644 --- a/public/styles.css +++ b/public/styles.css @@ -162,6 +162,11 @@ h1 { height: 210px; } +#password { + display: none; + left: 50%; + top: 50%; +} #diagramList { left: 340px; top: 400px; diff --git a/src/controllers/rigplatform.ts b/src/controllers/rigplatform.ts index 4b4d891..ff5cc82 100644 --- a/src/controllers/rigplatform.ts +++ b/src/controllers/rigplatform.ts @@ -162,10 +162,8 @@ export class Rigplatform { private initializeControllers() { - this.xr.input.onControllerAddedObservable.add((source) => { this.registerObserver(); - let controller; switch (source.inputSource.handedness) { case RIGHT: if (!this.rightController) { @@ -179,9 +177,6 @@ export class Rigplatform { break; } //this.xr.baseExperience.camera.position = new Vector3(0, 0, 0); - if (controller) { - controller.setRig(this); - } }); this.xr.input.onControllerRemovedObservable.add((source) => { switch (source.inputSource.handedness) { diff --git a/src/diagram/diagramManager.ts b/src/diagram/diagramManager.ts index b04d3d3..d6e4d28 100644 --- a/src/diagram/diagramManager.ts +++ b/src/diagram/diagramManager.ts @@ -94,13 +94,13 @@ export class DiagramManager { private onDiagramEvent(event: DiagramEvent) { + let diagramObject = this._diagramObjects.get(event.entity.id); switch (event.type) { case DiagramEventType.ADD: - let diagramObject = this._diagramObjects.get(event.entity.id); if (diagramObject) { diagramObject.fromDiagramEntity(event.entity); } else { - diagramObject = new DiagramObject(this._scene, + diagramObject = DiagramObject.CreateObject(this._scene, this.onDiagramEventObservable, {diagramEntity: event.entity, actionManager: this._diagramEntityActionManager}); } @@ -111,18 +111,20 @@ export class DiagramManager { } break; case DiagramEventType.REMOVE: - const object = this._diagramObjects.get(event.entity.id); - if (object) { - object.dispose(); + if (diagramObject) { + diagramObject.dispose(); } this._diagramObjects.delete(event.entity.id); break; case DiagramEventType.MODIFY: console.log(event); - if (event.entity.text) { - const diagramObject = this._diagramObjects.get(event.entity.id); + //diagramObject = this._diagramObjects.get(event.entity.id); + if (diagramObject && event.entity.text) { diagramObject.text = event.entity.text; } + if (event.entity.position) { + //diagramObject.position = event.entity.position; + } break; } } diff --git a/src/diagram/diagramMenuManager.ts b/src/diagram/diagramMenuManager.ts index e923f3d..5ea9ed9 100644 --- a/src/diagram/diagramMenuManager.ts +++ b/src/diagram/diagramMenuManager.ts @@ -42,7 +42,7 @@ export class DiagramMenuManager { if (viewOnly()) { this.toolbox.handleMesh.setEnabled(false); //this.scaleMenu.handleMesh.setEnabled(false) - this.configMenu.handleMesh.setEnabled(false); + this.configMenu.handleTransformNode.setEnabled(false); } controllers.controllerObservable.add((event: ControllerEvent) => { if (event.type == ControllerEventType.B_BUTTON) { @@ -65,7 +65,7 @@ export class DiagramMenuManager { } const configY = this._inputTextView.handleMesh.absolutePosition.y; if (configY > (cameraPos.y - .2)) { - this.configMenu.handleMesh.position.y = localCamera.y - .2; + this.configMenu.handleTransformNode.position.y = localCamera.y - .2; } } } diff --git a/src/diagram/diagramObject.ts b/src/diagram/diagramObject.ts index 3f57d91..f8db586 100644 --- a/src/diagram/diagramObject.ts +++ b/src/diagram/diagramObject.ts @@ -26,12 +26,12 @@ type DiagramObjectOptionsType = { export class DiagramObject { private readonly _logger: Logger = log.getLogger('DiagramObject'); private _scene: Scene; + public grabbed: boolean = false; private _from: string; private _to: string; private _observingStart: number; private _sceneObserver: Observer; private _eventObservable: Observable; - private _mesh: AbstractMesh; private _label: AbstractMesh; private _meshesPresent: boolean = false; private _positionHash: string; @@ -39,12 +39,6 @@ export class DiagramObject { private _toMesh: AbstractMesh; private _meshRemovedObserver: Observer; - public grabbed: boolean = false; - - public get mesh(): AbstractMesh { - return this._mesh; - } - constructor(scene: Scene, eventObservable: Observable, options?: DiagramObjectOptionsType) { this._eventObservable = eventObservable; this._scene = scene; @@ -58,7 +52,7 @@ export class DiagramObject { const myEntity = this.fromDiagramEntity(options.diagramEntity); if (!myEntity) { this._logger.warn('DiagramObject constructor called with invalid diagramEntity', options.diagramEntity); - return null; + this._valid = false; } } if (options.mesh) { @@ -69,6 +63,28 @@ export class DiagramObject { this._mesh.actionManager = options.actionManager; } } + this._valid = true; + } + + private _mesh: AbstractMesh; + + public get mesh(): AbstractMesh { + return this._mesh; + } + + private _valid: boolean = false; + + public get valid(): boolean { + return this._valid; + } + + public static CreateObject(scene: Scene, eventObservable: Observable, options: DiagramObjectOptionsType): DiagramObject { + const newObj = new DiagramObject(scene, eventObservable, options); + if (newObj.valid) { + return newObj; + } else { + return null; + } } private _baseTransform: TransformNode; @@ -216,9 +232,18 @@ export class DiagramObject { } } else { this._mesh.setParent(this._baseTransform); - this._baseTransform.position = xyztovec(entity.position); - this._baseTransform.rotation = xyztovec(entity.rotation); - this._mesh.scaling = xyztovec(entity.scale); + if (entity.position) { + this._baseTransform.position = xyztovec(entity.position) + } + ; + if (entity.rotation) { + this._baseTransform.rotation = xyztovec(entity.rotation) + } + ; + if (entity.scale) { + this._mesh.scaling = xyztovec(entity.scale) + } + ; this._mesh.position = Vector3.Zero(); this._mesh.rotation = Vector3.Zero(); } diff --git a/src/information/inputTextView.ts b/src/information/inputTextView.ts index f94a67d..9676651 100644 --- a/src/information/inputTextView.ts +++ b/src/information/inputTextView.ts @@ -1,4 +1,4 @@ -import {AbstractMesh, MeshBuilder, Observable, Scene, Vector3} from "@babylonjs/core"; +import {AbstractMesh, MeshBuilder, Observable, Scene, TransformNode, Vector3} from "@babylonjs/core"; import log, {Logger} from "loglevel"; import {AdvancedDynamicTexture, Control, InputText, VirtualKeyboard} from "@babylonjs/gui"; import {ControllerEventType, Controllers} from "../controllers/controllers"; @@ -33,8 +33,12 @@ export class InputTextView { this.createKeyboard(); } + public get handleMesh(): TransformNode { + return this.handle.transformNode; + } + public show(mesh: AbstractMesh) { - this.handle.mesh.setEnabled(true); + this.handle.transformNode.setEnabled(true); if (mesh.metadata?.text) { this.inputText.text = mesh.metadata?.text; } else { @@ -46,15 +50,6 @@ export class InputTextView { this.logger.debug(mesh.metadata); } - public get handleMesh(): AbstractMesh { - return this.handle.mesh; - } - - private hide() { - this.handle.mesh.setEnabled(false); - this.diagramMesh = null; - } - public createKeyboard() { const platform = this.scene.getMeshById('platform'); const position = new Vector3(0, 1.66, .53); @@ -70,18 +65,18 @@ export class InputTextView { this.scene.onNewMeshAddedObservable.add((mesh) => { if (mesh.id == 'platform') { this.logger.debug("platform added"); - handle.mesh.parent = mesh; + handle.transformNode.parent = mesh; if (!handle.idStored) { - handle.mesh.position = position; - handle.mesh.rotation = rotation; + handle.transformNode.position = position; + handle.transformNode.rotation = rotation; } } }, -1, false, this, false); } else { - handle.mesh.setParent(platform); + handle.transformNode.setParent(platform); if (!handle.idStored) { - handle.mesh.position = position; - handle.mesh.rotation = rotation; + handle.transformNode.position = position; + handle.transformNode.rotation = rotation; } } @@ -143,6 +138,11 @@ export class InputTextView { } }, -1, false, this, false); this.keyboard = keyboard; - this.handle.mesh.setEnabled(false); + this.handle.transformNode.setEnabled(false); + } + + private hide() { + this.handle.transformNode.setEnabled(false); + this.diagramMesh = null; } } \ No newline at end of file diff --git a/src/integration/encryption.ts b/src/integration/encryption.ts new file mode 100644 index 0000000..05cc388 --- /dev/null +++ b/src/integration/encryption.ts @@ -0,0 +1,90 @@ +import {createSHA256, pbkdf2} from "hash-wasm"; +import aes from 'js-crypto-aes'; +import {decode, encode} from "uint8-to-b64"; + +export class Encryption { + private _key: Uint8Array | null = null; + private _salt: Uint8Array | null = null; + private _iv: Uint8Array | null = null; + private _encrypted: Uint8Array | null = null; + + constructor() { + + } + + private _ready: boolean = false; + + public get ready() { + return this._ready; + } + + public async setPassword(password: string, saltString?: string) { + if (saltString) { + this._salt = decode(saltString); + } else { + const salt = new Uint8Array(16); + window.crypto.getRandomValues(salt); + this._salt = salt; + } + this._key = await pbkdf2({ + password: password, + salt: this._salt, + iterations: 10000, + hashLength: 32, + hashFunction: createSHA256(), + outputType: "binary" + }); + this._ready = true; + } + + public async encryptObject(obj: any) { + return await this.encrypt(JSON.stringify(obj)); + } + + public async decryptToObject(msg: string, iv: string) { + return JSON.parse(await this.decrypt(msg, iv)); + } + + public async encrypt(msg: string) { + if (!this._key) { + throw new Error('No password key set'); + } + + const iv = new Uint8Array(12); + window.crypto.getRandomValues(iv); + this._iv = iv; + + const arr: Uint8Array = await aes.encrypt( + new TextEncoder().encode(msg), this._key, {name: 'AES-GCM', iv: this._iv, tagLength: 16}); + this._encrypted = arr; + return this._encrypted; + } + + public async decrypt(msg: string, iv: string) { + if (!this._key) { + throw new Error('No key set'); + } + this._iv = decode(iv); + const msgArray = decode(msg); + //Uint8Array.from(atob(decode(msg)), c => c.charCodeAt(0)); + const output = await aes.decrypt(msgArray, this._key, {name: 'AES-GCM', iv: this._iv, tagLength: 16}); + return new TextDecoder().decode(output); + } + + public getEncrypted() { + if ( + !this._encrypted || + !this._iv || + !this._salt + ) { + return null; + } else { + return { + encrypted: encode(this._encrypted), + salt: encode(this._salt), + iv: encode(this._iv) + } + } + + } +} \ No newline at end of file diff --git a/src/integration/functions/hexFunctions.ts b/src/integration/functions/hexFunctions.ts index 37cddbc..11cc117 100644 --- a/src/integration/functions/hexFunctions.ts +++ b/src/integration/functions/hexFunctions.ts @@ -1,5 +1,5 @@ export function hex_to_ascii(input) { - var hex = input.toString(); + const hex = input.toString(); let output = ''; for (let n = 0; n < hex.length; n += 2) { output += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); @@ -10,7 +10,7 @@ export function hex_to_ascii(input) { export function ascii_to_hex(str) { const arr1 = []; for (let n = 0, l = str.length; n < l; n++) { - var hex = Number(str.charCodeAt(n)).toString(16); + const hex = Number(str.charCodeAt(n)).toString(16); arr1.push(hex); } return arr1.join(''); diff --git a/src/integration/functions/syncDoc.ts b/src/integration/functions/syncDoc.ts index e0fc9c4..6cbf654 100644 --- a/src/integration/functions/syncDoc.ts +++ b/src/integration/functions/syncDoc.ts @@ -2,22 +2,44 @@ import log from "loglevel"; import {DiagramEntity} from "../../diagram/types/diagramEntity"; import {Observable} from "@babylonjs/core"; import {UserModelType} from "../../users/userTypes"; +import {Encryption} from "../encryption"; -export function syncDoc(info: any, onDBRemoveObservable: Observable, onDBUpdateObservable: Observable, onUserObservable: Observable) { +export async function syncDoc(info: any, onDBRemoveObservable: Observable, onDBUpdateObservable: Observable, onUserObservable: Observable, + encryption: Encryption, key: string) { const logger = log.getLogger('syncDoc'); logger.debug(info); if (info.direction == 'pull') { const docs = info.change.docs; + let salt = null; for (const doc of docs) { - if (doc.type == 'user') { - //onUserObservable.notifyObservers(doc, -1); - } else { - logger.debug(doc); - if (doc._deleted) { - logger.debug('Delete', doc); - onDBRemoveObservable.notifyObservers({id: doc._id, template: doc.template}, 1); + if (doc.encrypted) { + if (salt != doc.encrypted.salt || (key && !encryption.ready)) { + await encryption.setPassword(key, doc.encrypted.salt); + salt = doc.encrypted.salt + } + const decrypted = await encryption.decryptToObject(doc.encrypted.encrypted, doc.encrypted.iv); + if (decrypted.type == 'user') { + //onUserObservable.notifyObservers(doc, -1); } else { - onDBUpdateObservable.notifyObservers(doc, 1); + logger.debug(decrypted); + if (doc._deleted) { + logger.debug('Delete', doc); + onDBRemoveObservable.notifyObservers({id: doc._id, template: decrypted.template}, 1); + } else { + onDBUpdateObservable.notifyObservers(decrypted, 1); + } + } + } else { + if (doc.type == 'user') { + //onUserObservable.notifyObservers(doc, -1); + } else { + logger.debug(doc); + if (doc._deleted) { + logger.debug('Delete', doc); + onDBRemoveObservable.notifyObservers({id: doc._id, template: doc.template}, 1); + } else { + onDBUpdateObservable.notifyObservers(doc, 1); + } } } } diff --git a/src/integration/newRelicQuery.ts b/src/integration/newRelicQuery.ts index 189aa6e..54a3900 100644 --- a/src/integration/newRelicQuery.ts +++ b/src/integration/newRelicQuery.ts @@ -53,7 +53,7 @@ export class NewRelicQuery { const plane = MeshBuilder.CreatePlane("plane", {width: .5, height: .25}, this.scene); plane.billboardMode = Mesh.BILLBOARDMODE_ALL; const advancedTexture = AdvancedDynamicTexture.CreateForMesh(plane, 1024, 512, false); - var text1 = new TextBlock(); + const text1 = new TextBlock(); text1.text = text; advancedTexture.background = "#000000"; text1.color = "white"; diff --git a/src/integration/pouchdbPersistenceManager.ts b/src/integration/pouchdbPersistenceManager.ts index e769240..ac198e6 100644 --- a/src/integration/pouchdbPersistenceManager.ts +++ b/src/integration/pouchdbPersistenceManager.ts @@ -11,6 +11,7 @@ import {syncDoc} from "./functions/syncDoc"; import {checkDb} from "./functions/checkDb"; import {UserModelType} from "../users/userTypes"; import {getMe} from "../util/me"; +import {Encryption} from "./encryption"; export class PouchdbPersistenceManager { private _logger: Logger = log.getLogger('PouchdbPersistenceManager'); @@ -20,10 +21,16 @@ export class PouchdbPersistenceManager { private db: PouchDB; private remote: PouchDB; private user: string; - - + private _encryption = new Encryption(); + private _encKey = null; constructor() { - + document.addEventListener('passwordset', (evt) => { + this._encKey = evt.detail || null; + if (this._encKey && typeof (this._encKey) == 'string') { + this.initialize(); + } + console.log(evt); + }); } public setDiagramManager(diagramManager: DiagramManager) { diagramManager.onDiagramEventObservable.add((evt) => { @@ -80,10 +87,7 @@ export class PouchdbPersistenceManager { } else { this._logger.error(err); } - } - - } public async remove(id: string) { if (!id) { @@ -101,17 +105,41 @@ export class PouchdbPersistenceManager { if (!entity) { return; } + if (this._encKey && !this._encryption.ready) { + await this._encryption.setPassword(this._encKey); + } try { const doc = await this.db.get(entity.id); - if (doc) { - const newDoc = {...doc, ...entity}; - this.db.put(newDoc); + if (this._encKey) { + + await this._encryption.encryptObject(entity); + const newDoc = { + _id: doc._id, + _rev: doc._rev, + encrypted: this._encryption.getEncrypted() + } + this.db.put(newDoc) + } else { + if (doc) { + const newDoc = {...doc, ...entity}; + this.db.put(newDoc); + } } + } catch (err) { if (err.status == 404) { try { - const newEntity = {...entity, _id: entity.id}; - this.db.put(newEntity); + if (this._encKey) { + await this._encryption.encryptObject(entity); + const newDoc = { + _id: entity.id, + encrypted: this._encryption.getEncrypted() + } + this.db.put(newDoc); + } else { + const newEntity = {...entity, _id: entity.id}; + this.db.put(newEntity); + } } catch (err2) { this._logger.error(err2); } @@ -128,28 +156,52 @@ export class PouchdbPersistenceManager { await this.sendLocalDataToScene(); } - private async setupMetadata(current: string) { + private async setupMetadata(current: string): Promise { try { const doc = await this.db.get('metadata'); - if (doc && doc.friendly) { - localStorage.setItem(current, doc.friendly); - } - if (doc && doc.camera) { + if (doc.encrypted) { + if (!this._encKey) { + const promptPassword = new CustomEvent('promptpassword', {detail: 'Please enter password'}); + document.dispatchEvent(promptPassword); + return false; + } + if (!this._encryption.ready) { + await this._encryption.setPassword(this._encKey, doc.encrypted.salt); + } + const decrypted = await this._encryption.decryptToObject(doc.encrypted.encrypted, doc.encrypted.iv); + if (decrypted.friendly) { + localStorage.setItem(current, decrypted.friendly); + } + } else { + if (doc && doc.friendly) { + localStorage.setItem(current, doc.friendly); + } + if (doc && doc.camera) { + } } } catch (err) { if (err.status == 404) { this._logger.debug('no metadata found'); const friendly = localStorage.getItem(current); if (friendly) { - this._logger.debug('local friendly name found ', friendly, ' setting metadata'); - const newDoc = {_id: 'metadata', id: 'metadata', friendly: friendly}; - await this.db.put(newDoc); + if (this._encKey) { + if (!this._encryption.ready) { + await this._encryption.setPassword(this._encKey); + } + await this._encryption.encryptObject({friendly: friendly}); + await this.db.put({_id: 'metadata', id: 'metadata', encrypted: this._encryption.getEncrypted()}) + } else { + this._logger.debug('local friendly name found ', friendly, ' setting metadata'); + const newDoc = {_id: 'metadata', id: 'metadata', friendly: friendly}; + await this.db.put(newDoc); + } } else { this._logger.debug('no friendly name found'); } } } + return true; } private async initLocal(): Promise { @@ -165,8 +217,9 @@ export class PouchdbPersistenceManager { this.db = new PouchDB(current, {auto_compaction: true}); //await this.db.compact(); if (sync) { - await this.setupMetadata(current); - await this.beginSync(current); + if (await this.setupMetadata(current)) { + await this.beginSync(current); + } } return true; } catch (err) { @@ -177,20 +230,38 @@ export class PouchdbPersistenceManager { } private async sendLocalDataToScene() { + + let salt = null; + const clear = localStorage.getItem('clearLocal'); try { const all = await this.db.allDocs({include_docs: true}); - for (const entity of all.rows) { - this._logger.debug(entity.doc); + for (const dbEntity of all.rows) { + this._logger.debug(dbEntity.doc); if (clear) { - this.remove(entity.id); + this.remove(dbEntity.id); } else { - if (entity.type == 'user') { - this.onUserObservable.notifyObservers(entity.doc); + if (dbEntity.doc.encrypted) { + if (!salt || salt != dbEntity.doc.encrypted.salt) { + await this._encryption.setPassword(this._encKey, dbEntity.doc.encrypted.salt); + salt = dbEntity.doc.encrypted.salt; + } + const decrypted = await this._encryption.decryptToObject(dbEntity.doc.encrypted.encrypted, dbEntity.doc.encrypted.iv); + if (decrypted.type == 'user') { + this.onUserObservable.notifyObservers(decrypted); + } else { + if (decrypted.id != 'metadata') { + this.onDBEntityUpdateObservable.notifyObservers(decrypted, DiagramEventObserverMask.FROM_DB); + } + } } else { - if (entity.id != 'metadata') { - this.onDBEntityUpdateObservable.notifyObservers(entity.doc, DiagramEventObserverMask.FROM_DB); + if (dbEntity.type == 'user') { + this.onUserObservable.notifyObservers(dbEntity.doc); + } else { + if (dbEntity.id != 'metadata') { + this.onDBEntityUpdateObservable.notifyObservers(dbEntity.doc, DiagramEventObserverMask.FROM_DB); + } } } } @@ -199,6 +270,12 @@ export class PouchdbPersistenceManager { localStorage.removeItem('clearLocal'); } } catch (err) { + switch (err.message) { + case 'WebCrypto_DecryptionFailure: ': + case 'Invalid data type!': + const promptPassword = new CustomEvent('promptpassword', {detail: 'Please enter password'}); + document.dispatchEvent(promptPassword); + } this._logger.error(err); } } @@ -242,9 +319,10 @@ export class PouchdbPersistenceManager { {auth: {username: remoteUserName, password: password}, skip_setup: true}); const dbInfo = await this.remote.info(); this._logger.debug(dbInfo); + this.db.sync(this.remote, {live: true, retry: true}) .on('change', (info) => { - syncDoc(info, this.onDBEntityRemoveObservable, this.onDBEntityUpdateObservable, this.onUserObservable); + syncDoc(info, this.onDBEntityRemoveObservable, this.onDBEntityUpdateObservable, this.onUserObservable, this._encryption, this._encKey); }) .on('active', (info) => { this._logger.debug('sync active', info) diff --git a/src/maps/functions/tileFunctions.ts b/src/maps/functions/tileFunctions.ts index ffbeca8..bfef677 100644 --- a/src/maps/functions/tileFunctions.ts +++ b/src/maps/functions/tileFunctions.ts @@ -3,7 +3,7 @@ export function tile2long(x, z) { } export function tile2lat(y, z) { - var n = Math.PI - 2 * Math.PI * y / Math.pow(2, z); + const n = Math.PI - 2 * Math.PI * y / Math.pow(2, z); return (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)))); } diff --git a/src/menus/configMenu.ts b/src/menus/configMenu.ts index ef07868..68506f3 100644 --- a/src/menus/configMenu.ts +++ b/src/menus/configMenu.ts @@ -1,5 +1,5 @@ import {AdvancedDynamicTexture, CheckboxGroup, RadioGroup, SelectionPanel, StackPanel} from "@babylonjs/gui"; -import {AbstractMesh, MeshBuilder, Scene, TransformNode, Vector3} from "@babylonjs/core"; +import {MeshBuilder, Scene, TransformNode, Vector3} from "@babylonjs/core"; import {AppConfig} from "../util/appConfig"; import log from "loglevel"; import {DefaultScene} from "../defaultScene"; @@ -35,8 +35,8 @@ export class ConfigMenu { this.buildMenu(); } - public get handleMesh(): AbstractMesh { - return this._handle.mesh; + public get handleTransformNode(): TransformNode { + return this._handle.transformNode; } private adjustRadio(radio: RadioGroup | CheckboxGroup) { @@ -167,20 +167,20 @@ export class ConfigMenu { const platform = this._scene.getMeshById('platform'); if (platform) { - this._handle.mesh.parent = platform; + this._handle.transformNode.parent = platform; if (!this._handle.idStored) { - this._handle.mesh.position = offset; - this._handle.mesh.rotation = rotation; + this._handle.transformNode.position = offset; + this._handle.transformNode.rotation = rotation; } } else { const handler = this._scene.onNewMeshAddedObservable.add((mesh) => { if (mesh && mesh.id == 'platform') { - this._handle.mesh.parent = mesh; + this._handle.transformNode.parent = mesh; if (!this._handle.idStored) { - this._handle.mesh.position = offset; - this._handle.mesh.rotation = rotation; + this._handle.transformNode.position = offset; + this._handle.transformNode.rotation = rotation; } diff --git a/src/newrelic/newRelicMenu.ts b/src/newrelic/newRelicMenu.ts index dcc4eba..b867028 100644 --- a/src/newrelic/newRelicMenu.ts +++ b/src/newrelic/newRelicMenu.ts @@ -14,7 +14,7 @@ export class NewRelicMenu extends AbstractMenu { buildMenu() { this.logger.debug('buildMenu'); - this.makeButton("credentials", "credentials"); + //this.makeButton("credentials", "credentials"); const grid = new Grid("grid"); grid.addColumnDefinition(.5); grid.addColumnDefinition(.5); @@ -22,12 +22,6 @@ export class NewRelicMenu extends AbstractMenu { } - makeButton(name: string, id: string) { - const button = super.makeButton(name, id); - button.onPointerClickObservable.add(this.handleClick, -1, false, this); - return button; - } - private handleClick(_info, state) { this.logger.debug("clicked " + state.currentTarget.name); switch (state.currentTarget.name) { diff --git a/src/objects/Button.ts b/src/objects/Button.ts index a9e121b..246d0cc 100644 --- a/src/objects/Button.ts +++ b/src/objects/Button.ts @@ -37,8 +37,8 @@ export class Button { public onPointerObservable: Observable = new Observable(); private _scene: Scene; private _mesh: AbstractMesh; - private _width: number; - private _height: number; + private readonly _width: number; + private readonly _height: number; private _background: Color3; private _color: Color3; private _hoverBackground: Color3; diff --git a/src/objects/handle.ts b/src/objects/handle.ts index 42eebc1..1315656 100644 --- a/src/objects/handle.ts +++ b/src/objects/handle.ts @@ -12,7 +12,7 @@ import log, {Logger} from "loglevel"; import {split} from "canvas-hypertxt"; export class Handle { - public mesh: TransformNode; + public transformNode: TransformNode; private readonly _menuItem: TransformNode; private _isStored: boolean = false; private _offset: Vector3; @@ -75,7 +75,7 @@ export class Handle { handle.rotation = this._rotation; } handle.metadata = {handle: true}; - this.mesh = handle; + this.transformNode = handle; } diff --git a/src/react/webApp.tsx b/src/react/webApp.tsx index d2acb25..714098a 100644 --- a/src/react/webApp.tsx +++ b/src/react/webApp.tsx @@ -27,6 +27,33 @@ function MainMenu({onClick}) { } +function PasswordDialog() { + const onsubmitClick = (evt) => { + evt.preventDefault(); + const password = (document.querySelector('#passwordInput') as HTMLInputElement).value; + if (password.length < 4) { + window.alert('Password must be longer than 4 characters'); + } else { + const event = new CustomEvent('passwordset', {detail: password}); + document.dispatchEvent(event); + document.querySelector('#password').style.display = 'none'; + } + } + const onCancelClick = (evt) => { + evt.preventDefault(); + document.querySelector('#password').style.display = 'none'; + } + return ( +
+
+
+ + +
+
+ ) +} + function CreateMenu({display, toggleCreateMenu}) { const onCreateClick = (evt) => { evt.preventDefault(); @@ -152,13 +179,21 @@ function Menu() { + ) } export default function WebApp() { + document.addEventListener('promptpassword', (evt) => { + const password = document.querySelector('#password'); + if (password) { + password.style.display = 'block'; + } + }); return (
+
) } diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts index 483d81a..250b840 100644 --- a/src/toolbox/toolbox.ts +++ b/src/toolbox/toolbox.ts @@ -38,8 +38,8 @@ export class Toolbox { return Toolbox._instance; } - public get handleMesh(): AbstractMesh { - return this._handle.mesh; + public get handleMesh(): TransformNode { + return this._handle.transformNode; } public isTool(mesh: AbstractMesh) { @@ -113,10 +113,10 @@ export class Toolbox { const rotation = new Vector3(.5, -.6, .18); const handle = this._handle; - handle.mesh.parent = mesh; + handle.transformNode.parent = mesh; if (!handle.idStored) { - handle.mesh.position = offset; - handle.mesh.rotation = rotation; + handle.transformNode.position = offset; + handle.transformNode.rotation = rotation; } } diff --git a/src/util/functions/sceneInspector.ts b/src/util/functions/sceneInspector.ts index d97ddcb..b33fe94 100644 --- a/src/util/functions/sceneInspector.ts +++ b/src/util/functions/sceneInspector.ts @@ -1,5 +1,6 @@ import {DefaultScene} from "../../defaultScene"; + export function addSceneInspector() { const scene = DefaultScene.Scene; window.addEventListener("keydown", (ev) => { @@ -10,6 +11,10 @@ export function addSceneInspector() { //voiceManager.stopRecording(); } if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.keyCode === 73) { + + const web = document.querySelector('#webApp'); + (web as HTMLDivElement).style.display = 'none'; + import ("@babylonjs/inspector").then((inspector) => { inspector.Inspector.Show(DefaultScene.Scene, { overlay: true, diff --git a/vite.config.ts b/vite.config.ts index 41996b7..a9e480f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,7 +6,15 @@ export default defineConfig({ test: {}, define: {}, build: { - sourcemap: "inline" + sourcemap: true, + rollupOptions: { + output: { + manualChunks: { + 'babylon': ['@babylonjs/core'], + 'crypto': ["js-crypto-aes", "hash-wasm", "uint8-to-b64"] + } + } + } }, optimizeDeps: { esbuildOptions: { @@ -31,6 +39,24 @@ export default defineConfig({ changeOrigin: true, }, } + + }, + preview: { + port: 3001, + proxy: { + '^/sync/.*': { + target: 'https://www.deepdiagram.com/', + changeOrigin: true, + }, + '^/create-db': { + target: 'https://www.deepdiagram.com/', + changeOrigin: true, + }, + '^/api/images': { + target: 'https://www.deepdiagram.com/', + changeOrigin: true, + }, + } }, base: "/"