Browse Source

fix: move lexical declarations outside case blocks in AbsurdSqlDatabaseService

- Move queryResult and allResult declarations outside switch statement
- Change const declarations to let since they're now in outer scope
- Remove const declarations from inside case blocks

This fixes the 'no-case-declarations' linter errors by ensuring variables
are declared in a scope that encompasses all case blocks, preventing
potential scoping issues.

Note: Type definition errors for external modules remain and should be
addressed separately.
pull/137/head
Matthew Raymer 2 weeks ago
parent
commit
0f1ac2b230
  1. 3034
      package-lock.json
  2. 68
      src/db/databaseUtil.ts
  3. 42
      src/libs/crypto/index.ts
  4. 6
      src/libs/util.ts
  5. 34
      src/services/AbsurdSqlDatabaseService.ts
  6. 5
      src/services/PlatformService.ts
  7. 5
      src/services/platforms/CapacitorPlatformService.ts
  8. 5
      src/services/platforms/ElectronPlatformService.ts
  9. 5
      src/services/platforms/PyWebViewPlatformService.ts
  10. 5
      src/services/platforms/WebPlatformService.ts
  11. 6
      src/utils/node-modules/crypto.js
  12. 8
      src/utils/node-modules/fs.js
  13. 14
      src/utils/node-modules/path.js
  14. 26
      src/views/AccountViewView.vue
  15. 6
      src/views/TestView.vue

3034
package-lock.json

File diff suppressed because it is too large

68
src/db/databaseUtil.ts

