diff --git a/crates/sp_client/src/injecteurhtml.rs b/crates/sp_client/src/injecteurhtml.rs new file mode 100644 index 0000000..72e0c73 --- /dev/null +++ b/crates/sp_client/src/injecteurhtml.rs @@ -0,0 +1,99 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn inject_html_create_id() -> String { + String::from(" +
+
+

Create an Id

+
Processes
+
+
+ +
+ +
+
+ +
+ Recover +
+
+

+
+
+ ") +} + +#[wasm_bindgen] +pub fn inject_html_recover() -> String { + String::from(" +
+
+

Recover my Id

+
Processes
+
+
+ + + +
+
+ +
+ Create an Id +
+

+ Revoke +

+
+
+ ") +} + +#[wasm_bindgen] +pub fn inject_html_revokeimage() -> String { + String::from(" +
+
+ + +
+
+ + +
+
+ ") +} + +#[wasm_bindgen] +pub fn inject_html_revoke() -> String { + String::from(" +
+
+

Revoke an Id

+
+ Recover +
+
+
+ + +
+
+ + +
+
+ +
+
+ ") +} diff --git a/crates/sp_client/src/lib.rs b/crates/sp_client/src/lib.rs index e5fdf85..0ce9757 100644 --- a/crates/sp_client/src/lib.rs +++ b/crates/sp_client/src/lib.rs @@ -1 +1,3 @@ pub mod api; +mod injecteurhtml; +mod process; diff --git a/crates/sp_client/src/process.rs b/crates/sp_client/src/process.rs new file mode 100644 index 0000000..e4daebb --- /dev/null +++ b/crates/sp_client/src/process.rs @@ -0,0 +1,10 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub fn get_process() -> Vec { + let mut data_process: Vec = Vec::new(); + data_process.push(String::from("process1")); + data_process.push(String::from("process2")); + data_process.push(String::from("process3")); + data_process +} \ No newline at end of file diff --git a/package.json b/package.json index 0e1084a..6dabb0b 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "author": "", "license": "ISC", "devDependencies": { + "copy-webpack-plugin": "^12.0.2", "html-webpack-plugin": "^5.6.0", "ts-loader": "^9.5.1", "typescript": "^5.3.3", diff --git a/src/assets/4nk_image.png b/src/assets/4nk_image.png new file mode 100644 index 0000000..d58693f Binary files /dev/null and b/src/assets/4nk_image.png differ diff --git a/src/assets/revoke.jpeg b/src/assets/revoke.jpeg new file mode 100644 index 0000000..232dcb9 Binary files /dev/null and b/src/assets/revoke.jpeg differ diff --git a/src/database.ts b/src/database.ts index 3d078c5..7a8a4fe 100644 --- a/src/database.ts +++ b/src/database.ts @@ -9,15 +9,15 @@ class Database { options: {} }, AnkUser: { - name: "4nkUser", + name: "user", options: {} }, AnkSession: { - name: "4nkSession", + name: "session", options: {} }, AnkProcess: { - name: "4nkProcess", + name: "process", options: {} } } diff --git a/src/index.html b/src/index.html index 00b59c8..a338e3f 100644 --- a/src/index.html +++ b/src/index.html @@ -2,9 +2,16 @@ + + + - 4NK Client + + 4NK Application +
+ +
- + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 257166f..59e125d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,10 +4,18 @@ import IndexedDB from './database' document.addEventListener('DOMContentLoaded', async () => { try { const services = await Services.getInstance(); - const indexedDB = await IndexedDB.getInstance(); + if ((await services.isNewUser())) { + services.displayCreateId(); + } + else { + services.displayRecover() + } + + const indexedDB = await IndexedDB.getInstance(); const db = indexedDB.getDb(); await indexedDB.writeObject(db, indexedDB.getStoreList().SpClient, services.new_sp_client(), "default"); + } catch (error) { console.error(error); } diff --git a/src/services.ts b/src/services.ts index ae9eafd..da08ff3 100644 --- a/src/services.ts +++ b/src/services.ts @@ -1,8 +1,11 @@ import IndexedDB from './database' +import Processstore from './store/processstore'; +import Userstore from './store/userstore'; class Services { private static instance: Services; private sdkClient: any; + private static CURRENT_PROCESS = "currentprocess"; // Private constructor to prevent direct instantiation from outside private constructor() {} @@ -44,6 +47,368 @@ class Services { } } + + public async isNewUser(): Promise { + let isNew = false; + try { + let listUserProcess = await Services.instance.getAllUserProces(); + if (listUserProcess.length == 0) { + isNew = true; + } + } catch (error) { + console.error("Failed to retrieve isNewUser :", error); + } + return isNew; + } + + public async displayCreateId(): Promise { + Services.instance.injectHtml(Services.instance.get_html_create_id()); + Services.instance.attachSubmitListener("form4nk", Services.instance.createId); + Services.instance.attachClickListener("displayrecover", Services.instance.displayRecover); + Services.instance.displayProcess(await Services.instance.getAllProcesAvailable()); + } + + public get_html_create_id(): string { + return this.sdkClient.inject_html_create_id(); + } + + public async createId(event: Event): Promise { + 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; + const process = processElement.value; + console.log("JS password: " + password + " process: " + process); + // To comment if test + if (!Services.instance.isPasswordValid(password)) return; + + // TODO get secretpart1 from User object + // const user = new this.sdkClient.User(password); + let secretpart1 = "LKHGKJJJ3H"; + + try { + let userstore = new Userstore; + userstore.secretpart1 = secretpart1; + userstore.process = process; + const indexedDb = await IndexedDB.getInstance(); + const db = indexedDb.getDb(); + + await indexedDb.writeObject(db, indexedDb.getStoreList().AnkUser, userstore, process); + console.log("JS Userstore added"); + + await indexedDb.writeObject(db, indexedDb.getStoreList().AnkSession, process, Services.CURRENT_PROCESS); + console.log("JS Sessionstore added currentprocess"); + } catch (error) { + console.error("Failed to write userstore object :", error); + } + + await Services.instance.displayRevokeImage(); + } + + public async displayRecover(): Promise { + Services.instance.injectHtml(Services.instance.get_html_recover()); + Services.instance.attachSubmitListener("form4nk", Services.instance.recover); + Services.instance.attachClickListener("displaycreateid", Services.instance.displayCreateId); + Services.instance.attachClickListener("displayrevoke", Services.instance.displayRevoke); + Services.instance.attachClickListener("submitButtonRevoke", Services.instance.revoke); + Services.instance.displayProcess(await Services.instance.getAllUserProces()); + } + + public get_html_recover(): string { + return this.sdkClient.inject_html_recover(); + } + + 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(): Promise { + const html = Services.instance.get_html_revokeimage(); + Services.instance.injectHtml(html); + Services.instance.attachSubmitListener("form4nk", Services.instance.revokeimage); + } + + public get_html_revokeimage(): string { + return this.sdkClient.inject_html_revokeimage(); + } + + public async revokeimage(event: Event): Promise { + event.preventDefault(); + console.log("JS revokeimage submit "); + // TODO + alert("Revokeimage submit to do ..., next page Update an id ..."); + + await Services.instance.displayUpdateAnId(); + } + + public async displayRevoke(): Promise { + const html = Services.instance.get_html_revoke(); + Services.instance.injectHtml(html); + Services.instance.attachClickListener("displayrecover", Services.instance.displayRecover); + Services.instance.attachSubmitListener("form4nk", Services.instance.revoke); + } + + public get_html_revoke(): string { + return this.sdkClient.inject_html_revoke(); + } + + public async revoke(event: Event): Promise { + event.preventDefault(); + console.log("JS revoke click "); + // TODO + alert("revoke click to do ..."); + } + + public async displayUpdateAnId() { + let currentProcess = await this.getCurrentProcess(); + + let body = ""; + let style = ""; + let script = ""; + let inputName = ""; + try { + const indexedDB = await IndexedDB.getInstance(); + const db = indexedDB.getDb(); + try { + let processObject = await indexedDB.getObject(db, indexedDB.getStoreList().AnkProcess, currentProcess); + body = processObject.html; + style = processObject.style; + script = processObject.script; + inputName = processObject.inputName; + } catch (error) { + console.log("JS Processstore not exist "); + } + } catch (error) { + console.error("Failed to retrieve userstore object :", error); + } + + Services.instance.injectUpdateAnIdHtml(body, style, script, inputName); + Services.instance.attachSubmitListener("form4nk", Services.instance.updateAnId); + } + + public injectUpdateAnIdHtml(bodyToInject: string, styleToInject: string, scriptToInject: string, inputName: string) { + console.log("JS html : "+bodyToInject); + const body = document.getElementsByTagName('body')[0]; + + if (!body) { + console.error("No body tag"); + return; + } + body.innerHTML = styleToInject + bodyToInject; + + const script = document.createElement("script"); + script.innerHTML = scriptToInject; + document.body.appendChild(script); + script.onload = () => { + console.log('Script loaded successfuly'); + }; + script.onerror = () => { + console.log('Error loading script'); + }; + } + + public async updateAnId(event: Event): Promise { + 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); + } + + public displayProcess(processList: string[]): void { + console.log("JS processList : "+processList); + const selectProcess = document.getElementById("selectProcess"); + if (selectProcess) { + processList.forEach((process) => { + let child = new Option(process, process); + if (!selectProcess.contains(child)) { + selectProcess.appendChild(child); + } + }) + } + } + + public async getAllUserProces(): Promise { + let userProcessList: string[] = []; + try { + const indexedDB = await IndexedDB.getInstance(); + const db = indexedDB.getDb(); + let userListObject = await indexedDB.getAll(db, indexedDB.getStoreList().AnkUser); + userListObject.forEach(async (userObject) => { + const processName = userObject.process; + userProcessList.push(processName); + console.log("JS Userstore found"); + }) + } catch (error) { + console.log("JS Userstore not found"); + } + return userProcessList; + } + + public async getAllProcesAvailable(): Promise { + let userProcessList = await Services.instance.getAllUserProces(); + let processList = await Services.instance.getAllProces(); + let availableProcessList = processList.filter(x => !userProcessList.includes(x)); + return availableProcessList; + } + + public async getAllProces(): Promise { + // if indexedDB is empty, get list from wasm + let processList: string[] = []; + try { + const indexedDB = await IndexedDB.getInstance(); + const db = indexedDB.getDb(); + let processListObject = await indexedDB.getAll(db, indexedDB.getStoreList().AnkProcess); + processListObject.forEach(async (processObject) => { + const processName = processObject.process; + processList.push(processName); + console.log("JS Processstore found"); + }) + } catch (error) { + console.log("JS Processstore not found"); + } + if (processList.length == 0) { + processList = await this.addProcessStore(); + } + return processList; + } + + public async addProcessStore(): Promise { + const processList = this.sdkClient.get_process() + processList.forEach(async (process: string) => { + // TODO process mock + let processstore = new Processstore; + processstore.process = process; + const indexedDB = await IndexedDB.getInstance(); + const db = indexedDB.getDb(); + await indexedDB.writeObject(db, indexedDB.getStoreList().AnkProcess, processstore, process); + console.log("JS Processstore mock added"); + }) + return processList; + } + + private async getSecretPart1(process: string): Promise { + let secretpart1 = ""; + try { + const indexedDB = await IndexedDB.getInstance(); + const db = indexedDB.getDb(); + try { + let userObject = await indexedDB.getObject(db, indexedDB.getStoreList().AnkUser, process); + secretpart1 = userObject.secretpart1; + console.log("JS Userstore exist secretpart1 : "+secretpart1); + } catch (error) { + console.log("JS Userstore not exist "); + } + } catch (error) { + console.error("Failed to retrieve userstore object :", error); + } + console.log("JS secretpart1 : "+secretpart1); + + return secretpart1; + } + + 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 injectHtml(html: string) { + console.log("JS html : "+html); + const container = document.getElementById('containerId'); + + if (!container) { + console.error("No html container"); + return; + } + container.innerHTML = html; + } + + public async getCurrentProcess(): Promise { + let currentProcess = ""; + try { + const indexedDB = await IndexedDB.getInstance(); + const db = indexedDB.getDb(); + currentProcess = await indexedDB.getObject(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; + } } export default Services; diff --git a/src/store/processstore.ts b/src/store/processstore.ts new file mode 100644 index 0000000..39cabee --- /dev/null +++ b/src/store/processstore.ts @@ -0,0 +1,237 @@ +class Processstore { + process: string; + html: string; + style: string; + script: string; + inputName: string; + createDate: Date; + + constructor() { + this.process = ""; + this.html = getMockHtml(); + this.style = getMockStyle(); + this.script = getMockScript(); + this.script = getMockScript(); + this.inputName = getMockinputName(); + this.createDate = new Date; + } +} + +export default Processstore; + +function getMockHtml(): string { + let html: string = ` + +
+
+

Update an Id

+
+
+
+ + + + + + + + + + + + + +
+
+ + +
+
+
+ +
+ +
+
+ + `; + return html; +} + +function getMockStyle(): string { + let style: string = ` + `; + return style; +} + +function getMockScript(): string { + let script: string = ` + var addSpAddressBtn = document.getElementById('add-sp-address-btn'); + var removeSpAddressBtn = document.querySelectorAll('.minus-sp-address-btn'); + + addSpAddressBtn.addEventListener('click', function (event) { + addDynamicField(this); + }); + + function addDynamicField(element) { + var addSpAddressBlock = document.getElementById('sp-address-block'); + var spAddress = addSpAddressBlock.querySelector('#sp-address').value; + addSpAddressBlock.querySelector('#sp-address').value = ''; + spAddress = spAddress.trim(); + if (spAddress != '') { + var sideBySideDiv = document.createElement('div'); + sideBySideDiv.className = 'side-by-side'; + + var inputElement = document.createElement('input'); + inputElement.type = 'text'; + inputElement.name = 'spAddresses[]'; + inputElement.setAttribute('form', 'no-form'); + inputElement.value = spAddress; + inputElement.disabled = true; + + var buttonElement = document.createElement('button'); + buttonElement.type = 'button'; + buttonElement.className = + 'circle-btn bg-secondary minus-sp-address-btn'; + buttonElement.innerHTML = '-'; + + buttonElement.addEventListener('click', function (event) { + removeDynamicField(this.parentElement); + }); + + sideBySideDiv.appendChild(inputElement); + sideBySideDiv.appendChild(buttonElement); + + addSpAddressBlock.appendChild(sideBySideDiv); + } + function removeDynamicField(element) { + element.remove(); + } + } + `; + return script; +} + +function getMockinputName(): string { + return "firstName"; +} \ No newline at end of file diff --git a/src/store/userstore.ts b/src/store/userstore.ts new file mode 100644 index 0000000..d450008 --- /dev/null +++ b/src/store/userstore.ts @@ -0,0 +1,15 @@ +class Userstore { + process: string; + secretpart1: string; + password: string; + createDate: Date; + + constructor() { + this.process = ""; + this.secretpart1 = ""; + this.password = ""; + this.createDate = new Date; + } +} + +export default Userstore; diff --git a/src/style/4nk.css b/src/style/4nk.css new file mode 100644 index 0000000..39377bb --- /dev/null +++ b/src/style/4nk.css @@ -0,0 +1,162 @@ +body { + margin: 0; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + background-color: #f4f4f4; + font-family: 'Arial', sans-serif; +} +.container { + text-align: center; +} +.card { + max-width: 400px; + width: 100%; + padding: 20px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + background-color: #ffffff; + border-radius: 8px; + text-align: left; + overflow: hidden; +} +form { + display: flex; + flex-direction: column; + /* flex-wrap: wrap; */ +} +label { + font-weight: bold; + margin-bottom: 8px; +} +hr { + border: 0; + height: 1px; + background-color: #ddd; + margin: 10px 0; +} +input, select { + width: 100%; + padding: 10px; + margin: 8px 0; + box-sizing: border-box; +} +select { + padding: 10px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; +} +button { + display: inline-block; + background-color: #4caf50; + color: #fff; + border: none; + padding: 12px 17px; + border-radius: 4px; + cursor: pointer; +} +button:hover { + background-color: #45a049; +} +.side-by-side { + display: flex; + align-items: center; + justify-content: space-between; +} +.side-by-side>* { + display: inline-block; +} +button.recover { + display: inline-block; + text-align: center; + text-decoration: none; + display: inline-block; + background-color: #4caf50; + color: #fff; + border: none; + padding: 12px 17px; + border-radius: 4px; + cursor: pointer; +} +button.recover:hover { + background-color: #45a049; +} +a.btn { + display: inline-block; + text-align: center; + text-decoration: none; + display: inline-block; + background-color: #4caf50; + color: #fff; + border: none; + padding: 12px 17px; + border-radius: 4px; + cursor: pointer; + } + +a.btn:hover { + background-color: #45a049; +} + +a { + text-decoration: none; + color: #78a6de; +} +.bg-secondary { + background-color: #2b81ed; +} +.bg-primary { + background-color: #1A61ED; +} +.bg-primary:hover { + background-color: #457be8; +} +.card2 { + display: flex; + flex-direction: column; + max-width: 400px; + width: 100%; + padding: 20px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + background-color: #ffffff; + border-radius: 8px; + text-align: center; + align-items: center; + overflow: hidden; +} +.card2 button { + max-width: 50px; + width: 100%; + background: none; + border: none; + cursor: pointer; +} +.card2 svg { + width: 100%; + height: auto; + fill: #333; +} +.image-label { + display: block; + color: #fff; + padding: 5px; + margin-top: 10px; +} +.image-container { + width: 400px; + height: 300px; + overflow: hidden; +} +.image-container img { + text-align: center; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center center; +} +.passwordalert { + color: #FF0000; +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index be13690..e40143f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { mode: 'development', @@ -32,6 +33,12 @@ module.exports = { new HtmlWebpackPlugin({ template: 'src/index.html' }), + new CopyWebpackPlugin({ + patterns: [ + { from: 'src/assets', to: './assets' }, + { from: 'src/style', to: './style' } + ], + }), ], devServer: { static: './dist',