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",
"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",

View File

@ -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",

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Scene>;
private _eventObservable: Observable<DiagramEvent>;
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<AbstractMesh>;
public grabbed: boolean = false;
public get mesh(): AbstractMesh {
return this._mesh;
}
constructor(scene: Scene, eventObservable: Observable<DiagramEvent>, 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<DiagramEvent>, 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();
}

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 {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;
}
}

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) {
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('');

View File

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

View File

@ -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";

View File

@ -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<boolean> {
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<boolean> {
@ -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)

View File

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

View File

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

View File

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

View File

@ -37,8 +37,8 @@ export class Button {
public onPointerObservable: Observable<ActionEvent> = new Observable<ActionEvent>();
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;

View File

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

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}) {
const onCreateClick = (evt) => {
evt.preventDefault();
@ -152,13 +179,21 @@ function Menu() {
<CreateMenu display={createState} toggleCreateMenu={handleCreateClick}/>
<DiagramList onClick={handleCreateClick} display={diagramListState}/>
</div>
)
}
export default function WebApp() {
document.addEventListener('promptpassword', (evt) => {
const password = document.querySelector('#password');
if (password) {
password.style.display = 'block';
}
});
return (
<div>
<Menu/>
<PasswordDialog/>
</div>
)
}

View File

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

View File

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

View File

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