added row level AES encryption.
This commit is contained in:
parent
d6941fd1bf
commit
3d3f73c259
29
package-lock.json
generated
29
package-lock.json
generated
@ -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",
|
||||
|
||||
16
package.json
16
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",
|
||||
|
||||
@ -162,6 +162,11 @@ h1 {
|
||||
height: 210px;
|
||||
}
|
||||
|
||||
#password {
|
||||
display: none;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
}
|
||||
#diagramList {
|
||||
left: 340px;
|
||||
top: 400px;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
90
src/integration/encryption.ts
Normal file
90
src/integration/encryption.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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('');
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))));
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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: "/"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user