Skip to content

Commit

Permalink
Callback should not be awaited!
Browse files Browse the repository at this point in the history
 Reverted linter fixes
  • Loading branch information
BraydenLangley committed Feb 8, 2025
1 parent 8220c19 commit 2c65043
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 145 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@
"project": "tsconfig.eslint.json",
"ignore": [
"dist",
"src/transaction/Transaction.ts"
"src/transaction/Transaction.ts",
"src/auth/transports/SimplifiedFetchTransport.ts"
]
}
}
217 changes: 73 additions & 144 deletions src/auth/transports/SimplifiedFetchTransport.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<void> {
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)
Expand All @@ -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<string, string>
body?: string | Record<string, unknown> | 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,
Expand All @@ -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') {
Expand Down Expand Up @@ -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)
Expand All @@ -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')
}

Expand All @@ -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<void>
): Promise<void> {
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<void>): Promise<void> {
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<string, string>
body: number[]
method: string,
urlPostfix: string,
headers: Record<string, string>,
body: number[],
requestId: string
} {
// Create a reader
Expand Down Expand Up @@ -361,4 +290,4 @@ export class SimplifiedFetchTransport implements Transport {
requestId
}
}
}
}

0 comments on commit 2c65043

Please sign in to comment.