@tinyweb_dev/oe-meeting-react 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  DEFAULT_IFRAME_ALLOW: () => DEFAULT_IFRAME_ALLOW,
25
25
  EMBED_MESSAGE_SOURCE: () => EMBED_MESSAGE_SOURCE,
26
26
  EMBED_PROTOCOL_VERSION: () => EMBED_PROTOCOL_VERSION,
27
+ EmbedBackgroundType: () => EmbedBackgroundType,
27
28
  EmbedCapability: () => EmbedCapability,
28
29
  EmbedCaptureOutcome: () => EmbedCaptureOutcome,
29
30
  EmbedCommandAction: () => EmbedCommandAction,
@@ -32,14 +33,17 @@ __export(index_exports, {
32
33
  EmbedEventType: () => EmbedEventType,
33
34
  EmbedQueryParam: () => EmbedQueryParam,
34
35
  EmbedRole: () => EmbedRole,
36
+ MAX_BACKGROUND_URL_LENGTH: () => MAX_BACKGROUND_URL_LENGTH,
35
37
  MAX_STAGE_TOOL_ID_LENGTH: () => MAX_STAGE_TOOL_ID_LENGTH,
36
38
  MAX_STAGE_URL_LENGTH: () => MAX_STAGE_URL_LENGTH,
37
39
  OeMeeting: () => OeMeeting,
38
40
  buildEmbedUrl: () => buildEmbedUrl,
39
41
  isStageToolId: () => isStageToolId,
40
42
  isValidStageUrl: () => isValidStageUrl,
43
+ normalizeEmbedBackground: () => normalizeEmbedBackground,
41
44
  normalizeOpenStageInput: () => normalizeOpenStageInput,
42
45
  parseEmbedEvent: () => parseEmbedEvent,
46
+ serializeEmbedBackground: () => serializeEmbedBackground,
43
47
  useOeMeeting: () => useOeMeeting
44
48
  });
45
49
  module.exports = __toCommonJS(index_exports);
@@ -75,6 +79,7 @@ var EmbedCommandAction = /* @__PURE__ */ ((EmbedCommandAction2) => {
75
79
  EmbedCommandAction2["TOGGLE_CAMERA"] = "toggleCamera";
76
80
  EmbedCommandAction2["OPEN_STAGE"] = "openStage";
77
81
  EmbedCommandAction2["CLOSE_STAGE"] = "closeStage";
82
+ EmbedCommandAction2["SET_BACKGROUND"] = "setBackground";
78
83
  return EmbedCommandAction2;
79
84
  })(EmbedCommandAction || {});
