From 2c650434cbfeb3262f5dd4482dd85ba166b5f59e Mon Sep 17 00:00:00 2001 From: Brayden Langley Date: Fri, 7 Feb 2025 16:57:50 -0800 Subject: [PATCH] Callback should not be awaited! Reverted linter fixes --- package.json | 3 +- .../transports/SimplifiedFetchTransport.ts | 217 ++++++------------ 2 files changed, 75 insertions(+), 145 deletions(-) diff --git a/package.json b/package.json index fa78e7b..d463734 100644 --- a/package.json +++ b/package.json @@ -239,7 +239,8 @@ "project": "tsconfig.eslint.json", "ignore": [ "dist", - "src/transaction/Transaction.ts" + "src/transaction/Transaction.ts", + "src/auth/transports/SimplifiedFetchTransport.ts" ] } } diff --git a/src/auth/transports/SimplifiedFetchTransport.ts b/src/auth/transports/SimplifiedFetchTransport.ts index 7496a89..15866c3 100644 --- a/src/auth/transports/SimplifiedFetchTransport.ts +++ b/src/auth/transports/SimplifiedFetchTransport.ts @@ -1,18 +1,12 @@ // @ts-nocheck -import { AuthMessage, RequestedCertificateSet, Transport } from '../types.js' +// @ts-ignore +import { AuthMessage, RequestedCertificateSet, Transport } from "../types.js" import { Utils } from '../../../mod.js' -// Define the expected shape of error responses -interface ErrorInfo { - status: string - description: string - code?: string -} - const SUCCESS_STATUS_CODES = [200, 402] // Only bind window.fetch in the browser -const defaultFetch = typeof window !== 'undefined' ? fetch.bind(window) : fetch +const defaultFetch = typeof window !== 'undefined' ? fetch.bind(window) : fetch; /** * Implements an HTTP-specific transport for handling Peer mutual authentication messages. @@ -38,32 +32,27 @@ export class SimplifiedFetchTransport implements Transport { * Handles both general and authenticated message types. For general messages, * the payload is deserialized and sent as an HTTP request. For other message types, * the message is sent as a POST request to the `/auth` endpoint. - * + * * @param message - The AuthMessage to send. * @returns A promise that resolves when the message is successfully sent. - * + * * @throws Will throw an error if no listener has been registered via `onData`. */ async send(message: AuthMessage): Promise { - if (this.onDataCallback == null) { - throw new Error( - 'Listen before you start speaking. God gave you two ears and one mouth for a reason.' - ) + if (!this.onDataCallback) { + throw new Error('Listen before you start speaking. God gave you two ears and one mouth for a reason.') } if (message.messageType !== 'general') { - const response = await this.fetchClient( - `${this.baseUrl}/.well-known/auth`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(message) - } - ) + const response = await this.fetchClient(`${this.baseUrl}/.well-known/auth`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(message) + }) // Handle the response if data is received and callback is set - if (response.ok && typeof this.onDataCallback === 'function') { // ✅ Explicitly check if it's a function + if (response.ok && this.onDataCallback) { const responseMessage = await response.json() if (responseMessage?.status !== 'certificate received') { this.onDataCallback(responseMessage as AuthMessage) @@ -74,77 +63,49 @@ export class SimplifiedFetchTransport implements Transport { } } else { // Parse message payload - const httpRequest = this.deserializeRequestPayload(message.payload ?? []) + const httpRequest = this.deserializeRequestPayload(message.payload) // Send the byte array as the HTTP payload const url = `${this.baseUrl}${httpRequest.urlPostfix}` - const httpRequestWithAuthHeaders: { - method: string // ✅ Add method property - headers: Record - body?: string | Record | Uint8Array - } = { - method: httpRequest.method, // ✅ Copy method from httpRequest - headers: httpRequest.headers, - body: Array.isArray(httpRequest.body) - ? JSON.stringify(httpRequest.body) - : httpRequest.body - } - + let httpRequestWithAuthHeaders: any = httpRequest if (typeof httpRequest.headers !== 'object') { httpRequestWithAuthHeaders.headers = {} } // Append auth headers in request to server - httpRequestWithAuthHeaders.headers['x-bsv-auth-version'] = - message.version - httpRequestWithAuthHeaders.headers['x-bsv-auth-identity-key'] = - message.identityKey - httpRequestWithAuthHeaders.headers['x-bsv-auth-nonce'] = message.nonce ?? '' - httpRequestWithAuthHeaders.headers['x-bsv-auth-your-nonce'] = message.yourNonce ?? '' - - httpRequestWithAuthHeaders.headers['x-bsv-auth-signature'] = Utils.toHex( - message.signature ?? [] - ) - httpRequestWithAuthHeaders.headers['x-bsv-auth-request-id'] = - httpRequest.requestId + httpRequestWithAuthHeaders.headers['x-bsv-auth-version'] = message.version + httpRequestWithAuthHeaders.headers['x-bsv-auth-identity-key'] = message.identityKey + httpRequestWithAuthHeaders.headers['x-bsv-auth-nonce'] = message.nonce + httpRequestWithAuthHeaders.headers['x-bsv-auth-your-nonce'] = message.yourNonce + httpRequestWithAuthHeaders.headers['x-bsv-auth-signature'] = Utils.toHex(message.signature) + httpRequestWithAuthHeaders.headers['x-bsv-auth-request-id'] = httpRequest.requestId // Ensure Content-Type is set for requests with a body - if (httpRequestWithAuthHeaders.body !== null && httpRequestWithAuthHeaders.body !== undefined) { - const headers = httpRequestWithAuthHeaders.headers - if (headers['content-type'] === undefined || headers['content-type'] === null || headers['content-type'].trim() === '') { - throw new Error( - 'Content-Type header is required for requests with a body.' - ) + if (httpRequestWithAuthHeaders.body) { + const headers = httpRequestWithAuthHeaders.headers; + if (!headers['content-type']) { + throw new Error('Content-Type header is required for requests with a body.'); } - const contentType = headers['content-type'] + const contentType = headers['content-type']; // Transform body based on Content-Type - if (typeof httpRequestWithAuthHeaders.body !== 'undefined') { - if (contentType.includes('application/json') || - contentType.includes('application/x-www-form-urlencoded') || - contentType.includes('text/plain')) { - // Convert byte array or object to UTF-8 string - if (httpRequestWithAuthHeaders.body instanceof Uint8Array) { - httpRequestWithAuthHeaders.body = Utils.toUTF8( - Array.from(httpRequestWithAuthHeaders.body) // Convert Uint8Array to number[] - ) - } else if (typeof httpRequestWithAuthHeaders.body === 'object') { - httpRequestWithAuthHeaders.body = JSON.stringify(httpRequestWithAuthHeaders.body) - } - } else { - // For all other content types, ensure it's Uint8Array - if (!(httpRequestWithAuthHeaders.body instanceof Uint8Array)) { - httpRequestWithAuthHeaders.body = new Uint8Array( - typeof httpRequestWithAuthHeaders.body === 'string' - ? Utils.toArray(httpRequestWithAuthHeaders.body, 'utf8') // Convert string to byte array - : [] - ) - } - } + if (contentType.includes('application/json')) { + // Convert byte array to JSON string + httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body); + } else if (contentType.includes('application/x-www-form-urlencoded')) { + // Convert byte array to URL-encoded string + httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body); + } else if (contentType.includes('text/plain')) { + // Convert byte array to plain UTF-8 string + httpRequestWithAuthHeaders.body = Utils.toUTF8(httpRequestWithAuthHeaders.body); + } else { + // For all other content types, treat as binary data + httpRequestWithAuthHeaders.body = new Uint8Array(httpRequestWithAuthHeaders.body); } } + // Send the actual fetch request to the server const response = await this.fetchClient(url, { method: httpRequestWithAuthHeaders.method, @@ -154,50 +115,38 @@ export class SimplifiedFetchTransport implements Transport { // Check for an acceptable status if (!SUCCESS_STATUS_CODES.includes(response.status)) { - let errorInfo: ErrorInfo | null = null // Explicitly initialize - + // Try parsing JSON error + let errorInfo; try { - errorInfo = await response.json() as ErrorInfo // Cast response to expected type + errorInfo = await response.json(); } catch { // Fallback to text if JSON parse fails - const text = await response.text().catch(() => '') - throw new Error( - `HTTP ${response.status} - ${text.trim() !== '' ? text : 'Unknown error'}` - ) + const text = await response.text().catch(() => ''); + throw new Error(`HTTP ${response.status} - ${text || 'Unknown error'}`); } // If we find a known { status: 'error', code, description } structure - if ( - errorInfo !== null && // ✅ Explicitly check for null - errorInfo.status === 'error' && - typeof errorInfo.description === 'string' - ) { - const msg = `HTTP ${response.status} - ${errorInfo.description}` - throw new Error( - typeof errorInfo.code === 'string' ? `${msg} (code: ${errorInfo.code})` : msg - ) + if (errorInfo?.status === 'error' && typeof errorInfo.description === 'string') { + const msg = `HTTP ${response.status} - ${errorInfo.description}`; + throw new Error(errorInfo.code ? `${msg} (code: ${errorInfo.code})` : msg); } // Otherwise just throw whatever we got - throw new Error( - `HTTP ${response.status} - ${JSON.stringify(errorInfo)}` - ) + throw new Error(`HTTP ${response.status} - ${JSON.stringify(errorInfo)}`); } const parsedBody = await response.arrayBuffer() const payloadWriter = new Utils.Writer() - payloadWriter.write( - Utils.toArray(response.headers.get('x-bsv-auth-request-id'), 'base64') - ) + payloadWriter.write(Utils.toArray(response.headers.get('x-bsv-auth-request-id'), 'base64')) payloadWriter.writeVarIntNum(response.status) // Filter out headers the server signed: // - Custom headers prefixed with x-bsv are included, except auth // - x-bsv-auth headers are not allowed // - authorization header is signed by the server - const includedHeaders: Array<[string, string]> = [] + const includedHeaders: [string, string][] = [] // Collect headers into a raw array for sorting - const headersArray: Array<[string, string]> = [] + const headersArray: [string, string][] = [] response.headers.forEach((value, key) => { const lowerKey = key.toLowerCase() if (lowerKey.startsWith('x-bsv-') || lowerKey === 'authorization') { @@ -227,7 +176,7 @@ export class SimplifiedFetchTransport implements Transport { } // Handle body - if (parsedBody !== null && parsedBody !== undefined) { // ✅ Explicitly check for null/undefined + if (parsedBody) { const bodyAsArray = Array.from(new Uint8Array(parsedBody)) payloadWriter.writeVarIntNum(bodyAsArray.length) payloadWriter.write(bodyAsArray) @@ -237,27 +186,18 @@ export class SimplifiedFetchTransport implements Transport { // Build the correct AuthMessage for the response const responseMessage: AuthMessage = { - version: response.headers.get('x-bsv-auth-version') ?? '', // Ensure string - messageType: - response.headers.get('x-bsv-auth-message-type') === - 'certificateRequest' - ? 'certificateRequest' - : 'general', - identityKey: response.headers.get('x-bsv-auth-identity-key') ?? '', - nonce: response.headers.get('x-bsv-auth-nonce') ?? undefined, - yourNonce: response.headers.get('x-bsv-auth-your-nonce') ?? undefined, - requestedCertificates: JSON.parse( - response.headers.get('x-bsv-auth-requested-certificates') ?? '[]' - ) as RequestedCertificateSet, + version: response.headers.get('x-bsv-auth-version'), + messageType: response.headers.get('x-bsv-auth-message-type') === 'certificateRequest' ? 'certificateRequest' : 'general', + identityKey: response.headers.get('x-bsv-auth-identity-key'), + nonce: response.headers.get('x-bsv-auth-nonce'), + yourNonce: response.headers.get('x-bsv-auth-your-nonce'), + requestedCertificates: JSON.parse(response.headers.get('x-bsv-auth-requested-certificates')) as RequestedCertificateSet, payload: payloadWriter.toArray(), - signature: Utils.toArray( - response.headers.get('x-bsv-auth-signature') ?? '', - 'hex' - ) + signature: Utils.toArray(response.headers.get('x-bsv-auth-signature'), 'hex'), } // If the server didn't provide the correct authentication headers, throw an error - if (responseMessage.version === undefined || responseMessage.version === null || responseMessage.version.trim() === '') { + if (!responseMessage.version) { throw new Error('HTTP server failed to authenticate') } @@ -267,41 +207,30 @@ export class SimplifiedFetchTransport implements Transport { } /** - * Registers a callback to handle incoming messages. + * Registers a callback to handle incoming messages. * This must be called before sending any messages to ensure responses can be processed. - * + * * @param callback - A function to invoke when an incoming AuthMessage is received. * @returns A promise that resolves once the callback is set. */ - async onData( - callback: (message: AuthMessage) => Promise - ): Promise { - this.onDataCallback = (m) => { // ✅ Removed `async` here - void (async () => { // ✅ Wraps the async function inside a void IIFE - try { - await callback(m) // ✅ Ensures proper `await` - } catch (error) { - console.error( - 'Error handling AuthMessage:', - error instanceof Error ? error.message : String(error) - ) - } - })() + async onData(callback: (message: AuthMessage) => Promise): Promise { + this.onDataCallback = (m) => { + callback(m) } } /** * Deserializes a request payload from a byte array into an HTTP request-like structure. - * + * * @param payload - The serialized payload to deserialize. * @returns An object representing the deserialized request, including the method, * URL postfix (path and query string), headers, body, and request ID. */ deserializeRequestPayload(payload: number[]): { - method: string - urlPostfix: string - headers: Record - body: number[] + method: string, + urlPostfix: string, + headers: Record, + body: number[], requestId: string } { // Create a reader @@ -361,4 +290,4 @@ export class SimplifiedFetchTransport implements Transport { requestId } } -} +} \ No newline at end of file