@ -11,7 +11,12 @@ export async function updateDefaultSettings(
delete settingsChanges.id; delete settingsChanges.id;
try { try {
const platformService = PlatformServiceFactory.getInstance(); const platformService = PlatformServiceFactory.getInstance();
const { sql, params } = generateUpdateStatement(settingsChanges, "settings", "id = ?", [MASTER_SETTINGS_KEY]); const { sql, params } = generateUpdateStatement(
settingsChanges,
"settings",
"id = ?",
[MASTER_SETTINGS_KEY],
);
const result = await platformService.dbExec(sql, params); const result = await platformService.dbExec(sql, params);
return result.changes === 1; return result.changes === 1;
} catch (error) { } catch (error) {
@ -40,7 +45,7 @@ export async function updateAccountSettings(
settingsChanges, settingsChanges,
"settings", "settings",
"accountDid = ?", "accountDid = ?",
[accountDid] [accountDid],
); );
const updateResult = await platform.dbExec(updateSql, updateParams); const updateResult = await platform.dbExec(updateSql, updateParams);
@ -51,9 +56,9 @@ export async function updateAccountSettings(
} else { } else {
const columns = Object.keys(settingsChanges); const columns = Object.keys(settingsChanges);
const values = Object.values(settingsChanges); const values = Object.values(settingsChanges);
const placeholders = values.map(() => '?').join(', '); const placeholders = values.map(() => "?").join(", ");
const insertSql = `INSERT INTO settings (${columns.join(', ')}) VALUES (${placeholders})`; const insertSql = `INSERT INTO settings (${columns.join(", ")}) VALUES (${placeholders})`;
const result = await platform.dbExec(insertSql, values); const result = await platform.dbExec(insertSql, values);
return result.changes === 1; return result.changes === 1;
@ -69,7 +74,9 @@ const DEFAULT_SETTINGS: Settings = {
// retrieves default settings // retrieves default settings
export async function retrieveSettingsForDefaultAccount(): Promise<Settings> { export async function retrieveSettingsForDefaultAccount(): Promise<Settings> {
const platform = PlatformServiceFactory.getInstance(); const platform = PlatformServiceFactory.getInstance();
const result = await platform.dbQuery("SELECT * FROM settings WHERE id = ?", [MASTER_SETTINGS_KEY]) const result = await platform.dbQuery("SELECT * FROM settings WHERE id = ?", [
MASTER_SETTINGS_KEY,
]);
if (!result) { if (!result) {
return DEFAULT_SETTINGS; return DEFAULT_SETTINGS;
} else { } else {
@ -85,10 +92,14 @@ export async function retrieveSettingsForActiveAccount(): Promise<Settings> {
const platform = PlatformServiceFactory.getInstance(); const platform = PlatformServiceFactory.getInstance();
const result = await platform.dbQuery( const result = await platform.dbQuery(
"SELECT * FROM settings WHERE accountDid = ?", "SELECT * FROM settings WHERE accountDid = ?",
[defaultSettings.activeDid] [defaultSettings.activeDid],
);
const overrideSettings = result
? (mapColumnsToValues(result.columns, result.values)[0] as Settings)
: {};
const overrideSettingsFiltered = Object.fromEntries(
Object.entries(overrideSettings).filter(([_, v]) => v !== null),
); );
const overrideSettings = result ? mapColumnsToValues(result.columns, result.values)[0] as Settings : {};
const overrideSettingsFiltered = Object.fromEntries(Object.entries(overrideSettings).filter(([_, v]) => v !== null));
return { ...defaultSettings, ...overrideSettingsFiltered }; return { ...defaultSettings, ...overrideSettingsFiltered };
} }
} }
@ -100,7 +111,7 @@ export async function logToDb(message: string): Promise<void> {
// Check if we have any logs for today // Check if we have any logs for today
const result = await platform.dbQuery( const result = await platform.dbQuery(
"SELECT message FROM logs WHERE date = ?", "SELECT message FROM logs WHERE date = ?",
[todayKey] [todayKey],
); );
if (!result || result.values.length === 0) { if (!result || result.values.length === 0) {
@ -109,19 +120,19 @@ export async function logToDb(message: string): Promise<void> {
// Insert new log // Insert new log
const fullMessage = `${new Date().toISOString()} ${message}`; const fullMessage = `${new Date().toISOString()} ${message}`;
await platform.dbExec( await platform.dbExec("INSERT INTO logs (date, message) VALUES (?, ?)", [
"INSERT INTO logs (date, message) VALUES (?, ?)", todayKey,
[todayKey, fullMessage] fullMessage,
); ]);
} else { } else {
// Append to existing log // Append to existing log
const prevMessages = result.values[0][0] as string; const prevMessages = result.values[0][0] as string;
const fullMessage = `${prevMessages}\n${new Date().toISOString()} ${message}`; const fullMessage = `${prevMessages}\n${new Date().toISOString()} ${message}`;
await platform.dbExec( await platform.dbExec("UPDATE logs SET message = ? WHERE date = ?", [
"UPDATE logs SET message = ? WHERE date = ?", fullMessage,
[fullMessage, todayKey] todayKey,
); ]);
} }
} }
@ -147,14 +158,14 @@ export async function logConsoleAndDb(
* @returns Object containing the SQL statement and parameters array * @returns Object containing the SQL statement and parameters array
*/ */
function generateUpdateStatement( function generateUpdateStatement(
model: Record<string, any>, model: Record<string, unknown>,
tableName: string, tableName: string,
whereClause: string, whereClause: string,
whereParams: any[] = [] whereParams: unknown[] = [],
): { sql: string; params: any[] } { ): { sql: string; params: unknown[] } {
// Filter out undefined/null values and create SET clause // Filter out undefined/null values and create SET clause
const setClauses: string[] = []; const setClauses: string[] = [];
const params: any[] = []; const params: unknown[] = [];
Object.entries(model).forEach(([key, value]) => { Object.entries(model).forEach(([key, value]) => {
if (value !== undefined) { if (value !== undefined) {
@ -164,14 +175,14 @@ function generateUpdateStatement(
}); });
if (setClauses.length === 0) { if (setClauses.length === 0) {
throw new Error('No valid fields to update'); throw new Error("No valid fields to update");
} }
const sql = `UPDATE ${tableName} SET ${setClauses.join(', ')} WHERE ${whereClause}`; const sql = `UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE ${whereClause}`;
return { return {
sql, sql,
params: [...params, ...whereParams] params: [...params, ...whereParams],
}; };
} }
@ -184,14 +195,13 @@ function generateUpdateStatement(
*/ */
export function mapColumnsToValues( export function mapColumnsToValues(
columns: string[], columns: string[],
values: any[][] values: unknown[][],
): Record<string, any>[] { ): Record<string, unknown>[] {
return values.map(row => { return values.map((row) => {
const obj: Record<string, any> = {}; const obj: Record<string, unknown> = {};
columns.forEach((column, index) => { columns.forEach((column, index) => {
obj[column] = row[index]; obj[column] = row[index];
}); });
return obj; return obj;
}); });
} }

42
src/libs/crypto/index.ts

@ -312,23 +312,29 @@ export async function testMessageEncryptionDecryption() {
} }
// Simple encryption/decryption using Node's crypto // Simple encryption/decryption using Node's crypto
export async function simpleEncrypt(text: string, secret: string): Promise<string> { export async function simpleEncrypt(
text: string,
secret: string,
): Promise<string> {
const iv = crypto.getRandomValues(new Uint8Array(16)); const iv = crypto.getRandomValues(new Uint8Array(16));
// Derive a 256-bit key from the secret using SHA-256 // Derive a 256-bit key from the secret using SHA-256
const keyData = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(secret)); const keyData = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(secret),
);
const key = await crypto.subtle.importKey( const key = await crypto.subtle.importKey(
'raw', "raw",
keyData, keyData,
{ name: 'AES-GCM' }, { name: "AES-GCM" },
false, false,
['encrypt'] ["encrypt"],
); );
const encrypted = await crypto.subtle.encrypt( const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv }, { name: "AES-GCM", iv },
key, key,
new TextEncoder().encode(text) new TextEncoder().encode(text),
); );
// Combine IV and encrypted data // Combine IV and encrypted data
@ -339,27 +345,33 @@ export async function simpleEncrypt(text: string, secret: string): Promise<strin
return btoa(String.fromCharCode(...result)); return btoa(String.fromCharCode(...result));
} }
export async function simpleDecrypt(encryptedText: string, secret: string): Promise<string> { export async function simpleDecrypt(
const data = Uint8Array.from(atob(encryptedText), c => c.charCodeAt(0)); encryptedText: string,
secret: string,
): Promise<string> {
const data = Uint8Array.from(atob(encryptedText), (c) => c.charCodeAt(0));
// Extract IV and encrypted data // Extract IV and encrypted data
const iv = data.slice(0, 16); const iv = data.slice(0, 16);
const encrypted = data.slice(16); const encrypted = data.slice(16);
// Derive the same 256-bit key from the secret using SHA-256 // Derive the same 256-bit key from the secret using SHA-256
const keyData = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(secret)); const keyData = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(secret),
);
const key = await crypto.subtle.importKey( const key = await crypto.subtle.importKey(
'raw', "raw",
keyData, keyData,
{ name: 'AES-GCM' }, { name: "AES-GCM" },
false, false,
['decrypt'] ["decrypt"],
); );
const decrypted = await crypto.subtle.decrypt( const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv }, { name: "AES-GCM", iv },
key, key,
encrypted encrypted,
); );
return new TextDecoder().decode(decrypted); return new TextDecoder().decode(decrypted);

6
src/libs/util.ts

@ -5,7 +5,11 @@ import { Buffer } from "buffer";
import * as R from "ramda"; import * as R from "ramda";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import { DEFAULT_PUSH_SERVER, NotificationIface, USE_DEXIE_DB } from "../constants/app"; import {
DEFAULT_PUSH_SERVER,
NotificationIface,
USE_DEXIE_DB,
} from "../constants/app";
import { import {
accountsDBPromise, accountsDBPromise,
retrieveSettingsForActiveAccount, retrieveSettingsForActiveAccount,

34
src/services/AbsurdSqlDatabaseService.ts

@ -20,11 +20,11 @@ interface AbsurdSqlDatabase {
} }
interface QueuedOperation { interface QueuedOperation {
type: 'run' | 'query' | 'getOneRow' | 'getAll'; type: "run" | "query" | "getOneRow" | "getAll";
sql: string; sql: string;
params: unknown[]; params: unknown[];
resolve: (value: any) => void; resolve: (value: unknown) => void;
reject: (reason: any) => void; reject: (reason: unknown) => void;
} }
class AbsurdSqlDatabaseService implements DatabaseService { class AbsurdSqlDatabaseService implements DatabaseService {
@ -129,19 +129,21 @@ class AbsurdSqlDatabaseService implements DatabaseService {
try { try {
let result; let result;
let queryResult;
let allResult;
switch (operation.type) { switch (operation.type) {
case 'run': case "run":
result = await this.db.run(operation.sql, operation.params); result = await this.db.run(operation.sql, operation.params);
break; break;
case 'query': case "query":
result = await this.db.exec(operation.sql, operation.params); result = await this.db.exec(operation.sql, operation.params);
break; break;
case 'getOneRow': case "getOneRow":
const queryResult = await this.db.exec(operation.sql, operation.params); queryResult = await this.db.exec(operation.sql, operation.params);
result = queryResult[0]?.values[0]; result = queryResult[0]?.values[0];
break; break;
case 'getAll': case "getAll":
const allResult = await this.db.exec(operation.sql, operation.params); allResult = await this.db.exec(operation.sql, operation.params);
result = allResult[0]?.values || []; result = allResult[0]?.values || [];
break; break;
} }
@ -155,7 +157,7 @@ class AbsurdSqlDatabaseService implements DatabaseService {
} }
private async queueOperation<T>( private async queueOperation<T>(
type: QueuedOperation['type'], type: QueuedOperation["type"],
sql: string, sql: string,
params: unknown[] = [], params: unknown[] = [],
): Promise<T> { ): Promise<T> {
@ -205,13 +207,17 @@ class AbsurdSqlDatabaseService implements DatabaseService {
params: unknown[] = [], params: unknown[] = [],
): Promise<{ changes: number; lastId?: number }> { ): Promise<{ changes: number; lastId?: number }> {
await this.waitForInitialization(); await this.waitForInitialization();
return this.queueOperation<{ changes: number; lastId?: number }>('run', sql, params); return this.queueOperation<{ changes: number; lastId?: number }>(
"run",
sql,
params,
);
} }
// Note that the resulting array may be empty if there are no results from the query // Note that the resulting array may be empty if there are no results from the query
async query(sql: string, params: unknown[] = []): Promise<QueryExecResult[]> { async query(sql: string, params: unknown[] = []): Promise<QueryExecResult[]> {
await this.waitForInitialization(); await this.waitForInitialization();
return this.queueOperation<QueryExecResult[]>('query', sql, params); return this.queueOperation<QueryExecResult[]>("query", sql, params);
} }
async getOneRow( async getOneRow(
@ -219,12 +225,12 @@ class AbsurdSqlDatabaseService implements DatabaseService {
params: unknown[] = [], params: unknown[] = [],
): Promise<unknown[] | undefined> { ): Promise<unknown[] | undefined> {
await this.waitForInitialization(); await this.waitForInitialization();
return this.queueOperation<unknown[] | undefined>('getOneRow', sql, params); return this.queueOperation<unknown[] | undefined>("getOneRow", sql, params);
} }
async getAll(sql: string, params: unknown[] = []): Promise<unknown[][]> { async getAll(sql: string, params: unknown[] = []): Promise<unknown[][]> {
await this.waitForInitialization(); await this.waitForInitialization();
return this.queueOperation<unknown[][]>('getAll', sql, params); return this.queueOperation<unknown[][]>("getAll", sql, params);
} }
} }

5
src/services/PlatformService.ts

@ -115,5 +115,8 @@ export interface PlatformService {
* @param params - The parameters to pass to the statement * @param params - The parameters to pass to the statement
* @returns Promise resolving to the result of the statement * @returns Promise resolving to the result of the statement
*/ */
dbExec(sql: string, params?: unknown[]): Promise<{ changes: number; lastId?: number }>; dbExec(
sql: string,
params?: unknown[],
): Promise<{ changes: number; lastId?: number }>;
} }

5
src/services/platforms/CapacitorPlatformService.ts

@ -481,7 +481,10 @@ export class CapacitorPlatformService implements PlatformService {
dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> { dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> {
throw new Error("Not implemented for " + sql + " with params " + params); throw new Error("Not implemented for " + sql + " with params " + params);
} }
dbExec(sql: string, params?: unknown[]): Promise<{ changes: number; lastId?: number }> { dbExec(
sql: string,
params?: unknown[],
): Promise<{ changes: number; lastId?: number }> {
throw new Error("Not implemented for " + sql + " with params " + params); throw new Error("Not implemented for " + sql + " with params " + params);
} }
} }

5
src/services/platforms/ElectronPlatformService.ts

@ -113,7 +113,10 @@ export class ElectronPlatformService implements PlatformService {
dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> { dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> {
throw new Error("Not implemented for " + sql + " with params " + params); throw new Error("Not implemented for " + sql + " with params " + params);
} }
dbExec(sql: string, params?: unknown[]): Promise<{ changes: number; lastId?: number }> { dbExec(
sql: string,
params?: unknown[],
): Promise<{ changes: number; lastId?: number }> {
throw new Error("Not implemented for " + sql + " with params " + params); throw new Error("Not implemented for " + sql + " with params " + params);
} }
} }

5
src/services/platforms/PyWebViewPlatformService.ts

@ -114,7 +114,10 @@ export class PyWebViewPlatformService implements PlatformService {
dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> { dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> {
throw new Error("Not implemented for " + sql + " with params " + params); throw new Error("Not implemented for " + sql + " with params " + params);
} }
dbExec(sql: string, params?: unknown[]): Promise<{ changes: number; lastId?: number }> { dbExec(
sql: string,
params?: unknown[],
): Promise<{ changes: number; lastId?: number }> {
throw new Error("Not implemented for " + sql + " with params " + params); throw new Error("Not implemented for " + sql + " with params " + params);
} }
} }

5
src/services/platforms/WebPlatformService.ts

@ -372,7 +372,10 @@ export class WebPlatformService implements PlatformService {
/** /**
* @see PlatformService.dbExec * @see PlatformService.dbExec
*/ */
dbExec(sql: string, params?: unknown[]): Promise<{ changes: number; lastId?: number }> { dbExec(
sql: string,
params?: unknown[],
): Promise<{ changes: number; lastId?: number }> {
return databaseService.run(sql, params); return databaseService.run(sql, params);
} }
} }

6
src/utils/node-modules/crypto.js

@ -9,9 +9,9 @@ const crypto = {
}, },
createHash: () => ({ createHash: () => ({
update: () => ({ update: () => ({
digest: () => new Uint8Array(32) // Return empty hash digest: () => new Uint8Array(32), // Return empty hash
}) }),
}) }),
}; };
export default crypto; export default crypto;

