openlearn-next 0.7.6 → 0.7.8
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 +66 -150
- package/dist/assets/{AcademicYearFilter-DUUGpDKt.js → AcademicYearFilter-Dm3no4Nb.js} +1 -1
- package/dist/assets/ActivityWorkspaceWidget-BXS1IamH.js +1 -0
- package/dist/assets/{AdminDirectoryView-DL9zZi-_.js → AdminDirectoryView-479SfYyD.js} +2 -2
- package/dist/assets/{AssessmentCenter-B2JzXBGe.js → AssessmentCenter-CJ-pchOQ.js} +1 -1
- package/dist/assets/{AssignmentFilePreview-Bk751Blz.js → AssignmentFilePreview-wjnrKjsp.js} +1 -1
- package/dist/assets/{BatchPickerModal-9ekXDUUj.js → BatchPickerModal-JvoD-1pF.js} +1 -1
- package/dist/assets/{ClassesView-PCt8I3MU.js → ClassesView-CB8q2kW0.js} +12 -12
- package/dist/assets/ClassroomCountdownWidget-BqzBpAvi.js +2 -0
- package/dist/assets/{CloudDriveModal-Mp59sr-C.js → CloudDriveModal-DHxOnL4m.js} +1 -1
- package/dist/assets/ComputerLabView-BEDe7WiU.js +1 -0
- package/dist/assets/CourseManagement-BjxQC6sq.js +1 -0
- package/dist/assets/{CourseWizardModal-B7-e8otR.js → CourseWizardModal-D3UPFtT_.js} +2 -2
- package/dist/assets/{CoursewareHubPanel-DLjH-UzF.js → CoursewareHubPanel-CqJqcZGs.js} +1 -1
- package/dist/assets/{Dashboard-D3LXLQNC.js → Dashboard-C3BaEHIG.js} +2 -2
- package/dist/assets/{ExportWeightModal-vd7XgaJ0.js → ExportWeightModal-iF0pVevG.js} +1 -1
- package/dist/assets/{HelpTour-BLuiS8rr.js → HelpTour-6ChRqWnB.js} +1 -1
- package/dist/assets/{HelpView-C4BHoNqR.js → HelpView-D3iYhzBz.js} +2 -2
- package/dist/assets/ImportLessonsModal-BOwj7ezj.js +1 -0
- package/dist/assets/{ImportModal-H5s5MyZY.js → ImportModal-DAHGdI9h.js} +1 -1
- package/dist/assets/{InteractiveCoursewareViewer-D9GzG28E.js → InteractiveCoursewareViewer-DX9xnk6j.js} +1 -1
- package/dist/assets/InteractiveWhiteboard-41mi9keH.js +65 -0
- package/dist/assets/LazyCourseware-DKyUHXaE.js +2 -0
- package/dist/assets/LazyWhiteboard-fXS2P44c.js +2 -0
- package/dist/assets/LessonEditorView-Dx-hOKtt.js +1 -0
- package/dist/assets/LiveClassroomView-m0hKdcVK.js +61 -0
- package/dist/assets/NotificationDetailModal-C2tg_IoJ.js +1 -0
- package/dist/assets/PluginView-BPRUpCjV.js +77 -0
- package/dist/assets/{ProcessLogsModal-_BwkmJVw.js → ProcessLogsModal-CULHzXp7.js} +1 -1
- package/dist/assets/{QuizGeneratorModal-DiItok6z.js → QuizGeneratorModal-CYxngpW9.js} +1 -1
- package/dist/assets/StudentAssignmentEvalPanel-Dfe4IOs5.js +1 -0
- package/dist/assets/{StudentAssignmentView-UK_7_MF3.js → StudentAssignmentView-IfRo3yJk.js} +1 -1
- package/dist/assets/StudentLessonView-Dw2uxBhq.js +1 -0
- package/dist/assets/StudentView-DVYUSrVU.js +2 -0
- package/dist/assets/{SystemResourceLibraryModal-dEdNKbk2.js → SystemResourceLibraryModal-BhbpfIBt.js} +1 -1
- package/dist/assets/TeacherView-CZjIejet.js +2 -0
- package/dist/assets/{TimetableView-AkReskNG.js → TimetableView-CupOIfEy.js} +1 -1
- package/dist/assets/{UTIF-4E-wdh3X.js → UTIF-BtQhea94.js} +1 -1
- package/dist/assets/index-BDFyco9S.css +1 -0
- package/dist/assets/index-BiHQqzdm.js +131 -0
- package/dist/assets/{index.es-WXdI0L-F.js → index.es-CijVATXs.js} +1 -1
- package/dist/assets/{pluginParsers-k90WUDrU.js → pluginParsers-C9W6GBVx.js} +1 -1
- package/dist/assets/{student-workspace-BDw1i4Vl.js → student-workspace-CsbA8Cs4.js} +1 -1
- package/dist/assets/{vendor-charts-SrpBsvM3.js → vendor-charts-skggaP9D.js} +1 -1
- package/dist/assets/{vendor-icons-hc6MjYFv.js → vendor-icons-DaYNfRAc.js} +2 -2
- package/dist/assets/{vendor-konva-Dne0Z-WC.js → vendor-konva-BOuJWFuE.js} +1 -1
- package/dist/assets/{vendor-pdf-By0fofA0.js → vendor-pdf-DVWGghIL.js} +3 -3
- package/dist/assets/{vendor-pptx-EjNUtdws.js → vendor-pptx-BqU7v5zS.js} +5 -5
- package/dist/assets/{vendor-react-Kv_JATwr.js → vendor-react-DHoBZpn2.js} +2 -2
- package/dist/assets/{vendor-utils-aM7aYvPP.js → vendor-utils-BxOuk3id.js} +1 -1
- package/dist/env-validation.cjs +7 -1
- package/dist/index.html +6 -6
- package/dist/server.cjs +343 -37
- package/migrations/044_students_gender.sql +8 -0
- package/migrations/README.md +1 -0
- package/migrations/schema-golden.json +3 -2
- package/package.json +2 -2
- package/dist/assets/ActivityWorkspaceWidget-0DUqiqFK.js +0 -1
- package/dist/assets/ClassroomCountdownWidget-HxelcHUp.js +0 -2
- package/dist/assets/ComputerLabView-FStILvFC.js +0 -1
- package/dist/assets/CourseManagement-BQw2idgZ.js +0 -1
- package/dist/assets/ImportLessonsModal-Y6uIIemO.js +0 -1
- package/dist/assets/InteractiveWhiteboard-DswIV-ES.js +0 -65
- package/dist/assets/LazyCourseware-BEoTTg5h.js +0 -2
- package/dist/assets/LazyWhiteboard-CMBBxnFX.js +0 -2
- package/dist/assets/LessonEditorView-BgJtG8Ks.js +0 -1
- package/dist/assets/LiveClassroomView-Czwt5WW-.js +0 -61
- package/dist/assets/NotificationDetailModal-D42vEs3S.js +0 -1
- package/dist/assets/PluginView-BcOWIjLu.js +0 -77
- package/dist/assets/StudentAssignmentEvalPanel-DHoZc3g0.js +0 -1
- package/dist/assets/StudentLessonView-C11Qkdi_.js +0 -1
- package/dist/assets/StudentView-j0hx3IOi.js +0 -2
- package/dist/assets/TeacherView-COZw2tFl.js +0 -2
- package/dist/assets/index-DN3E-Fmw.js +0 -131
- package/dist/assets/index-DPky2leG.css +0 -1
package/dist/server.cjs
CHANGED
|
@@ -916,7 +916,12 @@ var init_manifest_schema = __esm({
|
|
|
916
916
|
teacherPanelSchema = import_zod.z.object({
|
|
917
917
|
id: import_zod.z.string().min(1),
|
|
918
918
|
title: import_zod.z.string().min(1),
|
|
919
|
-
icon: import_zod.z.string().optional()
|
|
919
|
+
icon: import_zod.z.string().optional(),
|
|
920
|
+
// v0.7.6 双轨制 UI:第三方 ZIP 插件在 process 模式下无法传 React 组件,
|
|
921
|
+
// 只能声明可视沙箱 HTML 的相对路径。此处必须显式列出 —— zod 的 object
|
|
922
|
+
// 默认 strip 未声明键,漏声明会让 view 在 parse() 后被静默丢弃,
|
|
923
|
+
// 宿主便退回「占位空态」,插件作者看到的是空白面板且无任何报错。
|
|
924
|
+
view: import_zod.z.string().min(1).optional()
|
|
920
925
|
});
|
|
921
926
|
studentFullscreenSchema = import_zod.z.object({
|
|
922
927
|
id: import_zod.z.string().min(1),
|
|
@@ -994,7 +999,11 @@ var init_manifest_schema = __esm({
|
|
|
994
999
|
id: import_zod.z.string().min(1),
|
|
995
1000
|
label: import_zod.z.string().min(1),
|
|
996
1001
|
icon: import_zod.z.string().optional(),
|
|
997
|
-
position: import_zod.z.number().int().optional()
|
|
1002
|
+
position: import_zod.z.number().int().optional(),
|
|
1003
|
+
// 可视沙箱视图(v0.7.6+)。同 teacherPanelSchema.view:漏声明 = 被 strip,
|
|
1004
|
+
// 第三方插件的教师页签会退化成空白占位。
|
|
1005
|
+
view: import_zod.z.string().min(1).optional(),
|
|
1006
|
+
route: import_zod.z.string().optional()
|
|
998
1007
|
})
|
|
999
1008
|
).optional(),
|
|
1000
1009
|
"teacher.dashboard.widget": import_zod.z.array(
|
|
@@ -1002,7 +1011,8 @@ var init_manifest_schema = __esm({
|
|
|
1002
1011
|
id: import_zod.z.string().min(1),
|
|
1003
1012
|
label: import_zod.z.string().min(1),
|
|
1004
1013
|
icon: import_zod.z.string().optional(),
|
|
1005
|
-
position: import_zod.z.number().int().optional()
|
|
1014
|
+
position: import_zod.z.number().int().optional(),
|
|
1015
|
+
view: import_zod.z.string().min(1).optional()
|
|
1006
1016
|
})
|
|
1007
1017
|
).optional(),
|
|
1008
1018
|
"student.view": import_zod.z.array(
|
|
@@ -1010,14 +1020,17 @@ var init_manifest_schema = __esm({
|
|
|
1010
1020
|
id: import_zod.z.string().min(1),
|
|
1011
1021
|
label: import_zod.z.string().min(1),
|
|
1012
1022
|
icon: import_zod.z.string().optional(),
|
|
1013
|
-
route: import_zod.z.string().optional()
|
|
1023
|
+
route: import_zod.z.string().optional(),
|
|
1024
|
+
// 可视沙箱视图(v0.7.6+)。学生端同理:process 模式只能走 HTML 页面。
|
|
1025
|
+
view: import_zod.z.string().min(1).optional()
|
|
1014
1026
|
})
|
|
1015
1027
|
).optional(),
|
|
1016
1028
|
"student.lesson.tool": import_zod.z.array(
|
|
1017
1029
|
import_zod.z.object({
|
|
1018
1030
|
id: import_zod.z.string().min(1),
|
|
1019
1031
|
label: import_zod.z.string().min(1),
|
|
1020
|
-
icon: import_zod.z.string().optional()
|
|
1032
|
+
icon: import_zod.z.string().optional(),
|
|
1033
|
+
view: import_zod.z.string().min(1).optional()
|
|
1021
1034
|
})
|
|
1022
1035
|
).optional(),
|
|
1023
1036
|
// E-1:补全 contribution-registry.ts 已定义的全部服务端 slot
|
|
@@ -4587,14 +4600,33 @@ function wrapProcessManager(processService, tracker, pluginId, processOwnerLooku
|
|
|
4587
4600
|
})
|
|
4588
4601
|
};
|
|
4589
4602
|
}
|
|
4590
|
-
function wrapActionRegistry(actionRegistry, tracker, pluginId) {
|
|
4603
|
+
function wrapActionRegistry(actionRegistry, tracker, pluginId, manifestId) {
|
|
4604
|
+
const prefix = manifestId || pluginId;
|
|
4605
|
+
const isKernel = prefix.startsWith("@openlearn/");
|
|
4591
4606
|
return {
|
|
4592
4607
|
register: createSafeFunction((descriptor) => {
|
|
4593
|
-
|
|
4608
|
+
const commandType = descriptor.commandType;
|
|
4609
|
+
const prefixed = isKernel || !commandType || commandType.startsWith(`${prefix}.`) ? commandType : `${prefix}.${commandType}`;
|
|
4610
|
+
const effectiveDescriptor = prefixed !== commandType ? { ...descriptor, commandType: prefixed } : descriptor;
|
|
4611
|
+
return Promise.resolve(actionRegistry.register(effectiveDescriptor)).then(() => {
|
|
4612
|
+
if (prefixed !== commandType) {
|
|
4613
|
+
Promise.resolve(
|
|
4614
|
+
actionRegistry.register({
|
|
4615
|
+
...descriptor,
|
|
4616
|
+
id: `${descriptor.id}__raw_alias`,
|
|
4617
|
+
exposeToAgent: false
|
|
4618
|
+
})
|
|
4619
|
+
).catch(() => {
|
|
4620
|
+
});
|
|
4621
|
+
}
|
|
4594
4622
|
tracker.track(pluginId, {
|
|
4595
4623
|
dispose: () => {
|
|
4596
|
-
Promise.resolve(actionRegistry.unregister(
|
|
4624
|
+
Promise.resolve(actionRegistry.unregister(effectiveDescriptor.id)).catch(() => {
|
|
4597
4625
|
});
|
|
4626
|
+
if (prefixed !== commandType) {
|
|
4627
|
+
Promise.resolve(actionRegistry.unregister(`${descriptor.id}__raw_alias`)).catch(() => {
|
|
4628
|
+
});
|
|
4629
|
+
}
|
|
4598
4630
|
}
|
|
4599
4631
|
});
|
|
4600
4632
|
});
|
|
@@ -4835,7 +4867,7 @@ async function buildContext(serviceRegistry, tracker, pluginId, manifest, db2, s
|
|
|
4835
4867
|
// 可选方法,mock 或旧部署可缺省 —— 缺省时 isOwnProcess 退化为「不拦截」,与旧行为一致。
|
|
4836
4868
|
typeof processService.getProcessOwner === "function" ? (processId) => processService.getProcessOwner(processId) : void 0
|
|
4837
4869
|
);
|
|
4838
|
-
const wrappedActionRegistry = wrapActionRegistry(actionRegistryService, tracker, pluginId);
|
|
4870
|
+
const wrappedActionRegistry = wrapActionRegistry(actionRegistryService, tracker, pluginId, manifest.id);
|
|
4839
4871
|
const wrappedCapability = wrapCapability(capabilityService);
|
|
4840
4872
|
const wrappedStorage = wrapStorage(storageService, db2, manifest.id);
|
|
4841
4873
|
const wrappedAI = wrapAI(aiService, pluginId, tracker);
|
|
@@ -8480,10 +8512,28 @@ var init_service_host = __esm({
|
|
|
8480
8512
|
const actionRegistry = service;
|
|
8481
8513
|
if (msg.method === "register") {
|
|
8482
8514
|
const [descriptor] = effectiveArgs;
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8515
|
+
const pluginId = this.pluginId || this.pluginActorId.replace(/^plugin:/, "");
|
|
8516
|
+
const isKernel = pluginId.startsWith("@openlearn/");
|
|
8517
|
+
const rawCommandType = descriptor.commandType;
|
|
8518
|
+
const prefixed = isKernel || !rawCommandType || rawCommandType.startsWith(`${pluginId}.`) ? rawCommandType : resolvePluginCommandType(rawCommandType, pluginId);
|
|
8519
|
+
const effectiveDescriptor = prefixed !== rawCommandType ? { ...descriptor, commandType: prefixed } : descriptor;
|
|
8520
|
+
await actionRegistry.register(effectiveDescriptor);
|
|
8521
|
+
this.registeredActionIds.add(effectiveDescriptor.id);
|
|
8522
|
+
if (typeof effectiveDescriptor?.commandType === "string") {
|
|
8523
|
+
this.registeredActionCommandTypes.set(effectiveDescriptor.id, effectiveDescriptor.commandType);
|
|
8524
|
+
}
|
|
8525
|
+
if (prefixed !== rawCommandType) {
|
|
8526
|
+
try {
|
|
8527
|
+
const aliasId = `${descriptor.id}__raw_alias`;
|
|
8528
|
+
await actionRegistry.register({
|
|
8529
|
+
...descriptor,
|
|
8530
|
+
id: aliasId,
|
|
8531
|
+
exposeToAgent: false
|
|
8532
|
+
});
|
|
8533
|
+
this.registeredActionIds.add(aliasId);
|
|
8534
|
+
this.registeredActionCommandTypes.set(aliasId, rawCommandType);
|
|
8535
|
+
} catch {
|
|
8536
|
+
}
|
|
8487
8537
|
}
|
|
8488
8538
|
transport.postMessage({
|
|
8489
8539
|
type: "result",
|
|
@@ -8494,8 +8544,14 @@ var init_service_host = __esm({
|
|
|
8494
8544
|
} else if (msg.method === "unregister") {
|
|
8495
8545
|
const [actionId] = msg.args;
|
|
8496
8546
|
await actionRegistry.unregister(actionId);
|
|
8547
|
+
try {
|
|
8548
|
+
await actionRegistry.unregister(`${actionId}__raw_alias`);
|
|
8549
|
+
} catch {
|
|
8550
|
+
}
|
|
8497
8551
|
this.registeredActionIds.delete(actionId);
|
|
8552
|
+
this.registeredActionIds.delete(`${actionId}__raw_alias`);
|
|
8498
8553
|
this.registeredActionCommandTypes.delete(actionId);
|
|
8554
|
+
this.registeredActionCommandTypes.delete(`${actionId}__raw_alias`);
|
|
8499
8555
|
transport.postMessage({
|
|
8500
8556
|
type: "result",
|
|
8501
8557
|
invokeId: msg.invokeId,
|
|
@@ -15228,6 +15284,26 @@ var BuiltinPlugin = {
|
|
|
15228
15284
|
"page_meta"
|
|
15229
15285
|
// 分页与大纲元数据
|
|
15230
15286
|
];
|
|
15287
|
+
const isAllowedElementType = (type) => {
|
|
15288
|
+
if (!type || typeof type !== "string") return false;
|
|
15289
|
+
if (KNOWN_ELEMENT_TYPES2.includes(type)) return true;
|
|
15290
|
+
if (type.includes("/") || type.startsWith("plugin:") || type.startsWith("ext-") || type.startsWith("plugin-")) {
|
|
15291
|
+
return true;
|
|
15292
|
+
}
|
|
15293
|
+
try {
|
|
15294
|
+
const reg = pluginHost?.getContributionRegistry?.();
|
|
15295
|
+
if (reg) {
|
|
15296
|
+
const paletteItems = reg.getBySlot("editor.palette_item") || [];
|
|
15297
|
+
if (paletteItems.some((item) => item.type === type || item.id === type)) return true;
|
|
15298
|
+
const renderers = reg.getBySlot("whiteboard.renderer") || [];
|
|
15299
|
+
if (renderers.some((item) => item.targetType === type || item.id === type || item.elementType === type)) {
|
|
15300
|
+
return true;
|
|
15301
|
+
}
|
|
15302
|
+
}
|
|
15303
|
+
} catch {
|
|
15304
|
+
}
|
|
15305
|
+
return false;
|
|
15306
|
+
};
|
|
15231
15307
|
await actionRegistry.register({
|
|
15232
15308
|
id: "core-whiteboard-draw",
|
|
15233
15309
|
commandType: drawWhiteboardCmdType,
|
|
@@ -15251,8 +15327,8 @@ var BuiltinPlugin = {
|
|
|
15251
15327
|
await commandBus.registerHandler(drawWhiteboardCmdType, {
|
|
15252
15328
|
async execute(command) {
|
|
15253
15329
|
const payload = command.payload;
|
|
15254
|
-
if (!
|
|
15255
|
-
const hint = payload.type === "quiz" ? "" : payload.type && (payload.type.includes("quiz") || payload.type.includes("question")) ? ` \u63D0\u793A\uFF1A\u521B\u5EFA\u6D4B\u9A8C\u8BF7\u4F7F\u7528 quiz.create \u6216 quiz_pro.create \u5DE5\u5177\u3002` : ` \u5DF2\u77E5\u7C7B\u578B\uFF1A${KNOWN_ELEMENT_TYPES2.join(", ")}\u3002`;
|
|
15330
|
+
if (!isAllowedElementType(payload.type)) {
|
|
15331
|
+
const hint = payload.type === "quiz" ? "" : payload.type && (payload.type.includes("quiz") || payload.type.includes("question")) ? ` \u63D0\u793A\uFF1A\u521B\u5EFA\u6D4B\u9A8C\u8BF7\u4F7F\u7528 quiz.create \u6216 quiz_pro.create \u5DE5\u5177\u3002` : ` \u5DF2\u77E5\u7C7B\u578B\uFF1A${KNOWN_ELEMENT_TYPES2.join(", ")}\uFF0C\u6216\u63D2\u4EF6\u6269\u5C55\u56FE\u5143 (\u5982 ext-xxx/yyy)\u3002`;
|
|
15256
15332
|
throw new Error(`\u4E0D\u652F\u6301\u7684\u5143\u7D20\u7C7B\u578B "${payload.type}"\u3002${hint}`);
|
|
15257
15333
|
}
|
|
15258
15334
|
const elementId = (0, import_uuid7.v7)();
|
|
@@ -15494,7 +15570,7 @@ var BuiltinPlugin = {
|
|
|
15494
15570
|
);
|
|
15495
15571
|
const batchInsert = db2.transaction(() => {
|
|
15496
15572
|
for (const el of elements) {
|
|
15497
|
-
if (!
|
|
15573
|
+
if (!isAllowedElementType(el.type)) {
|
|
15498
15574
|
throw new Error(`\u4E0D\u652F\u6301\u7684\u5143\u7D20\u7C7B\u578B "${el.type}"\u3002\u5DF2\u77E5\u7C7B\u578B\uFF1A${KNOWN_ELEMENT_TYPES2.join(", ")}`);
|
|
15499
15575
|
}
|
|
15500
15576
|
const elementId = (0, import_uuid7.v7)();
|
|
@@ -19027,6 +19103,19 @@ function trustedInlineAllowlist(env = process.env) {
|
|
|
19027
19103
|
(env.OPENLEARN_TRUSTED_INLINE_PLUGINS ?? "").split(",").map((item) => item.trim()).filter((item) => item.length > 0)
|
|
19028
19104
|
);
|
|
19029
19105
|
}
|
|
19106
|
+
function matchesTrustedInlinePattern(pattern, manifestId) {
|
|
19107
|
+
if (!manifestId) return false;
|
|
19108
|
+
if (pattern === "*") return true;
|
|
19109
|
+
if (pattern.endsWith("*")) return manifestId.startsWith(pattern.slice(0, -1));
|
|
19110
|
+
return pattern === manifestId;
|
|
19111
|
+
}
|
|
19112
|
+
function isInTrustedInlineAllowlist(manifestId, env = process.env) {
|
|
19113
|
+
if (!manifestId) return false;
|
|
19114
|
+
for (const pattern of trustedInlineAllowlist(env)) {
|
|
19115
|
+
if (matchesTrustedInlinePattern(pattern, manifestId)) return true;
|
|
19116
|
+
}
|
|
19117
|
+
return false;
|
|
19118
|
+
}
|
|
19030
19119
|
function builtinPluginsDir() {
|
|
19031
19120
|
try {
|
|
19032
19121
|
const url = import_meta5.url;
|
|
@@ -19049,7 +19138,7 @@ function isInsideBuiltinPluginsDir(sourcePath, builtinDir = builtinPluginsDir())
|
|
|
19049
19138
|
}
|
|
19050
19139
|
function isTrustedInProcessPlugin(manifestId, env = process.env, sourcePath) {
|
|
19051
19140
|
if (isInsideBuiltinPluginsDir(sourcePath)) return true;
|
|
19052
|
-
if (manifestId
|
|
19141
|
+
if (isInTrustedInlineAllowlist(manifestId, env)) return true;
|
|
19053
19142
|
if (env.VITEST && env.OPENLEARN_ENFORCE_PLUGIN_ISOLATION !== "1") return true;
|
|
19054
19143
|
return false;
|
|
19055
19144
|
}
|
|
@@ -21037,7 +21126,7 @@ var import_node_fs11 = __toESM(require("node:fs"), 1);
|
|
|
21037
21126
|
var import_node_path13 = __toESM(require("node:path"), 1);
|
|
21038
21127
|
var import_node_url5 = require("node:url");
|
|
21039
21128
|
var import_meta7 = {};
|
|
21040
|
-
var FALLBACK_VERSION = "0.7.
|
|
21129
|
+
var FALLBACK_VERSION = "0.7.8";
|
|
21041
21130
|
function readPlatformVersion() {
|
|
21042
21131
|
const bases = [];
|
|
21043
21132
|
if (typeof __dirname !== "undefined") bases.push(__dirname);
|
|
@@ -32843,11 +32932,17 @@ function validateStartupEnv(env = process.env) {
|
|
|
32843
32932
|
severity: "warning",
|
|
32844
32933
|
message: "OPENLEARN_TRUSTED_INLINE_PLUGINS \u53EA\u542B\u7A7A\u767D/\u9017\u53F7\uFF0C\u7B49\u540C\u4E8E\u672A\u914D\u7F6E\u3002"
|
|
32845
32934
|
});
|
|
32935
|
+
} else if (ids.includes("*")) {
|
|
32936
|
+
warnings.push({
|
|
32937
|
+
key: "OPENLEARN_TRUSTED_INLINE_PLUGINS",
|
|
32938
|
+
severity: "warning",
|
|
32939
|
+
message: "OPENLEARN_TRUSTED_INLINE_PLUGINS \u542B\u901A\u914D\u7B26 `*`\uFF1A**\u6240\u6709**\u63D2\u4EF6\u90FD\u88AB\u89C6\u4E3A\u53D7\u4FE1\uFF0C\u53EF\u5728\u8FDB\u7A0B\u5185\u4EE5 inline/worker \u8FD0\u884C\uFF08\u542B\u65E5\u540E\u5B89\u88C5\u7684\u4EFB\u610F\u7B2C\u4E09\u65B9\u5305\uFF09\u3002\u5B83\u4EEC\u4E0E\u5BBF\u4E3B\u5171\u4EAB\u73AF\u5883\u53D8\u91CF\u4E0E\u7F51\u7EDC\u6808\uFF0C\u51FA\u7AD9\u7F51\u7EDC\u62E6\u622A\u5BF9\u5B83\u4EEC\u4E0D\u751F\u6548\uFF0C\u672A\u6355\u83B7\u5F02\u5E38\u53EF\u62D6\u57AE\u670D\u52A1\u3002\u4EC5\u9650\u672C\u5730\u5F00\u53D1\uFF1B\u751F\u4EA7\u73AF\u5883\u8BF7\u6539\u4E3A\u7CBE\u786E id \u6216\u524D\u7F00\u5F0F\uFF08\u5982 `@ext/*`\uFF09\u3002"
|
|
32940
|
+
});
|
|
32846
32941
|
} else if (isProduction) {
|
|
32847
32942
|
warnings.push({
|
|
32848
32943
|
key: "OPENLEARN_TRUSTED_INLINE_PLUGINS",
|
|
32849
32944
|
severity: "warning",
|
|
32850
|
-
message: `\u751F\u4EA7\u73AF\u5883\u4FE1\u4EFB\u4E86 ${ids.length} \u4E2A\
|
|
32945
|
+
message: `\u751F\u4EA7\u73AF\u5883\u4FE1\u4EFB\u4E86 ${ids.length} \u4E2A\u6761\u76EE\u5728**\u8FDB\u7A0B\u5185**\u8FD0\u884C\uFF1A${ids.join(", ")}\u3002 \u5B83\u4EEC\u4E0E\u5BBF\u4E3B\u5171\u4EAB\u73AF\u5883\u53D8\u91CF\u4E0E\u7F51\u7EDC\u6808\uFF0C\u51FA\u7AD9\u7F51\u7EDC\u62E6\u622A\u5BF9\u5B83\u4EEC\u4E0D\u751F\u6548\u3002 \u786E\u8BA4\u8FD9\u662F\u6709\u610F\u4E3A\u4E4B\uFF1B\u5426\u5219\u6E05\u7A7A\u8BE5\u53D8\u91CF\u8BA9\u5B83\u4EEC\u56DE\u5230 process \u5B50\u8FDB\u7A0B\u9694\u79BB\u3002`
|
|
32851
32946
|
});
|
|
32852
32947
|
}
|
|
32853
32948
|
}
|
|
@@ -33671,6 +33766,7 @@ var normalizeToolSchema = (schema) => {
|
|
|
33671
33766
|
};
|
|
33672
33767
|
var buildOpenAITools = (callerRole) => {
|
|
33673
33768
|
let actions = kernelContainer.actionRegistry.getAllActions();
|
|
33769
|
+
actions = actions.filter((action) => action.exposeToAgent !== false);
|
|
33674
33770
|
if (callerRole === "student") {
|
|
33675
33771
|
actions = actions.filter((action) => {
|
|
33676
33772
|
const cap = action.capabilityRequired || "";
|
|
@@ -34628,7 +34724,7 @@ function renderPrometheusMetrics(now = Date.now(), sample) {
|
|
|
34628
34724
|
// server/middleware/request-observability.ts
|
|
34629
34725
|
var REQUEST_ID_RE = /^[A-Za-z0-9._:-]{8,64}$/;
|
|
34630
34726
|
var QUIET_PATHS = /* @__PURE__ */ new Set(["/health", "/health/ready", "/", "/index.html", "/favicon.ico", "/robots.txt"]);
|
|
34631
|
-
var DEV_QUIET_PREFIXES = ["/@vite/", "/@fs/", "/src/", "/node_modules/", "/@id/"];
|
|
34727
|
+
var DEV_QUIET_PREFIXES = ["/@vite/", "/@fs/", "/src/", "/node_modules/", "/@id/", "/.vite-cache/"];
|
|
34632
34728
|
var DEFAULT_HTTP_SLOW_MS = 1e3;
|
|
34633
34729
|
function resolveHttpAccessLogMode(envAccessLog = process.env.HTTP_ACCESS_LOG, envLogLevel = process.env.LOG_LEVEL) {
|
|
34634
34730
|
const normalized = envAccessLog?.trim().toLowerCase();
|
|
@@ -36080,6 +36176,142 @@ post({ type: 'ready' });
|
|
|
36080
36176
|
</script>
|
|
36081
36177
|
</body></html>`;
|
|
36082
36178
|
|
|
36179
|
+
// server/utils/plugin-view-bridge.ts
|
|
36180
|
+
var PLUGIN_VIEW_CSP = "sandbox allow-scripts allow-forms allow-downloads; frame-ancestors 'self'; default-src 'self' 'unsafe-inline' data: blob: https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob: https:; script-src-attr 'unsafe-inline'; style-src 'self' 'unsafe-inline' data: blob: https:; style-src-attr 'unsafe-inline'; connect-src 'self' blob: data: https:;";
|
|
36181
|
+
var PLUGIN_VIEW_BRIDGE_JS = `(function () {
|
|
36182
|
+
'use strict';
|
|
36183
|
+
const CHANNEL = 'ol-plugin-view';
|
|
36184
|
+
let targetOrigin = '*';
|
|
36185
|
+
const pending = new Map();
|
|
36186
|
+
const initCallbacks = [];
|
|
36187
|
+
const contextUpdateCallbacks = [];
|
|
36188
|
+
let currentContext = null;
|
|
36189
|
+
|
|
36190
|
+
function post(msg) {
|
|
36191
|
+
if (window.parent && window.parent !== window) {
|
|
36192
|
+
window.parent.postMessage(Object.assign({ channel: CHANNEL }, msg), targetOrigin);
|
|
36193
|
+
}
|
|
36194
|
+
}
|
|
36195
|
+
|
|
36196
|
+
function call(op, args) {
|
|
36197
|
+
const callId = Math.random().toString(16).slice(2) + Date.now().toString(16);
|
|
36198
|
+
return new Promise(function (resolve, reject) {
|
|
36199
|
+
pending.set(callId, { resolve: resolve, reject: reject });
|
|
36200
|
+
post({ type: 'call', callId: callId, op: op, args: args || [] });
|
|
36201
|
+
});
|
|
36202
|
+
}
|
|
36203
|
+
|
|
36204
|
+
window.addEventListener('message', function (event) {
|
|
36205
|
+
const data = event.data;
|
|
36206
|
+
if (!data || data.channel !== CHANNEL) return;
|
|
36207
|
+
if (event.source !== window.parent) return;
|
|
36208
|
+
|
|
36209
|
+
if (data.type === 'init') {
|
|
36210
|
+
currentContext = data.payload || {};
|
|
36211
|
+
targetOrigin = event.origin && event.origin !== 'null' ? event.origin : '*';
|
|
36212
|
+
for (let i = 0; i < initCallbacks.length; i++) {
|
|
36213
|
+
try {
|
|
36214
|
+
initCallbacks[i](currentContext);
|
|
36215
|
+
} catch (e) {
|
|
36216
|
+
console.error('[OpenLearnPluginBridge] init callback error:', e);
|
|
36217
|
+
}
|
|
36218
|
+
}
|
|
36219
|
+
return;
|
|
36220
|
+
}
|
|
36221
|
+
|
|
36222
|
+
if (data.type === 'context-update') {
|
|
36223
|
+
currentContext = Object.assign({}, currentContext, data.payload || {});
|
|
36224
|
+
for (let i = 0; i < contextUpdateCallbacks.length; i++) {
|
|
36225
|
+
try {
|
|
36226
|
+
contextUpdateCallbacks[i](currentContext);
|
|
36227
|
+
} catch (e) {
|
|
36228
|
+
console.error('[OpenLearnPluginBridge] contextUpdate callback error:', e);
|
|
36229
|
+
}
|
|
36230
|
+
}
|
|
36231
|
+
return;
|
|
36232
|
+
}
|
|
36233
|
+
|
|
36234
|
+
if (data.type === 'reply') {
|
|
36235
|
+
const waiter = pending.get(data.callId);
|
|
36236
|
+
if (!waiter) return;
|
|
36237
|
+
pending.delete(data.callId);
|
|
36238
|
+
if (data.ok) {
|
|
36239
|
+
waiter.resolve(data.value);
|
|
36240
|
+
} else {
|
|
36241
|
+
waiter.reject(new Error(data.error || 'bridge call failed'));
|
|
36242
|
+
}
|
|
36243
|
+
return;
|
|
36244
|
+
}
|
|
36245
|
+
});
|
|
36246
|
+
|
|
36247
|
+
const bridge = {
|
|
36248
|
+
CHANNEL: CHANNEL,
|
|
36249
|
+
onInit: function (callback) {
|
|
36250
|
+
if (typeof callback === 'function') {
|
|
36251
|
+
if (currentContext) {
|
|
36252
|
+
try {
|
|
36253
|
+
callback(currentContext);
|
|
36254
|
+
} catch (e) {
|
|
36255
|
+
console.error('[OpenLearnPluginBridge] onInit callback error:', e);
|
|
36256
|
+
}
|
|
36257
|
+
}
|
|
36258
|
+
initCallbacks.push(callback);
|
|
36259
|
+
}
|
|
36260
|
+
return bridge;
|
|
36261
|
+
},
|
|
36262
|
+
onContextUpdate: function (callback) {
|
|
36263
|
+
if (typeof callback === 'function') {
|
|
36264
|
+
contextUpdateCallbacks.push(callback);
|
|
36265
|
+
}
|
|
36266
|
+
return bridge;
|
|
36267
|
+
},
|
|
36268
|
+
getContext: function () {
|
|
36269
|
+
return currentContext;
|
|
36270
|
+
},
|
|
36271
|
+
ui: {
|
|
36272
|
+
toast: function (message, type) {
|
|
36273
|
+
return call('ui.toast', [message, type || 'info']);
|
|
36274
|
+
},
|
|
36275
|
+
showToast: function (message, type) {
|
|
36276
|
+
return call('ui.toast', [message, type || 'info']);
|
|
36277
|
+
},
|
|
36278
|
+
resize: function (height) {
|
|
36279
|
+
post({ type: 'resize', height: Number(height) || 0 });
|
|
36280
|
+
},
|
|
36281
|
+
},
|
|
36282
|
+
invokeCommand: function (type, payload) {
|
|
36283
|
+
return call('command.invoke', [type, payload]);
|
|
36284
|
+
},
|
|
36285
|
+
api: {
|
|
36286
|
+
get: function (path) {
|
|
36287
|
+
return call('api.get', [path]);
|
|
36288
|
+
},
|
|
36289
|
+
post: function (path, body) {
|
|
36290
|
+
return call('api.post', [path, body]);
|
|
36291
|
+
},
|
|
36292
|
+
del: function (path) {
|
|
36293
|
+
return call('api.del', [path]);
|
|
36294
|
+
},
|
|
36295
|
+
},
|
|
36296
|
+
storage: {
|
|
36297
|
+
get: function (key) {
|
|
36298
|
+
return call('storage.get', [key]);
|
|
36299
|
+
},
|
|
36300
|
+
set: function (key, value) {
|
|
36301
|
+
return call('storage.set', [key, value]);
|
|
36302
|
+
},
|
|
36303
|
+
delete: function (key) {
|
|
36304
|
+
return call('storage.delete', [key]);
|
|
36305
|
+
},
|
|
36306
|
+
},
|
|
36307
|
+
};
|
|
36308
|
+
|
|
36309
|
+
window.OpenLearnPluginBridge = bridge;
|
|
36310
|
+
// \u901A\u77E5\u5BBF\u4E3B\u89C6\u56FE\u6C99\u7BB1 iframe \u5DF2\u5C31\u7EEA
|
|
36311
|
+
post({ type: 'ready' });
|
|
36312
|
+
})();
|
|
36313
|
+
`;
|
|
36314
|
+
|
|
36083
36315
|
// packages/core/bootstrap/adapter/bootstrap-registration.ts
|
|
36084
36316
|
var BootstrapRegistration = class {
|
|
36085
36317
|
static registerConfiguration(builder, context) {
|
|
@@ -42515,7 +42747,8 @@ async function importCoursePackage(pkg, options) {
|
|
|
42515
42747
|
let elements = [];
|
|
42516
42748
|
try {
|
|
42517
42749
|
elements = pkg.elements.map((element) => {
|
|
42518
|
-
|
|
42750
|
+
const isAllowed = KNOWN_ELEMENT_TYPES.has(element.type) || typeof element.type === "string" && (element.type.includes("/") || element.type.startsWith("plugin:") || element.type.startsWith("ext-") || element.type.startsWith("plugin-"));
|
|
42751
|
+
if (!isAllowed) {
|
|
42519
42752
|
throw new Error(`\u8BFE\u7A0B\u5305\u5305\u542B\u4E0D\u652F\u6301\u7684\u767D\u677F\u5143\u7D20\u7C7B\u578B\uFF1A${element.type}`);
|
|
42520
42753
|
}
|
|
42521
42754
|
const data = resolveAssetPlaceholders(element.data, materialized);
|
|
@@ -43365,6 +43598,14 @@ function sanitizePrivateNotes(raw) {
|
|
|
43365
43598
|
}
|
|
43366
43599
|
|
|
43367
43600
|
// server/services/roster-service.ts
|
|
43601
|
+
function normalizeStudentGender(value) {
|
|
43602
|
+
if (typeof value !== "string") return null;
|
|
43603
|
+
const v = value.trim().toLowerCase();
|
|
43604
|
+
if (!v) return null;
|
|
43605
|
+
if (["male", "m", "boy", "\u7537", "\u7537\u751F"].includes(v)) return "male";
|
|
43606
|
+
if (["female", "f", "girl", "\u5973", "\u5973\u751F"].includes(v)) return "female";
|
|
43607
|
+
return null;
|
|
43608
|
+
}
|
|
43368
43609
|
var RosterService = class {
|
|
43369
43610
|
constructor(db2 = kernelContainer.db, readDb, pluginHost) {
|
|
43370
43611
|
this.db = db2;
|
|
@@ -43657,7 +43898,7 @@ var RosterService = class {
|
|
|
43657
43898
|
const params = ownerScoped ? [scope.userId || "", scope.username || ""] : [];
|
|
43658
43899
|
const total = this.reader.prepare(`SELECT COUNT(*) AS n FROM students s ${where}`).get(...params).n;
|
|
43659
43900
|
const students = this.reader.prepare(
|
|
43660
|
-
`SELECT s.id, s.student_number, s.name, s.email, s.avatar, s.locked_lesson_id, s.lock_mode, s.lock_target, s.private_notes, s.created_at
|
|
43901
|
+
`SELECT s.id, s.student_number, s.name, s.email, s.avatar, s.gender, s.locked_lesson_id, s.lock_mode, s.lock_target, s.private_notes, s.created_at
|
|
43661
43902
|
FROM students s ${where}
|
|
43662
43903
|
ORDER BY s.created_at DESC LIMIT ? OFFSET ?`
|
|
43663
43904
|
).all(
|
|
@@ -43693,6 +43934,7 @@ var RosterService = class {
|
|
|
43693
43934
|
}
|
|
43694
43935
|
createStudent(params) {
|
|
43695
43936
|
const { name, email, student_number, password } = params;
|
|
43937
|
+
const gender = normalizeStudentGender(params.gender);
|
|
43696
43938
|
if (!name?.trim()) throw new Error("Student name is required");
|
|
43697
43939
|
const studentId = randomId();
|
|
43698
43940
|
let finalNum = student_number && student_number.trim() !== "" ? student_number.trim() : "";
|
|
@@ -43709,7 +43951,9 @@ var RosterService = class {
|
|
|
43709
43951
|
finalPlain = initialPassword;
|
|
43710
43952
|
}
|
|
43711
43953
|
const hashedPassword = hashPassword(finalPlain);
|
|
43712
|
-
this.db.prepare(
|
|
43954
|
+
this.db.prepare(
|
|
43955
|
+
"INSERT INTO students (id, student_number, name, email, password, gender, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
43956
|
+
).run(studentId, finalNum, name.trim(), email || "", hashedPassword, gender, Date.now());
|
|
43713
43957
|
return {
|
|
43714
43958
|
id: studentId,
|
|
43715
43959
|
student_number: finalNum,
|
|
@@ -43718,6 +43962,9 @@ var RosterService = class {
|
|
|
43718
43962
|
}
|
|
43719
43963
|
updateStudent(id, params) {
|
|
43720
43964
|
const { name, email, student_number, password, locked_lesson_id, lock_mode, lock_target, private_notes } = params;
|
|
43965
|
+
if (params.gender !== void 0) {
|
|
43966
|
+
this.db.prepare("UPDATE students SET gender = ? WHERE id = ?").run(normalizeStudentGender(params.gender), id);
|
|
43967
|
+
}
|
|
43721
43968
|
if (name) this.db.prepare("UPDATE students SET name = ? WHERE id = ?").run(name.trim(), id);
|
|
43722
43969
|
if (email !== void 0)
|
|
43723
43970
|
this.db.prepare("UPDATE students SET email = ? WHERE id = ?").run(email?.trim() || null, id);
|
|
@@ -43835,7 +44082,7 @@ var RosterService = class {
|
|
|
43835
44082
|
getClassStudents(classId) {
|
|
43836
44083
|
return this.reader.prepare(
|
|
43837
44084
|
`
|
|
43838
|
-
SELECT s.id, s.student_number, s.name, s.email, s.avatar, s.locked_lesson_id, s.lock_mode, s.lock_target, s.private_notes, s.created_at, cs.joined_at
|
|
44085
|
+
SELECT s.id, s.student_number, s.name, s.email, s.avatar, s.gender, s.locked_lesson_id, s.lock_mode, s.lock_target, s.private_notes, s.created_at, cs.joined_at
|
|
43839
44086
|
FROM students s
|
|
43840
44087
|
INNER JOIN class_students cs ON s.id = cs.student_id
|
|
43841
44088
|
WHERE cs.class_id = ?
|
|
@@ -43896,13 +44143,14 @@ var RosterService = class {
|
|
|
43896
44143
|
email,
|
|
43897
44144
|
studentNumber,
|
|
43898
44145
|
notes,
|
|
44146
|
+
gender: normalizeStudentGender(st?.gender),
|
|
43899
44147
|
plainPassword,
|
|
43900
44148
|
autoPassword,
|
|
43901
44149
|
hashedPassword: hashPassword(plainPassword)
|
|
43902
44150
|
});
|
|
43903
44151
|
}
|
|
43904
44152
|
const insertStudent = this.db.prepare(
|
|
43905
|
-
"INSERT INTO students (id, student_number, name, email, password, private_notes, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
44153
|
+
"INSERT INTO students (id, student_number, name, email, password, private_notes, gender, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
|
43906
44154
|
);
|
|
43907
44155
|
const findByEmail = this.db.prepare("SELECT id, student_number, name, email FROM students WHERE email = ?");
|
|
43908
44156
|
const findByNumber = this.db.prepare(
|
|
@@ -43929,7 +44177,16 @@ var RosterService = class {
|
|
|
43929
44177
|
}
|
|
43930
44178
|
const studentId = randomId("stu_");
|
|
43931
44179
|
const finalNum = row.studentNumber || generateStudentNumber(this.db) || `ST_${studentId}`;
|
|
43932
|
-
insertStudent.run(
|
|
44180
|
+
insertStudent.run(
|
|
44181
|
+
studentId,
|
|
44182
|
+
finalNum,
|
|
44183
|
+
row.name,
|
|
44184
|
+
row.email || null,
|
|
44185
|
+
row.hashedPassword,
|
|
44186
|
+
row.notes || null,
|
|
44187
|
+
row.gender,
|
|
44188
|
+
now
|
|
44189
|
+
);
|
|
43933
44190
|
results.push({
|
|
43934
44191
|
id: studentId,
|
|
43935
44192
|
name: row.name,
|
|
@@ -49881,7 +50138,9 @@ async function assertCommunityZipAttestation(req, zipPath, targetPluginId) {
|
|
|
49881
50138
|
integrity: verifyRes.integrity
|
|
49882
50139
|
};
|
|
49883
50140
|
}
|
|
49884
|
-
|
|
50141
|
+
const allowUnsignedEnv = String(process.env.OPENLEARN_ALLOW_UNSIGNED ?? "").toLowerCase();
|
|
50142
|
+
const allowUnsignedEffective = allowUnsigned || !["0", "false"].includes(allowUnsignedEnv);
|
|
50143
|
+
if (!allowUnsignedEffective) {
|
|
49885
50144
|
return {
|
|
49886
50145
|
ok: false,
|
|
49887
50146
|
status: 400,
|
|
@@ -50123,6 +50382,25 @@ function registerPluginsRoutes(ctx) {
|
|
|
50123
50382
|
})
|
|
50124
50383
|
);
|
|
50125
50384
|
});
|
|
50385
|
+
app.get("/api/plugins/isolation-policy", requireAuth("administrator"), (req, res) => {
|
|
50386
|
+
const manifestId = String(req.query.manifestId || "").trim();
|
|
50387
|
+
if (!manifestId) {
|
|
50388
|
+
return res.status(400).json({ success: false, error: "manifestId is required" });
|
|
50389
|
+
}
|
|
50390
|
+
const requestedRaw = String(req.query.requestedMode || "").toLowerCase();
|
|
50391
|
+
const requested = isValidExecutionMode(requestedRaw) ? requestedRaw : void 0;
|
|
50392
|
+
const trusted = isTrustedInProcessPlugin(manifestId);
|
|
50393
|
+
const decision = resolvePluginExecutionMode(manifestId, requested);
|
|
50394
|
+
res.json({
|
|
50395
|
+
success: true,
|
|
50396
|
+
manifestId,
|
|
50397
|
+
trusted,
|
|
50398
|
+
requestedMode: requested ?? null,
|
|
50399
|
+
resolvedMode: decision.mode,
|
|
50400
|
+
coerced: decision.coerced,
|
|
50401
|
+
allowlistSize: trustedInlineAllowlist().size
|
|
50402
|
+
});
|
|
50403
|
+
});
|
|
50126
50404
|
app.get("/api/plugins/by-manifest/:manifestId(*)", requireScopedAuth("authenticated"), (req, res) => {
|
|
50127
50405
|
try {
|
|
50128
50406
|
const manifestId = decodeURIComponent(req.params.manifestId);
|
|
@@ -50139,7 +50417,12 @@ function registerPluginsRoutes(ctx) {
|
|
|
50139
50417
|
status: found.status,
|
|
50140
50418
|
state: found.state,
|
|
50141
50419
|
manifest: found.manifest,
|
|
50142
|
-
isSystem: kernelContainer.pluginHost.isOfficialPlugin(found.pluginId, manifestId)
|
|
50420
|
+
isSystem: kernelContainer.pluginHost.isOfficialPlugin(found.pluginId, manifestId),
|
|
50421
|
+
// 升级路径的回填:按库里存的请求值 + 当前信任策略算出**实际会用的**档位。
|
|
50422
|
+
isolationMode: resolvePluginExecutionMode(
|
|
50423
|
+
manifestId,
|
|
50424
|
+
kernelContainer.db.prepare("SELECT execution_mode FROM plugins WHERE id = ?").get(found.pluginId)?.execution_mode
|
|
50425
|
+
).mode
|
|
50143
50426
|
});
|
|
50144
50427
|
} catch (e) {
|
|
50145
50428
|
sendSafeError(res, e);
|
|
@@ -57699,10 +57982,16 @@ async function startServer() {
|
|
|
57699
57982
|
})
|
|
57700
57983
|
);
|
|
57701
57984
|
const loginLimiter = createLoginLimiter(process.env.PLAYWRIGHT_TEST ? 1e3 : 5);
|
|
57702
|
-
const
|
|
57985
|
+
const isDev = process.env.NODE_ENV !== "production";
|
|
57986
|
+
const defaultWriteLimit = process.env.PLAYWRIGHT_TEST ? 1e4 : isDev ? 600 : 180;
|
|
57987
|
+
const writeLimiter = createWriteLimiter(defaultWriteLimit);
|
|
57988
|
+
const viewStateLimiter = createWriteLimiter(process.env.PLAYWRIGHT_TEST ? 1e4 : isDev ? 600 : 120);
|
|
57703
57989
|
const aiLimiter = createAiLimiter(process.env.PLAYWRIGHT_TEST ? 1e3 : 10);
|
|
57704
57990
|
app.use((req, res, next) => {
|
|
57705
57991
|
if (req.method === "POST" || req.method === "PUT" || req.method === "DELETE") {
|
|
57992
|
+
if (/^\/api\/classroom\/sessions\/[^/]+\/view-state\/?$/.test(req.path)) {
|
|
57993
|
+
return viewStateLimiter(req, res, next);
|
|
57994
|
+
}
|
|
57706
57995
|
return writeLimiter(req, res, next);
|
|
57707
57996
|
}
|
|
57708
57997
|
next();
|
|
@@ -57742,18 +58031,35 @@ async function startServer() {
|
|
|
57742
58031
|
app.use(
|
|
57743
58032
|
"/plugins",
|
|
57744
58033
|
(req, res, next) => {
|
|
57745
|
-
const token = getCookieToken(req);
|
|
57746
|
-
if (
|
|
57747
|
-
|
|
58034
|
+
const token = getCookieToken(req) || (typeof req.query.token === "string" ? req.query.token : null) || (req.headers.authorization?.startsWith("Bearer ") ? req.headers.authorization.slice(7).trim() : null);
|
|
58035
|
+
if (token) {
|
|
58036
|
+
const session = getValidSession(token);
|
|
58037
|
+
if (session) {
|
|
58038
|
+
return next();
|
|
58039
|
+
}
|
|
57748
58040
|
}
|
|
57749
|
-
const
|
|
57750
|
-
|
|
57751
|
-
|
|
58041
|
+
const dest = req.headers["sec-fetch-dest"];
|
|
58042
|
+
const site = req.headers["sec-fetch-site"];
|
|
58043
|
+
if (site === "same-origin" || typeof dest === "string" && dest !== "" && dest !== "document") {
|
|
58044
|
+
return next();
|
|
57752
58045
|
}
|
|
57753
|
-
|
|
58046
|
+
return res.status(401).json({ error: "Authentication required" });
|
|
57754
58047
|
},
|
|
57755
|
-
import_express4.default.static(getPluginsDir(), {
|
|
58048
|
+
import_express4.default.static(getPluginsDir(), {
|
|
58049
|
+
index: false,
|
|
58050
|
+
dotfiles: "ignore",
|
|
58051
|
+
fallthrough: false,
|
|
58052
|
+
maxAge: 0,
|
|
58053
|
+
setHeaders: (res, filePath) => {
|
|
58054
|
+
if (filePath.endsWith(".html") || filePath.endsWith(".htm")) {
|
|
58055
|
+
res.setHeader("Content-Security-Policy", PLUGIN_VIEW_CSP);
|
|
58056
|
+
}
|
|
58057
|
+
}
|
|
58058
|
+
})
|
|
57756
58059
|
);
|
|
58060
|
+
app.get("/plugin-sandbox/view-bridge.js", (_req, res) => {
|
|
58061
|
+
res.status(200).setHeader("Content-Type", "application/javascript; charset=utf-8").setHeader("Cache-Control", "public, max-age=3600").send(PLUGIN_VIEW_BRIDGE_JS);
|
|
58062
|
+
});
|
|
57757
58063
|
app.get("/plugin-sandbox/frame", (req, res) => {
|
|
57758
58064
|
const token = getCookieToken(req);
|
|
57759
58065
|
const session = token ? getValidSession(token) : null;
|
package/migrations/README.md
CHANGED
|
@@ -59,3 +59,4 @@
|
|
|
59
59
|
| 041 | 041_course_package_idempotency.sql | 课程包导入幂等(G-06):同一包重复导入不再产生重复课程 |
|
|
60
60
|
| 042 | 042_assignment_grade_resubmit_audit.sql | 学生重交审计:`assignment_grade_history` 补 `old_content`,可追溯「这份分数是针对哪份内容给的」 |
|
|
61
61
|
| 043 | 043_mark_sha256_passwords.sql | Issue #227:把仍是无盐 SHA-256 摘要的存量账号标记为 `must_change_password = 1`,强制走既有改密流程(SHA-256 不可逆,无法反推原口令) |
|
|
62
|
+
| 044 | 044_students_gender.sql | 学生性别 `students.gender`(male / female / NULL),供课堂专注力监控头像按性别渲染;历史数据为空,由教师新建时补录 |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "数据库结构金样(自动生成,勿手改):空库跑完 migrations/ 全部迁移后的表→列快照。启动时 assertSchemaMatchesGolden 以子集校验(实际库缺金样结构即拒绝启动;多出的表/列不报错)。改迁移后请运行 pnpm db:gen-golden 再生成。",
|
|
3
|
-
"migrationCount":
|
|
4
|
-
"generatedAt": "2026-10-
|
|
3
|
+
"migrationCount": 45,
|
|
4
|
+
"generatedAt": "2026-10-29T08:14:15.956Z",
|
|
5
5
|
"tables": {
|
|
6
6
|
"_academic_year_backfill": ["backfilled_at", "id"],
|
|
7
7
|
"_class_passcode_encryption": ["id", "note"],
|
|
@@ -598,6 +598,7 @@
|
|
|
598
598
|
"avatar",
|
|
599
599
|
"created_at",
|
|
600
600
|
"email",
|
|
601
|
+
"gender",
|
|
601
602
|
"id",
|
|
602
603
|
"lock_mode",
|
|
603
604
|
"lock_target",
|