Skip to content

Overview

  • File: src/components/app/call/notification.svelte

The Call Notification component handles incoming call notifications in the HVC Web application, providing real-time call alerts with audio feedback and user interaction options.

Type Definitions

Call Data Interface

svelte
interface ICallData {
  expires: Date;        // Call expiration time
  groupId: string;      // Unique group identifier
  groupName: string;    // Name of the group/meeting
  callerName: string;   // Name of the caller
}

Audio Management

Buzz Sound Setup

svelte
let audioInstance;
export let audio = audioInstance;

export function play() {
  audioInstance?.play();
}

export function stop() {
  audioInstance?.stop();
}

function init() {
  if (!window["buzz"]) {
    setTimeout(init, 100);
    return;
  }
  audioInstance = new window["buzz"].sound("/assets/media/ringtone", {
    formats: ["mp3"],
    preload: true,
    autoplay: false,
    loop: true
  });
}

Props

Configuration Props

svelte
export let handleLocally = true;      // Handle call actions locally
export let expirySeconds = 45;        // Call notification expiry time
export let refreshRate = 2000;        // Status check interval

State Management

Local State

svelte
let pendingCalls: ICallData[] = [];    // Active call notifications
let intervalHandle: number;            // Status check interval

Reactive Statements

svelte
$: myId = $userInfo?.id;
$: monitorSounds(pendingCalls);

Event Handling

Call Actions

svelte
async function accept(evt: ICallData) {
  if (handleLocally) {
    await acceptCall(evt.groupId);
    pendingCalls = pendingCalls.filter(x => x.groupId !== evt.groupId);
    const url = `/private/events/${evt.groupId}`;
    window.open(url);
  } else {
    dispatch("accept", evt);
  }
}

async function reject(evt: ICallData) {
  if (handleLocally) {
    pendingCalls = pendingCalls.filter(x => x.groupId !== evt.groupId);
    await cancelCall(evt.groupId, "Cancelled by User");
  } else {
    dispatch("reject", evt);
  }
}

Event Subscriptions

svelte
// Subscribe to call events
subscribe("onNewCall", onNewCall);
subscribe("onEndCall", onEndCall);
subscribe("onMeetingEvent", onMeetingEvent);

// Cleanup subscriptions
onDestroy(() => {
  unsubscribe("onNewCall", onNewCall);
  unsubscribe("onEndCall", onEndCall);
  unsubscribe("onMeetingEvent", onMeetingEvent);
});

Event Handlers

svelte
// New call received
async function onNewCall(data) {
  data.expires = dayjs().add(expirySeconds, "s").toDate();
  pendingCalls = [...pendingCalls, data];
  play();
}

// Call ended
async function onEndCall({groupId, reason}) {
  pendingCalls = pendingCalls.filter(x => x.groupId !== groupId);
}

// Meeting events
async function onMeetingEvent(data) {
  switch (data?.eventName) {
    case "Cancelled":
      remove(data.groupId, data);
      break;
    case "Started":
    case "User Joined":
      if (data.userId === myId) {
        remove(data.groupId, data);
      }
      break;
  }
}

User Interactions

Call Actions

svelte
// Accept call
async function accept(evt: ICallData) {
  if (handleLocally) {
    await acceptCall(evt.groupId);
    pendingCalls = pendingCalls.filter(x => x.groupId !== evt.groupId);
    window.open(`/private/events/${evt.groupId}`);
  } else {
    dispatch("accept", evt);
  }
}

// Reject call
async function reject(evt: ICallData) {
  if (handleLocally) {
    pendingCalls = pendingCalls.filter(x => x.groupId !== evt.groupId);
    await cancelCall(evt.groupId, "Cancelled by User");
  } else {
    dispatch("reject", evt);
  }
}

UI Implementation

Notification Container

svelte
<div aria-live="assertive" class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6 z-[100]">
  <div class="flex w-full flex-col items-center space-y-4 sm:items-end">
    {#each pendingCalls as call}
      <!-- Call notification card -->
    {/each}
  </div>
</div>

Call Card Template

svelte
<div class="pointer-events-auto w-full max-w-sm rounded-lg bg-white shadow-lg">
  <div class="p-4">
    <div class="flex items-start">
      <!-- Call icon -->
      <Icon src={PhoneIncoming} size="40" class="animate-bounce text-green-500" />
      
      <!-- Call details -->
      <div class="ml-3 w-0 flex-1">
        <p class="text-sm font-medium text-gray-900">{call.callerName}</p>
        <p class="mt-1 text-sm text-gray-500">{call.groupName}</p>
        
        <!-- Action buttons -->
        <div class="mt-4 flex">
          <button on:click={_ => accept(call)}>Accept</button>
          <button on:click={_ => reject(call)}>Decline</button>
        </div>
      </div>
    </div>
  </div>
</div>

Lifecycle Management

Component Initialization

svelte
onMount(() => {
  // Start status check interval
  intervalHandle = setInterval(() => {
    if (pendingCalls.find(x => x.expires < new Date())) {
      pendingCalls = pendingCalls.filter(x => x.expires > new Date());
    }
  }, refreshRate);
});

Cleanup

svelte
onDestroy(() => {
  // Clear interval
  clearInterval(intervalHandle);
  
  // Unsubscribe from events
  unsubscribe("onNewCall", onNewCall);
  unsubscribe("onEndCall", onEndCall);
  unsubscribe("onMeetingEvent", onMeetingEvent);
});

Usage

Basic Implementation

svelte
<script>
  import CallNotification from './components/app/call/notification.svelte';
</script>

<CallNotification />

Custom Event Handling

svelte
<CallNotification
  handleLocally={false}
  on:accept={handleAccept}
  on:reject={handleReject}
/>

Released under the MIT License.