8
src/utils/node-modules/fs.js

@ -1,18 +1,18 @@
// Minimal fs module implementation for browser // Minimal fs module implementation for browser
const fs = { const fs = {
readFileSync: () => { readFileSync: () => {
throw new Error('fs.readFileSync is not supported in browser'); throw new Error("fs.readFileSync is not supported in browser");
}, },
writeFileSync: () => { writeFileSync: () => {
throw new Error('fs.writeFileSync is not supported in browser'); throw new Error("fs.writeFileSync is not supported in browser");
}, },
existsSync: () => false, existsSync: () => false,
mkdirSync: () => {}, mkdirSync: () => {},
readdirSync: () => [], readdirSync: () => [],
statSync: () => ({ statSync: () => ({
isDirectory: () => false, isDirectory: () => false,
isFile: () => false isFile: () => false,
}) }),
}; };
export default fs; export default fs;

14
src/utils/node-modules/path.js

@ -1,13 +1,13 @@
// Minimal path module implementation for browser // Minimal path module implementation for browser
const path = { const path = {
resolve: (...parts) => parts.join('/'), resolve: (...parts) => parts.join("/"),
join: (...parts) => parts.join('/'), join: (...parts) => parts.join("/"),
dirname: (p) => p.split('/').slice(0, -1).join('/'), dirname: (p) => p.split("/").slice(0, -1).join("/"),
basename: (p) => p.split('/').pop(), basename: (p) => p.split("/").pop(),
extname: (p) => { extname: (p) => {
const parts = p.split('.'); const parts = p.split(".");
return parts.length > 1 ? '.' + parts.pop() : ''; return parts.length > 1 ? "." + parts.pop() : "";
} },
}; };
export default path; export default path;

