@tinyweb_dev/oe-meeting-react 0.2.0 → 0.3.1
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/README.md +25 -0
- package/dist/index.cjs +53 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -4
- package/dist/index.d.ts +78 -4
- package/dist/index.js +52 -1
- package/dist/index.js.map +1 -1
- package/docs/BACKEND-INTEGRATION.md +94 -1
- package/docs/REACT-INTEGRATION.md +112 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,13 +36,38 @@ export function OnlineLesson({
|
|
|
36
36
|
joinToken={joinToken}
|
|
37
37
|
height="100vh"
|
|
38
38
|
hideLeave
|
|
39
|
+
autoTranscribe
|
|
40
|
+
autoRecord
|
|
39
41
|
onJoined={(event) => console.info('joined', event.payload)}
|
|
40
42
|
onLeft={(event) => console.info('left', event.payload)}
|
|
43
|
+
onEnded={(event) => {
|
|
44
|
+
// Backend OE.School lấy transcript + recordings bằng roomSlug.
|
|
45
|
+
console.info('ended', event.payload.roomSlug)
|
|
46
|
+
}}
|
|
41
47
|
/>
|
|
42
48
|
);
|
|
43
49
|
}
|
|
44
50
|
```
|
|
45
51
|
|
|
52
|
+
## Transcript
|
|
53
|
+
|
|
54
|
+
`autoTranscribe` bật transcript ngay khi embed kết nối. Transcript là tài sản
|
|
55
|
+
cấp phòng: người đầu tiên vào bật lên, mọi người sau nhận
|
|
56
|
+
`outcome: "already_running"`, và nó vẫn chạy khi người bật rời sớm. Kết quả đến
|
|
57
|
+
qua `onTranscriptStarted`.
|
|
58
|
+
|
|
59
|
+
Sau buổi học, backend OE.School lấy transcript bằng
|
|
60
|
+
`GET /external-api/v1.0/rooms/:idOrSlug/transcript` (scope `transcripts:retrieve`).
|
|
61
|
+
Thời điểm gọi là event `ENDED` (`onEnded`), không phải `LEFT`.
|
|
62
|
+
|
|
63
|
+
## Recording
|
|
64
|
+
|
|
65
|
+
`autoRecord` tự ghi toàn bộ phòng khi embed kết nối và báo kết quả qua
|
|
66
|
+
`onRecordingStarted`. Backend tích hợp lấy danh sách cùng URL tải tạm thời bằng
|
|
67
|
+
`GET /external-api/v1.0/rooms/:idOrSlug/recordings` (scope
|
|
68
|
+
`recordings:retrieve`). Thời hạn tải do `RECORDING_EXPIRATION_DAYS` cấu hình;
|
|
69
|
+
bucket S3/MinIO phải có lifecycle rule cùng số ngày để xóa file thật.
|
|
70
|
+
|
|
46
71
|
## Bảo mật
|
|
47
72
|
|
|
48
73
|
- `joinToken` phải được backend OE.School lấy server-to-server từ external API.
|
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
|
+
EmbedCaptureOutcome: () => EmbedCaptureOutcome,
|
|
27
28
|
EmbedCommandAction: () => EmbedCommandAction,
|
|
28
29
|
EmbedCommandType: () => EmbedCommandType,
|
|
29
30
|
EmbedErrorCode: () => EmbedErrorCode,
|
|
@@ -57,6 +58,9 @@ var EmbedEventType = /* @__PURE__ */ ((EmbedEventType2) => {
|
|
|
57
58
|
EmbedEventType2["ENDED"] = "oemeeting:ended";
|
|
58
59
|
EmbedEventType2["ERROR"] = "oemeeting:error";
|
|
59
60
|
EmbedEventType2["STAGE_CHANGED"] = "oemeeting:stage-changed";
|
|
61
|
+
EmbedEventType2["TRANSCRIPT_STARTED"] = "oemeeting:transcript-started";
|
|
62
|
+
EmbedEventType2["RECORDING_STARTED"] = "oemeeting:recording-started";
|
|
63
|
+
EmbedEventType2["RECORDING_SAVED"] = "oemeeting:recording-saved";
|
|
60
64
|
return EmbedEventType2;
|
|
61
65
|
})(EmbedEventType || {});
|
|
62
66
|
var EmbedCommandType = /* @__PURE__ */ ((EmbedCommandType2) => {
|
|
@@ -80,6 +84,8 @@ var EmbedErrorCode = /* @__PURE__ */ ((EmbedErrorCode2) => {
|
|
|
80
84
|
EmbedErrorCode2["PROTOCOL_MISMATCH"] = "PROTOCOL_MISMATCH";
|
|
81
85
|
EmbedErrorCode2["STAGE_ORIGIN_DENIED"] = "STAGE_ORIGIN_DENIED";
|
|
82
86
|
EmbedErrorCode2["STAGE_URL_INVALID"] = "STAGE_URL_INVALID";
|
|
87
|
+
EmbedErrorCode2["TRANSCRIPT_START_FAILED"] = "TRANSCRIPT_START_FAILED";
|
|
88
|
+
EmbedErrorCode2["RECORDING_START_FAILED"] = "RECORDING_START_FAILED";
|
|
83
89
|
EmbedErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
84
90
|
return EmbedErrorCode2;
|
|
85
91
|
})(EmbedErrorCode || {});
|
|
@@ -98,8 +104,17 @@ var EmbedQueryParam = /* @__PURE__ */ ((EmbedQueryParam2) => {
|
|
|
98
104
|
EmbedQueryParam2["LANG"] = "lang";
|
|
99
105
|
EmbedQueryParam2["AUTO_JOIN"] = "autoJoin";
|
|
100
106
|
EmbedQueryParam2["HIDE_LEAVE"] = "hideLeave";
|
|
107
|
+
EmbedQueryParam2["AUTO_TRANSCRIBE"] = "autoTranscribe";
|
|
108
|
+
EmbedQueryParam2["AUTO_RECORD"] = "autoRecord";
|
|
101
109
|
return EmbedQueryParam2;
|
|
102
110
|
})(EmbedQueryParam || {});
|
|
111
|
+
var EmbedCaptureOutcome = /* @__PURE__ */ ((EmbedCaptureOutcome2) => {
|
|
112
|
+
EmbedCaptureOutcome2["STARTED"] = "started";
|
|
113
|
+
EmbedCaptureOutcome2["ALREADY_RUNNING"] = "already_running";
|
|
114
|
+
EmbedCaptureOutcome2["DISABLED"] = "disabled";
|
|
115
|
+
EmbedCaptureOutcome2["FAILED"] = "failed";
|
|
116
|
+
return EmbedCaptureOutcome2;
|
|
117
|
+
})(EmbedCaptureOutcome || {});
|
|
103
118
|
|
|
104
119
|
// src/buildEmbedUrl.ts
|
|
105
120
|
var QUERY_FLAG_ENABLED = "1";
|
|
@@ -130,7 +145,9 @@ function buildEmbedUrl(options) {
|
|
|
130
145
|
["externalUserId" /* EXTERNAL_USER_ID */, options.externalUserId],
|
|
131
146
|
["lang" /* LANG */, options.lang],
|
|
132
147
|
["autoJoin" /* AUTO_JOIN */, toFlag(options.autoJoin)],
|
|
133
|
-
["hideLeave" /* HIDE_LEAVE */, toFlag(options.hideLeave)]
|
|
148
|
+
["hideLeave" /* HIDE_LEAVE */, toFlag(options.hideLeave)],
|
|
149
|
+
["autoTranscribe" /* AUTO_TRANSCRIBE */, toFlag(options.autoTranscribe)],
|
|
150
|
+
["autoRecord" /* AUTO_RECORD */, toFlag(options.autoRecord)]
|
|
134
151
|
];
|
|
135
152
|
for (const [key, value] of entries) {
|
|
136
153
|
if (value !== void 0 && value !== "") {
|
|
@@ -176,6 +193,13 @@ function isRecord(value) {
|
|
|
176
193
|
function isOptionalString(value) {
|
|
177
194
|
return value === void 0 || typeof value === "string";
|
|
178
195
|
}
|
|
196
|
+
function isNullableString(value) {
|
|
197
|
+
return value === null || typeof value === "string";
|
|
198
|
+
}
|
|
199
|
+
var CAPTURE_OUTCOMES = new Set(Object.values(EmbedCaptureOutcome));
|
|
200
|
+
function isCaptureOutcome(value) {
|
|
201
|
+
return typeof value === "string" && CAPTURE_OUTCOMES.has(value);
|
|
202
|
+
}
|
|
179
203
|
function hasValidPayload(type, payload) {
|
|
180
204
|
switch (type) {
|
|
181
205
|
case "oemeeting:ready" /* READY */:
|
|
@@ -194,6 +218,12 @@ function hasValidPayload(type, payload) {
|
|
|
194
218
|
return typeof payload.code === "string" && typeof payload.message === "string";
|
|
195
219
|
case "oemeeting:stage-changed" /* STAGE_CHANGED */:
|
|
196
220
|
return (payload.url === null || typeof payload.url === "string") && isOptionalString(payload.title) && isOptionalString(payload.tool) && isOptionalString(payload.openedBy);
|
|
221
|
+
case "oemeeting:transcript-started" /* TRANSCRIPT_STARTED */:
|
|
222
|
+
return typeof payload.roomSlug === "string" && isCaptureOutcome(payload.outcome) && isNullableString(payload.transcriptId) && isNullableString(payload.status);
|
|
223
|
+
case "oemeeting:recording-started" /* RECORDING_STARTED */:
|
|
224
|
+
return typeof payload.roomSlug === "string" && isCaptureOutcome(payload.outcome) && isNullableString(payload.recordingId) && isNullableString(payload.status);
|
|
225
|
+
case "oemeeting:recording-saved" /* RECORDING_SAVED */:
|
|
226
|
+
return typeof payload.roomSlug === "string" && typeof payload.savedAt === "string";
|
|
197
227
|
}
|
|
198
228
|
}
|
|
199
229
|
function parseEmbedEvent(data) {
|
|
@@ -261,6 +291,15 @@ function useOeMeeting(options) {
|
|
|
261
291
|
case "oemeeting:stage-changed" /* STAGE_CHANGED */:
|
|
262
292
|
callbacks.onStageChanged?.(embedEvent);
|
|
263
293
|
break;
|
|
294
|
+
case "oemeeting:transcript-started" /* TRANSCRIPT_STARTED */:
|
|
295
|
+
callbacks.onTranscriptStarted?.(embedEvent);
|
|
296
|
+
break;
|
|
297
|
+
case "oemeeting:recording-started" /* RECORDING_STARTED */:
|
|
298
|
+
callbacks.onRecordingStarted?.(embedEvent);
|
|
299
|
+
break;
|
|
300
|
+
case "oemeeting:recording-saved" /* RECORDING_SAVED */:
|
|
301
|
+
callbacks.onRecordingSaved?.(embedEvent);
|
|
302
|
+
break;
|
|
264
303
|
}
|
|
265
304
|
};
|
|
266
305
|
window.addEventListener("message", onMessage);
|
|
@@ -334,6 +373,8 @@ var OeMeeting = (0, import_react2.forwardRef)(
|
|
|
334
373
|
lang,
|
|
335
374
|
autoJoin = true,
|
|
336
375
|
hideLeave,
|
|
376
|
+
autoTranscribe,
|
|
377
|
+
autoRecord,
|
|
337
378
|
title = "OE Meeting room",
|
|
338
379
|
width = "100%",
|
|
339
380
|
height = 640,
|
|
@@ -351,6 +392,9 @@ var OeMeeting = (0, import_react2.forwardRef)(
|
|
|
351
392
|
onEnded,
|
|
352
393
|
onError,
|
|
353
394
|
onStageChanged,
|
|
395
|
+
onTranscriptStarted,
|
|
396
|
+
onRecordingStarted,
|
|
397
|
+
onRecordingSaved,
|
|
354
398
|
onEvent
|
|
355
399
|
} = props;
|
|
356
400
|
const reactId = (0, import_react2.useId)();
|
|
@@ -371,11 +415,15 @@ var OeMeeting = (0, import_react2.forwardRef)(
|
|
|
371
415
|
lang,
|
|
372
416
|
autoJoin,
|
|
373
417
|
hideLeave,
|
|
418
|
+
autoTranscribe,
|
|
419
|
+
autoRecord,
|
|
374
420
|
instanceId,
|
|
375
421
|
hostOrigin
|
|
376
422
|
}),
|
|
377
423
|
[
|
|
378
424
|
autoJoin,
|
|
425
|
+
autoRecord,
|
|
426
|
+
autoTranscribe,
|
|
379
427
|
externalUserId,
|
|
380
428
|
hideLeave,
|
|
381
429
|
hostOrigin,
|
|
@@ -399,6 +447,9 @@ var OeMeeting = (0, import_react2.forwardRef)(
|
|
|
399
447
|
onEnded,
|
|
400
448
|
onError,
|
|
401
449
|
onStageChanged,
|
|
450
|
+
onTranscriptStarted,
|
|
451
|
+
onRecordingStarted,
|
|
452
|
+
onRecordingSaved,
|
|
402
453
|
onEvent
|
|
403
454
|
});
|
|
404
455
|
const wrapperStyle = {
|
|
@@ -443,6 +494,7 @@ OeMeeting.displayName = "OeMeeting";
|
|
|
443
494
|
DEFAULT_IFRAME_ALLOW,
|
|
444
495
|
EMBED_MESSAGE_SOURCE,
|
|
445
496
|
EMBED_PROTOCOL_VERSION,
|
|
497
|
+
EmbedCaptureOutcome,
|
|
446
498
|
EmbedCommandAction,
|
|
447
499
|
EmbedCommandType,
|
|
448
500
|
EmbedErrorCode,
|
package/dist/index.cjs.map
CHANGED
|
@@ -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 EmbedCommandAction,\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 { 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 EmbedStageChangedPayload,\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 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 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 instanceId,\n hostOrigin,\n }),\n [\n autoJoin,\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 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}\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 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}\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 ];\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 | '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 }\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 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 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 }\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","OeMeeting"]}
|
|
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 EmbedCaptureOutcome,\n EmbedCommandAction,\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 { 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","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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,IAAAA,gBAKO;;;ACPA,IAAM,yBAAyB;AAC/B,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAE7B,IAAK,iBAAL,kBAAKC,oBAAL;AACL,EAAAA,gBAAA,WAAQ;AACR,EAAAA,gBAAA,YAAS;AACT,EAAAA,gBAAA,UAAO;AACP,EAAAA,gBAAA,wBAAqB;AACrB,EAAAA,gBAAA,sBAAmB;AACnB,EAAAA,gBAAA,WAAQ;AACR,EAAAA,gBAAA,WAAQ;AACR,EAAAA,gBAAA,mBAAgB;AAChB,EAAAA,gBAAA,wBAAqB;AACrB,EAAAA,gBAAA,uBAAoB;AACpB,EAAAA,gBAAA,qBAAkB;AAXR,SAAAA;AAAA,GAAA;AAcL,IAAK,mBAAL,kBAAKC,sBAAL;AACL,EAAAA,kBAAA,aAAU;AADA,SAAAA;AAAA,GAAA;AAIL,IAAK,qBAAL,kBAAKC,wBAAL;AACL,EAAAA,oBAAA,WAAQ;AACR,EAAAA,oBAAA,gBAAa;AACb,EAAAA,oBAAA,mBAAgB;AAChB,EAAAA,oBAAA,gBAAa;AACb,EAAAA,oBAAA,iBAAc;AALJ,SAAAA;AAAA,GAAA;AAQL,IAAK,iBAAL,kBAAKC,oBAAL;AACL,EAAAA,gBAAA,mBAAgB;AAChB,EAAAA,gBAAA,mBAAgB;AAChB,EAAAA,gBAAA,oBAAiB;AACjB,EAAAA,gBAAA,wBAAqB;AACrB,EAAAA,gBAAA,uBAAoB;AACpB,EAAAA,gBAAA,uBAAoB;AACpB,EAAAA,gBAAA,yBAAsB;AACtB,EAAAA,gBAAA,uBAAoB;AACpB,EAAAA,gBAAA,6BAA0B;AAC1B,EAAAA,gBAAA,4BAAyB;AACzB,EAAAA,gBAAA,aAAU;AAXA,SAAAA;AAAA,GAAA;AAiBL,IAAM,uBAAuB;AAC7B,IAAM,2BAA2B;AAExC,IAAM,wBAAwB;AAEvB,SAAS,cAAc,OAAsC;AAClE,SACE,OAAO,UAAU,YACjB,MAAM,SAAS,KACf,MAAM,UAAU,4BAChB,sBAAsB,KAAK,KAAK;AAEpC;AAEO,IAAK,kBAAL,kBAAKC,qBAAL;AACL,EAAAA,iBAAA,iBAAc;AACd,EAAAA,iBAAA,iBAAc;AACd,EAAAA,iBAAA,gBAAa;AACb,EAAAA,iBAAA,eAAY;AACZ,EAAAA,iBAAA,sBAAmB;AACnB,EAAAA,iBAAA,UAAO;AACP,EAAAA,iBAAA,eAAY;AACZ,EAAAA,iBAAA,gBAAa;AACb,EAAAA,iBAAA,qBAAkB;AAClB,EAAAA,iBAAA,iBAAc;AAVJ,SAAAA;AAAA,GAAA;AAqBL,IAAK,sBAAL,kBAAKC,yBAAL;AACL,EAAAA,qBAAA,aAAU;AACV,EAAAA,qBAAA,qBAAkB;AAClB,EAAAA,qBAAA,cAAW;AACX,EAAAA,qBAAA,YAAS;AAJC,SAAAA;AAAA,GAAA;;;AC5EZ,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AAE5B,SAAS,OAAO,OAAgD;AAC9D,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,QAAQ,qBAAqB;AACtC;AAEA,SAAS,gBAAgB,QAAwB;AAC/C,SAAO,OAAO,QAAQ,QAAQ,EAAE;AAClC;AAEO,SAAS,cAAc,SAAuC;AACnE,QAAM,WAAW,QAAQ,SAAS,KAAK;AACvC,QAAM,YAAY,QAAQ,UAAU,KAAK;AACzC,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAEA,QAAM,SAAS,gBAAgB,QAAQ,UAAU,oBAAoB;AACrE,QAAM,MAAM,IAAI,IAAI,GAAG,MAAM,UAAU,mBAAmB,QAAQ,CAAC,EAAE;AACrE,QAAM,UAAwD;AAAA,IAC5D,iCAA8B,QAAQ,UAAU;AAAA,IAChD,iCAA8B,QAAQ,UAAU;AAAA,IAChD,+BAA6B,SAAS;AAAA,IACtC,6BAA4B,QAAQ,QAAQ;AAAA,IAC5C,0CAAmC,QAAQ,cAAc;AAAA,IACzD,oBAAuB,QAAQ,IAAI;AAAA,IACnC,6BAA4B,OAAO,QAAQ,QAAQ,CAAC;AAAA,IACpD,+BAA6B,OAAO,QAAQ,SAAS,CAAC;AAAA,IACtD,yCAAkC,OAAO,QAAQ,cAAc,CAAC;AAAA,IAChE,iCAA8B,OAAO,QAAQ,UAAU,CAAC;AAAA,EAC1D;AAEA,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AAClC,QAAI,UAAU,UAAa,UAAU,IAAI;AACvC,UAAI,aAAa,IAAI,KAAK,KAAK;AAAA,IACjC;AAAA,EACF;AACA,SAAO,IAAI,SAAS;AACtB;;;ACjDA,mBAOO;;;ACJA,SAAS,gBAAgB,KAAsB;AACpD,MAAI,IAAI,WAAW,KAAK,IAAI,SAAS,qBAAsB,QAAO;AAClE,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,QAAI,OAAO,aAAa,SAAU,QAAO;AACzC,QAAI,OAAO,aAAa,SAAS;AAC/B,aACE,OAAO,aAAa,eAAe,OAAO,aAAa;AAAA,IAE3D;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,wBACd,OACuB;AACvB,QAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,MAAI,CAAC,gBAAgB,GAAG,EAAG,QAAO;AAClC,QAAM,QAAQ,MAAM,OAAO,KAAK;AAChC,SAAO;AAAA,IACL;AAAA,IACA,GAAI,QAAQ,EAAE,OAAO,MAAM,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC;AAAA,IAC9C,GAAI,cAAc,MAAM,IAAI,IAAI,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,EAC1D;AACF;;;ACtBA,IAAM,cAAc,IAAI,IAAY,OAAO,OAAO,cAAc,CAAC;AAEjE,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,SAAS,iBAAiB,OAAyB;AACjD,SAAO,UAAU,UAAa,OAAO,UAAU;AACjD;AAEA,SAAS,iBAAiB,OAAyB;AACjD,SAAO,UAAU,QAAQ,OAAO,UAAU;AAC5C;AAEA,IAAM,mBAAmB,IAAI,IAAY,OAAO,OAAO,mBAAmB,CAAC;AAE3E,SAAS,iBAAiB,OAAyB;AACjD,SAAO,OAAO,UAAU,YAAY,iBAAiB,IAAI,KAAK;AAChE;AAEA,SAAS,gBACP,MACA,SACS;AACT,UAAQ,MAAM;AAAA,IACZ;AACE,aAAO,OAAO,QAAQ,aAAa;AAAA,IACrC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,aAAa,YAC5B,iBAAiB,QAAQ,IAAI,KAC7B,OAAO,QAAQ,aAAa;AAAA,IAEhC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,WAAW,aACzB,QAAQ,eAAe,UACtB,OAAO,QAAQ,eAAe;AAAA,IAEpC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,iBAAiB,QAAQ,IAAI,KAC7B,OAAO,QAAQ,qBAAqB;AAAA,IAExC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,qBAAqB;AAAA,IAExC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,YAAY;AAAA,IAE/B;AACE,aACE,OAAO,QAAQ,SAAS,YACxB,OAAO,QAAQ,YAAY;AAAA,IAE/B;AACE,cACG,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,aAChD,iBAAiB,QAAQ,KAAK,KAC9B,iBAAiB,QAAQ,IAAI,KAC7B,iBAAiB,QAAQ,QAAQ;AAAA,IAErC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,iBAAiB,QAAQ,OAAO,KAChC,iBAAiB,QAAQ,YAAY,KACrC,iBAAiB,QAAQ,MAAM;AAAA,IAEnC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,iBAAiB,QAAQ,OAAO,KAChC,iBAAiB,QAAQ,WAAW,KACpC,iBAAiB,QAAQ,MAAM;AAAA,IAEnC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,YAAY;AAAA,EAEjC;AACF;AAEO,SAAS,gBAAgB,MAAkC;AAChE,MAAI,CAAC,SAAS,IAAI,EAAG,QAAO;AAC5B,MACE,KAAK,WAAW,wBAChB,KAAK,YAAY,0BACjB,OAAO,KAAK,eAAe,YAC3B,KAAK,WAAW,WAAW,KAC3B,OAAO,KAAK,SAAS,YACrB,CAAC,YAAY,IAAI,KAAK,IAAI,KAC1B,CAAC,SAAS,KAAK,OAAO,GACtB;AACA,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,KAAK;AAClB,MAAI,CAAC,gBAAgB,MAAM,KAAK,OAAO,EAAG,QAAO;AACjD,SAAO;AACT;;;AFjEA,SAAS,YAAY,SAAmC;AACtD,QAAM,cAAc,QAAQ,QAAQ;AACpC,MAAI,CAAC,YAAa;AAClB,QAAM,UAA+B;AAAA,IACnC,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA,QAAQ,QAAQ;AAAA,IAChB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,EACxD;AACA,cAAY,YAAY,SAAS,QAAQ,YAAY;AACvD;AAEO,SAAS,aAAa,SAA8B;AACzD,QAAM,gBAAY,qBAAiC,IAAI;AACvD,QAAM,mBAAe,qBAAO,OAAO;AAEnC,8BAAU,MAAM;AACd,iBAAa,UAAU;AAAA,EACzB,GAAG,CAAC,OAAO,CAAC;AAEZ,8BAAU,MAAM;AACd,UAAM,YAAY,CAAC,UAAiC;AAClD,YAAM,cAAc,UAAU,SAAS;AACvC,UACE,CAAC,eACD,MAAM,WAAW,eACjB,MAAM,WAAW,QAAQ,cACzB;AACA;AAAA,MACF;AACA,YAAM,aAAa,gBAAgB,MAAM,IAAI;AAC7C,UAAI,CAAC,cAAc,WAAW,eAAe,QAAQ,WAAY;AAEjE,YAAM,YAAY,aAAa;AAC/B,gBAAU,UAAU,UAAU;AAC9B,cAAQ,WAAW,MAAM;AAAA,QACvB;AACE,oBAAU,UAAU,UAAU;AAC9B;AAAA,QACF;AACE,oBAAU,WAAW,UAAU;AAC/B;AAAA,QACF;AACE,oBAAU,SAAS,UAAU;AAC7B;AAAA,QACF;AACE,oBAAU,sBAAsB,UAAU;AAC1C;AAAA,QACF;AACE,oBAAU,oBAAoB,UAAU;AACxC;AAAA,QACF;AACE,oBAAU,UAAU,UAAU;AAC9B;AAAA,QACF;AACE,oBAAU,UAAU,UAAU;AAC9B;AAAA,QACF;AACE,oBAAU,iBAAiB,UAAU;AACrC;AAAA,QACF;AACE,oBAAU,sBAAsB,UAAU;AAC1C;AAAA,QACF;AACE,oBAAU,qBAAqB,UAAU;AACzC;AAAA,QACF;AACE,oBAAU,mBAAmB,UAAU;AACvC;AAAA,MACJ;AAAA,IACF;AAEA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,MAAM,OAAO,oBAAoB,WAAW,SAAS;AAAA,EAC9D,GAAG,CAAC,QAAQ,YAAY,QAAQ,YAAY,CAAC;AAE7C,QAAM,WAAO;AAAA,IACX,CAAC,QAA4B,YAC3B,YAAY;AAAA,MACV,QAAQ,UAAU;AAAA,MAClB,YAAY,QAAQ;AAAA,MACpB;AAAA,MACA,cAAc,QAAQ;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,IACH,CAAC,QAAQ,YAAY,QAAQ,YAAY;AAAA,EAC3C;AACA,QAAM,YAAQ,0BAAY,MAAM,wBAA6B,GAAG,CAAC,IAAI,CAAC;AACtE,QAAM,gBAAY;AAAA,IAChB,MAAM,iCAAkC;AAAA,IACxC,CAAC,IAAI;AAAA,EACP;AACA,QAAM,mBAAe;AAAA,IACnB,MAAM,uCAAqC;AAAA,IAC3C,CAAC,IAAI;AAAA,EACP;AACA,QAAM,gBAAY;AAAA,IAChB,CAAC,UAA0B;AACzB,YAAM,aAAa,wBAAwB,KAAK;AAChD,UAAI,CAAC,WAAY;AACjB,yCAAoC,UAAU;AAAA,IAChD;AAAA,IACA,CAAC,IAAI;AAAA,EACP;AACA,QAAM,iBAAa;AAAA,IACjB,MAAM,mCAAmC;AAAA,IACzC,CAAC,IAAI;AAAA,EACP;AACA,QAAM,gBAAY,0BAAY,MAAM,UAAU,SAAS,CAAC,CAAC;AACzD,QAAM,aAAS;AAAA,IACb,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,YAAY,WAAW,OAAO,WAAW,cAAc,SAAS;AAAA,EACnE;AAEA,wCAAoB,QAAQ,WAAW,MAAM,QAAQ,CAAC,MAAM,CAAC;AAC7D,SAAO,EAAE,WAAW,OAAO;AAC7B;;;AHzCQ;AA3HD,IAAM,uBACX;AAEF,SAAS,UAAU,OAAwD;AACzE,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,OAAO,UAAU,WAAW,GAAG,KAAK,OAAO;AACpD;AAEA,SAAS,UAAU,OAAuB;AACxC,SAAO,IAAI,IAAI,KAAK,EAAE;AACxB;AAEO,IAAM,gBAAY;AAAA,EACvB,SAASC,WAAU,OAAO,KAAK;AAC7B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI;AACJ,UAAM,cAAU,qBAAM;AACtB,UAAM,iBAAa;AAAA,MACjB,MAAM,OAAO,QAAQ,QAAQ,MAAM,EAAE,CAAC;AAAA,MACtC,CAAC,OAAO;AAAA,IACV;AACA,UAAM,kBAAc,uBAAQ,MAAM,UAAU,MAAM,GAAG,CAAC,MAAM,CAAC;AAC7D,UAAM,uBAAuB,gBAAgB;AAC7C,UAAM,aACJ,OAAO,WAAW,cAAc,SAAY,OAAO,SAAS;AAC9D,UAAM,UAAM;AAAA,MACV,MACE,cAAc;AAAA,QACZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,MACH;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,EAAE,UAAU,IAAI,aAAa;AAAA,MACjC;AAAA,MACA,cAAc;AAAA,MACd,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,eAA8B;AAAA,MAClC,OAAO,UAAU,KAAK;AAAA,MACtB,QAAQ,UAAU,MAAM;AAAA,MACxB,GAAG;AAAA,IACL;AACA,UAAM,aAA4B;AAAA,MAChC,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,GAAG;AAAA,IACL;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,OAAO;AAAA,QACP,4BAA0B;AAAA,QAE1B;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,WAAW;AAAA,YACX,OAAO;AAAA,YACP;AAAA,YACA;AAAA,YACA;AAAA,YACA,iBAAe;AAAA,YACf,gBAAe;AAAA;AAAA,QACjB;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;AAEA,UAAU,cAAc;","names":["import_react","EmbedEventType","EmbedCommandType","EmbedCommandAction","EmbedErrorCode","EmbedQueryParam","EmbedCaptureOutcome","OeMeeting"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -12,7 +12,10 @@ declare enum EmbedEventType {
|
|
|
12
12
|
PARTICIPANT_LEFT = "oemeeting:participant-left",
|
|
13
13
|
ENDED = "oemeeting:ended",
|
|
14
14
|
ERROR = "oemeeting:error",
|
|
15
|
-
STAGE_CHANGED = "oemeeting:stage-changed"
|
|
15
|
+
STAGE_CHANGED = "oemeeting:stage-changed",
|
|
16
|
+
TRANSCRIPT_STARTED = "oemeeting:transcript-started",
|
|
17
|
+
RECORDING_STARTED = "oemeeting:recording-started",
|
|
18
|
+
RECORDING_SAVED = "oemeeting:recording-saved"
|
|
16
19
|
}
|
|
17
20
|
declare enum EmbedCommandType {
|
|
18
21
|
COMMAND = "oemeeting:command"
|
|
@@ -33,6 +36,8 @@ declare enum EmbedErrorCode {
|
|
|
33
36
|
PROTOCOL_MISMATCH = "PROTOCOL_MISMATCH",
|
|
34
37
|
STAGE_ORIGIN_DENIED = "STAGE_ORIGIN_DENIED",
|
|
35
38
|
STAGE_URL_INVALID = "STAGE_URL_INVALID",
|
|
39
|
+
TRANSCRIPT_START_FAILED = "TRANSCRIPT_START_FAILED",
|
|
40
|
+
RECORDING_START_FAILED = "RECORDING_START_FAILED",
|
|
36
41
|
UNKNOWN = "UNKNOWN"
|
|
37
42
|
}
|
|
38
43
|
/** Open host-owned tool id. Meeting does not catalog values. */
|
|
@@ -48,7 +53,23 @@ declare enum EmbedQueryParam {
|
|
|
48
53
|
EXTERNAL_USER_ID = "externalUserId",
|
|
49
54
|
LANG = "lang",
|
|
50
55
|
AUTO_JOIN = "autoJoin",
|
|
51
|
-
HIDE_LEAVE = "hideLeave"
|
|
56
|
+
HIDE_LEAVE = "hideLeave",
|
|
57
|
+
AUTO_TRANSCRIBE = "autoTranscribe",
|
|
58
|
+
AUTO_RECORD = "autoRecord"
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* What happened to the auto-transcribe request the embed made.
|
|
62
|
+
*
|
|
63
|
+
* `ALREADY_RUNNING` is the normal outcome for every participant after the
|
|
64
|
+
* first, because the transcript is a room-level asset rather than a
|
|
65
|
+
* per-participant one. `DISABLED` means the OE Meeting deployment has
|
|
66
|
+
* transcription turned off.
|
|
67
|
+
*/
|
|
68
|
+
declare enum EmbedCaptureOutcome {
|
|
69
|
+
STARTED = "started",
|
|
70
|
+
ALREADY_RUNNING = "already_running",
|
|
71
|
+
DISABLED = "disabled",
|
|
72
|
+
FAILED = "failed"
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
interface EmbedMessageBase {
|
|
@@ -80,6 +101,11 @@ interface EmbedParticipantLeftPayload {
|
|
|
80
101
|
identity: string;
|
|
81
102
|
participantCount: number;
|
|
82
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Room closed or the last human participant left. Use `roomSlug` to fetch
|
|
106
|
+
* the transcript and recordings from your backend; do not call the external
|
|
107
|
+
* API from the browser.
|
|
108
|
+
*/
|
|
83
109
|
interface EmbedEndedPayload {
|
|
84
110
|
roomSlug: string;
|
|
85
111
|
endedAt: string;
|
|
@@ -94,6 +120,32 @@ interface EmbedStageChangedPayload {
|
|
|
94
120
|
tool?: StageToolId;
|
|
95
121
|
openedBy?: string;
|
|
96
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Reported once per meeting when `autoTranscribe` is set. Use `roomSlug` to
|
|
125
|
+
* fetch the transcript from your backend once the meeting ends.
|
|
126
|
+
*
|
|
127
|
+
* `transcriptId` is null when nothing was started (`DISABLED` or `FAILED`);
|
|
128
|
+
* pair a `FAILED` outcome with the `TRANSCRIPT_START_FAILED` error event for
|
|
129
|
+
* the reason.
|
|
130
|
+
*/
|
|
131
|
+
interface EmbedTranscriptStartedPayload {
|
|
132
|
+
roomSlug: string;
|
|
133
|
+
outcome: EmbedCaptureOutcome;
|
|
134
|
+
transcriptId: string | null;
|
|
135
|
+
status: string | null;
|
|
136
|
+
}
|
|
137
|
+
/** Reported after the embed handles an `autoRecord` request. */
|
|
138
|
+
interface EmbedRecordingStartedPayload {
|
|
139
|
+
roomSlug: string;
|
|
140
|
+
outcome: EmbedCaptureOutcome;
|
|
141
|
+
recordingId: string | null;
|
|
142
|
+
status: string | null;
|
|
143
|
+
}
|
|
144
|
+
/** Room recording finished uploading (metadata left `saving`). */
|
|
145
|
+
interface EmbedRecordingSavedPayload {
|
|
146
|
+
roomSlug: string;
|
|
147
|
+
savedAt: string;
|
|
148
|
+
}
|
|
97
149
|
interface OpenStageInput {
|
|
98
150
|
url: string;
|
|
99
151
|
title?: string;
|
|
@@ -108,6 +160,9 @@ type EmbedEventPayloadMap = {
|
|
|
108
160
|
[EmbedEventType.ENDED]: EmbedEndedPayload;
|
|
109
161
|
[EmbedEventType.ERROR]: EmbedErrorPayload;
|
|
110
162
|
[EmbedEventType.STAGE_CHANGED]: EmbedStageChangedPayload;
|
|
163
|
+
[EmbedEventType.TRANSCRIPT_STARTED]: EmbedTranscriptStartedPayload;
|
|
164
|
+
[EmbedEventType.RECORDING_STARTED]: EmbedRecordingStartedPayload;
|
|
165
|
+
[EmbedEventType.RECORDING_SAVED]: EmbedRecordingSavedPayload;
|
|
111
166
|
};
|
|
112
167
|
type EmbedEventByType<T extends EmbedEventType> = EmbedMessageBase & {
|
|
113
168
|
type: T;
|
|
@@ -141,6 +196,15 @@ interface OeMeetingProps {
|
|
|
141
196
|
lang?: string;
|
|
142
197
|
autoJoin?: boolean;
|
|
143
198
|
hideLeave?: boolean;
|
|
199
|
+
/**
|
|
200
|
+
* Start the shared transcript as soon as the embed connects. The transcript
|
|
201
|
+
* is a room-level asset: the first participant to connect starts it and it
|
|
202
|
+
* keeps running after they leave. Retrieve it afterwards from your backend
|
|
203
|
+
* with `GET /external-api/v1.0/rooms/:idOrSlug/transcript`.
|
|
204
|
+
*/
|
|
205
|
+
autoTranscribe?: boolean;
|
|
206
|
+
/** Start the shared room recording as soon as the embed connects. */
|
|
207
|
+
autoRecord?: boolean;
|
|
144
208
|
title?: string;
|
|
145
209
|
width?: number | string;
|
|
146
210
|
height?: number | string;
|
|
@@ -155,9 +219,17 @@ interface OeMeetingProps {
|
|
|
155
219
|
onLeft?: (event: EmbedEventOf<EmbedEventType.LEFT>) => void;
|
|
156
220
|
onParticipantJoined?: (event: EmbedEventOf<EmbedEventType.PARTICIPANT_JOINED>) => void;
|
|
157
221
|
onParticipantLeft?: (event: EmbedEventOf<EmbedEventType.PARTICIPANT_LEFT>) => void;
|
|
222
|
+
/**
|
|
223
|
+
* Meeting closed. Fetch transcript and recordings from your backend using
|
|
224
|
+
* `payload.roomSlug`. Distinct from `onLeft`, which fires when this user
|
|
225
|
+
* leaves while others may still be in the room.
|
|
226
|
+
*/
|
|
158
227
|
onEnded?: (event: EmbedEventOf<EmbedEventType.ENDED>) => void;
|
|
159
228
|
onError?: (event: EmbedEventOf<EmbedEventType.ERROR>) => void;
|
|
160
229
|
onStageChanged?: (event: EmbedEventOf<EmbedEventType.STAGE_CHANGED>) => void;
|
|
230
|
+
onTranscriptStarted?: (event: EmbedEventOf<EmbedEventType.TRANSCRIPT_STARTED>) => void;
|
|
231
|
+
onRecordingStarted?: (event: EmbedEventOf<EmbedEventType.RECORDING_STARTED>) => void;
|
|
232
|
+
onRecordingSaved?: (event: EmbedEventOf<EmbedEventType.RECORDING_SAVED>) => void;
|
|
161
233
|
onEvent?: (event: EmbedEvent) => void;
|
|
162
234
|
}
|
|
163
235
|
interface BuildEmbedUrlOptions {
|
|
@@ -169,6 +241,8 @@ interface BuildEmbedUrlOptions {
|
|
|
169
241
|
lang?: string;
|
|
170
242
|
autoJoin?: boolean;
|
|
171
243
|
hideLeave?: boolean;
|
|
244
|
+
autoTranscribe?: boolean;
|
|
245
|
+
autoRecord?: boolean;
|
|
172
246
|
instanceId?: string;
|
|
173
247
|
hostOrigin?: string;
|
|
174
248
|
}
|
|
@@ -176,7 +250,7 @@ interface BuildEmbedUrlOptions {
|
|
|
176
250
|
declare const DEFAULT_IFRAME_ALLOW = "camera; microphone; fullscreen; autoplay; display-capture; clipboard-write; encrypted-media";
|
|
177
251
|
declare const OeMeeting: react.ForwardRefExoticComponent<OeMeetingProps & react.RefAttributes<OeMeetingHandle>>;
|
|
178
252
|
|
|
179
|
-
type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onStageChanged' | 'onEvent'> & {
|
|
253
|
+
type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onStageChanged' | 'onTranscriptStarted' | 'onRecordingStarted' | 'onRecordingSaved' | 'onEvent'> & {
|
|
180
254
|
instanceId: string;
|
|
181
255
|
targetOrigin: string;
|
|
182
256
|
handleRef?: Ref<OeMeetingHandle>;
|
|
@@ -193,4 +267,4 @@ declare function parseEmbedEvent(data: unknown): EmbedEvent | null;
|
|
|
193
267
|
declare function isValidStageUrl(url: string): boolean;
|
|
194
268
|
declare function normalizeOpenStageInput(input: OpenStageInput): OpenStageInput | null;
|
|
195
269
|
|
|
196
|
-
export { type BuildEmbedUrlOptions, DEFAULT_EMBED_ORIGIN, DEFAULT_IFRAME_ALLOW, EMBED_MESSAGE_SOURCE, EMBED_PROTOCOL_VERSION, 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, EmbedQueryParam, type EmbedReadyPayload, type EmbedStageChangedPayload, MAX_STAGE_TOOL_ID_LENGTH, MAX_STAGE_URL_LENGTH, OeMeeting, type OeMeetingHandle, type OeMeetingProps, type OpenStageInput, type StageToolId, buildEmbedUrl, isStageToolId, isValidStageUrl, normalizeOpenStageInput, parseEmbedEvent, useOeMeeting };
|
|
270
|
+
export { type BuildEmbedUrlOptions, DEFAULT_EMBED_ORIGIN, DEFAULT_IFRAME_ALLOW, EMBED_MESSAGE_SOURCE, EMBED_PROTOCOL_VERSION, 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, EmbedQueryParam, type EmbedReadyPayload, type EmbedRecordingSavedPayload, type EmbedRecordingStartedPayload, 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,10 @@ declare enum EmbedEventType {
|
|
|
12
12
|
PARTICIPANT_LEFT = "oemeeting:participant-left",
|
|
13
13
|
ENDED = "oemeeting:ended",
|
|
14
14
|
ERROR = "oemeeting:error",
|
|
15
|
-
STAGE_CHANGED = "oemeeting:stage-changed"
|
|
15
|
+
STAGE_CHANGED = "oemeeting:stage-changed",
|
|
16
|
+
TRANSCRIPT_STARTED = "oemeeting:transcript-started",
|
|
17
|
+
RECORDING_STARTED = "oemeeting:recording-started",
|
|
18
|
+
RECORDING_SAVED = "oemeeting:recording-saved"
|
|
16
19
|
}
|
|
17
20
|
declare enum EmbedCommandType {
|
|
18
21
|
COMMAND = "oemeeting:command"
|
|
@@ -33,6 +36,8 @@ declare enum EmbedErrorCode {
|
|
|
33
36
|
PROTOCOL_MISMATCH = "PROTOCOL_MISMATCH",
|
|
34
37
|
STAGE_ORIGIN_DENIED = "STAGE_ORIGIN_DENIED",
|
|
35
38
|
STAGE_URL_INVALID = "STAGE_URL_INVALID",
|
|
39
|
+
TRANSCRIPT_START_FAILED = "TRANSCRIPT_START_FAILED",
|
|
40
|
+
RECORDING_START_FAILED = "RECORDING_START_FAILED",
|
|
36
41
|
UNKNOWN = "UNKNOWN"
|
|
37
42
|
}
|
|
38
43
|
/** Open host-owned tool id. Meeting does not catalog values. */
|
|
@@ -48,7 +53,23 @@ declare enum EmbedQueryParam {
|
|
|
48
53
|
EXTERNAL_USER_ID = "externalUserId",
|
|
49
54
|
LANG = "lang",
|
|
50
55
|
AUTO_JOIN = "autoJoin",
|
|
51
|
-
HIDE_LEAVE = "hideLeave"
|
|
56
|
+
HIDE_LEAVE = "hideLeave",
|
|
57
|
+
AUTO_TRANSCRIBE = "autoTranscribe",
|
|
58
|
+
AUTO_RECORD = "autoRecord"
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* What happened to the auto-transcribe request the embed made.
|
|
62
|
+
*
|
|
63
|
+
* `ALREADY_RUNNING` is the normal outcome for every participant after the
|
|
64
|
+
* first, because the transcript is a room-level asset rather than a
|
|
65
|
+
* per-participant one. `DISABLED` means the OE Meeting deployment has
|
|
66
|
+
* transcription turned off.
|
|
67
|
+
*/
|
|
68
|
+
declare enum EmbedCaptureOutcome {
|
|
69
|
+
STARTED = "started",
|
|
70
|
+
ALREADY_RUNNING = "already_running",
|
|
71
|
+
DISABLED = "disabled",
|
|
72
|
+
FAILED = "failed"
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
interface EmbedMessageBase {
|
|
@@ -80,6 +101,11 @@ interface EmbedParticipantLeftPayload {
|
|
|
80
101
|
identity: string;
|
|
81
102
|
participantCount: number;
|
|
82
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Room closed or the last human participant left. Use `roomSlug` to fetch
|
|
106
|
+
* the transcript and recordings from your backend; do not call the external
|
|
107
|
+
* API from the browser.
|
|
108
|
+
*/
|
|
83
109
|
interface EmbedEndedPayload {
|
|
84
110
|
roomSlug: string;
|
|
85
111
|
endedAt: string;
|
|
@@ -94,6 +120,32 @@ interface EmbedStageChangedPayload {
|
|
|
94
120
|
tool?: StageToolId;
|
|
95
121
|
openedBy?: string;
|
|
96
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Reported once per meeting when `autoTranscribe` is set. Use `roomSlug` to
|
|
125
|
+
* fetch the transcript from your backend once the meeting ends.
|
|
126
|
+
*
|
|
127
|
+
* `transcriptId` is null when nothing was started (`DISABLED` or `FAILED`);
|
|
128
|
+
* pair a `FAILED` outcome with the `TRANSCRIPT_START_FAILED` error event for
|
|
129
|
+
* the reason.
|
|
130
|
+
*/
|
|
131
|
+
interface EmbedTranscriptStartedPayload {
|
|
132
|
+
roomSlug: string;
|
|
133
|
+
outcome: EmbedCaptureOutcome;
|
|
134
|
+
transcriptId: string | null;
|
|
135
|
+
status: string | null;
|
|
136
|
+
}
|
|
137
|
+
/** Reported after the embed handles an `autoRecord` request. */
|
|
138
|
+
interface EmbedRecordingStartedPayload {
|
|
139
|
+
roomSlug: string;
|
|
140
|
+
outcome: EmbedCaptureOutcome;
|
|
141
|
+
recordingId: string | null;
|
|
142
|
+
status: string | null;
|
|
143
|
+
}
|
|
144
|
+
/** Room recording finished uploading (metadata left `saving`). */
|
|
145
|
+
interface EmbedRecordingSavedPayload {
|
|
146
|
+
roomSlug: string;
|
|
147
|
+
savedAt: string;
|
|
148
|
+
}
|
|
97
149
|
interface OpenStageInput {
|
|
98
150
|
url: string;
|
|
99
151
|
title?: string;
|
|
@@ -108,6 +160,9 @@ type EmbedEventPayloadMap = {
|
|
|
108
160
|
[EmbedEventType.ENDED]: EmbedEndedPayload;
|
|
109
161
|
[EmbedEventType.ERROR]: EmbedErrorPayload;
|
|
110
162
|
[EmbedEventType.STAGE_CHANGED]: EmbedStageChangedPayload;
|
|
163
|
+
[EmbedEventType.TRANSCRIPT_STARTED]: EmbedTranscriptStartedPayload;
|
|
164
|
+
[EmbedEventType.RECORDING_STARTED]: EmbedRecordingStartedPayload;
|
|
165
|
+
[EmbedEventType.RECORDING_SAVED]: EmbedRecordingSavedPayload;
|
|
111
166
|
};
|
|
112
167
|
type EmbedEventByType<T extends EmbedEventType> = EmbedMessageBase & {
|
|
113
168
|
type: T;
|
|
@@ -141,6 +196,15 @@ interface OeMeetingProps {
|
|
|
141
196
|
lang?: string;
|
|
142
197
|
autoJoin?: boolean;
|
|
143
198
|
hideLeave?: boolean;
|
|
199
|
+
/**
|
|
200
|
+
* Start the shared transcript as soon as the embed connects. The transcript
|
|
201
|
+
* is a room-level asset: the first participant to connect starts it and it
|
|
202
|
+
* keeps running after they leave. Retrieve it afterwards from your backend
|
|
203
|
+
* with `GET /external-api/v1.0/rooms/:idOrSlug/transcript`.
|
|
204
|
+
*/
|
|
205
|
+
autoTranscribe?: boolean;
|
|
206
|
+
/** Start the shared room recording as soon as the embed connects. */
|
|
207
|
+
autoRecord?: boolean;
|
|
144
208
|
title?: string;
|
|
145
209
|
width?: number | string;
|
|
146
210
|
height?: number | string;
|
|
@@ -155,9 +219,17 @@ interface OeMeetingProps {
|
|
|
155
219
|
onLeft?: (event: EmbedEventOf<EmbedEventType.LEFT>) => void;
|
|
156
220
|
onParticipantJoined?: (event: EmbedEventOf<EmbedEventType.PARTICIPANT_JOINED>) => void;
|
|
157
221
|
onParticipantLeft?: (event: EmbedEventOf<EmbedEventType.PARTICIPANT_LEFT>) => void;
|
|
222
|
+
/**
|
|
223
|
+
* Meeting closed. Fetch transcript and recordings from your backend using
|
|
224
|
+
* `payload.roomSlug`. Distinct from `onLeft`, which fires when this user
|
|
225
|
+
* leaves while others may still be in the room.
|
|
226
|
+
*/
|
|
158
227
|
onEnded?: (event: EmbedEventOf<EmbedEventType.ENDED>) => void;
|
|
159
228
|
onError?: (event: EmbedEventOf<EmbedEventType.ERROR>) => void;
|
|
160
229
|
onStageChanged?: (event: EmbedEventOf<EmbedEventType.STAGE_CHANGED>) => void;
|
|
230
|
+
onTranscriptStarted?: (event: EmbedEventOf<EmbedEventType.TRANSCRIPT_STARTED>) => void;
|
|
231
|
+
onRecordingStarted?: (event: EmbedEventOf<EmbedEventType.RECORDING_STARTED>) => void;
|
|
232
|
+
onRecordingSaved?: (event: EmbedEventOf<EmbedEventType.RECORDING_SAVED>) => void;
|
|
161
233
|
onEvent?: (event: EmbedEvent) => void;
|
|
162
234
|
}
|
|
163
235
|
interface BuildEmbedUrlOptions {
|
|
@@ -169,6 +241,8 @@ interface BuildEmbedUrlOptions {
|
|
|
169
241
|
lang?: string;
|
|
170
242
|
autoJoin?: boolean;
|
|
171
243
|
hideLeave?: boolean;
|
|
244
|
+
autoTranscribe?: boolean;
|
|
245
|
+
autoRecord?: boolean;
|
|
172
246
|
instanceId?: string;
|
|
173
247
|
hostOrigin?: string;
|
|
174
248
|
}
|
|
@@ -176,7 +250,7 @@ interface BuildEmbedUrlOptions {
|
|
|
176
250
|
declare const DEFAULT_IFRAME_ALLOW = "camera; microphone; fullscreen; autoplay; display-capture; clipboard-write; encrypted-media";
|
|
177
251
|
declare const OeMeeting: react.ForwardRefExoticComponent<OeMeetingProps & react.RefAttributes<OeMeetingHandle>>;
|
|
178
252
|
|
|
179
|
-
type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onStageChanged' | 'onEvent'> & {
|
|
253
|
+
type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onStageChanged' | 'onTranscriptStarted' | 'onRecordingStarted' | 'onRecordingSaved' | 'onEvent'> & {
|
|
180
254
|
instanceId: string;
|
|
181
255
|
targetOrigin: string;
|
|
182
256
|
handleRef?: Ref<OeMeetingHandle>;
|
|
@@ -193,4 +267,4 @@ declare function parseEmbedEvent(data: unknown): EmbedEvent | null;
|
|
|
193
267
|
declare function isValidStageUrl(url: string): boolean;
|
|
194
268
|
declare function normalizeOpenStageInput(input: OpenStageInput): OpenStageInput | null;
|
|
195
269
|
|
|
196
|
-
export { type BuildEmbedUrlOptions, DEFAULT_EMBED_ORIGIN, DEFAULT_IFRAME_ALLOW, EMBED_MESSAGE_SOURCE, EMBED_PROTOCOL_VERSION, 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, EmbedQueryParam, type EmbedReadyPayload, type EmbedStageChangedPayload, MAX_STAGE_TOOL_ID_LENGTH, MAX_STAGE_URL_LENGTH, OeMeeting, type OeMeetingHandle, type OeMeetingProps, type OpenStageInput, type StageToolId, buildEmbedUrl, isStageToolId, isValidStageUrl, normalizeOpenStageInput, parseEmbedEvent, useOeMeeting };
|
|
270
|
+
export { type BuildEmbedUrlOptions, DEFAULT_EMBED_ORIGIN, DEFAULT_IFRAME_ALLOW, EMBED_MESSAGE_SOURCE, EMBED_PROTOCOL_VERSION, 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, EmbedQueryParam, type EmbedReadyPayload, type EmbedRecordingSavedPayload, type EmbedRecordingStartedPayload, 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 };
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,9 @@ var EmbedEventType = /* @__PURE__ */ ((EmbedEventType2) => {
|
|
|
18
18
|
EmbedEventType2["ENDED"] = "oemeeting:ended";
|
|
19
19
|
EmbedEventType2["ERROR"] = "oemeeting:error";
|
|
20
20
|
EmbedEventType2["STAGE_CHANGED"] = "oemeeting:stage-changed";
|
|
21
|
+
EmbedEventType2["TRANSCRIPT_STARTED"] = "oemeeting:transcript-started";
|
|
22
|
+
EmbedEventType2["RECORDING_STARTED"] = "oemeeting:recording-started";
|
|
23
|
+
EmbedEventType2["RECORDING_SAVED"] = "oemeeting:recording-saved";
|
|
21
24
|
return EmbedEventType2;
|
|
22
25
|
})(EmbedEventType || {});
|
|
23
26
|
var EmbedCommandType = /* @__PURE__ */ ((EmbedCommandType2) => {
|
|
@@ -41,6 +44,8 @@ var EmbedErrorCode = /* @__PURE__ */ ((EmbedErrorCode2) => {
|
|
|
41
44
|
EmbedErrorCode2["PROTOCOL_MISMATCH"] = "PROTOCOL_MISMATCH";
|
|
42
45
|
EmbedErrorCode2["STAGE_ORIGIN_DENIED"] = "STAGE_ORIGIN_DENIED";
|
|
43
46
|
EmbedErrorCode2["STAGE_URL_INVALID"] = "STAGE_URL_INVALID";
|
|
47
|
+
EmbedErrorCode2["TRANSCRIPT_START_FAILED"] = "TRANSCRIPT_START_FAILED";
|
|
48
|
+
EmbedErrorCode2["RECORDING_START_FAILED"] = "RECORDING_START_FAILED";
|
|
44
49
|
EmbedErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
45
50
|
return EmbedErrorCode2;
|
|
46
51
|
})(EmbedErrorCode || {});
|
|
@@ -59,8 +64,17 @@ var EmbedQueryParam = /* @__PURE__ */ ((EmbedQueryParam2) => {
|
|
|
59
64
|
EmbedQueryParam2["LANG"] = "lang";
|
|
60
65
|
EmbedQueryParam2["AUTO_JOIN"] = "autoJoin";
|
|
61
66
|
EmbedQueryParam2["HIDE_LEAVE"] = "hideLeave";
|
|
67
|
+
EmbedQueryParam2["AUTO_TRANSCRIBE"] = "autoTranscribe";
|
|
68
|
+
EmbedQueryParam2["AUTO_RECORD"] = "autoRecord";
|
|
62
69
|
return EmbedQueryParam2;
|
|
63
70
|
})(EmbedQueryParam || {});
|
|
71
|
+
var EmbedCaptureOutcome = /* @__PURE__ */ ((EmbedCaptureOutcome2) => {
|
|
72
|
+
EmbedCaptureOutcome2["STARTED"] = "started";
|
|
73
|
+
EmbedCaptureOutcome2["ALREADY_RUNNING"] = "already_running";
|
|
74
|
+
EmbedCaptureOutcome2["DISABLED"] = "disabled";
|
|
75
|
+
EmbedCaptureOutcome2["FAILED"] = "failed";
|
|
76
|
+
return EmbedCaptureOutcome2;
|
|
77
|
+
})(EmbedCaptureOutcome || {});
|
|
64
78
|
|
|
65
79
|
// src/buildEmbedUrl.ts
|
|
66
80
|
var QUERY_FLAG_ENABLED = "1";
|
|
@@ -91,7 +105,9 @@ function buildEmbedUrl(options) {
|
|
|
91
105
|
["externalUserId" /* EXTERNAL_USER_ID */, options.externalUserId],
|
|
92
106
|
["lang" /* LANG */, options.lang],
|
|
93
107
|
["autoJoin" /* AUTO_JOIN */, toFlag(options.autoJoin)],
|
|
94
|
-
["hideLeave" /* HIDE_LEAVE */, toFlag(options.hideLeave)]
|
|
108
|
+
["hideLeave" /* HIDE_LEAVE */, toFlag(options.hideLeave)],
|
|
109
|
+
["autoTranscribe" /* AUTO_TRANSCRIBE */, toFlag(options.autoTranscribe)],
|
|
110
|
+
["autoRecord" /* AUTO_RECORD */, toFlag(options.autoRecord)]
|
|
95
111
|
];
|
|
96
112
|
for (const [key, value] of entries) {
|
|
97
113
|
if (value !== void 0 && value !== "") {
|
|
@@ -143,6 +159,13 @@ function isRecord(value) {
|
|
|
143
159
|
function isOptionalString(value) {
|
|
144
160
|
return value === void 0 || typeof value === "string";
|
|
145
161
|
}
|
|
162
|
+
function isNullableString(value) {
|
|
163
|
+
return value === null || typeof value === "string";
|
|
164
|
+
}
|
|
165
|
+
var CAPTURE_OUTCOMES = new Set(Object.values(EmbedCaptureOutcome));
|
|
166
|
+
function isCaptureOutcome(value) {
|
|
167
|
+
return typeof value === "string" && CAPTURE_OUTCOMES.has(value);
|
|
168
|
+
}
|
|
146
169
|
function hasValidPayload(type, payload) {
|
|
147
170
|
switch (type) {
|
|
148
171
|
case "oemeeting:ready" /* READY */:
|
|
@@ -161,6 +184,12 @@ function hasValidPayload(type, payload) {
|
|
|
161
184
|
return typeof payload.code === "string" && typeof payload.message === "string";
|
|
162
185
|
case "oemeeting:stage-changed" /* STAGE_CHANGED */:
|
|
163
186
|
return (payload.url === null || typeof payload.url === "string") && isOptionalString(payload.title) && isOptionalString(payload.tool) && isOptionalString(payload.openedBy);
|
|
187
|
+
case "oemeeting:transcript-started" /* TRANSCRIPT_STARTED */:
|
|
188
|
+
return typeof payload.roomSlug === "string" && isCaptureOutcome(payload.outcome) && isNullableString(payload.transcriptId) && isNullableString(payload.status);
|
|
189
|
+
case "oemeeting:recording-started" /* RECORDING_STARTED */:
|
|
190
|
+
return typeof payload.roomSlug === "string" && isCaptureOutcome(payload.outcome) && isNullableString(payload.recordingId) && isNullableString(payload.status);
|
|
191
|
+
case "oemeeting:recording-saved" /* RECORDING_SAVED */:
|
|
192
|
+
return typeof payload.roomSlug === "string" && typeof payload.savedAt === "string";
|
|
164
193
|
}
|
|
165
194
|
}
|
|
166
195
|
function parseEmbedEvent(data) {
|
|
@@ -228,6 +257,15 @@ function useOeMeeting(options) {
|
|
|
228
257
|
case "oemeeting:stage-changed" /* STAGE_CHANGED */:
|
|
229
258
|
callbacks.onStageChanged?.(embedEvent);
|
|
230
259
|
break;
|
|
260
|
+
case "oemeeting:transcript-started" /* TRANSCRIPT_STARTED */:
|
|
261
|
+
callbacks.onTranscriptStarted?.(embedEvent);
|
|
262
|
+
break;
|
|
263
|
+
case "oemeeting:recording-started" /* RECORDING_STARTED */:
|
|
264
|
+
callbacks.onRecordingStarted?.(embedEvent);
|
|
265
|
+
break;
|
|
266
|
+
case "oemeeting:recording-saved" /* RECORDING_SAVED */:
|
|
267
|
+
callbacks.onRecordingSaved?.(embedEvent);
|
|
268
|
+
break;
|
|
231
269
|
}
|
|
232
270
|
};
|
|
233
271
|
window.addEventListener("message", onMessage);
|
|
@@ -301,6 +339,8 @@ var OeMeeting = forwardRef(
|
|
|
301
339
|
lang,
|
|
302
340
|
autoJoin = true,
|
|
303
341
|
hideLeave,
|
|
342
|
+
autoTranscribe,
|
|
343
|
+
autoRecord,
|
|
304
344
|
title = "OE Meeting room",
|
|
305
345
|
width = "100%",
|
|
306
346
|
height = 640,
|
|
@@ -318,6 +358,9 @@ var OeMeeting = forwardRef(
|
|
|
318
358
|
onEnded,
|
|
319
359
|
onError,
|
|
320
360
|
onStageChanged,
|
|
361
|
+
onTranscriptStarted,
|
|
362
|
+
onRecordingStarted,
|
|
363
|
+
onRecordingSaved,
|
|
321
364
|
onEvent
|
|
322
365
|
} = props;
|
|
323
366
|
const reactId = useId();
|
|
@@ -338,11 +381,15 @@ var OeMeeting = forwardRef(
|
|
|
338
381
|
lang,
|
|
339
382
|
autoJoin,
|
|
340
383
|
hideLeave,
|
|
384
|
+
autoTranscribe,
|
|
385
|
+
autoRecord,
|
|
341
386
|
instanceId,
|
|
342
387
|
hostOrigin
|
|
343
388
|
}),
|
|
344
389
|
[
|
|
345
390
|
autoJoin,
|
|
391
|
+
autoRecord,
|
|
392
|
+
autoTranscribe,
|
|
346
393
|
externalUserId,
|
|
347
394
|
hideLeave,
|
|
348
395
|
hostOrigin,
|
|
@@ -366,6 +413,9 @@ var OeMeeting = forwardRef(
|
|
|
366
413
|
onEnded,
|
|
367
414
|
onError,
|
|
368
415
|
onStageChanged,
|
|
416
|
+
onTranscriptStarted,
|
|
417
|
+
onRecordingStarted,
|
|
418
|
+
onRecordingSaved,
|
|
369
419
|
onEvent
|
|
370
420
|
});
|
|
371
421
|
const wrapperStyle = {
|
|
@@ -409,6 +459,7 @@ export {
|
|
|
409
459
|
DEFAULT_IFRAME_ALLOW,
|
|
410
460
|
EMBED_MESSAGE_SOURCE,
|
|
411
461
|
EMBED_PROTOCOL_VERSION,
|
|
462
|
+
EmbedCaptureOutcome,
|
|
412
463
|
EmbedCommandAction,
|
|
413
464
|
EmbedCommandType,
|
|
414
465
|
EmbedErrorCode,
|
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 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 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 instanceId,\n hostOrigin,\n }),\n [\n autoJoin,\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 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}\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 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}\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 ];\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 | '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 }\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 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 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 }\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","OeMeeting","useMemo"]}
|
|
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","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":";AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA,WAAAA;AAAA,OAEK;;;ACPA,IAAM,yBAAyB;AAC/B,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAE7B,IAAK,iBAAL,kBAAKC,oBAAL;AACL,EAAAA,gBAAA,WAAQ;AACR,EAAAA,gBAAA,YAAS;AACT,EAAAA,gBAAA,UAAO;AACP,EAAAA,gBAAA,wBAAqB;AACrB,EAAAA,gBAAA,sBAAmB;AACnB,EAAAA,gBAAA,WAAQ;AACR,EAAAA,gBAAA,WAAQ;AACR,EAAAA,gBAAA,mBAAgB;AAChB,EAAAA,gBAAA,wBAAqB;AACrB,EAAAA,gBAAA,uBAAoB;AACpB,EAAAA,gBAAA,qBAAkB;AAXR,SAAAA;AAAA,GAAA;AAcL,IAAK,mBAAL,kBAAKC,sBAAL;AACL,EAAAA,kBAAA,aAAU;AADA,SAAAA;AAAA,GAAA;AAIL,IAAK,qBAAL,kBAAKC,wBAAL;AACL,EAAAA,oBAAA,WAAQ;AACR,EAAAA,oBAAA,gBAAa;AACb,EAAAA,oBAAA,mBAAgB;AAChB,EAAAA,oBAAA,gBAAa;AACb,EAAAA,oBAAA,iBAAc;AALJ,SAAAA;AAAA,GAAA;AAQL,IAAK,iBAAL,kBAAKC,oBAAL;AACL,EAAAA,gBAAA,mBAAgB;AAChB,EAAAA,gBAAA,mBAAgB;AAChB,EAAAA,gBAAA,oBAAiB;AACjB,EAAAA,gBAAA,wBAAqB;AACrB,EAAAA,gBAAA,uBAAoB;AACpB,EAAAA,gBAAA,uBAAoB;AACpB,EAAAA,gBAAA,yBAAsB;AACtB,EAAAA,gBAAA,uBAAoB;AACpB,EAAAA,gBAAA,6BAA0B;AAC1B,EAAAA,gBAAA,4BAAyB;AACzB,EAAAA,gBAAA,aAAU;AAXA,SAAAA;AAAA,GAAA;AAiBL,IAAM,uBAAuB;AAC7B,IAAM,2BAA2B;AAExC,IAAM,wBAAwB;AAEvB,SAAS,cAAc,OAAsC;AAClE,SACE,OAAO,UAAU,YACjB,MAAM,SAAS,KACf,MAAM,UAAU,4BAChB,sBAAsB,KAAK,KAAK;AAEpC;AAEO,IAAK,kBAAL,kBAAKC,qBAAL;AACL,EAAAA,iBAAA,iBAAc;AACd,EAAAA,iBAAA,iBAAc;AACd,EAAAA,iBAAA,gBAAa;AACb,EAAAA,iBAAA,eAAY;AACZ,EAAAA,iBAAA,sBAAmB;AACnB,EAAAA,iBAAA,UAAO;AACP,EAAAA,iBAAA,eAAY;AACZ,EAAAA,iBAAA,gBAAa;AACb,EAAAA,iBAAA,qBAAkB;AAClB,EAAAA,iBAAA,iBAAc;AAVJ,SAAAA;AAAA,GAAA;AAqBL,IAAK,sBAAL,kBAAKC,yBAAL;AACL,EAAAA,qBAAA,aAAU;AACV,EAAAA,qBAAA,qBAAkB;AAClB,EAAAA,qBAAA,cAAW;AACX,EAAAA,qBAAA,YAAS;AAJC,SAAAA;AAAA,GAAA;;;AC5EZ,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AAE5B,SAAS,OAAO,OAAgD;AAC9D,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,QAAQ,qBAAqB;AACtC;AAEA,SAAS,gBAAgB,QAAwB;AAC/C,SAAO,OAAO,QAAQ,QAAQ,EAAE;AAClC;AAEO,SAAS,cAAc,SAAuC;AACnE,QAAM,WAAW,QAAQ,SAAS,KAAK;AACvC,QAAM,YAAY,QAAQ,UAAU,KAAK;AACzC,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAEA,QAAM,SAAS,gBAAgB,QAAQ,UAAU,oBAAoB;AACrE,QAAM,MAAM,IAAI,IAAI,GAAG,MAAM,UAAU,mBAAmB,QAAQ,CAAC,EAAE;AACrE,QAAM,UAAwD;AAAA,IAC5D,iCAA8B,QAAQ,UAAU;AAAA,IAChD,iCAA8B,QAAQ,UAAU;AAAA,IAChD,+BAA6B,SAAS;AAAA,IACtC,6BAA4B,QAAQ,QAAQ;AAAA,IAC5C,0CAAmC,QAAQ,cAAc;AAAA,IACzD,oBAAuB,QAAQ,IAAI;AAAA,IACnC,6BAA4B,OAAO,QAAQ,QAAQ,CAAC;AAAA,IACpD,+BAA6B,OAAO,QAAQ,SAAS,CAAC;AAAA,IACtD,yCAAkC,OAAO,QAAQ,cAAc,CAAC;AAAA,IAChE,iCAA8B,OAAO,QAAQ,UAAU,CAAC;AAAA,EAC1D;AAEA,aAAW,CAAC,KAAK,KAAK,KAAK,SAAS;AAClC,QAAI,UAAU,UAAa,UAAU,IAAI;AACvC,UAAI,aAAa,IAAI,KAAK,KAAK;AAAA,IACjC;AAAA,EACF;AACA,SAAO,IAAI,SAAS;AACtB;;;ACjDA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;;;ACJA,SAAS,gBAAgB,KAAsB;AACpD,MAAI,IAAI,WAAW,KAAK,IAAI,SAAS,qBAAsB,QAAO;AAClE,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,GAAG;AAC1B,QAAI,OAAO,aAAa,SAAU,QAAO;AACzC,QAAI,OAAO,aAAa,SAAS;AAC/B,aACE,OAAO,aAAa,eAAe,OAAO,aAAa;AAAA,IAE3D;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,wBACd,OACuB;AACvB,QAAM,MAAM,MAAM,IAAI,KAAK;AAC3B,MAAI,CAAC,gBAAgB,GAAG,EAAG,QAAO;AAClC,QAAM,QAAQ,MAAM,OAAO,KAAK;AAChC,SAAO;AAAA,IACL;AAAA,IACA,GAAI,QAAQ,EAAE,OAAO,MAAM,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC;AAAA,IAC9C,GAAI,cAAc,MAAM,IAAI,IAAI,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,EAC1D;AACF;;;ACtBA,IAAM,cAAc,IAAI,IAAY,OAAO,OAAO,cAAc,CAAC;AAEjE,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,SAAS,iBAAiB,OAAyB;AACjD,SAAO,UAAU,UAAa,OAAO,UAAU;AACjD;AAEA,SAAS,iBAAiB,OAAyB;AACjD,SAAO,UAAU,QAAQ,OAAO,UAAU;AAC5C;AAEA,IAAM,mBAAmB,IAAI,IAAY,OAAO,OAAO,mBAAmB,CAAC;AAE3E,SAAS,iBAAiB,OAAyB;AACjD,SAAO,OAAO,UAAU,YAAY,iBAAiB,IAAI,KAAK;AAChE;AAEA,SAAS,gBACP,MACA,SACS;AACT,UAAQ,MAAM;AAAA,IACZ;AACE,aAAO,OAAO,QAAQ,aAAa;AAAA,IACrC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,aAAa,YAC5B,iBAAiB,QAAQ,IAAI,KAC7B,OAAO,QAAQ,aAAa;AAAA,IAEhC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,WAAW,aACzB,QAAQ,eAAe,UACtB,OAAO,QAAQ,eAAe;AAAA,IAEpC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,iBAAiB,QAAQ,IAAI,KAC7B,OAAO,QAAQ,qBAAqB;AAAA,IAExC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,qBAAqB;AAAA,IAExC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,YAAY;AAAA,IAE/B;AACE,aACE,OAAO,QAAQ,SAAS,YACxB,OAAO,QAAQ,YAAY;AAAA,IAE/B;AACE,cACG,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,aAChD,iBAAiB,QAAQ,KAAK,KAC9B,iBAAiB,QAAQ,IAAI,KAC7B,iBAAiB,QAAQ,QAAQ;AAAA,IAErC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,iBAAiB,QAAQ,OAAO,KAChC,iBAAiB,QAAQ,YAAY,KACrC,iBAAiB,QAAQ,MAAM;AAAA,IAEnC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,iBAAiB,QAAQ,OAAO,KAChC,iBAAiB,QAAQ,WAAW,KACpC,iBAAiB,QAAQ,MAAM;AAAA,IAEnC;AACE,aACE,OAAO,QAAQ,aAAa,YAC5B,OAAO,QAAQ,YAAY;AAAA,EAEjC;AACF;AAEO,SAAS,gBAAgB,MAAkC;AAChE,MAAI,CAAC,SAAS,IAAI,EAAG,QAAO;AAC5B,MACE,KAAK,WAAW,wBAChB,KAAK,YAAY,0BACjB,OAAO,KAAK,eAAe,YAC3B,KAAK,WAAW,WAAW,KAC3B,OAAO,KAAK,SAAS,YACrB,CAAC,YAAY,IAAI,KAAK,IAAI,KAC1B,CAAC,SAAS,KAAK,OAAO,GACtB;AACA,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,KAAK;AAClB,MAAI,CAAC,gBAAgB,MAAM,KAAK,OAAO,EAAG,QAAO;AACjD,SAAO;AACT;;;AFjEA,SAAS,YAAY,SAAmC;AACtD,QAAM,cAAc,QAAQ,QAAQ;AACpC,MAAI,CAAC,YAAa;AAClB,QAAM,UAA+B;AAAA,IACnC,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA,QAAQ,QAAQ;AAAA,IAChB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,EACxD;AACA,cAAY,YAAY,SAAS,QAAQ,YAAY;AACvD;AAEO,SAAS,aAAa,SAA8B;AACzD,QAAM,YAAY,OAAiC,IAAI;AACvD,QAAM,eAAe,OAAO,OAAO;AAEnC,YAAU,MAAM;AACd,iBAAa,UAAU;AAAA,EACzB,GAAG,CAAC,OAAO,CAAC;AAEZ,YAAU,MAAM;AACd,UAAM,YAAY,CAAC,UAAiC;AAClD,YAAM,cAAc,UAAU,SAAS;AACvC,UACE,CAAC,eACD,MAAM,WAAW,eACjB,MAAM,WAAW,QAAQ,cACzB;AACA;AAAA,MACF;AACA,YAAM,aAAa,gBAAgB,MAAM,IAAI;AAC7C,UAAI,CAAC,cAAc,WAAW,eAAe,QAAQ,WAAY;AAEjE,YAAM,YAAY,aAAa;AAC/B,gBAAU,UAAU,UAAU;AAC9B,cAAQ,WAAW,MAAM;AAAA,QACvB;AACE,oBAAU,UAAU,UAAU;AAC9B;AAAA,QACF;AACE,oBAAU,WAAW,UAAU;AAC/B;AAAA,QACF;AACE,oBAAU,SAAS,UAAU;AAC7B;AAAA,QACF;AACE,oBAAU,sBAAsB,UAAU;AAC1C;AAAA,QACF;AACE,oBAAU,oBAAoB,UAAU;AACxC;AAAA,QACF;AACE,oBAAU,UAAU,UAAU;AAC9B;AAAA,QACF;AACE,oBAAU,UAAU,UAAU;AAC9B;AAAA,QACF;AACE,oBAAU,iBAAiB,UAAU;AACrC;AAAA,QACF;AACE,oBAAU,sBAAsB,UAAU;AAC1C;AAAA,QACF;AACE,oBAAU,qBAAqB,UAAU;AACzC;AAAA,QACF;AACE,oBAAU,mBAAmB,UAAU;AACvC;AAAA,MACJ;AAAA,IACF;AAEA,WAAO,iBAAiB,WAAW,SAAS;AAC5C,WAAO,MAAM,OAAO,oBAAoB,WAAW,SAAS;AAAA,EAC9D,GAAG,CAAC,QAAQ,YAAY,QAAQ,YAAY,CAAC;AAE7C,QAAM,OAAO;AAAA,IACX,CAAC,QAA4B,YAC3B,YAAY;AAAA,MACV,QAAQ,UAAU;AAAA,MAClB,YAAY,QAAQ;AAAA,MACpB;AAAA,MACA,cAAc,QAAQ;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,IACH,CAAC,QAAQ,YAAY,QAAQ,YAAY;AAAA,EAC3C;AACA,QAAM,QAAQ,YAAY,MAAM,wBAA6B,GAAG,CAAC,IAAI,CAAC;AACtE,QAAM,YAAY;AAAA,IAChB,MAAM,iCAAkC;AAAA,IACxC,CAAC,IAAI;AAAA,EACP;AACA,QAAM,eAAe;AAAA,IACnB,MAAM,uCAAqC;AAAA,IAC3C,CAAC,IAAI;AAAA,EACP;AACA,QAAM,YAAY;AAAA,IAChB,CAAC,UAA0B;AACzB,YAAM,aAAa,wBAAwB,KAAK;AAChD,UAAI,CAAC,WAAY;AACjB,yCAAoC,UAAU;AAAA,IAChD;AAAA,IACA,CAAC,IAAI;AAAA,EACP;AACA,QAAM,aAAa;AAAA,IACjB,MAAM,mCAAmC;AAAA,IACzC,CAAC,IAAI;AAAA,EACP;AACA,QAAM,YAAY,YAAY,MAAM,UAAU,SAAS,CAAC,CAAC;AACzD,QAAM,SAAS;AAAA,IACb,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,YAAY,WAAW,OAAO,WAAW,cAAc,SAAS;AAAA,EACnE;AAEA,sBAAoB,QAAQ,WAAW,MAAM,QAAQ,CAAC,MAAM,CAAC;AAC7D,SAAO,EAAE,WAAW,OAAO;AAC7B;;;AHzCQ;AA3HD,IAAM,uBACX;AAEF,SAAS,UAAU,OAAwD;AACzE,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,OAAO,UAAU,WAAW,GAAG,KAAK,OAAO;AACpD;AAEA,SAAS,UAAU,OAAuB;AACxC,SAAO,IAAI,IAAI,KAAK,EAAE;AACxB;AAEO,IAAM,YAAY;AAAA,EACvB,SAASC,WAAU,OAAO,KAAK;AAC7B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI;AACJ,UAAM,UAAU,MAAM;AACtB,UAAM,aAAaC;AAAA,MACjB,MAAM,OAAO,QAAQ,QAAQ,MAAM,EAAE,CAAC;AAAA,MACtC,CAAC,OAAO;AAAA,IACV;AACA,UAAM,cAAcA,SAAQ,MAAM,UAAU,MAAM,GAAG,CAAC,MAAM,CAAC;AAC7D,UAAM,uBAAuB,gBAAgB;AAC7C,UAAM,aACJ,OAAO,WAAW,cAAc,SAAY,OAAO,SAAS;AAC9D,UAAM,MAAMA;AAAA,MACV,MACE,cAAc;AAAA,QACZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,MACH;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,EAAE,UAAU,IAAI,aAAa;AAAA,MACjC;AAAA,MACA,cAAc;AAAA,MACd,WAAW;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,eAA8B;AAAA,MAClC,OAAO,UAAU,KAAK;AAAA,MACtB,QAAQ,UAAU,MAAM;AAAA,MACxB,GAAG;AAAA,IACL;AACA,UAAM,aAA4B;AAAA,MAChC,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,GAAG;AAAA,IACL;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,OAAO;AAAA,QACP,4BAA0B;AAAA,QAE1B;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,WAAW;AAAA,YACX,OAAO;AAAA,YACP;AAAA,YACA;AAAA,YACA;AAAA,YACA,iBAAe;AAAA,YACf,gBAAe;AAAA;AAAA,QACjB;AAAA;AAAA,IACF;AAAA,EAEJ;AACF;AAEA,UAAU,cAAc;","names":["useMemo","EmbedEventType","EmbedCommandType","EmbedCommandAction","EmbedErrorCode","EmbedQueryParam","EmbedCaptureOutcome","OeMeeting","useMemo"]}
|
|
@@ -8,6 +8,8 @@ OE Meeting cần cấp cho OE.School một application credential gồm:
|
|
|
8
8
|
- `clientSecret`.
|
|
9
9
|
- Scope `rooms:create` để tạo phòng.
|
|
10
10
|
- Scope `rooms:retrieve` để đọc phòng và cấp join token.
|
|
11
|
+
- Scope `transcripts:retrieve` để đọc transcript sau buổi học.
|
|
12
|
+
- Scope `recordings:retrieve` để tải bản ghi sau buổi học.
|
|
11
13
|
- Tùy nhu cầu quản trị: `rooms:update`, `rooms:delete`.
|
|
12
14
|
|
|
13
15
|
Cấu hình ở backend OE.School, ví dụ:
|
|
@@ -123,7 +125,71 @@ Nên dùng identity có namespace, ví dụ `oe-school:user:<userId>`, để tr
|
|
|
123
125
|
identity giữa các hệ thống tích hợp. Không dùng token phiên đăng nhập OE.School
|
|
124
126
|
làm identity.
|
|
125
127
|
|
|
126
|
-
### 2.3.
|
|
128
|
+
### 2.3. Lấy transcript của buổi họp
|
|
129
|
+
|
|
130
|
+
```http
|
|
131
|
+
GET /external-api/v1.0/rooms/:idOrSlug/transcript
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Scope: `transcripts:retrieve`. `:idOrSlug` là room id hoặc slug đã tạo ở 2.1 —
|
|
135
|
+
cùng định danh mà OE.School đã lưu vào bản ghi buổi học.
|
|
136
|
+
|
|
137
|
+
Response mẫu:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"id": "8f1c0f2a-2c9e-4f2f-9a3b-1e6d5b0c7a11",
|
|
142
|
+
"roomId": "c4ac4f15-8417-4e19-a3dd-39de5697c19f",
|
|
143
|
+
"status": "completed",
|
|
144
|
+
"startedBy": null,
|
|
145
|
+
"startedByName": "Nguyen Van A",
|
|
146
|
+
"createdAt": "2026-08-17T02:00:12.000Z",
|
|
147
|
+
"endedAt": "2026-08-17T02:48:31.000Z",
|
|
148
|
+
"segments": [
|
|
149
|
+
{
|
|
150
|
+
"id": "0b0f9d54-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
|
|
151
|
+
"participantIdentity": "oe-school:user:12345",
|
|
152
|
+
"participantName": "Nguyen Van A",
|
|
153
|
+
"text": "Chào cả lớp, hôm nay chúng ta học bài 3.",
|
|
154
|
+
"language": "vi",
|
|
155
|
+
"spokenAt": "2026-08-17T02:00:45.000Z"
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
| Field | Mô tả |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `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. |
|
|
164
|
+
| `segments` | Các câu nói đã chốt, sắp xếp tăng dần theo `spokenAt`. |
|
|
165
|
+
| `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). |
|
|
166
|
+
|
|
167
|
+
Trả `404` khi phòng chưa từng có transcript nào. Transcript là tài sản cấp
|
|
168
|
+
phòng: nếu OE.School dùng lại một room cho nhiều buổi, endpoint trả phiên gần
|
|
169
|
+
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
|
|
170
|
+
slug ở 2.1) để tránh nhập nhằng.
|
|
171
|
+
|
|
172
|
+
Gọi endpoint này sau event `ENDED` (`onEnded` trên React SDK). Agent transcriber
|
|
173
|
+
chốt phiên (`status: "completed"`) khi phòng LiveKit đóng, nên gọi quá sớm sẽ
|
|
174
|
+
nhận được transcript còn `active` và thiếu các câu cuối. Poll tới khi
|
|
175
|
+
`status` là `completed`.
|
|
176
|
+
|
|
177
|
+
### 2.4. Lấy bản ghi của buổi họp
|
|
178
|
+
|
|
179
|
+
```http
|
|
180
|
+
GET /external-api/v1.0/rooms/:idOrSlug/recordings
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Scope: `recordings:retrieve`. Response trả `results` theo thứ tự mới nhất trước.
|
|
184
|
+
Bản ghi có `status: "saved"` và chưa hết hạn sẽ có `downloadUrl` tạm thời;
|
|
185
|
+
recording đang xử lý hoặc đã hết hạn trả `downloadUrl: null`. Hệ thống ngừng
|
|
186
|
+
cho tải sau `RECORDING_EXPIRATION_DAYS`; cấu hình lifecycle S3/MinIO cùng số
|
|
187
|
+
ngày trên prefix `RECORDING_OUTPUT_FOLDER` để file được xóa khỏi storage.
|
|
188
|
+
|
|
189
|
+
Gọi endpoint này từ backend OE.School khi nhận `ENDED`. Egress có thể vẫn
|
|
190
|
+
đang upload; poll tới khi một bản ghi có `status: "saved"` và `downloadUrl`.
|
|
191
|
+
|
|
192
|
+
### 2.5. Các endpoint quản trị khác
|
|
127
193
|
|
|
128
194
|
| Method | Endpoint | Scope |
|
|
129
195
|
|---|---|---|
|
|
@@ -151,6 +217,26 @@ interface JoinTokenResponse {
|
|
|
151
217
|
token: string;
|
|
152
218
|
}
|
|
153
219
|
|
|
220
|
+
interface TranscriptSegment {
|
|
221
|
+
id: string;
|
|
222
|
+
participantIdentity: string | null;
|
|
223
|
+
participantName: string;
|
|
224
|
+
text: string;
|
|
225
|
+
language: string | null;
|
|
226
|
+
spokenAt: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
interface MeetingTranscript {
|
|
230
|
+
id: string;
|
|
231
|
+
roomId: string;
|
|
232
|
+
status: 'active' | 'paused' | 'completed';
|
|
233
|
+
startedBy: string | null;
|
|
234
|
+
startedByName: string | null;
|
|
235
|
+
createdAt: string;
|
|
236
|
+
endedAt: string | null;
|
|
237
|
+
segments: TranscriptSegment[];
|
|
238
|
+
}
|
|
239
|
+
|
|
154
240
|
interface MeetingCredentials {
|
|
155
241
|
apiUrl: string;
|
|
156
242
|
clientId: string;
|
|
@@ -206,6 +292,13 @@ export class OeMeetingApi {
|
|
|
206
292
|
}),
|
|
207
293
|
});
|
|
208
294
|
}
|
|
295
|
+
|
|
296
|
+
getLessonTranscript(roomSlug: string): Promise<MeetingTranscript> {
|
|
297
|
+
return this.request(
|
|
298
|
+
`/external-api/v1.0/rooms/${encodeURIComponent(roomSlug)}/transcript`,
|
|
299
|
+
{ method: 'GET' },
|
|
300
|
+
);
|
|
301
|
+
}
|
|
209
302
|
}
|
|
210
303
|
```
|
|
211
304
|
|
|
@@ -100,6 +100,14 @@ export function OnlineLesson({ lessonId }: { lessonId: string }) {
|
|
|
100
100
|
onReady={(event) => console.info('Meeting ready', event.payload)}
|
|
101
101
|
onJoined={(event) => console.info('User joined', event.payload)}
|
|
102
102
|
onLeft={(event) => console.info('User left', event.payload)}
|
|
103
|
+
onEnded={(event) => {
|
|
104
|
+
void fetch(`/api/lessons/${lessonId}/meeting-ended`, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
credentials: 'include',
|
|
107
|
+
headers: { 'Content-Type': 'application/json' },
|
|
108
|
+
body: JSON.stringify({ roomSlug: event.payload.roomSlug }),
|
|
109
|
+
})
|
|
110
|
+
}}
|
|
103
111
|
onError={onError}
|
|
104
112
|
/>
|
|
105
113
|
);
|
|
@@ -119,6 +127,8 @@ này ở production. Khi chạy local, truyền URL frontend OE Meeting local, v
|
|
|
119
127
|
| `origin` | `string` | `https://meet.ocean.edu.vn` | Origin của OE Meeting. |
|
|
120
128
|
| `autoJoin` | `boolean` | `true` | Phiên bản hiện tại chỉ hỗ trợ `true`. |
|
|
121
129
|
| `hideLeave` | `boolean` | `false` | Ẩn nút rời phòng trong iframe. |
|
|
130
|
+
| `autoTranscribe` | `boolean` | `false` | Tự bật transcript dùng chung khi embed kết nối. |
|
|
131
|
+
| `autoRecord` | `boolean` | `false` | Tự ghi toàn bộ phòng khi embed kết nối. |
|
|
122
132
|
| `userName` | `string` | — | Thông tin tùy chọn truyền vào embed URL. |
|
|
123
133
|
| `externalUserId` | `string` | — | External identity tùy chọn. |
|
|
124
134
|
| `lang` | `string` | — | Ngôn ngữ giao diện. |
|
|
@@ -138,6 +148,9 @@ này ở production. Khi chạy local, truyền URL frontend OE Meeting local, v
|
|
|
138
148
|
| `onParticipantLeft` | callback | — | Một participant khác rời phòng. |
|
|
139
149
|
| `onEnded` | callback | — | Phòng kết thúc. |
|
|
140
150
|
| `onError` | callback | — | Lỗi typed từ embed. |
|
|
151
|
+
| `onTranscriptStarted` | callback | — | Kết quả của `autoTranscribe`. |
|
|
152
|
+
| `onRecordingStarted` | callback | — | Kết quả của `autoRecord`. |
|
|
153
|
+
| `onRecordingSaved` | callback | — | Recording đã upload xong (có thể tải). |
|
|
141
154
|
| `onEvent` | callback | — | Nhận tất cả event protocol. |
|
|
142
155
|
|
|
143
156
|
Không đặt `targetOrigin="*"`. SDK và iframe đều kiểm tra origin, source và
|
|
@@ -165,12 +178,105 @@ interface EmbedEventEnvelope<TPayload> {
|
|
|
165
178
|
| `PARTICIPANT_JOINED` | `identity`, `name?`, `participantCount` | Đã emit |
|
|
166
179
|
| `PARTICIPANT_LEFT` | `identity`, `participantCount` | Đã emit |
|
|
167
180
|
| `ERROR` | `code`, `message` | Đã emit |
|
|
168
|
-
| `ENDED` | `roomSlug`, `endedAt` | Đã
|
|
181
|
+
| `ENDED` | `roomSlug`, `endedAt` | Đã emit khi phòng đóng hoặc người cuối rời |
|
|
169
182
|
| `STAGE_CHANGED` | `url` (`string` \| `null`), `title?`, `tool?`, `openedBy?` | Đã emit |
|
|
183
|
+
| `TRANSCRIPT_STARTED` | `roomSlug`, `outcome`, `transcriptId` (`string` \| `null`), `status` (`string` \| `null`) | Đã emit khi bật `autoTranscribe` |
|
|
184
|
+
| `RECORDING_STARTED` | `roomSlug`, `outcome`, `recordingId` (`string` \| `null`), `status` (`string` \| `null`) | Đã emit khi bật `autoRecord` |
|
|
185
|
+
| `RECORDING_SAVED` | `roomSlug`, `savedAt` | Đã emit khi metadata recording rời `saving` (file đã lên storage) |
|
|
170
186
|
|
|
171
187
|
Có thể dùng `onEvent` để gửi analytics tập trung, nhưng không log join token,
|
|
172
188
|
iframe URL đầy đủ hoặc dữ liệu nhạy cảm.
|
|
173
189
|
|
|
190
|
+
### 4.1. Tự động bật transcript
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
<OeMeeting
|
|
194
|
+
roomSlug={access.roomSlug}
|
|
195
|
+
joinToken={access.joinToken}
|
|
196
|
+
autoTranscribe
|
|
197
|
+
onTranscriptStarted={(event) => {
|
|
198
|
+
console.info('transcript', event.payload.outcome, event.payload.transcriptId);
|
|
199
|
+
}}
|
|
200
|
+
/>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Transcript là tài sản cấp phòng, không phải cấp participant: người đầu tiên kết
|
|
204
|
+
nối bật nó lên, mọi người vào sau nhận `outcome: "already_running"`, và nó vẫn
|
|
205
|
+
chạy khi người bật rời phòng sớm.
|
|
206
|
+
|
|
207
|
+
| `outcome` | Ý nghĩa |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `started` | Lần kết nối này đã bật lên. |
|
|
210
|
+
| `already_running` | Đã chạy sẵn, trạng thái bình thường của mọi người vào sau. |
|
|
211
|
+
| `disabled` | Deployment OE Meeting tắt transcript. |
|
|
212
|
+
| `failed` | Không bật được; kèm theo một event `ERROR`. |
|
|
213
|
+
|
|
214
|
+
Request bật transcript được xác thực bằng chính join token của participant, nên
|
|
215
|
+
OE.School không cần thêm credential nào ở phía trình duyệt. Đổi lại, transcript
|
|
216
|
+
chỉ bắt đầu khi có người thực sự vào phòng: một buổi học không ai vào sẽ không
|
|
217
|
+
có transcript.
|
|
218
|
+
|
|
219
|
+
### 4.2. Tự động ghi hình
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
<OeMeeting
|
|
223
|
+
roomSlug={access.roomSlug}
|
|
224
|
+
joinToken={access.joinToken}
|
|
225
|
+
autoRecord
|
|
226
|
+
onRecordingStarted={(event) => {
|
|
227
|
+
console.info('recording', event.payload.outcome, event.payload.recordingId);
|
|
228
|
+
}}
|
|
229
|
+
/>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Recording cũng là tài sản cấp phòng: người đầu tiên kết nối bật egress,
|
|
233
|
+
mọi người vào sau nhận `outcome: "already_running"`. Local cần MinIO +
|
|
234
|
+
LiveKit Egress (`docker compose --profile s3 up -d`).
|
|
235
|
+
|
|
236
|
+
Lấy transcript và recording sau buổi học qua event `ENDED` — mục 4.3.
|
|
237
|
+
Đừng gọi external API từ trình duyệt: nó cần `X-Client-Secret`.
|
|
238
|
+
|
|
239
|
+
### 4.3. Lấy transcript và recording khi phòng kết thúc
|
|
240
|
+
|
|
241
|
+
`ENDED` khác `LEFT`: `LEFT` là local user rời phòng (có thể buổi học vẫn
|
|
242
|
+
tiếp tục), còn `ENDED` là tín hiệu buổi họp đã đóng. Embed phát `ENDED` khi:
|
|
243
|
+
|
|
244
|
+
- LiveKit đóng/xóa phòng (`ROOM_CLOSED`, `ROOM_DELETED`, `SERVER_SHUTDOWN`), hoặc
|
|
245
|
+
- Người vừa disconnect là human cuối cùng còn lại (agent transcriber không
|
|
246
|
+
được tính).
|
|
247
|
+
|
|
248
|
+
Emit `ENDED` trước `LEFT` để trang cha vẫn nhận được nếu nó unmount iframe
|
|
249
|
+
ngay khi `onLeft`.
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
<OeMeeting
|
|
253
|
+
roomSlug={access.roomSlug}
|
|
254
|
+
joinToken={access.joinToken}
|
|
255
|
+
autoTranscribe
|
|
256
|
+
autoRecord
|
|
257
|
+
onEnded={(event) => {
|
|
258
|
+
void fetch(`/api/lessons/${lessonId}/meeting-ended`, {
|
|
259
|
+
method: 'POST',
|
|
260
|
+
credentials: 'include',
|
|
261
|
+
headers: { 'Content-Type': 'application/json' },
|
|
262
|
+
body: JSON.stringify({ roomSlug: event.payload.roomSlug }),
|
|
263
|
+
})
|
|
264
|
+
}}
|
|
265
|
+
/>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Endpoint cùng origin đó gọi external API từ backend OE.School (cần
|
|
269
|
+
`X-Client-Secret`):
|
|
270
|
+
|
|
271
|
+
- `GET /external-api/v1.0/rooms/:idOrSlug/transcript`
|
|
272
|
+
- `GET /external-api/v1.0/rooms/:idOrSlug/recordings`
|
|
273
|
+
|
|
274
|
+
Transcript có thể còn `active` và recording có thể chưa `saved` ngay lúc
|
|
275
|
+
nhận `ENDED`: agent chốt transcript và Egress upload file khi phòng LiveKit
|
|
276
|
+
đóng. Dùng `onRecordingSaved` khi metadata rời `saving` — lúc đó MP4 đã
|
|
277
|
+
lên storage. Poll `GET .../recordings` tới khi `status` là `saved` nếu chưa
|
|
278
|
+
nhận event này. Xem `BACKEND-INTEGRATION.md` mục 2.3 và 2.4.
|
|
279
|
+
|
|
174
280
|
## 5. Điều khiển phòng từ trang cha
|
|
175
281
|
|
|
176
282
|
```tsx
|
|
@@ -231,6 +337,8 @@ export function MeetingWithControls(props: {
|
|
|
231
337
|
| `PROTOCOL_MISMATCH` | SDK và embed khác version. | Đồng bộ phiên bản SDK/OE Meeting. | Đã khai báo, chưa emit |
|
|
232
338
|
| `STAGE_ORIGIN_DENIED` | Origin URL Stage không nằm allowlist. | Dùng origin đã khai báo (`hostOrigin` / `VITE_STAGE_ALLOWED_ORIGINS`). | Đã emit |
|
|
233
339
|
| `STAGE_URL_INVALID` | URL Stage thiếu hoặc không hợp lệ. | Chỉ `https` (hoặc `http` localhost). | Đã emit |
|
|
340
|
+
| `TRANSCRIPT_START_FAILED` | `autoTranscribe` không bật được transcript. | Không retry từ trang cha; kiểm tra agent transcriber. | Đã emit |
|
|
341
|
+
| `RECORDING_START_FAILED` | `autoRecord` không bật được recording. | Không retry từ trang cha; kiểm tra LiveKit Egress và S3. | Đã emit |
|
|
234
342
|
|
|
235
343
|
## 7. Local development
|
|
236
344
|
|
|
@@ -275,7 +383,8 @@ về đúng origin đó.
|
|
|
275
383
|
- [ ] Có trạng thái loading, forbidden, token expired và connection failed.
|
|
276
384
|
- [ ] Không dùng `targetOrigin="*"`.
|
|
277
385
|
- [ ] Camera/microphone hoạt động trong iframe.
|
|
278
|
-
- [ ] Event join/leave được gửi analytics nếu nghiệp vụ yêu cầu.
|
|
386
|
+
- [ ] Event join/leave/`ENDED` được gửi analytics nếu nghiệp vụ yêu cầu.
|
|
387
|
+
- [ ] `onEnded` gọi backend cùng origin để lấy transcript và recordings.
|
|
279
388
|
|
|
280
389
|
### Kiểm thử nghiệp vụ
|
|
281
390
|
|
|
@@ -294,7 +403,7 @@ về đúng origin đó.
|
|
|
294
403
|
- OE Meeting chưa đồng bộ roster vào database; OE.School chịu trách nhiệm duyệt
|
|
295
404
|
quyền trước mỗi lần cấp token.
|
|
296
405
|
- Chưa có API thu hồi token đã cấp hoặc kick participant theo lesson attendance.
|
|
297
|
-
- Protocol v1 chưa có
|
|
406
|
+
- Protocol v1 chưa có token handshake qua `postMessage`.
|
|
298
407
|
- `autoJoin={false}` chưa có prejoin flow; phiên bản hiện tại yêu cầu auto join.
|
|
299
408
|
- Các lỗi token/access chưa được phân loại riêng; frontend có thể nhận
|
|
300
409
|
`CONNECTION_FAILED` cho các lỗi kết nối liên quan token.
|