diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml index a0f688b46..3c150e505 100644 --- a/.github/workflows/prod-deploy.yml +++ b/.github/workflows/prod-deploy.yml @@ -1,19 +1,16 @@ name: Production deploy on: - workflow_dispatch: + release: + types: [published] jobs: deploy-and-tarball: name: Netlify deploy and tarball - outputs: - version: ${{ steps.vars.outputs.tag }} runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - name: Setup node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: @@ -21,19 +18,6 @@ jobs: package-manager-cache: false - name: Install dependencies run: npm ci - - name: Run semantic release - run: npm run semantic-release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} - GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} - GIT_COMMITTER_NAME: ${{ secrets.GIT_AUTHOR_NAME }} - GIT_COMMITTER_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} - - name: Get version from tag - id: vars - run: | - TAG=$(git describe --tags --abbrev=0) - echo "tag=$TAG" >> $GITHUB_OUTPUT - name: Build app env: NODE_OPTIONS: '--max_old_space_size=4096' @@ -42,7 +26,7 @@ jobs: uses: nwtgck/actions-netlify@4cbaf4c08f1a7bfa537d6113472ef4424e4eb654 # v3.0.0 with: publish-dir: dist - deploy-message: 'Prod deploy ${{ steps.vars.outputs.tag }}' + deploy-message: 'Prod deploy ${{ github.ref_name }}' enable-commit-comment: false github-token: ${{ secrets.GITHUB_TOKEN }} production-deploy: true @@ -52,6 +36,9 @@ jobs: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID_APP }} timeout-minutes: 1 + - name: Get version from tag + id: vars + run: echo "tag=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - name: Create tar.gz run: tar -czvf cinny-${{ steps.vars.outputs.tag }}.tar.gz dist - name: Sign tar.gz @@ -67,16 +54,12 @@ jobs: - name: Upload tagged release uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: - tag_name: ${{ steps.vars.outputs.tag }} files: | cinny-${{ steps.vars.outputs.tag }}.tar.gz cinny-${{ steps.vars.outputs.tag }}.tar.gz.asc publish-image: name: Push Docker image to Docker Hub, GHCR - needs: deploy-and-tarball - env: - VERSION: ${{ needs.deploy-and-tarball.outputs.version }} runs-on: ubuntu-latest permissions: contents: read @@ -84,8 +67,6 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 - name: Set up Docker Buildx @@ -108,9 +89,6 @@ jobs: images: | ${{ secrets.DOCKER_USERNAME }}/cinny ghcr.io/${{ github.repository }} - tags: | - type=raw,value=${{ env.VERSION }} - type=raw,value=latest - name: Build and push Docker image uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: diff --git a/package.json b/package.json index bbf619e40..7896c320f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lotus-chat", - "version": "4.12.1-lotus", + "version": "4.12.2-lotus", "description": "Lotus Chat — Matrix client for Lotus Guild", "main": "index.js", "type": "module", @@ -18,7 +18,6 @@ "typecheck": "tsc --noEmit", "prepare": "husky", "commit": "git-cz", - "semantic-release": "semantic-release", "postinstall": "node scripts/patch-folds.mjs" }, "lint-staged": { @@ -30,35 +29,6 @@ "path": "./node_modules/cz-conventional-changelog" } }, - "release": { - "branches": [ - "dev" - ], - "plugins": [ - "@semantic-release/commit-analyzer", - "@semantic-release/release-notes-generator", - [ - "@semantic-release/exec", - { - "prepareCmd": "node scripts/update-version.js ${nextRelease.version}" - } - ], - [ - "@semantic-release/git", - { - "assets": [ - "package.json", - "package-lock.json", - "src/app/features/settings/about/About.tsx", - "src/app/pages/auth/AuthFooter.tsx", - "src/app/pages/client/WelcomePage.tsx" - ], - "message": "chore(release): ${nextRelease.version} [skip ci]" - } - ], - "@semantic-release/github" - ] - }, "keywords": [], "author": "Ajay Bura", "license": "AGPL-3.0-only", @@ -132,8 +102,6 @@ "@element-hq/element-call-embedded": "0.19.4", "@rollup/plugin-inject": "5.0.5", "@rollup/plugin-wasm": "6.2.2", - "@semantic-release/exec": "7.1.0", - "@semantic-release/git": "10.0.1", "@sentry/vite-plugin": "5.3.0", "@types/chroma-js": "3.1.2", "@types/file-saver": "2.0.7", @@ -163,7 +131,6 @@ "husky": "9.1.7", "lint-staged": "17.0.5", "prettier": "3.8.3", - "semantic-release": "25.0.3", "typescript": "6.0.3", "vite": "8.0.14", "vite-plugin-pwa": "1.3.0", diff --git a/src/app/components/CallEmbedProvider.tsx b/src/app/components/CallEmbedProvider.tsx index 17d431628..be91e373e 100644 --- a/src/app/components/CallEmbedProvider.tsx +++ b/src/app/components/CallEmbedProvider.tsx @@ -111,11 +111,14 @@ function IncomingCall({ dm, info, onIgnore, onAnswer, onReject }: IncomingCallPr const session = useCallSession(room); useCallMembersChange( session, - useCallback(() => { - if (session.memberships.length === 0) { - onIgnore(); - } - }, [session, onIgnore]), + useCallback( + (members) => { + if (members.length === 0) { + onIgnore(); + } + }, + [onIgnore], + ), ); const playSound = useCallback(() => { diff --git a/src/app/features/call-status/CallStatus.tsx b/src/app/features/call-status/CallStatus.tsx index 1d30d1b40..1ff3b837a 100644 --- a/src/app/features/call-status/CallStatus.tsx +++ b/src/app/features/call-status/CallStatus.tsx @@ -22,7 +22,7 @@ export function CallStatus({ callEmbed }: CallStatusProps) { const { room } = callEmbed; const callSession = useCallSession(room); - const callMembers = useCallMembers(room, callSession); + const callMembers = useCallMembers(callSession); const screenSize = useScreenSize(); const callJoined = useCallJoined(callEmbed); const speakers = useCallSpeakers(callEmbed); diff --git a/src/app/features/call-status/LiveChip.tsx b/src/app/features/call-status/LiveChip.tsx index 483d20ce5..0ee1eb4c1 100644 --- a/src/app/features/call-status/LiveChip.tsx +++ b/src/app/features/call-status/LiveChip.tsx @@ -82,7 +82,7 @@ export function LiveChip({ count, room, members }: LiveChipProps) { return ( {visibleMembers.map((callMember) => { - const userId = callMember.sender; + const { userId } = callMember; if (!userId) return null; const name = getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId; const avatarMxc = getMemberAvatarMxc(room, userId); @@ -39,7 +39,7 @@ export function MemberGlance({ room, members, speakers, max = 6 }: MemberGlanceP return ( {truncatedMembers.map((member) => ( - + ))} {members.length > max && ( 0; const callEmbed = useCallEmbed(); diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx index 018d336ea..6dce00a18 100644 --- a/src/app/features/room-nav/RoomNavItem.tsx +++ b/src/app/features/room-nav/RoomNavItem.tsx @@ -282,7 +282,7 @@ function RoomNavItem_({ const optionsVisible = hover || !!menuAnchor; const callSession = useCallSession(room); - const callMembers = useCallMembers(room, callSession); + const callMembers = useCallMembers(callSession); const startCall = useCallStart(direct); const callEmbed = useCallEmbed(); const callPref = useAtomValue(useCallPreferencesAtom()); diff --git a/src/app/features/room/Room.tsx b/src/app/features/room/Room.tsx index 11b30d937..215bdf3d8 100644 --- a/src/app/features/room/Room.tsx +++ b/src/app/features/room/Room.tsx @@ -27,7 +27,7 @@ export function Room() { const mx = useMatrixClient(); const callSession = useCallSession(room); - const callMembers = useCallMembers(room, callSession); + const callMembers = useCallMembers(callSession); const callEmbed = useCallEmbed(); const [isDrawer] = useSetting(settingsAtom, 'isPeopleDrawer'); diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index b87dca75d..31cc6d182 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -26,7 +26,6 @@ import { HTMLReactParserOptions } from 'html-react-parser'; import classNames from 'classnames'; import { ReactEditor } from 'slate-react'; import { Editor } from 'slate'; -import { type SessionMembershipData } from 'matrix-js-sdk/lib/matrixrtc'; import to from 'await-to-js'; import { useAtomValue, useSetAtom } from 'jotai'; import { @@ -1796,7 +1795,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli const senderId = mEvent.getSender() ?? ''; const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId); - const content = mEvent.getContent(); + const content = mEvent.getContent(); const prevContent = mEvent.getPrevContent(); const callJoined = content.application; diff --git a/src/app/hooks/useCall.ts b/src/app/hooks/useCall.ts index 34cc61a5c..217ba1727 100644 --- a/src/app/hooks/useCall.ts +++ b/src/app/hooks/useCall.ts @@ -2,6 +2,7 @@ import { Room } from 'matrix-js-sdk'; import { MatrixRTCSession, MatrixRTCSessionEvent, + MatrixRTCSessionEventHandlerMap, } from 'matrix-js-sdk/lib/matrixrtc/MatrixRTCSession'; import { CallMembership } from 'matrix-js-sdk/lib/matrixrtc/CallMembership'; import { useEffect, useState } from 'react'; @@ -33,30 +34,27 @@ export const useCallSession = (room: Room): MatrixRTCSession => { return session; }; -export const useCallMembers = (room: Room, session: MatrixRTCSession): CallMembership[] => { - const [memberships, setMemberships] = useState(session.memberships); - +export const useCallMembersChange = ( + session: MatrixRTCSession, + callback: (members: CallMembership[]) => void, +): void => { useEffect(() => { - const updateMemberships = () => { - setMemberships([...session.memberships]); - }; + const handleMembershipsChange: MatrixRTCSessionEventHandlerMap[MatrixRTCSessionEvent.MembershipsChanged] = + (oldestMembership, newMemberships) => { + callback(newMemberships); + }; - updateMemberships(); - - session.on(MatrixRTCSessionEvent.MembershipsChanged, updateMemberships); + session.on(MatrixRTCSessionEvent.MembershipsChanged, handleMembershipsChange); return () => { - session.removeListener(MatrixRTCSessionEvent.MembershipsChanged, updateMemberships); - }; - }, [session, room]); - - return memberships; -}; - -export const useCallMembersChange = (session: MatrixRTCSession, callback: () => void): void => { - useEffect(() => { - session.on(MatrixRTCSessionEvent.MembershipsChanged, callback); - return () => { - session.removeListener(MatrixRTCSessionEvent.MembershipsChanged, callback); + session.removeListener(MatrixRTCSessionEvent.MembershipsChanged, handleMembershipsChange); }; }, [session, callback]); }; + +export const useCallMembers = (session: MatrixRTCSession): CallMembership[] => { + const [memberships, setMemberships] = useState(session.memberships); + + useCallMembersChange(session, setMemberships); + + return memberships; +}; diff --git a/src/app/hooks/useCallSpeakers.ts b/src/app/hooks/useCallSpeakers.ts index 7843dc3b5..6360087a0 100644 --- a/src/app/hooks/useCallSpeakers.ts +++ b/src/app/hooks/useCallSpeakers.ts @@ -8,7 +8,7 @@ import { useCallJoined } from './useCallEmbed'; export const useCallSpeakers = (callEmbed: CallEmbed): Set => { const [speakers, setSpeakers] = useState(new Set()); const callSession = useCallSession(callEmbed.room); - const callMembers = useCallMembers(callEmbed.room, callSession); + const callMembers = useCallMembers(callSession); const joined = useCallJoined(callEmbed); const videoContainers = useMemo(() => { diff --git a/src/app/plugins/call/CallWidgetDriver.ts b/src/app/plugins/call/CallWidgetDriver.ts index a3ff2ef87..0dff3f70c 100644 --- a/src/app/plugins/call/CallWidgetDriver.ts +++ b/src/app/plugins/call/CallWidgetDriver.ts @@ -8,7 +8,6 @@ import { type IWidgetApiErrorResponseDataDetails, type ISearchUserDirectoryResult, type IGetMediaConfigResult, - type UpdateDelayedEventAction, OpenIDRequestState, SimpleObservable, IOpenIDUpdate, @@ -56,14 +55,11 @@ export class CallWidgetDriver extends WidgetDriver { stateKey: string | null = null, targetRoomId: string | null = null, ): Promise { - const client = this.mx; const roomId = targetRoomId || this.inRoomId; - if (!client || !roomId) throw new Error('Not in a room or not attached to a client'); - let r: { event_id: string } | null; if (typeof stateKey === 'string') { - r = await client.sendStateEvent( + r = await this.mx.sendStateEvent( roomId, eventType as keyof StateEvents, content as StateEvents[keyof StateEvents], @@ -71,9 +67,9 @@ export class CallWidgetDriver extends WidgetDriver { ); } else if (eventType === EventType.RoomRedaction) { // special case: extract the `redacts` property and call redact - r = await client.redactEvent(roomId, content.redacts); + r = await this.mx.redactEvent(roomId, content.redacts); } else { - r = await client.sendEvent( + r = await this.mx.sendEvent( roomId, eventType as keyof TimelineEvents, content as TimelineEvents[keyof TimelineEvents], @@ -91,11 +87,8 @@ export class CallWidgetDriver extends WidgetDriver { stateKey: string | null = null, targetRoomId: string | null = null, ): Promise { - const client = this.mx; const roomId = targetRoomId || this.inRoomId; - if (!client || !roomId) throw new Error('Not in a room or not attached to a client'); - let delayOpts; if (delay !== null) { delayOpts = { @@ -113,7 +106,7 @@ export class CallWidgetDriver extends WidgetDriver { let r: SendDelayedEventResponse | null; if (stateKey !== null) { // state event - r = await client._unstable_sendDelayedStateEvent( + r = await this.mx._unstable_sendDelayedStateEvent( roomId, delayOpts, eventType as keyof StateEvents, @@ -122,7 +115,7 @@ export class CallWidgetDriver extends WidgetDriver { ); } else { // message event - r = await client._unstable_sendDelayedEvent( + r = await this.mx._unstable_sendDelayedEvent( roomId, delayOpts, null, @@ -137,17 +130,6 @@ export class CallWidgetDriver extends WidgetDriver { }; } - public async updateDelayedEvent( - delayId: string, - action: UpdateDelayedEventAction, - ): Promise { - const client = this.mx; - - if (!client) throw new Error('Not in a room or not attached to a client'); - - await client._unstable_updateDelayedEvent(delayId, action); - } - public async cancelScheduledDelayedEvent(delayId: string): Promise { await this.mx._unstable_cancelScheduledDelayedEvent(delayId); } @@ -165,10 +147,8 @@ export class CallWidgetDriver extends WidgetDriver { encrypted: boolean, contentMap: { [userId: string]: { [deviceId: string]: object } }, ): Promise { - const client = this.mx; - if (encrypted) { - const crypto = client.getCrypto(); + const crypto = this.mx.getCrypto(); if (!crypto) throw new Error('E2EE not enabled'); // attempt to re-batch these up into a single request @@ -193,11 +173,11 @@ export class CallWidgetDriver extends WidgetDriver { JSON.parse(stringifiedContent), ); - await client.queueToDevice(batch); + await this.mx.queueToDevice(batch); }), ); } else { - await client.queueToDevice({ + await this.mx.queueToDevice({ eventType, batch: Object.entries(contentMap).flatMap(([userId, userContentMap]) => Object.entries(userContentMap).map(([deviceId, content]) => ({ @@ -277,7 +257,6 @@ export class CallWidgetDriver extends WidgetDriver { limit?: number, direction?: 'f' | 'b', ): Promise { - const client = this.mx; const dir = direction as Direction; const targetRoomId = roomId ?? this.inRoomId ?? undefined; @@ -285,7 +264,7 @@ export class CallWidgetDriver extends WidgetDriver { throw new Error('Error while reading the current room'); } - const { events, nextBatch, prevBatch } = await client.relations( + const { events, nextBatch, prevBatch } = await this.mx.relations( targetRoomId, eventId, relationType ?? null, @@ -304,9 +283,7 @@ export class CallWidgetDriver extends WidgetDriver { searchTerm: string, limit?: number, ): Promise { - const client = this.mx; - - const { limited, results } = await client.searchUserDirectory({ term: searchTerm, limit }); + const { limited, results } = await this.mx.searchUserDirectory({ term: searchTerm, limit }); return { limited, @@ -319,15 +296,11 @@ export class CallWidgetDriver extends WidgetDriver { } public async getMediaConfig(): Promise { - const client = this.mx; - - return client.getMediaConfig(); + return this.mx.getMediaConfig(); } public async uploadFile(file: XMLHttpRequestBodyInit): Promise<{ contentUri: string }> { - const client = this.mx; - - const uploadResult = await client.uploadContent(file); + const uploadResult = await this.mx.uploadContent(file); return { contentUri: uploadResult.content_uri }; }