sa2kit 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/CollisionBalls-BpHufX3H.d.mts +41 -0
- package/dist/CollisionBalls-BpHufX3H.d.ts +41 -0
- package/dist/ConfigService-BxK06xP6.d.mts +262 -0
- package/dist/ConfigService-BxK06xP6.d.ts +262 -0
- package/dist/UniversalFileService-BpvbZitV.d.mts +139 -0
- package/dist/UniversalFileService-GsP6D3Rc.d.ts +139 -0
- package/dist/audioDetection/index.d.mts +449 -0
- package/dist/audioDetection/index.d.ts +449 -0
- package/dist/audioDetection/index.js +1244 -0
- package/dist/audioDetection/index.js.map +1 -0
- package/dist/audioDetection/index.mjs +1227 -0
- package/dist/audioDetection/index.mjs.map +1 -0
- package/dist/auth/legacy/core/index.d.mts +42 -0
- package/dist/auth/legacy/core/index.d.ts +42 -0
- package/dist/auth/legacy/core/index.js +242 -0
- package/dist/auth/legacy/core/index.js.map +1 -0
- package/dist/auth/legacy/core/index.mjs +226 -0
- package/dist/auth/legacy/core/index.mjs.map +1 -0
- package/dist/auth/legacy/db/index.d.mts +5 -0
- package/dist/auth/legacy/db/index.d.ts +5 -0
- package/dist/auth/legacy/db/index.js +261 -0
- package/dist/auth/legacy/db/index.js.map +1 -0
- package/dist/auth/legacy/db/index.mjs +250 -0
- package/dist/auth/legacy/db/index.mjs.map +1 -0
- package/dist/auth/legacy/index.d.mts +5 -0
- package/dist/auth/legacy/index.d.ts +5 -0
- package/dist/auth/legacy/index.js +1107 -0
- package/dist/auth/legacy/index.js.map +1 -0
- package/dist/auth/legacy/index.mjs +1086 -0
- package/dist/auth/legacy/index.mjs.map +1 -0
- package/dist/auth/legacy/logic/index.d.mts +9 -0
- package/dist/auth/legacy/logic/index.d.ts +9 -0
- package/dist/auth/legacy/logic/index.js +194 -0
- package/dist/auth/legacy/logic/index.js.map +1 -0
- package/dist/auth/legacy/logic/index.mjs +187 -0
- package/dist/auth/legacy/logic/index.mjs.map +1 -0
- package/dist/auth/legacy/miniapp/index.d.mts +5 -0
- package/dist/auth/legacy/miniapp/index.d.ts +5 -0
- package/dist/auth/legacy/miniapp/index.js +506 -0
- package/dist/auth/legacy/miniapp/index.js.map +1 -0
- package/dist/auth/legacy/miniapp/index.mjs +487 -0
- package/dist/auth/legacy/miniapp/index.mjs.map +1 -0
- package/dist/auth/legacy/routes/index.d.mts +53 -0
- package/dist/auth/legacy/routes/index.d.ts +53 -0
- package/dist/auth/legacy/routes/index.js +278 -0
- package/dist/auth/legacy/routes/index.js.map +1 -0
- package/dist/auth/legacy/routes/index.mjs +271 -0
- package/dist/auth/legacy/routes/index.mjs.map +1 -0
- package/dist/auth/legacy/schema/index.d.mts +401 -0
- package/dist/auth/legacy/schema/index.d.ts +401 -0
- package/dist/auth/legacy/schema/index.js +50 -0
- package/dist/auth/legacy/schema/index.js.map +1 -0
- package/dist/auth/legacy/schema/index.mjs +44 -0
- package/dist/auth/legacy/schema/index.mjs.map +1 -0
- package/dist/auth/legacy/server/index.d.mts +13 -0
- package/dist/auth/legacy/server/index.d.ts +13 -0
- package/dist/auth/legacy/server/index.js +21 -0
- package/dist/auth/legacy/server/index.js.map +1 -0
- package/dist/auth/legacy/server/index.mjs +19 -0
- package/dist/auth/legacy/server/index.mjs.map +1 -0
- package/dist/auth/legacy/services/index.d.mts +40 -0
- package/dist/auth/legacy/services/index.d.ts +40 -0
- package/dist/auth/legacy/services/index.js +258 -0
- package/dist/auth/legacy/services/index.js.map +1 -0
- package/dist/auth/legacy/services/index.mjs +252 -0
- package/dist/auth/legacy/services/index.mjs.map +1 -0
- package/dist/auth/legacy/ui/miniapp/index.d.mts +10 -0
- package/dist/auth/legacy/ui/miniapp/index.d.ts +10 -0
- package/dist/auth/legacy/ui/miniapp/index.js +298 -0
- package/dist/auth/legacy/ui/miniapp/index.js.map +1 -0
- package/dist/auth/legacy/ui/miniapp/index.mjs +290 -0
- package/dist/auth/legacy/ui/miniapp/index.mjs.map +1 -0
- package/dist/auth/legacy/ui/web/index.d.mts +22 -0
- package/dist/auth/legacy/ui/web/index.d.ts +22 -0
- package/dist/auth/legacy/ui/web/index.js +899 -0
- package/dist/auth/legacy/ui/web/index.js.map +1 -0
- package/dist/auth/legacy/ui/web/index.mjs +889 -0
- package/dist/auth/legacy/ui/web/index.mjs.map +1 -0
- package/dist/auth/legacy/web/index.d.mts +5 -0
- package/dist/auth/legacy/web/index.d.ts +5 -0
- package/dist/auth/legacy/web/index.js +1107 -0
- package/dist/auth/legacy/web/index.js.map +1 -0
- package/dist/auth/legacy/web/index.mjs +1086 -0
- package/dist/auth/legacy/web/index.mjs.map +1 -0
- package/dist/auth/rn/index.d.mts +64 -0
- package/dist/auth/rn/index.d.ts +64 -0
- package/dist/auth/rn/index.js +765 -0
- package/dist/auth/rn/index.js.map +1 -0
- package/dist/auth/rn/index.mjs +754 -0
- package/dist/auth/rn/index.mjs.map +1 -0
- package/dist/base-api-client-ACKKt13v.d.mts +277 -0
- package/dist/base-api-client-ACKKt13v.d.ts +277 -0
- package/dist/boothVaultService-Cn4WPhjg.d.mts +83 -0
- package/dist/boothVaultService-Cn4WPhjg.d.ts +83 -0
- package/dist/business/index.d.mts +6 -0
- package/dist/business/index.d.ts +6 -0
- package/dist/business/index.js +1682 -0
- package/dist/business/index.js.map +1 -0
- package/dist/business/index.mjs +1675 -0
- package/dist/business/index.mjs.map +1 -0
- package/dist/calendar/index.d.mts +1325 -0
- package/dist/calendar/index.d.ts +1325 -0
- package/dist/calendar/index.js +5964 -0
- package/dist/calendar/index.js.map +1 -0
- package/dist/calendar/index.mjs +5878 -0
- package/dist/calendar/index.mjs.map +1 -0
- package/dist/calendar/routes/index.d.mts +118 -0
- package/dist/calendar/routes/index.d.ts +118 -0
- package/dist/calendar/routes/index.js +755 -0
- package/dist/calendar/routes/index.js.map +1 -0
- package/dist/calendar/routes/index.mjs +747 -0
- package/dist/calendar/routes/index.mjs.map +1 -0
- package/dist/components/index.d.mts +405 -0
- package/dist/components/index.d.ts +405 -0
- package/dist/components/index.js +2516 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +2396 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/drizzle-schema-BNhqj2AZ.d.mts +1114 -0
- package/dist/drizzle-schema-BNhqj2AZ.d.ts +1114 -0
- package/dist/festivalCard/index.d.mts +76 -0
- package/dist/festivalCard/index.d.ts +76 -0
- package/dist/festivalCard/index.js +1492 -0
- package/dist/festivalCard/index.js.map +1 -0
- package/dist/festivalCard/index.mjs +1475 -0
- package/dist/festivalCard/index.mjs.map +1 -0
- package/dist/festivalCard/routes/index.d.mts +42 -0
- package/dist/festivalCard/routes/index.d.ts +42 -0
- package/dist/festivalCard/routes/index.js +361 -0
- package/dist/festivalCard/routes/index.js.map +1 -0
- package/dist/festivalCard/routes/index.mjs +356 -0
- package/dist/festivalCard/routes/index.mjs.map +1 -0
- package/dist/festivalCard/server/index.d.mts +120 -0
- package/dist/festivalCard/server/index.d.ts +120 -0
- package/dist/festivalCard/server/index.js +272 -0
- package/dist/festivalCard/server/index.js.map +1 -0
- package/dist/festivalCard/server/index.mjs +265 -0
- package/dist/festivalCard/server/index.mjs.map +1 -0
- package/dist/festivalCardService-BFCRhJrq.d.ts +13 -0
- package/dist/festivalCardService-GriR2VMc.d.mts +13 -0
- package/dist/index-1Ag7IBXN.d.ts +144 -0
- package/dist/index-DNKZ7-R_.d.mts +184 -0
- package/dist/index-DNKZ7-R_.d.ts +184 -0
- package/dist/index-DSel44Ke.d.mts +93 -0
- package/dist/index-DSel44Ke.d.ts +93 -0
- package/dist/index-DdeZSeTJ.d.mts +144 -0
- package/dist/index-DrPcMJPc.d.mts +250 -0
- package/dist/index-DrPcMJPc.d.ts +250 -0
- package/dist/index.d.mts +5334 -0
- package/dist/index.d.ts +5334 -0
- package/dist/index.js +18809 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +18533 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mikuContest/ui/web/index.d.mts +2 -0
- package/dist/mikuContest/ui/web/index.d.ts +2 -0
- package/dist/mikuContest/ui/web/index.js +353 -0
- package/dist/mikuContest/ui/web/index.js.map +1 -0
- package/dist/mikuContest/ui/web/index.mjs +343 -0
- package/dist/mikuContest/ui/web/index.mjs.map +1 -0
- package/dist/mikuFireworks3D/index.d.mts +268 -0
- package/dist/mikuFireworks3D/index.d.ts +268 -0
- package/dist/mikuFireworks3D/index.js +1267 -0
- package/dist/mikuFireworks3D/index.js.map +1 -0
- package/dist/mikuFireworks3D/index.mjs +1228 -0
- package/dist/mikuFireworks3D/index.mjs.map +1 -0
- package/dist/mikuFusionGame/index.d.mts +117 -0
- package/dist/mikuFusionGame/index.d.ts +117 -0
- package/dist/mikuFusionGame/index.js +1208 -0
- package/dist/mikuFusionGame/index.js.map +1 -0
- package/dist/mikuFusionGame/index.mjs +1195 -0
- package/dist/mikuFusionGame/index.mjs.map +1 -0
- package/dist/mmd/admin/index.d.mts +487 -0
- package/dist/mmd/admin/index.d.ts +487 -0
- package/dist/mmd/admin/index.js +1058 -0
- package/dist/mmd/admin/index.js.map +1 -0
- package/dist/mmd/admin/index.mjs +1027 -0
- package/dist/mmd/admin/index.mjs.map +1 -0
- package/dist/mmd/index.d.mts +2467 -0
- package/dist/mmd/index.d.ts +2467 -0
- package/dist/mmd/index.js +10119 -0
- package/dist/mmd/index.js.map +1 -0
- package/dist/mmd/index.mjs +10028 -0
- package/dist/mmd/index.mjs.map +1 -0
- package/dist/mmd/server/index.d.mts +139 -0
- package/dist/mmd/server/index.d.ts +139 -0
- package/dist/mmd/server/index.js +424 -0
- package/dist/mmd/server/index.js.map +1 -0
- package/dist/mmd/server/index.mjs +404 -0
- package/dist/mmd/server/index.mjs.map +1 -0
- package/dist/music/index.d.mts +74 -0
- package/dist/music/index.d.ts +74 -0
- package/dist/music/index.js +830 -0
- package/dist/music/index.js.map +1 -0
- package/dist/music/index.mjs +809 -0
- package/dist/music/index.mjs.map +1 -0
- package/dist/music/server/index.d.mts +1 -0
- package/dist/music/server/index.d.ts +1 -0
- package/dist/music/server/index.js +194 -0
- package/dist/music/server/index.js.map +1 -0
- package/dist/music/server/index.mjs +182 -0
- package/dist/music/server/index.mjs.map +1 -0
- package/dist/navigation/index.d.mts +93 -0
- package/dist/navigation/index.d.ts +93 -0
- package/dist/navigation/index.js +453 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/navigation/index.mjs +443 -0
- package/dist/navigation/index.mjs.map +1 -0
- package/dist/portfolio/index.d.mts +66 -0
- package/dist/portfolio/index.d.ts +66 -0
- package/dist/portfolio/index.js +736 -0
- package/dist/portfolio/index.js.map +1 -0
- package/dist/portfolio/index.mjs +724 -0
- package/dist/portfolio/index.mjs.map +1 -0
- package/dist/qqbot/server/index.d.mts +216 -0
- package/dist/qqbot/server/index.d.ts +216 -0
- package/dist/qqbot/server/index.js +394 -0
- package/dist/qqbot/server/index.js.map +1 -0
- package/dist/qqbot/server/index.mjs +385 -0
- package/dist/qqbot/server/index.mjs.map +1 -0
- package/dist/qqbot/ui/web/index.d.mts +10 -0
- package/dist/qqbot/ui/web/index.d.ts +10 -0
- package/dist/qqbot/ui/web/index.js +105 -0
- package/dist/qqbot/ui/web/index.js.map +1 -0
- package/dist/qqbot/ui/web/index.mjs +99 -0
- package/dist/qqbot/ui/web/index.mjs.map +1 -0
- package/dist/screenReceiver/index.d.mts +86 -0
- package/dist/screenReceiver/index.d.ts +86 -0
- package/dist/screenReceiver/index.js +281 -0
- package/dist/screenReceiver/index.js.map +1 -0
- package/dist/screenReceiver/index.mjs +273 -0
- package/dist/screenReceiver/index.mjs.map +1 -0
- package/dist/testYourself/admin/index.d.mts +58 -0
- package/dist/testYourself/admin/index.d.ts +58 -0
- package/dist/testYourself/admin/index.js +1009 -0
- package/dist/testYourself/admin/index.js.map +1 -0
- package/dist/testYourself/admin/index.mjs +1002 -0
- package/dist/testYourself/admin/index.mjs.map +1 -0
- package/dist/testYourself/index.d.mts +53 -0
- package/dist/testYourself/index.d.ts +53 -0
- package/dist/testYourself/index.js +2551 -0
- package/dist/testYourself/index.js.map +1 -0
- package/dist/testYourself/index.mjs +2531 -0
- package/dist/testYourself/index.mjs.map +1 -0
- package/dist/testYourself/server/index.d.mts +1029 -0
- package/dist/testYourself/server/index.d.ts +1029 -0
- package/dist/testYourself/server/index.js +825 -0
- package/dist/testYourself/server/index.js.map +1 -0
- package/dist/testYourself/server/index.mjs +816 -0
- package/dist/testYourself/server/index.mjs.map +1 -0
- package/dist/types-BTiaMsBz.d.mts +292 -0
- package/dist/types-DyG3ZV9V.d.mts +270 -0
- package/dist/types-DyG3ZV9V.d.ts +270 -0
- package/dist/types-ERmJyjx8.d.ts +292 -0
- package/dist/types-HorDyIRv.d.mts +303 -0
- package/dist/types-HorDyIRv.d.ts +303 -0
- package/dist/types-tQfupO6d.d.mts +70 -0
- package/dist/types-tQfupO6d.d.ts +70 -0
- package/dist/vocaloidBooth/index.d.mts +64 -0
- package/dist/vocaloidBooth/index.d.ts +64 -0
- package/dist/vocaloidBooth/index.js +376 -0
- package/dist/vocaloidBooth/index.js.map +1 -0
- package/dist/vocaloidBooth/index.mjs +362 -0
- package/dist/vocaloidBooth/index.mjs.map +1 -0
- package/dist/vocaloidBooth/server/index.d.mts +111 -0
- package/dist/vocaloidBooth/server/index.d.ts +111 -0
- package/dist/vocaloidBooth/server/index.js +247 -0
- package/dist/vocaloidBooth/server/index.js.map +1 -0
- package/dist/vocaloidBooth/server/index.mjs +237 -0
- package/dist/vocaloidBooth/server/index.mjs.map +1 -0
- package/dist/vocaloidBooth/web/index.d.mts +3 -0
- package/dist/vocaloidBooth/web/index.d.ts +3 -0
- package/dist/vocaloidBooth/web/index.js +376 -0
- package/dist/vocaloidBooth/web/index.js.map +1 -0
- package/dist/vocaloidBooth/web/index.mjs +362 -0
- package/dist/vocaloidBooth/web/index.mjs.map +1 -0
- package/package.json +11 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/qqbot/ui/web/NapCatConsole.tsx
|
|
4
|
+
var panelStyle = {
|
|
5
|
+
border: "1px solid #d9e2ec",
|
|
6
|
+
borderRadius: 12,
|
|
7
|
+
padding: 16,
|
|
8
|
+
display: "grid",
|
|
9
|
+
gap: 12,
|
|
10
|
+
background: "linear-gradient(145deg, #fffef9, #f3f8ff)",
|
|
11
|
+
boxShadow: "0 8px 24px rgba(15, 23, 42, 0.08)"
|
|
12
|
+
};
|
|
13
|
+
var NapCatConsole = ({
|
|
14
|
+
endpoint,
|
|
15
|
+
initialAction = "send_group_msg",
|
|
16
|
+
request = fetch
|
|
17
|
+
}) => {
|
|
18
|
+
const [action, setAction] = useState(initialAction);
|
|
19
|
+
const [payloadText, setPayloadText] = useState('{\n "group_id": 123456,\n "message": "Hello from sa2kit"\n}');
|
|
20
|
+
const [result, setResult] = useState(null);
|
|
21
|
+
const [submitting, setSubmitting] = useState(false);
|
|
22
|
+
const prettyResult = useMemo(() => {
|
|
23
|
+
if (!result) return "";
|
|
24
|
+
return JSON.stringify(result, null, 2);
|
|
25
|
+
}, [result]);
|
|
26
|
+
const onSubmit = async () => {
|
|
27
|
+
setSubmitting(true);
|
|
28
|
+
try {
|
|
29
|
+
const payload = JSON.parse(payloadText);
|
|
30
|
+
const response = await request(`${endpoint}/action/${action}`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "Content-Type": "application/json" },
|
|
33
|
+
body: JSON.stringify(payload)
|
|
34
|
+
});
|
|
35
|
+
const body = await response.json();
|
|
36
|
+
setResult(body);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
setResult({
|
|
39
|
+
ok: false,
|
|
40
|
+
error: error instanceof Error ? error.message : "Request failed"
|
|
41
|
+
});
|
|
42
|
+
} finally {
|
|
43
|
+
setSubmitting(false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return /* @__PURE__ */ React.createElement("section", { style: panelStyle }, /* @__PURE__ */ React.createElement("h3", { style: { margin: 0, fontSize: 20, fontFamily: "Avenir Next, Helvetica, sans-serif" } }, "NapCat QQ Bot Console"), /* @__PURE__ */ React.createElement("label", { style: { display: "grid", gap: 6 } }, /* @__PURE__ */ React.createElement("span", { style: { fontWeight: 600 } }, "OneBot Action"), /* @__PURE__ */ React.createElement(
|
|
47
|
+
"input",
|
|
48
|
+
{
|
|
49
|
+
value: action,
|
|
50
|
+
onChange: (event) => setAction(event.target.value),
|
|
51
|
+
placeholder: "send_group_msg",
|
|
52
|
+
style: { border: "1px solid #bcccdc", borderRadius: 8, padding: "10px 12px" }
|
|
53
|
+
}
|
|
54
|
+
)), /* @__PURE__ */ React.createElement("label", { style: { display: "grid", gap: 6 } }, /* @__PURE__ */ React.createElement("span", { style: { fontWeight: 600 } }, "JSON Payload"), /* @__PURE__ */ React.createElement(
|
|
55
|
+
"textarea",
|
|
56
|
+
{
|
|
57
|
+
rows: 8,
|
|
58
|
+
value: payloadText,
|
|
59
|
+
onChange: (event) => setPayloadText(event.target.value),
|
|
60
|
+
style: { border: "1px solid #bcccdc", borderRadius: 8, padding: "10px 12px", fontFamily: "Menlo, monospace" }
|
|
61
|
+
}
|
|
62
|
+
)), /* @__PURE__ */ React.createElement(
|
|
63
|
+
"button",
|
|
64
|
+
{
|
|
65
|
+
type: "button",
|
|
66
|
+
onClick: onSubmit,
|
|
67
|
+
disabled: submitting,
|
|
68
|
+
style: {
|
|
69
|
+
border: 0,
|
|
70
|
+
borderRadius: 8,
|
|
71
|
+
padding: "10px 14px",
|
|
72
|
+
cursor: submitting ? "not-allowed" : "pointer",
|
|
73
|
+
color: "#fff",
|
|
74
|
+
background: submitting ? "#829ab1" : "#0b7285",
|
|
75
|
+
width: 180,
|
|
76
|
+
fontWeight: 700
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
submitting ? "Requesting..." : "Run Action"
|
|
80
|
+
), prettyResult ? /* @__PURE__ */ React.createElement(
|
|
81
|
+
"pre",
|
|
82
|
+
{
|
|
83
|
+
style: {
|
|
84
|
+
margin: 0,
|
|
85
|
+
padding: 12,
|
|
86
|
+
borderRadius: 8,
|
|
87
|
+
background: "#102a43",
|
|
88
|
+
color: "#f0f4f8",
|
|
89
|
+
overflowX: "auto",
|
|
90
|
+
fontSize: 12
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
prettyResult
|
|
94
|
+
) : null);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export { NapCatConsole };
|
|
98
|
+
//# sourceMappingURL=index.mjs.map
|
|
99
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/qqbot/ui/web/NapCatConsole.tsx"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import React, { useMemo, useState } from 'react';\nimport { NapCatWebApiResponse } from '../../types';\n\nexport interface NapCatConsoleProps {\n endpoint: string;\n initialAction?: string;\n request?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;\n}\n\nconst panelStyle: React.CSSProperties = {\n border: '1px solid #d9e2ec',\n borderRadius: 12,\n padding: 16,\n display: 'grid',\n gap: 12,\n background: 'linear-gradient(145deg, #fffef9, #f3f8ff)',\n boxShadow: '0 8px 24px rgba(15, 23, 42, 0.08)',\n};\n\nexport const NapCatConsole: React.FC<NapCatConsoleProps> = ({\n endpoint,\n initialAction = 'send_group_msg',\n request = fetch,\n}) => {\n const [action, setAction] = useState(initialAction);\n const [payloadText, setPayloadText] = useState('{\\n \"group_id\": 123456,\\n \"message\": \"Hello from sa2kit\"\\n}');\n const [result, setResult] = useState<NapCatWebApiResponse<unknown> | null>(null);\n const [submitting, setSubmitting] = useState(false);\n\n const prettyResult = useMemo(() => {\n if (!result) return '';\n return JSON.stringify(result, null, 2);\n }, [result]);\n\n const onSubmit = async () => {\n setSubmitting(true);\n try {\n const payload = JSON.parse(payloadText);\n const response = await request(`${endpoint}/action/${action}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n });\n\n const body = (await response.json()) as NapCatWebApiResponse<unknown>;\n setResult(body);\n } catch (error) {\n setResult({\n ok: false,\n error: error instanceof Error ? error.message : 'Request failed',\n });\n } finally {\n setSubmitting(false);\n }\n };\n\n return (\n <section style={panelStyle}>\n <h3 style={{ margin: 0, fontSize: 20, fontFamily: 'Avenir Next, Helvetica, sans-serif' }}>NapCat QQ Bot Console</h3>\n <label style={{ display: 'grid', gap: 6 }}>\n <span style={{ fontWeight: 600 }}>OneBot Action</span>\n <input\n value={action}\n onChange={(event) => setAction(event.target.value)}\n placeholder=\"send_group_msg\"\n style={{ border: '1px solid #bcccdc', borderRadius: 8, padding: '10px 12px' }}\n />\n </label>\n <label style={{ display: 'grid', gap: 6 }}>\n <span style={{ fontWeight: 600 }}>JSON Payload</span>\n <textarea\n rows={8}\n value={payloadText}\n onChange={(event) => setPayloadText(event.target.value)}\n style={{ border: '1px solid #bcccdc', borderRadius: 8, padding: '10px 12px', fontFamily: 'Menlo, monospace' }}\n />\n </label>\n <button\n type=\"button\"\n onClick={onSubmit}\n disabled={submitting}\n style={{\n border: 0,\n borderRadius: 8,\n padding: '10px 14px',\n cursor: submitting ? 'not-allowed' : 'pointer',\n color: '#fff',\n background: submitting ? '#829ab1' : '#0b7285',\n width: 180,\n fontWeight: 700,\n }}\n >\n {submitting ? 'Requesting...' : 'Run Action'}\n </button>\n {prettyResult ? (\n <pre\n style={{\n margin: 0,\n padding: 12,\n borderRadius: 8,\n background: '#102a43',\n color: '#f0f4f8',\n overflowX: 'auto',\n fontSize: 12,\n }}\n >\n {prettyResult}\n </pre>\n ) : null}\n </section>\n );\n};\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React__default, { RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
type ScreenReceiverRole = 'viewer' | 'broadcaster';
|
|
4
|
+
type ScreenReceiverClientRole = ScreenReceiverRole | 'sender';
|
|
5
|
+
type ScreenReceiverSignalType = 'offer' | 'answer' | 'ice';
|
|
6
|
+
interface ScreenReceiverJoinMessage {
|
|
7
|
+
type: 'join';
|
|
8
|
+
roomId: string;
|
|
9
|
+
role?: ScreenReceiverClientRole;
|
|
10
|
+
peerId?: string;
|
|
11
|
+
}
|
|
12
|
+
interface ScreenReceiverSignalMessage {
|
|
13
|
+
type: ScreenReceiverSignalType;
|
|
14
|
+
sdp?: string;
|
|
15
|
+
candidate?: RTCIceCandidateInit;
|
|
16
|
+
targetPeerId?: string;
|
|
17
|
+
}
|
|
18
|
+
type ScreenReceiverIncomingMessage = ScreenReceiverJoinMessage | ScreenReceiverSignalMessage | Record<string, unknown>;
|
|
19
|
+
interface ScreenReceiverJoinedMessage {
|
|
20
|
+
type: 'joined';
|
|
21
|
+
roomId: string;
|
|
22
|
+
peerId: string;
|
|
23
|
+
role: ScreenReceiverRole;
|
|
24
|
+
}
|
|
25
|
+
interface ScreenReceiverPeerJoinedMessage {
|
|
26
|
+
type: 'peer_joined';
|
|
27
|
+
roomId: string;
|
|
28
|
+
peerId: string;
|
|
29
|
+
role: ScreenReceiverRole;
|
|
30
|
+
}
|
|
31
|
+
interface ScreenReceiverPeerLeftMessage {
|
|
32
|
+
type: 'peer_left';
|
|
33
|
+
roomId: string;
|
|
34
|
+
peerId: string;
|
|
35
|
+
}
|
|
36
|
+
interface ScreenReceiverRoomStateMessage {
|
|
37
|
+
type: 'room_state';
|
|
38
|
+
roomId: string;
|
|
39
|
+
peers: Array<{
|
|
40
|
+
peerId: string;
|
|
41
|
+
role: ScreenReceiverRole;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
interface ScreenReceiverErrorMessage {
|
|
45
|
+
type: 'error';
|
|
46
|
+
reason: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface ScreenReceiverLogEntry {
|
|
50
|
+
id: number;
|
|
51
|
+
text: string;
|
|
52
|
+
}
|
|
53
|
+
interface UseScreenReceiverOptions {
|
|
54
|
+
wsUrl: string;
|
|
55
|
+
roomId: string;
|
|
56
|
+
iceServers?: RTCIceServer[];
|
|
57
|
+
maxLogs?: number;
|
|
58
|
+
}
|
|
59
|
+
interface UseScreenReceiverReturn {
|
|
60
|
+
connect: () => Promise<void>;
|
|
61
|
+
disconnect: () => void;
|
|
62
|
+
clearLogs: () => void;
|
|
63
|
+
logs: ScreenReceiverLogEntry[];
|
|
64
|
+
isConnected: boolean;
|
|
65
|
+
connectionState: RTCPeerConnectionState | 'idle';
|
|
66
|
+
iceConnectionState: RTCIceConnectionState | 'idle';
|
|
67
|
+
videoRef: RefObject<HTMLVideoElement>;
|
|
68
|
+
stream: MediaStream | null;
|
|
69
|
+
}
|
|
70
|
+
declare function useScreenReceiver(options: UseScreenReceiverOptions): UseScreenReceiverReturn;
|
|
71
|
+
|
|
72
|
+
interface ResolveScreenReceiverSignalUrlOptions {
|
|
73
|
+
signalUrl?: string;
|
|
74
|
+
path?: string;
|
|
75
|
+
port?: number;
|
|
76
|
+
}
|
|
77
|
+
declare function resolveScreenReceiverSignalUrl(options?: ResolveScreenReceiverSignalUrlOptions): string;
|
|
78
|
+
|
|
79
|
+
interface ScreenReceiverPanelProps {
|
|
80
|
+
defaultSignalUrl?: string;
|
|
81
|
+
defaultRoomId?: string;
|
|
82
|
+
className?: string;
|
|
83
|
+
}
|
|
84
|
+
declare function ScreenReceiverPanel(props: ScreenReceiverPanelProps): React__default.JSX.Element;
|
|
85
|
+
|
|
86
|
+
export { type ResolveScreenReceiverSignalUrlOptions, type ScreenReceiverClientRole, type ScreenReceiverErrorMessage, type ScreenReceiverIncomingMessage, type ScreenReceiverJoinMessage, type ScreenReceiverJoinedMessage, type ScreenReceiverLogEntry, ScreenReceiverPanel, type ScreenReceiverPanelProps, type ScreenReceiverPeerJoinedMessage, type ScreenReceiverPeerLeftMessage, type ScreenReceiverRole, type ScreenReceiverRoomStateMessage, type ScreenReceiverSignalMessage, type ScreenReceiverSignalType, type UseScreenReceiverOptions, type UseScreenReceiverReturn, resolveScreenReceiverSignalUrl, useScreenReceiver };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React__default, { RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
type ScreenReceiverRole = 'viewer' | 'broadcaster';
|
|
4
|
+
type ScreenReceiverClientRole = ScreenReceiverRole | 'sender';
|
|
5
|
+
type ScreenReceiverSignalType = 'offer' | 'answer' | 'ice';
|
|
6
|
+
interface ScreenReceiverJoinMessage {
|
|
7
|
+
type: 'join';
|
|
8
|
+
roomId: string;
|
|
9
|
+
role?: ScreenReceiverClientRole;
|
|
10
|
+
peerId?: string;
|
|
11
|
+
}
|
|
12
|
+
interface ScreenReceiverSignalMessage {
|
|
13
|
+
type: ScreenReceiverSignalType;
|
|
14
|
+
sdp?: string;
|
|
15
|
+
candidate?: RTCIceCandidateInit;
|
|
16
|
+
targetPeerId?: string;
|
|
17
|
+
}
|
|
18
|
+
type ScreenReceiverIncomingMessage = ScreenReceiverJoinMessage | ScreenReceiverSignalMessage | Record<string, unknown>;
|
|
19
|
+
interface ScreenReceiverJoinedMessage {
|
|
20
|
+
type: 'joined';
|
|
21
|
+
roomId: string;
|
|
22
|
+
peerId: string;
|
|
23
|
+
role: ScreenReceiverRole;
|
|
24
|
+
}
|
|
25
|
+
interface ScreenReceiverPeerJoinedMessage {
|
|
26
|
+
type: 'peer_joined';
|
|
27
|
+
roomId: string;
|
|
28
|
+
peerId: string;
|
|
29
|
+
role: ScreenReceiverRole;
|
|
30
|
+
}
|
|
31
|
+
interface ScreenReceiverPeerLeftMessage {
|
|
32
|
+
type: 'peer_left';
|
|
33
|
+
roomId: string;
|
|
34
|
+
peerId: string;
|
|
35
|
+
}
|
|
36
|
+
interface ScreenReceiverRoomStateMessage {
|
|
37
|
+
type: 'room_state';
|
|
38
|
+
roomId: string;
|
|
39
|
+
peers: Array<{
|
|
40
|
+
peerId: string;
|
|
41
|
+
role: ScreenReceiverRole;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
interface ScreenReceiverErrorMessage {
|
|
45
|
+
type: 'error';
|
|
46
|
+
reason: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface ScreenReceiverLogEntry {
|
|
50
|
+
id: number;
|
|
51
|
+
text: string;
|
|
52
|
+
}
|
|
53
|
+
interface UseScreenReceiverOptions {
|
|
54
|
+
wsUrl: string;
|
|
55
|
+
roomId: string;
|
|
56
|
+
iceServers?: RTCIceServer[];
|
|
57
|
+
maxLogs?: number;
|
|
58
|
+
}
|
|
59
|
+
interface UseScreenReceiverReturn {
|
|
60
|
+
connect: () => Promise<void>;
|
|
61
|
+
disconnect: () => void;
|
|
62
|
+
clearLogs: () => void;
|
|
63
|
+
logs: ScreenReceiverLogEntry[];
|
|
64
|
+
isConnected: boolean;
|
|
65
|
+
connectionState: RTCPeerConnectionState | 'idle';
|
|
66
|
+
iceConnectionState: RTCIceConnectionState | 'idle';
|
|
67
|
+
videoRef: RefObject<HTMLVideoElement>;
|
|
68
|
+
stream: MediaStream | null;
|
|
69
|
+
}
|
|
70
|
+
declare function useScreenReceiver(options: UseScreenReceiverOptions): UseScreenReceiverReturn;
|
|
71
|
+
|
|
72
|
+
interface ResolveScreenReceiverSignalUrlOptions {
|
|
73
|
+
signalUrl?: string;
|
|
74
|
+
path?: string;
|
|
75
|
+
port?: number;
|
|
76
|
+
}
|
|
77
|
+
declare function resolveScreenReceiverSignalUrl(options?: ResolveScreenReceiverSignalUrlOptions): string;
|
|
78
|
+
|
|
79
|
+
interface ScreenReceiverPanelProps {
|
|
80
|
+
defaultSignalUrl?: string;
|
|
81
|
+
defaultRoomId?: string;
|
|
82
|
+
className?: string;
|
|
83
|
+
}
|
|
84
|
+
declare function ScreenReceiverPanel(props: ScreenReceiverPanelProps): React__default.JSX.Element;
|
|
85
|
+
|
|
86
|
+
export { type ResolveScreenReceiverSignalUrlOptions, type ScreenReceiverClientRole, type ScreenReceiverErrorMessage, type ScreenReceiverIncomingMessage, type ScreenReceiverJoinMessage, type ScreenReceiverJoinedMessage, type ScreenReceiverLogEntry, ScreenReceiverPanel, type ScreenReceiverPanelProps, type ScreenReceiverPeerJoinedMessage, type ScreenReceiverPeerLeftMessage, type ScreenReceiverRole, type ScreenReceiverRoomStateMessage, type ScreenReceiverSignalMessage, type ScreenReceiverSignalType, type UseScreenReceiverOptions, type UseScreenReceiverReturn, resolveScreenReceiverSignalUrl, useScreenReceiver };
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var React__default = /*#__PURE__*/_interopDefault(React);
|
|
8
|
+
|
|
9
|
+
// src/screenReceiver/useScreenReceiver.ts
|
|
10
|
+
var DEFAULT_STUN_SERVER = { urls: "stun:stun.l.google.com:19302" };
|
|
11
|
+
function parseMessage(payload) {
|
|
12
|
+
try {
|
|
13
|
+
if (typeof payload === "string") return JSON.parse(payload);
|
|
14
|
+
if (payload instanceof ArrayBuffer) return JSON.parse(new TextDecoder().decode(payload));
|
|
15
|
+
return JSON.parse(String(payload));
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function useScreenReceiver(options) {
|
|
21
|
+
const { wsUrl, roomId, iceServers = [DEFAULT_STUN_SERVER], maxLogs = 80 } = options;
|
|
22
|
+
const [logs, setLogs] = React.useState([]);
|
|
23
|
+
const [isConnected, setIsConnected] = React.useState(false);
|
|
24
|
+
const [connectionState, setConnectionState] = React.useState("idle");
|
|
25
|
+
const [iceConnectionState, setIceConnectionState] = React.useState("idle");
|
|
26
|
+
const [stream, setStream] = React.useState(null);
|
|
27
|
+
const logIdRef = React.useRef(0);
|
|
28
|
+
const peerRef = React.useRef({ pendingCandidates: [] });
|
|
29
|
+
const videoRef = React.useRef(null);
|
|
30
|
+
const appendLog = React.useCallback(
|
|
31
|
+
(text) => {
|
|
32
|
+
logIdRef.current += 1;
|
|
33
|
+
setLogs((prev) => [...prev, { id: logIdRef.current, text }].slice(-maxLogs));
|
|
34
|
+
},
|
|
35
|
+
[maxLogs]
|
|
36
|
+
);
|
|
37
|
+
const disconnect = React.useCallback(() => {
|
|
38
|
+
peerRef.current.ws?.close();
|
|
39
|
+
peerRef.current.pc?.close();
|
|
40
|
+
peerRef.current = { pendingCandidates: [] };
|
|
41
|
+
setIsConnected(false);
|
|
42
|
+
setConnectionState("idle");
|
|
43
|
+
setIceConnectionState("idle");
|
|
44
|
+
setStream(null);
|
|
45
|
+
}, []);
|
|
46
|
+
const connect = React.useCallback(async () => {
|
|
47
|
+
disconnect();
|
|
48
|
+
const normalizedWsUrl = wsUrl.trim();
|
|
49
|
+
const normalizedRoomId = roomId.trim();
|
|
50
|
+
if (!normalizedWsUrl) {
|
|
51
|
+
appendLog("ws url is empty");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (!normalizedRoomId) {
|
|
55
|
+
appendLog("room id is empty");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
appendLog(`connecting: ${normalizedWsUrl}`);
|
|
59
|
+
let ws;
|
|
60
|
+
try {
|
|
61
|
+
ws = new WebSocket(normalizedWsUrl);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
appendLog(`ws create error: ${String(error)}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const pc = new RTCPeerConnection({ iceServers });
|
|
67
|
+
pc.addTransceiver("video", { direction: "recvonly" });
|
|
68
|
+
peerRef.current.ws = ws;
|
|
69
|
+
peerRef.current.pc = pc;
|
|
70
|
+
peerRef.current.pendingCandidates = [];
|
|
71
|
+
ws.onopen = () => {
|
|
72
|
+
appendLog("ws connected");
|
|
73
|
+
setIsConnected(true);
|
|
74
|
+
ws.send(JSON.stringify({ type: "join", roomId: normalizedRoomId, role: "viewer" }));
|
|
75
|
+
};
|
|
76
|
+
ws.onclose = () => {
|
|
77
|
+
appendLog("ws closed");
|
|
78
|
+
setIsConnected(false);
|
|
79
|
+
};
|
|
80
|
+
ws.onerror = (event) => {
|
|
81
|
+
appendLog(`ws error: ${event.type}`);
|
|
82
|
+
};
|
|
83
|
+
ws.onmessage = async (event) => {
|
|
84
|
+
const message = parseMessage(event.data);
|
|
85
|
+
if (!message) {
|
|
86
|
+
appendLog("ws message parse error");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (message.type === "joined") {
|
|
90
|
+
const joined = message;
|
|
91
|
+
peerRef.current.selfPeerId = joined.peerId;
|
|
92
|
+
appendLog(`joined room ${joined.roomId} as ${joined.peerId}`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (message.type === "room_state") {
|
|
96
|
+
const roomState = message;
|
|
97
|
+
const broadcaster = roomState.peers.find((peer) => peer.role === "broadcaster");
|
|
98
|
+
if (broadcaster) {
|
|
99
|
+
peerRef.current.publisherPeerId = broadcaster.peerId;
|
|
100
|
+
appendLog(`broadcaster online: ${broadcaster.peerId}`);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (message.type === "offer" && typeof message.sdp === "string") {
|
|
105
|
+
appendLog("offer received");
|
|
106
|
+
peerRef.current.publisherPeerId = typeof message.fromPeerId === "string" ? message.fromPeerId : void 0;
|
|
107
|
+
await pc.setRemoteDescription({ type: "offer", sdp: message.sdp });
|
|
108
|
+
for (const candidate of peerRef.current.pendingCandidates) {
|
|
109
|
+
try {
|
|
110
|
+
await pc.addIceCandidate(candidate);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
appendLog(`ice err: ${String(error)}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
peerRef.current.pendingCandidates = [];
|
|
116
|
+
const answer = await pc.createAnswer();
|
|
117
|
+
await pc.setLocalDescription(answer);
|
|
118
|
+
ws.send(
|
|
119
|
+
JSON.stringify({
|
|
120
|
+
type: "answer",
|
|
121
|
+
sdp: answer.sdp,
|
|
122
|
+
targetPeerId: peerRef.current.publisherPeerId
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
appendLog("answer sent");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (message.type === "ice" && message.candidate) {
|
|
129
|
+
if (!pc.remoteDescription) {
|
|
130
|
+
peerRef.current.pendingCandidates.push(message.candidate);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
await pc.addIceCandidate(message.candidate);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
appendLog(`ice err: ${String(error)}`);
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (message.type === "peer_left") {
|
|
141
|
+
const peerLeft = message;
|
|
142
|
+
if (peerLeft.peerId === peerRef.current.publisherPeerId) {
|
|
143
|
+
peerRef.current.publisherPeerId = void 0;
|
|
144
|
+
appendLog(`publisher left: ${peerLeft.peerId}`);
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (message.type === "error") {
|
|
149
|
+
appendLog(`server error: ${String(message.reason ?? "unknown")}`);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
pc.ontrack = (event) => {
|
|
153
|
+
const receivedStream = event.streams[0];
|
|
154
|
+
if (receivedStream) {
|
|
155
|
+
setStream(receivedStream);
|
|
156
|
+
appendLog("track received");
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
pc.onicecandidate = (event) => {
|
|
160
|
+
if (!event.candidate) return;
|
|
161
|
+
ws.send(
|
|
162
|
+
JSON.stringify({
|
|
163
|
+
type: "ice",
|
|
164
|
+
candidate: event.candidate,
|
|
165
|
+
targetPeerId: peerRef.current.publisherPeerId
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
pc.onconnectionstatechange = () => {
|
|
170
|
+
const state = pc.connectionState || "new";
|
|
171
|
+
setConnectionState(state);
|
|
172
|
+
appendLog(`pc state: ${state}`);
|
|
173
|
+
};
|
|
174
|
+
pc.oniceconnectionstatechange = () => {
|
|
175
|
+
const state = pc.iceConnectionState || "new";
|
|
176
|
+
setIceConnectionState(state);
|
|
177
|
+
appendLog(`ice state: ${state}`);
|
|
178
|
+
};
|
|
179
|
+
}, [appendLog, disconnect, iceServers, roomId, wsUrl]);
|
|
180
|
+
React.useEffect(() => () => disconnect(), [disconnect]);
|
|
181
|
+
React.useEffect(() => {
|
|
182
|
+
if (!videoRef.current) return;
|
|
183
|
+
videoRef.current.srcObject = stream;
|
|
184
|
+
}, [stream]);
|
|
185
|
+
const clearLogs = React.useCallback(() => setLogs([]), []);
|
|
186
|
+
return React.useMemo(
|
|
187
|
+
() => ({
|
|
188
|
+
connect,
|
|
189
|
+
disconnect,
|
|
190
|
+
clearLogs,
|
|
191
|
+
logs,
|
|
192
|
+
isConnected,
|
|
193
|
+
connectionState,
|
|
194
|
+
iceConnectionState,
|
|
195
|
+
videoRef,
|
|
196
|
+
stream
|
|
197
|
+
}),
|
|
198
|
+
[clearLogs, connect, connectionState, disconnect, iceConnectionState, isConnected, logs, stream]
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/screenReceiver/signalUrl.ts
|
|
203
|
+
var DEFAULT_PORT = 8787;
|
|
204
|
+
var DEFAULT_PATH = "/ws";
|
|
205
|
+
function resolveScreenReceiverSignalUrl(options = {}) {
|
|
206
|
+
const { signalUrl, path = DEFAULT_PATH, port = DEFAULT_PORT } = options;
|
|
207
|
+
if (signalUrl && signalUrl.trim()) return signalUrl.trim();
|
|
208
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
209
|
+
if (typeof window !== "undefined") {
|
|
210
|
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
211
|
+
return `${protocol}//${window.location.host}${normalizedPath}`;
|
|
212
|
+
}
|
|
213
|
+
return `ws://127.0.0.1:${port}${normalizedPath}`;
|
|
214
|
+
}
|
|
215
|
+
var DEFAULT_ROOM_ID = "screen-room-1";
|
|
216
|
+
function ScreenReceiverPanel(props) {
|
|
217
|
+
const { defaultSignalUrl, defaultRoomId = DEFAULT_ROOM_ID, className } = props;
|
|
218
|
+
const initialSignalUrl = React.useMemo(
|
|
219
|
+
() => resolveScreenReceiverSignalUrl({ signalUrl: defaultSignalUrl }),
|
|
220
|
+
[defaultSignalUrl]
|
|
221
|
+
);
|
|
222
|
+
const [wsUrl, setWsUrl] = React.useState(initialSignalUrl);
|
|
223
|
+
const [roomId, setRoomId] = React.useState(defaultRoomId);
|
|
224
|
+
const receiver = useScreenReceiver({ wsUrl, roomId });
|
|
225
|
+
const logs = React.useMemo(() => receiver.logs.map((entry) => entry.text).join("\n"), [receiver.logs]);
|
|
226
|
+
return /* @__PURE__ */ React__default.default.createElement("div", { className }, /* @__PURE__ */ React__default.default.createElement("div", { className: "flex flex-col gap-4" }, /* @__PURE__ */ React__default.default.createElement("div", { className: "grid gap-3 md:grid-cols-[2fr,1fr,auto]" }, /* @__PURE__ */ React__default.default.createElement("label", { className: "flex flex-col gap-1 text-sm" }, /* @__PURE__ */ React__default.default.createElement("span", null, "Signaling WS"), /* @__PURE__ */ React__default.default.createElement(
|
|
227
|
+
"input",
|
|
228
|
+
{
|
|
229
|
+
className: "rounded-md border px-3 py-2",
|
|
230
|
+
value: wsUrl,
|
|
231
|
+
onChange: (event) => setWsUrl(event.target.value)
|
|
232
|
+
}
|
|
233
|
+
)), /* @__PURE__ */ React__default.default.createElement("label", { className: "flex flex-col gap-1 text-sm" }, /* @__PURE__ */ React__default.default.createElement("span", null, "Room"), /* @__PURE__ */ React__default.default.createElement(
|
|
234
|
+
"input",
|
|
235
|
+
{
|
|
236
|
+
className: "rounded-md border px-3 py-2",
|
|
237
|
+
value: roomId,
|
|
238
|
+
onChange: (event) => setRoomId(event.target.value)
|
|
239
|
+
}
|
|
240
|
+
)), /* @__PURE__ */ React__default.default.createElement("div", { className: "flex items-end gap-2" }, /* @__PURE__ */ React__default.default.createElement(
|
|
241
|
+
"button",
|
|
242
|
+
{
|
|
243
|
+
onClick: () => void receiver.connect(),
|
|
244
|
+
className: "rounded-md border bg-black px-4 py-2 text-sm text-white"
|
|
245
|
+
},
|
|
246
|
+
receiver.isConnected ? "Reconnect" : "Connect"
|
|
247
|
+
), /* @__PURE__ */ React__default.default.createElement(
|
|
248
|
+
"button",
|
|
249
|
+
{
|
|
250
|
+
onClick: receiver.disconnect,
|
|
251
|
+
className: "rounded-md border px-4 py-2 text-sm"
|
|
252
|
+
},
|
|
253
|
+
"Disconnect"
|
|
254
|
+
))), /* @__PURE__ */ React__default.default.createElement("div", { className: "grid gap-3 md:grid-cols-3" }, /* @__PURE__ */ React__default.default.createElement(StatusItem, { label: "WebSocket", value: receiver.isConnected ? "connected" : "idle" }), /* @__PURE__ */ React__default.default.createElement(StatusItem, { label: "PeerConnection", value: receiver.connectionState }), /* @__PURE__ */ React__default.default.createElement(StatusItem, { label: "ICE State", value: receiver.iceConnectionState })), /* @__PURE__ */ React__default.default.createElement("div", { className: "grid gap-4 lg:grid-cols-[3fr,2fr]" }, /* @__PURE__ */ React__default.default.createElement("div", { className: "rounded-xl border bg-black p-3" }, /* @__PURE__ */ React__default.default.createElement(
|
|
255
|
+
"video",
|
|
256
|
+
{
|
|
257
|
+
ref: receiver.videoRef,
|
|
258
|
+
autoPlay: true,
|
|
259
|
+
playsInline: true,
|
|
260
|
+
controls: true,
|
|
261
|
+
muted: true,
|
|
262
|
+
className: "h-[360px] w-full rounded-lg bg-black"
|
|
263
|
+
}
|
|
264
|
+
)), /* @__PURE__ */ React__default.default.createElement("div", { className: "rounded-xl border p-3" }, /* @__PURE__ */ React__default.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React__default.default.createElement("p", { className: "text-sm font-semibold" }, "Session Log"), /* @__PURE__ */ React__default.default.createElement(
|
|
265
|
+
"button",
|
|
266
|
+
{
|
|
267
|
+
onClick: receiver.clearLogs,
|
|
268
|
+
className: "text-xs underline-offset-2 hover:underline"
|
|
269
|
+
},
|
|
270
|
+
"Clear"
|
|
271
|
+
)), /* @__PURE__ */ React__default.default.createElement("pre", { className: "mt-3 h-[320px] overflow-auto rounded-lg border bg-slate-50 p-3 text-xs" }, logs || "No logs yet.")))));
|
|
272
|
+
}
|
|
273
|
+
function StatusItem({ label, value }) {
|
|
274
|
+
return /* @__PURE__ */ React__default.default.createElement("div", { className: "rounded-md border px-3 py-2" }, /* @__PURE__ */ React__default.default.createElement("p", { className: "text-xs text-slate-500" }, label), /* @__PURE__ */ React__default.default.createElement("p", { className: "text-sm font-medium" }, value));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
exports.ScreenReceiverPanel = ScreenReceiverPanel;
|
|
278
|
+
exports.resolveScreenReceiverSignalUrl = resolveScreenReceiverSignalUrl;
|
|
279
|
+
exports.useScreenReceiver = useScreenReceiver;
|
|
280
|
+
//# sourceMappingURL=index.js.map
|
|
281
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/screenReceiver/useScreenReceiver.ts","../../src/screenReceiver/signalUrl.ts","../../src/screenReceiver/ScreenReceiverPanel.tsx"],"names":["useState","useRef","useCallback","useEffect","useMemo","React"],"mappings":"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","file":"index.js","sourcesContent":["import type { RefObject } from 'react';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type {\n ScreenReceiverJoinedMessage,\n ScreenReceiverPeerLeftMessage,\n ScreenReceiverRoomStateMessage,\n} from './types';\n\nconst DEFAULT_STUN_SERVER: RTCIceServer = { urls: 'stun:stun.l.google.com:19302' };\n\nexport interface ScreenReceiverLogEntry {\n id: number;\n text: string;\n}\n\nexport interface UseScreenReceiverOptions {\n wsUrl: string;\n roomId: string;\n iceServers?: RTCIceServer[];\n maxLogs?: number;\n}\n\nexport interface UseScreenReceiverReturn {\n connect: () => Promise<void>;\n disconnect: () => void;\n clearLogs: () => void;\n logs: ScreenReceiverLogEntry[];\n isConnected: boolean;\n connectionState: RTCPeerConnectionState | 'idle';\n iceConnectionState: RTCIceConnectionState | 'idle';\n videoRef: RefObject<HTMLVideoElement>;\n stream: MediaStream | null;\n}\n\ntype PeerContext = {\n ws?: WebSocket;\n pc?: RTCPeerConnection;\n pendingCandidates: RTCIceCandidateInit[];\n selfPeerId?: string;\n publisherPeerId?: string;\n};\n\nfunction parseMessage(payload: MessageEvent['data']): Record<string, any> | null {\n try {\n if (typeof payload === 'string') return JSON.parse(payload);\n if (payload instanceof ArrayBuffer) return JSON.parse(new TextDecoder().decode(payload));\n return JSON.parse(String(payload));\n } catch {\n return null;\n }\n}\n\nexport function useScreenReceiver(options: UseScreenReceiverOptions): UseScreenReceiverReturn {\n const { wsUrl, roomId, iceServers = [DEFAULT_STUN_SERVER], maxLogs = 80 } = options;\n const [logs, setLogs] = useState<ScreenReceiverLogEntry[]>([]);\n const [isConnected, setIsConnected] = useState(false);\n const [connectionState, setConnectionState] = useState<RTCPeerConnectionState | 'idle'>('idle');\n const [iceConnectionState, setIceConnectionState] = useState<RTCIceConnectionState | 'idle'>('idle');\n const [stream, setStream] = useState<MediaStream | null>(null);\n const logIdRef = useRef(0);\n const peerRef = useRef<PeerContext>({ pendingCandidates: [] });\n const videoRef = useRef<HTMLVideoElement>(null);\n\n const appendLog = useCallback(\n (text: string) => {\n logIdRef.current += 1;\n setLogs((prev) => [...prev, { id: logIdRef.current, text }].slice(-maxLogs));\n },\n [maxLogs],\n );\n\n const disconnect = useCallback(() => {\n peerRef.current.ws?.close();\n peerRef.current.pc?.close();\n peerRef.current = { pendingCandidates: [] };\n setIsConnected(false);\n setConnectionState('idle');\n setIceConnectionState('idle');\n setStream(null);\n }, []);\n\n const connect = useCallback(async () => {\n disconnect();\n\n const normalizedWsUrl = wsUrl.trim();\n const normalizedRoomId = roomId.trim();\n if (!normalizedWsUrl) {\n appendLog('ws url is empty');\n return;\n }\n if (!normalizedRoomId) {\n appendLog('room id is empty');\n return;\n }\n\n appendLog(`connecting: ${normalizedWsUrl}`);\n\n let ws: WebSocket;\n try {\n ws = new WebSocket(normalizedWsUrl);\n } catch (error) {\n appendLog(`ws create error: ${String(error)}`);\n return;\n }\n\n const pc = new RTCPeerConnection({ iceServers });\n pc.addTransceiver('video', { direction: 'recvonly' });\n\n peerRef.current.ws = ws;\n peerRef.current.pc = pc;\n peerRef.current.pendingCandidates = [];\n\n ws.onopen = () => {\n appendLog('ws connected');\n setIsConnected(true);\n ws.send(JSON.stringify({ type: 'join', roomId: normalizedRoomId, role: 'viewer' }));\n };\n\n ws.onclose = () => {\n appendLog('ws closed');\n setIsConnected(false);\n };\n\n ws.onerror = (event) => {\n appendLog(`ws error: ${event.type}`);\n };\n\n ws.onmessage = async (event) => {\n const message = parseMessage(event.data);\n if (!message) {\n appendLog('ws message parse error');\n return;\n }\n\n if (message.type === 'joined') {\n const joined = message as ScreenReceiverJoinedMessage;\n peerRef.current.selfPeerId = joined.peerId;\n appendLog(`joined room ${joined.roomId} as ${joined.peerId}`);\n return;\n }\n\n if (message.type === 'room_state') {\n const roomState = message as ScreenReceiverRoomStateMessage;\n const broadcaster = roomState.peers.find((peer) => peer.role === 'broadcaster');\n if (broadcaster) {\n peerRef.current.publisherPeerId = broadcaster.peerId;\n appendLog(`broadcaster online: ${broadcaster.peerId}`);\n }\n return;\n }\n\n if (message.type === 'offer' && typeof message.sdp === 'string') {\n appendLog('offer received');\n peerRef.current.publisherPeerId = typeof message.fromPeerId === 'string' ? message.fromPeerId : undefined;\n await pc.setRemoteDescription({ type: 'offer', sdp: message.sdp });\n for (const candidate of peerRef.current.pendingCandidates) {\n try {\n await pc.addIceCandidate(candidate);\n } catch (error) {\n appendLog(`ice err: ${String(error)}`);\n }\n }\n peerRef.current.pendingCandidates = [];\n\n const answer = await pc.createAnswer();\n await pc.setLocalDescription(answer);\n ws.send(\n JSON.stringify({\n type: 'answer',\n sdp: answer.sdp,\n targetPeerId: peerRef.current.publisherPeerId,\n }),\n );\n appendLog('answer sent');\n return;\n }\n\n if (message.type === 'ice' && message.candidate) {\n if (!pc.remoteDescription) {\n peerRef.current.pendingCandidates.push(message.candidate as RTCIceCandidateInit);\n return;\n }\n try {\n await pc.addIceCandidate(message.candidate as RTCIceCandidateInit);\n } catch (error) {\n appendLog(`ice err: ${String(error)}`);\n }\n return;\n }\n\n if (message.type === 'peer_left') {\n const peerLeft = message as ScreenReceiverPeerLeftMessage;\n if (peerLeft.peerId === peerRef.current.publisherPeerId) {\n peerRef.current.publisherPeerId = undefined;\n appendLog(`publisher left: ${peerLeft.peerId}`);\n }\n return;\n }\n\n if (message.type === 'error') {\n appendLog(`server error: ${String(message.reason ?? 'unknown')}`);\n }\n };\n\n pc.ontrack = (event) => {\n const receivedStream = event.streams[0];\n if (receivedStream) {\n setStream(receivedStream);\n appendLog('track received');\n }\n };\n\n pc.onicecandidate = (event) => {\n if (!event.candidate) return;\n ws.send(\n JSON.stringify({\n type: 'ice',\n candidate: event.candidate,\n targetPeerId: peerRef.current.publisherPeerId,\n }),\n );\n };\n\n pc.onconnectionstatechange = () => {\n const state = pc.connectionState || 'new';\n setConnectionState(state);\n appendLog(`pc state: ${state}`);\n };\n\n pc.oniceconnectionstatechange = () => {\n const state = pc.iceConnectionState || 'new';\n setIceConnectionState(state);\n appendLog(`ice state: ${state}`);\n };\n }, [appendLog, disconnect, iceServers, roomId, wsUrl]);\n\n useEffect(() => () => disconnect(), [disconnect]);\n\n useEffect(() => {\n if (!videoRef.current) return;\n videoRef.current.srcObject = stream;\n }, [stream]);\n\n const clearLogs = useCallback(() => setLogs([]), []);\n\n return useMemo(\n () => ({\n connect,\n disconnect,\n clearLogs,\n logs,\n isConnected,\n connectionState,\n iceConnectionState,\n videoRef,\n stream,\n }),\n [clearLogs, connect, connectionState, disconnect, iceConnectionState, isConnected, logs, stream],\n );\n}\n","const DEFAULT_PORT = 8787;\nconst DEFAULT_PATH = '/ws';\n\nexport interface ResolveScreenReceiverSignalUrlOptions {\n signalUrl?: string;\n path?: string;\n port?: number;\n}\n\nexport function resolveScreenReceiverSignalUrl(\n options: ResolveScreenReceiverSignalUrlOptions = {},\n): string {\n const { signalUrl, path = DEFAULT_PATH, port = DEFAULT_PORT } = options;\n if (signalUrl && signalUrl.trim()) return signalUrl.trim();\n\n const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n if (typeof window !== 'undefined') {\n const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n return `${protocol}//${window.location.host}${normalizedPath}`;\n }\n\n return `ws://127.0.0.1:${port}${normalizedPath}`;\n}\n","import React, { useMemo, useState } from 'react';\nimport { resolveScreenReceiverSignalUrl } from './signalUrl';\nimport { useScreenReceiver } from './useScreenReceiver';\n\nexport interface ScreenReceiverPanelProps {\n defaultSignalUrl?: string;\n defaultRoomId?: string;\n className?: string;\n}\n\nconst DEFAULT_ROOM_ID = 'screen-room-1';\n\nexport function ScreenReceiverPanel(props: ScreenReceiverPanelProps) {\n const { defaultSignalUrl, defaultRoomId = DEFAULT_ROOM_ID, className } = props;\n const initialSignalUrl = useMemo(\n () => resolveScreenReceiverSignalUrl({ signalUrl: defaultSignalUrl }),\n [defaultSignalUrl],\n );\n const [wsUrl, setWsUrl] = useState(initialSignalUrl);\n const [roomId, setRoomId] = useState(defaultRoomId);\n const receiver = useScreenReceiver({ wsUrl, roomId });\n const logs = useMemo(() => receiver.logs.map((entry) => entry.text).join('\\n'), [receiver.logs]);\n\n return (\n <div className={className}>\n <div className=\"flex flex-col gap-4\">\n <div className=\"grid gap-3 md:grid-cols-[2fr,1fr,auto]\">\n <label className=\"flex flex-col gap-1 text-sm\">\n <span>Signaling WS</span>\n <input\n className=\"rounded-md border px-3 py-2\"\n value={wsUrl}\n onChange={(event) => setWsUrl(event.target.value)}\n />\n </label>\n <label className=\"flex flex-col gap-1 text-sm\">\n <span>Room</span>\n <input\n className=\"rounded-md border px-3 py-2\"\n value={roomId}\n onChange={(event) => setRoomId(event.target.value)}\n />\n </label>\n <div className=\"flex items-end gap-2\">\n <button\n onClick={() => void receiver.connect()}\n className=\"rounded-md border bg-black px-4 py-2 text-sm text-white\"\n >\n {receiver.isConnected ? 'Reconnect' : 'Connect'}\n </button>\n <button\n onClick={receiver.disconnect}\n className=\"rounded-md border px-4 py-2 text-sm\"\n >\n Disconnect\n </button>\n </div>\n </div>\n\n <div className=\"grid gap-3 md:grid-cols-3\">\n <StatusItem label=\"WebSocket\" value={receiver.isConnected ? 'connected' : 'idle'} />\n <StatusItem label=\"PeerConnection\" value={receiver.connectionState} />\n <StatusItem label=\"ICE State\" value={receiver.iceConnectionState} />\n </div>\n\n <div className=\"grid gap-4 lg:grid-cols-[3fr,2fr]\">\n <div className=\"rounded-xl border bg-black p-3\">\n <video\n ref={receiver.videoRef}\n autoPlay\n playsInline\n controls\n muted\n className=\"h-[360px] w-full rounded-lg bg-black\"\n />\n </div>\n <div className=\"rounded-xl border p-3\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm font-semibold\">Session Log</p>\n <button\n onClick={receiver.clearLogs}\n className=\"text-xs underline-offset-2 hover:underline\"\n >\n Clear\n </button>\n </div>\n <pre className=\"mt-3 h-[320px] overflow-auto rounded-lg border bg-slate-50 p-3 text-xs\">\n {logs || 'No logs yet.'}\n </pre>\n </div>\n </div>\n </div>\n </div>\n );\n}\n\nfunction StatusItem({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"rounded-md border px-3 py-2\">\n <p className=\"text-xs text-slate-500\">{label}</p>\n <p className=\"text-sm font-medium\">{value}</p>\n </div>\n );\n}\n"]}
|