26
src/views/AccountViewView.vue

@ -1351,15 +1351,19 @@ export default class AccountViewView extends Vue {
*/ */
async processIdentity() { async processIdentity() {
const platformService = PlatformServiceFactory.getInstance(); const platformService = PlatformServiceFactory.getInstance();
const dbAccount = await platformService.dbQuery("SELECT * FROM accounts WHERE did = ?", [this.activeDid]); const dbAccount = await platformService.dbQuery(
"SELECT * FROM accounts WHERE did = ?",
[this.activeDid],
);
let account: Account | undefined = undefined; let account: Account | undefined = undefined;
if (dbAccount) { if (dbAccount) {
account = databaseUtil.mapColumnsToValues(dbAccount.columns, dbAccount.values)[0] as Account; account = databaseUtil.mapColumnsToValues(
dbAccount.columns,
dbAccount.values,
)[0] as Account;
} }
if (USE_DEXIE_DB) { if (USE_DEXIE_DB) {
account = await retrieveAccountMetadata( account = await retrieveAccountMetadata(this.activeDid);
this.activeDid,
);
} }
if (account?.identity) { if (account?.identity) {
const identity = JSON.parse(account.identity as string) as IIdentifier; const identity = JSON.parse(account.identity as string) as IIdentifier;
@ -1817,7 +1821,9 @@ export default class AccountViewView extends Vue {
if (!this.isRegistered) { if (!this.isRegistered) {
// the user was not known to be registered, but now they are (because we got no error) so let's record it // the user was not known to be registered, but now they are (because we got no error) so let's record it
try { try {
await databaseUtil.updateAccountSettings(did, { isRegistered: true }); await databaseUtil.updateAccountSettings(did, {
isRegistered: true,
});
if (USE_DEXIE_DB) { if (USE_DEXIE_DB) {
await db.open(); await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, { await db.settings.update(MASTER_SETTINGS_KEY, {
@ -2099,7 +2105,9 @@ export default class AccountViewView extends Vue {
throw Error("Profile not saved"); throw Error("Profile not saved");
} }
} catch (error) { } catch (error) {
databaseUtil.logConsoleAndDb("Error saving profile: " + errorStringForLog(error)); databaseUtil.logConsoleAndDb(
"Error saving profile: " + errorStringForLog(error),
);
if (USE_DEXIE_DB) { if (USE_DEXIE_DB) {
logConsoleAndDb("Error saving profile: " + errorStringForLog(error)); logConsoleAndDb("Error saving profile: " + errorStringForLog(error));
} }
@ -2192,7 +2200,9 @@ export default class AccountViewView extends Vue {
throw Error("Profile not deleted"); throw Error("Profile not deleted");
} }
} catch (error) { } catch (error) {
databaseUtil.logConsoleAndDb("Error deleting profile: " + errorStringForLog(error)); databaseUtil.logConsoleAndDb(
"Error deleting profile: " + errorStringForLog(error),
);
if (USE_DEXIE_DB) { if (USE_DEXIE_DB) {
logConsoleAndDb("Error deleting profile: " + errorStringForLog(error)); logConsoleAndDb("Error deleting profile: " + errorStringForLog(error));
} }

6
src/views/TestView.vue

@ -445,11 +445,13 @@ export default class Help extends Vue {
} }
public async testMessageEncryptionDecryption() { public async testMessageEncryptionDecryption() {
this.messageEncryptionTestResult = await cryptoLib.testMessageEncryptionDecryption(); this.messageEncryptionTestResult =
await cryptoLib.testMessageEncryptionDecryption();
} }
public async testSimpleEncryptionDecryption() { public async testSimpleEncryptionDecryption() {
this.simpleEncryptionTestResult = await cryptoLib.testSimpleEncryptionDecryption(); this.simpleEncryptionTestResult =
await cryptoLib.testSimpleEncryptionDecryption();
} }
public async createJwtSimplewebauthn() { public async createJwtSimplewebauthn() {

Loading…
Cancel
Save