Office: use pulse for invites and join requests (#9941)

Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Anton Alexeyev
2025-10-02 11:37:55 +07:00
committed by GitHub
co-authored by Copilot
parent 5f3911d5c7
commit ff4fdc228a
28 changed files with 585 additions and 578 deletions
-65
View File
@@ -31,8 +31,6 @@ import {
import {
type DevicesPreference,
type Floor,
type Invite,
type JoinRequest,
loveId,
type Meeting,
type MeetingMinutes,
@@ -40,7 +38,6 @@ import {
type MeetingSchedule,
type Office,
type ParticipantInfo,
type RequestStatus,
type Room,
type RoomAccess,
type RoomInfo,
@@ -163,31 +160,6 @@ export class TParticipantInfo extends TDoc implements ParticipantInfo {
account!: AccountUuid | null
}
@Model(love.class.JoinRequest, core.class.Doc, DOMAIN_TRANSIENT)
export class TJoinRequest extends TDoc implements JoinRequest {
@Prop(TypeRef(contact.class.Person), getEmbeddedLabel('From'))
person!: Ref<Person>
@Prop(TypeRef(love.class.Room), love.string.Room)
room!: Ref<Room>
status!: RequestStatus
}
@Model(love.class.Invite, core.class.Doc, DOMAIN_TRANSIENT)
export class TInvite extends TDoc implements Invite {
@Prop(TypeRef(contact.class.Person), getEmbeddedLabel('From'))
from!: Ref<Person>
@Prop(TypeRef(contact.class.Person), getEmbeddedLabel('Target'))
target!: Ref<Person>
@Prop(TypeRef(love.class.Room), love.string.Room)
room!: Ref<Room>
status!: RequestStatus
}
@Model(love.class.DevicesPreference, preference.class.Preference)
export class TDevicesPreference extends TPreference implements DevicesPreference {
blurRadius!: number
@@ -272,10 +244,8 @@ export function createModel (builder: Builder): void {
TFloor,
TOffice,
TParticipantInfo,
TJoinRequest,
TDevicesPreference,
TRoomInfo,
TInvite,
TMeeting,
TMeetingMinutes,
TMeetingSchedule
@@ -388,36 +358,6 @@ export function createModel (builder: Builder): void {
love.ids.LoveNotificationGroup
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
hidden: false,
generated: false,
label: love.string.InivitingLabel,
group: love.ids.LoveNotificationGroup,
txClasses: [core.class.TxCreateDoc],
objectClass: love.class.Invite,
defaultEnabled: true
},
love.ids.InviteNotification
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
hidden: false,
generated: false,
label: love.string.KnockingLabel,
group: love.ids.LoveNotificationGroup,
txClasses: [],
objectClass: love.class.JoinRequest,
defaultEnabled: true
},
love.ids.KnockNotification
)
builder.createDoc(notification.class.NotificationProviderDefaults, core.space.Model, {
provider: notification.providers.SoundNotificationProvider,
excludeIgnore: [love.ids.KnockNotification],
@@ -514,11 +454,6 @@ export function createModel (builder: Builder): void {
components: { input: { component: chunter.component.ChatMessageInput, props: { collection: 'messages' } } }
})
builder.mixin(love.class.JoinRequest, core.class.Class, core.mixin.TxAccessLevel, {
createAccessLevel: AccountRole.Guest,
removeAccessLevel: AccountRole.Guest
})
builder.mixin(love.class.ParticipantInfo, core.class.Class, core.mixin.TxAccessLevel, {
createAccessLevel: AccountRole.Guest,
updateAccessLevel: AccountRole.Guest
-16
View File
@@ -64,22 +64,6 @@ export function createModel (builder: Builder): void {
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverLove.trigger.OnKnock,
txMatch: {
objectClass: love.class.JoinRequest,
_class: core.class.TxCreateDoc
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverLove.trigger.OnInvite,
txMatch: {
objectClass: love.class.Invite,
_class: core.class.TxCreateDoc
}
})
builder.mixin(love.class.MeetingMinutes, core.class.Class, serverCore.mixin.SearchPresenter, {
searchIcon: love.icon.MeetingMinutes,
title: [['title']]
+1
View File
@@ -62,6 +62,7 @@
"@hcengineering/theme": "^0.6.5",
"@hcengineering/retry": "^0.6.0",
"@hcengineering/hulylake-client": "^0.6.0",
"@hcengineering/hulypulse-client": "^0.6.0",
"fast-equals": "^5.2.2",
"png-chunks-extract": "^1.0.0",
"svelte": "^4.2.20",
+1
View File
@@ -78,3 +78,4 @@ export * from './drawingCommand'
export * from './drawingCommandsProcessor'
export * from './link-preview'
export * from './communication'
export * from './pulse'
@@ -13,7 +13,7 @@
import { HulypulseClient } from '@hcengineering/hulypulse-client'
import { getMetadata } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import presentation from './plugin'
let pulseclient: HulypulseClient | undefined
+1 -1
View File
@@ -143,7 +143,7 @@ export function showPopup (
id,
close: closePopupOp,
update: (props) => {
updatePopup(id, props)
updatePopup(id, { props })
}
}
}
+1
View File
@@ -65,6 +65,7 @@
"@hcengineering/emoji-resources": "^0.6.0",
"@hcengineering/theme": "^0.6.5",
"@hcengineering/account-client": "^0.6.0",
"@hcengineering/hulypulse-client": "^0.6.0",
"@livekit/krisp-noise-filter": "^0.3.0",
"@livekit/track-processors": "^0.5.6",
"livekit-client": "^2.15.6",
@@ -5,7 +5,8 @@
import { ActionIcon } from '@hcengineering/ui'
import love from '../plugin'
import { myInfo } from '../stores'
import { joinMeeting, kick, sendInvites } from '../meetings'
import { joinMeeting, kick } from '../meetings'
import { sendInvites } from '../invites'
export let room: Room
export let person: Ref<Person>
@@ -30,7 +31,7 @@
label={love.string.Invite}
icon={love.icon.Invite}
action={() => {
void sendInvites([person])
sendInvites([person])
}}
/>
{#if room.access === RoomAccess.Knock}
@@ -5,6 +5,11 @@
import love from '../plugin'
import { liveKitClient, lk } from '../utils'
import { lkSessionConnected } from '../liveKitClient'
import { subscribeInviteRequests, unsubscribeInviteRequests } from '../invites'
import { Room } from '@hcengineering/love'
import { subscribeJoinRequests, unsubscribeJoinRequests } from '../joinRequests'
import { Ref } from '@hcengineering/core'
import { myInfo } from '../stores'
let parentElement: HTMLDivElement
@@ -33,13 +38,26 @@
}
}
onMount(() => {
function subscribeRoomRequests (room: Ref<Room> | undefined): void {
unsubscribeJoinRequests()
.then(() => subscribeJoinRequests(room))
.catch((e) => {
console.log(e)
})
}
$: subscribeRoomRequests($myInfo?.room)
onMount(async () => {
pushRootBarComponent('left', love.component.ControlExt, 20)
lk.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)
lk.on(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
await subscribeInviteRequests()
})
onDestroy(async () => {
await unsubscribeInviteRequests()
lk.off(RoomEvent.TrackSubscribed, handleTrackSubscribed)
lk.off(RoomEvent.TrackUnsubscribed, handleTrackUnsubscribed)
if ($lkSessionConnected) {
@@ -14,29 +14,17 @@
-->
<script lang="ts">
import { IdMap, Ref, toIdMap } from '@hcengineering/core'
import { Invite, isOffice, JoinRequest, ParticipantInfo, RequestStatus, Room } from '@hcengineering/love'
import { createQuery, getClient } from '@hcengineering/presentation'
import {
closePopup,
eventToHTMLElement,
Location,
location,
PopupResult,
showPopup,
closeTooltip
} from '@hcengineering/ui'
import { isOffice, ParticipantInfo, Room } from '@hcengineering/love'
import { getClient } from '@hcengineering/presentation'
import { closePopup, eventToHTMLElement, Location, location, showPopup, closeTooltip } from '@hcengineering/ui'
import { onDestroy } from 'svelte'
import workbench from '@hcengineering/workbench'
import { closeWidget, sidebarStore } from '@hcengineering/workbench-resources'
import love from '../../plugin'
import { activeInvites, currentRoom, infos, myInfo, myInvites, myRequests, rooms } from '../../stores'
import { currentRoom, infos, myInfo, rooms } from '../../stores'
import { createMeetingWidget, getRoomName } from '../../utils'
import ActiveInvitesPopup from './invites/ActiveInvitesPopup.svelte'
import InvitePopup from './invites/InvitePopup.svelte'
import PersonActionPopup from '../PersonActionPopup.svelte'
import RequestPopup from './invites/RequestPopup.svelte'
import RequestingPopup from './invites/RequestingPopup.svelte'
import RoomPopup from '../RoomPopup.svelte'
import RoomButton from '../RoomButton.svelte'
import { leaveMeeting, currentMeetingRoom } from '../../meetings'
@@ -70,56 +58,6 @@
$: activeRooms = getActiveRooms($rooms, $infos)
const query = createQuery()
let requests: JoinRequest[] = []
query.query(love.class.JoinRequest, { status: RequestStatus.Pending }, (res) => {
requests = res
})
let activeRequest: JoinRequest | undefined = undefined
const joinRequestCategory = 'joinRequest'
function checkRequests (requests: JoinRequest[], $myInfo: ParticipantInfo | undefined): void {
if (activeRequest !== undefined) {
// try to find active request, if it not exists close popup
if (requests.find((r) => r._id === activeRequest?._id && r.room === $myInfo?.room) === undefined) {
closePopup(joinRequestCategory)
activeRequest = undefined
}
}
if (activeRequest === undefined) {
activeRequest = requests.find((r) => r.room === $myInfo?.room)
if (activeRequest !== undefined) {
showPopup(RequestPopup, { request: activeRequest }, undefined, undefined, undefined, {
category: joinRequestCategory,
overlay: false,
fixed: true
})
}
}
}
const myJoinRequestCategory = 'MyJoinRequest'
let myRequestsPopup: PopupResult | undefined = undefined
function checkMyRequests (requests: JoinRequest[]): void {
if (requests.length > 0) {
if (myRequestsPopup === undefined) {
myRequestsPopup = showPopup(RequestingPopup, { request: requests[0] }, undefined, undefined, undefined, {
category: myJoinRequestCategory,
overlay: false,
fixed: true
})
}
} else if (myRequestsPopup !== undefined) {
myRequestsPopup.close()
myRequestsPopup = undefined
}
}
$: checkMyRequests($myRequests)
$: checkRequests(requests, $myInfo)
function openRoom (room: Room): (e: MouseEvent) => void {
return (e: MouseEvent) => {
closeTooltip()
@@ -127,55 +65,12 @@
}
}
let activeInvite: Invite | undefined = undefined
const inviteCategory = 'inviteReq'
function checkInvites (invites: Invite[]): void {
if (activeInvite !== undefined) {
// try to find active request, if it not exists close popup
if (invites.find((r) => r._id === activeInvite?._id) === undefined) {
closePopup(inviteCategory)
activeInvite = undefined
}
}
if (activeInvite === undefined) {
activeInvite = invites[0]
if (activeInvite !== undefined) {
showPopup(InvitePopup, { invite: activeInvite }, undefined, undefined, undefined, {
category: inviteCategory,
overlay: false,
fixed: true
})
}
}
}
$: checkInvites($myInvites)
const myInvitesCategory = 'myInvites'
let myInvitesPopup: PopupResult | undefined = undefined
function checkActiveInvites (invites: Invite[]): void {
if (invites.length > 0) {
if (myInvitesPopup === undefined) {
myInvitesPopup = showPopup(ActiveInvitesPopup, {}, undefined, undefined, undefined, {
category: myInvitesCategory,
overlay: false,
fixed: true
})
}
} else if (myInvitesPopup !== undefined) {
myInvitesPopup.close()
myInvitesPopup = undefined
}
}
$: reception = $rooms.find((f) => f._id === love.ids.Reception)
$: receptionParticipants = $infos.filter((p) => p.room === love.ids.Reception)
$: checkActiveInvites($activeInvites)
function checkActiveMeeting (loc: Location, meetingSessionConnected: boolean, room: Ref<Room> | undefined): void {
const meetingWidgetState = $sidebarStore.widgetsState.get(love.ids.MeetingWidget)
const isMeetingWidgetCreated = meetingWidgetState !== undefined
@@ -211,9 +106,6 @@
onDestroy(() => {
closePopup(myInvitesCategory)
closePopup(inviteCategory)
closePopup(joinRequestCategory)
closePopup(myJoinRequestCategory)
closeWidget(love.ids.MeetingWidget)
})
@@ -15,11 +15,12 @@
<script lang="ts">
import { ButtonBaseSize, IconSize, ModernButton, showPopup } from '@hcengineering/ui'
import { Employee } from '@hcengineering/contact'
import { currentMeetingRoom, sendInvites } from '../../../meetings'
import { currentMeetingRoom } from '../../../meetings'
import love from '../../../plugin'
import { SelectUsersPopup } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { createEventDispatcher } from 'svelte'
import { sendInvites } from '../../../invites'
import { infos } from '../../../stores'
export let employee: Employee | undefined = undefined
@@ -33,7 +34,7 @@
async function invite (): Promise<void> {
if (employee !== undefined) {
await sendInvites([employee._id])
sendInvites([employee._id])
} else {
openSelectUsersPopup()
}
@@ -53,7 +54,7 @@
'top',
(result?: Ref<Employee>[]) => {
if (result != null) {
void sendInvites(result)
sendInvites(result)
}
dispatch('close')
}
@@ -13,17 +13,41 @@
// limitations under the License.
-->
<script lang="ts">
import contact from '@hcengineering/contact'
import contact, { Person } from '@hcengineering/contact'
import { CombineAvatars } from '@hcengineering/contact-resources'
import { Button, Label } from '@hcengineering/ui'
import love from '../../../plugin'
import { cancelInvites } from '../../../meetings'
import { activeInvites } from '../../../stores'
import { Ref } from '@hcengineering/core'
import { onMount } from 'svelte'
import {
cancelInvites,
closeInvitesPopup,
inviteRequestSecondsToLive,
subscribeInviteResponses,
unsubscribeInviteResponses,
updateInvites
} from '../../../invites'
$: persons = $activeInvites.map((p) => p.target)
export let persons: Array<Ref<Person>>
export let meetingId: string
onMount(() => {
void subscribeInviteResponses()
void doUpdateInvites()
const interval = setInterval(doUpdateInvites, (inviteRequestSecondsToLive - 2) * 1000)
return () => {
void unsubscribeInviteResponses()
clearInterval(interval)
void cancelInvites(meetingId)
}
})
async function doUpdateInvites (): Promise<void> {
await updateInvites(persons, meetingId)
}
async function cancel (): Promise<void> {
await cancelInvites($activeInvites)
closeInvitesPopup()
}
</script>
@@ -15,17 +15,16 @@
<script lang="ts">
import { formatName, Person } from '@hcengineering/contact'
import { Avatar, getPersonByPersonRefCb } from '@hcengineering/contact-resources'
import { playNotificationSound } from '@hcengineering/presentation'
import { playSound } from '@hcengineering/presentation'
import { Button, Label } from '@hcengineering/ui'
import { Invite } from '@hcengineering/love'
import { onDestroy, onMount } from 'svelte'
import love from '../../../plugin'
import { acceptInvite, rejectInvite } from '../../../meetings'
export let invite: Invite
import { InviteRequest, responseToInviteRequest } from '../../../invites'
export let invite: InviteRequest
let person: Person | undefined = undefined
$: getPersonByPersonRefCb(invite.from, (p) => {
person = p ?? undefined
})
@@ -33,15 +32,15 @@
let stopSound: (() => void) | null = null
async function accept (): Promise<void> {
await acceptInvite(invite)
await responseToInviteRequest(true)
}
async function decline (): Promise<void> {
await rejectInvite(invite)
await responseToInviteRequest(false)
}
onMount(async () => {
stopSound = await playNotificationSound(love.sound.Knock, love.class.Invite, true)
stopSound = await playSound(love.sound.Knock, true)
})
onDestroy(() => {
@@ -83,10 +82,4 @@
color: var(--caption-color);
font-weight: 700;
}
.roomTitle {
color: var(--caption-color);
font-weight: 700;
font-size: 1rem;
}
</style>
@@ -14,18 +14,40 @@
-->
<script lang="ts">
import { Button, Label } from '@hcengineering/ui'
import { JoinRequest } from '@hcengineering/love'
import love from '../../../plugin'
import { rooms } from '../../../stores'
import { getRoomLabel } from '../../../utils'
import { cancelJoinRequest } from '../../../meetings'
import {
cancelJoinRequest,
closeJoinRequestPopup,
joinRequestSecondsToLive,
subscribeJoinResponses,
unsubscribeJoinResponses,
updateJoinRequest
} from '../../../joinRequests'
import { onMount } from 'svelte'
export let request: JoinRequest
export let meetingId: string
$: room = $rooms.find((p) => p._id === request.room)
$: room = $rooms.find((p) => p._id === meetingId)
onMount(() => {
void subscribeJoinResponses()
void doUpdateRequest()
const interval = setInterval(doUpdateRequest, (joinRequestSecondsToLive - 2) * 1000)
return () => {
void unsubscribeJoinResponses()
clearInterval(interval)
void cancelJoinRequest()
}
})
async function doUpdateRequest (): Promise<void> {
await updateJoinRequest()
}
async function cancel (): Promise<void> {
await cancelJoinRequest(request)
closeJoinRequestPopup()
}
</script>
@@ -15,30 +15,29 @@
<script lang="ts">
import { formatName } from '@hcengineering/contact'
import { Avatar, getPersonByPersonRefStore } from '@hcengineering/contact-resources'
import { playNotificationSound } from '@hcengineering/presentation'
import { playSound } from '@hcengineering/presentation'
import { Button, Label } from '@hcengineering/ui'
import { JoinRequest } from '@hcengineering/love'
import love from '../../../plugin'
import { onDestroy, onMount } from 'svelte'
import { acceptJoinRequest, rejectJoinRequest } from '../../../meetings'
import { responseToJoinRequest, JoinRequest } from '../../../joinRequests'
export let request: JoinRequest
$: personByRefStore = getPersonByPersonRefStore([request.person])
$: person = $personByRefStore.get(request.person)
$: personByRefStore = getPersonByPersonRefStore([request.from])
$: person = $personByRefStore.get(request.from)
let stopSound: (() => void) | null = null
async function accept (): Promise<void> {
await acceptJoinRequest(request)
await responseToJoinRequest(request, true)
}
async function decline (): Promise<void> {
await rejectJoinRequest(request)
await responseToJoinRequest(request, false)
}
onMount(async () => {
stopSound = await playNotificationSound(love.sound.Knock, love.class.JoinRequest, true)
stopSound = await playSound(love.sound.Knock, true)
})
onDestroy(() => {
+234
View File
@@ -0,0 +1,234 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type Ref } from '@hcengineering/core'
import { getCurrentEmployee, type Person } from '@hcengineering/contact'
import { type PopupResult, showPopup } from '@hcengineering/ui'
import { type UnsubscribeCallback } from '@hcengineering/hulypulse-client'
import presentation, { createPulseClient } from '@hcengineering/presentation'
import { getMetadata } from '@hcengineering/platform'
import { get } from 'svelte/store'
import { myInfo } from './stores'
import love, { type Room } from '@hcengineering/love'
import InviteRequestPopup from './components/meeting/invites/InviteRequestPopup.svelte'
import InviteResponsePopup from './components/meeting/invites/InviteResponsePopup.svelte'
import { joinOrCreateMeetingByInvite } from './meetings'
export const inviteRequestSecondsToLive = 5
const responseSecondsToLive = 2
const pulsePrefix = 'love/invite'
export interface InviteRequest {
from: Ref<Person>
meetingId: string
}
export interface InviteResponse {
from: Ref<Person>
meetingId: string
accept: boolean
}
const requestPopupCategory = 'inviteRequest'
let unsubscribeResponse: UnsubscribeCallback | undefined
let requestPopup: PopupResult | undefined
let requestPersons: Array<Ref<Person>> | undefined
export async function subscribeInviteResponses (): Promise<void> {
const client = await createPulseClient()
if (client === undefined) return
const currentPerson = getCurrentEmployee()
unsubscribeResponse = await client.subscribe(
`${getPulsePrefix()}/response/${currentPerson}/`,
(key: string, inviteResponse: InviteResponse | undefined) => {
void onInviteResponse(key, inviteResponse)
}
)
}
export async function unsubscribeInviteResponses (): Promise<void> {
if (unsubscribeResponse !== undefined) {
await unsubscribeResponse()
}
}
export function sendInvites (persons: Array<Ref<Person>>): void {
closeInvitesPopup()
requestPersons = persons
const myParticipation = get(myInfo)
const room = myParticipation?.room
if (room === undefined || room === love.ids.Reception) return
requestPopup = showPopup(InviteRequestPopup, { persons, meetingId: room }, undefined, undefined, undefined, {
category: requestPopupCategory,
overlay: false,
fixed: true
})
}
export function closeInvitesPopup (): void {
requestPopup?.close()
requestPopup = undefined
}
export async function updateInvites (persons: Array<Ref<Person>>, meetingId: string): Promise<void> {
const client = await createPulseClient()
const currentPerson = getCurrentEmployee()
if (client === undefined) return
try {
for (const person of persons) {
await client.put(
`${getPulsePrefix()}/request/${person}/${currentPerson}/${meetingId}`,
{ from: currentPerson, meetingId },
inviteRequestSecondsToLive
)
}
} catch (error) {
console.warn('failed to put invite info:', error)
}
}
export async function cancelInvites (meetingId: string): Promise<void> {
const client = await createPulseClient()
const currentPerson = getCurrentEmployee()
if (client === undefined || requestPersons === undefined) return
try {
for (const person of requestPersons) {
await client.delete(`${getPulsePrefix()}/request/${person}/${currentPerson}/${meetingId}`)
}
} catch (error) {
console.warn('failed to delete invite info:', error)
}
}
async function onInviteResponse (_key: string, response: InviteResponse | undefined): Promise<void> {
const client = await createPulseClient()
if (client === undefined || response === undefined || requestPersons === undefined) return
const currentPerson = getCurrentEmployee()
await client.delete(`${getPulsePrefix()}/request/${response.from}/${currentPerson}/${response.meetingId}`)
await client.delete(`${getPulsePrefix()}/response/${currentPerson}/${response.from}/${response.meetingId}`)
requestPersons = requestPersons.filter((p) => p !== response.from)
if (requestPersons.length === 0) {
closeInvitesPopup()
} else {
requestPopup?.update({ persons: requestPersons })
}
if (!response.accept) return
await joinOrCreateMeetingByInvite(response.meetingId as Ref<Room>)
}
const responsePopupCategory = 'inviteResponse'
let responsePopup: PopupResult | undefined
let unsubscribeRequest: UnsubscribeCallback | undefined
let activeRequestKey: string | undefined
let activeRequestsMap: Map<string, InviteRequest | undefined>
export async function subscribeInviteRequests (): Promise<void> {
const client = await createPulseClient()
if (client === undefined) return
const currentPerson = getCurrentEmployee()
activeRequestsMap = new Map<string, InviteRequest>()
unsubscribeRequest = await client.subscribe(`${getPulsePrefix()}/request/${currentPerson}/`, onInviteRequest)
}
export async function unsubscribeInviteRequests (): Promise<void> {
if (unsubscribeRequest !== undefined) {
await unsubscribeRequest()
}
}
export async function responseToInviteRequest (accept: boolean): Promise<void> {
if (activeRequestKey === undefined) return
const client = await createPulseClient()
if (client === undefined) return
const activeInvite = activeRequestsMap.get(activeRequestKey)
if (activeInvite === undefined) return
const currentPerson = getCurrentEmployee()
await client.put(
`${getPulsePrefix()}/response/${activeInvite.from}/${currentPerson}/${activeInvite.meetingId}`,
{ accept, from: currentPerson, meetingId: activeInvite.meetingId },
responseSecondsToLive
)
responsePopup?.close()
responsePopup = undefined
if (activeRequestKey === undefined) return
activeRequestsMap.set(activeRequestKey, undefined)
activeRequestKey = undefined
if (!accept) return
await joinOrCreateMeetingByInvite(activeInvite.meetingId as Ref<Room>)
}
function onInviteRequest (key: string, request: InviteRequest | undefined): void {
if (request === undefined) {
activeRequestsMap.delete(key)
if (activeRequestKey === key) {
activeRequestKey = undefined
}
} else if (!activeRequestsMap.has(key)) {
activeRequestsMap.set(key, request)
}
let keyChanged: boolean = false
if (activeRequestKey === undefined && activeRequestsMap.size > 0) {
const requests = Array.from(activeRequestsMap.entries())
const unprocessedRequest = requests.find((v: [string, InviteRequest | undefined]) => v[1] !== undefined)
if (unprocessedRequest !== undefined) {
activeRequestKey = unprocessedRequest[0]
keyChanged = true
}
}
if (activeRequestKey === undefined) {
responsePopup?.close()
responsePopup = undefined
return
}
if (responsePopup === undefined) {
responsePopup = showPopup(
InviteResponsePopup,
{ invite: activeRequestsMap.get(activeRequestKey) },
undefined,
undefined,
undefined,
{
category: responsePopupCategory,
overlay: false,
fixed: true
}
)
return
}
if (keyChanged) {
responsePopup?.update({ invite: activeRequestsMap.get(activeRequestKey) })
}
}
function getPulsePrefix (): string {
const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? ''
return `${workspace}/${pulsePrefix}`
}
+227
View File
@@ -0,0 +1,227 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type Ref } from '@hcengineering/core'
import { getCurrentEmployee, type Person } from '@hcengineering/contact'
import { type PopupResult, showPopup } from '@hcengineering/ui'
import { type UnsubscribeCallback } from '@hcengineering/hulypulse-client'
import presentation, { createPulseClient } from '@hcengineering/presentation'
import { getMetadata } from '@hcengineering/platform'
import { type Room } from '@hcengineering/love'
import { joinOrCreateMeetingByInvite } from './meetings'
import JoinRequestPopup from './components/meeting/invites/JoinRequestPopup.svelte'
import JoinResponsePopup from './components/meeting/invites/JoinResponsePopup.svelte'
export const joinRequestSecondsToLive = 5
const responseSecondsToLive = 2
const pulsePrefix = 'love/join'
export interface JoinRequest {
from: Ref<Person>
meetingId: string
}
export interface JoinResponse {
meetingId: string
accept: boolean
}
const requestPopupCategory = 'joinRequest'
let unsubscribeResponse: UnsubscribeCallback | undefined
let requestPopup: PopupResult | undefined
let requestMeetingId: string | undefined
export async function subscribeJoinResponses (): Promise<void> {
const client = await createPulseClient()
const currentPerson = getCurrentEmployee()
if (client === undefined) return
unsubscribeResponse = await client.subscribe(
`${getPulsePrefix()}/response/${currentPerson}/`,
(key: string, inviteResponse: JoinResponse | undefined) => {
void onJoinResponse(key, inviteResponse)
}
)
}
export async function unsubscribeJoinResponses (): Promise<void> {
if (unsubscribeResponse !== undefined) {
await unsubscribeResponse()
}
}
export function sendJoinRequest (meetingId: string): void {
closeJoinRequestPopup()
requestMeetingId = meetingId
requestPopup = showPopup(JoinRequestPopup, { meetingId }, undefined, undefined, undefined, {
category: requestPopupCategory,
overlay: false,
fixed: true
})
}
export function closeJoinRequestPopup (): void {
requestPopup?.close()
requestPopup = undefined
}
export async function updateJoinRequest (): Promise<void> {
const client = await createPulseClient()
const currentPerson = getCurrentEmployee()
if (client === undefined) return
try {
await client.put(
`${getPulsePrefix()}/request/${requestMeetingId}/${currentPerson}`,
{ from: currentPerson, meetingId: requestMeetingId },
joinRequestSecondsToLive
)
} catch (error) {
console.warn('failed to put joinRequest:', error)
}
}
export async function cancelJoinRequest (): Promise<void> {
const client = await createPulseClient()
const currentPerson = getCurrentEmployee()
if (client === undefined) return
try {
await client.delete(`${getPulsePrefix()}/request/${requestMeetingId}/${currentPerson}`)
} catch (error) {
console.warn('failed to delete invite info:', error)
}
requestMeetingId = undefined
}
async function onJoinResponse (_key: string, response: JoinResponse | undefined): Promise<void> {
const client = await createPulseClient()
if (
client === undefined ||
response === undefined ||
requestMeetingId === undefined ||
requestMeetingId !== response.meetingId
) {
return
}
const currentPerson = getCurrentEmployee()
await client.delete(`${getPulsePrefix()}/request/${requestMeetingId}/${currentPerson}`)
await client.delete(`${getPulsePrefix()}/response/${currentPerson}/${requestMeetingId}`)
closeJoinRequestPopup()
if (!response.accept) return
await joinOrCreateMeetingByInvite(response.meetingId as Ref<Room>)
}
const responsePopupCategory = 'joinResponse'
let responsePopup: PopupResult | undefined
let unsubscribeRequest: UnsubscribeCallback | undefined
let activeRequestKey: string | undefined
let activeRequestsMap: Map<string, JoinRequest | undefined> | undefined
export async function subscribeJoinRequests (meetingId: string | undefined): Promise<void> {
if (meetingId === undefined) return
const client = await createPulseClient()
if (client === undefined) return
activeRequestsMap = new Map<string, JoinRequest>()
unsubscribeRequest = await client.subscribe(`${getPulsePrefix()}/request/${meetingId}/`, onJoinRequest)
}
export async function unsubscribeJoinRequests (): Promise<void> {
activeRequestsMap = undefined
if (unsubscribeRequest !== undefined) {
await unsubscribeRequest()
}
}
export async function responseToJoinRequest (joinRequest: JoinRequest, accept: boolean): Promise<void> {
if (activeRequestKey === undefined) return
const client = await createPulseClient()
if (client === undefined || activeRequestsMap === undefined) return
const activeRequest = activeRequestsMap.get(activeRequestKey)
if (activeRequest === undefined) return
await client.put(
`${getPulsePrefix()}/response/${joinRequest.from}/${joinRequest.meetingId}`,
{ accept, meetingId: joinRequest.meetingId },
responseSecondsToLive
)
responsePopup?.close()
responsePopup = undefined
if (activeRequestKey === undefined) return
activeRequestsMap.set(activeRequestKey, undefined)
activeRequestKey = undefined
if (!accept) return
await joinOrCreateMeetingByInvite(joinRequest.meetingId as Ref<Room>)
}
function onJoinRequest (key: string, request: JoinRequest | undefined): void {
if (activeRequestsMap === undefined) return
if (request === undefined) {
activeRequestsMap.delete(key)
if (activeRequestKey === key) {
activeRequestKey = undefined
}
} else if (!activeRequestsMap.has(key)) {
activeRequestsMap.set(key, request)
}
let keyChanged: boolean = false
if (activeRequestKey === undefined && activeRequestsMap.size > 0) {
const requests = Array.from(activeRequestsMap.entries())
const unprocessedRequest = requests.find((v: [string, JoinRequest | undefined]) => v[1] !== undefined)
if (unprocessedRequest !== undefined) {
activeRequestKey = unprocessedRequest[0]
keyChanged = true
}
}
if (activeRequestKey === undefined) {
responsePopup?.close()
responsePopup = undefined
return
}
if (responsePopup === undefined) {
responsePopup = showPopup(
JoinResponsePopup,
{ request: activeRequestsMap.get(activeRequestKey) },
undefined,
undefined,
undefined,
{
category: responsePopupCategory,
overlay: false,
fixed: true
}
)
return
}
if (keyChanged) {
responsePopup?.update({ invite: activeRequestsMap.get(activeRequestKey) })
}
}
function getPulsePrefix (): string {
const workspace = getMetadata(presentation.metadata.WorkspaceUuid) ?? ''
return `${workspace}/${pulsePrefix}`
}
+10 -124
View File
@@ -1,16 +1,6 @@
import core, { AccountRole, getCurrentAccount, type Ref } from '@hcengineering/core'
import love, {
getFreeRoomPlace,
MeetingStatus,
type Room,
RoomType,
isOffice,
RoomAccess,
RequestStatus,
type JoinRequest,
type Invite
} from '@hcengineering/love'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import love, { getFreeRoomPlace, MeetingStatus, type Room, RoomType, isOffice, RoomAccess } from '@hcengineering/love'
import presentation, { getClient } from '@hcengineering/presentation'
import {
closeMeetingMinutes,
getLiveKitEndpoint,
@@ -25,10 +15,9 @@ import { infos, myInfo, myOffice, rooms } from './stores'
import { getCurrentEmployee, type Person } from '@hcengineering/contact'
import { getPersonByPersonRef } from '@hcengineering/contact-resources'
import { getMetadata } from '@hcengineering/platform'
import { sendJoinRequest, unsubscribeJoinRequests } from './joinRequests'
export let currentMeetingRoom: Ref<Room> | undefined
const requestsQuery = createQuery(true)
const invitesQuery = createQuery(true)
export async function createMeeting (room: Room): Promise<void> {
if (room.access === RoomAccess.DND) return
@@ -37,7 +26,7 @@ export async function createMeeting (room: Room): Promise<void> {
const currentPerson = await getPersonByPersonRef(me)
if (isOffice(room) && room.person !== currentPerson?._id) {
await sendJoinRequest(room)
sendJoinRequest(room._id)
return
}
@@ -79,6 +68,7 @@ export async function leaveMeeting (): Promise<void> {
}
}
await liveKitClient.disconnect()
await unsubscribeJoinRequests()
closeMeetingMinutes()
currentMeetingRoom = undefined
}
@@ -88,17 +78,18 @@ export async function joinMeeting (room: Room): Promise<void> {
const isGuest = getCurrentAccount().role === AccountRole.Guest
if (room.access === RoomAccess.Knock || isOffice(room) || isGuest) {
await sendJoinRequest(room)
sendJoinRequest(room._id)
return
}
await connectToMeeting(room)
}
export async function acceptInvite (invite: Invite): Promise<void> {
export async function joinOrCreateMeetingByInvite (roomId: Ref<Room>): Promise<void> {
if (currentMeetingRoom === roomId) return
const client = getClient()
await client.update(invite, { status: RequestStatus.Approved })
const room = getRoomById(invite.room)
const room = getRoomById(roomId)
if (room === undefined) return
@@ -113,111 +104,6 @@ export async function acceptInvite (invite: Invite): Promise<void> {
await connectToMeeting(room)
}
export async function rejectInvite (invite: Invite): Promise<void> {
const client = getClient()
await client.update(invite, { status: RequestStatus.Rejected })
}
export async function cancelInvites (invites: Invite[]): Promise<void> {
const client = getClient()
for (const invite of invites) {
if (invite.status === RequestStatus.Pending) {
await client.remove(invite)
}
}
}
export async function sendInvites (persons: Array<Ref<Person>>): Promise<void> {
const myParticipation = get(myInfo)
const room = myParticipation?.room
if (room === undefined || room === love.ids.Reception) return
const client = getClient()
const me = getCurrentEmployee()
const inviteIds: Array<Ref<Invite>> = []
for (const person of persons) {
inviteIds.push(
await client.createDoc(love.class.Invite, core.space.Workspace, {
target: person,
room,
status: RequestStatus.Pending,
from: me
})
)
}
invitesQuery.query(love.class.Invite, { from: me, _id: { $in: inviteIds } }, (res) => {
for (const invite of res) {
if (invite === undefined || invite.status !== RequestStatus.Approved) {
continue
}
invitesQuery.unsubscribe()
const room = getRoomById(invite.room)
if (room === undefined) return
void connectToMeeting(room)
}
})
}
export async function acceptJoinRequest (request: JoinRequest): Promise<void> {
const client = getClient()
const room = getRoomById(request.room)
if (room === undefined) return
const meeting = await client.findOne(love.class.MeetingMinutes, {
attachedTo: room._id,
status: MeetingStatus.Active
})
if (meeting === undefined) {
await createMeetingDocument(room)
}
await client.update(request, { status: RequestStatus.Approved })
await connectToMeeting(room)
}
export async function rejectJoinRequest (request: JoinRequest): Promise<void> {
const client = getClient()
await client.update(request, { status: RequestStatus.Rejected })
}
export async function cancelJoinRequest (request: JoinRequest): Promise<void> {
if (request.status === RequestStatus.Pending) {
const client = getClient()
await client.remove(request)
}
}
async function sendJoinRequest (room: Room): Promise<void> {
if (room.access === RoomAccess.DND) return
const me = getCurrentEmployee()
const currentPerson = await getPersonByPersonRef(me)
if (currentPerson == null) {
return
}
if (isOffice(room) && room.person === currentPerson._id) {
return
}
const client = getClient()
const _id = await client.createDoc(love.class.JoinRequest, core.space.Workspace, {
person: currentPerson._id,
room: room._id,
status: RequestStatus.Pending
})
requestsQuery.query(love.class.JoinRequest, { person: me, _id }, (res) => {
const req = res[0]
if (req === undefined) return
if (req.status === RequestStatus.Pending) return
requestsQuery.unsubscribe()
if (req.status === RequestStatus.Approved) {
void connectToMeeting(room)
}
})
}
export async function kick (person: Ref<Person>): Promise<void> {
const client = getClient()
const allRooms = get(rooms)
+3 -37
View File
@@ -3,11 +3,8 @@ import { getCurrentEmployee } from '@hcengineering/contact'
import { getPersonRefByPersonId } from '@hcengineering/contact-resources'
import { type Ref } from '@hcengineering/core'
import {
RequestStatus,
type DevicesPreference,
type Floor,
type Invite,
type JoinRequest,
type MeetingMinutes,
type Office,
type ParticipantInfo,
@@ -43,16 +40,6 @@ export const activeFloor = derived([rooms, myInfo, myOffice], ([rooms, myInfo, m
}
return res ?? love.ids.MainFloor
})
export const myRequests = writable<JoinRequest[]>([])
export const invites = writable<Invite[]>([])
export const myInvites = derived([invites, myInfo], ([val, info]) => {
const personId = getCurrentEmployee()
return val.filter((p) => p.target === personId && info?.room !== p.room)
})
export const activeInvites = derived(invites, (val) => {
const personId = getCurrentEmployee()
return val.filter((p) => p.from === personId)
})
export const myPreferences = writable<DevicesPreference | undefined>()
export let $myPreferences: DevicesPreference | undefined
@@ -79,9 +66,7 @@ const officeLoaded = writable(false)
const query = createQuery(true)
const statusQuery = createQuery(true)
const floorsQuery = createQuery(true)
const requestsQuery = createQuery(true)
const preferencesQuery = createQuery(true)
const invitesQuery = createQuery(true)
onClient(() => {
const roomPromise = new Promise<void>((resolve) =>
@@ -102,16 +87,6 @@ onClient(() => {
resolve()
})
)
const requestPromise = new Promise<void>((resolve) =>
requestsQuery.query(
love.class.JoinRequest,
{ person: getCurrentEmployee(), status: RequestStatus.Pending },
(res) => {
myRequests.set(res)
resolve()
}
)
)
const preferencePromise = new Promise<void>((resolve) =>
preferencesQuery.query(love.class.DevicesPreference, {}, (res) => {
myPreferences.set(res[0])
@@ -120,18 +95,9 @@ onClient(() => {
})
)
const invitesPromise = new Promise<void>((resolve) =>
invitesQuery.query(love.class.Invite, { status: RequestStatus.Pending }, (res) => {
invites.set(res)
resolve()
})
)
void Promise.all([roomPromise, infoPromise, floorPromise, requestPromise, preferencePromise, invitesPromise]).then(
() => {
officeLoaded.set(true)
}
)
void Promise.all([roomPromise, infoPromise, floorPromise, preferencePromise]).then(() => {
officeLoaded.set(true)
})
})
export async function waitForOfficeLoaded (): Promise<void> {
-3
View File
@@ -21,7 +21,6 @@ import core, {
import login from '@hcengineering/login'
import {
isOffice,
type JoinRequest,
LoveEvents,
loveId,
type Meeting,
@@ -280,8 +279,6 @@ export async function navigateToMeetingMinutes (room: Room): Promise<void> {
await navigateToOfficeDoc(room)
}
export const joinRequest: Ref<JoinRequest> | undefined = undefined
export function calculateFloorSize (_rooms: Room[], _preview?: boolean): number {
let fH: number = 5
_rooms.forEach((room) => {
-4
View File
@@ -8,8 +8,6 @@ import { Widget } from '@hcengineering/workbench'
import {
DevicesPreference,
Floor,
Invite,
JoinRequest,
Meeting,
MeetingMinutes,
MeetingSchedule,
@@ -28,10 +26,8 @@ const love = plugin(loveId, {
Floor: '' as Ref<Class<Floor>>,
Office: '' as Ref<Class<Office>>,
ParticipantInfo: '' as Ref<Class<ParticipantInfo>>,
JoinRequest: '' as Ref<Class<JoinRequest>>,
DevicesPreference: '' as Ref<Class<DevicesPreference>>,
RoomInfo: '' as Ref<Class<RoomInfo>>,
Invite: '' as Ref<Class<Invite>>,
MeetingMinutes: '' as Ref<Class<MeetingMinutes>>
},
mixin: {
-19
View File
@@ -127,25 +127,6 @@ export interface MeetingSchedule extends Schedule {
room: Ref<Room>
}
export enum RequestStatus {
Pending,
Approved,
Rejected
}
export interface JoinRequest extends Doc {
person: Ref<Person>
room: Ref<Room>
status: RequestStatus
}
export interface Invite extends Doc {
from: Ref<Person>
target: Ref<Person>
room: Ref<Room>
status: RequestStatus
}
export interface DevicesPreference extends Preference {
micEnabled: boolean
noiseCancellation: boolean
@@ -16,7 +16,7 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte'
import { HulypulseClient } from '@hcengineering/hulypulse-client'
import { createPulseClient } from '../pulse'
import { createPulseClient } from '@hcengineering/presentation'
let parentElement: HTMLDivElement
-1
View File
@@ -22,7 +22,6 @@ import { getFollowee, publishData, followeeDataSubscribe, followeeDataUnsubscrib
export { Presence, PresenceAvatars }
export * from './pulse'
export * from './presence'
export * from './typing'
export * from './types'
+1 -3
View File
@@ -15,9 +15,7 @@ import { type Employee, type Person } from '@hcengineering/contact'
import { type UnsubscribeCallback, type Callback } from '@hcengineering/hulypulse-client'
import { type Class, type Doc, type Ref } from '@hcengineering/core'
import { getMetadata } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import { createPulseClient } from './pulse'
import presentation, { createPulseClient } from '@hcengineering/presentation'
export interface PresenceInfo {
personId: Ref<Person>
+1 -2
View File
@@ -14,9 +14,8 @@
import { type Employee, type Person } from '@hcengineering/contact'
import { type UnsubscribeCallback, type Callback } from '@hcengineering/hulypulse-client'
import { getMetadata } from '@hcengineering/platform'
import presentation from '@hcengineering/presentation'
import presentation, { createPulseClient } from '@hcengineering/presentation'
import { type Doc, type Ref } from '@hcengineering/core'
import { createPulseClient } from './pulse'
const typingDelaySeconds = 2
+3 -150
View File
@@ -13,7 +13,7 @@
// limitations under the License.
//
import contact, { Employee, formatName, getName, Person } from '@hcengineering/contact'
import contact, { Employee, getName, Person } from '@hcengineering/contact'
import core, {
type AccountUuid,
combineAttributes,
@@ -31,28 +31,18 @@ import core, {
UserStatus
} from '@hcengineering/core'
import love, {
Invite,
isOffice,
JoinRequest,
loveId,
MeetingMinutes,
MeetingStatus,
Office,
ParticipantInfo,
RequestStatus,
Room,
RoomAccess,
RoomInfo
} from '@hcengineering/love'
import notification from '@hcengineering/notification'
import { getMetadata, translate } from '@hcengineering/platform'
import { getSocialStrings } from '@hcengineering/server-contact'
import { getMetadata } from '@hcengineering/platform'
import serverCore, { TriggerControl } from '@hcengineering/server-core'
import {
createPushNotification,
getNotificationProviderControl,
isAllowed
} from '@hcengineering/server-notification-resources'
import view from '@hcengineering/view'
import { workbenchId } from '@hcengineering/workbench'
@@ -290,29 +280,6 @@ async function getRoomName (control: TriggerControl, roomId: Ref<Room>): Promise
return room.name
}
async function rejectJoinRequests (info: ParticipantInfo, control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {})
const oldRoomInfo = roomInfos.find((ri) => ri.persons.includes(info.person))
if (oldRoomInfo !== undefined) {
const restPersons = oldRoomInfo.persons.filter((p) => p !== info.person)
if (restPersons.length === 0) {
const requests = await control.findAll(control.ctx, love.class.JoinRequest, {
room: oldRoomInfo.room,
status: RequestStatus.Pending
})
for (const request of requests) {
res.push(
control.txFactory.createTxUpdateDoc(love.class.JoinRequest, core.space.Workspace, request._id, {
status: RequestStatus.Rejected
})
)
}
}
}
return res
}
async function setDefaultRoomAccess (info: ParticipantInfo, control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
const roomInfos = await control.queryFind(control.ctx, love.class.RoomInfo, {})
@@ -387,7 +354,6 @@ export async function OnParticipantInfo (txes: Tx[], control: TriggerControl): P
if (info === undefined) {
continue
}
result.push(...(await rejectJoinRequests(info, control)))
result.push(...(await setDefaultRoomAccess(info, control)))
result.push(...(await roomJoinHandler(info, control)))
}
@@ -395,117 +361,6 @@ export async function OnParticipantInfo (txes: Tx[], control: TriggerControl): P
return result
}
export async function OnKnock (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
for (const tx of txes) {
const actualTx = tx as TxCreateDoc<JoinRequest>
if (actualTx._class === core.class.TxCreateDoc) {
const request = TxProcessor.createDoc2Doc(actualTx)
if (request.status === RequestStatus.Pending) {
const roomInfo = (await control.findAll(control.ctx, love.class.RoomInfo, { room: request.room }))[0]
if (roomInfo !== undefined) {
const from = (await control.findAll(control.ctx, contact.class.Person, { _id: request.person }))[0]
if (from === undefined) {
continue
}
const type = await control.modelDb.findOne(notification.class.NotificationType, {
_id: love.ids.KnockNotification
})
if (type === undefined) {
continue
}
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
_id: notification.providers.PushNotificationProvider
})
if (provider === undefined) {
continue
}
const notificationControl = await getNotificationProviderControl(control.ctx, control)
for (const user of roomInfo.persons) {
const socialStrings = await getSocialStrings(control, user)
if (socialStrings.length === 0) continue
if (isAllowed(control, socialStrings, type, provider, notificationControl)) {
const path = [workbenchId, control.workspace.url, loveId]
const title = await translate(love.string.KnockingLabel, {})
const body = await translate(love.string.IsKnocking, {
name: formatName(from.name, control.branding?.lastNameFirst)
})
const employee = await control.findAll(
control.ctx,
contact.mixin.Employee,
{ _id: user as Ref<Employee> },
{ limit: 1 }
)
const account = employee[0]?.personUuid
if (account === undefined) continue
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
user: account
})
await createPushNotification(control, account, title, body, request._id, subscriptions, from, path)
}
}
}
}
}
}
return []
}
export async function OnInvite (txes: Tx[], control: TriggerControl): Promise<Tx[]> {
for (const tx of txes) {
const actualTx = tx as TxCreateDoc<Invite>
if (actualTx._class === core.class.TxCreateDoc) {
const invite = TxProcessor.createDoc2Doc(actualTx)
if (invite.status === RequestStatus.Pending) {
const target = (
await control.findAll(
control.ctx,
contact.mixin.Employee,
{ _id: invite.target as Ref<Employee> },
{ limit: 1 }
)
)[0]
if (target === undefined) {
continue
}
const from = (await control.findAll(control.ctx, contact.class.Person, { _id: invite.from }))[0]
const type = await control.modelDb.findOne(notification.class.NotificationType, {
_id: love.ids.InviteNotification
})
if (type === undefined) {
continue
}
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
_id: notification.providers.PushNotificationProvider
})
if (provider === undefined) {
continue
}
const notificationControl = await getNotificationProviderControl(control.ctx, control)
const socialStrings = await getSocialStrings(control, target._id)
if (isAllowed(control, socialStrings, type, provider, notificationControl)) {
const path = [workbenchId, control.workspace.url, loveId]
const title = await translate(love.string.InivitingLabel, {})
const body =
from !== undefined
? await translate(love.string.InvitingYou, {
name: formatName(from.name, control.branding?.lastNameFirst)
})
: await translate(love.string.InivitingLabel, {})
const account = target?.personUuid
if (account === undefined) continue
const subscriptions = await control.findAll(control.ctx, notification.class.PushSubscription, {
user: account
})
await createPushNotification(control, account, title, body, invite._id, subscriptions, from, path)
}
}
}
}
return []
}
export async function meetingMinutesHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
const meetingMinutes = doc as MeetingMinutes
const front = control.branding?.front ?? getMetadata(serverCore.metadata.FrontUrl) ?? ''
@@ -587,8 +442,6 @@ export default async () => ({
OnEmployee,
OnUserStatus,
OnParticipantInfo,
OnRoomInfo,
OnKnock,
OnInvite
OnRoomInfo
}
})
-1
View File
@@ -21,7 +21,6 @@ export default plugin(serverLoveId, {
OnUserStatus: '' as Resource<TriggerFunc>,
OnParticipantInfo: '' as Resource<TriggerFunc>,
OnRoomInfo: '' as Resource<TriggerFunc>,
OnInvite: '' as Resource<TriggerFunc>,
OnKnock: '' as Resource<TriggerFunc>
}
})