added row level AES encryption.

This commit is contained in:
Michael Mainguy 2024-06-10 15:52:45 -05:00
parent d6941fd1bf
commit 3d3f73c259
22 changed files with 421 additions and 123 deletions

29
package-lock.json generated
View File

@ -32,7 +32,9 @@
"earcut": "^2.2.4", "earcut": "^2.2.4",
"events": "^3.3.0", "events": "^3.3.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"hash-wasm": "4.11.0",
"hls.js": "^1.1.4", "hls.js": "^1.1.4",
"js-crypto-aes": "1.0.6",
"loglevel": "^1.9.1", "loglevel": "^1.9.1",
"niceware": "^4.0.0", "niceware": "^4.0.0",
"peer-lite": "2.0.2", "peer-lite": "2.0.2",
@ -43,6 +45,7 @@
"recordrtc": "^5.6.0", "recordrtc": "^5.6.0",
"rfc4648": "^1.5.3", "rfc4648": "^1.5.3",
"round": "^2.0.1", "round": "^2.0.1",
"uint8-to-b64": "^1.0.2",
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
@ -1702,6 +1705,11 @@
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true "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": { "node_modules/hls.js": {
"version": "1.5.8", "version": "1.5.8",
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.8.tgz", "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.8.tgz",
@ -1826,6 +1834,19 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true "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": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -3029,6 +3050,14 @@
"integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==",
"dev": true "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": { "node_modules/undici-types": {
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",

View File

@ -22,36 +22,28 @@
"@babylonjs/inspector": "^7.9.0", "@babylonjs/inspector": "^7.9.0",
"@babylonjs/loaders": "^7.9.0", "@babylonjs/loaders": "^7.9.0",
"@babylonjs/materials": "^7.9.0", "@babylonjs/materials": "^7.9.0",
"@babylonjs/procedural-textures": "^7.9.0",
"@babylonjs/serializers": "^7.9.0", "@babylonjs/serializers": "^7.9.0",
"@maptiler/client": "1.8.1", "@maptiler/client": "1.8.1",
"@picovoice/cobra-web": "^2.0.3", "@picovoice/cobra-web": "^2.0.3",
"@picovoice/eagle-web": "^1.0.0", "@picovoice/eagle-web": "^1.0.0",
"@picovoice/web-voice-processor": "^4.0.9", "@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/node": "^18.14.0",
"@types/react": "^18.2.72", "@types/react": "^18.2.72",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^18.2.22",
"axios": "^1.6.8", "axios": "^1.6.8",
"canvas-hypertxt": "1.0.3", "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", "hls.js": "^1.1.4",
"loglevel": "^1.9.1", "loglevel": "^1.9.1",
"niceware": "^4.0.0",
"peer-lite": "2.0.2", "peer-lite": "2.0.2",
"pouchdb": "^8.0.1", "pouchdb": "^8.0.1",
"pouchdb-find": "^8.0.1",
"query-string": "^8.1.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"recordrtc": "^5.6.0", "recordrtc": "^5.6.0",
"rfc4648": "^1.5.3", "rfc4648": "^1.5.3",
"round": "^2.0.1", "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": { "devDependencies": {
"@types/dom-to-image": "^2.6.7", "@types/dom-to-image": "^2.6.7",

View File

@ -162,6 +162,11 @@ h1 {
height: 210px; height: 210px;
} }
#password {
display: none;
left: 50%;
top: 50%;
}
#diagramList { #diagramList {
left: 340px; left: 340px;
top: 400px; top: 400px;

View File

@ -162,10 +162,8 @@ export class Rigplatform {
private initializeControllers() { private initializeControllers() {
this.xr.input.onControllerAddedObservable.add((source) => { this.xr.input.onControllerAddedObservable.add((source) => {
this.registerObserver(); this.registerObserver();
let controller;
switch (source.inputSource.handedness) { switch (source.inputSource.handedness) {
case RIGHT: case RIGHT:
if (!this.rightController) { if (!this.rightController) {
@ -179,9 +177,6 @@ export class Rigplatform {
break; break;
} }
//this.xr.baseExperience.camera.position = new Vector3(0, 0, 0); //this.xr.baseExperience.camera.position = new Vector3(0, 0, 0);
if (controller) {
controller.setRig(this);
}
}); });
this.xr.input.onControllerRemovedObservable.add((source) => { this.xr.input.onControllerRemovedObservable.add((source) => {
switch (source.inputSource.handedness) { switch (source.inputSource.handedness) {

View File

@ -94,13 +94,13 @@ export class DiagramManager {
private onDiagramEvent(event: DiagramEvent) { private onDiagramEvent(event: DiagramEvent) {
let diagramObject = this._diagramObjects.get(event.entity.id);
switch (event.type) { switch (event.type) {
case DiagramEventType.ADD: case DiagramEventType.ADD:
let diagramObject = this._diagramObjects.get(event.entity.id);
if (diagramObject) { if (diagramObject) {
diagramObject.fromDiagramEntity(event.entity); diagramObject.fromDiagramEntity(event.entity);
} else { } else {
diagramObject = new DiagramObject(this._scene, diagramObject = DiagramObject.CreateObject(this._scene,
this.onDiagramEventObservable, this.onDiagramEventObservable,
{diagramEntity: event.entity, actionManager: this._diagramEntityActionManager}); {diagramEntity: event.entity, actionManager: this._diagramEntityActionManager});
} }
@ -111,18 +111,20 @@ export class DiagramManager {
} }
break; break;
case DiagramEventType.REMOVE: case DiagramEventType.REMOVE:
const object = this._diagramObjects.get(event.entity.id); if (diagramObject) {
if (object) { diagramObject.dispose();
object.dispose();
} }
this._diagramObjects.delete(event.entity.id); this._diagramObjects.delete(event.entity.id);
break; break;
case DiagramEventType.MODIFY: case DiagramEventType.MODIFY:
console.log(event); console.log(event);
if (event.entity.text) { //diagramObject = this._diagramObjects.get(event.entity.id);
const diagramObject = this._diagramObjects.get(event.entity.id); if (diagramObject && event.entity.text) {
diagramObject.text = event.entity.text; diagramObject.text = event.entity.text;
} }
if (event.entity.position) {
//diagramObject.position = event.entity.position;
}
break; break;
} }
} }

View File

@ -42,7 +42,7 @@ export class DiagramMenuManager {
if (viewOnly()) { if (viewOnly()) {
this.toolbox.handleMesh.setEnabled(false); this.toolbox.handleMesh.setEnabled(false);
//this.scaleMenu.handleMesh.setEnabled(false) //this.scaleMenu.handleMesh.setEnabled(false)
this.configMenu.handleMesh.setEnabled(false); this.configMenu.handleTransformNode.setEnabled(false);
} }
controllers.controllerObservable.add((event: ControllerEvent) => { controllers.controllerObservable.add((event: ControllerEvent) => {
if (event.type == ControllerEventType.B_BUTTON) { if (event.type == ControllerEventType.B_BUTTON) {
@ -65,7 +65,7 @@ export class DiagramMenuManager {
} }
const configY = this._inputTextView.handleMesh.absolutePosition.y; const configY = this._inputTextView.handleMesh.absolutePosition.y;
if (configY > (cameraPos.y - .2)) { if (configY > (cameraPos.y - .2)) {
this.configMenu.handleMesh.position.y = localCamera.y - .2; this.configMenu.handleTransformNode.position.y = localCamera.y - .2;
} }
} }
} }

View File

@ -26,12 +26,12 @@ type DiagramObjectOptionsType = {
export class DiagramObject { export class DiagramObject {
private readonly _logger: Logger = log.getLogger('DiagramObject'); private readonly _logger: Logger = log.getLogger('DiagramObject');
private _scene: Scene; private _scene: Scene;
public grabbed: boolean = false;
private _from: string; private _from: string;
private _to: string; private _to: string;
private _observingStart: number; private _observingStart: number;
private _sceneObserver: Observer<Scene>; private _sceneObserver: Observer<Scene>;
private _eventObservable: Observable<DiagramEvent>; private _eventObservable: Observable<DiagramEvent>;
private _mesh: AbstractMesh;
private _label: AbstractMesh; private _label: AbstractMesh;
private _meshesPresent: boolean = false; private _meshesPresent: boolean = false;
private _positionHash: string; private _positionHash: string;
@ -39,12 +39,6 @@ export class DiagramObject {
private _toMesh: AbstractMesh; private _toMesh: AbstractMesh;
private _meshRemovedObserver: Observer<AbstractMesh>; private _meshRemovedObserver: Observer<AbstractMesh>;
public grabbed: boolean = false;
public get mesh(): AbstractMesh {
return this._mesh;
}
constructor(scene: Scene, eventObservable: Observable<DiagramEvent>, options?: DiagramObjectOptionsType) { constructor(scene: Scene, eventObservable: Observable<DiagramEvent>, options?: DiagramObjectOptionsType) {
this._eventObservable = eventObservable; this._eventObservable = eventObservable;
this._scene = scene; this._scene = scene;
@ -58,7 +52,7 @@ export class DiagramObject {
const myEntity = this.fromDiagramEntity(options.diagramEntity); const myEntity = this.fromDiagramEntity(options.diagramEntity);
if (!myEntity) { if (!myEntity) {
this._logger.warn('DiagramObject constructor called with invalid diagramEntity', options.diagramEntity); this._logger.warn('DiagramObject constructor called with invalid diagramEntity', options.diagramEntity);
return null; this._valid = false;
} }
} }
if (options.mesh) { if (options.mesh) {
@ -69,6 +63,28 @@ export class DiagramObject {
this._mesh.actionManager = options.actionManager; 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<DiagramEvent>, options: DiagramObjectOptionsType): DiagramObject {
const newObj = new DiagramObject(scene, eventObservable, options);
if (newObj.valid) {
return newObj;
} else {
return null;
}
} }
private _baseTransform: TransformNode; private _baseTransform: TransformNode;
@ -216,9 +232,18 @@ export class DiagramObject {
} }
} else { } else {
this._mesh.setParent(this._baseTransform); this._mesh.setParent(this._baseTransform);
this._baseTransform.position = xyztovec(entity.position); if (entity.position) {
this._baseTransform.rotation = xyztovec(entity.rotation); this._baseTransform.position = xyztovec(entity.position)
this._mesh.scaling = xyztovec(entity.scale); }
;
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.position = Vector3.Zero();
this._mesh.rotation = Vector3.Zero(); this._mesh.rotation = Vector3.Zero();
} }

View File

@ -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 log, {Logger} from "loglevel";
import {AdvancedDynamicTexture, Control, InputText, VirtualKeyboard} from "@babylonjs/gui"; import {AdvancedDynamicTexture, Control, InputText, VirtualKeyboard} from "@babylonjs/gui";
import {ControllerEventType, Controllers} from "../controllers/controllers"; import {ControllerEventType, Controllers} from "../controllers/controllers";
@ -33,8 +33,12 @@ export class InputTextView {
this.createKeyboard(); this.createKeyboard();
} }
public get handleMesh(): TransformNode {
return this.handle.transformNode;
}
public show(mesh: AbstractMesh) { public show(mesh: AbstractMesh) {
this.handle.mesh.setEnabled(true); this.handle.transformNode.setEnabled(true);
if (mesh.metadata?.text) { if (mesh.metadata?.text) {
this.inputText.text = mesh.metadata?.text; this.inputText.text = mesh.metadata?.text;
} else { } else {
@ -46,15 +50,6 @@ export class InputTextView {
this.logger.debug(mesh.metadata); 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() { public createKeyboard() {
const platform = this.scene.getMeshById('platform'); const platform = this.scene.getMeshById('platform');
const position = new Vector3(0, 1.66, .53); const position = new Vector3(0, 1.66, .53);
@ -70,18 +65,18 @@ export class InputTextView {
this.scene.onNewMeshAddedObservable.add((mesh) => { this.scene.onNewMeshAddedObservable.add((mesh) => {
if (mesh.id == 'platform') { if (mesh.id == 'platform') {
this.logger.debug("platform added"); this.logger.debug("platform added");
handle.mesh.parent = mesh; handle.transformNode.parent = mesh;
if (!handle.idStored) { if (!handle.idStored) {
handle.mesh.position = position; handle.transformNode.position = position;
handle.mesh.rotation = rotation; handle.transformNode.rotation = rotation;
} }
} }
}, -1, false, this, false); }, -1, false, this, false);
} else { } else {
handle.mesh.setParent(platform); handle.transformNode.setParent(platform);
if (!handle.idStored) { if (!handle.idStored) {
handle.mesh.position = position; handle.transformNode.position = position;
handle.mesh.rotation = rotation; handle.transformNode.rotation = rotation;
} }
} }
@ -143,6 +138,11 @@ export class InputTextView {
} }
}, -1, false, this, false); }, -1, false, this, false);
this.keyboard = keyboard; this.keyboard = keyboard;
this.handle.mesh.setEnabled(false); this.handle.transformNode.setEnabled(false);
}
private hide() {
this.handle.transformNode.setEnabled(false);
this.diagramMesh = null;
} }
} }

View File

@ -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)
}
}
}
}

View File

@ -1,5 +1,5 @@
export function hex_to_ascii(input) { export function hex_to_ascii(input) {
var hex = input.toString(); const hex = input.toString();
let output = ''; let output = '';
for (let n = 0; n < hex.length; n += 2) { for (let n = 0; n < hex.length; n += 2) {
output += String.fromCharCode(parseInt(hex.substr(n, 2), 16)); 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) { export function ascii_to_hex(str) {
const arr1 = []; const arr1 = [];
for (let n = 0, l = str.length; n < l; n++) { 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); arr1.push(hex);
} }
return arr1.join(''); return arr1.join('');

View File

@ -2,22 +2,44 @@ import log from "loglevel";
import {DiagramEntity} from "../../diagram/types/diagramEntity"; import {DiagramEntity} from "../../diagram/types/diagramEntity";
import {Observable} from "@babylonjs/core"; import {Observable} from "@babylonjs/core";
import {UserModelType} from "../../users/userTypes"; import {UserModelType} from "../../users/userTypes";
import {Encryption} from "../encryption";
export function syncDoc(info: any, onDBRemoveObservable: Observable<DiagramEntity>, onDBUpdateObservable: Observable<DiagramEntity>, onUserObservable: Observable<UserModelType>) { export async function syncDoc(info: any, onDBRemoveObservable: Observable<DiagramEntity>, onDBUpdateObservable: Observable<DiagramEntity>, onUserObservable: Observable<UserModelType>,
encryption: Encryption, key: string) {
const logger = log.getLogger('syncDoc'); const logger = log.getLogger('syncDoc');
logger.debug(info); logger.debug(info);
if (info.direction == 'pull') { if (info.direction == 'pull') {
const docs = info.change.docs; const docs = info.change.docs;
let salt = null;
for (const doc of docs) { for (const doc of docs) {
if (doc.type == 'user') { if (doc.encrypted) {
//onUserObservable.notifyObservers(doc, -1); if (salt != doc.encrypted.salt || (key && !encryption.ready)) {
} else { await encryption.setPassword(key, doc.encrypted.salt);
logger.debug(doc); salt = doc.encrypted.salt
if (doc._deleted) { }
logger.debug('Delete', doc); const decrypted = await encryption.decryptToObject(doc.encrypted.encrypted, doc.encrypted.iv);
onDBRemoveObservable.notifyObservers({id: doc._id, template: doc.template}, 1); if (decrypted.type == 'user') {
//onUserObservable.notifyObservers(doc, -1);
} else { } 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);
}
} }
} }
} }

View File

@ -53,7 +53,7 @@ export class NewRelicQuery {
const plane = MeshBuilder.CreatePlane("plane", {width: .5, height: .25}, this.scene); const plane = MeshBuilder.CreatePlane("plane", {width: .5, height: .25}, this.scene);
plane.billboardMode = Mesh.BILLBOARDMODE_ALL; plane.billboardMode = Mesh.BILLBOARDMODE_ALL;
const advancedTexture = AdvancedDynamicTexture.CreateForMesh(plane, 1024, 512, false); const advancedTexture = AdvancedDynamicTexture.CreateForMesh(plane, 1024, 512, false);
var text1 = new TextBlock(); const text1 = new TextBlock();
text1.text = text; text1.text = text;
advancedTexture.background = "#000000"; advancedTexture.background = "#000000";
text1.color = "white"; text1.color = "white";

View File

@ -11,6 +11,7 @@ import {syncDoc} from "./functions/syncDoc";
import {checkDb} from "./functions/checkDb"; import {checkDb} from "./functions/checkDb";
import {UserModelType} from "../users/userTypes"; import {UserModelType} from "../users/userTypes";
import {getMe} from "../util/me"; import {getMe} from "../util/me";
import {Encryption} from "./encryption";
export class PouchdbPersistenceManager { export class PouchdbPersistenceManager {
private _logger: Logger = log.getLogger('PouchdbPersistenceManager'); private _logger: Logger = log.getLogger('PouchdbPersistenceManager');
@ -20,10 +21,16 @@ export class PouchdbPersistenceManager {
private db: PouchDB; private db: PouchDB;
private remote: PouchDB; private remote: PouchDB;
private user: string; private user: string;
private _encryption = new Encryption();
private _encKey = null;
constructor() { 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) { public setDiagramManager(diagramManager: DiagramManager) {
diagramManager.onDiagramEventObservable.add((evt) => { diagramManager.onDiagramEventObservable.add((evt) => {
@ -80,10 +87,7 @@ export class PouchdbPersistenceManager {
} else { } else {
this._logger.error(err); this._logger.error(err);
} }
} }
} }
public async remove(id: string) { public async remove(id: string) {
if (!id) { if (!id) {
@ -101,17 +105,41 @@ export class PouchdbPersistenceManager {
if (!entity) { if (!entity) {
return; return;
} }
if (this._encKey && !this._encryption.ready) {
await this._encryption.setPassword(this._encKey);
}
try { try {
const doc = await this.db.get(entity.id); const doc = await this.db.get(entity.id);
if (doc) { if (this._encKey) {
const newDoc = {...doc, ...entity};
this.db.put(newDoc); 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) { } catch (err) {
if (err.status == 404) { if (err.status == 404) {
try { try {
const newEntity = {...entity, _id: entity.id}; if (this._encKey) {
this.db.put(newEntity); 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) { } catch (err2) {
this._logger.error(err2); this._logger.error(err2);
} }
@ -128,28 +156,52 @@ export class PouchdbPersistenceManager {
await this.sendLocalDataToScene(); await this.sendLocalDataToScene();
} }
private async setupMetadata(current: string) { private async setupMetadata(current: string): Promise<boolean> {
try { try {
const doc = await this.db.get('metadata'); const doc = await this.db.get('metadata');
if (doc && doc.friendly) { if (doc.encrypted) {
localStorage.setItem(current, doc.friendly); if (!this._encKey) {
} const promptPassword = new CustomEvent('promptpassword', {detail: 'Please enter password'});
if (doc && doc.camera) { 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) { } catch (err) {
if (err.status == 404) { if (err.status == 404) {
this._logger.debug('no metadata found'); this._logger.debug('no metadata found');
const friendly = localStorage.getItem(current); const friendly = localStorage.getItem(current);
if (friendly) { if (friendly) {
this._logger.debug('local friendly name found ', friendly, ' setting metadata'); if (this._encKey) {
const newDoc = {_id: 'metadata', id: 'metadata', friendly: friendly}; if (!this._encryption.ready) {
await this.db.put(newDoc); 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 { } else {
this._logger.debug('no friendly name found'); this._logger.debug('no friendly name found');
} }
} }
} }
return true;
} }
private async initLocal(): Promise<boolean> { private async initLocal(): Promise<boolean> {
@ -165,8 +217,9 @@ export class PouchdbPersistenceManager {
this.db = new PouchDB(current, {auto_compaction: true}); this.db = new PouchDB(current, {auto_compaction: true});
//await this.db.compact(); //await this.db.compact();
if (sync) { if (sync) {
await this.setupMetadata(current); if (await this.setupMetadata(current)) {
await this.beginSync(current); await this.beginSync(current);
}
} }
return true; return true;
} catch (err) { } catch (err) {
@ -177,20 +230,38 @@ export class PouchdbPersistenceManager {
} }
private async sendLocalDataToScene() { private async sendLocalDataToScene() {
let salt = null;
const clear = localStorage.getItem('clearLocal'); const clear = localStorage.getItem('clearLocal');
try { try {
const all = await this.db.allDocs({include_docs: true}); const all = await this.db.allDocs({include_docs: true});
for (const entity of all.rows) { for (const dbEntity of all.rows) {
this._logger.debug(entity.doc); this._logger.debug(dbEntity.doc);
if (clear) { if (clear) {
this.remove(entity.id); this.remove(dbEntity.id);
} else { } else {
if (entity.type == 'user') { if (dbEntity.doc.encrypted) {
this.onUserObservable.notifyObservers(entity.doc); 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 { } else {
if (entity.id != 'metadata') { if (dbEntity.type == 'user') {
this.onDBEntityUpdateObservable.notifyObservers(entity.doc, DiagramEventObserverMask.FROM_DB); 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'); localStorage.removeItem('clearLocal');
} }
} catch (err) { } 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); this._logger.error(err);
} }
} }
@ -242,9 +319,10 @@ export class PouchdbPersistenceManager {
{auth: {username: remoteUserName, password: password}, skip_setup: true}); {auth: {username: remoteUserName, password: password}, skip_setup: true});
const dbInfo = await this.remote.info(); const dbInfo = await this.remote.info();
this._logger.debug(dbInfo); this._logger.debug(dbInfo);
this.db.sync(this.remote, {live: true, retry: true}) this.db.sync(this.remote, {live: true, retry: true})
.on('change', (info) => { .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) => { .on('active', (info) => {
this._logger.debug('sync active', info) this._logger.debug('sync active', info)

View File

@ -3,7 +3,7 @@ export function tile2long(x, z) {
} }
export function tile2lat(y, 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)))); return (180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))));
} }

View File

@ -1,5 +1,5 @@
import {AdvancedDynamicTexture, CheckboxGroup, RadioGroup, SelectionPanel, StackPanel} from "@babylonjs/gui"; 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 {AppConfig} from "../util/appConfig";
import log from "loglevel"; import log from "loglevel";
import {DefaultScene} from "../defaultScene"; import {DefaultScene} from "../defaultScene";
@ -35,8 +35,8 @@ export class ConfigMenu {
this.buildMenu(); this.buildMenu();
} }
public get handleMesh(): AbstractMesh { public get handleTransformNode(): TransformNode {
return this._handle.mesh; return this._handle.transformNode;
} }
private adjustRadio(radio: RadioGroup | CheckboxGroup) { private adjustRadio(radio: RadioGroup | CheckboxGroup) {
@ -167,20 +167,20 @@ export class ConfigMenu {
const platform = this._scene.getMeshById('platform'); const platform = this._scene.getMeshById('platform');
if (platform) { if (platform) {
this._handle.mesh.parent = platform; this._handle.transformNode.parent = platform;
if (!this._handle.idStored) { if (!this._handle.idStored) {
this._handle.mesh.position = offset; this._handle.transformNode.position = offset;
this._handle.mesh.rotation = rotation; this._handle.transformNode.rotation = rotation;
} }
} else { } else {
const handler = this._scene.onNewMeshAddedObservable.add((mesh) => { const handler = this._scene.onNewMeshAddedObservable.add((mesh) => {
if (mesh && mesh.id == 'platform') { if (mesh && mesh.id == 'platform') {
this._handle.mesh.parent = mesh; this._handle.transformNode.parent = mesh;
if (!this._handle.idStored) { if (!this._handle.idStored) {
this._handle.mesh.position = offset; this._handle.transformNode.position = offset;
this._handle.mesh.rotation = rotation; this._handle.transformNode.rotation = rotation;
} }

View File

@ -14,7 +14,7 @@ export class NewRelicMenu extends AbstractMenu {
buildMenu() { buildMenu() {
this.logger.debug('buildMenu'); this.logger.debug('buildMenu');
this.makeButton("credentials", "credentials"); //this.makeButton("credentials", "credentials");
const grid = new Grid("grid"); const grid = new Grid("grid");
grid.addColumnDefinition(.5); grid.addColumnDefinition(.5);
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) { private handleClick(_info, state) {
this.logger.debug("clicked " + state.currentTarget.name); this.logger.debug("clicked " + state.currentTarget.name);
switch (state.currentTarget.name) { switch (state.currentTarget.name) {

View File

@ -37,8 +37,8 @@ export class Button {
public onPointerObservable: Observable<ActionEvent> = new Observable<ActionEvent>(); public onPointerObservable: Observable<ActionEvent> = new Observable<ActionEvent>();
private _scene: Scene; private _scene: Scene;
private _mesh: AbstractMesh; private _mesh: AbstractMesh;
private _width: number; private readonly _width: number;
private _height: number; private readonly _height: number;
private _background: Color3; private _background: Color3;
private _color: Color3; private _color: Color3;
private _hoverBackground: Color3; private _hoverBackground: Color3;

View File

@ -12,7 +12,7 @@ import log, {Logger} from "loglevel";
import {split} from "canvas-hypertxt"; import {split} from "canvas-hypertxt";
export class Handle { export class Handle {
public mesh: TransformNode; public transformNode: TransformNode;
private readonly _menuItem: TransformNode; private readonly _menuItem: TransformNode;
private _isStored: boolean = false; private _isStored: boolean = false;
private _offset: Vector3; private _offset: Vector3;
@ -75,7 +75,7 @@ export class Handle {
handle.rotation = this._rotation; handle.rotation = this._rotation;
} }
handle.metadata = {handle: true}; handle.metadata = {handle: true};
this.mesh = handle; this.transformNode = handle;
} }

View File

@ -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 (
<div className="overlay" id="password">
<div>
<div><input autoComplete="on" id="passwordInput" placeholder="Enter password" type="password"/></div>
<div><a href="#" id="passwordActionLink" onClick={onsubmitClick}>Enter</a></div>
<div><a className="cancel" href="#" onClick={onCancelClick} id="cancelPasswordLink">Cancel</a></div>
</div>
</div>
)
}
function CreateMenu({display, toggleCreateMenu}) { function CreateMenu({display, toggleCreateMenu}) {
const onCreateClick = (evt) => { const onCreateClick = (evt) => {
evt.preventDefault(); evt.preventDefault();
@ -152,13 +179,21 @@ function Menu() {
<CreateMenu display={createState} toggleCreateMenu={handleCreateClick}/> <CreateMenu display={createState} toggleCreateMenu={handleCreateClick}/>
<DiagramList onClick={handleCreateClick} display={diagramListState}/> <DiagramList onClick={handleCreateClick} display={diagramListState}/>
</div> </div>
) )
} }
export default function WebApp() { export default function WebApp() {
document.addEventListener('promptpassword', (evt) => {
const password = document.querySelector('#password');
if (password) {
password.style.display = 'block';
}
});
return ( return (
<div> <div>
<Menu/> <Menu/>
<PasswordDialog/>
</div> </div>
) )
} }

View File

@ -38,8 +38,8 @@ export class Toolbox {
return Toolbox._instance; return Toolbox._instance;
} }
public get handleMesh(): AbstractMesh { public get handleMesh(): TransformNode {
return this._handle.mesh; return this._handle.transformNode;
} }
public isTool(mesh: AbstractMesh) { public isTool(mesh: AbstractMesh) {
@ -113,10 +113,10 @@ export class Toolbox {
const rotation = new Vector3(.5, -.6, .18); const rotation = new Vector3(.5, -.6, .18);
const handle = this._handle; const handle = this._handle;
handle.mesh.parent = mesh; handle.transformNode.parent = mesh;
if (!handle.idStored) { if (!handle.idStored) {
handle.mesh.position = offset; handle.transformNode.position = offset;
handle.mesh.rotation = rotation; handle.transformNode.rotation = rotation;
} }
} }

View File

@ -1,5 +1,6 @@
import {DefaultScene} from "../../defaultScene"; import {DefaultScene} from "../../defaultScene";
export function addSceneInspector() { export function addSceneInspector() {
const scene = DefaultScene.Scene; const scene = DefaultScene.Scene;
window.addEventListener("keydown", (ev) => { window.addEventListener("keydown", (ev) => {
@ -10,6 +11,10 @@ export function addSceneInspector() {
//voiceManager.stopRecording(); //voiceManager.stopRecording();
} }
if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.keyCode === 73) { 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) => { import ("@babylonjs/inspector").then((inspector) => {
inspector.Inspector.Show(DefaultScene.Scene, { inspector.Inspector.Show(DefaultScene.Scene, {
overlay: true, overlay: true,

View File

@ -6,7 +6,15 @@ export default defineConfig({
test: {}, test: {},
define: {}, define: {},
build: { build: {
sourcemap: "inline" sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
'babylon': ['@babylonjs/core'],
'crypto': ["js-crypto-aes", "hash-wasm", "uint8-to-b64"]
}
}
}
}, },
optimizeDeps: { optimizeDeps: {
esbuildOptions: { esbuildOptions: {
@ -31,6 +39,24 @@ export default defineConfig({
changeOrigin: true, 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: "/" base: "/"