80
85
  var EmbedErrorCode = /* @__PURE__ */ ((EmbedErrorCode2) => {
@@ -91,6 +96,16 @@ var EmbedErrorCode = /* @__PURE__ */ ((EmbedErrorCode2) => {
91
96
  EmbedErrorCode2["UNKNOWN"] = "UNKNOWN";
92
97
  return EmbedErrorCode2;
93
98
  })(EmbedErrorCode || {});
99
+ var EmbedBackgroundType = /* @__PURE__ */ ((EmbedBackgroundType2) => {
100
+ EmbedBackgroundType2["BLUR"] = "blur";
101
+ EmbedBackgroundType2["IMAGE"] = "image";
102
+ EmbedBackgroundType2["NONE"] = "none";
103
+ return EmbedBackgroundType2;
104
+ })(EmbedBackgroundType || {});
105
+ var MAX_BACKGROUND_URL_LENGTH = 2048;
106
+ var DEFAULT_BACKGROUND_BLUR_RADIUS = 10;
107
+ var MIN_BACKGROUND_BLUR_RADIUS = 1;
108
+ var MAX_BACKGROUND_BLUR_RADIUS = 50;
94
109
  var MAX_STAGE_URL_LENGTH = 2048;
95
110
  var MAX_STAGE_TOOL_ID_LENGTH = 32;
96
111
  var STAGE_TOOL_ID_PATTERN = /^[a-z][a-z0-9._-]{0,31}$/;
@@ -106,8 +121,10 @@ var EmbedQueryParam = /* @__PURE__ */ ((EmbedQueryParam2) => {
106
121
  EmbedQueryParam2["LANG"] = "lang";
107
122
  EmbedQueryParam2["AUTO_JOIN"] = "autoJoin";
108
123
  EmbedQueryParam2["HIDE_LEAVE"] = "hideLeave";
124
+ EmbedQueryParam2["AUTO_CAMERA_OFF"] = "autoCameraOff";
109
125
  EmbedQueryParam2["AUTO_TRANSCRIBE"] = "autoTranscribe";
110
126
  EmbedQueryParam2["AUTO_RECORD"] = "autoRecord";
127
+ EmbedQueryParam2["BACKGROUND"] = "background";
111
128
  return EmbedQueryParam2;
112
129
  })(EmbedQueryParam || {});
113
130
  var EmbedCaptureOutcome = /* @__PURE__ */ ((EmbedCaptureOutcome2) => {
@@ -127,6 +144,62 @@ var EmbedCapability = /* @__PURE__ */ ((EmbedCapability2) => {
127
144
  return EmbedCapability2;
128
145
  })(EmbedCapability || {});
129
146
 
147
+ // src/openStage.ts
148
+ function isValidStageUrl(url) {
149
+ if (url.length === 0 || url.length > MAX_STAGE_URL_LENGTH) return false;
150
+ try {
151
+ const parsed = new URL(url);
152
+ if (parsed.protocol === "https:") return true;
153
+ if (parsed.protocol === "http:") {
154
+ return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
155
+ }
156
+ return false;
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+ function normalizeOpenStageInput(input) {
162
+ const url = input.url.trim();
163
+ if (!isValidStageUrl(url)) return null;
164
+ const title = input.title?.trim();
165
+ return {
166
+ url,
167
+ ...title ? { title: title.slice(0, 200) } : {},
168
+ ...isStageToolId(input.tool) ? { tool: input.tool } : {}
169
+ };
170
+ }
171
+
172
+ // src/background.ts
173
+ var isValidBackgroundUrl = (url) => url.length > 0 && url.length <= MAX_BACKGROUND_URL_LENGTH && isValidStageUrl(url);
174
+ var clampBlurRadius = (value) => Number.isFinite(value) ? Math.min(MAX_BACKGROUND_BLUR_RADIUS, Math.max(MIN_BACKGROUND_BLUR_RADIUS, Math.round(value))) : DEFAULT_BACKGROUND_BLUR_RADIUS;
175
+ function normalizeEmbedBackground(input) {
176
+ const locked = input.locked === true ? { locked: true } : {};
177
+ if (input.type === "none" /* NONE */) {
178
+ return { type: "none" /* NONE */, ...locked };
179
+ }
180
+ if (input.type === "blur" /* BLUR */) {
181
+ return {
182
+ type: "blur" /* BLUR */,
183
+ blurRadius: clampBlurRadius(input.blurRadius ?? DEFAULT_BACKGROUND_BLUR_RADIUS),
184
+ ...locked
185
+ };
186
+ }
187
+ if (input.type === "image" /* IMAGE */) {
188
+ const url = input.url?.trim();
189
+ if (!url || !isValidBackgroundUrl(url)) return null;
190
+ return {
191
+ type: "image" /* IMAGE */,
192
+ url,
193
+ ...locked
194
+ };
195
+ }
196
+ return null;
197
+ }
198
+ function serializeEmbedBackground(input) {
199
+ const normalized = normalizeEmbedBackground(input);
200
+ return normalized ? JSON.stringify(normalized) : void 0;
201
+ }
202
+
130
203
  // src/buildEmbedUrl.ts
131
204
  var QUERY_FLAG_ENABLED = "1";
132
205
  var QUERY_FLAG_DISABLED = "0";
@@ -148,6 +221,7 @@ function buildEmbedUrl(options) {
148
221
  }
149
222
  const origin = normalizeOrigin(options.origin ?? DEFAULT_EMBED_ORIGIN);
150
223
  const url = new URL(`${origin}/embed/${encodeURIComponent(roomSlug)}`);
224
+ const backgroundParam = options.background ? serializeEmbedBackground(options.background) : void 0;
151
225
  const entries = [
152
226
  ["instanceId" /* INSTANCE_ID */, options.instanceId],
153
227
  ["hostOrigin" /* HOST_ORIGIN */, options.hostOrigin],
@@ -157,8 +231,10 @@ function buildEmbedUrl(options) {
157
231
  ["lang" /* LANG */, options.lang],
158
232
  ["autoJoin" /* AUTO_JOIN */, toFlag(options.autoJoin)],
159
233
  ["hideLeave" /* HIDE_LEAVE */, toFlag(options.hideLeave)],
234
+ ["autoCameraOff" /* AUTO_CAMERA_OFF */, toFlag(options.autoCameraOff)],
160
235
  ["autoTranscribe" /* AUTO_TRANSCRIBE */, toFlag(options.autoTranscribe)],
161
- ["autoRecord" /* AUTO_RECORD */, toFlag(options.autoRecord)]
236
+ ["autoRecord" /* AUTO_RECORD */, toFlag(options.autoRecord)],
237
+ ["background" /* BACKGROUND */, backgroundParam]
162
238
  ];
163
239
  for (const [key, value] of entries) {
164
240
  if (value !== void 0 && value !== "") {
@@ -171,31 +247,6 @@ function buildEmbedUrl(options) {
171
247
  // src/useOeMeeting.ts
172
248
  var import_react = require("react");
173
249
 
174
- // src/openStage.ts
175
- function isValidStageUrl(url) {
176
- if (url.length === 0 || url.length > MAX_STAGE_URL_LENGTH) return false;
177
- try {
178
- const parsed = new URL(url);
179
- if (parsed.protocol === "https:") return true;
180
- if (parsed.protocol === "http:") {
181
- return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
182
- }
183
- return false;
184
- } catch {
185
- return false;
186
- }
187
- }
188
- function normalizeOpenStageInput(input) {
189
- const url = input.url.trim();
190
- if (!isValidStageUrl(url)) return null;
191
- const title = input.title?.trim();
192
- return {
193
- url,
194
- ...title ? { title: title.slice(0, 200) } : {},
195
- ...isStageToolId(input.tool) ? { tool: input.tool } : {}
196
- };
197
- }
198
-
199
250
  // src/parseEmbedEvent.ts
200
251
  var EVENT_TYPES = new Set(Object.values(EmbedEventType));
201
252
  function isRecord(value) {
@@ -264,6 +315,7 @@ function postCommand(options) {
264
315
  function useOeMeeting(options) {
265
316
  const iframeRef = (0, import_react.useRef)(null);
266
317
  const callbacksRef = (0, import_react.useRef)(options);
318
+ const pendingBackgroundRef = (0, import_react.useRef)(null);
267
319
  (0, import_react.useEffect)(() => {
268
320
  callbacksRef.current = options;
269
321
  }, [options]);
@@ -280,6 +332,15 @@ function useOeMeeting(options) {
280
332
  switch (embedEvent.type) {
281
333
  case "oemeeting:ready" /* READY */:
282
334
  callbacks.onReady?.(embedEvent);
335
+ if (pendingBackgroundRef.current) {
336
+ postCommand({
337
+ iframe: iframeRef.current,
338
+ instanceId: options.instanceId,
339
+ action: "setBackground" /* SET_BACKGROUND */,
340
+ targetOrigin: options.targetOrigin,
341
+ payload: pendingBackgroundRef.current
342
+ });
343
+ }
283
344
  break;
284
345
  case "oemeeting:joined" /* JOINED */:
285
346
  callbacks.onJoined?.(embedEvent);
@@ -347,6 +408,16 @@ function useOeMeeting(options) {
347
408
  () => send("closeStage" /* CLOSE_STAGE */),
348
409
  [send]
349
410
  );
411
+ (0, import_react.useEffect)(() => {
412
+ if (!options.background) {
413
+ pendingBackgroundRef.current = null;
414
+ return;
415
+ }
416
+ const normalized = normalizeEmbedBackground(options.background);
417
+ if (!normalized) return;
418
+ pendingBackgroundRef.current = normalized;
419
+ send("setBackground" /* SET_BACKGROUND */, normalized);
420
+ }, [options.background, send]);
350
421
  const getIframe = (0, import_react.useCallback)(() => iframeRef.current, []);
351
422
  const handle = (0, import_react.useMemo)(
352
423
  () => ({
@@ -384,8 +455,10 @@ var OeMeeting = (0, import_react2.forwardRef)(
384
455
  lang,
385
456
  autoJoin = true,
386
457
  hideLeave,
458
+ autoCameraOff,
387
459
  autoTranscribe,
388
460
  autoRecord,
461
+ background,
389
462
  title = "OE Meeting room",
390
463
  width = "100%",
391
464
  height = 640,
@@ -416,6 +489,7 @@ var OeMeeting = (0, import_react2.forwardRef)(
416
489
  const embedOrigin = (0, import_react2.useMemo)(() => getOrigin(origin), [origin]);
417
490
  const resolvedTargetOrigin = targetOrigin ?? embedOrigin;
418
491
  const hostOrigin = typeof window === "undefined" ? void 0 : window.location.origin;
492
+ const initialBackgroundRef = (0, import_react2.useRef)(background);
419
493
  const src = (0, import_react2.useMemo)(
420
494
  () => buildEmbedUrl({
421
495
  roomSlug,
@@ -426,8 +500,10 @@ var OeMeeting = (0, import_react2.forwardRef)(
426
500
  lang,
427
501
  autoJoin,
428
502
  hideLeave,
503
+ autoCameraOff,
429
504
  autoTranscribe,
430
505
  autoRecord,
506
+ background: initialBackgroundRef.current,
431
507
  instanceId,
432
508
  hostOrigin
433
509
  }),
@@ -443,10 +519,12 @@ var OeMeeting = (0, import_react2.forwardRef)(
443
519
  lang,
444
520
  origin,
445
521
  roomSlug,
522
+ autoCameraOff,
446
523
  userName
447
524
  ]
448
525
  );
449
526
  const { iframeRef } = useOeMeeting({
527
+ background,
450
528
  instanceId,
451
529
  targetOrigin: resolvedTargetOrigin,
452
530
  handleRef: ref,
@@ -505,6 +583,7 @@ OeMeeting.displayName = "OeMeeting";
505
583
  DEFAULT_IFRAME_ALLOW,
506
584
  EMBED_MESSAGE_SOURCE,
507
585
  EMBED_PROTOCOL_VERSION,
586
+ EmbedBackgroundType,
508
587
  EmbedCapability,
509
588
  EmbedCaptureOutcome,
510
589
  EmbedCommandAction,
@@ -513,14 +592,17 @@ OeMeeting.displayName = "OeMeeting";
513
592
  EmbedEventType,
514
593
  EmbedQueryParam,
515
594
  EmbedRole,
595
+ MAX_BACKGROUND_URL_LENGTH,
516
596
  MAX_STAGE_TOOL_ID_LENGTH,
517
597
  MAX_STAGE_URL_LENGTH,
518
598
  OeMeeting,
519
599
  buildEmbedUrl,
520
600
  isStageToolId,
521
601
  isValidStageUrl,
602
+ normalizeEmbedBackground,
522
603
  normalizeOpenStageInput,
523
604
  parseEmbedEvent,
605
+ serializeEmbedBackground,
524
606
  useOeMeeting
525
607
  });
526
608
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/OeMeeting.tsx","../src/embed-protocol.ts","../src/buildEmbedUrl.ts","../src/useOeMeeting.ts","../src/openStage.ts","../src/parseEmbedEvent.ts"],"sourcesContent":["export { OeMeeting, DEFAULT_IFRAME_ALLOW } from './OeMeeting';\nexport { useOeMeeting } from './useOeMeeting';\nexport { buildEmbedUrl } from './buildEmbedUrl';\nexport { parseEmbedEvent } from './parseEmbedEvent';\nexport {\n DEFAULT_EMBED_ORIGIN,\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n EmbedCapability,\n EmbedCaptureOutcome,\n EmbedCommandAction,\n EmbedRole,\n EmbedCommandType,\n EmbedErrorCode,\n EmbedEventType,\n EmbedQueryParam,\n isStageToolId,\n MAX_STAGE_TOOL_ID_LENGTH,\n MAX_STAGE_URL_LENGTH,\n} from './embed-protocol';\nexport type { EmbedPermissionMap, StageToolId } from './embed-protocol';\nexport { isValidStageUrl, normalizeOpenStageInput } from './openStage';\nexport type {\n BuildEmbedUrlOptions,\n EmbedCommandMessage,\n EmbedEndedPayload,\n EmbedErrorPayload,\n EmbedEvent,\n EmbedEventOf,\n EmbedEventPayloadMap,\n EmbedJoinedPayload,\n EmbedLeftPayload,\n EmbedMessageBase,\n EmbedParticipantJoinedPayload,\n EmbedParticipantLeftPayload,\n EmbedReadyPayload,\n EmbedRecordingSavedPayload,\n EmbedRecordingStartedPayload,\n EmbedStageChangedPayload,\n EmbedTranscriptStartedPayload,\n OeMeetingHandle,\n OeMeetingProps,\n OpenStageInput,\n} from './types';\n","'use client';\n\nimport {\n forwardRef,\n useId,\n useMemo,\n type CSSProperties,\n} from 'react';\nimport { buildEmbedUrl } from './buildEmbedUrl';\nimport { DEFAULT_EMBED_ORIGIN } from './embed-protocol';\nimport type { OeMeetingHandle, OeMeetingProps } from './types';\nimport { useOeMeeting } from './useOeMeeting';\n\nexport const DEFAULT_IFRAME_ALLOW =\n 'camera; microphone; fullscreen; autoplay; display-capture; clipboard-write; encrypted-media';\n\nfunction toCssSize(value: number | string | undefined): string | undefined {\n if (value === undefined) return undefined;\n return typeof value === 'number' ? `${value}px` : value;\n}\n\nfunction getOrigin(value: string): string {\n return new URL(value).origin;\n}\n\nexport const OeMeeting = forwardRef<OeMeetingHandle, OeMeetingProps>(\n function OeMeeting(props, ref) {\n const {\n roomSlug,\n joinToken,\n userName,\n externalUserId,\n origin = DEFAULT_EMBED_ORIGIN,\n lang,\n autoJoin = true,\n hideLeave,\n autoTranscribe,\n autoRecord,\n title = 'OE Meeting room',\n width = '100%',\n height = 640,\n className,\n style,\n iframeClassName,\n iframeStyle,\n allow = DEFAULT_IFRAME_ALLOW,\n targetOrigin,\n onReady,\n onJoined,\n onLeft,\n onParticipantJoined,\n onParticipantLeft,\n onEnded,\n onError,\n onStageChanged,\n onTranscriptStarted,\n onRecordingStarted,\n onRecordingSaved,\n onEvent,\n } = props;\n const reactId = useId();\n const instanceId = useMemo(\n () => `oem-${reactId.replace(/:/g, '')}`,\n [reactId],\n );\n const embedOrigin = useMemo(() => getOrigin(origin), [origin]);\n const resolvedTargetOrigin = targetOrigin ?? embedOrigin;\n const hostOrigin =\n typeof window === 'undefined' ? undefined : window.location.origin;\n const src = useMemo(\n () =>\n buildEmbedUrl({\n roomSlug,\n joinToken,\n userName,\n externalUserId,\n origin,\n lang,\n autoJoin,\n hideLeave,\n autoTranscribe,\n autoRecord,\n instanceId,\n hostOrigin,\n }),\n [\n autoJoin,\n autoRecord,\n autoTranscribe,\n externalUserId,\n hideLeave,\n hostOrigin,\n instanceId,\n joinToken,\n lang,\n origin,\n roomSlug,\n userName,\n ],\n );\n const { iframeRef } = useOeMeeting({\n instanceId,\n targetOrigin: resolvedTargetOrigin,\n handleRef: ref,\n onReady,\n onJoined,\n onLeft,\n onParticipantJoined,\n onParticipantLeft,\n onEnded,\n onError,\n onStageChanged,\n onTranscriptStarted,\n onRecordingStarted,\n onRecordingSaved,\n onEvent,\n });\n const wrapperStyle: CSSProperties = {\n width: toCssSize(width),\n height: toCssSize(height),\n ...style,\n };\n const frameStyle: CSSProperties = {\n width: '100%',\n height: '100%',\n border: 0,\n display: 'block',\n ...iframeStyle,\n };\n\n return (\n <div\n className={className}\n style={wrapperStyle}\n data-oe-meeting-instance={instanceId}\n >\n <iframe\n ref={iframeRef}\n className={iframeClassName}\n style={frameStyle}\n src={src}\n title={title}\n allow={allow}\n allowFullScreen\n referrerPolicy=\"strict-origin-when-cross-origin\"\n />\n </div>\n );\n },\n);\n\nOeMeeting.displayName = 'OeMeeting';\n","export const EMBED_PROTOCOL_VERSION = 1 as const;\nexport const EMBED_MESSAGE_SOURCE = 'oemeeting' as const;\nexport const DEFAULT_EMBED_ORIGIN = 'https://meet.ocean.edu.vn';\n\nexport enum EmbedEventType {\n READY = 'oemeeting:ready',\n JOINED = 'oemeeting:joined',\n LEFT = 'oemeeting:left',\n PARTICIPANT_JOINED = 'oemeeting:participant-joined',\n PARTICIPANT_LEFT = 'oemeeting:participant-left',\n ENDED = 'oemeeting:ended',\n ERROR = 'oemeeting:error',\n STAGE_CHANGED = 'oemeeting:stage-changed',\n TRANSCRIPT_STARTED = 'oemeeting:transcript-started',\n RECORDING_STARTED = 'oemeeting:recording-started',\n RECORDING_SAVED = 'oemeeting:recording-saved',\n}\n\nexport enum EmbedCommandType {\n COMMAND = 'oemeeting:command',\n}\n\nexport enum EmbedCommandAction {\n LEAVE = 'leave',\n TOGGLE_MIC = 'toggleMic',\n TOGGLE_CAMERA = 'toggleCamera',\n OPEN_STAGE = 'openStage',\n CLOSE_STAGE = 'closeStage',\n}\n\nexport enum EmbedErrorCode {\n TOKEN_INVALID = 'TOKEN_INVALID',\n TOKEN_EXPIRED = 'TOKEN_EXPIRED',\n ROOM_NOT_FOUND = 'ROOM_NOT_FOUND',\n ROOM_ACCESS_DENIED = 'ROOM_ACCESS_DENIED',\n CONNECTION_FAILED = 'CONNECTION_FAILED',\n PROTOCOL_MISMATCH = 'PROTOCOL_MISMATCH',\n STAGE_ORIGIN_DENIED = 'STAGE_ORIGIN_DENIED',\n STAGE_URL_INVALID = 'STAGE_URL_INVALID',\n TRANSCRIPT_START_FAILED = 'TRANSCRIPT_START_FAILED',\n RECORDING_START_FAILED = 'RECORDING_START_FAILED',\n UNKNOWN = 'UNKNOWN',\n}\n\n/** Open host-owned tool id. Meeting does not catalog values. */\nexport type StageToolId = string;\n\nexport const MAX_STAGE_URL_LENGTH = 2048;\nexport const MAX_STAGE_TOOL_ID_LENGTH = 32;\n\nconst STAGE_TOOL_ID_PATTERN = /^[a-z][a-z0-9._-]{0,31}$/;\n\nexport function isStageToolId(value: unknown): value is StageToolId {\n return (\n typeof value === 'string' &&\n value.length > 0 &&\n value.length <= MAX_STAGE_TOOL_ID_LENGTH &&\n STAGE_TOOL_ID_PATTERN.test(value)\n );\n}\n\nexport enum EmbedQueryParam {\n INSTANCE_ID = 'instanceId',\n HOST_ORIGIN = 'hostOrigin',\n JOIN_TOKEN = 'joinToken',\n USER_NAME = 'userName',\n EXTERNAL_USER_ID = 'externalUserId',\n LANG = 'lang',\n AUTO_JOIN = 'autoJoin',\n HIDE_LEAVE = 'hideLeave',\n AUTO_TRANSCRIBE = 'autoTranscribe',\n AUTO_RECORD = 'autoRecord',\n}\n\n/**\n * What happened to the auto-transcribe request the embed made.\n *\n * `ALREADY_RUNNING` is the normal outcome for every participant after the\n * first, because the transcript is a room-level asset rather than a\n * per-participant one. `DISABLED` means the OE Meeting deployment has\n * transcription turned off.\n */\nexport enum EmbedCaptureOutcome {\n STARTED = 'started',\n ALREADY_RUNNING = 'already_running',\n DISABLED = 'disabled',\n FAILED = 'failed',\n}\n\n/**\n * Role the host app assigns when minting a join token.\n * Keep values aligned with `EmbedRole` in the backend embed-permission enum.\n */\nexport enum EmbedRole {\n OWNER = 'owner',\n MEMBER = 'member',\n}\n\n/**\n * Capability the host app may grant per role. Meeting enforces this on the\n * join token, not on a React prop. `screenShare` is the only value today.\n */\nexport enum EmbedCapability {\n SCREEN_SHARE = 'screenShare',\n}\n\nexport interface EmbedPermissionMap {\n owner?: EmbedCapability[];\n member?: EmbedCapability[];\n}\n","import {\n DEFAULT_EMBED_ORIGIN,\n EmbedQueryParam,\n} from './embed-protocol';\nimport type { BuildEmbedUrlOptions } from './types';\n\nconst QUERY_FLAG_ENABLED = '1';\nconst QUERY_FLAG_DISABLED = '0';\n\nfunction toFlag(value: boolean | undefined): string | undefined {\n if (value === undefined) return undefined;\n return value ? QUERY_FLAG_ENABLED : QUERY_FLAG_DISABLED;\n}\n\nfunction normalizeOrigin(origin: string): string {\n return origin.replace(/\\/+$/, '');\n}\n\nexport function buildEmbedUrl(options: BuildEmbedUrlOptions): string {\n const roomSlug = options.roomSlug.trim();\n const joinToken = options.joinToken.trim();\n if (!roomSlug) {\n throw new Error('OE Meeting embed requires a non-empty roomSlug.');\n }\n if (!joinToken) {\n throw new Error('OE Meeting embed requires a non-empty joinToken.');\n }\n\n const origin = normalizeOrigin(options.origin ?? DEFAULT_EMBED_ORIGIN);\n const url = new URL(`${origin}/embed/${encodeURIComponent(roomSlug)}`);\n const entries: Array<[EmbedQueryParam, string | undefined]> = [\n [EmbedQueryParam.INSTANCE_ID, options.instanceId],\n [EmbedQueryParam.HOST_ORIGIN, options.hostOrigin],\n [EmbedQueryParam.JOIN_TOKEN, joinToken],\n [EmbedQueryParam.USER_NAME, options.userName],\n [EmbedQueryParam.EXTERNAL_USER_ID, options.externalUserId],\n [EmbedQueryParam.LANG, options.lang],\n [EmbedQueryParam.AUTO_JOIN, toFlag(options.autoJoin)],\n [EmbedQueryParam.HIDE_LEAVE, toFlag(options.hideLeave)],\n [EmbedQueryParam.AUTO_TRANSCRIBE, toFlag(options.autoTranscribe)],\n [EmbedQueryParam.AUTO_RECORD, toFlag(options.autoRecord)],\n ];\n\n for (const [key, value] of entries) {\n if (value !== undefined && value !== '') {\n url.searchParams.set(key, value);\n }\n }\n return url.toString();\n}\n","import {\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n type Ref,\n} from 'react';\nimport {\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n EmbedCommandAction,\n EmbedCommandType,\n EmbedEventType,\n} from './embed-protocol';\nimport { normalizeOpenStageInput } from './openStage';\nimport { parseEmbedEvent } from './parseEmbedEvent';\nimport type {\n EmbedCommandMessage,\n OeMeetingHandle,\n OeMeetingProps,\n OpenStageInput,\n} from './types';\n\ntype UseOeMeetingOptions = Pick<\n OeMeetingProps,\n | 'onReady'\n | 'onJoined'\n | 'onLeft'\n | 'onParticipantJoined'\n | 'onParticipantLeft'\n | 'onEnded'\n | 'onError'\n | 'onStageChanged'\n | 'onTranscriptStarted'\n | 'onRecordingStarted'\n | 'onRecordingSaved'\n | 'onEvent'\n> & {\n instanceId: string;\n targetOrigin: string;\n handleRef?: Ref<OeMeetingHandle>;\n};\n\ninterface PostCommandOptions {\n iframe: HTMLIFrameElement | null;\n instanceId: string;\n action: EmbedCommandAction;\n targetOrigin: string;\n payload?: OpenStageInput;\n}\n\nfunction postCommand(options: PostCommandOptions): void {\n const frameWindow = options.iframe?.contentWindow;\n if (!frameWindow) return;\n const message: EmbedCommandMessage = {\n source: EMBED_MESSAGE_SOURCE,\n version: EMBED_PROTOCOL_VERSION,\n instanceId: options.instanceId,\n type: EmbedCommandType.COMMAND,\n action: options.action,\n ...(options.payload ? { payload: options.payload } : {}),\n };\n frameWindow.postMessage(message, options.targetOrigin);\n}\n\nexport function useOeMeeting(options: UseOeMeetingOptions) {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const callbacksRef = useRef(options);\n\n useEffect(() => {\n callbacksRef.current = options;\n }, [options]);\n\n useEffect(() => {\n const onMessage = (event: MessageEvent<unknown>) => {\n const frameWindow = iframeRef.current?.contentWindow;\n if (\n !frameWindow ||\n event.source !== frameWindow ||\n event.origin !== options.targetOrigin\n ) {\n return;\n }\n const embedEvent = parseEmbedEvent(event.data);\n if (!embedEvent || embedEvent.instanceId !== options.instanceId) return;\n\n const callbacks = callbacksRef.current;\n callbacks.onEvent?.(embedEvent);\n switch (embedEvent.type) {\n case EmbedEventType.READY:\n callbacks.onReady?.(embedEvent);\n break;\n case EmbedEventType.JOINED:\n callbacks.onJoined?.(embedEvent);\n break;\n case EmbedEventType.LEFT:\n callbacks.onLeft?.(embedEvent);\n break;\n case EmbedEventType.PARTICIPANT_JOINED:\n callbacks.onParticipantJoined?.(embedEvent);\n break;\n case EmbedEventType.PARTICIPANT_LEFT:\n callbacks.onParticipantLeft?.(embedEvent);\n break;\n case EmbedEventType.ENDED:\n callbacks.onEnded?.(embedEvent);\n break;\n case EmbedEventType.ERROR:\n callbacks.onError?.(embedEvent);\n break;\n case EmbedEventType.STAGE_CHANGED:\n callbacks.onStageChanged?.(embedEvent);\n break;\n case EmbedEventType.TRANSCRIPT_STARTED:\n callbacks.onTranscriptStarted?.(embedEvent);\n break;\n case EmbedEventType.RECORDING_STARTED:\n callbacks.onRecordingStarted?.(embedEvent);\n break;\n case EmbedEventType.RECORDING_SAVED:\n callbacks.onRecordingSaved?.(embedEvent);\n break;\n }\n };\n\n window.addEventListener('message', onMessage);\n return () => window.removeEventListener('message', onMessage);\n }, [options.instanceId, options.targetOrigin]);\n\n const send = useCallback(\n (action: EmbedCommandAction, payload?: OpenStageInput) =>\n postCommand({\n iframe: iframeRef.current,\n instanceId: options.instanceId,\n action,\n targetOrigin: options.targetOrigin,\n payload,\n }),\n [options.instanceId, options.targetOrigin],\n );\n const leave = useCallback(() => send(EmbedCommandAction.LEAVE), [send]);\n const toggleMic = useCallback(\n () => send(EmbedCommandAction.TOGGLE_MIC),\n [send],\n );\n const toggleCamera = useCallback(\n () => send(EmbedCommandAction.TOGGLE_CAMERA),\n [send],\n );\n const openStage = useCallback(\n (input: OpenStageInput) => {\n const normalized = normalizeOpenStageInput(input);\n if (!normalized) return;\n send(EmbedCommandAction.OPEN_STAGE, normalized);\n },\n [send],\n );\n const closeStage = useCallback(\n () => send(EmbedCommandAction.CLOSE_STAGE),\n [send],\n );\n const getIframe = useCallback(() => iframeRef.current, []);\n const handle = useMemo<OeMeetingHandle>(\n () => ({\n leave,\n toggleMic,\n toggleCamera,\n openStage,\n closeStage,\n getIframe,\n }),\n [closeStage, getIframe, leave, openStage, toggleCamera, toggleMic],\n );\n\n useImperativeHandle(options.handleRef, () => handle, [handle]);\n return { iframeRef, handle };\n}\n","import { isStageToolId, MAX_STAGE_URL_LENGTH } from './embed-protocol';\nimport type { OpenStageInput } from './types';\n\nexport function isValidStageUrl(url: string): boolean {\n if (url.length === 0 || url.length > MAX_STAGE_URL_LENGTH) return false;\n try {\n const parsed = new URL(url);\n if (parsed.protocol === 'https:') return true;\n if (parsed.protocol === 'http:') {\n return (\n parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1'\n );\n }\n return false;\n } catch {\n return false;\n }\n}\n\nexport function normalizeOpenStageInput(\n input: OpenStageInput,\n): OpenStageInput | null {\n const url = input.url.trim();\n if (!isValidStageUrl(url)) return null;\n const title = input.title?.trim();\n return {\n url,\n ...(title ? { title: title.slice(0, 200) } : {}),\n ...(isStageToolId(input.tool) ? { tool: input.tool } : {}),\n };\n}\n","import {\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n EmbedCaptureOutcome,\n EmbedEventType,\n} from './embed-protocol';\nimport type { EmbedEvent } from './types';\n\nconst EVENT_TYPES = new Set<string>(Object.values(EmbedEventType));\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isOptionalString(value: unknown): boolean {\n return value === undefined || typeof value === 'string';\n}\n\nfunction isNullableString(value: unknown): boolean {\n return value === null || typeof value === 'string';\n}\n\nconst CAPTURE_OUTCOMES = new Set<string>(Object.values(EmbedCaptureOutcome));\n\nfunction isCaptureOutcome(value: unknown): boolean {\n return typeof value === 'string' && CAPTURE_OUTCOMES.has(value);\n}\n\nfunction hasValidPayload(\n type: EmbedEventType,\n payload: Record<string, unknown>,\n): boolean {\n switch (type) {\n case EmbedEventType.READY:\n return typeof payload.roomSlug === 'string';\n case EmbedEventType.JOINED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.identity === 'string' &&\n isOptionalString(payload.name) &&\n typeof payload.joinedAt === 'string'\n );\n case EmbedEventType.LEFT:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.identity === 'string' &&\n typeof payload.leftAt === 'string' &&\n (payload.durationMs === undefined ||\n typeof payload.durationMs === 'number')\n );\n case EmbedEventType.PARTICIPANT_JOINED:\n return (\n typeof payload.identity === 'string' &&\n isOptionalString(payload.name) &&\n typeof payload.participantCount === 'number'\n );\n case EmbedEventType.PARTICIPANT_LEFT:\n return (\n typeof payload.identity === 'string' &&\n typeof payload.participantCount === 'number'\n );\n case EmbedEventType.ENDED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.endedAt === 'string'\n );\n case EmbedEventType.ERROR:\n return (\n typeof payload.code === 'string' &&\n typeof payload.message === 'string'\n );\n case EmbedEventType.STAGE_CHANGED:\n return (\n (payload.url === null || typeof payload.url === 'string') &&\n isOptionalString(payload.title) &&\n isOptionalString(payload.tool) &&\n isOptionalString(payload.openedBy)\n );\n case EmbedEventType.TRANSCRIPT_STARTED:\n return (\n typeof payload.roomSlug === 'string' &&\n isCaptureOutcome(payload.outcome) &&\n isNullableString(payload.transcriptId) &&\n isNullableString(payload.status)\n );\n case EmbedEventType.RECORDING_STARTED:\n return (\n typeof payload.roomSlug === 'string' &&\n isCaptureOutcome(payload.outcome) &&\n isNullableString(payload.recordingId) &&\n isNullableString(payload.status)\n );\n case EmbedEventType.RECORDING_SAVED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.savedAt === 'string'\n );\n }\n}\n\nexport function parseEmbedEvent(data: unknown): EmbedEvent | null {\n if (!isRecord(data)) return null;\n if (\n data.source !== EMBED_MESSAGE_SOURCE ||\n data.version !== EMBED_PROTOCOL_VERSION ||\n typeof data.instanceId !== 'string' ||\n data.instanceId.length === 0 ||\n typeof data.type !== 'string' ||\n !EVENT_TYPES.has(data.type) ||\n !isRecord(data.payload)\n ) {\n return null;\n }\n\n const type = data.type as EmbedEventType;\n if (!hasValidPayload(type, data.payload)) return null;\n return data as unknown as EmbedEvent;\n}\n"],"mappings":"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","names":["import_react","EmbedEventType","EmbedCommandType","EmbedCommandAction","EmbedErrorCode","EmbedQueryParam","EmbedCaptureOutcome","EmbedRole","EmbedCapability","OeMeeting"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/OeMeeting.tsx","../src/embed-protocol.ts","../src/openStage.ts","../src/background.ts","../src/buildEmbedUrl.ts","../src/useOeMeeting.ts","../src/parseEmbedEvent.ts"],"sourcesContent":["export { OeMeeting, DEFAULT_IFRAME_ALLOW } from './OeMeeting';\nexport { useOeMeeting } from './useOeMeeting';\nexport { buildEmbedUrl } from './buildEmbedUrl';\nexport { parseEmbedEvent } from './parseEmbedEvent';\nexport {\n DEFAULT_EMBED_ORIGIN,\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n EmbedCapability,\n EmbedCaptureOutcome,\n EmbedBackgroundType,\n EmbedCommandAction,\n EmbedRole,\n EmbedCommandType,\n EmbedErrorCode,\n EmbedEventType,\n EmbedQueryParam,\n isStageToolId,\n MAX_BACKGROUND_URL_LENGTH,\n MAX_STAGE_TOOL_ID_LENGTH,\n MAX_STAGE_URL_LENGTH,\n} from './embed-protocol';\nexport type { EmbedBackground, EmbedPermissionMap, StageToolId } from './embed-protocol';\nexport { isValidStageUrl, normalizeOpenStageInput } from './openStage';\nexport { normalizeEmbedBackground, serializeEmbedBackground } from './background';\nexport type {\n BuildEmbedUrlOptions,\n EmbedCommandMessage,\n EmbedEndedPayload,\n EmbedErrorPayload,\n EmbedEvent,\n EmbedEventOf,\n EmbedEventPayloadMap,\n EmbedJoinedPayload,\n EmbedLeftPayload,\n EmbedMessageBase,\n EmbedParticipantJoinedPayload,\n EmbedParticipantLeftPayload,\n EmbedReadyPayload,\n EmbedRecordingSavedPayload,\n EmbedRecordingStartedPayload,\n EmbedStageChangedPayload,\n EmbedTranscriptStartedPayload,\n OeMeetingHandle,\n OeMeetingProps,\n OpenStageInput,\n} from './types';\n","'use client';\n\nimport {\n forwardRef,\n useId,\n useMemo,\n useRef,\n type CSSProperties,\n} from 'react';\nimport { buildEmbedUrl } from './buildEmbedUrl';\nimport { DEFAULT_EMBED_ORIGIN } from './embed-protocol';\nimport type { OeMeetingHandle, OeMeetingProps } from './types';\nimport { useOeMeeting } from './useOeMeeting';\n\nexport const DEFAULT_IFRAME_ALLOW =\n 'camera; microphone; fullscreen; autoplay; display-capture; clipboard-write; encrypted-media';\n\nfunction toCssSize(value: number | string | undefined): string | undefined {\n if (value === undefined) return undefined;\n return typeof value === 'number' ? `${value}px` : value;\n}\n\nfunction getOrigin(value: string): string {\n return new URL(value).origin;\n}\n\nexport const OeMeeting = forwardRef<OeMeetingHandle, OeMeetingProps>(\n function OeMeeting(props, ref) {\n const {\n roomSlug,\n joinToken,\n userName,\n externalUserId,\n origin = DEFAULT_EMBED_ORIGIN,\n lang,\n autoJoin = true,\n hideLeave,\n autoCameraOff,\n autoTranscribe,\n autoRecord,\n background,\n title = 'OE Meeting room',\n width = '100%',\n height = 640,\n className,\n style,\n iframeClassName,\n iframeStyle,\n allow = DEFAULT_IFRAME_ALLOW,\n targetOrigin,\n onReady,\n onJoined,\n onLeft,\n onParticipantJoined,\n onParticipantLeft,\n onEnded,\n onError,\n onStageChanged,\n onTranscriptStarted,\n onRecordingStarted,\n onRecordingSaved,\n onEvent,\n } = props;\n const reactId = useId();\n const instanceId = useMemo(\n () => `oem-${reactId.replace(/:/g, '')}`,\n [reactId],\n );\n const embedOrigin = useMemo(() => getOrigin(origin), [origin]);\n const resolvedTargetOrigin = targetOrigin ?? embedOrigin;\n const hostOrigin =\n typeof window === 'undefined' ? undefined : window.location.origin;\n // Only the mount-time background rides the embed URL. Later prop changes\n // travel as `setBackground` commands (useOeMeeting); rebuilding src would\n // reload the whole meeting mid-session.\n const initialBackgroundRef = useRef(background);\n const src = useMemo(\n () =>\n buildEmbedUrl({\n roomSlug,\n joinToken,\n userName,\n externalUserId,\n origin,\n lang,\n autoJoin,\n hideLeave,\n autoCameraOff,\n autoTranscribe,\n autoRecord,\n background: initialBackgroundRef.current,\n instanceId,\n hostOrigin,\n }),\n [\n autoJoin,\n autoRecord,\n autoTranscribe,\n externalUserId,\n hideLeave,\n hostOrigin,\n instanceId,\n joinToken,\n lang,\n origin,\n roomSlug,\n autoCameraOff,\n userName,\n ],\n );\n const { iframeRef } = useOeMeeting({\n background,\n instanceId,\n targetOrigin: resolvedTargetOrigin,\n handleRef: ref,\n onReady,\n onJoined,\n onLeft,\n onParticipantJoined,\n onParticipantLeft,\n onEnded,\n onError,\n onStageChanged,\n onTranscriptStarted,\n onRecordingStarted,\n onRecordingSaved,\n onEvent,\n });\n const wrapperStyle: CSSProperties = {\n width: toCssSize(width),\n height: toCssSize(height),\n ...style,\n };\n const frameStyle: CSSProperties = {\n width: '100%',\n height: '100%',\n border: 0,\n display: 'block',\n ...iframeStyle,\n };\n\n return (\n <div\n className={className}\n style={wrapperStyle}\n data-oe-meeting-instance={instanceId}\n >\n <iframe\n ref={iframeRef}\n className={iframeClassName}\n style={frameStyle}\n src={src}\n title={title}\n allow={allow}\n allowFullScreen\n referrerPolicy=\"strict-origin-when-cross-origin\"\n />\n </div>\n );\n },\n);\n\nOeMeeting.displayName = 'OeMeeting';\n","export const EMBED_PROTOCOL_VERSION = 1 as const;\nexport const EMBED_MESSAGE_SOURCE = 'oemeeting' as const;\nexport const DEFAULT_EMBED_ORIGIN = 'https://meet.ocean.edu.vn';\n\nexport enum EmbedEventType {\n READY = 'oemeeting:ready',\n JOINED = 'oemeeting:joined',\n LEFT = 'oemeeting:left',\n PARTICIPANT_JOINED = 'oemeeting:participant-joined',\n PARTICIPANT_LEFT = 'oemeeting:participant-left',\n ENDED = 'oemeeting:ended',\n ERROR = 'oemeeting:error',\n STAGE_CHANGED = 'oemeeting:stage-changed',\n TRANSCRIPT_STARTED = 'oemeeting:transcript-started',\n RECORDING_STARTED = 'oemeeting:recording-started',\n RECORDING_SAVED = 'oemeeting:recording-saved',\n}\n\nexport enum EmbedCommandType {\n COMMAND = 'oemeeting:command',\n}\n\nexport enum EmbedCommandAction {\n LEAVE = 'leave',\n TOGGLE_MIC = 'toggleMic',\n TOGGLE_CAMERA = 'toggleCamera',\n OPEN_STAGE = 'openStage',\n CLOSE_STAGE = 'closeStage',\n SET_BACKGROUND = 'setBackground',\n}\n\nexport enum EmbedErrorCode {\n TOKEN_INVALID = 'TOKEN_INVALID',\n TOKEN_EXPIRED = 'TOKEN_EXPIRED',\n ROOM_NOT_FOUND = 'ROOM_NOT_FOUND',\n ROOM_ACCESS_DENIED = 'ROOM_ACCESS_DENIED',\n CONNECTION_FAILED = 'CONNECTION_FAILED',\n PROTOCOL_MISMATCH = 'PROTOCOL_MISMATCH',\n STAGE_ORIGIN_DENIED = 'STAGE_ORIGIN_DENIED',\n STAGE_URL_INVALID = 'STAGE_URL_INVALID',\n TRANSCRIPT_START_FAILED = 'TRANSCRIPT_START_FAILED',\n RECORDING_START_FAILED = 'RECORDING_START_FAILED',\n UNKNOWN = 'UNKNOWN',\n}\n\n/**\n * Host-controlled camera background applied to this embed's local user only.\n * `none` clears any active background effect. The host app enforces who may\n * set it; the meeting does not interpret roles for this value.\n */\nexport enum EmbedBackgroundType {\n BLUR = 'blur',\n IMAGE = 'image',\n NONE = 'none',\n}\n\nexport const MAX_BACKGROUND_URL_LENGTH = 2048;\nexport const DEFAULT_BACKGROUND_BLUR_RADIUS = 10;\nexport const MIN_BACKGROUND_BLUR_RADIUS = 1;\nexport const MAX_BACKGROUND_BLUR_RADIUS = 50;\n\nexport interface EmbedBackground {\n type: EmbedBackgroundType;\n /** BLUR only. Defaults to `DEFAULT_BACKGROUND_BLUR_RADIUS`, clamped 1-50. */\n blurRadius?: number;\n /** IMAGE only. http(s) URL, max `MAX_BACKGROUND_URL_LENGTH`. */\n url?: string;\n /** When true the embed user cannot change blur/virtual backgrounds. */\n locked?: boolean;\n}\n\n/** Open host-owned tool id. Meeting does not catalog values. */\nexport type StageToolId = string;\n\nexport const MAX_STAGE_URL_LENGTH = 2048;\nexport const MAX_STAGE_TOOL_ID_LENGTH = 32;\n\nconst STAGE_TOOL_ID_PATTERN = /^[a-z][a-z0-9._-]{0,31}$/;\n\nexport function isStageToolId(value: unknown): value is StageToolId {\n return (\n typeof value === 'string' &&\n value.length > 0 &&\n value.length <= MAX_STAGE_TOOL_ID_LENGTH &&\n STAGE_TOOL_ID_PATTERN.test(value)\n );\n}\n\nexport enum EmbedQueryParam {\n INSTANCE_ID = 'instanceId',\n HOST_ORIGIN = 'hostOrigin',\n JOIN_TOKEN = 'joinToken',\n USER_NAME = 'userName',\n EXTERNAL_USER_ID = 'externalUserId',\n LANG = 'lang',\n AUTO_JOIN = 'autoJoin',\n HIDE_LEAVE = 'hideLeave',\n AUTO_CAMERA_OFF = 'autoCameraOff',\n AUTO_TRANSCRIBE = 'autoTranscribe',\n AUTO_RECORD = 'autoRecord',\n BACKGROUND = 'background',\n}\n\n/**\n * What happened to the auto-transcribe request the embed made.\n *\n * `ALREADY_RUNNING` is the normal outcome for every participant after the\n * first, because the transcript is a room-level asset rather than a\n * per-participant one. `DISABLED` means the OE Meeting deployment has\n * transcription turned off.\n */\nexport enum EmbedCaptureOutcome {\n STARTED = 'started',\n ALREADY_RUNNING = 'already_running',\n DISABLED = 'disabled',\n FAILED = 'failed',\n}\n\n/**\n * Role the host app assigns when minting a join token.\n * Keep values aligned with `EmbedRole` in the backend embed-permission enum.\n */\nexport enum EmbedRole {\n OWNER = 'owner',\n MEMBER = 'member',\n}\n\n/**\n * Capability the host app may grant per role. Meeting enforces this on the\n * join token, not on a React prop. `screenShare` is the only value today.\n */\nexport enum EmbedCapability {\n SCREEN_SHARE = 'screenShare',\n}\n\nexport interface EmbedPermissionMap {\n owner?: EmbedCapability[];\n member?: EmbedCapability[];\n}\n","import { isStageToolId, MAX_STAGE_URL_LENGTH } from './embed-protocol';\nimport type { OpenStageInput } from './types';\n\nexport function isValidStageUrl(url: string): boolean {\n if (url.length === 0 || url.length > MAX_STAGE_URL_LENGTH) return false;\n try {\n const parsed = new URL(url);\n if (parsed.protocol === 'https:') return true;\n if (parsed.protocol === 'http:') {\n return (\n parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1'\n );\n }\n return false;\n } catch {\n return false;\n }\n}\n\nexport function normalizeOpenStageInput(\n input: OpenStageInput,\n): OpenStageInput | null {\n const url = input.url.trim();\n if (!isValidStageUrl(url)) return null;\n const title = input.title?.trim();\n return {\n url,\n ...(title ? { title: title.slice(0, 200) } : {}),\n ...(isStageToolId(input.tool) ? { tool: input.tool } : {}),\n };\n}\n","import { isValidStageUrl } from './openStage';\nimport {\n DEFAULT_BACKGROUND_BLUR_RADIUS,\n EmbedBackgroundType,\n MAX_BACKGROUND_BLUR_RADIUS,\n MAX_BACKGROUND_URL_LENGTH,\n MIN_BACKGROUND_BLUR_RADIUS,\n type EmbedBackground,\n} from './embed-protocol';\n\n/** Same http(s) policy as the stage URL, including the localhost exception. */\nconst isValidBackgroundUrl = (url: string): boolean =>\n url.length > 0 &&\n url.length <= MAX_BACKGROUND_URL_LENGTH &&\n isValidStageUrl(url);\n\nconst clampBlurRadius = (value: number): number =>\n Number.isFinite(value)\n ? Math.min(MAX_BACKGROUND_BLUR_RADIUS, Math.max(MIN_BACKGROUND_BLUR_RADIUS, Math.round(value)))\n : DEFAULT_BACKGROUND_BLUR_RADIUS;\n\n/**\n * Validate and normalize a host-provided background. Returns null for invalid\n * input so callers can silently drop it, mirroring `normalizeOpenStageInput`.\n */\nexport function normalizeEmbedBackground(\n input: EmbedBackground,\n): EmbedBackground | null {\n const locked = input.locked === true ? { locked: true } : {};\n\n if (input.type === EmbedBackgroundType.NONE) {\n return { type: EmbedBackgroundType.NONE, ...locked };\n }\n\n if (input.type === EmbedBackgroundType.BLUR) {\n return {\n type: EmbedBackgroundType.BLUR,\n blurRadius: clampBlurRadius(input.blurRadius ?? DEFAULT_BACKGROUND_BLUR_RADIUS),\n ...locked,\n };\n }\n\n if (input.type === EmbedBackgroundType.IMAGE) {\n const url = input.url?.trim();\n if (!url || !isValidBackgroundUrl(url)) return null;\n return {\n type: EmbedBackgroundType.IMAGE,\n url,\n ...locked,\n };\n }\n\n return null;\n}\n\n/** JSON wire format shared by the `background` query param and the command payload. */\nexport function serializeEmbedBackground(\n input: EmbedBackground,\n): string | undefined {\n const normalized = normalizeEmbedBackground(input);\n return normalized ? JSON.stringify(normalized) : undefined;\n}\n","import {\n DEFAULT_EMBED_ORIGIN,\n EmbedQueryParam,\n} from './embed-protocol';\nimport { serializeEmbedBackground } from './background';\nimport type { BuildEmbedUrlOptions } from './types';\n\nconst QUERY_FLAG_ENABLED = '1';\nconst QUERY_FLAG_DISABLED = '0';\n\nfunction toFlag(value: boolean | undefined): string | undefined {\n if (value === undefined) return undefined;\n return value ? QUERY_FLAG_ENABLED : QUERY_FLAG_DISABLED;\n}\n\nfunction normalizeOrigin(origin: string): string {\n return origin.replace(/\\/+$/, '');\n}\n\nexport function buildEmbedUrl(options: BuildEmbedUrlOptions): string {\n const roomSlug = options.roomSlug.trim();\n const joinToken = options.joinToken.trim();\n if (!roomSlug) {\n throw new Error('OE Meeting embed requires a non-empty roomSlug.');\n }\n if (!joinToken) {\n throw new Error('OE Meeting embed requires a non-empty joinToken.');\n }\n\n const origin = normalizeOrigin(options.origin ?? DEFAULT_EMBED_ORIGIN);\n const url = new URL(`${origin}/embed/${encodeURIComponent(roomSlug)}`);\n const backgroundParam = options.background\n ? serializeEmbedBackground(options.background)\n : undefined;\n\n const entries: Array<[EmbedQueryParam, string | undefined]> = [\n [EmbedQueryParam.INSTANCE_ID, options.instanceId],\n [EmbedQueryParam.HOST_ORIGIN, options.hostOrigin],\n [EmbedQueryParam.JOIN_TOKEN, joinToken],\n [EmbedQueryParam.USER_NAME, options.userName],\n [EmbedQueryParam.EXTERNAL_USER_ID, options.externalUserId],\n [EmbedQueryParam.LANG, options.lang],\n [EmbedQueryParam.AUTO_JOIN, toFlag(options.autoJoin)],\n [EmbedQueryParam.HIDE_LEAVE, toFlag(options.hideLeave)],\n [EmbedQueryParam.AUTO_CAMERA_OFF, toFlag(options.autoCameraOff)],\n [EmbedQueryParam.AUTO_TRANSCRIBE, toFlag(options.autoTranscribe)],\n [EmbedQueryParam.AUTO_RECORD, toFlag(options.autoRecord)],\n [EmbedQueryParam.BACKGROUND, backgroundParam],\n ];\n\n for (const [key, value] of entries) {\n if (value !== undefined && value !== '') {\n url.searchParams.set(key, value);\n }\n }\n return url.toString();\n}\n","import {\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n type Ref,\n} from 'react';\nimport {\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n EmbedCommandAction,\n EmbedCommandType,\n EmbedEventType,\n} from './embed-protocol';\nimport { normalizeEmbedBackground } from './background';\nimport { normalizeOpenStageInput } from './openStage';\nimport { parseEmbedEvent } from './parseEmbedEvent';\nimport type {\n EmbedCommandMessage,\n OeMeetingHandle,\n OeMeetingProps,\n OpenStageInput,\n} from './types';\nimport type { EmbedBackground } from './embed-protocol';\n\ntype UseOeMeetingOptions = Pick<\n OeMeetingProps,\n | 'onReady'\n | 'onJoined'\n | 'onLeft'\n | 'onParticipantJoined'\n | 'onParticipantLeft'\n | 'onEnded'\n | 'onError'\n | 'onStageChanged'\n | 'onTranscriptStarted'\n | 'onRecordingStarted'\n | 'onRecordingSaved'\n | 'onEvent'\n | 'background'\n> & {\n instanceId: string;\n targetOrigin: string;\n handleRef?: Ref<OeMeetingHandle>;\n};\n\ninterface PostCommandOptions {\n iframe: HTMLIFrameElement | null;\n instanceId: string;\n action: EmbedCommandAction;\n targetOrigin: string;\n payload?: OpenStageInput | EmbedBackground;\n}\n\nfunction postCommand(options: PostCommandOptions): void {\n const frameWindow = options.iframe?.contentWindow;\n if (!frameWindow) return;\n const message: EmbedCommandMessage = {\n source: EMBED_MESSAGE_SOURCE,\n version: EMBED_PROTOCOL_VERSION,\n instanceId: options.instanceId,\n type: EmbedCommandType.COMMAND,\n action: options.action,\n ...(options.payload ? { payload: options.payload } : {}),\n };\n frameWindow.postMessage(message, options.targetOrigin);\n}\n\nexport function useOeMeeting(options: UseOeMeetingOptions) {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const callbacksRef = useRef(options);\n // Last background the host set. Resent on READY: commands posted before the\n // iframe bridge mounts are dropped by the browser.\n const pendingBackgroundRef = useRef<EmbedBackground | null>(null);\n\n useEffect(() => {\n callbacksRef.current = options;\n }, [options]);\n\n useEffect(() => {\n const onMessage = (event: MessageEvent<unknown>) => {\n const frameWindow = iframeRef.current?.contentWindow;\n if (\n !frameWindow ||\n event.source !== frameWindow ||\n event.origin !== options.targetOrigin\n ) {\n return;\n }\n const embedEvent = parseEmbedEvent(event.data);\n if (!embedEvent || embedEvent.instanceId !== options.instanceId) return;\n\n const callbacks = callbacksRef.current;\n callbacks.onEvent?.(embedEvent);\n switch (embedEvent.type) {\n case EmbedEventType.READY:\n callbacks.onReady?.(embedEvent);\n if (pendingBackgroundRef.current) {\n postCommand({\n iframe: iframeRef.current,\n instanceId: options.instanceId,\n action: EmbedCommandAction.SET_BACKGROUND,\n targetOrigin: options.targetOrigin,\n payload: pendingBackgroundRef.current,\n });\n }\n break;\n case EmbedEventType.JOINED:\n callbacks.onJoined?.(embedEvent);\n break;\n case EmbedEventType.LEFT:\n callbacks.onLeft?.(embedEvent);\n break;\n case EmbedEventType.PARTICIPANT_JOINED:\n callbacks.onParticipantJoined?.(embedEvent);\n break;\n case EmbedEventType.PARTICIPANT_LEFT:\n callbacks.onParticipantLeft?.(embedEvent);\n break;\n case EmbedEventType.ENDED:\n callbacks.onEnded?.(embedEvent);\n break;\n case EmbedEventType.ERROR:\n callbacks.onError?.(embedEvent);\n break;\n case EmbedEventType.STAGE_CHANGED:\n callbacks.onStageChanged?.(embedEvent);\n break;\n case EmbedEventType.TRANSCRIPT_STARTED:\n callbacks.onTranscriptStarted?.(embedEvent);\n break;\n case EmbedEventType.RECORDING_STARTED:\n callbacks.onRecordingStarted?.(embedEvent);\n break;\n case EmbedEventType.RECORDING_SAVED:\n callbacks.onRecordingSaved?.(embedEvent);\n break;\n }\n };\n\n window.addEventListener('message', onMessage);\n return () => window.removeEventListener('message', onMessage);\n }, [options.instanceId, options.targetOrigin]);\n\n const send = useCallback(\n (\n action: EmbedCommandAction,\n payload?: OpenStageInput | EmbedBackground\n ) =>\n postCommand({\n iframe: iframeRef.current,\n instanceId: options.instanceId,\n action,\n targetOrigin: options.targetOrigin,\n payload,\n }),\n [options.instanceId, options.targetOrigin],\n );\n const leave = useCallback(() => send(EmbedCommandAction.LEAVE), [send]);\n const toggleMic = useCallback(\n () => send(EmbedCommandAction.TOGGLE_MIC),\n [send],\n );\n const toggleCamera = useCallback(\n () => send(EmbedCommandAction.TOGGLE_CAMERA),\n [send],\n );\n const openStage = useCallback(\n (input: OpenStageInput) => {\n const normalized = normalizeOpenStageInput(input);\n if (!normalized) return;\n send(EmbedCommandAction.OPEN_STAGE, normalized);\n },\n [send],\n );\n const closeStage = useCallback(\n () => send(EmbedCommandAction.CLOSE_STAGE),\n [send],\n );\n\n useEffect(() => {\n if (!options.background) {\n pendingBackgroundRef.current = null;\n return;\n }\n const normalized = normalizeEmbedBackground(options.background);\n if (!normalized) return;\n pendingBackgroundRef.current = normalized;\n send(EmbedCommandAction.SET_BACKGROUND, normalized);\n }, [options.background, send]);\n const getIframe = useCallback(() => iframeRef.current, []);\n const handle = useMemo<OeMeetingHandle>(\n () => ({\n leave,\n toggleMic,\n toggleCamera,\n openStage,\n closeStage,\n getIframe,\n }),\n [closeStage, getIframe, leave, openStage, toggleCamera, toggleMic],\n );\n\n useImperativeHandle(options.handleRef, () => handle, [handle]);\n return { iframeRef, handle };\n}\n","import {\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n EmbedCaptureOutcome,\n EmbedEventType,\n} from './embed-protocol';\nimport type { EmbedEvent } from './types';\n\nconst EVENT_TYPES = new Set<string>(Object.values(EmbedEventType));\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isOptionalString(value: unknown): boolean {\n return value === undefined || typeof value === 'string';\n}\n\nfunction isNullableString(value: unknown): boolean {\n return value === null || typeof value === 'string';\n}\n\nconst CAPTURE_OUTCOMES = new Set<string>(Object.values(EmbedCaptureOutcome));\n\nfunction isCaptureOutcome(value: unknown): boolean {\n return typeof value === 'string' && CAPTURE_OUTCOMES.has(value);\n}\n\nfunction hasValidPayload(\n type: EmbedEventType,\n payload: Record<string, unknown>,\n): boolean {\n switch (type) {\n case EmbedEventType.READY:\n return typeof payload.roomSlug === 'string';\n case EmbedEventType.JOINED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.identity === 'string' &&\n isOptionalString(payload.name) &&\n typeof payload.joinedAt === 'string'\n );\n case EmbedEventType.LEFT:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.identity === 'string' &&\n typeof payload.leftAt === 'string' &&\n (payload.durationMs === undefined ||\n typeof payload.durationMs === 'number')\n );\n case EmbedEventType.PARTICIPANT_JOINED:\n return (\n typeof payload.identity === 'string' &&\n isOptionalString(payload.name) &&\n typeof payload.participantCount === 'number'\n );\n case EmbedEventType.PARTICIPANT_LEFT:\n return (\n typeof payload.identity === 'string' &&\n typeof payload.participantCount === 'number'\n );\n case EmbedEventType.ENDED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.endedAt === 'string'\n );\n case EmbedEventType.ERROR:\n return (\n typeof payload.code === 'string' &&\n typeof payload.message === 'string'\n );\n case EmbedEventType.STAGE_CHANGED:\n return (\n (payload.url === null || typeof payload.url === 'string') &&\n isOptionalString(payload.title) &&\n isOptionalString(payload.tool) &&\n isOptionalString(payload.openedBy)\n );\n case EmbedEventType.TRANSCRIPT_STARTED:\n return (\n typeof payload.roomSlug === 'string' &&\n isCaptureOutcome(payload.outcome) &&\n isNullableString(payload.transcriptId) &&\n isNullableString(payload.status)\n );\n case EmbedEventType.RECORDING_STARTED:\n return (\n typeof payload.roomSlug === 'string' &&\n isCaptureOutcome(payload.outcome) &&\n isNullableString(payload.recordingId) &&\n isNullableString(payload.status)\n );\n case EmbedEventType.RECORDING_SAVED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.savedAt === 'string'\n );\n }\n}\n\nexport function parseEmbedEvent(data: unknown): EmbedEvent | null {\n if (!isRecord(data)) return null;\n if (\n data.source !== EMBED_MESSAGE_SOURCE ||\n data.version !== EMBED_PROTOCOL_VERSION ||\n typeof data.instanceId !== 'string' ||\n data.instanceId.length === 0 ||\n typeof data.type !== 'string' ||\n !EVENT_TYPES.has(data.type) ||\n !isRecord(data.payload)\n ) {\n return null;\n }\n\n const type = data.type as EmbedEventType;\n if (!hasValidPayload(type, data.payload)) return null;\n return data as unknown as EmbedEvent;\n}\n"],"mappings":"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","names":["import_react","EmbedEventType","EmbedCommandType","EmbedCommandAction","EmbedErrorCode","EmbedBackgroundType","EmbedQueryParam","EmbedCaptureOutcome","EmbedRole","EmbedCapability","OeMeeting"]}
package/dist/index.d.cts CHANGED
@@ -25,7 +25,8 @@ declare enum EmbedCommandAction {
25
25
  TOGGLE_MIC = "toggleMic",
26
26
  TOGGLE_CAMERA = "toggleCamera",
27
27
  OPEN_STAGE = "openStage",
28
- CLOSE_STAGE = "closeStage"
28
+ CLOSE_STAGE = "closeStage",
29
+ SET_BACKGROUND = "setBackground"
29
30
  }
30
31
  declare enum EmbedErrorCode {
31
32
  TOKEN_INVALID = "TOKEN_INVALID",
@@ -40,6 +41,26 @@ declare enum EmbedErrorCode {
40
41
  RECORDING_START_FAILED = "RECORDING_START_FAILED",
41
42
  UNKNOWN = "UNKNOWN"
42
43
  }
44
+ /**
45
+ * Host-controlled camera background applied to this embed's local user only.
46
+ * `none` clears any active background effect. The host app enforces who may
47
+ * set it; the meeting does not interpret roles for this value.
48
+ */
49
+ declare enum EmbedBackgroundType {
50
+ BLUR = "blur",
51
+ IMAGE = "image",
52
+ NONE = "none"
53
+ }
54
+ declare const MAX_BACKGROUND_URL_LENGTH = 2048;
55
+ interface EmbedBackground {
56
+ type: EmbedBackgroundType;
57
+ /** BLUR only. Defaults to `DEFAULT_BACKGROUND_BLUR_RADIUS`, clamped 1-50. */
58
+ blurRadius?: number;
59
+ /** IMAGE only. http(s) URL, max `MAX_BACKGROUND_URL_LENGTH`. */
60
+ url?: string;
61
+ /** When true the embed user cannot change blur/virtual backgrounds. */
62
+ locked?: boolean;
63
+ }
43
64
  /** Open host-owned tool id. Meeting does not catalog values. */
44
65
  type StageToolId = string;
45
66
  declare const MAX_STAGE_URL_LENGTH = 2048;
@@ -54,8 +75,10 @@ declare enum EmbedQueryParam {
54
75
  LANG = "lang",
55
76
  AUTO_JOIN = "autoJoin",
56
77
  HIDE_LEAVE = "hideLeave",
78
+ AUTO_CAMERA_OFF = "autoCameraOff",
57
79
  AUTO_TRANSCRIBE = "autoTranscribe",
58
- AUTO_RECORD = "autoRecord"
80
+ AUTO_RECORD = "autoRecord",
81
+ BACKGROUND = "background"
59
82
  }
60
83
  /**
61
84
  * What happened to the auto-transcribe request the embed made.
@@ -196,7 +219,7 @@ type EmbedEventOf<T extends EmbedEventType> = Extract<EmbedEvent, {
196
219
  type EmbedCommandMessage = EmbedMessageBase & {
197
220
  type: 'oemeeting:command';
198
221
  action: EmbedCommandAction;
199
- payload?: OpenStageInput;
222
+ payload?: OpenStageInput | EmbedBackground;
200
223
  };
201
224
  interface OeMeetingHandle {
202
225
  leave: () => void;
@@ -215,6 +238,12 @@ interface OeMeetingProps {
215
238
  lang?: string;
216
239
  autoJoin?: boolean;
217
240
  hideLeave?: boolean;
241
+ /**
242
+ * Start with the camera off: the prejoin screen initializes with video
243
+ * disabled and the room does not publish video on connect. The user can
244
+ * still turn the camera on manually.
245
+ */
246
+ autoCameraOff?: boolean;
218
247
  /**
219
248
  * Start the shared transcript as soon as the embed connects. The transcript
220
249
  * is a room-level asset: the first participant to connect starts it and it
@@ -224,6 +253,14 @@ interface OeMeetingProps {
224
253
  autoTranscribe?: boolean;
225
254
  /** Start the shared room recording as soon as the embed connects. */
226
255
  autoRecord?: boolean;
256
+ /**
257
+ * Apply a camera background to this embed's local user (blur / image /
258
+ * none), optionally locking their own background choices. Pass the same
259
+ * value for every participant to brand an event. The initial value rides
260
+ * the embed URL; later changes are sent as a `setBackground` command once
261
+ * the embed is ready. Invalid input is ignored.
262
+ */
263
+ background?: EmbedBackground;
227
264
  title?: string;
228
265
  width?: number | string;
229
266
  height?: number | string;
@@ -260,8 +297,10 @@ interface BuildEmbedUrlOptions {
260
297
  lang?: string;
261
298
  autoJoin?: boolean;
262
299
  hideLeave?: boolean;
300
+ autoCameraOff?: boolean;
263
301
  autoTranscribe?: boolean;
264
302
  autoRecord?: boolean;
303
+ background?: EmbedBackground;
265
304
  instanceId?: string;
266
305
  hostOrigin?: string;
267
306
  }
@@ -269,7 +308,7 @@ interface BuildEmbedUrlOptions {
269
308
  declare const DEFAULT_IFRAME_ALLOW = "camera; microphone; fullscreen; autoplay; display-capture; clipboard-write; encrypted-media";
270
309
  declare const OeMeeting: react.ForwardRefExoticComponent<OeMeetingProps & react.RefAttributes<OeMeetingHandle>>;
271
310
 
272
- type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onStageChanged' | 'onTranscriptStarted' | 'onRecordingStarted' | 'onRecordingSaved' | 'onEvent'> & {
311
+ type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onStageChanged' | 'onTranscriptStarted' | 'onRecordingStarted' | 'onRecordingSaved' | 'onEvent' | 'background'> & {
273
312
  instanceId: string;
274
313
  targetOrigin: string;
275
314
  handleRef?: Ref<OeMeetingHandle>;
@@ -286,4 +325,12 @@ declare function parseEmbedEvent(data: unknown): EmbedEvent | null;
286
325
  declare function isValidStageUrl(url: string): boolean;
287
326
  declare function normalizeOpenStageInput(input: OpenStageInput): OpenStageInput | null;
288
327
 
289
- export { type BuildEmbedUrlOptions, DEFAULT_EMBED_ORIGIN, DEFAULT_IFRAME_ALLOW, EMBED_MESSAGE_SOURCE, EMBED_PROTOCOL_VERSION, EmbedCapability, EmbedCaptureOutcome, EmbedCommandAction, type EmbedCommandMessage, EmbedCommandType, type EmbedEndedPayload, EmbedErrorCode, type EmbedErrorPayload, type EmbedEvent, type EmbedEventOf, type EmbedEventPayloadMap, EmbedEventType, type EmbedJoinedPayload, type EmbedLeftPayload, type EmbedMessageBase, type EmbedParticipantJoinedPayload, type EmbedParticipantLeftPayload, type EmbedPermissionMap, EmbedQueryParam, type EmbedReadyPayload, type EmbedRecordingSavedPayload, type EmbedRecordingStartedPayload, EmbedRole, type EmbedStageChangedPayload, type EmbedTranscriptStartedPayload, MAX_STAGE_TOOL_ID_LENGTH, MAX_STAGE_URL_LENGTH, OeMeeting, type OeMeetingHandle, type OeMeetingProps, type OpenStageInput, type StageToolId, buildEmbedUrl, isStageToolId, isValidStageUrl, normalizeOpenStageInput, parseEmbedEvent, useOeMeeting };
328
+ /**
329
+ * Validate and normalize a host-provided background. Returns null for invalid
330
+ * input so callers can silently drop it, mirroring `normalizeOpenStageInput`.
331
+ */
332
+ declare function normalizeEmbedBackground(input: EmbedBackground): EmbedBackground | null;
333
+ /** JSON wire format shared by the `background` query param and the command payload. */
334
+ declare function serializeEmbedBackground(input: EmbedBackground): string | undefined;
335
+
336
+ export { type BuildEmbedUrlOptions, DEFAULT_EMBED_ORIGIN, DEFAULT_IFRAME_ALLOW, EMBED_MESSAGE_SOURCE, EMBED_PROTOCOL_VERSION, type EmbedBackground, EmbedBackgroundType, EmbedCapability, EmbedCaptureOutcome, EmbedCommandAction, type EmbedCommandMessage, EmbedCommandType, type EmbedEndedPayload, EmbedErrorCode, type EmbedErrorPayload, type EmbedEvent, type EmbedEventOf, type EmbedEventPayloadMap, EmbedEventType, type EmbedJoinedPayload, type EmbedLeftPayload, type EmbedMessageBase, type EmbedParticipantJoinedPayload, type EmbedParticipantLeftPayload, type EmbedPermissionMap, EmbedQueryParam, type EmbedReadyPayload, type EmbedRecordingSavedPayload, type EmbedRecordingStartedPayload, EmbedRole, type EmbedStageChangedPayload, type EmbedTranscriptStartedPayload, MAX_BACKGROUND_URL_LENGTH, MAX_STAGE_TOOL_ID_LENGTH, MAX_STAGE_URL_LENGTH, OeMeeting, type OeMeetingHandle, type OeMeetingProps, type OpenStageInput, type StageToolId, buildEmbedUrl, isStageToolId, isValidStageUrl, normalizeEmbedBackground, normalizeOpenStageInput, parseEmbedEvent, serializeEmbedBackground, useOeMeeting };
package/dist/index.d.ts CHANGED
@@ -25,7 +25,8 @@ declare enum EmbedCommandAction {
25
25
  TOGGLE_MIC = "toggleMic",
26
26
  TOGGLE_CAMERA = "toggleCamera",
27
27
  OPEN_STAGE = "openStage",
28
- CLOSE_STAGE = "closeStage"
28
+ CLOSE_STAGE = "closeStage",
29
+ SET_BACKGROUND = "setBackground"
29
30
  }
30
31
  declare enum EmbedErrorCode {
31
32
  TOKEN_INVALID = "TOKEN_INVALID",
@@ -40,6 +41,26 @@ declare enum EmbedErrorCode {
40
41
  RECORDING_START_FAILED = "RECORDING_START_FAILED",
41
42
  UNKNOWN = "UNKNOWN"
42
43
  }
44
+ /**
45
+ * Host-controlled camera background applied to this embed's local user only.
46
+ * `none` clears any active background effect. The host app enforces who may
47
+ * set it; the meeting does not interpret roles for this value.
48
+ */
49
+ declare enum EmbedBackgroundType {
50
+ BLUR = "blur",
51
+ IMAGE = "image",
52
+ NONE = "none"
53
+ }
54
+ declare const MAX_BACKGROUND_URL_LENGTH = 2048;
55
+ interface EmbedBackground {
56
+ type: EmbedBackgroundType;
57
+ /** BLUR only. Defaults to `DEFAULT_BACKGROUND_BLUR_RADIUS`, clamped 1-50. */
58
+ blurRadius?: number;
59
+ /** IMAGE only. http(s) URL, max `MAX_BACKGROUND_URL_LENGTH`. */
60
+ url?: string;
61
+ /** When true the embed user cannot change blur/virtual backgrounds. */
62
+ locked?: boolean;
63
+ }
43
64
  /** Open host-owned tool id. Meeting does not catalog values. */
44
65
  type StageToolId = string;
45
66
  declare const MAX_STAGE_URL_LENGTH = 2048;
@@ -54,8 +75,10 @@ declare enum EmbedQueryParam {
54
75
  LANG = "lang",
55
76
  AUTO_JOIN = "autoJoin",
56
77
  HIDE_LEAVE = "hideLeave",
78
+ AUTO_CAMERA_OFF = "autoCameraOff",
57
79
  AUTO_TRANSCRIBE = "autoTranscribe",
58
- AUTO_RECORD = "autoRecord"
80
+ AUTO_RECORD = "autoRecord",
81
+ BACKGROUND = "background"
59
82
  }
60
83
  /**
61
84
  * What happened to the auto-transcribe request the embed made.
@@ -196,7 +219,7 @@ type EmbedEventOf<T extends EmbedEventType> = Extract<EmbedEvent, {
196
219
  type EmbedCommandMessage = EmbedMessageBase & {
197
220
  type: 'oemeeting:command';
198
221
  action: EmbedCommandAction;
199
- payload?: OpenStageInput;
222
+ payload?: OpenStageInput | EmbedBackground;
200
223
  };
201
224
  interface OeMeetingHandle {
202
225
  leave: () => void;
@@ -215,6 +238,12 @@ interface OeMeetingProps {
215
238
  lang?: string;
216
239
  autoJoin?: boolean;
217
240
  hideLeave?: boolean;
241
+ /**
242
+ * Start with the camera off: the prejoin screen initializes with video
243
+ * disabled and the room does not publish video on connect. The user can
244
+ * still turn the camera on manually.
245
+ */
246
+ autoCameraOff?: boolean;
218
247
  /**
219
248
  * Start the shared transcript as soon as the embed connects. The transcript
220
249
  * is a room-level asset: the first participant to connect starts it and it
@@ -224,6 +253,14 @@ interface OeMeetingProps {
224
253
  autoTranscribe?: boolean;
225
254
  /** Start the shared room recording as soon as the embed connects. */
226
255
  autoRecord?: boolean;
256
+ /**
257
+ * Apply a camera background to this embed's local user (blur / image /
258
+ * none), optionally locking their own background choices. Pass the same
259
+ * value for every participant to brand an event. The initial value rides
260
+ * the embed URL; later changes are sent as a `setBackground` command once
261
+ * the embed is ready. Invalid input is ignored.
262
+ */
263
+ background?: EmbedBackground;
227
264
  title?: string;
228
265
  width?: number | string;
229
266
  height?: number | string;
@@ -260,8 +297,10 @@ interface BuildEmbedUrlOptions {
260
297
  lang?: string;
261
298
  autoJoin?: boolean;
262
299
  hideLeave?: boolean;
300
+ autoCameraOff?: boolean;
263
301
  autoTranscribe?: boolean;
264
302
  autoRecord?: boolean;
303
+ background?: EmbedBackground;
265
304
  instanceId?: string;
266
305
  hostOrigin?: string;
267
306
  }
@@ -269,7 +308,7 @@ interface BuildEmbedUrlOptions {
269
308
  declare const DEFAULT_IFRAME_ALLOW = "camera; microphone; fullscreen; autoplay; display-capture; clipboard-write; encrypted-media";
270
309
  declare const OeMeeting: react.ForwardRefExoticComponent<OeMeetingProps & react.RefAttributes<OeMeetingHandle>>;
271
310
 
272
- type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onStageChanged' | 'onTranscriptStarted' | 'onRecordingStarted' | 'onRecordingSaved' | 'onEvent'> & {
311
+ type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onStageChanged' | 'onTranscriptStarted' | 'onRecordingStarted' | 'onRecordingSaved' | 'onEvent' | 'background'> & {
273
312
  instanceId: string;
274
313
  targetOrigin: string;
275
314
  handleRef?: Ref<OeMeetingHandle>;
@@ -286,4 +325,12 @@ declare function parseEmbedEvent(data: unknown): EmbedEvent | null;
286
325
  declare function isValidStageUrl(url: string): boolean;
287
326
  declare function normalizeOpenStageInput(input: OpenStageInput): OpenStageInput | null;
288
327
 
289
- export { type BuildEmbedUrlOptions, DEFAULT_EMBED_ORIGIN, DEFAULT_IFRAME_ALLOW, EMBED_MESSAGE_SOURCE, EMBED_PROTOCOL_VERSION, EmbedCapability, EmbedCaptureOutcome, EmbedCommandAction, type EmbedCommandMessage, EmbedCommandType, type EmbedEndedPayload, EmbedErrorCode, type EmbedErrorPayload, type EmbedEvent, type EmbedEventOf, type EmbedEventPayloadMap, EmbedEventType, type EmbedJoinedPayload, type EmbedLeftPayload, type EmbedMessageBase, type EmbedParticipantJoinedPayload, type EmbedParticipantLeftPayload, type EmbedPermissionMap, EmbedQueryParam, type EmbedReadyPayload, type EmbedRecordingSavedPayload, type EmbedRecordingStartedPayload, EmbedRole, type EmbedStageChangedPayload, type EmbedTranscriptStartedPayload, MAX_STAGE_TOOL_ID_LENGTH, MAX_STAGE_URL_LENGTH, OeMeeting, type OeMeetingHandle, type OeMeetingProps, type OpenStageInput, type StageToolId, buildEmbedUrl, isStageToolId, isValidStageUrl, normalizeOpenStageInput, parseEmbedEvent, useOeMeeting };
328
+ /**
329
+ * Validate and normalize a host-provided background. Returns null for invalid
330
+ * input so callers can silently drop it, mirroring `normalizeOpenStageInput`.
331
+ */
332
+ declare function normalizeEmbedBackground(input: EmbedBackground): EmbedBackground | null;
333
+ /** JSON wire format shared by the `background` query param and the command payload. */
334
+ declare function serializeEmbedBackground(input: EmbedBackground): string | undefined;
335
+
336
+ export { type BuildEmbedUrlOptions, DEFAULT_EMBED_ORIGIN, DEFAULT_IFRAME_ALLOW, EMBED_MESSAGE_SOURCE, EMBED_PROTOCOL_VERSION, type EmbedBackground, EmbedBackgroundType, EmbedCapability, EmbedCaptureOutcome, EmbedCommandAction, type EmbedCommandMessage, EmbedCommandType, type EmbedEndedPayload, EmbedErrorCode, type EmbedErrorPayload, type EmbedEvent, type EmbedEventOf, type EmbedEventPayloadMap, EmbedEventType, type EmbedJoinedPayload, type EmbedLeftPayload, type EmbedMessageBase, type EmbedParticipantJoinedPayload, type EmbedParticipantLeftPayload, type EmbedPermissionMap, EmbedQueryParam, type EmbedReadyPayload, type EmbedRecordingSavedPayload, type EmbedRecordingStartedPayload, EmbedRole, type EmbedStageChangedPayload, type EmbedTranscriptStartedPayload, MAX_BACKGROUND_URL_LENGTH, MAX_STAGE_TOOL_ID_LENGTH, MAX_STAGE_URL_LENGTH, OeMeeting, type OeMeetingHandle, type OeMeetingProps, type OpenStageInput, type StageToolId, buildEmbedUrl, isStageToolId, isValidStageUrl, normalizeEmbedBackground, normalizeOpenStageInput, parseEmbedEvent, serializeEmbedBackground, useOeMeeting };
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
  import {
3
3
  forwardRef,
4
4
  useId,
5
- useMemo as useMemo2
5
+ useMemo as useMemo2,
6
+ useRef as useRef2
6
7
  } from "react";
7
8
 
8
9
  // src/embed-protocol.ts
@@ -33,6 +34,7 @@ var EmbedCommandAction = /* @__PURE__ */ ((EmbedCommandAction2) => {
33
34
  EmbedCommandAction2["TOGGLE_CAMERA"] = "toggleCamera";
34
35
  EmbedCommandAction2["OPEN_STAGE"] = "openStage";
35
36
  EmbedCommandAction2["CLOSE_STAGE"] = "closeStage";
37
+ EmbedCommandAction2["SET_BACKGROUND"] = "setBackground";
36
38
  return EmbedCommandAction2;
37
39
  })(EmbedCommandAction || {});
38
40
  var EmbedErrorCode = /* @__PURE__ */ ((EmbedErrorCode2) => {
@@ -49,6 +51,16 @@ var EmbedErrorCode = /* @__PURE__ */ ((EmbedErrorCode2) => {
49
51
  EmbedErrorCode2["UNKNOWN"] = "UNKNOWN";
50
52
  return EmbedErrorCode2;
51
53
  })(EmbedErrorCode || {});
54
+ var EmbedBackgroundType = /* @__PURE__ */ ((EmbedBackgroundType2) => {
55
+ EmbedBackgroundType2["BLUR"] = "blur";
56
+ EmbedBackgroundType2["IMAGE"] = "image";
57
+ EmbedBackgroundType2["NONE"] = "none";
58
+ return EmbedBackgroundType2;
59
+ })(EmbedBackgroundType || {});
60
+ var MAX_BACKGROUND_URL_LENGTH = 2048;
61
+ var DEFAULT_BACKGROUND_BLUR_RADIUS = 10;
62
+ var MIN_BACKGROUND_BLUR_RADIUS = 1;
63
+ var MAX_BACKGROUND_BLUR_RADIUS = 50;
52
64
  var MAX_STAGE_URL_LENGTH = 2048;
53
65
  var MAX_STAGE_TOOL_ID_LENGTH = 32;
54
66
  var STAGE_TOOL_ID_PATTERN = /^[a-z][a-z0-9._-]{0,31}$/;
@@ -64,8 +76,10 @@ var EmbedQueryParam = /* @__PURE__ */ ((EmbedQueryParam2) => {
64
76
  EmbedQueryParam2["LANG"] = "lang";
65
77
  EmbedQueryParam2["AUTO_JOIN"] = "autoJoin";
66
78
  EmbedQueryParam2["HIDE_LEAVE"] = "hideLeave";
79
+ EmbedQueryParam2["AUTO_CAMERA_OFF"] = "autoCameraOff";
67
80
  EmbedQueryParam2["AUTO_TRANSCRIBE"] = "autoTranscribe";
68
81
  EmbedQueryParam2["AUTO_RECORD"] = "autoRecord";
82
+ EmbedQueryParam2["BACKGROUND"] = "background";
69
83
  return EmbedQueryParam2;
70
84
  })(EmbedQueryParam || {});
71
85
  var EmbedCaptureOutcome = /* @__PURE__ */ ((EmbedCaptureOutcome2) => {
@@ -85,6 +99,62 @@ var EmbedCapability = /* @__PURE__ */ ((EmbedCapability2) => {
85
99
  return EmbedCapability2;
86
100
  })(EmbedCapability || {});
87
101
 
102
+ // src/openStage.ts
103
+ function isValidStageUrl(url) {
104
+ if (url.length === 0 || url.length > MAX_STAGE_URL_LENGTH) return false;
105
+ try {
106
+ const parsed = new URL(url);
107
+ if (parsed.protocol === "https:") return true;
108
+ if (parsed.protocol === "http:") {
109
+ return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
110
+ }
111
+ return false;
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+ function normalizeOpenStageInput(input) {
117
+ const url = input.url.trim();
118
+ if (!isValidStageUrl(url)) return null;
119
+ const title = input.title?.trim();
120
+ return {
121
+ url,
122
+ ...title ? { title: title.slice(0, 200) } : {},
123
+ ...isStageToolId(input.tool) ? { tool: input.tool } : {}
124
+ };
125
+ }
126
+
127
+ // src/background.ts
128
+ var isValidBackgroundUrl = (url) => url.length > 0 && url.length <= MAX_BACKGROUND_URL_LENGTH && isValidStageUrl(url);
129
+ var clampBlurRadius = (value) => Number.isFinite(value) ? Math.min(MAX_BACKGROUND_BLUR_RADIUS, Math.max(MIN_BACKGROUND_BLUR_RADIUS, Math.round(value))) : DEFAULT_BACKGROUND_BLUR_RADIUS;
130
+ function normalizeEmbedBackground(input) {
131
+ const locked = input.locked === true ? { locked: true } : {};
132
+ if (input.type === "none" /* NONE */) {
133
+ return { type: "none" /* NONE */, ...locked };
134
+ }
135
+ if (input.type === "blur" /* BLUR */) {
136
+ return {
137
+ type: "blur" /* BLUR */,
138
+ blurRadius: clampBlurRadius(input.blurRadius ?? DEFAULT_BACKGROUND_BLUR_RADIUS),
139
+ ...locked
140
+ };
141
+ }
142
+ if (input.type === "image" /* IMAGE */) {
143
+ const url = input.url?.trim();
144
+ if (!url || !isValidBackgroundUrl(url)) return null;
145
+ return {
146
+ type: "image" /* IMAGE */,
147
+ url,
148
+ ...locked
149
+ };
150
+ }
151
+ return null;
152
+ }
153
+ function serializeEmbedBackground(input) {
154
+ const normalized = normalizeEmbedBackground(input);
155
+ return normalized ? JSON.stringify(normalized) : void 0;
156
+ }
157
+
88
158
  // src/buildEmbedUrl.ts
89
159
  var QUERY_FLAG_ENABLED = "1";
90
160
  var QUERY_FLAG_DISABLED = "0";
@@ -106,6 +176,7 @@ function buildEmbedUrl(options) {
106
176
  }
107
177
  const origin = normalizeOrigin(options.origin ?? DEFAULT_EMBED_ORIGIN);
108
178
  const url = new URL(`${origin}/embed/${encodeURIComponent(roomSlug)}`);
179
+ const backgroundParam = options.background ? serializeEmbedBackground(options.background) : void 0;
109
180
  const entries = [
110
181
  ["instanceId" /* INSTANCE_ID */, options.instanceId],
111
182
  ["hostOrigin" /* HOST_ORIGIN */, options.hostOrigin],
@@ -115,8 +186,10 @@ function buildEmbedUrl(options) {
115
186
  ["lang" /* LANG */, options.lang],
116
187
  ["autoJoin" /* AUTO_JOIN */, toFlag(options.autoJoin)],
117
188
  ["hideLeave" /* HIDE_LEAVE */, toFlag(options.hideLeave)],
189
+ ["autoCameraOff" /* AUTO_CAMERA_OFF */, toFlag(options.autoCameraOff)],
118
190
  ["autoTranscribe" /* AUTO_TRANSCRIBE */, toFlag(options.autoTranscribe)],
119
- ["autoRecord" /* AUTO_RECORD */, toFlag(options.autoRecord)]
191
+ ["autoRecord" /* AUTO_RECORD */, toFlag(options.autoRecord)],
192
+ ["background" /* BACKGROUND */, backgroundParam]
120
193
  ];
121
194
  for (const [key, value] of entries) {
122
195
  if (value !== void 0 && value !== "") {
@@ -135,31 +208,6 @@ import {
135
208
  useRef
136
209
  } from "react";
137
210
 
138
- // src/openStage.ts
139
- function isValidStageUrl(url) {
140
- if (url.length === 0 || url.length > MAX_STAGE_URL_LENGTH) return false;
141
- try {
142
- const parsed = new URL(url);
143
- if (parsed.protocol === "https:") return true;
144
- if (parsed.protocol === "http:") {
145
- return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
146
- }
147
- return false;
148
- } catch {
149
- return false;
150
- }
151
- }
152
- function normalizeOpenStageInput(input) {
153
- const url = input.url.trim();
154
- if (!isValidStageUrl(url)) return null;
155
- const title = input.title?.trim();
156
- return {
157
- url,
158
- ...title ? { title: title.slice(0, 200) } : {},
159
- ...isStageToolId(input.tool) ? { tool: input.tool } : {}
160
- };
161
- }
162
-
163
211
  // src/parseEmbedEvent.ts
164
212
  var EVENT_TYPES = new Set(Object.values(EmbedEventType));
165
213
  function isRecord(value) {
@@ -228,6 +276,7 @@ function postCommand(options) {
228
276
  function useOeMeeting(options) {
229
277
  const iframeRef = useRef(null);
230
278
  const callbacksRef = useRef(options);
279
+ const pendingBackgroundRef = useRef(null);
231
280
  useEffect(() => {
232
281
  callbacksRef.current = options;
233
282
  }, [options]);
@@ -244,6 +293,15 @@ function useOeMeeting(options) {
244
293
  switch (embedEvent.type) {
245
294
  case "oemeeting:ready" /* READY */:
246
295
  callbacks.onReady?.(embedEvent);
296
+ if (pendingBackgroundRef.current) {
297
+ postCommand({
298
+ iframe: iframeRef.current,
299
+ instanceId: options.instanceId,
300
+ action: "setBackground" /* SET_BACKGROUND */,
301
+ targetOrigin: options.targetOrigin,
302
+ payload: pendingBackgroundRef.current
303
+ });
304
+ }
247
305
  break;
248
306
  case "oemeeting:joined" /* JOINED */:
249
307
  callbacks.onJoined?.(embedEvent);
@@ -311,6 +369,16 @@ function useOeMeeting(options) {
311
369
  () => send("closeStage" /* CLOSE_STAGE */),
312
370
  [send]
313
371
  );
372
+ useEffect(() => {
373
+ if (!options.background) {
374
+ pendingBackgroundRef.current = null;
375
+ return;
376
+ }
377
+ const normalized = normalizeEmbedBackground(options.background);
378
+ if (!normalized) return;
379
+ pendingBackgroundRef.current = normalized;
380
+ send("setBackground" /* SET_BACKGROUND */, normalized);
381
+ }, [options.background, send]);
314
382
  const getIframe = useCallback(() => iframeRef.current, []);
315
383
  const handle = useMemo(
316
384
  () => ({
@@ -348,8 +416,10 @@ var OeMeeting = forwardRef(
348
416
  lang,
349
417
  autoJoin = true,
350
418
  hideLeave,
419
+ autoCameraOff,
351
420
  autoTranscribe,
352
421
  autoRecord,
422
+ background,
353
423
  title = "OE Meeting room",
354
424
  width = "100%",
355
425
  height = 640,
@@ -380,6 +450,7 @@ var OeMeeting = forwardRef(
380
450
  const embedOrigin = useMemo2(() => getOrigin(origin), [origin]);
381
451
  const resolvedTargetOrigin = targetOrigin ?? embedOrigin;
382
452
  const hostOrigin = typeof window === "undefined" ? void 0 : window.location.origin;
453
+ const initialBackgroundRef = useRef2(background);
383
454
  const src = useMemo2(
384
455
  () => buildEmbedUrl({
385
456
  roomSlug,
@@ -390,8 +461,10 @@ var OeMeeting = forwardRef(
390
461
  lang,
391
462
  autoJoin,
392
463
  hideLeave,
464
+ autoCameraOff,
393
465
  autoTranscribe,
394
466
  autoRecord,
467
+ background: initialBackgroundRef.current,
395
468
  instanceId,
396
469
  hostOrigin
397
470
  }),
@@ -407,10 +480,12 @@ var OeMeeting = forwardRef(
407
480
  lang,
408
481
  origin,
409
482
  roomSlug,
483
+ autoCameraOff,
410
484
  userName
411
485
  ]
412
486
  );
413
487
  const { iframeRef } = useOeMeeting({
488
+ background,
414
489
  instanceId,
415
490
  targetOrigin: resolvedTargetOrigin,
416
491
  handleRef: ref,
@@ -468,6 +543,7 @@ export {
468
543
  DEFAULT_IFRAME_ALLOW,
469
544
  EMBED_MESSAGE_SOURCE,
470
545
  EMBED_PROTOCOL_VERSION,
546
+ EmbedBackgroundType,
471
547
  EmbedCapability,
472
548
  EmbedCaptureOutcome,
473
549
  EmbedCommandAction,
@@ -476,14 +552,17 @@ export {
476
552
  EmbedEventType,
477
553
  EmbedQueryParam,
478
554
  EmbedRole,
555
+ MAX_BACKGROUND_URL_LENGTH,
479
556
  MAX_STAGE_TOOL_ID_LENGTH,
480
557
  MAX_STAGE_URL_LENGTH,
481
558
  OeMeeting,
482
559
  buildEmbedUrl,
483
560
  isStageToolId,
484
561
  isValidStageUrl,
562
+ normalizeEmbedBackground,
485
563
  normalizeOpenStageInput,
486
564
  parseEmbedEvent,
565
+ serializeEmbedBackground,
487
566
  useOeMeeting
488
567
  };
489
568
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/OeMeeting.tsx","../src/embed-protocol.ts","../src/buildEmbedUrl.ts","../src/useOeMeeting.ts","../src/openStage.ts","../src/parseEmbedEvent.ts"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n useId,\n useMemo,\n type CSSProperties,\n} from 'react';\nimport { buildEmbedUrl } from './buildEmbedUrl';\nimport { DEFAULT_EMBED_ORIGIN } from './embed-protocol';\nimport type { OeMeetingHandle, OeMeetingProps } from './types';\nimport { useOeMeeting } from './useOeMeeting';\n\nexport const DEFAULT_IFRAME_ALLOW =\n 'camera; microphone; fullscreen; autoplay; display-capture; clipboard-write; encrypted-media';\n\nfunction toCssSize(value: number | string | undefined): string | undefined {\n if (value === undefined) return undefined;\n return typeof value === 'number' ? `${value}px` : value;\n}\n\nfunction getOrigin(value: string): string {\n return new URL(value).origin;\n}\n\nexport const OeMeeting = forwardRef<OeMeetingHandle, OeMeetingProps>(\n function OeMeeting(props, ref) {\n const {\n roomSlug,\n joinToken,\n userName,\n externalUserId,\n origin = DEFAULT_EMBED_ORIGIN,\n lang,\n autoJoin = true,\n hideLeave,\n autoTranscribe,\n autoRecord,\n title = 'OE Meeting room',\n width = '100%',\n height = 640,\n className,\n style,\n iframeClassName,\n iframeStyle,\n allow = DEFAULT_IFRAME_ALLOW,\n targetOrigin,\n onReady,\n onJoined,\n onLeft,\n onParticipantJoined,\n onParticipantLeft,\n onEnded,\n onError,\n onStageChanged,\n onTranscriptStarted,\n onRecordingStarted,\n onRecordingSaved,\n onEvent,\n } = props;\n const reactId = useId();\n const instanceId = useMemo(\n () => `oem-${reactId.replace(/:/g, '')}`,\n [reactId],\n );\n const embedOrigin = useMemo(() => getOrigin(origin), [origin]);\n const resolvedTargetOrigin = targetOrigin ?? embedOrigin;\n const hostOrigin =\n typeof window === 'undefined' ? undefined : window.location.origin;\n const src = useMemo(\n () =>\n buildEmbedUrl({\n roomSlug,\n joinToken,\n userName,\n externalUserId,\n origin,\n lang,\n autoJoin,\n hideLeave,\n autoTranscribe,\n autoRecord,\n instanceId,\n hostOrigin,\n }),\n [\n autoJoin,\n autoRecord,\n autoTranscribe,\n externalUserId,\n hideLeave,\n hostOrigin,\n instanceId,\n joinToken,\n lang,\n origin,\n roomSlug,\n userName,\n ],\n );\n const { iframeRef } = useOeMeeting({\n instanceId,\n targetOrigin: resolvedTargetOrigin,\n handleRef: ref,\n onReady,\n onJoined,\n onLeft,\n onParticipantJoined,\n onParticipantLeft,\n onEnded,\n onError,\n onStageChanged,\n onTranscriptStarted,\n onRecordingStarted,\n onRecordingSaved,\n onEvent,\n });\n const wrapperStyle: CSSProperties = {\n width: toCssSize(width),\n height: toCssSize(height),\n ...style,\n };\n const frameStyle: CSSProperties = {\n width: '100%',\n height: '100%',\n border: 0,\n display: 'block',\n ...iframeStyle,\n };\n\n return (\n <div\n className={className}\n style={wrapperStyle}\n data-oe-meeting-instance={instanceId}\n >\n <iframe\n ref={iframeRef}\n className={iframeClassName}\n style={frameStyle}\n src={src}\n title={title}\n allow={allow}\n allowFullScreen\n referrerPolicy=\"strict-origin-when-cross-origin\"\n />\n </div>\n );\n },\n);\n\nOeMeeting.displayName = 'OeMeeting';\n","export const EMBED_PROTOCOL_VERSION = 1 as const;\nexport const EMBED_MESSAGE_SOURCE = 'oemeeting' as const;\nexport const DEFAULT_EMBED_ORIGIN = 'https://meet.ocean.edu.vn';\n\nexport enum EmbedEventType {\n READY = 'oemeeting:ready',\n JOINED = 'oemeeting:joined',\n LEFT = 'oemeeting:left',\n PARTICIPANT_JOINED = 'oemeeting:participant-joined',\n PARTICIPANT_LEFT = 'oemeeting:participant-left',\n ENDED = 'oemeeting:ended',\n ERROR = 'oemeeting:error',\n STAGE_CHANGED = 'oemeeting:stage-changed',\n TRANSCRIPT_STARTED = 'oemeeting:transcript-started',\n RECORDING_STARTED = 'oemeeting:recording-started',\n RECORDING_SAVED = 'oemeeting:recording-saved',\n}\n\nexport enum EmbedCommandType {\n COMMAND = 'oemeeting:command',\n}\n\nexport enum EmbedCommandAction {\n LEAVE = 'leave',\n TOGGLE_MIC = 'toggleMic',\n TOGGLE_CAMERA = 'toggleCamera',\n OPEN_STAGE = 'openStage',\n CLOSE_STAGE = 'closeStage',\n}\n\nexport enum EmbedErrorCode {\n TOKEN_INVALID = 'TOKEN_INVALID',\n TOKEN_EXPIRED = 'TOKEN_EXPIRED',\n ROOM_NOT_FOUND = 'ROOM_NOT_FOUND',\n ROOM_ACCESS_DENIED = 'ROOM_ACCESS_DENIED',\n CONNECTION_FAILED = 'CONNECTION_FAILED',\n PROTOCOL_MISMATCH = 'PROTOCOL_MISMATCH',\n STAGE_ORIGIN_DENIED = 'STAGE_ORIGIN_DENIED',\n STAGE_URL_INVALID = 'STAGE_URL_INVALID',\n TRANSCRIPT_START_FAILED = 'TRANSCRIPT_START_FAILED',\n RECORDING_START_FAILED = 'RECORDING_START_FAILED',\n UNKNOWN = 'UNKNOWN',\n}\n\n/** Open host-owned tool id. Meeting does not catalog values. */\nexport type StageToolId = string;\n\nexport const MAX_STAGE_URL_LENGTH = 2048;\nexport const MAX_STAGE_TOOL_ID_LENGTH = 32;\n\nconst STAGE_TOOL_ID_PATTERN = /^[a-z][a-z0-9._-]{0,31}$/;\n\nexport function isStageToolId(value: unknown): value is StageToolId {\n return (\n typeof value === 'string' &&\n value.length > 0 &&\n value.length <= MAX_STAGE_TOOL_ID_LENGTH &&\n STAGE_TOOL_ID_PATTERN.test(value)\n );\n}\n\nexport enum EmbedQueryParam {\n INSTANCE_ID = 'instanceId',\n HOST_ORIGIN = 'hostOrigin',\n JOIN_TOKEN = 'joinToken',\n USER_NAME = 'userName',\n EXTERNAL_USER_ID = 'externalUserId',\n LANG = 'lang',\n AUTO_JOIN = 'autoJoin',\n HIDE_LEAVE = 'hideLeave',\n AUTO_TRANSCRIBE = 'autoTranscribe',\n AUTO_RECORD = 'autoRecord',\n}\n\n/**\n * What happened to the auto-transcribe request the embed made.\n *\n * `ALREADY_RUNNING` is the normal outcome for every participant after the\n * first, because the transcript is a room-level asset rather than a\n * per-participant one. `DISABLED` means the OE Meeting deployment has\n * transcription turned off.\n */\nexport enum EmbedCaptureOutcome {\n STARTED = 'started',\n ALREADY_RUNNING = 'already_running',\n DISABLED = 'disabled',\n FAILED = 'failed',\n}\n\n/**\n * Role the host app assigns when minting a join token.\n * Keep values aligned with `EmbedRole` in the backend embed-permission enum.\n */\nexport enum EmbedRole {\n OWNER = 'owner',\n MEMBER = 'member',\n}\n\n/**\n * Capability the host app may grant per role. Meeting enforces this on the\n * join token, not on a React prop. `screenShare` is the only value today.\n */\nexport enum EmbedCapability {\n SCREEN_SHARE = 'screenShare',\n}\n\nexport interface EmbedPermissionMap {\n owner?: EmbedCapability[];\n member?: EmbedCapability[];\n}\n","import {\n DEFAULT_EMBED_ORIGIN,\n EmbedQueryParam,\n} from './embed-protocol';\nimport type { BuildEmbedUrlOptions } from './types';\n\nconst QUERY_FLAG_ENABLED = '1';\nconst QUERY_FLAG_DISABLED = '0';\n\nfunction toFlag(value: boolean | undefined): string | undefined {\n if (value === undefined) return undefined;\n return value ? QUERY_FLAG_ENABLED : QUERY_FLAG_DISABLED;\n}\n\nfunction normalizeOrigin(origin: string): string {\n return origin.replace(/\\/+$/, '');\n}\n\nexport function buildEmbedUrl(options: BuildEmbedUrlOptions): string {\n const roomSlug = options.roomSlug.trim();\n const joinToken = options.joinToken.trim();\n if (!roomSlug) {\n throw new Error('OE Meeting embed requires a non-empty roomSlug.');\n }\n if (!joinToken) {\n throw new Error('OE Meeting embed requires a non-empty joinToken.');\n }\n\n const origin = normalizeOrigin(options.origin ?? DEFAULT_EMBED_ORIGIN);\n const url = new URL(`${origin}/embed/${encodeURIComponent(roomSlug)}`);\n const entries: Array<[EmbedQueryParam, string | undefined]> = [\n [EmbedQueryParam.INSTANCE_ID, options.instanceId],\n [EmbedQueryParam.HOST_ORIGIN, options.hostOrigin],\n [EmbedQueryParam.JOIN_TOKEN, joinToken],\n [EmbedQueryParam.USER_NAME, options.userName],\n [EmbedQueryParam.EXTERNAL_USER_ID, options.externalUserId],\n [EmbedQueryParam.LANG, options.lang],\n [EmbedQueryParam.AUTO_JOIN, toFlag(options.autoJoin)],\n [EmbedQueryParam.HIDE_LEAVE, toFlag(options.hideLeave)],\n [EmbedQueryParam.AUTO_TRANSCRIBE, toFlag(options.autoTranscribe)],\n [EmbedQueryParam.AUTO_RECORD, toFlag(options.autoRecord)],\n ];\n\n for (const [key, value] of entries) {\n if (value !== undefined && value !== '') {\n url.searchParams.set(key, value);\n }\n }\n return url.toString();\n}\n","import {\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n type Ref,\n} from 'react';\nimport {\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n EmbedCommandAction,\n EmbedCommandType,\n EmbedEventType,\n} from './embed-protocol';\nimport { normalizeOpenStageInput } from './openStage';\nimport { parseEmbedEvent } from './parseEmbedEvent';\nimport type {\n EmbedCommandMessage,\n OeMeetingHandle,\n OeMeetingProps,\n OpenStageInput,\n} from './types';\n\ntype UseOeMeetingOptions = Pick<\n OeMeetingProps,\n | 'onReady'\n | 'onJoined'\n | 'onLeft'\n | 'onParticipantJoined'\n | 'onParticipantLeft'\n | 'onEnded'\n | 'onError'\n | 'onStageChanged'\n | 'onTranscriptStarted'\n | 'onRecordingStarted'\n | 'onRecordingSaved'\n | 'onEvent'\n> & {\n instanceId: string;\n targetOrigin: string;\n handleRef?: Ref<OeMeetingHandle>;\n};\n\ninterface PostCommandOptions {\n iframe: HTMLIFrameElement | null;\n instanceId: string;\n action: EmbedCommandAction;\n targetOrigin: string;\n payload?: OpenStageInput;\n}\n\nfunction postCommand(options: PostCommandOptions): void {\n const frameWindow = options.iframe?.contentWindow;\n if (!frameWindow) return;\n const message: EmbedCommandMessage = {\n source: EMBED_MESSAGE_SOURCE,\n version: EMBED_PROTOCOL_VERSION,\n instanceId: options.instanceId,\n type: EmbedCommandType.COMMAND,\n action: options.action,\n ...(options.payload ? { payload: options.payload } : {}),\n };\n frameWindow.postMessage(message, options.targetOrigin);\n}\n\nexport function useOeMeeting(options: UseOeMeetingOptions) {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const callbacksRef = useRef(options);\n\n useEffect(() => {\n callbacksRef.current = options;\n }, [options]);\n\n useEffect(() => {\n const onMessage = (event: MessageEvent<unknown>) => {\n const frameWindow = iframeRef.current?.contentWindow;\n if (\n !frameWindow ||\n event.source !== frameWindow ||\n event.origin !== options.targetOrigin\n ) {\n return;\n }\n const embedEvent = parseEmbedEvent(event.data);\n if (!embedEvent || embedEvent.instanceId !== options.instanceId) return;\n\n const callbacks = callbacksRef.current;\n callbacks.onEvent?.(embedEvent);\n switch (embedEvent.type) {\n case EmbedEventType.READY:\n callbacks.onReady?.(embedEvent);\n break;\n case EmbedEventType.JOINED:\n callbacks.onJoined?.(embedEvent);\n break;\n case EmbedEventType.LEFT:\n callbacks.onLeft?.(embedEvent);\n break;\n case EmbedEventType.PARTICIPANT_JOINED:\n callbacks.onParticipantJoined?.(embedEvent);\n break;\n case EmbedEventType.PARTICIPANT_LEFT:\n callbacks.onParticipantLeft?.(embedEvent);\n break;\n case EmbedEventType.ENDED:\n callbacks.onEnded?.(embedEvent);\n break;\n case EmbedEventType.ERROR:\n callbacks.onError?.(embedEvent);\n break;\n case EmbedEventType.STAGE_CHANGED:\n callbacks.onStageChanged?.(embedEvent);\n break;\n case EmbedEventType.TRANSCRIPT_STARTED:\n callbacks.onTranscriptStarted?.(embedEvent);\n break;\n case EmbedEventType.RECORDING_STARTED:\n callbacks.onRecordingStarted?.(embedEvent);\n break;\n case EmbedEventType.RECORDING_SAVED:\n callbacks.onRecordingSaved?.(embedEvent);\n break;\n }\n };\n\n window.addEventListener('message', onMessage);\n return () => window.removeEventListener('message', onMessage);\n }, [options.instanceId, options.targetOrigin]);\n\n const send = useCallback(\n (action: EmbedCommandAction, payload?: OpenStageInput) =>\n postCommand({\n iframe: iframeRef.current,\n instanceId: options.instanceId,\n action,\n targetOrigin: options.targetOrigin,\n payload,\n }),\n [options.instanceId, options.targetOrigin],\n );\n const leave = useCallback(() => send(EmbedCommandAction.LEAVE), [send]);\n const toggleMic = useCallback(\n () => send(EmbedCommandAction.TOGGLE_MIC),\n [send],\n );\n const toggleCamera = useCallback(\n () => send(EmbedCommandAction.TOGGLE_CAMERA),\n [send],\n );\n const openStage = useCallback(\n (input: OpenStageInput) => {\n const normalized = normalizeOpenStageInput(input);\n if (!normalized) return;\n send(EmbedCommandAction.OPEN_STAGE, normalized);\n },\n [send],\n );\n const closeStage = useCallback(\n () => send(EmbedCommandAction.CLOSE_STAGE),\n [send],\n );\n const getIframe = useCallback(() => iframeRef.current, []);\n const handle = useMemo<OeMeetingHandle>(\n () => ({\n leave,\n toggleMic,\n toggleCamera,\n openStage,\n closeStage,\n getIframe,\n }),\n [closeStage, getIframe, leave, openStage, toggleCamera, toggleMic],\n );\n\n useImperativeHandle(options.handleRef, () => handle, [handle]);\n return { iframeRef, handle };\n}\n","import { isStageToolId, MAX_STAGE_URL_LENGTH } from './embed-protocol';\nimport type { OpenStageInput } from './types';\n\nexport function isValidStageUrl(url: string): boolean {\n if (url.length === 0 || url.length > MAX_STAGE_URL_LENGTH) return false;\n try {\n const parsed = new URL(url);\n if (parsed.protocol === 'https:') return true;\n if (parsed.protocol === 'http:') {\n return (\n parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1'\n );\n }\n return false;\n } catch {\n return false;\n }\n}\n\nexport function normalizeOpenStageInput(\n input: OpenStageInput,\n): OpenStageInput | null {\n const url = input.url.trim();\n if (!isValidStageUrl(url)) return null;\n const title = input.title?.trim();\n return {\n url,\n ...(title ? { title: title.slice(0, 200) } : {}),\n ...(isStageToolId(input.tool) ? { tool: input.tool } : {}),\n };\n}\n","import {\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n EmbedCaptureOutcome,\n EmbedEventType,\n} from './embed-protocol';\nimport type { EmbedEvent } from './types';\n\nconst EVENT_TYPES = new Set<string>(Object.values(EmbedEventType));\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isOptionalString(value: unknown): boolean {\n return value === undefined || typeof value === 'string';\n}\n\nfunction isNullableString(value: unknown): boolean {\n return value === null || typeof value === 'string';\n}\n\nconst CAPTURE_OUTCOMES = new Set<string>(Object.values(EmbedCaptureOutcome));\n\nfunction isCaptureOutcome(value: unknown): boolean {\n return typeof value === 'string' && CAPTURE_OUTCOMES.has(value);\n}\n\nfunction hasValidPayload(\n type: EmbedEventType,\n payload: Record<string, unknown>,\n): boolean {\n switch (type) {\n case EmbedEventType.READY:\n return typeof payload.roomSlug === 'string';\n case EmbedEventType.JOINED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.identity === 'string' &&\n isOptionalString(payload.name) &&\n typeof payload.joinedAt === 'string'\n );\n case EmbedEventType.LEFT:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.identity === 'string' &&\n typeof payload.leftAt === 'string' &&\n (payload.durationMs === undefined ||\n typeof payload.durationMs === 'number')\n );\n case EmbedEventType.PARTICIPANT_JOINED:\n return (\n typeof payload.identity === 'string' &&\n isOptionalString(payload.name) &&\n typeof payload.participantCount === 'number'\n );\n case EmbedEventType.PARTICIPANT_LEFT:\n return (\n typeof payload.identity === 'string' &&\n typeof payload.participantCount === 'number'\n );\n case EmbedEventType.ENDED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.endedAt === 'string'\n );\n case EmbedEventType.ERROR:\n return (\n typeof payload.code === 'string' &&\n typeof payload.message === 'string'\n );\n case EmbedEventType.STAGE_CHANGED:\n return (\n (payload.url === null || typeof payload.url === 'string') &&\n isOptionalString(payload.title) &&\n isOptionalString(payload.tool) &&\n isOptionalString(payload.openedBy)\n );\n case EmbedEventType.TRANSCRIPT_STARTED:\n return (\n typeof payload.roomSlug === 'string' &&\n isCaptureOutcome(payload.outcome) &&\n isNullableString(payload.transcriptId) &&\n isNullableString(payload.status)\n );\n case EmbedEventType.RECORDING_STARTED:\n return (\n typeof payload.roomSlug === 'string' &&\n isCaptureOutcome(payload.outcome) &&\n isNullableString(payload.recordingId) &&\n isNullableString(payload.status)\n );\n case EmbedEventType.RECORDING_SAVED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.savedAt === 'string'\n );\n }\n}\n\nexport function parseEmbedEvent(data: unknown): EmbedEvent | null {\n if (!isRecord(data)) return null;\n if (\n data.source !== EMBED_MESSAGE_SOURCE ||\n data.version !== EMBED_PROTOCOL_VERSION ||\n typeof data.instanceId !== 'string' ||\n data.instanceId.length === 0 ||\n typeof data.type !== 'string' ||\n !EVENT_TYPES.has(data.type) ||\n !isRecord(data.payload)\n ) {\n return null;\n }\n\n const type = data.type as EmbedEventType;\n if (!hasValidPayload(type, data.payload)) return null;\n return data as unknown as EmbedEvent;\n}\n"],"mappings":"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","names":["useMemo","EmbedEventType","EmbedCommandType","EmbedCommandAction","EmbedErrorCode","EmbedQueryParam","EmbedCaptureOutcome","EmbedRole","EmbedCapability","OeMeeting","useMemo"]}
1
+ {"version":3,"sources":["../src/OeMeeting.tsx","../src/embed-protocol.ts","../src/openStage.ts","../src/background.ts","../src/buildEmbedUrl.ts","../src/useOeMeeting.ts","../src/parseEmbedEvent.ts"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n useId,\n useMemo,\n useRef,\n type CSSProperties,\n} from 'react';\nimport { buildEmbedUrl } from './buildEmbedUrl';\nimport { DEFAULT_EMBED_ORIGIN } from './embed-protocol';\nimport type { OeMeetingHandle, OeMeetingProps } from './types';\nimport { useOeMeeting } from './useOeMeeting';\n\nexport const DEFAULT_IFRAME_ALLOW =\n 'camera; microphone; fullscreen; autoplay; display-capture; clipboard-write; encrypted-media';\n\nfunction toCssSize(value: number | string | undefined): string | undefined {\n if (value === undefined) return undefined;\n return typeof value === 'number' ? `${value}px` : value;\n}\n\nfunction getOrigin(value: string): string {\n return new URL(value).origin;\n}\n\nexport const OeMeeting = forwardRef<OeMeetingHandle, OeMeetingProps>(\n function OeMeeting(props, ref) {\n const {\n roomSlug,\n joinToken,\n userName,\n externalUserId,\n origin = DEFAULT_EMBED_ORIGIN,\n lang,\n autoJoin = true,\n hideLeave,\n autoCameraOff,\n autoTranscribe,\n autoRecord,\n background,\n title = 'OE Meeting room',\n width = '100%',\n height = 640,\n className,\n style,\n iframeClassName,\n iframeStyle,\n allow = DEFAULT_IFRAME_ALLOW,\n targetOrigin,\n onReady,\n onJoined,\n onLeft,\n onParticipantJoined,\n onParticipantLeft,\n onEnded,\n onError,\n onStageChanged,\n onTranscriptStarted,\n onRecordingStarted,\n onRecordingSaved,\n onEvent,\n } = props;\n const reactId = useId();\n const instanceId = useMemo(\n () => `oem-${reactId.replace(/:/g, '')}`,\n [reactId],\n );\n const embedOrigin = useMemo(() => getOrigin(origin), [origin]);\n const resolvedTargetOrigin = targetOrigin ?? embedOrigin;\n const hostOrigin =\n typeof window === 'undefined' ? undefined : window.location.origin;\n // Only the mount-time background rides the embed URL. Later prop changes\n // travel as `setBackground` commands (useOeMeeting); rebuilding src would\n // reload the whole meeting mid-session.\n const initialBackgroundRef = useRef(background);\n const src = useMemo(\n () =>\n buildEmbedUrl({\n roomSlug,\n joinToken,\n userName,\n externalUserId,\n origin,\n lang,\n autoJoin,\n hideLeave,\n autoCameraOff,\n autoTranscribe,\n autoRecord,\n background: initialBackgroundRef.current,\n instanceId,\n hostOrigin,\n }),\n [\n autoJoin,\n autoRecord,\n autoTranscribe,\n externalUserId,\n hideLeave,\n hostOrigin,\n instanceId,\n joinToken,\n lang,\n origin,\n roomSlug,\n autoCameraOff,\n userName,\n ],\n );\n const { iframeRef } = useOeMeeting({\n background,\n instanceId,\n targetOrigin: resolvedTargetOrigin,\n handleRef: ref,\n onReady,\n onJoined,\n onLeft,\n onParticipantJoined,\n onParticipantLeft,\n onEnded,\n onError,\n onStageChanged,\n onTranscriptStarted,\n onRecordingStarted,\n onRecordingSaved,\n onEvent,\n });\n const wrapperStyle: CSSProperties = {\n width: toCssSize(width),\n height: toCssSize(height),\n ...style,\n };\n const frameStyle: CSSProperties = {\n width: '100%',\n height: '100%',\n border: 0,\n display: 'block',\n ...iframeStyle,\n };\n\n return (\n <div\n className={className}\n style={wrapperStyle}\n data-oe-meeting-instance={instanceId}\n >\n <iframe\n ref={iframeRef}\n className={iframeClassName}\n style={frameStyle}\n src={src}\n title={title}\n allow={allow}\n allowFullScreen\n referrerPolicy=\"strict-origin-when-cross-origin\"\n />\n </div>\n );\n },\n);\n\nOeMeeting.displayName = 'OeMeeting';\n","export const EMBED_PROTOCOL_VERSION = 1 as const;\nexport const EMBED_MESSAGE_SOURCE = 'oemeeting' as const;\nexport const DEFAULT_EMBED_ORIGIN = 'https://meet.ocean.edu.vn';\n\nexport enum EmbedEventType {\n READY = 'oemeeting:ready',\n JOINED = 'oemeeting:joined',\n LEFT = 'oemeeting:left',\n PARTICIPANT_JOINED = 'oemeeting:participant-joined',\n PARTICIPANT_LEFT = 'oemeeting:participant-left',\n ENDED = 'oemeeting:ended',\n ERROR = 'oemeeting:error',\n STAGE_CHANGED = 'oemeeting:stage-changed',\n TRANSCRIPT_STARTED = 'oemeeting:transcript-started',\n RECORDING_STARTED = 'oemeeting:recording-started',\n RECORDING_SAVED = 'oemeeting:recording-saved',\n}\n\nexport enum EmbedCommandType {\n COMMAND = 'oemeeting:command',\n}\n\nexport enum EmbedCommandAction {\n LEAVE = 'leave',\n TOGGLE_MIC = 'toggleMic',\n TOGGLE_CAMERA = 'toggleCamera',\n OPEN_STAGE = 'openStage',\n CLOSE_STAGE = 'closeStage',\n SET_BACKGROUND = 'setBackground',\n}\n\nexport enum EmbedErrorCode {\n TOKEN_INVALID = 'TOKEN_INVALID',\n TOKEN_EXPIRED = 'TOKEN_EXPIRED',\n ROOM_NOT_FOUND = 'ROOM_NOT_FOUND',\n ROOM_ACCESS_DENIED = 'ROOM_ACCESS_DENIED',\n CONNECTION_FAILED = 'CONNECTION_FAILED',\n PROTOCOL_MISMATCH = 'PROTOCOL_MISMATCH',\n STAGE_ORIGIN_DENIED = 'STAGE_ORIGIN_DENIED',\n STAGE_URL_INVALID = 'STAGE_URL_INVALID',\n TRANSCRIPT_START_FAILED = 'TRANSCRIPT_START_FAILED',\n RECORDING_START_FAILED = 'RECORDING_START_FAILED',\n UNKNOWN = 'UNKNOWN',\n}\n\n/**\n * Host-controlled camera background applied to this embed's local user only.\n * `none` clears any active background effect. The host app enforces who may\n * set it; the meeting does not interpret roles for this value.\n */\nexport enum EmbedBackgroundType {\n BLUR = 'blur',\n IMAGE = 'image',\n NONE = 'none',\n}\n\nexport const MAX_BACKGROUND_URL_LENGTH = 2048;\nexport const DEFAULT_BACKGROUND_BLUR_RADIUS = 10;\nexport const MIN_BACKGROUND_BLUR_RADIUS = 1;\nexport const MAX_BACKGROUND_BLUR_RADIUS = 50;\n\nexport interface EmbedBackground {\n type: EmbedBackgroundType;\n /** BLUR only. Defaults to `DEFAULT_BACKGROUND_BLUR_RADIUS`, clamped 1-50. */\n blurRadius?: number;\n /** IMAGE only. http(s) URL, max `MAX_BACKGROUND_URL_LENGTH`. */\n url?: string;\n /** When true the embed user cannot change blur/virtual backgrounds. */\n locked?: boolean;\n}\n\n/** Open host-owned tool id. Meeting does not catalog values. */\nexport type StageToolId = string;\n\nexport const MAX_STAGE_URL_LENGTH = 2048;\nexport const MAX_STAGE_TOOL_ID_LENGTH = 32;\n\nconst STAGE_TOOL_ID_PATTERN = /^[a-z][a-z0-9._-]{0,31}$/;\n\nexport function isStageToolId(value: unknown): value is StageToolId {\n return (\n typeof value === 'string' &&\n value.length > 0 &&\n value.length <= MAX_STAGE_TOOL_ID_LENGTH &&\n STAGE_TOOL_ID_PATTERN.test(value)\n );\n}\n\nexport enum EmbedQueryParam {\n INSTANCE_ID = 'instanceId',\n HOST_ORIGIN = 'hostOrigin',\n JOIN_TOKEN = 'joinToken',\n USER_NAME = 'userName',\n EXTERNAL_USER_ID = 'externalUserId',\n LANG = 'lang',\n AUTO_JOIN = 'autoJoin',\n HIDE_LEAVE = 'hideLeave',\n AUTO_CAMERA_OFF = 'autoCameraOff',\n AUTO_TRANSCRIBE = 'autoTranscribe',\n AUTO_RECORD = 'autoRecord',\n BACKGROUND = 'background',\n}\n\n/**\n * What happened to the auto-transcribe request the embed made.\n *\n * `ALREADY_RUNNING` is the normal outcome for every participant after the\n * first, because the transcript is a room-level asset rather than a\n * per-participant one. `DISABLED` means the OE Meeting deployment has\n * transcription turned off.\n */\nexport enum EmbedCaptureOutcome {\n STARTED = 'started',\n ALREADY_RUNNING = 'already_running',\n DISABLED = 'disabled',\n FAILED = 'failed',\n}\n\n/**\n * Role the host app assigns when minting a join token.\n * Keep values aligned with `EmbedRole` in the backend embed-permission enum.\n */\nexport enum EmbedRole {\n OWNER = 'owner',\n MEMBER = 'member',\n}\n\n/**\n * Capability the host app may grant per role. Meeting enforces this on the\n * join token, not on a React prop. `screenShare` is the only value today.\n */\nexport enum EmbedCapability {\n SCREEN_SHARE = 'screenShare',\n}\n\nexport interface EmbedPermissionMap {\n owner?: EmbedCapability[];\n member?: EmbedCapability[];\n}\n","import { isStageToolId, MAX_STAGE_URL_LENGTH } from './embed-protocol';\nimport type { OpenStageInput } from './types';\n\nexport function isValidStageUrl(url: string): boolean {\n if (url.length === 0 || url.length > MAX_STAGE_URL_LENGTH) return false;\n try {\n const parsed = new URL(url);\n if (parsed.protocol === 'https:') return true;\n if (parsed.protocol === 'http:') {\n return (\n parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1'\n );\n }\n return false;\n } catch {\n return false;\n }\n}\n\nexport function normalizeOpenStageInput(\n input: OpenStageInput,\n): OpenStageInput | null {\n const url = input.url.trim();\n if (!isValidStageUrl(url)) return null;\n const title = input.title?.trim();\n return {\n url,\n ...(title ? { title: title.slice(0, 200) } : {}),\n ...(isStageToolId(input.tool) ? { tool: input.tool } : {}),\n };\n}\n","import { isValidStageUrl } from './openStage';\nimport {\n DEFAULT_BACKGROUND_BLUR_RADIUS,\n EmbedBackgroundType,\n MAX_BACKGROUND_BLUR_RADIUS,\n MAX_BACKGROUND_URL_LENGTH,\n MIN_BACKGROUND_BLUR_RADIUS,\n type EmbedBackground,\n} from './embed-protocol';\n\n/** Same http(s) policy as the stage URL, including the localhost exception. */\nconst isValidBackgroundUrl = (url: string): boolean =>\n url.length > 0 &&\n url.length <= MAX_BACKGROUND_URL_LENGTH &&\n isValidStageUrl(url);\n\nconst clampBlurRadius = (value: number): number =>\n Number.isFinite(value)\n ? Math.min(MAX_BACKGROUND_BLUR_RADIUS, Math.max(MIN_BACKGROUND_BLUR_RADIUS, Math.round(value)))\n : DEFAULT_BACKGROUND_BLUR_RADIUS;\n\n/**\n * Validate and normalize a host-provided background. Returns null for invalid\n * input so callers can silently drop it, mirroring `normalizeOpenStageInput`.\n */\nexport function normalizeEmbedBackground(\n input: EmbedBackground,\n): EmbedBackground | null {\n const locked = input.locked === true ? { locked: true } : {};\n\n if (input.type === EmbedBackgroundType.NONE) {\n return { type: EmbedBackgroundType.NONE, ...locked };\n }\n\n if (input.type === EmbedBackgroundType.BLUR) {\n return {\n type: EmbedBackgroundType.BLUR,\n blurRadius: clampBlurRadius(input.blurRadius ?? DEFAULT_BACKGROUND_BLUR_RADIUS),\n ...locked,\n };\n }\n\n if (input.type === EmbedBackgroundType.IMAGE) {\n const url = input.url?.trim();\n if (!url || !isValidBackgroundUrl(url)) return null;\n return {\n type: EmbedBackgroundType.IMAGE,\n url,\n ...locked,\n };\n }\n\n return null;\n}\n\n/** JSON wire format shared by the `background` query param and the command payload. */\nexport function serializeEmbedBackground(\n input: EmbedBackground,\n): string | undefined {\n const normalized = normalizeEmbedBackground(input);\n return normalized ? JSON.stringify(normalized) : undefined;\n}\n","import {\n DEFAULT_EMBED_ORIGIN,\n EmbedQueryParam,\n} from './embed-protocol';\nimport { serializeEmbedBackground } from './background';\nimport type { BuildEmbedUrlOptions } from './types';\n\nconst QUERY_FLAG_ENABLED = '1';\nconst QUERY_FLAG_DISABLED = '0';\n\nfunction toFlag(value: boolean | undefined): string | undefined {\n if (value === undefined) return undefined;\n return value ? QUERY_FLAG_ENABLED : QUERY_FLAG_DISABLED;\n}\n\nfunction normalizeOrigin(origin: string): string {\n return origin.replace(/\\/+$/, '');\n}\n\nexport function buildEmbedUrl(options: BuildEmbedUrlOptions): string {\n const roomSlug = options.roomSlug.trim();\n const joinToken = options.joinToken.trim();\n if (!roomSlug) {\n throw new Error('OE Meeting embed requires a non-empty roomSlug.');\n }\n if (!joinToken) {\n throw new Error('OE Meeting embed requires a non-empty joinToken.');\n }\n\n const origin = normalizeOrigin(options.origin ?? DEFAULT_EMBED_ORIGIN);\n const url = new URL(`${origin}/embed/${encodeURIComponent(roomSlug)}`);\n const backgroundParam = options.background\n ? serializeEmbedBackground(options.background)\n : undefined;\n\n const entries: Array<[EmbedQueryParam, string | undefined]> = [\n [EmbedQueryParam.INSTANCE_ID, options.instanceId],\n [EmbedQueryParam.HOST_ORIGIN, options.hostOrigin],\n [EmbedQueryParam.JOIN_TOKEN, joinToken],\n [EmbedQueryParam.USER_NAME, options.userName],\n [EmbedQueryParam.EXTERNAL_USER_ID, options.externalUserId],\n [EmbedQueryParam.LANG, options.lang],\n [EmbedQueryParam.AUTO_JOIN, toFlag(options.autoJoin)],\n [EmbedQueryParam.HIDE_LEAVE, toFlag(options.hideLeave)],\n [EmbedQueryParam.AUTO_CAMERA_OFF, toFlag(options.autoCameraOff)],\n [EmbedQueryParam.AUTO_TRANSCRIBE, toFlag(options.autoTranscribe)],\n [EmbedQueryParam.AUTO_RECORD, toFlag(options.autoRecord)],\n [EmbedQueryParam.BACKGROUND, backgroundParam],\n ];\n\n for (const [key, value] of entries) {\n if (value !== undefined && value !== '') {\n url.searchParams.set(key, value);\n }\n }\n return url.toString();\n}\n","import {\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n type Ref,\n} from 'react';\nimport {\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n EmbedCommandAction,\n EmbedCommandType,\n EmbedEventType,\n} from './embed-protocol';\nimport { normalizeEmbedBackground } from './background';\nimport { normalizeOpenStageInput } from './openStage';\nimport { parseEmbedEvent } from './parseEmbedEvent';\nimport type {\n EmbedCommandMessage,\n OeMeetingHandle,\n OeMeetingProps,\n OpenStageInput,\n} from './types';\nimport type { EmbedBackground } from './embed-protocol';\n\ntype UseOeMeetingOptions = Pick<\n OeMeetingProps,\n | 'onReady'\n | 'onJoined'\n | 'onLeft'\n | 'onParticipantJoined'\n | 'onParticipantLeft'\n | 'onEnded'\n | 'onError'\n | 'onStageChanged'\n | 'onTranscriptStarted'\n | 'onRecordingStarted'\n | 'onRecordingSaved'\n | 'onEvent'\n | 'background'\n> & {\n instanceId: string;\n targetOrigin: string;\n handleRef?: Ref<OeMeetingHandle>;\n};\n\ninterface PostCommandOptions {\n iframe: HTMLIFrameElement | null;\n instanceId: string;\n action: EmbedCommandAction;\n targetOrigin: string;\n payload?: OpenStageInput | EmbedBackground;\n}\n\nfunction postCommand(options: PostCommandOptions): void {\n const frameWindow = options.iframe?.contentWindow;\n if (!frameWindow) return;\n const message: EmbedCommandMessage = {\n source: EMBED_MESSAGE_SOURCE,\n version: EMBED_PROTOCOL_VERSION,\n instanceId: options.instanceId,\n type: EmbedCommandType.COMMAND,\n action: options.action,\n ...(options.payload ? { payload: options.payload } : {}),\n };\n frameWindow.postMessage(message, options.targetOrigin);\n}\n\nexport function useOeMeeting(options: UseOeMeetingOptions) {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const callbacksRef = useRef(options);\n // Last background the host set. Resent on READY: commands posted before the\n // iframe bridge mounts are dropped by the browser.\n const pendingBackgroundRef = useRef<EmbedBackground | null>(null);\n\n useEffect(() => {\n callbacksRef.current = options;\n }, [options]);\n\n useEffect(() => {\n const onMessage = (event: MessageEvent<unknown>) => {\n const frameWindow = iframeRef.current?.contentWindow;\n if (\n !frameWindow ||\n event.source !== frameWindow ||\n event.origin !== options.targetOrigin\n ) {\n return;\n }\n const embedEvent = parseEmbedEvent(event.data);\n if (!embedEvent || embedEvent.instanceId !== options.instanceId) return;\n\n const callbacks = callbacksRef.current;\n callbacks.onEvent?.(embedEvent);\n switch (embedEvent.type) {\n case EmbedEventType.READY:\n callbacks.onReady?.(embedEvent);\n if (pendingBackgroundRef.current) {\n postCommand({\n iframe: iframeRef.current,\n instanceId: options.instanceId,\n action: EmbedCommandAction.SET_BACKGROUND,\n targetOrigin: options.targetOrigin,\n payload: pendingBackgroundRef.current,\n });\n }\n break;\n case EmbedEventType.JOINED:\n callbacks.onJoined?.(embedEvent);\n break;\n case EmbedEventType.LEFT:\n callbacks.onLeft?.(embedEvent);\n break;\n case EmbedEventType.PARTICIPANT_JOINED:\n callbacks.onParticipantJoined?.(embedEvent);\n break;\n case EmbedEventType.PARTICIPANT_LEFT:\n callbacks.onParticipantLeft?.(embedEvent);\n break;\n case EmbedEventType.ENDED:\n callbacks.onEnded?.(embedEvent);\n break;\n case EmbedEventType.ERROR:\n callbacks.onError?.(embedEvent);\n break;\n case EmbedEventType.STAGE_CHANGED:\n callbacks.onStageChanged?.(embedEvent);\n break;\n case EmbedEventType.TRANSCRIPT_STARTED:\n callbacks.onTranscriptStarted?.(embedEvent);\n break;\n case EmbedEventType.RECORDING_STARTED:\n callbacks.onRecordingStarted?.(embedEvent);\n break;\n case EmbedEventType.RECORDING_SAVED:\n callbacks.onRecordingSaved?.(embedEvent);\n break;\n }\n };\n\n window.addEventListener('message', onMessage);\n return () => window.removeEventListener('message', onMessage);\n }, [options.instanceId, options.targetOrigin]);\n\n const send = useCallback(\n (\n action: EmbedCommandAction,\n payload?: OpenStageInput | EmbedBackground\n ) =>\n postCommand({\n iframe: iframeRef.current,\n instanceId: options.instanceId,\n action,\n targetOrigin: options.targetOrigin,\n payload,\n }),\n [options.instanceId, options.targetOrigin],\n );\n const leave = useCallback(() => send(EmbedCommandAction.LEAVE), [send]);\n const toggleMic = useCallback(\n () => send(EmbedCommandAction.TOGGLE_MIC),\n [send],\n );\n const toggleCamera = useCallback(\n () => send(EmbedCommandAction.TOGGLE_CAMERA),\n [send],\n );\n const openStage = useCallback(\n (input: OpenStageInput) => {\n const normalized = normalizeOpenStageInput(input);\n if (!normalized) return;\n send(EmbedCommandAction.OPEN_STAGE, normalized);\n },\n [send],\n );\n const closeStage = useCallback(\n () => send(EmbedCommandAction.CLOSE_STAGE),\n [send],\n );\n\n useEffect(() => {\n if (!options.background) {\n pendingBackgroundRef.current = null;\n return;\n }\n const normalized = normalizeEmbedBackground(options.background);\n if (!normalized) return;\n pendingBackgroundRef.current = normalized;\n send(EmbedCommandAction.SET_BACKGROUND, normalized);\n }, [options.background, send]);\n const getIframe = useCallback(() => iframeRef.current, []);\n const handle = useMemo<OeMeetingHandle>(\n () => ({\n leave,\n toggleMic,\n toggleCamera,\n openStage,\n closeStage,\n getIframe,\n }),\n [closeStage, getIframe, leave, openStage, toggleCamera, toggleMic],\n );\n\n useImperativeHandle(options.handleRef, () => handle, [handle]);\n return { iframeRef, handle };\n}\n","import {\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n EmbedCaptureOutcome,\n EmbedEventType,\n} from './embed-protocol';\nimport type { EmbedEvent } from './types';\n\nconst EVENT_TYPES = new Set<string>(Object.values(EmbedEventType));\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction isOptionalString(value: unknown): boolean {\n return value === undefined || typeof value === 'string';\n}\n\nfunction isNullableString(value: unknown): boolean {\n return value === null || typeof value === 'string';\n}\n\nconst CAPTURE_OUTCOMES = new Set<string>(Object.values(EmbedCaptureOutcome));\n\nfunction isCaptureOutcome(value: unknown): boolean {\n return typeof value === 'string' && CAPTURE_OUTCOMES.has(value);\n}\n\nfunction hasValidPayload(\n type: EmbedEventType,\n payload: Record<string, unknown>,\n): boolean {\n switch (type) {\n case EmbedEventType.READY:\n return typeof payload.roomSlug === 'string';\n case EmbedEventType.JOINED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.identity === 'string' &&\n isOptionalString(payload.name) &&\n typeof payload.joinedAt === 'string'\n );\n case EmbedEventType.LEFT:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.identity === 'string' &&\n typeof payload.leftAt === 'string' &&\n (payload.durationMs === undefined ||\n typeof payload.durationMs === 'number')\n );\n case EmbedEventType.PARTICIPANT_JOINED:\n return (\n typeof payload.identity === 'string' &&\n isOptionalString(payload.name) &&\n typeof payload.participantCount === 'number'\n );\n case EmbedEventType.PARTICIPANT_LEFT:\n return (\n typeof payload.identity === 'string' &&\n typeof payload.participantCount === 'number'\n );\n case EmbedEventType.ENDED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.endedAt === 'string'\n );\n case EmbedEventType.ERROR:\n return (\n typeof payload.code === 'string' &&\n typeof payload.message === 'string'\n );\n case EmbedEventType.STAGE_CHANGED:\n return (\n (payload.url === null || typeof payload.url === 'string') &&\n isOptionalString(payload.title) &&\n isOptionalString(payload.tool) &&\n isOptionalString(payload.openedBy)\n );\n case EmbedEventType.TRANSCRIPT_STARTED:\n return (\n typeof payload.roomSlug === 'string' &&\n isCaptureOutcome(payload.outcome) &&\n isNullableString(payload.transcriptId) &&\n isNullableString(payload.status)\n );\n case EmbedEventType.RECORDING_STARTED:\n return (\n typeof payload.roomSlug === 'string' &&\n isCaptureOutcome(payload.outcome) &&\n isNullableString(payload.recordingId) &&\n isNullableString(payload.status)\n );\n case EmbedEventType.RECORDING_SAVED:\n return (\n typeof payload.roomSlug === 'string' &&\n typeof payload.savedAt === 'string'\n );\n }\n}\n\nexport function parseEmbedEvent(data: unknown): EmbedEvent | null {\n if (!isRecord(data)) return null;\n if (\n data.source !== EMBED_MESSAGE_SOURCE ||\n data.version !== EMBED_PROTOCOL_VERSION ||\n typeof data.instanceId !== 'string' ||\n data.instanceId.length === 0 ||\n typeof data.type !== 'string' ||\n !EVENT_TYPES.has(data.type) ||\n !isRecord(data.payload)\n ) {\n return null;\n }\n\n const type = data.type as EmbedEventType;\n if (!hasValidPayload(type, data.payload)) return null;\n return data as unknown as EmbedEvent;\n}\n"],"mappings":"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","names":["useMemo","useRef","EmbedEventType","EmbedCommandType","EmbedCommandAction","EmbedErrorCode","EmbedBackgroundType","EmbedQueryParam","EmbedCaptureOutcome","EmbedRole","EmbedCapability","OeMeeting","useMemo","useRef"]}
@@ -53,7 +53,9 @@ Request:
53
53
  "name": "Lesson 98765 - 2026-08-17",
54
54
  "slug": "oe-school-lesson-98765",
55
55
  "access_level": "restricted",
56
- "configuration": {}
56
+ "configuration": {
57
+ "speakerDiarization": true
58
+ }
57
59
  }
58
60
  ```
59
61
 
@@ -62,8 +64,7 @@ Request:
62
64
  | `name` | Có | Tên hiển thị, tối đa 500 ký tự. |
63
65
  | `slug` | Không | Khóa ổn định của phòng, tối đa 100 ký tự. Nếu bỏ trống, server sinh từ `name`. |
64
66
  | `access_level` | Không | Nên luôn dùng `restricted`; external API mặc định là `restricted`. |
65
- | `configuration` | Không | Cấu hình mở rộng của phòng. |
66
-
67
+ | `configuration` | Không | Cấu hình mở rộng. `speakerDiarization: true` bảo agent tách người nói. Mặc định tắt. Gửi trên lần tạo phòng, hoặc lần tạo lại cùng slug. |
67
68
  Response mẫu:
68
69
 
69
70
  ```json
@@ -211,7 +212,7 @@ Response mẫu:
211
212
  | Field | Mô tả |
212
213
  |---|---|
213
214
  | `status` | `active` khi buổi học đang diễn ra, `paused` khi tạm dừng, `completed` sau khi phòng kết thúc. |
214
- | `segments` | Các câu nói đã chốt, sắp xếp tăng dần theo `spokenAt`. |
215
+ | `segments` | Các câu nói đã chốt, sắp xếp tăng dần theo `spokenAt`. Client chưa bật `speakerDiarization` nhận đúng shape cũ, không có `speakerId`. |
215
216
  | `startedBy` | `null` với phiên do embed tự bật (người tham gia là khách của OE.School, không có tài khoản OE Meeting). |
216
217
 
217
218
  Trả `404` khi phòng chưa từng có transcript nào. Transcript là tài sản cấp
@@ -219,6 +220,38 @@ phòng: nếu OE.School dùng lại một room cho nhiều buổi, endpoint tr
219
220
  nhất và các segment của phiên đó. Dùng một room cho mỗi buổi học (theo quy ước
220
221
  slug ở 2.1) để tránh nhập nhằng.
221
222
 
223
+ Với phòng đã tạo với `speakerDiarization: true`, mỗi segment có thêm field
224
+ `speakerId` — nhãn giọng nói Soniox gán trong phạm vi một audio track (mic
225
+ chung, tab share). Hai giọng trên một mic sẽ ra `"1"`, `"2"` xen kẽ:
226
+
227
+ ```json
228
+ "segments": [
229
+ {
230
+ "id": "0b0f9d54-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
231
+ "participantIdentity": "oe-school:user:12345",
232
+ "participantName": "Nguyen Van A",
233
+ "speakerId": "1",
234
+ "text": "Chào cả lớp, hôm nay chúng ta học bài 3.",
235
+ "language": "vi",
236
+ "spokenAt": "2026-08-17T02:00:45.000Z"
237
+ },
238
+ {
239
+ "id": "1c2d3e4f-...",
240
+ "participantIdentity": "oe-school:user:12345",
241
+ "participantName": "Nguyen Van A",
242
+ "speakerId": "2",
243
+ "text": "Khoan, cho em hỏi bài 3 ạ.",
244
+ "language": "vi",
245
+ "spokenAt": "2026-08-17T02:00:52.000Z"
246
+ }
247
+ ]
248
+ ```
249
+
250
+ `speakerId` là nhãn giọng, không phải tên người: mọi segment vẫn mang
251
+ `participantName` của chủ track. Muốn tên thật cho từng giọng phải tự map
252
+ ngoài hệ thống. Nhãn chỉ phân biệt trong phạm vi một track, không phải toàn
253
+ phòng, và không có trên live caption trong lúc họp.
254
+
222
255
  Gọi endpoint này sau event `ENDED` (`onEnded` trên React SDK). Agent transcriber
223
256
  chốt phiên (`status: "completed"`) khi phòng LiveKit đóng, nên gọi quá sớm sẽ
224
257
  nhận được transcript còn `active` và thiếu các câu cuối. Poll tới khi
@@ -271,6 +304,7 @@ interface TranscriptSegment {
271
304
  id: string;
272
305
  participantIdentity: string | null;
273
306
  participantName: string;
307
+ speakerId?: string;
274
308
  text: string;
275
309
  language: string | null;
276
310
  spokenAt: string;
@@ -125,10 +125,12 @@ này ở production. Khi chạy local, truyền URL frontend OE Meeting local, v
125
125
  | `roomSlug` | `string` | Bắt buộc | Slug phòng do backend trả về. |
126
126
  | `joinToken` | `string` | Bắt buộc | Token cấp riêng cho user hiện tại. |
127
127
  | `origin` | `string` | `https://meet.ocean.edu.vn` | Origin của OE Meeting. |
128
- | `autoJoin` | `boolean` | `true` | Phiên bản hiện tại chỉ hỗ trợ `true`. |
128
+ | `autoJoin` | `boolean` | `true` | `false` hiển thị màn preview (prejoin) trong iframe: kiểm tra camera/mic, chọn thiết bị rồi bấm vào mới vào phòng. Khi bật prejoin, `onReady`/`onJoined` chỉ kích hoạt sau khi người dùng vào phòng. |
129
129
  | `hideLeave` | `boolean` | `false` | Ẩn nút rời phòng trong iframe. |
130
+ | `autoCameraOff` | `boolean` | `false` | Vào phòng với camera tắt: màn prejoin khởi tạo camera đang tắt và phòng không phát video lúc kết nối. Người dùng vẫn có thể bật lại thủ công. |
130
131
  | `autoTranscribe` | `boolean` | `false` | Tự bật transcript dùng chung khi embed kết nối. |
131
132
  | `autoRecord` | `boolean` | `false` | Tự ghi toàn bộ phòng khi embed kết nối. |
133
+ | `background` | `EmbedBackground` | — | Áp background camera (blur / ảnh / none) cho user của iframe này, kèm tuỳ chọn khóa lựa chọn nền của người dùng. Dùng để branding sự kiện: host app truyền cùng một giá trị cho mọi người tham gia. Giá trị ban đầu đi theo embed URL; đổi prop sau mount được gửi vào iframe dưới dạng command `setBackground` (buffer đến khi embed ready). Input không hợp lệ bị bỏ qua. |
132
134
  | `userName` | `string` | — | Thông tin tùy chọn truyền vào embed URL. |
133
135
  | `externalUserId` | `string` | — | External identity tùy chọn. |
134
136
  | `lang` | `string` | — | Ngôn ngữ giao diện. |
@@ -156,6 +158,45 @@ này ở production. Khi chạy local, truyền URL frontend OE Meeting local, v
156
158
  Không đặt `targetOrigin="*"`. SDK và iframe đều kiểm tra origin, source và
157
159
  `instanceId` trước khi xử lý message.
158
160
 
161
+ ### 3.0.1. Background chung cho sự kiện (`background`)
162
+
163
+ ```tsx
164
+ import { EmbedBackgroundType } from '@tinyweb_dev/oe-meeting-react'
165
+
166
+ // Ảnh branding cho mọi người tham gia, người dùng không đổi được
167
+ <OeMeeting
168
+ roomSlug="event-room"
169
+ joinToken={token}
170
+ background={{
171
+ type: EmbedBackgroundType.IMAGE,
172
+ url: 'https://cdn.example.com/event-brand.jpg',
173
+ locked: true,
174
+ }}
175
+ />
176
+
177
+ // Blur nhẹ, người dùng vẫn tự đổi được
178
+ background={{ type: EmbedBackgroundType.BLUR, blurRadius: 5 }}
179
+
180
+ // Bỏ background đang áp (mở khóa nếu đang khóa)
181
+ background={{ type: EmbedBackgroundType.NONE }}
182
+ ```
183
+
184
+ Quy tắc:
185
+
186
+ - `type: 'blur'` | `'image'` | `'none'`. `blurRadius` mặc định 10, clamp 1–50;
187
+ `url` phải là http(s) (http chỉ cho localhost) và ≤ 2048 ký tự.
188
+ - **Ảnh cross-origin tự đi qua proxy của meeting backend** (`GET /api/v1.0/embed/background-image`): host app truyền URL ảnh công khai bất kỳ, **không cần CORS/ACAO** trên host ảnh. Proxy bảo vệ cho production: join token, DNS pin chặn rebinding, redirect validate lại từng hop (tối đa 3), chặn IP private/loopback/link-local (IPv4+IPv6), chỉ `image/*` ≤ 10MB, rate limit theo identity (mặc định 30/phút), cache 10 phút (budget 32MB). Ops: `EMBED_BACKGROUND_PROXY_ENABLED` (kill switch), `EMBED_BACKGROUND_PROXY_ALLOWED_HOSTS` (allowlist hostname, tùy chọn), `EMBED_BACKGROUND_PROXY_RATE_LIMIT`. URL same-origin với meeting tải trực tiếp. URL không tải được bị bỏ qua an toàn (nền không áp, khóa mở, camera vẫn chạy).
189
+ - `locked: true` vô hiệu hóa chọn blur/nền trong panel Effects của người dùng
190
+ (cả prejoin lẫn trong phòng); `false`/không truyền thì chỉ là mặc định ban đầu.
191
+ - Meeting không kiểm tra role: chỉ tác động lên iframe của chính người đó,
192
+ nên host app tự enforce "ai được set" (vd chỉ dashboard của owner).
193
+ - Đồng bộ tới mọi người tham gia là việc host app: hãy render cùng một giá trị
194
+ `background` trên mọi phiên embed và đẩy update qua kênh realtime của bạn
195
+ khi owner đổi giữa buổi. Người vào phòng trực tiếp (không qua embed) không
196
+ nhận được background này.
197
+ - Command `setBackground` gửi trong lúc prejoin (`autoJoin={false}`, chưa bấm
198
+ Join) bị mất — giá trị ban đầu qua URL vẫn đúng.
199
+
159
200
  ## 3.1. Phân quyền theo role
160
201
 
161
202
  `<OeMeeting>` không có prop quyền. Quyền nằm trong `joinToken`.
@@ -443,6 +484,5 @@ về đúng origin đó.
443
484
  quyền trước mỗi lần cấp token.
444
485
  - Chưa có API thu hồi token đã cấp hoặc kick participant theo lesson attendance.
445
486
  - Protocol v1 chưa có token handshake qua `postMessage`.
446
- - `autoJoin={false}` chưa có prejoin flow; phiên bản hiện tại yêu cầu auto join.
447
487
  - Các lỗi token/access chưa được phân loại riêng; frontend có thể nhận
448
488
  `CONNECTION_FAILED` cho các lỗi kết nối liên quan token.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinyweb_dev/oe-meeting-react",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "React SDK to embed OE Meeting rooms via iframe and postMessage",
5
5
  "publishConfig": {
6
6
  "access": "public"