diff --git a/lib/shared/farcaster/farcaster-api.js b/lib/shared/farcaster/farcaster-api.js --- a/lib/shared/farcaster/farcaster-api.js +++ b/lib/shared/farcaster/farcaster-api.js @@ -20,7 +20,9 @@ farcasterDCUserValidator, type FarcasterDCUser, } from './farcaster-user-types.js'; +import { logTypes, useDebugLogs } from '../../components/debug-logs-context.js'; import { useTunnelbroker } from '../../tunnelbroker/tunnelbroker-context.js'; +import { getMessageForException } from '../../utils/errors.js'; import { tShapeInexact, assertWithValidator, @@ -55,23 +57,36 @@ input: SendFarcasterTextMessageInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: SendFarcasterTextMessageInput) => { - const response = await sendFarcasterRequest({ - apiVersion: 'fc', - endpoint: 'message', - method: { type: 'PUT' }, - payload: JSON.stringify(input), - }); - - const parsedResult = JSON.parse(response); - const result: SendFarcasterMessageResult = assertWithValidator( - parsedResult, - sendFarcasterMessageResultValidator, - ); - return result; + try { + const response = await sendFarcasterRequest({ + apiVersion: 'fc', + endpoint: 'message', + method: { type: 'PUT' }, + payload: JSON.stringify(input), + }); + + const parsedResult = JSON.parse(response); + const result: SendFarcasterMessageResult = assertWithValidator( + parsedResult, + sendFarcasterMessageResultValidator, + ); + return result; + } catch (error) { + addLog( + 'Farcaster API: Failed to send text message', + JSON.stringify({ + input, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -114,36 +129,52 @@ input: FetchFarcasterMessageInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: FetchFarcasterMessageInput) => { - const { conversationId, cursor, limit, messageId } = input; - const params: { [string]: string } = { - conversationId, - }; - if (cursor !== undefined && cursor !== null) { - params.cursor = cursor; - } - if (limit !== undefined && limit !== null) { - params.limit = limit.toString(); - } - if (messageId !== undefined && messageId !== null) { - params.messageId = messageId; + try { + const { conversationId, cursor, limit, messageId } = input; + const params: { [string]: string } = { + conversationId, + }; + if (cursor !== undefined && cursor !== null) { + params.cursor = cursor; + } + if (limit !== undefined && limit !== null) { + params.limit = limit.toString(); + } + if (messageId !== undefined && messageId !== null) { + params.messageId = messageId; + } + + const response = await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-conversation-messages', + method: { type: 'GET' }, + payload: new URLSearchParams(params).toString(), + }); + const parsedResult = JSON.parse(response); + const result: FetchFarcasterMessageResult = assertWithValidator( + parsedResult, + fetchFarcasterMessageResultValidator, + ); + return result; + } catch (error) { + addLog( + 'Farcaster API: Failed to fetch messages', + JSON.stringify({ + conversationId: input.conversationId, + cursor: input.cursor, + limit: input.limit, + messageId: input.messageId, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; } - - const response = await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-conversation-messages', - method: { type: 'GET' }, - payload: new URLSearchParams(params).toString(), - }); - const parsedResult = JSON.parse(response); - const result: FetchFarcasterMessageResult = assertWithValidator( - parsedResult, - fetchFarcasterMessageResultValidator, - ); - return result; }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -193,38 +224,54 @@ input: FetchFarcasterInboxInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: FetchFarcasterInboxInput) => { - const { limit, category, cursor, filter } = input; - - const params: { [string]: string } = {}; - if (cursor !== undefined && cursor !== null) { - params.cursor = cursor; - } - if (limit !== undefined && limit !== null) { - params.limit = limit.toString(); - } - if (category !== undefined && category !== null) { - params.category = category; - } - if (filter !== undefined && filter !== null) { - params.filter = filter; + try { + const { limit, category, cursor, filter } = input; + + const params: { [string]: string } = {}; + if (cursor !== undefined && cursor !== null) { + params.cursor = cursor; + } + if (limit !== undefined && limit !== null) { + params.limit = limit.toString(); + } + if (category !== undefined && category !== null) { + params.category = category; + } + if (filter !== undefined && filter !== null) { + params.filter = filter; + } + + const response = await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-inbox', + method: { type: 'GET' }, + payload: new URLSearchParams(params).toString(), + }); + const parsedResult = JSON.parse(response); + const result: FetchFarcasterInboxResult = assertWithValidator( + parsedResult, + fetchFarcasterInboxResultValidator, + ); + return result; + } catch (error) { + addLog( + 'Farcaster API: Failed to fetch inbox', + JSON.stringify({ + limit: input.limit, + category: input.category, + cursor: input.cursor, + filter: input.filter, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; } - - const response = await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-inbox', - method: { type: 'GET' }, - payload: new URLSearchParams(params).toString(), - }); - const parsedResult = JSON.parse(response); - const result: FetchFarcasterInboxResult = assertWithValidator( - parsedResult, - fetchFarcasterInboxResultValidator, - ); - return result; }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -254,27 +301,40 @@ input: FetchFarcasterConversationInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: FetchFarcasterConversationInput) => { - const { conversationId } = input; - const params = new URLSearchParams({ - conversationId, - }); - - const response = await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-conversation', - method: { type: 'GET' }, - payload: params.toString(), - }); - const parsedResult = JSON.parse(response); - const result: FetchFarcasterConversationResult = assertWithValidator( - parsedResult, - fetchFarcasterConversationResultValidator, - ); - return result; + try { + const { conversationId } = input; + const params = new URLSearchParams({ + conversationId, + }); + + const response = await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-conversation', + method: { type: 'GET' }, + payload: params.toString(), + }); + const parsedResult = JSON.parse(response); + const result: FetchFarcasterConversationResult = assertWithValidator( + parsedResult, + fetchFarcasterConversationResultValidator, + ); + return result; + } catch (error) { + addLog( + 'Farcaster API: Failed to fetch conversation', + JSON.stringify({ + conversationId: input.conversationId, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -304,28 +364,41 @@ input: FetchFarcasterConversationInvitesInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: FetchFarcasterConversationInvitesInput) => { - const { conversationId } = input; - const params = new URLSearchParams({ - conversationId, - }); - - const response = await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-group-invites', - method: { type: 'GET' }, - payload: params.toString(), - }); - const parsedResult = JSON.parse(response); - const result: FetchFarcasterConversationInvitesResult = - assertWithValidator( - parsedResult, - fetchFarcasterConversationInvitesResultValidator, + try { + const { conversationId } = input; + const params = new URLSearchParams({ + conversationId, + }); + + const response = await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-group-invites', + method: { type: 'GET' }, + payload: params.toString(), + }); + const parsedResult = JSON.parse(response); + const result: FetchFarcasterConversationInvitesResult = + assertWithValidator( + parsedResult, + fetchFarcasterConversationInvitesResultValidator, + ); + return result; + } catch (error) { + addLog( + 'Farcaster API: Failed to fetch conversation invites', + JSON.stringify({ + conversationId: input.conversationId, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), ); - return result; + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -339,16 +412,31 @@ input: UpdateFarcasterGroupNameAndDescriptionInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: UpdateFarcasterGroupNameAndDescriptionInput) => { - await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-group', - method: { type: 'POST' }, - payload: JSON.stringify(input), - }); + try { + await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-group', + method: { type: 'POST' }, + payload: JSON.stringify(input), + }); + } catch (error) { + addLog( + 'Farcaster API: Failed to update group name and description', + JSON.stringify({ + conversationId: input.conversationId, + name: input.name, + hasDescription: !!input.description, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -361,16 +449,30 @@ input: UpdateFarcasterSubscriptionInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: UpdateFarcasterSubscriptionInput) => { - await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-conversation-notifications', - method: { type: 'POST' }, - payload: JSON.stringify(input), - }); + try { + await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-conversation-notifications', + method: { type: 'POST' }, + payload: JSON.stringify(input), + }); + } catch (error) { + addLog( + 'Farcaster API: Failed to update subscription', + JSON.stringify({ + conversationId: input.conversationId, + muted: input.muted, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -382,23 +484,36 @@ input: StreamFarcasterDirectCastReadInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: StreamFarcasterDirectCastReadInput) => { - const { conversationId } = input; - const streamMessage = { - messageType: 'direct-cast-read', - payload: { conversationId }, - data: conversationId, - }; - - await sendFarcasterRequest({ - apiVersion: '', - endpoint: 'direct-cast-read', - method: { type: 'STREAM' }, - payload: JSON.stringify(streamMessage), - }); + try { + const { conversationId } = input; + const streamMessage = { + messageType: 'direct-cast-read', + payload: { conversationId }, + data: conversationId, + }; + + await sendFarcasterRequest({ + apiVersion: '', + endpoint: 'direct-cast-read', + method: { type: 'STREAM' }, + payload: JSON.stringify(streamMessage), + }); + } catch (error) { + addLog( + 'Farcaster API: Failed to stream direct cast read', + JSON.stringify({ + conversationId: input.conversationId, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -410,16 +525,29 @@ input: MarkFarcasterDirectCastUnreadInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: MarkFarcasterDirectCastUnreadInput) => { - await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-manually-mark-unread', - method: { type: 'PUT' }, - payload: JSON.stringify(input), - }); + try { + await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-manually-mark-unread', + method: { type: 'PUT' }, + payload: JSON.stringify(input), + }); + } catch (error) { + addLog( + 'Farcaster API: Failed to mark direct cast unread', + JSON.stringify({ + conversationId: input.conversationId, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -451,23 +579,38 @@ input: CreateFarcasterGroupInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: CreateFarcasterGroupInput) => { - const response = await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-group', - method: { type: 'PUT' }, - payload: JSON.stringify(input), - }); - - const parsedResult = JSON.parse(response); - const result: CreateFarcasterGroupResult = assertWithValidator( - parsedResult, - createFarcasterGroupResultValidator, - ); - return result; + try { + const response = await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-group', + method: { type: 'PUT' }, + payload: JSON.stringify(input), + }); + + const parsedResult = JSON.parse(response); + const result: CreateFarcasterGroupResult = assertWithValidator( + parsedResult, + createFarcasterGroupResultValidator, + ); + return result; + } catch (error) { + addLog( + 'Farcaster API: Failed to create group', + JSON.stringify({ + participantCount: input.participantFids.length, + name: input.name, + hasDescription: !!input.description, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -510,38 +653,54 @@ input: GetFarcasterDirectCastUsersInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: GetFarcasterDirectCastUsersInput) => { - const { q, limit, cursor, excludeFIDs } = input; - let query = { q }; - if (limit) { - query = { ...query, limit }; - } - if (excludeFIDs) { - query = { ...query, excludeFIDs }; - } - if (cursor) { - query = { ...query, cursor }; - } else { - // vNext doesn't work when cursor is set - query = { ...query, vNext: 'true' }; + try { + const { q, limit, cursor, excludeFIDs } = input; + let query = { q }; + if (limit) { + query = { ...query, limit }; + } + if (excludeFIDs) { + query = { ...query, excludeFIDs }; + } + if (cursor) { + query = { ...query, cursor }; + } else { + // vNext doesn't work when cursor is set + query = { ...query, vNext: 'true' }; + } + + const params = new URLSearchParams(query); + const response = await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-users', + method: { type: 'GET' }, + payload: params.toString(), + }); + const parsedResult = JSON.parse(response); + const result: GetFarcasterDirectCastUsersResult = assertWithValidator( + parsedResult, + getFarcasterDirectCastUsersResultValidator, + ); + return result; + } catch (error) { + addLog( + 'Farcaster API: Failed to get direct cast users', + JSON.stringify({ + query: input.q, + limit: input.limit, + cursor: input.cursor, + excludeFIDsCount: input.excludeFIDs?.length, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; } - - const params = new URLSearchParams(query); - const response = await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-users', - method: { type: 'GET' }, - payload: params.toString(), - }); - const parsedResult = JSON.parse(response); - const result: GetFarcasterDirectCastUsersResult = assertWithValidator( - parsedResult, - getFarcasterDirectCastUsersResultValidator, - ); - return result; }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -581,16 +740,30 @@ input: ModifyFarcasterMembershipInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: ModifyFarcasterMembershipInput) => { - await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-group-membership', - method: { type: 'POST' }, - payload: JSON.stringify(input), - }); + try { + await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-group-membership', + method: { type: 'POST' }, + payload: JSON.stringify(input), + }); + } catch (error) { + addLog( + 'Farcaster API: Failed to modify group membership', + JSON.stringify({ + conversationId: input.conversationId, + action: input.action, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -605,19 +778,35 @@ input: SendReactionInput, ) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: SendReactionInput) => { - const { action, ...payload } = input; - const method = - action === 'remove_reaction' ? { type: 'DELETE' } : { type: 'PUT' }; - await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-message-reaction', - method, - payload: JSON.stringify(payload), - }); + try { + const { action, ...payload } = input; + const method = + action === 'remove_reaction' ? { type: 'DELETE' } : { type: 'PUT' }; + await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-message-reaction', + method, + payload: JSON.stringify(payload), + }); + } catch (error) { + addLog( + 'Farcaster API: Failed to send reaction', + JSON.stringify({ + conversationId: input.conversationId, + messageId: input.messageId, + reaction: input.reaction, + action: input.action, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); } @@ -627,16 +816,29 @@ function useAcceptInvite(): (input: AcceptInviteInput) => Promise { const { sendFarcasterRequest } = useTunnelbroker(); + const { addLog } = useDebugLogs(); return React.useCallback( async (input: AcceptInviteInput) => { - await sendFarcasterRequest({ - apiVersion: 'v2', - endpoint: 'direct-cast-accept-group-invite', - method: { type: 'POST' }, - payload: JSON.stringify(input), - }); + try { + await sendFarcasterRequest({ + apiVersion: 'v2', + endpoint: 'direct-cast-accept-group-invite', + method: { type: 'POST' }, + payload: JSON.stringify(input), + }); + } catch (error) { + addLog( + 'Farcaster API: Failed to accept group invite', + JSON.stringify({ + conversationId: input.conversationId, + error: getMessageForException(error), + }), + new Set([logTypes.FARCASTER, logTypes.ERROR]), + ); + throw error; + } }, - [sendFarcasterRequest], + [addLog, sendFarcasterRequest], ); }