Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F32175330
D15189.1765066972.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
14 KB
Referenced Files
None
Subscribers
None
D15189.1765066972.diff
View Options
diff --git a/lib/hooks/input-state-container-hooks.js b/lib/hooks/input-state-container-hooks.js
--- a/lib/hooks/input-state-container-hooks.js
+++ b/lib/hooks/input-state-container-hooks.js
@@ -10,6 +10,7 @@
import { useMediaMetadataReassignment } from './upload-hooks.js';
import { useProcessBlobHolders } from '../actions/holder-actions.js';
import { useSendComposableDMOperation } from '../shared/dm-ops/process-dm-ops.js';
+import { useSendFarcasterTextMessage } from '../shared/farcaster/farcaster-api.js';
import { useMessageCreationSideEffectsFunc } from '../shared/message-utils.js';
import { threadSpecs } from '../shared/threads/thread-specs.js';
import { messageTypes } from '../types/message-types-enum.js';
@@ -32,6 +33,7 @@
const sendComposableDMOperation = useSendComposableDMOperation();
const sideEffectsFunction =
useMessageCreationSideEffectsFunc<RawTextMessageInfo>(messageTypes.TEXT);
+ const sendFarcasterTextMessage = useSendFarcasterTextMessage();
return React.useCallback(
(
@@ -51,9 +53,15 @@
sendKeyserverTextMessage,
sendComposableDMOperation,
sideEffectsFunction,
+ sendFarcasterTextMessage,
},
),
- [sendComposableDMOperation, sendKeyserverTextMessage, sideEffectsFunction],
+ [
+ sendComposableDMOperation,
+ sendFarcasterTextMessage,
+ sendKeyserverTextMessage,
+ sideEffectsFunction,
+ ],
);
}
diff --git a/lib/shared/farcaster/farcaster-api.js b/lib/shared/farcaster/farcaster-api.js
new file mode 100644
--- /dev/null
+++ b/lib/shared/farcaster/farcaster-api.js
@@ -0,0 +1,85 @@
+// @flow
+
+import * as React from 'react';
+
+import type { FarcasterMessage } from './farcaster-message-types.js';
+import { useTunnelbroker } from '../../tunnelbroker/tunnelbroker-context.js';
+
+export type SendFarcasterTextMessageInput =
+ | {
+ +groupId: string,
+ +message: string,
+ }
+ | { +recipientFid: string, +message: string };
+export type SendFarcasterMessageResult = {
+ +result: {
+ +messageId: string,
+ },
+};
+function useSendFarcasterTextMessage(): (
+ input: SendFarcasterTextMessageInput,
+) => Promise<SendFarcasterMessageResult> {
+ const { sendFarcasterRequest } = useTunnelbroker();
+ return React.useCallback(
+ async (input: SendFarcasterTextMessageInput) => {
+ const response = await sendFarcasterRequest({
+ apiVersion: 'fc',
+ endpoint: 'message',
+ method: { type: 'PUT' },
+ payload: JSON.stringify(input),
+ });
+
+ //FIXME: add validators to avoid crashing clients
+ const result: SendFarcasterMessageResult = JSON.parse(response);
+ return result;
+ },
+ [sendFarcasterRequest],
+ );
+}
+
+export type FetchFarcasterMessageInput = {
+ +conversationId: string,
+ +cursor?: number,
+ +limit?: number,
+ +messageId?: string,
+};
+export type FetchFarcasterMessageResult = {
+ +result: {
+ +messages: $ReadOnlyArray<FarcasterMessage>,
+ },
+};
+function useFetchFarcasterMessage(): (
+ input: FetchFarcasterMessageInput,
+) => Promise<FetchFarcasterMessageResult> {
+ const { sendFarcasterRequest } = useTunnelbroker();
+ return React.useCallback(
+ async (input: FetchFarcasterMessageInput) => {
+ const { conversationId, cursor, limit, messageId } = input;
+ const params = new URLSearchParams({
+ conversationId: conversationId.toString(),
+ });
+ if (cursor !== undefined && cursor !== null) {
+ params.set('cursor', cursor.toString());
+ }
+ if (limit !== undefined && limit !== null) {
+ params.set('limit', limit.toString());
+ }
+ if (messageId !== undefined && messageId !== null) {
+ params.set('messageId', messageId.toString());
+ }
+
+ const response = await sendFarcasterRequest({
+ apiVersion: 'v2',
+ endpoint: 'direct-cast-conversation-messages',
+ method: { type: 'GET' },
+ payload: params.toString(),
+ });
+ //FIXME: add validators to avoid crashing clients
+ const result: FetchFarcasterMessageResult = JSON.parse(response);
+ return result;
+ },
+ [sendFarcasterRequest],
+ );
+}
+
+export { useSendFarcasterTextMessage, useFetchFarcasterMessage };
diff --git a/lib/shared/farcaster/farcaster-message-types.js b/lib/shared/farcaster/farcaster-message-types.js
new file mode 100644
--- /dev/null
+++ b/lib/shared/farcaster/farcaster-message-types.js
@@ -0,0 +1,70 @@
+// @flow
+
+type FarcasterBaseMessage = {
+ +conversationId: string,
+ +hasMention: boolean,
+ +isDeleted: boolean,
+ +isPinned: boolean,
+ +viewerContext: ViewerContext,
+ +senderContext: UserContext,
+ +reactions: $ReadOnlyArray<ReactionType>,
+ +senderFid: number,
+ +serverTimestamp: number,
+ +message: string,
+ +messageId: string,
+};
+
+type ViewerContext = {
+ +focused: boolean,
+ +isLastReadMessage: boolean,
+ +reactions: $ReadOnlyArray<ReactionType>,
+};
+
+type UserContext = {
+ +displayName: string,
+ +fid: number,
+ +username: number,
+ +pfp: {
+ +url: string,
+ +verified: boolean,
+ },
+};
+
+//TODO: confirm this type
+type ReactionType = string;
+type MentionType = string;
+
+type FarcasterMedia = {
+ +height: number,
+ +width: number,
+ +mimeType: string,
+ +staticRaster: string,
+};
+
+export type FarcasterTextMessage = {
+ ...FarcasterBaseMessage,
+ +type: 'text',
+ +isProgrammatic: boolean,
+ +mentions: $ReadOnlyArray<MentionType>,
+};
+
+export type FarcasterMultimediaMessage = {
+ ...FarcasterBaseMessage,
+ +type: 'text',
+ +isProgrammatic: boolean,
+ +mentions: $ReadOnlyArray<MentionType>,
+ +metadata: {
+ +medias: $ReadOnlyArray<FarcasterMedia>,
+ },
+};
+
+export type FarcasterGroupMembershipAdditionMessage = {
+ ...FarcasterBaseMessage,
+ +type: 'group_membership_addition',
+ +actionTargetUserContext: UserContext,
+};
+
+export type FarcasterMessage =
+ | FarcasterTextMessage
+ | FarcasterMultimediaMessage
+ | FarcasterGroupMembershipAdditionMessage;
diff --git a/lib/shared/message-utils.js b/lib/shared/message-utils.js
--- a/lib/shared/message-utils.js
+++ b/lib/shared/message-utils.js
@@ -4,6 +4,7 @@
import _maxBy from 'lodash/fp/maxBy.js';
import * as React from 'react';
+import { useFetchFarcasterMessage } from './farcaster/farcaster-api.js';
import { codeBlockRegex, type ParserRules } from './markdown.js';
import type { CreationSideEffectsFunc } from './messages/message-spec.js';
import { messageSpecs } from './messages/message-specs.js';
@@ -603,6 +604,7 @@
const callFetchMessagesBeforeCursor = useFetchMessagesBeforeCursor();
const callFetchMostRecentMessages = useFetchMostRecentMessages();
const dispatchActionPromise = useDispatchActionPromise();
+ const fetchFarcasterMessages = useFetchFarcasterMessage();
React.useEffect(() => {
registerFetchKey(fetchMessagesBeforeCursorActionTypes);
@@ -630,6 +632,7 @@
keyserverFetchMessagesBeforeCursor: callFetchMessagesBeforeCursor,
keyserverFetchMostRecentMessages: callFetchMostRecentMessages,
dispatchActionPromise,
+ fetchFarcasterMessages,
},
);
},
@@ -637,6 +640,7 @@
callFetchMessagesBeforeCursor,
callFetchMostRecentMessages,
dispatchActionPromise,
+ fetchFarcasterMessages,
messageIDs?.length,
oldestMessageServerID,
threadID,
diff --git a/lib/shared/threads/protocols/farcaster-thread-protocol.js b/lib/shared/threads/protocols/farcaster-thread-protocol.js
--- a/lib/shared/threads/protocols/farcaster-thread-protocol.js
+++ b/lib/shared/threads/protocols/farcaster-thread-protocol.js
@@ -1,5 +1,9 @@
// @flow
+import invariant from 'invariant';
+import uuid from 'uuid';
+
+import { fetchMessagesBeforeCursorActionTypes } from '../../../actions/message-actions.js';
import type { RolePermissionBlobs } from '../../../permissions/keyserver-permissions.js';
import type { SetThreadUnreadStatusPayload } from '../../../types/activity-types.js';
import type {
@@ -7,9 +11,13 @@
DeleteEntryResult,
SaveEntryResult,
} from '../../../types/entry-types.js';
+import type { Media } from '../../../types/media-types.js';
+import { messageTypes } from '../../../types/message-types-enum.js';
import {
type SendMessagePayload,
type SendMultimediaMessagePayload,
+ messageTruncationStatus,
+ type RawMessageInfo,
} from '../../../types/message-types.js';
import type {
MemberInfoSansPermissions,
@@ -24,11 +32,44 @@
} from '../../../types/thread-types.js';
import { pendingThickSidebarURLPrefix } from '../../../utils/validation-utils.js';
import { messageNotifyTypes } from '../../messages/message-spec.js';
-import type { ThreadProtocol } from '../thread-spec.js';
+import type {
+ ThreadProtocol,
+ ProtocolSendTextMessageInput,
+ SendTextMessageUtils,
+ ProtocolFetchMessageInput,
+ FetchMessageUtils,
+} from '../thread-spec.js';
+
+function getUserID(fid: number): string {
+ //TODO: this should be Comm's userID, blocked on ENG-10946
+ return fid.toString();
+}
const farcasterThreadProtocol: ThreadProtocol<MemberInfoSansPermissions> = {
- sendTextMessage: async (): Promise<SendMessagePayload> => {
- throw new Error('sendTextMessage method is not yet implemented');
+ sendTextMessage: async (
+ message: ProtocolSendTextMessageInput,
+ utils: SendTextMessageUtils,
+ ): Promise<SendMessagePayload> => {
+ const { sendFarcasterTextMessage } = utils;
+ const { messageInfo } = message;
+ const { localID } = messageInfo;
+ invariant(
+ localID !== null && localID !== undefined,
+ 'localID should be set',
+ );
+
+ const time = Date.now();
+ const result = await sendFarcasterTextMessage({
+ groupId: 'efa192faf954b2f8',
+ message: messageInfo.text,
+ });
+
+ return {
+ localID,
+ serverID: result.result.messageId,
+ threadID: messageInfo.threadID,
+ time: time,
+ };
},
sendMultimediaMessage: async (): Promise<SendMultimediaMessagePayload> => {
@@ -85,8 +126,96 @@
throw new Error('convertClientDBThreadInfo method is not yet implemented');
},
- fetchMessages: async (): Promise<void> => {
- throw new Error('fetchMessages method is not yet implemented');
+ fetchMessages: async (
+ input: ProtocolFetchMessageInput,
+ utils: FetchMessageUtils,
+ ): Promise<void> => {
+ console.log('fetchMessages method is not yet implemented');
+ const { fetchFarcasterMessages } = utils;
+ //TODO: currentNumberOfFetchedMessages should be offset,
+ // but this is not supported by Farcaster
+ const { threadID, numMessagesToFetch, currentNumberOfFetchedMessages } =
+ input;
+
+ //TODO: use threadID
+ let payload = {
+ conversationId: 'efa192faf954b2f8',
+ };
+ if (currentNumberOfFetchedMessages) {
+ payload = {
+ ...payload,
+ limit: numMessagesToFetch,
+ };
+ }
+
+ const promise = (async () => {
+ const {
+ result: { messages },
+ } = await fetchFarcasterMessages(payload);
+
+ const rawMessageInfos: $ReadOnlyArray<RawMessageInfo> = messages
+ .map(msg => {
+ if (msg.type === 'group_membership_addition') {
+ return ({
+ id: msg.messageId,
+ type: messageTypes.ADD_MEMBERS,
+ threadID,
+ creatorID: getUserID(msg.senderFid),
+ time: parseInt(msg.serverTimestamp, 10),
+ addedUserIDs: [getUserID(msg.actionTargetUserContext.fid)],
+ }: RawMessageInfo);
+ }
+
+ if (msg.type === 'text' && !!msg?.metadata?.medias) {
+ return ({
+ id: msg.messageId,
+ type: messageTypes.MULTIMEDIA,
+ threadID,
+ creatorID: getUserID(msg.senderFid),
+ time: parseInt(msg.serverTimestamp, 10),
+ media: msg?.metadata?.medias.map(
+ med =>
+ ({
+ id: uuid.v4(),
+ uri: med.staticRaster,
+ type: 'photo',
+ thumbHash: null,
+ dimensions: {
+ height: med.height,
+ width: med.width,
+ },
+ }: Media),
+ ),
+ }: RawMessageInfo);
+ }
+
+ if (msg.type === 'text') {
+ return ({
+ id: msg.messageId,
+ type: messageTypes.TEXT,
+ threadID,
+ creatorID: getUserID(msg.senderFid),
+ time: parseInt(msg.serverTimestamp, 10),
+ text: msg.message,
+ }: RawMessageInfo);
+ }
+
+ return null;
+ })
+ .filter(Boolean);
+
+ return {
+ threadID,
+ //TODO: confirm this
+ truncationStatus: messageTruncationStatus.EXHAUSTIVE,
+ rawMessageInfos,
+ };
+ })();
+ void utils.dispatchActionPromise(
+ fetchMessagesBeforeCursorActionTypes,
+ promise,
+ );
+ await promise;
},
createPendingThread: (): RawThreadInfo => {
diff --git a/lib/shared/threads/thread-spec.js b/lib/shared/threads/thread-spec.js
--- a/lib/shared/threads/thread-spec.js
+++ b/lib/shared/threads/thread-spec.js
@@ -84,6 +84,12 @@
OutboundComposableDMOperationSpecification,
OutboundDMOperationSpecification,
} from '../dm-ops/dm-op-types.js';
+import type {
+ FetchFarcasterMessageInput,
+ FetchFarcasterMessageResult,
+ SendFarcasterMessageResult,
+ SendFarcasterTextMessageInput,
+} from '../farcaster/farcaster-api.js';
import type { FetchThickMessagesType } from '../message-utils.js';
import type {
CreationSideEffectsFunc,
@@ -109,6 +115,9 @@
+sendKeyserverTextMessage: SendTextMessageInput => Promise<SendMessageResult>,
+sendComposableDMOperation: OutboundComposableDMOperationSpecification => Promise<ProcessOutboundP2PMessagesResult>,
+sideEffectsFunction: CreationSideEffectsFunc<RawTextMessageInfo>,
+ +sendFarcasterTextMessage: (
+ input: SendFarcasterTextMessageInput,
+ ) => Promise<SendFarcasterMessageResult>,
};
export type ProtocolSendMultimediaMessageInput = {
@@ -242,6 +251,9 @@
+keyserverFetchMessagesBeforeCursor: FetchMessagesBeforeCursorInput => Promise<FetchMessageInfosPayload>,
+keyserverFetchMostRecentMessages: FetchMostRecentMessagesInput => Promise<FetchMessageInfosPayload>,
+dispatchActionPromise: DispatchActionPromise,
+ +fetchFarcasterMessages: (
+ input: FetchFarcasterMessageInput,
+ ) => Promise<FetchFarcasterMessageResult>,
};
export type ProtocolCreatePendingThreadInput = {
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Dec 7, 12:22 AM (2 h, 5 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5841255
Default Alt Text
D15189.1765066972.diff (14 KB)
Attached To
Mode
D15189: [lib] Facaster MVP
Attached
Detach File
Event Timeline
Log In to Comment