@tinyweb_dev/oe-meeting-react 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INTEGRATION.md +5 -5
- package/README.md +5 -3
- package/dist/index.cjs +80 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -5
- package/dist/index.d.ts +34 -5
- package/dist/index.js +75 -6
- package/dist/index.js.map +1 -1
- package/docs/BACKEND-INTEGRATION.md +2 -2
- package/docs/REACT-INTEGRATION.md +11 -6
- package/docs/RELEASE.md +31 -20
- package/package.json +1 -1
package/INTEGRATION.md
CHANGED
|
@@ -10,7 +10,7 @@ SSO bằng join token và trao đổi typed events/commands với iframe.
|
|
|
10
10
|
- [Tích hợp React SDK](docs/REACT-INTEGRATION.md): component `OeMeeting`, props,
|
|
11
11
|
events, commands, xử lý lỗi và local development.
|
|
12
12
|
- [README package](README.md): ví dụ sử dụng nhanh và public API.
|
|
13
|
-
- [Release npm](docs/RELEASE.md):
|
|
13
|
+
- [Release npm](docs/RELEASE.md): Trusted Publishing (OIDC), dry-run và quy trình
|
|
14
14
|
phát hành bằng tag.
|
|
15
15
|
|
|
16
16
|
## Kiến trúc
|
|
@@ -32,7 +32,7 @@ OE Meeting external API
|
|
|
32
32
|
│ Trả roomSlug + joinToken cho browser
|
|
33
33
|
▼
|
|
34
34
|
<OeMeeting roomSlug={roomSlug} joinToken={joinToken} />
|
|
35
|
-
└─ iframe https://meet.
|
|
35
|
+
└─ iframe https://meet.ocean.edu.vn/embed/:roomSlug
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
## Access control hai lớp
|
|
@@ -58,9 +58,9 @@ OE.School hoặc secret manager.
|
|
|
58
58
|
## Production endpoint
|
|
59
59
|
|
|
60
60
|
```text
|
|
61
|
-
Meeting origin: https://meet.
|
|
62
|
-
External API: https://meet.
|
|
63
|
-
Embed route: https://meet.
|
|
61
|
+
Meeting origin: https://meet.ocean.edu.vn
|
|
62
|
+
External API: https://meet.ocean.edu.vn/external-api/v1.0
|
|
63
|
+
Embed route: https://meet.ocean.edu.vn/embed/:roomSlug
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
## Checklist nhanh
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ React SDK để nhúng phòng OE Meeting vào OE.School hoặc hệ thống LMS
|
|
|
7
7
|
- [Tổng quan và checklist](INTEGRATION.md)
|
|
8
8
|
- [Backend OE.School: external API, roster và join token](docs/BACKEND-INTEGRATION.md)
|
|
9
9
|
- [Frontend OE.School: React component, events và commands](docs/REACT-INTEGRATION.md)
|
|
10
|
-
- [Quy trình release npm
|
|
10
|
+
- [Quy trình release npm (OIDC Trusted Publishing)](docs/RELEASE.md)
|
|
11
11
|
|
|
12
12
|
## Ví dụ nhanh
|
|
13
13
|
|
|
@@ -52,5 +52,7 @@ export function OnlineLesson({
|
|
|
52
52
|
|
|
53
53
|
## Imperative API
|
|
54
54
|
|
|
55
|
-
`OeMeetingHandle` cung cấp `leave()`, `toggleMic()`, `toggleCamera()
|
|
56
|
-
`getIframe()`.
|
|
55
|
+
`OeMeetingHandle` cung cấp `leave()`, `toggleMic()`, `toggleCamera()`,
|
|
56
|
+
`openStage()`, `closeStage()` và `getIframe()`. Host giữ nội dung tool
|
|
57
|
+
(trang PDF, nét vẽ); Meeting chỉ sync URL Stage. Host stamp
|
|
58
|
+
`attributes.stage` (`open` \| `follow`) nếu cần hạn chế origin OPEN/CLOSE.
|
package/dist/index.cjs
CHANGED
|
@@ -29,8 +29,13 @@ __export(index_exports, {
|
|
|
29
29
|
EmbedErrorCode: () => EmbedErrorCode,
|
|
30
30
|
EmbedEventType: () => EmbedEventType,
|
|
31
31
|
EmbedQueryParam: () => EmbedQueryParam,
|
|
32
|
+
MAX_STAGE_TOOL_ID_LENGTH: () => MAX_STAGE_TOOL_ID_LENGTH,
|
|
33
|
+
MAX_STAGE_URL_LENGTH: () => MAX_STAGE_URL_LENGTH,
|
|
32
34
|
OeMeeting: () => OeMeeting,
|
|
33
35
|
buildEmbedUrl: () => buildEmbedUrl,
|
|
36
|
+
isStageToolId: () => isStageToolId,
|
|
37
|
+
isValidStageUrl: () => isValidStageUrl,
|
|
38
|
+
normalizeOpenStageInput: () => normalizeOpenStageInput,
|
|
34
39
|
parseEmbedEvent: () => parseEmbedEvent,
|
|
35
40
|
useOeMeeting: () => useOeMeeting
|
|
36
41
|
});
|
|
@@ -42,7 +47,7 @@ var import_react2 = require("react");
|
|
|
42
47
|
// src/embed-protocol.ts
|
|
43
48
|
var EMBED_PROTOCOL_VERSION = 1;
|
|
44
49
|
var EMBED_MESSAGE_SOURCE = "oemeeting";
|
|
45
|
-
var DEFAULT_EMBED_ORIGIN = "https://meet.
|
|
50
|
+
var DEFAULT_EMBED_ORIGIN = "https://meet.ocean.edu.vn";
|
|
46
51
|
var EmbedEventType = /* @__PURE__ */ ((EmbedEventType2) => {
|
|
47
52
|
EmbedEventType2["READY"] = "oemeeting:ready";
|
|
48
53
|
EmbedEventType2["JOINED"] = "oemeeting:joined";
|
|
@@ -51,6 +56,7 @@ var EmbedEventType = /* @__PURE__ */ ((EmbedEventType2) => {
|
|
|
51
56
|
EmbedEventType2["PARTICIPANT_LEFT"] = "oemeeting:participant-left";
|
|
52
57
|
EmbedEventType2["ENDED"] = "oemeeting:ended";
|
|
53
58
|
EmbedEventType2["ERROR"] = "oemeeting:error";
|
|
59
|
+
EmbedEventType2["STAGE_CHANGED"] = "oemeeting:stage-changed";
|
|
54
60
|
return EmbedEventType2;
|
|
55
61
|
})(EmbedEventType || {});
|
|
56
62
|
var EmbedCommandType = /* @__PURE__ */ ((EmbedCommandType2) => {
|
|
@@ -61,6 +67,8 @@ var EmbedCommandAction = /* @__PURE__ */ ((EmbedCommandAction2) => {
|
|
|
61
67
|
EmbedCommandAction2["LEAVE"] = "leave";
|
|
62
68
|
EmbedCommandAction2["TOGGLE_MIC"] = "toggleMic";
|
|
63
69
|
EmbedCommandAction2["TOGGLE_CAMERA"] = "toggleCamera";
|
|
70
|
+
EmbedCommandAction2["OPEN_STAGE"] = "openStage";
|
|
71
|
+
EmbedCommandAction2["CLOSE_STAGE"] = "closeStage";
|
|
64
72
|
return EmbedCommandAction2;
|
|
65
73
|
})(EmbedCommandAction || {});
|
|
66
74
|
var EmbedErrorCode = /* @__PURE__ */ ((EmbedErrorCode2) => {
|
|
@@ -70,9 +78,17 @@ var EmbedErrorCode = /* @__PURE__ */ ((EmbedErrorCode2) => {
|
|
|
70
78
|
EmbedErrorCode2["ROOM_ACCESS_DENIED"] = "ROOM_ACCESS_DENIED";
|
|
71
79
|
EmbedErrorCode2["CONNECTION_FAILED"] = "CONNECTION_FAILED";
|
|
72
80
|
EmbedErrorCode2["PROTOCOL_MISMATCH"] = "PROTOCOL_MISMATCH";
|
|
81
|
+
EmbedErrorCode2["STAGE_ORIGIN_DENIED"] = "STAGE_ORIGIN_DENIED";
|
|
82
|
+
EmbedErrorCode2["STAGE_URL_INVALID"] = "STAGE_URL_INVALID";
|
|
73
83
|
EmbedErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
74
84
|
return EmbedErrorCode2;
|
|
75
85
|
})(EmbedErrorCode || {});
|
|
86
|
+
var MAX_STAGE_URL_LENGTH = 2048;
|
|
87
|
+
var MAX_STAGE_TOOL_ID_LENGTH = 32;
|
|
88
|
+
var STAGE_TOOL_ID_PATTERN = /^[a-z][a-z0-9._-]{0,31}$/;
|
|
89
|
+
function isStageToolId(value) {
|
|
90
|
+
return typeof value === "string" && value.length > 0 && value.length <= MAX_STAGE_TOOL_ID_LENGTH && STAGE_TOOL_ID_PATTERN.test(value);
|
|
91
|
+
}
|
|
76
92
|
var EmbedQueryParam = /* @__PURE__ */ ((EmbedQueryParam2) => {
|
|
77
93
|
EmbedQueryParam2["INSTANCE_ID"] = "instanceId";
|
|
78
94
|
EmbedQueryParam2["HOST_ORIGIN"] = "hostOrigin";
|
|
@@ -127,6 +143,31 @@ function buildEmbedUrl(options) {
|
|
|
127
143
|
// src/useOeMeeting.ts
|
|
128
144
|
var import_react = require("react");
|
|
129
145
|
|
|
146
|
+
// src/openStage.ts
|
|
147
|
+
function isValidStageUrl(url) {
|
|
148
|
+
if (url.length === 0 || url.length > MAX_STAGE_URL_LENGTH) return false;
|
|
149
|
+
try {
|
|
150
|
+
const parsed = new URL(url);
|
|
151
|
+
if (parsed.protocol === "https:") return true;
|
|
152
|
+
if (parsed.protocol === "http:") {
|
|
153
|
+
return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function normalizeOpenStageInput(input) {
|
|
161
|
+
const url = input.url.trim();
|
|
162
|
+
if (!isValidStageUrl(url)) return null;
|
|
163
|
+
const title = input.title?.trim();
|
|
164
|
+
return {
|
|
165
|
+
url,
|
|
166
|
+
...title ? { title: title.slice(0, 200) } : {},
|
|
167
|
+
...isStageToolId(input.tool) ? { tool: input.tool } : {}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
130
171
|
// src/parseEmbedEvent.ts
|
|
131
172
|
var EVENT_TYPES = new Set(Object.values(EmbedEventType));
|
|
132
173
|
function isRecord(value) {
|
|
@@ -151,6 +192,8 @@ function hasValidPayload(type, payload) {
|
|
|
151
192
|
return typeof payload.roomSlug === "string" && typeof payload.endedAt === "string";
|
|
152
193
|
case "oemeeting:error" /* ERROR */:
|
|
153
194
|
return typeof payload.code === "string" && typeof payload.message === "string";
|
|
195
|
+
case "oemeeting:stage-changed" /* STAGE_CHANGED */:
|
|
196
|
+
return (payload.url === null || typeof payload.url === "string") && isOptionalString(payload.title) && isOptionalString(payload.tool) && isOptionalString(payload.openedBy);
|
|
154
197
|
}
|
|
155
198
|
}
|
|
156
199
|
function parseEmbedEvent(data) {
|
|
@@ -172,7 +215,8 @@ function postCommand(options) {
|
|
|
172
215
|
version: EMBED_PROTOCOL_VERSION,
|
|
173
216
|
instanceId: options.instanceId,
|
|
174
217
|
type: "oemeeting:command" /* COMMAND */,
|
|
175
|
-
action: options.action
|
|
218
|
+
action: options.action,
|
|
219
|
+
...options.payload ? { payload: options.payload } : {}
|
|
176
220
|
};
|
|
177
221
|
frameWindow.postMessage(message, options.targetOrigin);
|
|
178
222
|
}
|
|
@@ -214,17 +258,21 @@ function useOeMeeting(options) {
|
|
|
214
258
|
case "oemeeting:error" /* ERROR */:
|
|
215
259
|
callbacks.onError?.(embedEvent);
|
|
216
260
|
break;
|
|
261
|
+
case "oemeeting:stage-changed" /* STAGE_CHANGED */:
|
|
262
|
+
callbacks.onStageChanged?.(embedEvent);
|
|
263
|
+
break;
|
|
217
264
|
}
|
|
218
265
|
};
|
|
219
266
|
window.addEventListener("message", onMessage);
|
|
220
267
|
return () => window.removeEventListener("message", onMessage);
|
|
221
268
|
}, [options.instanceId, options.targetOrigin]);
|
|
222
269
|
const send = (0, import_react.useCallback)(
|
|
223
|
-
(action) => postCommand({
|
|
270
|
+
(action, payload) => postCommand({
|
|
224
271
|
iframe: iframeRef.current,
|
|
225
272
|
instanceId: options.instanceId,
|
|
226
273
|
action,
|
|
227
|
-
targetOrigin: options.targetOrigin
|
|
274
|
+
targetOrigin: options.targetOrigin,
|
|
275
|
+
payload
|
|
228
276
|
}),
|
|
229
277
|
[options.instanceId, options.targetOrigin]
|
|
230
278
|
);
|
|
@@ -237,10 +285,29 @@ function useOeMeeting(options) {
|
|
|
237
285
|
() => send("toggleCamera" /* TOGGLE_CAMERA */),
|
|
238
286
|
[send]
|
|
239
287
|
);
|
|
288
|
+
const openStage = (0, import_react.useCallback)(
|
|
289
|
+
(input) => {
|
|
290
|
+
const normalized = normalizeOpenStageInput(input);
|
|
291
|
+
if (!normalized) return;
|
|
292
|
+
send("openStage" /* OPEN_STAGE */, normalized);
|
|
293
|
+
},
|
|
294
|
+
[send]
|
|
295
|
+
);
|
|
296
|
+
const closeStage = (0, import_react.useCallback)(
|
|
297
|
+
() => send("closeStage" /* CLOSE_STAGE */),
|
|
298
|
+
[send]
|
|
299
|
+
);
|
|
240
300
|
const getIframe = (0, import_react.useCallback)(() => iframeRef.current, []);
|
|
241
301
|
const handle = (0, import_react.useMemo)(
|
|
242
|
-
() => ({
|
|
243
|
-
|
|
302
|
+
() => ({
|
|
303
|
+
leave,
|
|
304
|
+
toggleMic,
|
|
305
|
+
toggleCamera,
|
|
306
|
+
openStage,
|
|
307
|
+
closeStage,
|
|
308
|
+
getIframe
|
|
309
|
+
}),
|
|
310
|
+
[closeStage, getIframe, leave, openStage, toggleCamera, toggleMic]
|
|
244
311
|
);
|
|
245
312
|
(0, import_react.useImperativeHandle)(options.handleRef, () => handle, [handle]);
|
|
246
313
|
return { iframeRef, handle };
|
|
@@ -283,6 +350,7 @@ var OeMeeting = (0, import_react2.forwardRef)(
|
|
|
283
350
|
onParticipantLeft,
|
|
284
351
|
onEnded,
|
|
285
352
|
onError,
|
|
353
|
+
onStageChanged,
|
|
286
354
|
onEvent
|
|
287
355
|
} = props;
|
|
288
356
|
const reactId = (0, import_react2.useId)();
|
|
@@ -330,6 +398,7 @@ var OeMeeting = (0, import_react2.forwardRef)(
|
|
|
330
398
|
onParticipantLeft,
|
|
331
399
|
onEnded,
|
|
332
400
|
onError,
|
|
401
|
+
onStageChanged,
|
|
333
402
|
onEvent
|
|
334
403
|
});
|
|
335
404
|
const wrapperStyle = {
|
|
@@ -379,8 +448,13 @@ OeMeeting.displayName = "OeMeeting";
|
|
|
379
448
|
EmbedErrorCode,
|
|
380
449
|
EmbedEventType,
|
|
381
450
|
EmbedQueryParam,
|
|
451
|
+
MAX_STAGE_TOOL_ID_LENGTH,
|
|
452
|
+
MAX_STAGE_URL_LENGTH,
|
|
382
453
|
OeMeeting,
|
|
383
454
|
buildEmbedUrl,
|
|
455
|
+
isStageToolId,
|
|
456
|
+
isValidStageUrl,
|
|
457
|
+
normalizeOpenStageInput,
|
|
384
458
|
parseEmbedEvent,
|
|
385
459
|
useOeMeeting
|
|
386
460
|
});
|
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/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} from './embed-protocol';\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 OeMeetingHandle,\n OeMeetingProps,\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 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 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.oceanedu.org';\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}\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}\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 UNKNOWN = 'UNKNOWN',\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 { parseEmbedEvent } from './parseEmbedEvent';\nimport type {\n EmbedCommandMessage,\n OeMeetingHandle,\n OeMeetingProps,\n} from './types';\n\ntype UseOeMeetingOptions = Pick<\n OeMeetingProps,\n | 'onReady'\n | 'onJoined'\n | 'onLeft'\n | 'onParticipantJoined'\n | 'onParticipantLeft'\n | 'onEnded'\n | 'onError'\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}\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 };\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 }\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) =>\n postCommand({\n iframe: iframeRef.current,\n instanceId: options.instanceId,\n action,\n targetOrigin: options.targetOrigin,\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 getIframe = useCallback(() => iframeRef.current, []);\n const handle = useMemo<OeMeetingHandle>(\n () => ({ leave, toggleMic, toggleCamera, getIframe }),\n [getIframe, leave, toggleCamera, toggleMic],\n );\n\n useImperativeHandle(options.handleRef, () => handle, [handle]);\n return { iframeRef, handle };\n}\n","import {\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n 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 }\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 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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -3,7 +3,7 @@ import { CSSProperties, Ref } from 'react';
|
|
|
3
3
|
|
|
4
4
|
declare const EMBED_PROTOCOL_VERSION: 1;
|
|
5
5
|
declare const EMBED_MESSAGE_SOURCE: "oemeeting";
|
|
6
|
-
declare const DEFAULT_EMBED_ORIGIN = "https://meet.
|
|
6
|
+
declare const DEFAULT_EMBED_ORIGIN = "https://meet.ocean.edu.vn";
|
|
7
7
|
declare enum EmbedEventType {
|
|
8
8
|
READY = "oemeeting:ready",
|
|
9
9
|
JOINED = "oemeeting:joined",
|
|
@@ -11,7 +11,8 @@ declare enum EmbedEventType {
|
|
|
11
11
|
PARTICIPANT_JOINED = "oemeeting:participant-joined",
|
|
12
12
|
PARTICIPANT_LEFT = "oemeeting:participant-left",
|
|
13
13
|
ENDED = "oemeeting:ended",
|
|
14
|
-
ERROR = "oemeeting:error"
|
|
14
|
+
ERROR = "oemeeting:error",
|
|
15
|
+
STAGE_CHANGED = "oemeeting:stage-changed"
|
|
15
16
|
}
|
|
16
17
|
declare enum EmbedCommandType {
|
|
17
18
|
COMMAND = "oemeeting:command"
|
|
@@ -19,7 +20,9 @@ declare enum EmbedCommandType {
|
|
|
19
20
|
declare enum EmbedCommandAction {
|
|
20
21
|
LEAVE = "leave",
|
|
21
22
|
TOGGLE_MIC = "toggleMic",
|
|
22
|
-
TOGGLE_CAMERA = "toggleCamera"
|
|
23
|
+
TOGGLE_CAMERA = "toggleCamera",
|
|
24
|
+
OPEN_STAGE = "openStage",
|
|
25
|
+
CLOSE_STAGE = "closeStage"
|
|
23
26
|
}
|
|
24
27
|
declare enum EmbedErrorCode {
|
|
25
28
|
TOKEN_INVALID = "TOKEN_INVALID",
|
|
@@ -28,8 +31,15 @@ declare enum EmbedErrorCode {
|
|
|
28
31
|
ROOM_ACCESS_DENIED = "ROOM_ACCESS_DENIED",
|
|
29
32
|
CONNECTION_FAILED = "CONNECTION_FAILED",
|
|
30
33
|
PROTOCOL_MISMATCH = "PROTOCOL_MISMATCH",
|
|
34
|
+
STAGE_ORIGIN_DENIED = "STAGE_ORIGIN_DENIED",
|
|
35
|
+
STAGE_URL_INVALID = "STAGE_URL_INVALID",
|
|
31
36
|
UNKNOWN = "UNKNOWN"
|
|
32
37
|
}
|
|
38
|
+
/** Open host-owned tool id. Meeting does not catalog values. */
|
|
39
|
+
type StageToolId = string;
|
|
40
|
+
declare const MAX_STAGE_URL_LENGTH = 2048;
|
|
41
|
+
declare const MAX_STAGE_TOOL_ID_LENGTH = 32;
|
|
42
|
+
declare function isStageToolId(value: unknown): value is StageToolId;
|
|
33
43
|
declare enum EmbedQueryParam {
|
|
34
44
|
INSTANCE_ID = "instanceId",
|
|
35
45
|
HOST_ORIGIN = "hostOrigin",
|
|
@@ -78,6 +88,17 @@ interface EmbedErrorPayload {
|
|
|
78
88
|
code: EmbedErrorCode | string;
|
|
79
89
|
message: string;
|
|
80
90
|
}
|
|
91
|
+
interface EmbedStageChangedPayload {
|
|
92
|
+
url: string | null;
|
|
93
|
+
title?: string;
|
|
94
|
+
tool?: StageToolId;
|
|
95
|
+
openedBy?: string;
|
|
96
|
+
}
|
|
97
|
+
interface OpenStageInput {
|
|
98
|
+
url: string;
|
|
99
|
+
title?: string;
|
|
100
|
+
tool?: StageToolId;
|
|
101
|
+
}
|
|
81
102
|
type EmbedEventPayloadMap = {
|
|
82
103
|
[EmbedEventType.READY]: EmbedReadyPayload;
|
|
83
104
|
[EmbedEventType.JOINED]: EmbedJoinedPayload;
|
|
@@ -86,6 +107,7 @@ type EmbedEventPayloadMap = {
|
|
|
86
107
|
[EmbedEventType.PARTICIPANT_LEFT]: EmbedParticipantLeftPayload;
|
|
87
108
|
[EmbedEventType.ENDED]: EmbedEndedPayload;
|
|
88
109
|
[EmbedEventType.ERROR]: EmbedErrorPayload;
|
|
110
|
+
[EmbedEventType.STAGE_CHANGED]: EmbedStageChangedPayload;
|
|
89
111
|
};
|
|
90
112
|
type EmbedEventByType<T extends EmbedEventType> = EmbedMessageBase & {
|
|
91
113
|
type: T;
|
|
@@ -100,11 +122,14 @@ type EmbedEventOf<T extends EmbedEventType> = Extract<EmbedEvent, {
|
|
|
100
122
|
type EmbedCommandMessage = EmbedMessageBase & {
|
|
101
123
|
type: 'oemeeting:command';
|
|
102
124
|
action: EmbedCommandAction;
|
|
125
|
+
payload?: OpenStageInput;
|
|
103
126
|
};
|
|
104
127
|
interface OeMeetingHandle {
|
|
105
128
|
leave: () => void;
|
|
106
129
|
toggleMic: () => void;
|
|
107
130
|
toggleCamera: () => void;
|
|
131
|
+
openStage: (input: OpenStageInput) => void;
|
|
132
|
+
closeStage: () => void;
|
|
108
133
|
getIframe: () => HTMLIFrameElement | null;
|
|
109
134
|
}
|
|
110
135
|
interface OeMeetingProps {
|
|
@@ -132,6 +157,7 @@ interface OeMeetingProps {
|
|
|
132
157
|
onParticipantLeft?: (event: EmbedEventOf<EmbedEventType.PARTICIPANT_LEFT>) => void;
|
|
133
158
|
onEnded?: (event: EmbedEventOf<EmbedEventType.ENDED>) => void;
|
|
134
159
|
onError?: (event: EmbedEventOf<EmbedEventType.ERROR>) => void;
|
|
160
|
+
onStageChanged?: (event: EmbedEventOf<EmbedEventType.STAGE_CHANGED>) => void;
|
|
135
161
|
onEvent?: (event: EmbedEvent) => void;
|
|
136
162
|
}
|
|
137
163
|
interface BuildEmbedUrlOptions {
|
|
@@ -150,7 +176,7 @@ interface BuildEmbedUrlOptions {
|
|
|
150
176
|
declare const DEFAULT_IFRAME_ALLOW = "camera; microphone; fullscreen; autoplay; display-capture; clipboard-write; encrypted-media";
|
|
151
177
|
declare const OeMeeting: react.ForwardRefExoticComponent<OeMeetingProps & react.RefAttributes<OeMeetingHandle>>;
|
|
152
178
|
|
|
153
|
-
type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onEvent'> & {
|
|
179
|
+
type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onStageChanged' | 'onEvent'> & {
|
|
154
180
|
instanceId: string;
|
|
155
181
|
targetOrigin: string;
|
|
156
182
|
handleRef?: Ref<OeMeetingHandle>;
|
|
@@ -164,4 +190,7 @@ declare function buildEmbedUrl(options: BuildEmbedUrlOptions): string;
|
|
|
164
190
|
|
|
165
191
|
declare function parseEmbedEvent(data: unknown): EmbedEvent | null;
|
|
166
192
|
|
|
167
|
-
|
|
193
|
+
declare function isValidStageUrl(url: string): boolean;
|
|
194
|
+
declare function normalizeOpenStageInput(input: OpenStageInput): OpenStageInput | null;
|
|
195
|
+
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { CSSProperties, Ref } from 'react';
|
|
|
3
3
|
|
|
4
4
|
declare const EMBED_PROTOCOL_VERSION: 1;
|
|
5
5
|
declare const EMBED_MESSAGE_SOURCE: "oemeeting";
|
|
6
|
-
declare const DEFAULT_EMBED_ORIGIN = "https://meet.
|
|
6
|
+
declare const DEFAULT_EMBED_ORIGIN = "https://meet.ocean.edu.vn";
|
|
7
7
|
declare enum EmbedEventType {
|
|
8
8
|
READY = "oemeeting:ready",
|
|
9
9
|
JOINED = "oemeeting:joined",
|
|
@@ -11,7 +11,8 @@ declare enum EmbedEventType {
|
|
|
11
11
|
PARTICIPANT_JOINED = "oemeeting:participant-joined",
|
|
12
12
|
PARTICIPANT_LEFT = "oemeeting:participant-left",
|
|
13
13
|
ENDED = "oemeeting:ended",
|
|
14
|
-
ERROR = "oemeeting:error"
|
|
14
|
+
ERROR = "oemeeting:error",
|
|
15
|
+
STAGE_CHANGED = "oemeeting:stage-changed"
|
|
15
16
|
}
|
|
16
17
|
declare enum EmbedCommandType {
|
|
17
18
|
COMMAND = "oemeeting:command"
|
|
@@ -19,7 +20,9 @@ declare enum EmbedCommandType {
|
|
|
19
20
|
declare enum EmbedCommandAction {
|
|
20
21
|
LEAVE = "leave",
|
|
21
22
|
TOGGLE_MIC = "toggleMic",
|
|
22
|
-
TOGGLE_CAMERA = "toggleCamera"
|
|
23
|
+
TOGGLE_CAMERA = "toggleCamera",
|
|
24
|
+
OPEN_STAGE = "openStage",
|
|
25
|
+
CLOSE_STAGE = "closeStage"
|
|
23
26
|
}
|
|
24
27
|
declare enum EmbedErrorCode {
|
|
25
28
|
TOKEN_INVALID = "TOKEN_INVALID",
|
|
@@ -28,8 +31,15 @@ declare enum EmbedErrorCode {
|
|
|
28
31
|
ROOM_ACCESS_DENIED = "ROOM_ACCESS_DENIED",
|
|
29
32
|
CONNECTION_FAILED = "CONNECTION_FAILED",
|
|
30
33
|
PROTOCOL_MISMATCH = "PROTOCOL_MISMATCH",
|
|
34
|
+
STAGE_ORIGIN_DENIED = "STAGE_ORIGIN_DENIED",
|
|
35
|
+
STAGE_URL_INVALID = "STAGE_URL_INVALID",
|
|
31
36
|
UNKNOWN = "UNKNOWN"
|
|
32
37
|
}
|
|
38
|
+
/** Open host-owned tool id. Meeting does not catalog values. */
|
|
39
|
+
type StageToolId = string;
|
|
40
|
+
declare const MAX_STAGE_URL_LENGTH = 2048;
|
|
41
|
+
declare const MAX_STAGE_TOOL_ID_LENGTH = 32;
|
|
42
|
+
declare function isStageToolId(value: unknown): value is StageToolId;
|
|
33
43
|
declare enum EmbedQueryParam {
|
|
34
44
|
INSTANCE_ID = "instanceId",
|
|
35
45
|
HOST_ORIGIN = "hostOrigin",
|
|
@@ -78,6 +88,17 @@ interface EmbedErrorPayload {
|
|
|
78
88
|
code: EmbedErrorCode | string;
|
|
79
89
|
message: string;
|
|
80
90
|
}
|
|
91
|
+
interface EmbedStageChangedPayload {
|
|
92
|
+
url: string | null;
|
|
93
|
+
title?: string;
|
|
94
|
+
tool?: StageToolId;
|
|
95
|
+
openedBy?: string;
|
|
96
|
+
}
|
|
97
|
+
interface OpenStageInput {
|
|
98
|
+
url: string;
|
|
99
|
+
title?: string;
|
|
100
|
+
tool?: StageToolId;
|
|
101
|
+
}
|
|
81
102
|
type EmbedEventPayloadMap = {
|
|
82
103
|
[EmbedEventType.READY]: EmbedReadyPayload;
|
|
83
104
|
[EmbedEventType.JOINED]: EmbedJoinedPayload;
|
|
@@ -86,6 +107,7 @@ type EmbedEventPayloadMap = {
|
|
|
86
107
|
[EmbedEventType.PARTICIPANT_LEFT]: EmbedParticipantLeftPayload;
|
|
87
108
|
[EmbedEventType.ENDED]: EmbedEndedPayload;
|
|
88
109
|
[EmbedEventType.ERROR]: EmbedErrorPayload;
|
|
110
|
+
[EmbedEventType.STAGE_CHANGED]: EmbedStageChangedPayload;
|
|
89
111
|
};
|
|
90
112
|
type EmbedEventByType<T extends EmbedEventType> = EmbedMessageBase & {
|
|
91
113
|
type: T;
|
|
@@ -100,11 +122,14 @@ type EmbedEventOf<T extends EmbedEventType> = Extract<EmbedEvent, {
|
|
|
100
122
|
type EmbedCommandMessage = EmbedMessageBase & {
|
|
101
123
|
type: 'oemeeting:command';
|
|
102
124
|
action: EmbedCommandAction;
|
|
125
|
+
payload?: OpenStageInput;
|
|
103
126
|
};
|
|
104
127
|
interface OeMeetingHandle {
|
|
105
128
|
leave: () => void;
|
|
106
129
|
toggleMic: () => void;
|
|
107
130
|
toggleCamera: () => void;
|
|
131
|
+
openStage: (input: OpenStageInput) => void;
|
|
132
|
+
closeStage: () => void;
|
|
108
133
|
getIframe: () => HTMLIFrameElement | null;
|
|
109
134
|
}
|
|
110
135
|
interface OeMeetingProps {
|
|
@@ -132,6 +157,7 @@ interface OeMeetingProps {
|
|
|
132
157
|
onParticipantLeft?: (event: EmbedEventOf<EmbedEventType.PARTICIPANT_LEFT>) => void;
|
|
133
158
|
onEnded?: (event: EmbedEventOf<EmbedEventType.ENDED>) => void;
|
|
134
159
|
onError?: (event: EmbedEventOf<EmbedEventType.ERROR>) => void;
|
|
160
|
+
onStageChanged?: (event: EmbedEventOf<EmbedEventType.STAGE_CHANGED>) => void;
|
|
135
161
|
onEvent?: (event: EmbedEvent) => void;
|
|
136
162
|
}
|
|
137
163
|
interface BuildEmbedUrlOptions {
|
|
@@ -150,7 +176,7 @@ interface BuildEmbedUrlOptions {
|
|
|
150
176
|
declare const DEFAULT_IFRAME_ALLOW = "camera; microphone; fullscreen; autoplay; display-capture; clipboard-write; encrypted-media";
|
|
151
177
|
declare const OeMeeting: react.ForwardRefExoticComponent<OeMeetingProps & react.RefAttributes<OeMeetingHandle>>;
|
|
152
178
|
|
|
153
|
-
type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onEvent'> & {
|
|
179
|
+
type UseOeMeetingOptions = Pick<OeMeetingProps, 'onReady' | 'onJoined' | 'onLeft' | 'onParticipantJoined' | 'onParticipantLeft' | 'onEnded' | 'onError' | 'onStageChanged' | 'onEvent'> & {
|
|
154
180
|
instanceId: string;
|
|
155
181
|
targetOrigin: string;
|
|
156
182
|
handleRef?: Ref<OeMeetingHandle>;
|
|
@@ -164,4 +190,7 @@ declare function buildEmbedUrl(options: BuildEmbedUrlOptions): string;
|
|
|
164
190
|
|
|
165
191
|
declare function parseEmbedEvent(data: unknown): EmbedEvent | null;
|
|
166
192
|
|
|
167
|
-
|
|
193
|
+
declare function isValidStageUrl(url: string): boolean;
|
|
194
|
+
declare function normalizeOpenStageInput(input: OpenStageInput): OpenStageInput | null;
|
|
195
|
+
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
// src/embed-protocol.ts
|
|
9
9
|
var EMBED_PROTOCOL_VERSION = 1;
|
|
10
10
|
var EMBED_MESSAGE_SOURCE = "oemeeting";
|
|
11
|
-
var DEFAULT_EMBED_ORIGIN = "https://meet.
|
|
11
|
+
var DEFAULT_EMBED_ORIGIN = "https://meet.ocean.edu.vn";
|
|
12
12
|
var EmbedEventType = /* @__PURE__ */ ((EmbedEventType2) => {
|
|
13
13
|
EmbedEventType2["READY"] = "oemeeting:ready";
|
|
14
14
|
EmbedEventType2["JOINED"] = "oemeeting:joined";
|
|
@@ -17,6 +17,7 @@ var EmbedEventType = /* @__PURE__ */ ((EmbedEventType2) => {
|
|
|
17
17
|
EmbedEventType2["PARTICIPANT_LEFT"] = "oemeeting:participant-left";
|
|
18
18
|
EmbedEventType2["ENDED"] = "oemeeting:ended";
|
|
19
19
|
EmbedEventType2["ERROR"] = "oemeeting:error";
|
|
20
|
+
EmbedEventType2["STAGE_CHANGED"] = "oemeeting:stage-changed";
|
|
20
21
|
return EmbedEventType2;
|
|
21
22
|
})(EmbedEventType || {});
|
|
22
23
|
var EmbedCommandType = /* @__PURE__ */ ((EmbedCommandType2) => {
|
|
@@ -27,6 +28,8 @@ var EmbedCommandAction = /* @__PURE__ */ ((EmbedCommandAction2) => {
|
|
|
27
28
|
EmbedCommandAction2["LEAVE"] = "leave";
|
|
28
29
|
EmbedCommandAction2["TOGGLE_MIC"] = "toggleMic";
|
|
29
30
|
EmbedCommandAction2["TOGGLE_CAMERA"] = "toggleCamera";
|
|
31
|
+
EmbedCommandAction2["OPEN_STAGE"] = "openStage";
|
|
32
|
+
EmbedCommandAction2["CLOSE_STAGE"] = "closeStage";
|
|
30
33
|
return EmbedCommandAction2;
|
|
31
34
|
})(EmbedCommandAction || {});
|
|
32
35
|
var EmbedErrorCode = /* @__PURE__ */ ((EmbedErrorCode2) => {
|
|
@@ -36,9 +39,17 @@ var EmbedErrorCode = /* @__PURE__ */ ((EmbedErrorCode2) => {
|
|
|
36
39
|
EmbedErrorCode2["ROOM_ACCESS_DENIED"] = "ROOM_ACCESS_DENIED";
|
|
37
40
|
EmbedErrorCode2["CONNECTION_FAILED"] = "CONNECTION_FAILED";
|
|
38
41
|
EmbedErrorCode2["PROTOCOL_MISMATCH"] = "PROTOCOL_MISMATCH";
|
|
42
|
+
EmbedErrorCode2["STAGE_ORIGIN_DENIED"] = "STAGE_ORIGIN_DENIED";
|
|
43
|
+
EmbedErrorCode2["STAGE_URL_INVALID"] = "STAGE_URL_INVALID";
|
|
39
44
|
EmbedErrorCode2["UNKNOWN"] = "UNKNOWN";
|
|
40
45
|
return EmbedErrorCode2;
|
|
41
46
|
})(EmbedErrorCode || {});
|
|
47
|
+
var MAX_STAGE_URL_LENGTH = 2048;
|
|
48
|
+
var MAX_STAGE_TOOL_ID_LENGTH = 32;
|
|
49
|
+
var STAGE_TOOL_ID_PATTERN = /^[a-z][a-z0-9._-]{0,31}$/;
|
|
50
|
+
function isStageToolId(value) {
|
|
51
|
+
return typeof value === "string" && value.length > 0 && value.length <= MAX_STAGE_TOOL_ID_LENGTH && STAGE_TOOL_ID_PATTERN.test(value);
|
|
52
|
+
}
|
|
42
53
|
var EmbedQueryParam = /* @__PURE__ */ ((EmbedQueryParam2) => {
|
|
43
54
|
EmbedQueryParam2["INSTANCE_ID"] = "instanceId";
|
|
44
55
|
EmbedQueryParam2["HOST_ORIGIN"] = "hostOrigin";
|
|
@@ -99,6 +110,31 @@ import {
|
|
|
99
110
|
useRef
|
|
100
111
|
} from "react";
|
|
101
112
|
|
|
113
|
+
// src/openStage.ts
|
|
114
|
+
function isValidStageUrl(url) {
|
|
115
|
+
if (url.length === 0 || url.length > MAX_STAGE_URL_LENGTH) return false;
|
|
116
|
+
try {
|
|
117
|
+
const parsed = new URL(url);
|
|
118
|
+
if (parsed.protocol === "https:") return true;
|
|
119
|
+
if (parsed.protocol === "http:") {
|
|
120
|
+
return parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1";
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function normalizeOpenStageInput(input) {
|
|
128
|
+
const url = input.url.trim();
|
|
129
|
+
if (!isValidStageUrl(url)) return null;
|
|
130
|
+
const title = input.title?.trim();
|
|
131
|
+
return {
|
|
132
|
+
url,
|
|
133
|
+
...title ? { title: title.slice(0, 200) } : {},
|
|
134
|
+
...isStageToolId(input.tool) ? { tool: input.tool } : {}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
102
138
|
// src/parseEmbedEvent.ts
|
|
103
139
|
var EVENT_TYPES = new Set(Object.values(EmbedEventType));
|
|
104
140
|
function isRecord(value) {
|
|
@@ -123,6 +159,8 @@ function hasValidPayload(type, payload) {
|
|
|
123
159
|
return typeof payload.roomSlug === "string" && typeof payload.endedAt === "string";
|
|
124
160
|
case "oemeeting:error" /* ERROR */:
|
|
125
161
|
return typeof payload.code === "string" && typeof payload.message === "string";
|
|
162
|
+
case "oemeeting:stage-changed" /* STAGE_CHANGED */:
|
|
163
|
+
return (payload.url === null || typeof payload.url === "string") && isOptionalString(payload.title) && isOptionalString(payload.tool) && isOptionalString(payload.openedBy);
|
|
126
164
|
}
|
|
127
165
|
}
|
|
128
166
|
function parseEmbedEvent(data) {
|
|
@@ -144,7 +182,8 @@ function postCommand(options) {
|
|
|
144
182
|
version: EMBED_PROTOCOL_VERSION,
|
|
145
183
|
instanceId: options.instanceId,
|
|
146
184
|
type: "oemeeting:command" /* COMMAND */,
|
|
147
|
-
action: options.action
|
|
185
|
+
action: options.action,
|
|
186
|
+
...options.payload ? { payload: options.payload } : {}
|
|
148
187
|
};
|
|
149
188
|
frameWindow.postMessage(message, options.targetOrigin);
|
|
150
189
|
}
|
|
@@ -186,17 +225,21 @@ function useOeMeeting(options) {
|
|
|
186
225
|
case "oemeeting:error" /* ERROR */:
|
|
187
226
|
callbacks.onError?.(embedEvent);
|
|
188
227
|
break;
|
|
228
|
+
case "oemeeting:stage-changed" /* STAGE_CHANGED */:
|
|
229
|
+
callbacks.onStageChanged?.(embedEvent);
|
|
230
|
+
break;
|
|
189
231
|
}
|
|
190
232
|
};
|
|
191
233
|
window.addEventListener("message", onMessage);
|
|
192
234
|
return () => window.removeEventListener("message", onMessage);
|
|
193
235
|
}, [options.instanceId, options.targetOrigin]);
|
|
194
236
|
const send = useCallback(
|
|
195
|
-
(action) => postCommand({
|
|
237
|
+
(action, payload) => postCommand({
|
|
196
238
|
iframe: iframeRef.current,
|
|
197
239
|
instanceId: options.instanceId,
|
|
198
240
|
action,
|
|
199
|
-
targetOrigin: options.targetOrigin
|
|
241
|
+
targetOrigin: options.targetOrigin,
|
|
242
|
+
payload
|
|
200
243
|
}),
|
|
201
244
|
[options.instanceId, options.targetOrigin]
|
|
202
245
|
);
|
|
@@ -209,10 +252,29 @@ function useOeMeeting(options) {
|
|
|
209
252
|
() => send("toggleCamera" /* TOGGLE_CAMERA */),
|
|
210
253
|
[send]
|
|
211
254
|
);
|
|
255
|
+
const openStage = useCallback(
|
|
256
|
+
(input) => {
|
|
257
|
+
const normalized = normalizeOpenStageInput(input);
|
|
258
|
+
if (!normalized) return;
|
|
259
|
+
send("openStage" /* OPEN_STAGE */, normalized);
|
|
260
|
+
},
|
|
261
|
+
[send]
|
|
262
|
+
);
|
|
263
|
+
const closeStage = useCallback(
|
|
264
|
+
() => send("closeStage" /* CLOSE_STAGE */),
|
|
265
|
+
[send]
|
|
266
|
+
);
|
|
212
267
|
const getIframe = useCallback(() => iframeRef.current, []);
|
|
213
268
|
const handle = useMemo(
|
|
214
|
-
() => ({
|
|
215
|
-
|
|
269
|
+
() => ({
|
|
270
|
+
leave,
|
|
271
|
+
toggleMic,
|
|
272
|
+
toggleCamera,
|
|
273
|
+
openStage,
|
|
274
|
+
closeStage,
|
|
275
|
+
getIframe
|
|
276
|
+
}),
|
|
277
|
+
[closeStage, getIframe, leave, openStage, toggleCamera, toggleMic]
|
|
216
278
|
);
|
|
217
279
|
useImperativeHandle(options.handleRef, () => handle, [handle]);
|
|
218
280
|
return { iframeRef, handle };
|
|
@@ -255,6 +317,7 @@ var OeMeeting = forwardRef(
|
|
|
255
317
|
onParticipantLeft,
|
|
256
318
|
onEnded,
|
|
257
319
|
onError,
|
|
320
|
+
onStageChanged,
|
|
258
321
|
onEvent
|
|
259
322
|
} = props;
|
|
260
323
|
const reactId = useId();
|
|
@@ -302,6 +365,7 @@ var OeMeeting = forwardRef(
|
|
|
302
365
|
onParticipantLeft,
|
|
303
366
|
onEnded,
|
|
304
367
|
onError,
|
|
368
|
+
onStageChanged,
|
|
305
369
|
onEvent
|
|
306
370
|
});
|
|
307
371
|
const wrapperStyle = {
|
|
@@ -350,8 +414,13 @@ export {
|
|
|
350
414
|
EmbedErrorCode,
|
|
351
415
|
EmbedEventType,
|
|
352
416
|
EmbedQueryParam,
|
|
417
|
+
MAX_STAGE_TOOL_ID_LENGTH,
|
|
418
|
+
MAX_STAGE_URL_LENGTH,
|
|
353
419
|
OeMeeting,
|
|
354
420
|
buildEmbedUrl,
|
|
421
|
+
isStageToolId,
|
|
422
|
+
isValidStageUrl,
|
|
423
|
+
normalizeOpenStageInput,
|
|
355
424
|
parseEmbedEvent,
|
|
356
425
|
useOeMeeting
|
|
357
426
|
};
|
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/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 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 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.oceanedu.org';\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}\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}\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 UNKNOWN = 'UNKNOWN',\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 { parseEmbedEvent } from './parseEmbedEvent';\nimport type {\n EmbedCommandMessage,\n OeMeetingHandle,\n OeMeetingProps,\n} from './types';\n\ntype UseOeMeetingOptions = Pick<\n OeMeetingProps,\n | 'onReady'\n | 'onJoined'\n | 'onLeft'\n | 'onParticipantJoined'\n | 'onParticipantLeft'\n | 'onEnded'\n | 'onError'\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}\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 };\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 }\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) =>\n postCommand({\n iframe: iframeRef.current,\n instanceId: options.instanceId,\n action,\n targetOrigin: options.targetOrigin,\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 getIframe = useCallback(() => iframeRef.current, []);\n const handle = useMemo<OeMeetingHandle>(\n () => ({ leave, toggleMic, toggleCamera, getIframe }),\n [getIframe, leave, toggleCamera, toggleMic],\n );\n\n useImperativeHandle(options.handleRef, () => handle, [handle]);\n return { iframeRef, handle };\n}\n","import {\n EMBED_MESSAGE_SOURCE,\n EMBED_PROTOCOL_VERSION,\n 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 }\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 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"]}
|
|
@@ -13,7 +13,7 @@ OE Meeting cần cấp cho OE.School một application credential gồm:
|
|
|
13
13
|
Cấu hình ở backend OE.School, ví dụ:
|
|
14
14
|
|
|
15
15
|
```dotenv
|
|
16
|
-
OE_MEETING_API_URL=https://meet.
|
|
16
|
+
OE_MEETING_API_URL=https://meet.ocean.edu.vn
|
|
17
17
|
OE_MEETING_CLIENT_ID=oe-school
|
|
18
18
|
OE_MEETING_CLIENT_SECRET=<secret-from-secret-manager>
|
|
19
19
|
```
|
|
@@ -27,7 +27,7 @@ vào repository.
|
|
|
27
27
|
Base URL production:
|
|
28
28
|
|
|
29
29
|
```text
|
|
30
|
-
https://meet.
|
|
30
|
+
https://meet.ocean.edu.vn/external-api/v1.0
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Mọi request server-to-server phải gửi:
|
|
@@ -93,7 +93,7 @@ export function OnlineLesson({ lessonId }: { lessonId: string }) {
|
|
|
93
93
|
<OeMeeting
|
|
94
94
|
roomSlug={access.roomSlug}
|
|
95
95
|
joinToken={access.joinToken}
|
|
96
|
-
origin="https://meet.
|
|
96
|
+
origin="https://meet.ocean.edu.vn"
|
|
97
97
|
width="100%"
|
|
98
98
|
height="calc(100vh - 64px)"
|
|
99
99
|
hideLeave
|
|
@@ -106,7 +106,7 @@ export function OnlineLesson({ lessonId }: { lessonId: string }) {
|
|
|
106
106
|
}
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
`origin` có giá trị mặc định là `https://meet.
|
|
109
|
+
`origin` có giá trị mặc định là `https://meet.ocean.edu.vn`, nên có thể bỏ prop
|
|
110
110
|
này ở production. Khi chạy local, truyền URL frontend OE Meeting local, ví dụ
|
|
111
111
|
`http://localhost:5173`.
|
|
112
112
|
|
|
@@ -116,7 +116,7 @@ này ở production. Khi chạy local, truyền URL frontend OE Meeting local, v
|
|
|
116
116
|
|---|---|---|---|
|
|
117
117
|
| `roomSlug` | `string` | Bắt buộc | Slug phòng do backend trả về. |
|
|
118
118
|
| `joinToken` | `string` | Bắt buộc | Token cấp riêng cho user hiện tại. |
|
|
119
|
-
| `origin` | `string` | `https://meet.
|
|
119
|
+
| `origin` | `string` | `https://meet.ocean.edu.vn` | Origin của OE Meeting. |
|
|
120
120
|
| `autoJoin` | `boolean` | `true` | Phiên bản hiện tại chỉ hỗ trợ `true`. |
|
|
121
121
|
| `hideLeave` | `boolean` | `false` | Ẩn nút rời phòng trong iframe. |
|
|
122
122
|
| `userName` | `string` | — | Thông tin tùy chọn truyền vào embed URL. |
|
|
@@ -166,6 +166,7 @@ interface EmbedEventEnvelope<TPayload> {
|
|
|
166
166
|
| `PARTICIPANT_LEFT` | `identity`, `participantCount` | Đã emit |
|
|
167
167
|
| `ERROR` | `code`, `message` | Đã emit |
|
|
168
168
|
| `ENDED` | `roomSlug`, `endedAt` | Đã khai báo, chưa có nguồn emit |
|
|
169
|
+
| `STAGE_CHANGED` | `url` (`string` \| `null`), `title?`, `tool?`, `openedBy?` | Đã emit |
|
|
169
170
|
|
|
170
171
|
Có thể dùng `onEvent` để gửi analytics tập trung, nhưng không log join token,
|
|
171
172
|
iframe URL đầy đủ hoặc dữ liệu nhạy cảm.
|
|
@@ -213,6 +214,8 @@ export function MeetingWithControls(props: {
|
|
|
213
214
|
- `leave()`.
|
|
214
215
|
- `toggleMic()`.
|
|
215
216
|
- `toggleCamera()`.
|
|
217
|
+
- `openStage({ url, title?, tool? })` — ignore nếu `attributes.stage=follow`.
|
|
218
|
+
- `closeStage()` — opener, `stage=open`, hoặc admin room.
|
|
216
219
|
- `getIframe()`.
|
|
217
220
|
|
|
218
221
|
## 6. Error codes
|
|
@@ -226,14 +229,16 @@ export function MeetingWithControls(props: {
|
|
|
226
229
|
| `ROOM_NOT_FOUND` | Phòng không tồn tại. | Tạo/đồng bộ lại phòng của lesson. | Đã khai báo, chưa phân loại riêng |
|
|
227
230
|
| `ROOM_ACCESS_DENIED` | User không có quyền. | Không retry tự động; hiển thị thông báo phù hợp. | Đã khai báo, chưa phân loại riêng |
|
|
228
231
|
| `PROTOCOL_MISMATCH` | SDK và embed khác version. | Đồng bộ phiên bản SDK/OE Meeting. | Đã khai báo, chưa emit |
|
|
232
|
+
| `STAGE_ORIGIN_DENIED` | Origin URL Stage không nằm allowlist. | Dùng origin đã khai báo (`hostOrigin` / `VITE_STAGE_ALLOWED_ORIGINS`). | Đã emit |
|
|
233
|
+
| `STAGE_URL_INVALID` | URL Stage thiếu hoặc không hợp lệ. | Chỉ `https` (hoặc `http` localhost). | Đã emit |
|
|
229
234
|
|
|
230
235
|
## 7. Local development
|
|
231
236
|
|
|
232
|
-
|
|
237
|
+
Frontend OE Meeting mặc định chạy cổng `3000`.
|
|
233
238
|
|
|
234
239
|
```tsx
|
|
235
240
|
<OeMeeting
|
|
236
|
-
origin="http://localhost:
|
|
241
|
+
origin="http://localhost:3000"
|
|
237
242
|
roomSlug={roomSlug}
|
|
238
243
|
joinToken={joinToken}
|
|
239
244
|
/>
|
|
@@ -266,7 +271,7 @@ về đúng origin đó.
|
|
|
266
271
|
### Frontend OE.School
|
|
267
272
|
|
|
268
273
|
- [ ] Chỉ render `OeMeeting` sau khi nhận `roomSlug` và `joinToken`.
|
|
269
|
-
- [ ] Production dùng origin `https://meet.
|
|
274
|
+
- [ ] Production dùng origin `https://meet.ocean.edu.vn`.
|
|
270
275
|
- [ ] Có trạng thái loading, forbidden, token expired và connection failed.
|
|
271
276
|
- [ ] Không dùng `targetOrigin="*"`.
|
|
272
277
|
- [ ] Camera/microphone hoạt động trong iframe.
|
package/docs/RELEASE.md
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
# Release npm — `@tinyweb_dev/oe-meeting-react`
|
|
2
2
|
|
|
3
3
|
Workflow [`release_npm.yml`](../../../../.github/workflows/release_npm.yml) build,
|
|
4
|
-
typecheck, test, kiểm tra tarball và publish React SDK lên npm
|
|
4
|
+
typecheck, test, kiểm tra tarball và publish React SDK lên npm bằng
|
|
5
|
+
**npm Trusted Publishing (OIDC)** — không dùng long-lived `NPM_TOKEN`.
|
|
5
6
|
|
|
6
7
|
## Chuẩn bị một lần
|
|
7
8
|
|
|
8
9
|
### 1. Kiểm tra npm organization
|
|
9
10
|
|
|
10
|
-
Tài khoản
|
|
11
|
-
`@tinyweb_dev`.
|
|
11
|
+
Tài khoản maintainer phải có quyền cấu hình Trusted Publisher cho package
|
|
12
|
+
public trong scope `@tinyweb_dev`.
|
|
12
13
|
|
|
13
|
-
### 2.
|
|
14
|
+
### 2. Cấu hình Trusted Publisher trên npmjs.com
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
### 3. Thêm GitHub Actions secret
|
|
19
|
-
|
|
20
|
-
Trong repository `tinyweb-01/oe-meeting`, mở:
|
|
16
|
+
Trên npmjs.com, mở package (hoặc org) `@tinyweb_dev/oe-meeting-react` →
|
|
17
|
+
**Trusted Publisher** / **Publishing access**:
|
|
21
18
|
|
|
22
19
|
```text
|
|
23
|
-
|
|
20
|
+
Organization or user: tinyweb-01
|
|
21
|
+
Repository: oe-meeting
|
|
22
|
+
Workflow filename: release_npm.yml
|
|
23
|
+
Allowed actions: npm publish
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Không cần GitHub secret `NPM_TOKEN`. Workflow dùng OIDC (`id-token: write`)
|
|
27
|
+
và npm CLI >= 11.5.1 trên Node >= 22.14.
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
Granular access token (GAT) bypass-2FA không còn phù hợp cho CI publish
|
|
30
|
+
([npm v12 / GAT deprecation](https://github.blog/changelog/2026-07-08-npm-install-time-security-and-gat-bypass2fa-deprecation/)).
|
|
31
|
+
|
|
32
|
+
### 3. Publish lần đầu (package chưa tồn tại)
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
Nếu package chưa có trên registry, tạo Trusted Publisher ở cấp org
|
|
35
|
+
`@tinyweb_dev` trỏ đúng GitHub repo/workflow ở trên, rồi chạy release.
|
|
36
|
+
Không commit token vào source, workflow, log hoặc file `.npmrc`.
|
|
34
37
|
|
|
35
38
|
## Kiểm thử pipeline không publish
|
|
36
39
|
|
|
@@ -43,7 +46,7 @@ dry_run = true
|
|
|
43
46
|
Workflow sẽ chạy đầy đủ build, typecheck, test, `npm pack` và
|
|
44
47
|
`npm publish --dry-run`; không upload package.
|
|
45
48
|
|
|
46
|
-
Có thể kiểm tra tương đương ở local:
|
|
49
|
+
Có thể kiểm tra tương đương ở local (local publish vẫn cần 2FA tương tác):
|
|
47
50
|
|
|
48
51
|
```bash
|
|
49
52
|
cd src/sdk
|
|
@@ -105,12 +108,20 @@ bản mới.
|
|
|
105
108
|
một commit/ref đã review và package version chưa tồn tại trên npm. Luồng tag vẫn
|
|
106
109
|
là cách phát hành chuẩn vì tạo dấu mốc release rõ ràng trong Git.
|
|
107
110
|
|
|
111
|
+
Local publish (maintainer + 2FA):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
cd src/sdk/react
|
|
115
|
+
npm publish --access public
|
|
116
|
+
```
|
|
117
|
+
|
|
108
118
|
## Troubleshooting
|
|
109
119
|
|
|
110
120
|
| Lỗi | Nguyên nhân thường gặp | Cách xử lý |
|
|
111
121
|
|---|---|---|
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
122
|
+
| `E404` trên `PUT .../oe-meeting-react` | Token/GAT bị từ chối (npm trả 404 thay vì 401/403), hoặc chưa cấu hình Trusted Publisher | Cấu hình OIDC Trusted Publisher; không dùng `NPM_TOKEN` bypass-2FA. |
|
|
123
|
+
| `ENEEDAUTH` / `E401` | OIDC chưa được npm chấp nhận, hoặc local thiếu login | Kiểm tra Trusted Publisher khớp org/repo/workflow; local thì `npm login` + 2FA. |
|
|
124
|
+
| `E403` | Publisher không có quyền scope/package | Kiểm tra quyền org `@tinyweb_dev` và Trusted Publisher. |
|
|
114
125
|
| Tag không khớp version | Tag và `package.json` khác nhau | Tăng version, commit rồi tạo tag đúng convention. |
|
|
115
126
|
| Version đã tồn tại | Npm không cho overwrite | Tăng patch/minor/major version. |
|
|
116
127
|
| Build/typecheck/test fail | Source chưa đạt release gate | Fix lỗi; không bỏ qua gate publish. |
|