diff --git a/pkg/README.md b/pkg/README.md new file mode 100644 index 0000000..e1b8eb9 --- /dev/null +++ b/pkg/README.md @@ -0,0 +1,104 @@ +# @nkode/client-wasm + +nKode client compiled to WebAssembly with TypeScript bindings. + +Provides OPAQUE (aPAKE) authentication flows that run entirely in the browser — no server-side secret key handling needed. + +## Installation + +```bash +npm install @nkode/client-wasm +``` + +## Usage + +```typescript +import init, { NKodeClient } from '@nkode/client-wasm'; + +// Initialize the WASM module +await init(); + +const client = new NKodeClient('https://api.nkode.example.com'); + +// Generate a new secret key (16 random bytes, hex-encoded) +const secretKey = NKodeClient.generateSecretKey(); +// Store this securely — it's the user's authentication key! + +// Register a new user +await client.registerKey('user@example.com', secretKey); + +// Login +const session = await client.loginKey('user@example.com', secretKey); +console.log(session.sessionId); // UUID +console.log(session.userId); // UUID +console.log(session.createdAt); // ISO 8601 +console.log(session.expiresAt); // ISO 8601 + +// Code-based flows (for icon passcode) +await client.registerCode('user@example.com', passcodeBytes); +const codeSession = await client.loginCode('user@example.com', passcodeBytes); +``` + +## API + +### `NKodeClient` + +#### `new NKodeClient(baseUrl: string)` +Create a client connected to the nKode server. + +#### `static generateSecretKey(): string` +Generate a random 16-byte secret key as a hex string (32 chars). + +#### `registerKey(email: string, secretKeyHex: string): Promise` +Register a new user with OPAQUE key-based registration. + +#### `loginKey(email: string, secretKeyHex: string): Promise` +Login with OPAQUE key-based authentication. + +#### `registerCode(email: string, passcodeBytes: Uint8Array): Promise` +Register with OPAQUE code-based flow. + +#### `loginCode(email: string, passcodeBytes: Uint8Array): Promise` +Login with OPAQUE code-based flow. + +### `NKodeSession` + +```typescript +interface NKodeSession { + sessionId: string; // UUID + userId: string; // UUID + createdAt: string; // ISO 8601 timestamp + expiresAt: string; // ISO 8601 timestamp +} +``` + +## Building from Source + +```bash +# Prerequisites +rustup target add wasm32-unknown-unknown +cargo install wasm-pack + +# Build +./build.sh # For bundlers (webpack/vite) +./build.sh web # For ES modules +./build.sh nodejs # For Node.js +``` + +## Architecture + +This crate is a standalone WASM bridge that: +- Uses `opaque-ke` for client-side OPAQUE protocol +- Uses the browser's Fetch API for HTTP transport +- Shares `common` types with the Rust server +- Runs entirely in the browser — no server round-trips for crypto + +The OPAQUE flows (registration + login) are reimplemented for the WASM +single-threaded environment (no `Send`/`Sync` bounds, no tokio). + +## Security + +- Secret keys never leave the browser +- OPAQUE ensures the server never sees the user's password +- Session keys are derived from the OPAQUE protocol +- All HTTP communication should use HTTPS diff --git a/pkg/nkode_client_wasm.d.ts b/pkg/nkode_client_wasm.d.ts new file mode 100644 index 0000000..8a708ef --- /dev/null +++ b/pkg/nkode_client_wasm.d.ts @@ -0,0 +1,148 @@ +/* tslint:disable */ +/* eslint-disable */ + +export class NKodeClient { + free(): void; + [Symbol.dispose](): void; + /** + * Login with OPAQUE code-based authentication. + * Stores the session key internally for subsequent authenticated requests. + * + * @param email - User's email address + * @param passcodeBytes - Passcode as Uint8Array + * @returns Promise + */ + loginCode(email: string, passcode_bytes: Uint8Array): Promise; + /** + * Get the current session's user ID, or null if not logged in. + */ + getUserId(): string | undefined; + /** + * Check if the client has an active session (from a prior login call). + */ + hasSession(): boolean; + /** + * Register a new user via OPAQUE key-based registration. + * + * @param email - User's email address + * @param secretKeyHex - 16-byte secret key as hex string (32 chars) + * @returns Promise - Resolves on success, rejects with error string + */ + registerKey(email: string, secret_key_hex: string): Promise; + /** + * Clear the stored session (local logout). + */ + clearSession(): void; + /** + * Fetch new icons from the server (requires active key session). + * + * @param count - Number of icons to fetch + * @returns Promise - JSON: { icons: [{ file_name, file_type, img_data }] } + */ + getNewIcons(count: number): Promise; + /** + * Register via OPAQUE code-based registration. + * + * @param email - User's email address + * @param passcodeBytes - Passcode as Uint8Array + * @returns Promise + */ + registerCode(email: string, passcode_bytes: Uint8Array): Promise; + /** + * Get login data for a user (requires active session). + * + * @param userId - Target user ID (must match session user) + * @returns Promise - JSON with keypad config + */ + getLoginData(user_id: string): Promise; + /** + * Create new login data on the server (requires active key session). + * + * @param loginDataJson - JSON string of LoginDataPayload + * @returns Promise + */ + postLoginData(login_data_json: string): Promise; + /** + * Update login data on the server (requires active key session). + * + * @param loginDataJson - JSON string of LoginDataPayload + * @returns Promise + */ + updateLoginData(login_data_json: string): Promise; + /** + * Decipher key selections into OPAQUE passcode bytes for code login. + * Call this after the user taps their nKode sequence on the keypad. + * + * @param userId - The user's ID + * @param secretKeyHex - The user's secret key as hex + * @param loginDataBytes - Raw JSON bytes of login data (from server) + * @param keySelections - Array of key indices the user tapped + * @returns Uint8Array - The passcode bytes to pass to loginCode() + */ + decipherSelection(secret_key_hex: string, login_data_json: string, key_selections: Uint32Array): Uint8Array; + /** + * Prepare for code login: fetch login data, reconstruct keypad, and fetch icons. + * Returns the keypad configuration with icons for UI display. + * + * Also stores the raw login data JSON internally for decipherSelection(). + * + * @param userId - The user's ID + * @param secretKeyHex - The user's secret key as hex string + * @returns Promise - { keypadIndices, propertiesPerKey, numberOfKeys, mask, icons, loginDataJson } + */ + prepareCodeLogin(user_id: string, secret_key_hex: string): Promise; + /** + * Generate a new random 16-byte secret key, returned as a hex string (32 chars). + */ + static generateSecretKey(): string; + /** + * Prepare icons for code registration (requires active key session). + * Fetches icons from the server and randomizes their names via ChaCha20. + * Stores intermediate state internally for completeCodeRegistration(). + * + * @returns Promise - JSON: { icons: [{ file_name, file_type, img_data }] } + */ + prepareCodeRegistration(): Promise; + /** + * Complete code registration after icon selection. + * Enciphers the selection, registers OPAQUE code auth, and stores login data. + * + * @param selectedIndices - Array of icon indices the user selected (global indices, not key indices) + * @returns Promise + */ + completeCodeRegistration(selected_indices: Uint32Array): Promise; + /** + * Complete code registration with email (full version). + * Enciphers the selection, registers OPAQUE code auth, and stores login data on server. + * + * @param email - User's email address + * @param selectedIndices - Uint32Array of icon indices the user selected + * @returns Promise + */ + completeCodeRegistrationWithEmail(email: string, selected_indices: Uint32Array): Promise; + /** + * Create a new client pointed at the given nKode server base URL. + */ + constructor(base_url: string); + /** + * Login with OPAQUE key-based authentication. + * Stores the session key internally for subsequent authenticated requests. + * + * @param email - User's email address + * @param secretKeyHex - 16-byte secret key as hex string + * @returns Promise - Session info object + */ + loginKey(email: string, secret_key_hex: string): Promise; + /** + * Set (store) icons on the server (requires active key session). + * + * @param iconsJson - JSON string of { icons: [{ file_name, file_type, img_data }] } + * @returns Promise + */ + setIcons(icons_json: string): Promise; +} + +/** + * Initialize panic hook for better error messages in browser console. + */ +export function init(): void; diff --git a/pkg/nkode_client_wasm.js b/pkg/nkode_client_wasm.js new file mode 100644 index 0000000..1ea2456 --- /dev/null +++ b/pkg/nkode_client_wasm.js @@ -0,0 +1,5 @@ +import * as wasm from "./nkode_client_wasm_bg.wasm"; +export * from "./nkode_client_wasm_bg.js"; +import { __wbg_set_wasm } from "./nkode_client_wasm_bg.js"; +__wbg_set_wasm(wasm); +wasm.__wbindgen_start(); diff --git a/pkg/nkode_client_wasm_bg.js b/pkg/nkode_client_wasm_bg.js new file mode 100644 index 0000000..3c4a898 --- /dev/null +++ b/pkg/nkode_client_wasm_bg.js @@ -0,0 +1,984 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + +function addToExternrefTable0(obj) { + const idx = wasm.__externref_table_alloc(); + wasm.__wbindgen_externrefs.set(idx, obj); + return idx; +} + +const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(state => state.dtor(state.a, state.b)); + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +function getArrayU8FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); +} + +let cachedDataViewMemory0 = null; +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return decodeText(ptr, len); +} + +let cachedUint32ArrayMemory0 = null; +function getUint32ArrayMemory0() { + if (cachedUint32ArrayMemory0 === null || cachedUint32ArrayMemory0.byteLength === 0) { + cachedUint32ArrayMemory0 = new Uint32Array(wasm.memory.buffer); + } + return cachedUint32ArrayMemory0; +} + +let cachedUint8ArrayMemory0 = null; +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + const idx = addToExternrefTable0(e); + wasm.__wbindgen_exn_store(idx); + } +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +function makeMutClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + state.a = a; + real._wbg_cb_unref(); + } + }; + real._wbg_cb_unref = () => { + if (--state.cnt === 0) { + state.dtor(state.a, state.b); + state.a = 0; + CLOSURE_DTORS.unregister(state); + } + }; + CLOSURE_DTORS.register(real, state, state); + return real; +} + +function passArray32ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 4, 4) >>> 0; + getUint32ArrayMemory0().set(arg, ptr / 4); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1, 1) >>> 0; + getUint8ArrayMemory0().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = cachedTextEncoder.encodeInto(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function takeFromExternrefTable0(idx) { + const value = wasm.__wbindgen_externrefs.get(idx); + wasm.__externref_table_dealloc(idx); + return value; +} + +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); +cachedTextDecoder.decode(); +const MAX_SAFARI_DECODE_BYTES = 2146435072; +let numBytesDecoded = 0; +function decodeText(ptr, len) { + numBytesDecoded += len; + if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) { + cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + cachedTextDecoder.decode(); + numBytesDecoded = len; + } + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +const cachedTextEncoder = new TextEncoder(); + +if (!('encodeInto' in cachedTextEncoder)) { + cachedTextEncoder.encodeInto = function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; + } +} + +let WASM_VECTOR_LEN = 0; + +function wasm_bindgen__convert__closures_____invoke__h8f97ce5df83102bb(arg0, arg1, arg2) { + wasm.wasm_bindgen__convert__closures_____invoke__h8f97ce5df83102bb(arg0, arg1, arg2); +} + +function wasm_bindgen__convert__closures_____invoke__h01837f788748d23a(arg0, arg1, arg2, arg3) { + wasm.wasm_bindgen__convert__closures_____invoke__h01837f788748d23a(arg0, arg1, arg2, arg3); +} + +const __wbindgen_enum_RequestMode = ["same-origin", "no-cors", "cors", "navigate"]; + +const NKodeClientFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_nkodeclient_free(ptr >>> 0, 1)); + +/** + * nKode client for browser usage. + * + * Exposes OPAQUE registration and login flows via WebAssembly, + * plus authenticated endpoint access using stored session keys. + * + * ## TypeScript Usage + * ```ts + * import init, { NKodeClient } from 'nkode-client-wasm'; + * + * await init(); + * const client = new NKodeClient('https://api.nkode.example.com'); + * + * const key = NKodeClient.generateSecretKey(); + * await client.registerKey('user@example.com', key); + * const session = await client.loginKey('user@example.com', key); + * + * // Authenticated endpoints (uses stored session key from login) + * const icons = await client.getNewIcons(9); + * ``` + */ +export class NKodeClient { + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + NKodeClientFinalization.unregister(this); + return ptr; + } + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_nkodeclient_free(ptr, 0); + } + /** + * Login with OPAQUE code-based authentication. + * Stores the session key internally for subsequent authenticated requests. + * + * @param email - User's email address + * @param passcodeBytes - Passcode as Uint8Array + * @returns Promise + * @param {string} email + * @param {Uint8Array} passcode_bytes + * @returns {Promise} + */ + loginCode(email, passcode_bytes) { + const ptr0 = passStringToWasm0(email, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passArray8ToWasm0(passcode_bytes, wasm.__wbindgen_malloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_loginCode(this.__wbg_ptr, ptr0, len0, ptr1, len1); + return ret; + } + /** + * Get the current session's user ID, or null if not logged in. + * @returns {string | undefined} + */ + getUserId() { + const ret = wasm.nkodeclient_getUserId(this.__wbg_ptr); + let v1; + if (ret[0] !== 0) { + v1 = getStringFromWasm0(ret[0], ret[1]).slice(); + wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); + } + return v1; + } + /** + * Check if the client has an active session (from a prior login call). + * @returns {boolean} + */ + hasSession() { + const ret = wasm.nkodeclient_hasSession(this.__wbg_ptr); + return ret !== 0; + } + /** + * Register a new user via OPAQUE key-based registration. + * + * @param email - User's email address + * @param secretKeyHex - 16-byte secret key as hex string (32 chars) + * @returns Promise - Resolves on success, rejects with error string + * @param {string} email + * @param {string} secret_key_hex + * @returns {Promise} + */ + registerKey(email, secret_key_hex) { + const ptr0 = passStringToWasm0(email, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(secret_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_registerKey(this.__wbg_ptr, ptr0, len0, ptr1, len1); + return ret; + } + /** + * Clear the stored session (local logout). + */ + clearSession() { + wasm.nkodeclient_clearSession(this.__wbg_ptr); + } + /** + * Fetch new icons from the server (requires active key session). + * + * @param count - Number of icons to fetch + * @returns Promise - JSON: { icons: [{ file_name, file_type, img_data }] } + * @param {number} count + * @returns {Promise} + */ + getNewIcons(count) { + const ret = wasm.nkodeclient_getNewIcons(this.__wbg_ptr, count); + return ret; + } + /** + * Register via OPAQUE code-based registration. + * + * @param email - User's email address + * @param passcodeBytes - Passcode as Uint8Array + * @returns Promise + * @param {string} email + * @param {Uint8Array} passcode_bytes + * @returns {Promise} + */ + registerCode(email, passcode_bytes) { + const ptr0 = passStringToWasm0(email, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passArray8ToWasm0(passcode_bytes, wasm.__wbindgen_malloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_registerCode(this.__wbg_ptr, ptr0, len0, ptr1, len1); + return ret; + } + /** + * Get login data for a user (requires active session). + * + * @param userId - Target user ID (must match session user) + * @returns Promise - JSON with keypad config + * @param {string} user_id + * @returns {Promise} + */ + getLoginData(user_id) { + const ptr0 = passStringToWasm0(user_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_getLoginData(this.__wbg_ptr, ptr0, len0); + return ret; + } + /** + * Create new login data on the server (requires active key session). + * + * @param loginDataJson - JSON string of LoginDataPayload + * @returns Promise + * @param {string} login_data_json + * @returns {Promise} + */ + postLoginData(login_data_json) { + const ptr0 = passStringToWasm0(login_data_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_postLoginData(this.__wbg_ptr, ptr0, len0); + return ret; + } + /** + * Update login data on the server (requires active key session). + * + * @param loginDataJson - JSON string of LoginDataPayload + * @returns Promise + * @param {string} login_data_json + * @returns {Promise} + */ + updateLoginData(login_data_json) { + const ptr0 = passStringToWasm0(login_data_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_updateLoginData(this.__wbg_ptr, ptr0, len0); + return ret; + } + /** + * Decipher key selections into OPAQUE passcode bytes for code login. + * Call this after the user taps their nKode sequence on the keypad. + * + * @param userId - The user's ID + * @param secretKeyHex - The user's secret key as hex + * @param loginDataBytes - Raw JSON bytes of login data (from server) + * @param keySelections - Array of key indices the user tapped + * @returns Uint8Array - The passcode bytes to pass to loginCode() + * @param {string} secret_key_hex + * @param {string} login_data_json + * @param {Uint32Array} key_selections + * @returns {Uint8Array} + */ + decipherSelection(secret_key_hex, login_data_json, key_selections) { + const ptr0 = passStringToWasm0(secret_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(login_data_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ptr2 = passArray32ToWasm0(key_selections, wasm.__wbindgen_malloc); + const len2 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_decipherSelection(this.__wbg_ptr, ptr0, len0, ptr1, len1, ptr2, len2); + if (ret[3]) { + throw takeFromExternrefTable0(ret[2]); + } + var v4 = getArrayU8FromWasm0(ret[0], ret[1]).slice(); + wasm.__wbindgen_free(ret[0], ret[1] * 1, 1); + return v4; + } + /** + * Prepare for code login: fetch login data, reconstruct keypad, and fetch icons. + * Returns the keypad configuration with icons for UI display. + * + * Also stores the raw login data JSON internally for decipherSelection(). + * + * @param userId - The user's ID + * @param secretKeyHex - The user's secret key as hex string + * @returns Promise - { keypadIndices, propertiesPerKey, numberOfKeys, mask, icons, loginDataJson } + * @param {string} user_id + * @param {string} secret_key_hex + * @returns {Promise} + */ + prepareCodeLogin(user_id, secret_key_hex) { + const ptr0 = passStringToWasm0(user_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(secret_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_prepareCodeLogin(this.__wbg_ptr, ptr0, len0, ptr1, len1); + return ret; + } + /** + * Generate a new random 16-byte secret key, returned as a hex string (32 chars). + * @returns {string} + */ + static generateSecretKey() { + let deferred1_0; + let deferred1_1; + try { + const ret = wasm.nkodeclient_generateSecretKey(); + deferred1_0 = ret[0]; + deferred1_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred1_0, deferred1_1, 1); + } + } + /** + * Prepare icons for code registration (requires active key session). + * Fetches icons from the server and randomizes their names via ChaCha20. + * Stores intermediate state internally for completeCodeRegistration(). + * + * @returns Promise - JSON: { icons: [{ file_name, file_type, img_data }] } + * @returns {Promise} + */ + prepareCodeRegistration() { + const ret = wasm.nkodeclient_prepareCodeRegistration(this.__wbg_ptr); + return ret; + } + /** + * Complete code registration after icon selection. + * Enciphers the selection, registers OPAQUE code auth, and stores login data. + * + * @param selectedIndices - Array of icon indices the user selected (global indices, not key indices) + * @returns Promise + * @param {Uint32Array} selected_indices + * @returns {Promise} + */ + completeCodeRegistration(selected_indices) { + const ptr0 = passArray32ToWasm0(selected_indices, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_completeCodeRegistration(this.__wbg_ptr, ptr0, len0); + return ret; + } + /** + * Complete code registration with email (full version). + * Enciphers the selection, registers OPAQUE code auth, and stores login data on server. + * + * @param email - User's email address + * @param selectedIndices - Uint32Array of icon indices the user selected + * @returns Promise + * @param {string} email + * @param {Uint32Array} selected_indices + * @returns {Promise} + */ + completeCodeRegistrationWithEmail(email, selected_indices) { + const ptr0 = passStringToWasm0(email, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passArray32ToWasm0(selected_indices, wasm.__wbindgen_malloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_completeCodeRegistrationWithEmail(this.__wbg_ptr, ptr0, len0, ptr1, len1); + return ret; + } + /** + * Create a new client pointed at the given nKode server base URL. + * @param {string} base_url + */ + constructor(base_url) { + const ptr0 = passStringToWasm0(base_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_new(ptr0, len0); + this.__wbg_ptr = ret >>> 0; + NKodeClientFinalization.register(this, this.__wbg_ptr, this); + return this; + } + /** + * Login with OPAQUE key-based authentication. + * Stores the session key internally for subsequent authenticated requests. + * + * @param email - User's email address + * @param secretKeyHex - 16-byte secret key as hex string + * @returns Promise - Session info object + * @param {string} email + * @param {string} secret_key_hex + * @returns {Promise} + */ + loginKey(email, secret_key_hex) { + const ptr0 = passStringToWasm0(email, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(secret_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_loginKey(this.__wbg_ptr, ptr0, len0, ptr1, len1); + return ret; + } + /** + * Set (store) icons on the server (requires active key session). + * + * @param iconsJson - JSON string of { icons: [{ file_name, file_type, img_data }] } + * @returns Promise + * @param {string} icons_json + * @returns {Promise} + */ + setIcons(icons_json) { + const ptr0 = passStringToWasm0(icons_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.nkodeclient_setIcons(this.__wbg_ptr, ptr0, len0); + return ret; + } +} +if (Symbol.dispose) NKodeClient.prototype[Symbol.dispose] = NKodeClient.prototype.free; + +/** + * Initialize panic hook for better error messages in browser console. + */ +export function init() { + wasm.init(); +} + +export function __wbg_Error_52673b7de5a0ca89(arg0, arg1) { + const ret = Error(getStringFromWasm0(arg0, arg1)); + return ret; +}; + +export function __wbg_String_8f0eb39a4a4c2f66(arg0, arg1) { + const ret = String(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); +}; + +export function __wbg___wbindgen_debug_string_adfb662ae34724b6(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); +}; + +export function __wbg___wbindgen_is_function_8d400b8b1af978cd(arg0) { + const ret = typeof(arg0) === 'function'; + return ret; +}; + +export function __wbg___wbindgen_is_object_ce774f3490692386(arg0) { + const val = arg0; + const ret = typeof(val) === 'object' && val !== null; + return ret; +}; + +export function __wbg___wbindgen_is_string_704ef9c8fc131030(arg0) { + const ret = typeof(arg0) === 'string'; + return ret; +}; + +export function __wbg___wbindgen_is_undefined_f6b95eab589e0269(arg0) { + const ret = arg0 === undefined; + return ret; +}; + +export function __wbg___wbindgen_string_get_a2a31e16edf96e42(arg0, arg1) { + const obj = arg1; + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); +}; + +export function __wbg___wbindgen_throw_dd24417ed36fc46e(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); +}; + +export function __wbg__wbg_cb_unref_87dfb5aaa0cbcea7(arg0) { + arg0._wbg_cb_unref(); +}; + +export function __wbg_arrayBuffer_c04af4fce566092d() { return handleError(function (arg0) { + const ret = arg0.arrayBuffer(); + return ret; +}, arguments) }; + +export function __wbg_call_3020136f7a2d6e44() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.call(arg1, arg2); + return ret; +}, arguments) }; + +export function __wbg_call_abb4ff46ce38be40() { return handleError(function (arg0, arg1) { + const ret = arg0.call(arg1); + return ret; +}, arguments) }; + +export function __wbg_crypto_574e78ad8b13b65f(arg0) { + const ret = arg0.crypto; + return ret; +}; + +export function __wbg_error_7534b8e9a36f1ab4(arg0, arg1) { + let deferred0_0; + let deferred0_1; + try { + deferred0_0 = arg0; + deferred0_1 = arg1; + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(deferred0_0, deferred0_1, 1); + } +}; + +export function __wbg_fetch_8119fbf8d0e4f4d1(arg0, arg1) { + const ret = arg0.fetch(arg1); + return ret; +}; + +export function __wbg_getRandomValues_1c61fac11405ffdc() { return handleError(function (arg0, arg1) { + globalThis.crypto.getRandomValues(getArrayU8FromWasm0(arg0, arg1)); +}, arguments) }; + +export function __wbg_getRandomValues_b8f5dbd5f3995a9e() { return handleError(function (arg0, arg1) { + arg0.getRandomValues(arg1); +}, arguments) }; + +export function __wbg_getTime_ad1e9878a735af08(arg0) { + const ret = arg0.getTime(); + return ret; +}; + +export function __wbg_headers_850c3fb50632ae78(arg0) { + const ret = arg0.headers; + return ret; +}; + +export function __wbg_instanceof_Response_cd74d1c2ac92cb0b(arg0) { + let result; + try { + result = arg0 instanceof Response; + } catch (_) { + result = false; + } + const ret = result; + return ret; +}; + +export function __wbg_instanceof_Window_b5cf7783caa68180(arg0) { + let result; + try { + result = arg0 instanceof Window; + } catch (_) { + result = false; + } + const ret = result; + return ret; +}; + +export function __wbg_length_22ac23eaec9d8053(arg0) { + const ret = arg0.length; + return ret; +}; + +export function __wbg_msCrypto_a61aeb35a24c1329(arg0) { + const ret = arg0.msCrypto; + return ret; +}; + +export function __wbg_new_0_23cedd11d9b40c9d() { + const ret = new Date(); + return ret; +}; + +export function __wbg_new_1ba21ce319a06297() { + const ret = new Object(); + return ret; +}; + +export function __wbg_new_25f239778d6112b9() { + const ret = new Array(); + return ret; +}; + +export function __wbg_new_6421f6084cc5bc5a(arg0) { + const ret = new Uint8Array(arg0); + return ret; +}; + +export function __wbg_new_8a6f238a6ece86ea() { + const ret = new Error(); + return ret; +}; + +export function __wbg_new_b546ae120718850e() { + const ret = new Map(); + return ret; +}; + +export function __wbg_new_ff12d2b041fb48f1(arg0, arg1) { + try { + var state0 = {a: arg0, b: arg1}; + var cb0 = (arg0, arg1) => { + const a = state0.a; + state0.a = 0; + try { + return wasm_bindgen__convert__closures_____invoke__h01837f788748d23a(a, state0.b, arg0, arg1); + } finally { + state0.a = a; + } + }; + const ret = new Promise(cb0); + return ret; + } finally { + state0.a = state0.b = 0; + } +}; + +export function __wbg_new_from_slice_f9c22b9153b26992(arg0, arg1) { + const ret = new Uint8Array(getArrayU8FromWasm0(arg0, arg1)); + return ret; +}; + +export function __wbg_new_no_args_cb138f77cf6151ee(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return ret; +}; + +export function __wbg_new_with_length_aa5eaf41d35235e5(arg0) { + const ret = new Uint8Array(arg0 >>> 0); + return ret; +}; + +export function __wbg_new_with_str_and_init_c5748f76f5108934() { return handleError(function (arg0, arg1, arg2) { + const ret = new Request(getStringFromWasm0(arg0, arg1), arg2); + return ret; +}, arguments) }; + +export function __wbg_node_905d3e251edff8a2(arg0) { + const ret = arg0.node; + return ret; +}; + +export function __wbg_ok_dd98ecb60d721e20(arg0) { + const ret = arg0.ok; + return ret; +}; + +export function __wbg_process_dc0fbacc7c1c06f7(arg0) { + const ret = arg0.process; + return ret; +}; + +export function __wbg_prototypesetcall_dfe9b766cdc1f1fd(arg0, arg1, arg2) { + Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2); +}; + +export function __wbg_queueMicrotask_9b549dfce8865860(arg0) { + const ret = arg0.queueMicrotask; + return ret; +}; + +export function __wbg_queueMicrotask_fca69f5bfad613a5(arg0) { + queueMicrotask(arg0); +}; + +export function __wbg_randomFillSync_ac0988aba3254290() { return handleError(function (arg0, arg1) { + arg0.randomFillSync(arg1); +}, arguments) }; + +export function __wbg_require_60cc747a6bc5215a() { return handleError(function () { + const ret = module.require; + return ret; +}, arguments) }; + +export function __wbg_resolve_fd5bfbaa4ce36e1e(arg0) { + const ret = Promise.resolve(arg0); + return ret; +}; + +export function __wbg_set_3f1d0b984ed272ed(arg0, arg1, arg2) { + arg0[arg1] = arg2; +}; + +export function __wbg_set_425eb8b710d5beee() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + arg0.set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); +}, arguments) }; + +export function __wbg_set_781438a03c0c3c81() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.set(arg0, arg1, arg2); + return ret; +}, arguments) }; + +export function __wbg_set_7df433eea03a5c14(arg0, arg1, arg2) { + arg0[arg1 >>> 0] = arg2; +}; + +export function __wbg_set_body_8e743242d6076a4f(arg0, arg1) { + arg0.body = arg1; +}; + +export function __wbg_set_efaaf145b9377369(arg0, arg1, arg2) { + const ret = arg0.set(arg1, arg2); + return ret; +}; + +export function __wbg_set_method_76c69e41b3570627(arg0, arg1, arg2) { + arg0.method = getStringFromWasm0(arg1, arg2); +}; + +export function __wbg_set_mode_611016a6818fc690(arg0, arg1) { + arg0.mode = __wbindgen_enum_RequestMode[arg1]; +}; + +export function __wbg_stack_0ed75d68575b0f3c(arg0, arg1) { + const ret = arg1.stack; + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); +}; + +export function __wbg_static_accessor_GLOBAL_769e6b65d6557335() { + const ret = typeof global === 'undefined' ? null : global; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); +}; + +export function __wbg_static_accessor_GLOBAL_THIS_60cf02db4de8e1c1() { + const ret = typeof globalThis === 'undefined' ? null : globalThis; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); +}; + +export function __wbg_static_accessor_SELF_08f5a74c69739274() { + const ret = typeof self === 'undefined' ? null : self; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); +}; + +export function __wbg_static_accessor_WINDOW_a8924b26aa92d024() { + const ret = typeof window === 'undefined' ? null : window; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); +}; + +export function __wbg_status_9bfc680efca4bdfd(arg0) { + const ret = arg0.status; + return ret; +}; + +export function __wbg_subarray_845f2f5bce7d061a(arg0, arg1, arg2) { + const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0); + return ret; +}; + +export function __wbg_text_51046bb33d257f63() { return handleError(function (arg0) { + const ret = arg0.text(); + return ret; +}, arguments) }; + +export function __wbg_then_429f7caf1026411d(arg0, arg1, arg2) { + const ret = arg0.then(arg1, arg2); + return ret; +}; + +export function __wbg_then_4f95312d68691235(arg0, arg1) { + const ret = arg0.then(arg1); + return ret; +}; + +export function __wbg_versions_c01dfd4722a88165(arg0) { + const ret = arg0.versions; + return ret; +}; + +export function __wbindgen_cast_2241b6af4c4b2941(arg0, arg1) { + // Cast intrinsic for `Ref(String) -> Externref`. + const ret = getStringFromWasm0(arg0, arg1); + return ret; +}; + +export function __wbindgen_cast_4625c577ab2ec9ee(arg0) { + // Cast intrinsic for `U64 -> Externref`. + const ret = BigInt.asUintN(64, arg0); + return ret; +}; + +export function __wbindgen_cast_9ae0607507abb057(arg0) { + // Cast intrinsic for `I64 -> Externref`. + const ret = arg0; + return ret; +}; + +export function __wbindgen_cast_cb9088102bce6b30(arg0, arg1) { + // Cast intrinsic for `Ref(Slice(U8)) -> NamedExternref("Uint8Array")`. + const ret = getArrayU8FromWasm0(arg0, arg1); + return ret; +}; + +export function __wbindgen_cast_d6cd19b81560fd6e(arg0) { + // Cast intrinsic for `F64 -> Externref`. + const ret = arg0; + return ret; +}; + +export function __wbindgen_cast_f2cc0f2a96e2ef5b(arg0, arg1) { + // Cast intrinsic for `Closure(Closure { dtor_idx: 115, function: Function { arguments: [Externref], shim_idx: 116, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h28b97059ae600264, wasm_bindgen__convert__closures_____invoke__h8f97ce5df83102bb); + return ret; +}; + +export function __wbindgen_init_externref_table() { + const table = wasm.__wbindgen_externrefs; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); +}; diff --git a/pkg/nkode_client_wasm_bg.wasm b/pkg/nkode_client_wasm_bg.wasm new file mode 100644 index 0000000..035991b Binary files /dev/null and b/pkg/nkode_client_wasm_bg.wasm differ diff --git a/pkg/nkode_client_wasm_bg.wasm.d.ts b/pkg/nkode_client_wasm_bg.wasm.d.ts new file mode 100644 index 0000000..0687037 --- /dev/null +++ b/pkg/nkode_client_wasm_bg.wasm.d.ts @@ -0,0 +1,35 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const __wbg_nkodeclient_free: (a: number, b: number) => void; +export const init: () => void; +export const nkodeclient_clearSession: (a: number) => void; +export const nkodeclient_completeCodeRegistration: (a: number, b: number, c: number) => any; +export const nkodeclient_completeCodeRegistrationWithEmail: (a: number, b: number, c: number, d: number, e: number) => any; +export const nkodeclient_decipherSelection: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number]; +export const nkodeclient_generateSecretKey: () => [number, number]; +export const nkodeclient_getLoginData: (a: number, b: number, c: number) => any; +export const nkodeclient_getNewIcons: (a: number, b: number) => any; +export const nkodeclient_getUserId: (a: number) => [number, number]; +export const nkodeclient_hasSession: (a: number) => number; +export const nkodeclient_loginCode: (a: number, b: number, c: number, d: number, e: number) => any; +export const nkodeclient_loginKey: (a: number, b: number, c: number, d: number, e: number) => any; +export const nkodeclient_new: (a: number, b: number) => number; +export const nkodeclient_postLoginData: (a: number, b: number, c: number) => any; +export const nkodeclient_prepareCodeLogin: (a: number, b: number, c: number, d: number, e: number) => any; +export const nkodeclient_prepareCodeRegistration: (a: number) => any; +export const nkodeclient_registerCode: (a: number, b: number, c: number, d: number, e: number) => any; +export const nkodeclient_registerKey: (a: number, b: number, c: number, d: number, e: number) => any; +export const nkodeclient_setIcons: (a: number, b: number, c: number) => any; +export const nkodeclient_updateLoginData: (a: number, b: number, c: number) => any; +export const wasm_bindgen__convert__closures_____invoke__h8f97ce5df83102bb: (a: number, b: number, c: any) => void; +export const wasm_bindgen__closure__destroy__h28b97059ae600264: (a: number, b: number) => void; +export const wasm_bindgen__convert__closures_____invoke__h01837f788748d23a: (a: number, b: number, c: any, d: any) => void; +export const __wbindgen_malloc: (a: number, b: number) => number; +export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; +export const __wbindgen_exn_store: (a: number) => void; +export const __externref_table_alloc: () => number; +export const __wbindgen_externrefs: WebAssembly.Table; +export const __wbindgen_free: (a: number, b: number, c: number) => void; +export const __externref_table_dealloc: (a: number) => void; +export const __wbindgen_start: () => void; diff --git a/pkg/package.json b/pkg/package.json new file mode 100644 index 0000000..af7b277 --- /dev/null +++ b/pkg/package.json @@ -0,0 +1,18 @@ +{ + "name": "nkode-client-wasm", + "type": "module", + "description": "nKode client compiled to WebAssembly with TypeScript bindings", + "version": "0.1.0", + "files": [ + "nkode_client_wasm_bg.wasm", + "nkode_client_wasm.js", + "nkode_client_wasm_bg.js", + "nkode_client_wasm.d.ts" + ], + "main": "nkode_client_wasm.js", + "types": "nkode_client_wasm.d.ts", + "sideEffects": [ + "./nkode_client_wasm.js", + "./snippets/*" + ] +} \ No newline at end of file