@42px/matrix-effector

This implementation of DAL uses the effector state manager.

Usage no npm install needed!

<script type="module">
  import 42pxMatrixEffector from 'https://cdn.skypack.dev/@42px/matrix-effector';
</script>

README

Data Access Layer for matrix-js-sdk

This implementation of DAL uses the effector state manager.

Installation

1.4.0 version is unstable, please avoid it

npm install @42px/matrix-effector

Usage

import { IndexedDBStore } from 'matrix-js-sdk'
import {
  initStoreFx,
  loginByPasswordFx,
  prependClientParams,
  onInitialSync,
  newMessagesLoaded,
  initTimelineWindowFx,
  $currentRoomId, // Use these stores in your view
  $messages,
  initRoom,
  onRoomInitialized,
  loadRoom,
  paginateForward,
  paginateBackward,
  searchRoomMessagesFx,
  loadTimelineWindowFx,
  onCachedState,
  getRoomsWithActivitiesFx,
  createOnSyncThrottled,
  checkEventPermissionsFx,
  getUrlPreviewFx
} from '@42px/matrix-effector'
import { forward, guard, sample } from 'effector'
import {
  initApp,
  onLoadMoreBack,
  onLoadMoreFront,
  onSearch,
  onSearchInput,
  onSearchResultSelect,
  roomSelected,
} from './events'
import {
  $rooms,
  $searchInput,
  $searchResults,
  $currentUser,
} from './state'

const chatWindowSize = 20

$rooms.on(getRoomsWithActivitiesFx.doneData, (_, value) => value)
$searchInput.on(onSearchInput, (_, value) => value)
$searchResults.on(searchRoomMessagesFx.doneData, (_, messages) => messages)
$currentUser.on(getLoggedUserFx.doneData, (_, user) => user)

const store: IndexedDBStore = new IndexedDBStore({
  indexedDB: window.indexedDB,
  dbName: 'zuggat-cache',
})
prependClientParams({
  baseUrl: process.env.MATRIX_BASE_URL,
  store,
  timelineSupport: true,
  unstableClientRelationAggregation: true,
})

forward({
  from: initApp,
  to: initStoreFx,
})
forward({
  from: initStoreFx.done.map(() => ({
    user: 'username',
    password: 'password',
  })),
  to: loginByPasswordFx,
})
forward({
  from: loginByPasswordFx.done,
  to: getLoggedUserFx,
})
// new messages event (live timeline)
newMessagesLoaded.watch((messages) => console.log(messages))

// on back pagination complete
onPaginateBackwardDone.watch(() => console.log('paginated back in history'))

// explicit triger to load live timeline (for example: to get to the end of history)
toLiveTimeline()

liveTimelineLoaded.watch(() => console.log('room live timeline loaded'))

// events to go to message in current room
loadRoomMessage({ initialEventId: 'messageId' })

loadRoomMessageDone.watch(() => console.log('loaded timeline around message'))

// To select current room trigger initRoom - it will also create a TimelineWindow instance for it
forward({
  from: roomSelected.map((roomId) => { roomId }),
  to: initRoom,
})
forward({
  from: onRoomInitialized,
  to: loadRoom,
})
guard({
  source: sample(
    [$searchInput, $currentRoomId],
    onSearch,
    (([term, roomId]) => ({ term, roomId, orderBy: 'recent' })),
  ),
  filter: combine($currentRoomId, $searchInput, (roomId, term) => Boolean(roomId) && Boolean(term)),
  target: searchRoomMessagesFx,
})
guard({
  source: onSearchResultSelect.map((eventId) => ({
    initialEventId: eventId,
    initialWindowSize: chatWindowSize,
  })),
  filter: $currentRoomId.map((roomId) => Boolean(roomId)),
  target: loadRoom,
})
guard({
  source: onLoadMoreBack.map(() => ({ size: 10 })),
  filter: $currentRoomId.map((roomId) => Boolean(roomId)),
  target: paginateBackward,
})
guard({
  source: onLoadMoreFront.map(() => ({ size: 10 })),
  filter: $currentRoomId.map((roomId) => Boolean(roomId)),
  target: paginateForward,
})
const onSyncThrottled = createOnSyncThrottled(500)
forward({
  from: [onCachedState, onInitialSync, onSyncThrottled],
  to: getRoomsWithActivitiesFx,
})
guard({
  source: sample(
    $currentRoomId,
    $selectedMessage.updates,
    (roomId, messageId) => ({
      roomId: roomId as string,
      eventId: messageId as string,
    }),
  ),
  filter: combine(
    $hasRoom,
    $selectedMessage,
    (hasRoom, messageId) => hasRoom && Boolean(messageId),
  ),
  target: checkEventPermissionsFx,
})
checkEventPermissionsFx.doneData.watch(({ canRedact, canEdit }) => console.log(canRedact, canEdit))

Utils

get RoomMember Avatar url / Room Avatar Url

import { getRoomMemberAvatarUrl, getSenderAvatarUrl, Message } from '@42px/matrix-effector'

...

 const dialogAvatar = getRoomMemberAvatarUrl({
  roomId: item.roomId,
  userId: item.directUserId,
  width: 40,
  height: 40,
  resizeMethod: 'crop',
})

const avatarUrl = getSenderAvatarUrl({
  sender: message.sender,
  width: 40,
  height: 40,
  resizeMethod: 'crop',
})

API Reference

Store

$messages

const $messages = store<Message>()

Store provides access to messages in opened room

$currentRoomId

const $currentRoomId = store<RoomWithActivity["roomId"] | null>(null)

Store provides access to roomId in opened room

$currentRoom

const $currentRoom = store<RoomWithActivity | null>(null)

Store provides access to opened room

$currentRoomMembers

const $currentRoomMembers = store<MappedRoomMember[] | null>(null)

Store contains members of the selected room

Events

onRoomMemberUpdate

const onRoomMemberUpdate = event<RoomMember>()

The event occurs when a new member is invited, the member enters the room, starts typing, changes his name, the member's power level changes.

onRoomLoaded

const onRoomLoaded = event<void>()

Event emmits when room initialized and messages loaded in $messages.

roomCreated

const roomCreated = event<Room>()

Event emmits when you are invited to a new room.

const directRoomCreated = event<Room>()

Event emmits when you are invited to a new direct room.

New rooms come in the matrix "Room" event.

Effects

createDirectRoomFx

const createDirectRoomFx = effect<CreateDirectRoomParams, { roomId: string }, Error>()

Creates a direct room with private visibility and set yur m.direct account data