Skip to content

Commit

Permalink
Add more types to <whereby-embed> web component
Browse files Browse the repository at this point in the history
When used in React context, you are now able to get a typed ref when
using the web component. The typed ref allows you to listen to and
handle events with type safety, and also issue commands in a type-safe
manner.
  • Loading branch information
havardholvik committed Jan 23, 2024
1 parent e34f003 commit 62954c3
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 11 deletions.
60 changes: 54 additions & 6 deletions src/lib/embed/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { define, ref } from "heresy";
import { ReactHTMLElement } from "react";

import { parseRoomUrlAndSubdomain } from "../utils/roomUrl";
import { sdkVersion } from "../version";

interface WherebyEmbedAttributes {
interface WherebyEmbedAttributes extends ReactHTMLElement<HTMLElement> {
audio: string;
avatarUrl: string;
background: string;
Expand Down Expand Up @@ -38,6 +39,42 @@ interface WherebyEmbedAttributes {
video: string;
virtualBackgroundUrl: string;
}

interface WherebyEmbedElementEventMap {
ready: CustomEvent;
knock: CustomEvent;
participantupdate: CustomEvent<{ count: number }>;
join: CustomEvent;
leave: CustomEvent<{ removed: boolean }>;
participant_join: CustomEvent<{ participant: { metadata: string } }>;
participant_leave: CustomEvent<{ participant: { metadata: string } }>;
microphone_toggle: CustomEvent<{ enabled: boolean }>;
camera_toggle: CustomEvent<{ enabled: boolean }>;
chat_toggle: CustomEvent<{ open: boolean }>;
pip_toggle: CustomEvent<{ open: boolean }>;
deny_device_permission: CustomEvent<{ denied: boolean }>;
screenshare_toggle: CustomEvent<{ enabled: boolean }>;
streaming_status_change: CustomEvent<{ status: string }>;
connection_status_change: CustomEvent<{ status: "stable" | "unstable" }>;
}

export interface WherebyEmbedElement extends HTMLIFrameElement {
addEventListener<K extends keyof (WherebyEmbedElementEventMap & HTMLElementEventMap)>(
type: K,
listener: (this: HTMLIFrameElement, ev: (WherebyEmbedElementEventMap & HTMLElementEventMap)[K]) => void,
options?: boolean | AddEventListenerOptions | undefined
): void;

startRecording: () => void;
stopRecording: () => void;
startStreaming: () => void;
stopStreaming: () => void;
toggleCamera: (enabled?: boolean) => void;
toggleMicrophone: (enabled?: boolean) => void;
toggleScreenshare: (enabled?: boolean) => void;
toogleChat: (enabled?: boolean) => void;
}

declare global {
namespace JSX {
interface IntrinsicElements {
Expand Down Expand Up @@ -132,22 +169,33 @@ define("WherebyEmbed", {
stopStreaming() {
this._postCommand("stop_streaming");
},
toggleCamera(enabled: boolean) {
toggleCamera(enabled?: boolean) {
this._postCommand("toggle_camera", [enabled]);
},
toggleMicrophone(enabled: boolean) {
toggleMicrophone(enabled?: boolean) {
this._postCommand("toggle_microphone", [enabled]);
},
toggleScreenshare(enabled: boolean) {
toggleScreenshare(enabled?: boolean) {
this._postCommand("toggle_screenshare", [enabled]);
},
toggleChat(enabled: boolean) {
toggleChat(enabled?: boolean) {
this._postCommand("toggle_chat", [enabled]);
},

onmessage({ origin, data }: { origin: string; data: { type: string; payload: string } }) {
/**
* This is not typed yet, refer to https://www.divotion.com/blog/creating-type-safe-events
* on how to achieve type safety.
*/
onmessage<E extends keyof WherebyEmbedElementEventMap>({
origin,
data,
}: {
origin: string;
data: { type: E; payload: WherebyEmbedElementEventMap[E] };
}) {
if (!this.roomUrl || origin !== this.roomUrl.origin) return;
const { type, payload: detail } = data;

this.dispatchEvent(new CustomEvent(type, { detail }));
},
render() {
Expand Down
24 changes: 19 additions & 5 deletions src/stories/prebuilt-ui.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Story } from "@storybook/react";
import React from "react";
import "../lib/embed";
import React, { useEffect, useRef, useState } from "react";
import type { WherebyEmbedElement } from "../lib/embed";

interface WherebyEmbedAttributes {
audio: boolean;
Expand Down Expand Up @@ -75,8 +75,21 @@ const WherebyEmbed = ({
video,
virtualBackgroundUrl,
}: Partial<WherebyEmbedAttributes>) => {
const elmRef = useRef<WherebyEmbedElement>(null);
const [cameraEnabled, setCameraEnabled] = useState(false);

useEffect(() => {
const element = elmRef.current;

element?.addEventListener("camera_toggle", (e) => {
const cameraEnabled = e.detail.enabled;
setCameraEnabled(cameraEnabled);
});
}, []);

return (
<p>
<p>Camera enabled: {cameraEnabled}</p>
<whereby-embed
audio={offOn(audio)}
avatarUrl={avatarUrl}
Expand All @@ -98,15 +111,16 @@ const WherebyEmbed = ({
virtualBackgroundUrl={virtualBackgroundUrl}
room={room}
style={{ height: "100vh", width: "100%" }}
ref={elmRef}
/>
</p>
);
};

const Template: Story<Partial<WherebyEmbedAttributes>> = (args) => WherebyEmbed(args);
export const WherebyEmbedElement = Template.bind({});
export const WherebyEmbedElementExample = Template.bind({});

WherebyEmbedElement.args = {
WherebyEmbedElementExample.args = {
audio: true,
avatarUrl: "",
background: true,
Expand All @@ -127,7 +141,7 @@ WherebyEmbedElement.args = {
virtualBackgroundUrl: "",
};

WherebyEmbedElement.parameters = {
WherebyEmbedElementExample.parameters = {
docs: {
transformSource: (src: string) => {
return (src || "").replace(/><iframe(.+)$/, " />");
Expand Down

0 comments on commit 62954c3

Please sign in to comment.