639 lines
24 KiB
TypeScript
639 lines
24 KiB
TypeScript
import { createUserReturn, User, Process } from '../dist/pkg/sdk_client';
|
|
import IndexedDB from './database'
|
|
import { WebSocketClient } from './websockets';
|
|
|
|
class Services {
|
|
private static instance: Services;
|
|
private sdkClient: any;
|
|
private current_process: string | null = null;
|
|
private websocketConnection: WebSocketClient[] = [];
|
|
|
|
// Private constructor to prevent direct instantiation from outside
|
|
private constructor() {}
|
|
|
|
// Method to access the singleton instance of Services
|
|
public static async getInstance(): Promise<Services> {
|
|
if (!Services.instance) {
|
|
Services.instance = new Services();
|
|
await Services.instance.init();
|
|
}
|
|
return Services.instance;
|
|
}
|
|
|
|
// The init method is now part of the instance, and should only be called once
|
|
private async init(): Promise<void> {
|
|
this.sdkClient = await import("../dist/pkg/sdk_client");
|
|
this.sdkClient.setup();
|
|
await this.updateProcesses();
|
|
}
|
|
|
|
public async addWebsocketConnection(url: string): Promise<void> {
|
|
const services = await Services.getInstance();
|
|
const newClient = new WebSocketClient(url, services);
|
|
if (!services.websocketConnection.includes(newClient)) {
|
|
services.websocketConnection.push(newClient);
|
|
}
|
|
}
|
|
|
|
public async isNewUser(): Promise<boolean> {
|
|
let isNew = false;
|
|
try {
|
|
const indexedDB = await IndexedDB.getInstance();
|
|
const db = await indexedDB.getDb();
|
|
let userListObject = await indexedDB.getAll<User>(db, indexedDB.getStoreList().AnkUser);
|
|
if (userListObject.length == 0) {
|
|
isNew = true;
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to retrieve isNewUser :", error);
|
|
}
|
|
return isNew;
|
|
}
|
|
|
|
public async displayCreateId(): Promise<void> {
|
|
const services = await Services.getInstance();
|
|
await services.createIdInjectHtml();
|
|
services.attachSubmitListener("form4nk", (event) => services.createId(event));
|
|
services.attachClickListener("displayrecover", services.displayRecover);
|
|
await services.displayProcess();
|
|
}
|
|
|
|
public async createId(event: Event): Promise<void> {
|
|
event.preventDefault();
|
|
|
|
const passwordElement = document.getElementById("password") as HTMLInputElement;
|
|
const processElement = document.getElementById("selectProcess") as HTMLSelectElement;
|
|
|
|
if (!passwordElement || !processElement) {
|
|
console.error("One or more elements not found");
|
|
return;
|
|
}
|
|
|
|
const password = passwordElement.value;
|
|
this.current_process = processElement.value;
|
|
// console.log("JS password: " + password + " process: " + this.current_process);
|
|
// To comment if test
|
|
// if (!Services.instance.isPasswordValid(password)) return;
|
|
|
|
const label = null;
|
|
const birthday_signet = 50000;
|
|
const birthday_main = 500000;
|
|
|
|
const services = await Services.getInstance();
|
|
let createUserReturn: createUserReturn = services.sdkClient.create_user(password, label, birthday_main, birthday_signet, this.current_process);
|
|
|
|
let user = createUserReturn.user;
|
|
|
|
const shares = user.shares;
|
|
// send the shares on the network
|
|
const revokeData = user.revoke_data;
|
|
if (!revokeData) {
|
|
console.error('Failed to get revoke data from wasm');
|
|
return;
|
|
}
|
|
|
|
user.shares = [];
|
|
user.revoke_data = null;
|
|
|
|
try {
|
|
const indexedDb = await IndexedDB.getInstance();
|
|
const db = await indexedDb.getDb();
|
|
await indexedDb.writeObject(db, indexedDb.getStoreList().AnkUser, user, null);
|
|
} catch (error) {
|
|
console.error("Failed to write user object :", error);
|
|
}
|
|
|
|
let sp_address = "";
|
|
try {
|
|
sp_address = services.sdkClient.get_receiving_address(user.pre_id);
|
|
console.info('Using sp_address:', sp_address);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
|
|
await services.obtainTokenWithFaucet(sp_address);
|
|
|
|
await services.displayRevokeImage(new Uint8Array(revokeData));
|
|
}
|
|
|
|
public async displayRecover(): Promise<void> {
|
|
const services = await Services.getInstance();
|
|
await services.recoverInjectHtml();
|
|
services.attachSubmitListener("form4nk", (event) => services.recover(event));
|
|
services.attachClickListener("displaycreateid", services.displayCreateId);
|
|
services.attachClickListener("displayrevoke", services.displayRevoke);
|
|
services.attachClickListener("submitButtonRevoke", services.revoke);
|
|
await services.displayProcess();
|
|
}
|
|
|
|
public async recover(event: Event) {
|
|
event.preventDefault();
|
|
console.log("JS recover submit ");
|
|
|
|
const passwordElement = document.getElementById("password") as HTMLInputElement;
|
|
const processElement = document.getElementById("selectProcess") as HTMLSelectElement;
|
|
|
|
if (!passwordElement || !processElement) {
|
|
console.error("One or more elements not found");
|
|
return;
|
|
}
|
|
|
|
const password = passwordElement.value;
|
|
const process = processElement.value;
|
|
console.log("JS password: " + password + " process: " + process);
|
|
// To comment if test
|
|
// if (!Services.instance.isPasswordValid(password)) return;
|
|
|
|
// TODO
|
|
alert("Recover submit to do ...");
|
|
}
|
|
|
|
public async displayRevokeImage(revokeData: Uint8Array): Promise<void> {
|
|
const services = await Services.getInstance();
|
|
await services.revokeImageInjectHtml();
|
|
services.attachClickListener("displayupdateanid", services.displayUpdateAnId);
|
|
|
|
let imageBytes = await services.getRecoverImage('assets/4nk_revoke.jpg');
|
|
if (imageBytes != null) {
|
|
var elem = document.getElementById("revoke") as HTMLAnchorElement;
|
|
if (elem != null) {
|
|
let imageWithData = services.sdkClient.add_data_to_image(imageBytes, revokeData, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async getRecoverImage(imageUrl:string): Promise<Uint8Array|null> {
|
|
let imageBytes = null;
|
|
try {
|
|
const response = await fetch(imageUrl);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
|
|
}
|
|
const arrayBuffer = await response.arrayBuffer();
|
|
imageBytes = new Uint8Array(arrayBuffer);
|
|
} catch (error) {
|
|
console.error("Failed to get image : "+imageUrl, error);
|
|
}
|
|
return imageBytes;
|
|
}
|
|
|
|
public async displayRevoke(): Promise<void> {
|
|
const services = await Services.getInstance();
|
|
await services.revokeInjectHtml();
|
|
services.attachClickListener("displayrecover", Services.instance.displayRecover);
|
|
services.attachSubmitListener("form4nk", Services.instance.revoke);
|
|
}
|
|
|
|
public async revoke(event: Event): Promise<void> {
|
|
event.preventDefault();
|
|
console.log("JS revoke click ");
|
|
// TODO
|
|
alert("revoke click to do ...");
|
|
}
|
|
|
|
public async displayUpdateAnId() {
|
|
const services = await Services.getInstance();
|
|
|
|
await services.updateIdInjectHtml();
|
|
|
|
services.attachSubmitListener("form4nk", services.updateAnId);
|
|
}
|
|
|
|
public async parseNetworkMessage(raw: string): Promise<parseNetworkMsgReturn | null> {
|
|
const services = await Services.getInstance();
|
|
try {
|
|
const msg: parseNetworkMsgReturn = services.sdkClient.parse_network_msg(raw);
|
|
return msg;
|
|
} catch (error) {
|
|
console.error(error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async updateAnId(event: Event): Promise<void> {
|
|
event.preventDefault();
|
|
|
|
// TODO get values
|
|
const firstNameElement = 'firstName';
|
|
const lastNameElement = 'lastName';
|
|
const firstName = document.getElementById(firstNameElement) as HTMLInputElement;
|
|
const lastName = document.getElementById(lastNameElement) as HTMLInputElement;
|
|
|
|
console.log("JS updateAnId submit ");
|
|
// TODO
|
|
alert("updateAnId submit to do ... Name : "+firstName.value + " "+lastName.value);
|
|
|
|
// TODO Mock add user member to process
|
|
}
|
|
|
|
public async displayProcess(): Promise<void> {
|
|
const services = await Services.getInstance();
|
|
const processList = await services.getAllProcess();
|
|
const selectProcess = document.getElementById("selectProcess");
|
|
if (selectProcess) {
|
|
processList.forEach((process) => {
|
|
let child = new Option(process.name, process.name);
|
|
if (!selectProcess.contains(child)) {
|
|
selectProcess.appendChild(child);
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
public async addProcess(process: Process): Promise<void> {
|
|
try {
|
|
const indexedDB = await IndexedDB.getInstance();
|
|
const db = await indexedDB.getDb();
|
|
await indexedDB.writeObject(db, indexedDB.getStoreList().AnkProcess, process, null);
|
|
} catch (error) {
|
|
console.log('addProcess failed: ',error);
|
|
}
|
|
}
|
|
|
|
public async getAllProcess(): Promise<Process[]> {
|
|
try {
|
|
const indexedDB = await IndexedDB.getInstance();
|
|
const db = await indexedDB.getDb();
|
|
let processListObject = await indexedDB.getAll<Process>(db, indexedDB.getStoreList().AnkProcess);
|
|
return processListObject;
|
|
} catch (error) {
|
|
console.log('getAllProcess failed: ',error);
|
|
return [];
|
|
}
|
|
}
|
|
public async checkTransaction(tx: string, tweak_data: string, blkheight: number): Promise<string | null> {
|
|
const services = await Services.getInstance();
|
|
|
|
try {
|
|
const updated_user: string = services.sdkClient.check_transaction_for_silent_payments(tx, blkheight, tweak_data);
|
|
await services.updateOwnedOutputsForUser(updated_user);
|
|
return updated_user;
|
|
} catch (error) {
|
|
console.error(error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async getAllProcessForUser(pre_id: string): Promise<Process[]> {
|
|
const services = await Services.getInstance();
|
|
let user: User;
|
|
let userProcessList: Process[] = [];
|
|
try {
|
|
const indexedDB = await IndexedDB.getInstance();
|
|
const db = await indexedDB.getDb();
|
|
user = await indexedDB.getObject<User>(db, indexedDB.getStoreList().AnkUser, pre_id);
|
|
} catch (error) {
|
|
console.error('getAllUserProcess failed: ',error);
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
const processListObject = await services.getAllProcess();
|
|
processListObject.forEach(async (processObject) => {
|
|
if (processObject.members.includes(user.pre_id)) {
|
|
userProcessList.push(processObject);
|
|
}
|
|
})
|
|
} catch (error) {
|
|
console.error('getAllUserProcess failed: ',error);
|
|
return [];
|
|
}
|
|
return userProcessList;
|
|
}
|
|
|
|
public async getProcessByName(name: string): Promise<Process | null> {
|
|
console.log('getProcessByName name: '+name);
|
|
const indexedDB = await IndexedDB.getInstance();
|
|
const db = await indexedDB.getDb();
|
|
const process = await indexedDB.getFirstMatchWithIndex<Process>(db, indexedDB.getStoreList().AnkProcess, 'by_name', name);
|
|
console.log('getProcessByName process: '+process);
|
|
|
|
return process;
|
|
}
|
|
|
|
public async updateProcesses(): Promise<void> {
|
|
const services = await Services.getInstance();
|
|
const processList: Process[] = services.sdkClient.get_processes();
|
|
|
|
processList.forEach(async (process: Process) => {
|
|
const indexedDB = await IndexedDB.getInstance();
|
|
const db = await indexedDB.getDb();
|
|
try {
|
|
const processStore = await indexedDB.getObject<Process>(db, indexedDB.getStoreList().AnkProcess, process.id);
|
|
if (!processStore) {
|
|
await indexedDB.writeObject(db, indexedDB.getStoreList().AnkProcess, process, null);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error while writing process', process.name, 'to indexedDB:', error);
|
|
}
|
|
})
|
|
}
|
|
|
|
public attachClickListener(elementId: string, callback: (event: Event) => void): void {
|
|
const element = document.getElementById(elementId);
|
|
element?.removeEventListener("click", callback);
|
|
element?.addEventListener("click", callback);
|
|
}
|
|
|
|
public attachSubmitListener(elementId: string, callback: (event: Event) => void): void {
|
|
const element = document.getElementById(elementId);
|
|
element?.removeEventListener("submit", callback);
|
|
element?.addEventListener("submit", callback);
|
|
}
|
|
public async revokeInjectHtml() {
|
|
const container = document.getElementById('containerId');
|
|
|
|
if (!container) {
|
|
console.error("No html container");
|
|
return;
|
|
}
|
|
|
|
container.innerHTML =
|
|
` <div class='card'>
|
|
<div class='side-by-side'>
|
|
<h3>Revoke an Id</h3>
|
|
<div>
|
|
<a href='#' id='displayrecover'>Recover</a>
|
|
</div>
|
|
</div>
|
|
<form id='form4nk' action='#'>
|
|
<label for='password'>Password :</label>
|
|
<input type='password' id='password' />
|
|
<hr/>
|
|
<div class='image-container'>
|
|
<label class='image-label'>Revoke image</label>
|
|
<img src='assets/revoke.jpeg' alt='' />
|
|
</div>
|
|
<hr/>
|
|
<button type='submit' id='submitButton' class='recover bg-primary'>Revoke</button>
|
|
</form>
|
|
</div>
|
|
`;
|
|
}
|
|
public async revokeImageInjectHtml() {
|
|
const container = document.getElementById('containerId');
|
|
|
|
if (!container) {
|
|
console.error("No html container");
|
|
return;
|
|
}
|
|
|
|
container.innerHTML =
|
|
`<div class='card'>
|
|
<div class='side-by-side'>
|
|
<h3>Revoke image</h3>
|
|
<div><a href='#' id='displayupdateanid'>Update an Id</a></div>
|
|
</div>
|
|
</div>
|
|
<div class='card-revoke'>
|
|
<a href='#' download='revoke_4NK.jpg' id='revoke'>
|
|
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'>
|
|
<path
|
|
d='M246.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 109.3V320c0 17.7 14.3 32 32 32s32-14.3 32-32V109.3l73.4 73.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-128-128zM64 352c0-17.7-14.3-32-32-32s-32 14.3-32 32v64c0 53 43 96 96 96H352c53 0 96-43 96-96V352c0-17.7-14.3-32-32-32s-32 14.3-32 32v64c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V352z'
|
|
/>
|
|
</svg>
|
|
</a>
|
|
<div class='image-container'>
|
|
<img src='assets/4nk_revoke.jpg' alt='' />
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
public async recoverInjectHtml() {
|
|
const container = document.getElementById('containerId');
|
|
|
|
if (!container) {
|
|
console.error("No html container");
|
|
return;
|
|
}
|
|
|
|
const services = await Services.getInstance();
|
|
await services.updateProcesses();
|
|
|
|
container.innerHTML =
|
|
`<div class='card'>
|
|
<div class='side-by-side'>
|
|
<h3>Recover my Id</h3>
|
|
<div><a href='#'>Processes</a></div>
|
|
</div>
|
|
<form id='form4nk' action='#'>
|
|
<label for='password'>Password :</label>
|
|
<input type='password' id='password' />
|
|
<input type='hidden' id='currentpage' value='recover' />
|
|
<select id='selectProcess' class='custom-select'></select><hr/>
|
|
<div class='side-by-side'>
|
|
<button type='submit' id='submitButton' class='recover bg-primary'>Recover</button>
|
|
<div>
|
|
<a href='#' id='displaycreateid'>Create an Id</a>
|
|
</div>
|
|
</div><hr/>
|
|
<a href='#' id='displayrevoke' class='btn'>Revoke</a>
|
|
</form><br/>
|
|
<div id='passwordalert' class='passwordalert'></div>
|
|
</div>`;
|
|
}
|
|
|
|
public async createIdInjectHtml() {
|
|
const container = document.getElementById('containerId');
|
|
|
|
if (!container) {
|
|
console.error("No html container");
|
|
return;
|
|
}
|
|
|
|
container.innerHTML =
|
|
`<div class='card'>
|
|
<div class='side-by-side'>
|
|
<h3>Create an Id</h3>
|
|
<div><a href='#'>Processes</a></div>
|
|
</div>
|
|
<form id='form4nk' action='#'>
|
|
<label for='password'>Password :</label>
|
|
<input type='password' id='password' /><hr/>
|
|
<input type='hidden' id='currentpage' value='creatid' />
|
|
<select id='selectProcess' class='custom-select'></select><hr/>
|
|
<div class='side-by-side'>
|
|
<button type='submit' id='submitButton' class='bg-primary'>Create</button>
|
|
<div>
|
|
<a href='#' id='displayrecover'>Recover</a>
|
|
</div>
|
|
</div>
|
|
</form><br/>
|
|
<div id='passwordalert' class='passwordalert'></div>
|
|
</div>`;
|
|
}
|
|
|
|
public async updateIdInjectHtml() {
|
|
const container = document.getElementById('containerId');
|
|
|
|
if (!container) {
|
|
console.error("No html container");
|
|
return;
|
|
}
|
|
|
|
container.innerHTML =
|
|
`<body>
|
|
<div class='container'>
|
|
<div>
|
|
<h3>Update an Id</h3>
|
|
</div>
|
|
<hr />
|
|
<form id='form4nk' action='#'>
|
|
<label for='firstName'>First Name:</label>
|
|
<input type='text' id='firstName' name='firstName' required />
|
|
|
|
<label for='lastName'>Last Name:</label>
|
|
<input type='text' id='lastName' name='lastName' required />
|
|
|
|
<label for='Birthday'>Birthday:</label>
|
|
<input type='date' id='Birthday' name='birthday' />
|
|
|
|
<label for='file'>File:</label>
|
|
<input type='file' id='fileInput' name='file' />
|
|
|
|
<label>Third parties:</label>
|
|
<div id='sp-address-block'>
|
|
<div class='side-by-side'>
|
|
<input
|
|
type='text'
|
|
name='sp-address'
|
|
id='sp-address'
|
|
placeholder='sp address'
|
|
form='no-form'
|
|
/>
|
|
<button
|
|
type='button'
|
|
class='circle-btn bg-secondary'
|
|
id='add-sp-address-btn'
|
|
>
|
|
+
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class='div-text-area'>
|
|
<textarea
|
|
name='bio'
|
|
id=''
|
|
cols='30'
|
|
rows='10'
|
|
placeholder='Bio'
|
|
></textarea>
|
|
</div>
|
|
<button type='submit' class='bg-primary'>Update</button>
|
|
</form>
|
|
</div>
|
|
</body>`;
|
|
}
|
|
|
|
public async injectHtml(processName: string) {
|
|
const container = document.getElementById('containerId');
|
|
|
|
if (!container) {
|
|
console.error("No html container");
|
|
return;
|
|
}
|
|
|
|
const services = await Services.getInstance();
|
|
|
|
// do we have all processes in db?
|
|
const knownProcesses = await services.getAllProcess();
|
|
const processesFromNetwork: Process[] = services.sdkClient.get_processes();
|
|
|
|
const processToAdd = processesFromNetwork.filter(processFromNetwork => !knownProcesses.some(knownProcess => knownProcess.id === processFromNetwork.id));
|
|
|
|
processToAdd.forEach(async p => {
|
|
await services.addProcess(p);
|
|
})
|
|
|
|
// get the process we need
|
|
const process = await services.getProcessByName(processName);
|
|
if (process) {
|
|
container.innerHTML = process.html;
|
|
} else {
|
|
console.error("No process ", processName);
|
|
}
|
|
}
|
|
|
|
// public async getCurrentProcess(): Promise<string> {
|
|
// let currentProcess = "";
|
|
// try {
|
|
// const indexedDB = await IndexedDB.getInstance();
|
|
// const db = indexedDB.getDb();
|
|
// currentProcess = await indexedDB.getObject<string>(db, indexedDB.getStoreList().AnkSession, Services.CURRENT_PROCESS);
|
|
// } catch (error) {
|
|
// console.error("Failed to retrieve currentprocess object :", error);
|
|
// }
|
|
// return currentProcess;
|
|
// }
|
|
|
|
public isPasswordValid(password: string) {
|
|
var alertElem = document.getElementById("passwordalert");
|
|
var success = true;
|
|
var strength = 0;
|
|
if (password.match(/[a-z]+/)) {
|
|
var strength = 0;
|
|
strength += 1;
|
|
}
|
|
if (password.match(/[A-Z]+/)) {
|
|
strength += 1;
|
|
}
|
|
if (password.match(/[0-9]+/)) {
|
|
strength += 1;
|
|
}
|
|
if (password.match(/[$@#&!]+/)) {
|
|
strength += 1;
|
|
}
|
|
if (alertElem !== null) {
|
|
// TODO Passer à 18
|
|
if (password.length < 4) {
|
|
alertElem.innerHTML = "Password size is < 4";
|
|
success = false;
|
|
} else {
|
|
if (password.length > 30) {
|
|
alertElem.innerHTML = "Password size is > 30";
|
|
success = false;
|
|
} else {
|
|
if (strength < 4) {
|
|
alertElem.innerHTML = "Password need [a-z] [A-Z] [0-9]+ [$@#&!]+";
|
|
success = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
private async pickWebsocketConnectionRandom(): Promise<WebSocketClient | null> {
|
|
const services = await Services.getInstance();
|
|
const websockets = services.websocketConnection;
|
|
if (websockets.length === 0) {
|
|
console.error("No websocket connection available at the moment");
|
|
return null;
|
|
} else {
|
|
const random = Math.floor(Math.random() * websockets.length);
|
|
return websockets[random];
|
|
}
|
|
}
|
|
|
|
public async obtainTokenWithFaucet(spaddress: string): Promise<string | null> {
|
|
const services = await Services.getInstance();
|
|
const connection = await services.pickWebsocketConnectionRandom();
|
|
if (!connection) {
|
|
return null;
|
|
}
|
|
try {
|
|
const flag: AnkFlag = "Faucet";
|
|
const faucetMsg: FaucetMessage = {
|
|
'sp_address': spaddress
|
|
}
|
|
connection.sendMessage(flag, JSON.stringify(faucetMsg));
|
|
} catch (error) {
|
|
console.error("Failed to obtain tokens with relay ", connection.getUrl());
|
|
return null;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export default Services;
|