@towns-protocol/sdk
Version:
For more details, visit the following resources:
988 lines • 41.1 kB
JavaScript
import { MembershipOp, } from '@towns-protocol/proto';
import { EventStatus, RiverTimelineEvent, MessageType, Membership, getIsMentioned, getReactionParentId, getReplyParentId, getThreadParentId, } from './timelineTypes';
import { checkNever, isDefined, logNever } from '../../check';
import { isLocalEvent, isRemoteEvent, } from '../../types';
import { streamIdAsString, streamIdFromBytes, userIdFromAddress } from '../../id';
import { bin_toHexString, dlogger } from '@towns-protocol/dlog';
import { getSpaceReviewEventDataBin } from '@towns-protocol/web3';
const logger = dlogger('csb:timeline');
export function toEvent(timelineEvent, userId) {
const eventId = timelineEvent.hashStr;
const senderId = getSenderId(timelineEvent);
const sender = {
id: senderId,
};
const { content, error } = toTownsContent(timelineEvent);
const isSender = sender.id === userId;
const fbc = `${content?.kind ?? '??'} ${getFallbackContent(sender.id, content, error)}`;
function extractSessionId(event) {
const payload = event.remoteEvent?.event.payload;
if (!payload ||
payload.case !== 'channelPayload' ||
payload.value.content.case !== 'message') {
return undefined;
}
return payload.value.content.value.sessionIdBytes.length > 0
? bin_toHexString(payload.value.content.value.sessionIdBytes)
: payload.value.content.value.sessionId;
}
const sessionId = extractSessionId(timelineEvent);
return {
eventId: eventId,
localEventId: timelineEvent.localEvent?.localId,
eventNum: timelineEvent.eventNum,
latestEventId: eventId,
latestEventNum: timelineEvent.eventNum,
status: isSender ? getEventStatus(timelineEvent) : EventStatus.RECEIVED,
createdAtEpochMs: Number(timelineEvent.createdAtEpochMs),
updatedAtEpochMs: undefined,
content: content,
fallbackContent: fbc,
isEncrypting: eventId.startsWith('~'),
isLocalPending: timelineEvent.remoteEvent === undefined,
isSendFailed: timelineEvent.localEvent?.status === 'failed',
confirmedEventNum: timelineEvent.confirmedEventNum,
confirmedInBlockNum: timelineEvent.miniblockNum,
threadParentId: getThreadParentId(content),
replyParentId: getReplyParentId(content),
reactionParentId: getReactionParentId(content),
isMentioned: getIsMentioned(content, userId),
isRedacted: false, // redacted is handled in use timeline store when the redaction event is received
sender,
sessionId,
};
}
function getSenderId(timelineEvent) {
const payload = timelineEvent.remoteEvent?.event.payload;
if (payload?.case === 'memberPayload' &&
payload?.value.content.case === 'memberBlockchainTransaction') {
return userIdFromAddress(payload.value.content.value.fromUserAddress);
}
return timelineEvent.creatorUserId;
}
function toTownsContent(timelineEvent) {
if (isLocalEvent(timelineEvent)) {
return toTownsContent_FromChannelMessage(timelineEvent.localEvent.channelMessage, 'local event', timelineEvent.hashStr);
}
else if (isRemoteEvent(timelineEvent)) {
return toTownsContent_fromParsedEvent(timelineEvent.hashStr, timelineEvent);
}
else {
return { error: 'unknown event content type' };
}
}
function validateEvent(eventId, message) {
let error;
if (!message.event.payload || !message.event.payload.value) {
error = { error: 'payloadless payload' };
return error;
}
if (!message.event.payload.case) {
error = { error: 'caseless payload' };
return error;
}
if (!message.event.payload.value.content.case) {
error = { error: `${message.event.payload.case} - caseless payload content` };
return error;
}
const description = `${message.event.payload.case}::${message.event.payload.value.content.case} id: ${eventId}`;
return { description };
}
function toTownsContent_fromParsedEvent(eventId, timelineEvent) {
const message = timelineEvent.remoteEvent;
const { error, description } = validateEvent(eventId, message);
if (error) {
return { error };
}
if (!description) {
return { error: 'no description' };
}
switch (message.event.payload.case) {
case 'userPayload':
return toTownsContent_UserPayload(timelineEvent, message, message.event.payload.value, description);
case 'channelPayload':
return toTownsContent_ChannelPayload(eventId, timelineEvent, message.event.payload.value, description);
case 'dmChannelPayload':
return toTownsContent_ChannelPayload(eventId, timelineEvent, message.event.payload.value, description);
case 'gdmChannelPayload':
return toTownsContent_ChannelPayload(eventId, timelineEvent, message.event.payload.value, description);
case 'spacePayload':
return toTownsContent_SpacePayload(eventId, message, message.event.payload.value, description);
case 'userMetadataPayload':
return {
error: `${description} userMetadataPayload not implemented`,
};
case 'userSettingsPayload':
return {
error: `${description} userSettingsPayload not implemented`,
};
case 'userInboxPayload':
return {
error: `${description} userInboxPayload not implemented`,
};
case 'miniblockHeader':
return toTownsContent_MiniblockHeader(eventId, message, message.event.payload.value, description);
case 'mediaPayload':
return {
error: `${description} mediaPayload not supported?`,
};
case 'memberPayload':
return toTownsContent_MemberPayload(timelineEvent, message, message.event.payload.value, description);
case 'metadataPayload':
return {
error: `${description} metadataPayload not supported for timeline events`,
};
case undefined:
return { error: `Undefined payload case: ${description}` };
default:
logNever(message.event.payload);
return { error: `Unknown payload case: ${description}` };
}
}
function toTownsContent_MiniblockHeader(eventId, message, value, _description) {
return {
content: {
kind: RiverTimelineEvent.MiniblockHeader,
miniblockNum: value.miniblockNum,
hasSnapshot: value.snapshot !== undefined || value.snapshotHash !== undefined,
},
};
}
function toTownsContent_MemberPayload(event, message, value, description) {
switch (value.content.case) {
case 'membership':
return {
content: {
kind: RiverTimelineEvent.StreamMembership,
userId: userIdFromAddress(value.content.value.userAddress),
initiatorId: userIdFromAddress(value.content.value.initiatorAddress),
membership: toMembership(value.content.value.op),
reason: value.content.value.reason,
},
};
case 'keySolicitation':
return {
content: {
kind: RiverTimelineEvent.KeySolicitation,
sessionIds: value.content.value.sessionIds,
deviceKey: value.content.value.deviceKey,
isNewDevice: value.content.value.isNewDevice,
},
};
case 'keyFulfillment':
return {
content: {
kind: RiverTimelineEvent.Fulfillment,
sessionIds: value.content.value.sessionIds,
deviceKey: value.content.value.deviceKey,
to: userIdFromAddress(value.content.value.userAddress),
from: message.creatorUserId,
},
};
case 'displayName':
return {
content: {
kind: RiverTimelineEvent.SpaceDisplayName,
userId: message.creatorUserId,
displayName: event.decryptedContent?.kind === 'text'
? event.decryptedContent.content
: value.content.value.ciphertext,
},
};
case 'username':
return {
content: {
kind: RiverTimelineEvent.SpaceUsername,
userId: message.creatorUserId,
username: event.decryptedContent?.kind === 'text'
? event.decryptedContent.content
: value.content.value.ciphertext,
},
};
case 'ensAddress':
return {
content: {
kind: RiverTimelineEvent.SpaceEnsAddress,
userId: message.creatorUserId,
ensAddress: value.content.value,
},
};
case 'nft':
return {
content: {
kind: RiverTimelineEvent.SpaceNft,
userId: message.creatorUserId,
contractAddress: bin_toHexString(value.content.value.contractAddress),
tokenId: bin_toHexString(value.content.value.tokenId),
chainId: value.content.value.chainId,
},
};
case 'pin':
return {
content: {
kind: RiverTimelineEvent.Pin,
userId: message.creatorUserId,
pinnedEventId: bin_toHexString(value.content.value.eventId),
},
};
case 'unpin':
return {
content: {
kind: RiverTimelineEvent.Unpin,
userId: message.creatorUserId,
unpinnedEventId: bin_toHexString(value.content.value.eventId),
},
};
case 'memberBlockchainTransaction': {
const fromUserAddress = value.content.value.fromUserAddress;
const transaction = value.content.value.transaction;
if (!transaction) {
return { error: `${description} no transaction` };
}
switch (transaction.content.case) {
case 'tip': {
if (!transaction.receipt?.transactionHash) {
return { error: `${description} no transactionHash` };
}
const tipContent = transaction.content.value;
if (!tipContent.event) {
return { error: `${description} no event in tip` };
}
return {
content: {
kind: RiverTimelineEvent.TipEvent,
transaction: transaction,
tip: tipContent,
transactionHash: bin_toHexString(transaction.receipt.transactionHash),
fromUserId: userIdFromAddress(fromUserAddress),
refEventId: bin_toHexString(tipContent.event.messageId),
toUserId: userIdFromAddress(tipContent.toUserAddress),
},
};
}
case 'spaceReview': {
if (!transaction.receipt) {
return { error: `${description} no receipt` };
}
const reviewContent = transaction.content.value;
if (!reviewContent.event) {
return { error: `${description} no event in space review` };
}
const { comment, rating } = getSpaceReviewEventDataBin(transaction.receipt.logs, reviewContent.event.user);
return {
content: {
kind: RiverTimelineEvent.SpaceReview,
action: reviewContent.action,
rating: rating,
comment: comment,
fromUserId: userIdFromAddress(fromUserAddress),
},
};
}
case 'tokenTransfer': {
const transferContent = transaction.content.value;
return {
content: {
kind: RiverTimelineEvent.TokenTransfer,
transaction: transaction,
transfer: transferContent,
fromUserId: userIdFromAddress(fromUserAddress),
createdAtEpochMs: event.createdAtEpochMs,
threadParentId: bin_toHexString(transferContent.messageId),
},
};
}
case undefined:
return {
content: {
kind: RiverTimelineEvent.MemberBlockchainTransaction,
transaction: value.content.value.transaction,
fromUserId: bin_toHexString(value.content.value.fromUserAddress),
},
};
default:
logNever(transaction.content);
return { error: `${description} unknown transaction content` };
}
}
case 'encryptionAlgorithm':
return {
error: `Encryption Algorithm not supported: ${description}`,
};
case undefined:
return { error: `Undefined payload case: ${description}` };
default:
logNever(value.content);
return {
error: `Unknown payload case: ${description}`,
};
}
}
function toTownsContent_UserPayload(event, message, value, description) {
switch (value.content.case) {
case 'inception': {
return {
content: {
kind: RiverTimelineEvent.Inception,
creatorId: message.creatorUserId,
type: message.event.payload.case,
},
};
}
case 'userMembership': {
const payload = value.content.value;
const streamId = streamIdFromBytes(payload.streamId);
return {
content: {
kind: RiverTimelineEvent.StreamMembership,
userId: '', // this is just the current user
initiatorId: '',
membership: toMembership(payload.op),
streamId: streamId,
},
};
}
case 'userMembershipAction': {
// these are admin actions where you can invite, join, or kick someone
const payload = value.content.value;
return {
content: {
kind: RiverTimelineEvent.StreamMembership,
userId: userIdFromAddress(payload.userId),
initiatorId: event.remoteEvent.creatorUserId,
membership: toMembership(payload.op),
},
};
}
case 'blockchainTransaction': {
return {
content: {
kind: RiverTimelineEvent.UserBlockchainTransaction,
transaction: value.content.value,
},
};
}
case 'receivedBlockchainTransaction': {
return {
content: {
kind: RiverTimelineEvent.UserReceivedBlockchainTransaction,
receivedTransaction: value.content.value,
},
};
}
case undefined: {
return { error: `Undefined payload case: ${description}` };
}
default: {
logNever(value.content);
return {
error: `Unknown payload case: ${description}`,
};
}
}
}
function toTownsContent_ChannelPayload(eventId, timelineEvent, value, description) {
const message = timelineEvent.remoteEvent;
switch (value.content.case) {
case 'inception': {
return {
content: {
kind: RiverTimelineEvent.Inception,
creatorId: message.creatorUserId,
type: message.event.payload.case,
},
};
}
case 'message': {
if (timelineEvent.decryptedContent?.kind === 'channelMessage') {
return toTownsContent_FromChannelMessage(timelineEvent.decryptedContent.content, description, eventId);
}
const payload = value.content.value;
return toTownsContent_ChannelPayload_Message(timelineEvent, payload, description);
}
case 'channelProperties': {
const payload = value.content.value;
return toTownsContent_ChannelPayload_ChannelProperties(timelineEvent, payload, description);
}
case 'redaction':
return {
content: {
kind: RiverTimelineEvent.RedactionActionEvent,
refEventId: bin_toHexString(value.content.value.eventId),
adminRedaction: true,
},
};
case undefined: {
return { error: `Undefined payload case: ${description}` };
}
default:
logNever(value.content);
return { error: `Unknown payload case: ${description}` };
}
}
function toTownsContent_FromChannelMessage(channelMessage, description, eventId) {
switch (channelMessage.payload?.case) {
case 'post':
return (toTownsContent_ChannelPayload_Message_Post(channelMessage.payload.value, eventId, undefined, description) ?? {
error: `${description} unknown message type`,
});
case 'reaction':
return {
content: {
kind: RiverTimelineEvent.Reaction,
reaction: channelMessage.payload.value.reaction,
targetEventId: channelMessage.payload.value.refEventId,
},
};
case 'redaction':
return {
content: {
kind: RiverTimelineEvent.RedactionActionEvent,
refEventId: channelMessage.payload.value.refEventId,
adminRedaction: false,
},
};
case 'edit': {
const newPost = channelMessage.payload.value.post;
if (!newPost) {
return { error: `${description} no post in edit` };
}
const newContent = toTownsContent_ChannelPayload_Message_Post(newPost, eventId, channelMessage.payload.value.refEventId, description);
return newContent ?? { error: `${description} no content in edit` };
}
case undefined:
return { error: `Undefined payload case: ${description}` };
default: {
logNever(channelMessage.payload);
return {
error: `Unknown payload case: ${description}`,
};
}
}
}
function toTownsContent_ChannelPayload_Message(timelineEvent, payload, _description) {
if (payload.refEventId) {
return {
content: {
kind: RiverTimelineEvent.ChannelMessageEncryptedWithRef,
refEventId: payload.refEventId,
},
};
}
return {
// if payload is an EncryptedData message, than it is encrypted content kind
content: {
kind: RiverTimelineEvent.ChannelMessageEncrypted,
error: timelineEvent.decryptedContentError,
},
};
}
function toTownsContent_ChannelPayload_ChannelProperties(timelineEvent, _payload, _description) {
if (timelineEvent.decryptedContent?.kind === 'channelProperties') {
return {
content: {
kind: RiverTimelineEvent.ChannelProperties,
properties: timelineEvent.decryptedContent.content,
},
};
}
// If the payload is encrypted, we display nothing.
return {
content: {
kind: RiverTimelineEvent.EncryptedChannelProperties,
error: timelineEvent.decryptedContentError,
},
};
}
function toTownsContent_ChannelPayload_Message_Post(value, eventId, editsEventId, description) {
switch (value.content.case) {
case 'text':
return {
content: {
kind: RiverTimelineEvent.ChannelMessage,
body: value.content.value.body,
threadId: value.threadId,
threadPreview: value.threadPreview,
replyId: value.replyId,
replyPreview: value.replyPreview,
mentions: value.content.value.mentions,
editsEventId: editsEventId,
content: {
msgType: MessageType.Text,
},
attachments: toAttachments(value.content.value.attachments, eventId),
},
};
case 'image':
return {
content: {
kind: RiverTimelineEvent.ChannelMessage,
body: value.content.value.title,
threadId: value.threadId,
threadPreview: value.threadPreview,
mentions: [],
editsEventId: editsEventId,
content: {
msgType: MessageType.Image,
info: value.content.value.info,
thumbnail: value.content.value.thumbnail,
},
},
};
case 'gm':
return {
content: {
kind: RiverTimelineEvent.ChannelMessage,
body: value.content.value.typeUrl,
threadId: value.threadId,
threadPreview: value.threadPreview,
mentions: [],
editsEventId: editsEventId,
content: {
msgType: MessageType.GM,
data: value.content.value.value,
},
},
};
case undefined:
return { error: `Undefined payload case: ${description}` };
default:
logNever(value.content);
return { error: `Unknown payload case: ${description}` };
}
}
function toTownsContent_SpacePayload(eventId, message, value, description) {
switch (value.content.case) {
case 'inception': {
return {
content: {
kind: RiverTimelineEvent.Inception,
creatorId: message.creatorUserId,
type: message.event.payload.case,
},
};
}
case 'channel': {
const payload = value.content.value;
const childId = streamIdAsString(payload.channelId);
return {
content: {
kind: RiverTimelineEvent.ChannelCreate,
creatorId: message.creatorUserId,
channelId: childId,
channelOp: payload.op,
channelSettings: payload.settings,
},
};
}
case 'updateChannelAutojoin': {
const payload = value.content.value;
const channelId = streamIdAsString(payload.channelId);
return {
content: {
kind: RiverTimelineEvent.SpaceUpdateAutojoin,
autojoin: payload.autojoin,
channelId: channelId,
},
};
}
case 'updateChannelHideUserJoinLeaveEvents': {
const payload = value.content.value;
const channelId = streamIdAsString(payload.channelId);
return {
content: {
kind: RiverTimelineEvent.SpaceUpdateHideUserJoinLeaves,
hideUserJoinLeaves: payload.hideUserJoinLeaveEvents,
channelId: channelId,
},
};
}
case 'spaceImage': {
return {
content: {
kind: RiverTimelineEvent.SpaceImage,
},
};
}
case undefined:
return { error: `Undefined payload case: ${description}` };
default:
logNever(value.content);
return { error: `Unknown payload case: ${description}` };
}
}
function getEventStatus(timelineEvent) {
if (timelineEvent.remoteEvent) {
return EventStatus.SENT;
}
else if (timelineEvent.localEvent && timelineEvent.hashStr.startsWith('~')) {
return EventStatus.ENCRYPTING;
}
else if (timelineEvent.localEvent) {
switch (timelineEvent.localEvent.status) {
case 'failed':
return EventStatus.NOT_SENT;
case 'sending':
return EventStatus.SENDING;
case 'sent':
return EventStatus.SENT;
default:
logNever(timelineEvent.localEvent.status);
return EventStatus.NOT_SENT;
}
}
else {
logger.error('$$$ timelineStoreEvents unknown event status', { timelineEvent });
return EventStatus.NOT_SENT;
}
}
function toAttachments(attachments, parentEventId) {
return attachments
.map((attachment, index) => toAttachment(attachment, parentEventId, index))
.filter(isDefined);
}
function toAttachment(attachment, parentEventId, index) {
const id = `${parentEventId}-${index}`;
switch (attachment.content.case) {
case 'chunkedMedia': {
const info = attachment.content.value.info;
if (!info) {
logger.error('$$$ timelineStoreEvents invalid chunkedMedia attachment', {
attachment,
});
return undefined;
}
const thumbnailInfo = attachment.content.value.thumbnail?.info;
const thumbnailContent = attachment.content.value.thumbnail?.content;
const thumbnail = thumbnailInfo && thumbnailContent
? {
info: thumbnailInfo,
content: thumbnailContent,
}
: undefined;
const encryption = attachment.content.value.encryption.case === 'aesgcm'
? attachment.content.value.encryption.value
: undefined;
if (!encryption) {
logger.error('$$$ timelineStoreEvents invalid chunkedMedia encryption', {
attachment,
});
return undefined;
}
return {
type: 'chunked_media',
info,
streamId: attachment.content.value.streamId,
encryption,
id,
thumbnail: thumbnail,
};
}
case 'embeddedMedia': {
const info = attachment.content.value.info;
if (!info) {
return undefined;
}
return {
type: 'embedded_media',
info,
content: attachment.content.value.content,
id,
};
}
case 'image': {
const info = attachment.content.value.info;
if (!info) {
return undefined;
}
return { type: 'image', info, id };
}
case 'embeddedMessage': {
const content = attachment.content.value;
if (!content?.post || !content?.info) {
return undefined;
}
const channelMessageEvent = toTownsContent_ChannelPayload_Message_Post(content.post, content.info.messageId, undefined, '').content;
return channelMessageEvent?.kind === RiverTimelineEvent.ChannelMessage
? {
type: 'embedded_message',
...content,
info: content.info,
channelMessageEvent: channelMessageEvent,
id,
}
: undefined;
}
case 'unfurledUrl': {
const content = attachment.content.value;
return {
type: 'unfurled_link',
url: content.url,
title: content.title,
description: content.description,
image: content.image,
id,
};
}
case 'ticker': {
const content = attachment.content.value;
return {
id,
type: 'ticker',
address: content.address,
chainId: content.chainId,
};
}
default:
return undefined;
}
}
/// fill in the event with the decrypted content
/// i don't love this since it duplicates code, but it makes it so that we don't have to keep the
/// original RemoteTimelineEvent around in memory
export function toDecryptedEvent(event, decryptedContent, userId) {
if (!event.content) {
logger.error('$$$ timelineStoreEvents invalid event in toDecryptedEvent', {
event,
decryptedContent,
});
return event;
}
switch (event.content.kind) {
case RiverTimelineEvent.ChannelMessageEncrypted:
case RiverTimelineEvent.ChannelMessageEncryptedWithRef: {
if (decryptedContent.kind === 'channelMessage') {
const { content, error } = toTownsContent_FromChannelMessage(decryptedContent.content, `ChannelMessage: ${event.eventId}`, event.eventId);
if (error) {
return event;
}
return {
...event,
content,
threadParentId: getThreadParentId(content),
replyParentId: getReplyParentId(content),
reactionParentId: getReactionParentId(content),
isMentioned: getIsMentioned(content, userId),
};
}
else {
logger.error('$$$ timelineStoreEvents invalid channelMessageEncrypted', {
event,
decryptedContent,
});
return event;
}
}
case RiverTimelineEvent.SpaceDisplayName: {
if (decryptedContent.kind === 'text') {
return {
...event,
content: {
...event.content,
displayName: decryptedContent.content,
},
};
}
else {
logger.error('$$$ timelineStoreEvents invalid spaceDisplayName', {
event,
decryptedContent,
});
return event;
}
}
case RiverTimelineEvent.SpaceUsername: {
if (decryptedContent.kind === 'text') {
return {
...event,
content: {
...event.content,
username: decryptedContent.content,
},
};
}
else {
logger.error('$$$ timelineStoreEvents invalid spaceUsername', {
event,
decryptedContent,
});
return event;
}
}
case RiverTimelineEvent.EncryptedChannelProperties: {
if (decryptedContent.kind === 'channelProperties') {
return {
...event,
content: {
kind: RiverTimelineEvent.ChannelProperties,
properties: decryptedContent.content,
},
};
}
else {
logger.error('$$$ timelineStoreEvents invalid encryptedChannelProperties', {
event,
decryptedContent,
});
return event;
}
}
case RiverTimelineEvent.ChannelCreate:
case RiverTimelineEvent.ChannelMessage:
case RiverTimelineEvent.ChannelMessageMissing:
case RiverTimelineEvent.ChannelProperties:
case RiverTimelineEvent.Inception:
case RiverTimelineEvent.KeySolicitation:
case RiverTimelineEvent.Fulfillment:
case RiverTimelineEvent.MemberBlockchainTransaction:
case RiverTimelineEvent.MiniblockHeader:
case RiverTimelineEvent.Pin:
case RiverTimelineEvent.Reaction:
case RiverTimelineEvent.RedactedEvent:
case RiverTimelineEvent.RedactionActionEvent:
case RiverTimelineEvent.SpaceEnsAddress:
case RiverTimelineEvent.SpaceNft:
case RiverTimelineEvent.SpaceReview:
case RiverTimelineEvent.TokenTransfer:
case RiverTimelineEvent.TipEvent:
case RiverTimelineEvent.SpaceUpdateAutojoin:
case RiverTimelineEvent.SpaceUpdateHideUserJoinLeaves:
case RiverTimelineEvent.SpaceImage:
case RiverTimelineEvent.StreamEncryptionAlgorithm:
case RiverTimelineEvent.StreamMembership:
case RiverTimelineEvent.Unpin:
case RiverTimelineEvent.UserBlockchainTransaction:
case RiverTimelineEvent.UserReceivedBlockchainTransaction:
return event;
default:
logNever(event.content);
return event;
}
}
export function toDecryptedContentErrorEvent(event, error) {
switch (event.content?.kind) {
case RiverTimelineEvent.ChannelMessageEncrypted:
case RiverTimelineEvent.EncryptedChannelProperties:
return {
...event,
content: {
...event.content,
error,
},
};
break;
}
return event;
}
export function toMembership(membershipOp) {
switch (membershipOp) {
case MembershipOp.SO_JOIN:
return Membership.Join;
case MembershipOp.SO_INVITE:
return Membership.Invite;
case MembershipOp.SO_LEAVE:
return Membership.Leave;
case MembershipOp.SO_UNSPECIFIED:
return Membership.None;
case undefined:
return Membership.None;
default:
checkNever(membershipOp);
}
}
export function getFallbackContent(senderDisplayName, content, error) {
if (error) {
return error;
}
if (!content) {
throw new Error('Either content or error should be defined');
}
switch (content.kind) {
case RiverTimelineEvent.MiniblockHeader:
return `Miniblock miniblockNum:${content.miniblockNum}, hasSnapshot:${content.hasSnapshot.toString()}`;
case RiverTimelineEvent.Reaction:
return `${senderDisplayName} reacted with ${content.reaction} to ${content.targetEventId}`;
case RiverTimelineEvent.Inception:
return content.type ? `type: ${content.type}` : '';
case RiverTimelineEvent.ChannelMessageEncrypted:
return `Decrypting...`;
case RiverTimelineEvent.StreamMembership: {
return (`[${content.membership}] userId: ${content.userId} initiatorId: ${content.initiatorId}` +
(content.reason ? ` reason: ${content.reason}` : ''));
}
case RiverTimelineEvent.ChannelMessage:
return `${senderDisplayName}: ${content.body}`;
case RiverTimelineEvent.ChannelProperties:
return `properties: ${content.properties.name ?? ''} ${content.properties.topic ?? ''}`;
case RiverTimelineEvent.EncryptedChannelProperties:
return `Decrypting Channel Properties...`;
case RiverTimelineEvent.SpaceUsername:
return `username: ${content.username}`;
case RiverTimelineEvent.SpaceDisplayName:
return `username: ${content.displayName}`;
case RiverTimelineEvent.SpaceEnsAddress:
return `ensAddress: ${bin_toHexString(content.ensAddress)}`;
case RiverTimelineEvent.SpaceNft:
return `contractAddress: ${content.contractAddress}, tokenId: ${content.tokenId}, chainId: ${content.chainId}`;
case RiverTimelineEvent.RedactedEvent:
return `~Redacted~`;
case RiverTimelineEvent.RedactionActionEvent:
return `Redacts ${content.refEventId} adminRedaction: ${content.adminRedaction}`;
case RiverTimelineEvent.ChannelCreate:
if (content.channelSettings !== undefined) {
return `channelId: ${content.channelId} autojoin: ${content.channelSettings.autojoin} hideUserJoinLeaves: ${content.channelSettings.hideUserJoinLeaveEvents}`;
}
return `channelId: ${content.channelId}`;
case RiverTimelineEvent.SpaceUpdateAutojoin:
return `channelId: ${content.channelId} autojoin: ${content.autojoin}`;
case RiverTimelineEvent.SpaceUpdateHideUserJoinLeaves:
return `channelId: ${content.channelId} hideUserJoinLeaves: ${content.hideUserJoinLeaves}`;
case RiverTimelineEvent.SpaceImage:
return `SpaceImage`;
case RiverTimelineEvent.Fulfillment:
return `Fulfillment from: ${content.from} to: ${content.deviceKey} count: ${content.sessionIds.length} sessionIds: ${content.sessionIds.length ? content.sessionIds.join(',') : 'forNewDevice: true'}`;
case RiverTimelineEvent.KeySolicitation:
return `KeySolicitation deviceKey: ${content.deviceKey} sessionIds: ${content.sessionIds.length} isNewDevice: ${content.isNewDevice}`;
case RiverTimelineEvent.ChannelMessageMissing:
return `eventId: ${content.eventId}`;
case RiverTimelineEvent.ChannelMessageEncryptedWithRef:
return `refEventId: ${content.refEventId}`;
case RiverTimelineEvent.Pin:
return `pinnedEventId: ${content.pinnedEventId} by: ${content.userId}`;
case RiverTimelineEvent.Unpin:
return `unpinnedEventId: ${content.unpinnedEventId} by: ${content.userId}`;
case RiverTimelineEvent.UserBlockchainTransaction:
return getFallbackContent_BlockchainTransaction(content.transaction);
case RiverTimelineEvent.MemberBlockchainTransaction:
return `memberTransaction from: ${content.fromUserId} ${getFallbackContent_BlockchainTransaction(content.transaction)}`;
case RiverTimelineEvent.TipEvent:
return `tip from: ${content.fromUserId} to: ${content.toUserId} refEventId: ${content.refEventId} amount: ${content.tip.event?.amount.toString() ?? '??'}`;
case RiverTimelineEvent.TokenTransfer:
return `tokenTransfer from: ${content.fromUserId} amount: ${content.transfer.amount}`;
case RiverTimelineEvent.SpaceReview:
return `spaceReview from: ${content.fromUserId} rating: ${content.rating} comment: ${content.comment}`;
case RiverTimelineEvent.UserReceivedBlockchainTransaction:
return `kind: ${content.receivedTransaction.transaction?.content?.case ?? '??'} fromUserAddress: ${content.receivedTransaction.fromUserAddress
? bin_toHexString(content.receivedTransaction.fromUserAddress)
: ''}`;
case RiverTimelineEvent.StreamEncryptionAlgorithm:
return `algorithm: ${content.algorithm}`;
default:
checkNever(content); // these are client side after parsing events, everything should be covered
}
}
function getFallbackContent_BlockchainTransaction(transaction) {
if (!transaction) {
return '??';
}
switch (transaction.content.case) {
case 'tip':
if (!transaction.content.value?.event) {
return '??';
}
return `kind: ${transaction.content.case} messageId: ${bin_toHexString(transaction.content.value.event.messageId)} receiver: ${bin_toHexString(transaction.content.value.event.receiver)} amount: ${transaction.content.value.event.amount.toString()}`;
default:
return `kind: ${transaction.content.case ?? 'unspecified'}`;
}
}
//# sourceMappingURL=timelineEvent.js.map