openlearn-next 0.2.1 → 0.2.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/dist/index.html CHANGED
@@ -15,9 +15,9 @@
15
15
  }
16
16
  }
17
17
  </script>
18
- <script type="module" crossorigin src="/assets/index-zi7zNIte.js"></script>
18
+ <script type="module" crossorigin src="/assets/index-D_zAVH7u.js"></script>
19
19
  <link rel="stylesheet" crossorigin href="/assets/vendor-reveal-DcQQzuI9.css">
20
- <link rel="stylesheet" crossorigin href="/assets/index-CaJDJGky.css">
20
+ <link rel="stylesheet" crossorigin href="/assets/index-CsN5CIqI.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
package/dist/server.cjs CHANGED
@@ -18418,9 +18418,69 @@ var BRIDGE_SDK_CODE = `(function() {
18418
18418
  } catch (err) {}
18419
18419
  }
18420
18420
 
18421
+ // \u2500\u2500 Helper: create a safe postMessage wrapper that normalises 'null' \u2192 '*' \u2500\u2500
18422
+ function __makeSafePostMessage(realTarget, label) {
18423
+ return function(message, targetOrigin, transfer) {
18424
+ try {
18425
+ if (message && typeof message === 'object') {
18426
+ if (!message.attempt_id && window.__LMS_STUDENT__?.attempt_id) {
18427
+ message.attempt_id = window.__LMS_STUDENT__.attempt_id;
18428
+ }
18429
+ if (!message.uuid && window.__LMS_COURSEWARE__?.uuid) {
18430
+ message.uuid = window.__LMS_COURSEWARE__.uuid;
18431
+ }
18432
+ }
18433
+ } catch (e) {}
18434
+
18435
+ var origin = targetOrigin;
18436
+ if (origin === 'null' || origin === null || origin === undefined) {
18437
+ console.warn('[LMS Bridge Notice] Normalized invalid targetOrigin "' + origin + '" to "*" for ' + label);
18438
+ origin = '*';
18439
+ }
18440
+ try {
18441
+ return realTarget.postMessage.call(realTarget, message, origin, transfer);
18442
+ } catch (err) {
18443
+ if (err.name === 'SyntaxError' && origin !== '*') {
18444
+ console.warn('[LMS Bridge Notice] Recovered SyntaxError on ' + label + ', fallback to "*"', err);
18445
+ return realTarget.postMessage.call(realTarget, message, '*', transfer);
18446
+ }
18447
+ throw err;
18448
+ }
18449
+ };
18450
+ }
18451
+
18452
+ // \u2500\u2500 Helper: create a Proxy wrapper around a cross-origin WindowProxy \u2500\u2500
18453
+ // This is necessary because sandboxed iframes (without allow-same-origin)
18454
+ // cannot set properties on cross-origin WindowProxy objects.
18455
+ // We use Object.defineProperty to shadow window.parent / window.top
18456
+ // with a Proxy that intercepts the .postMessage() call.
18457
+ function __proxyWindow(realRef, propName) {
18458
+ try {
18459
+ var safePost = __makeSafePostMessage(realRef, propName + '.postMessage');
18460
+ var proxyObj = new Proxy(realRef, {
18461
+ get: function(target, prop) {
18462
+ if (prop === 'postMessage') return safePost;
18463
+ try {
18464
+ var val = target[prop];
18465
+ if (typeof val === 'function') return val.bind(target);
18466
+ return val;
18467
+ } catch (e) { return undefined; }
18468
+ }
18469
+ });
18470
+ Object.defineProperty(window, propName, {
18471
+ get: function() { return proxyObj; },
18472
+ configurable: true
18473
+ });
18474
+ } catch (e) {
18475
+ // Proxy or defineProperty not supported, fall back to direct override attempt
18476
+ try { realRef.postMessage = __makeSafePostMessage(realRef, propName + '.postMessage (fallback)'); } catch (_) {}
18477
+ }
18478
+ }
18479
+
18421
18480
  // Proxy postMessage calls to enrich them with attempt_id/uuid and normalize targetOrigin
18422
18481
  try {
18423
- const originalPostMessage = window.postMessage;
18482
+ // 1. Override window.postMessage (self-targeting, always works)
18483
+ var originalPostMessage = window.postMessage;
18424
18484
  window.postMessage = function(message, targetOrigin, transfer) {
18425
18485
  try {
18426
18486
  if (message && typeof message === 'object') {
@@ -18433,49 +18493,67 @@ var BRIDGE_SDK_CODE = `(function() {
18433
18493
  }
18434
18494
  } catch (e) {}
18435
18495
 
18436
- let origin = targetOrigin;
18437
- if (origin === 'null') {
18496
+ var origin = targetOrigin;
18497
+ if (origin === 'null' || origin === null || origin === undefined) {
18498
+ console.warn('[LMS Bridge Notice] Normalized invalid targetOrigin "null" to "*" for postMessage call');
18438
18499
  origin = '*';
18439
18500
  }
18440
18501
  try {
18441
18502
  return originalPostMessage.call(this, message, origin, transfer);
18442
18503
  } catch (err) {
18443
18504
  if (err.name === 'SyntaxError' && origin !== '*') {
18505
+ console.warn('[LMS Bridge Notice] Recovered SyntaxError on postMessage, fallback targetOrigin to "*"', err);
18444
18506
  return originalPostMessage.call(this, message, '*', transfer);
18445
18507
  }
18446
18508
  throw err;
18447
18509
  }
18448
18510
  };
18449
18511
 
18512
+ // 2. Shadow window.parent with a Proxy that intercepts .postMessage()
18450
18513
  if (window.parent && window.parent !== window) {
18451
- const parentPostMessage = window.parent.postMessage;
18452
- try {
18453
- window.parent.postMessage = function(message, targetOrigin, transfer) {
18454
- try {
18455
- if (message && typeof message === 'object') {
18456
- if (!message.attempt_id && window.__LMS_STUDENT__?.attempt_id) {
18457
- message.attempt_id = window.__LMS_STUDENT__.attempt_id;
18458
- }
18459
- if (!message.uuid && window.__LMS_COURSEWARE__?.uuid) {
18460
- message.uuid = window.__LMS_COURSEWARE__.uuid;
18514
+ __proxyWindow(window.parent, 'parent');
18515
+ }
18516
+
18517
+ // 3. Shadow window.top with a Proxy that intercepts .postMessage()
18518
+ try {
18519
+ if (window.top && window.top !== window) {
18520
+ __proxyWindow(window.top, 'top');
18521
+ }
18522
+ } catch (e) {}
18523
+
18524
+ // 4. Intercept message event listeners to sanitize event.source.postMessage replies
18525
+ var origAddEventListener = window.addEventListener;
18526
+ if (typeof origAddEventListener === 'function') {
18527
+ window.addEventListener = function(type, listener, options) {
18528
+ if (type === 'message' && typeof listener === 'function') {
18529
+ var wrappedListener = function(event) {
18530
+ try {
18531
+ if (event && event.source && typeof event.source.postMessage === 'function') {
18532
+ var origSourcePostMessage = event.source.postMessage;
18533
+ event.source.postMessage = function(msg, targetOrigin, transfer) {
18534
+ var origin = targetOrigin;
18535
+ if (origin === 'null' || origin === null) {
18536
+ console.warn('[LMS Bridge Notice] Normalized invalid targetOrigin "null" to "*" on event.source.postMessage');
18537
+ origin = '*';
18538
+ }
18539
+ try {
18540
+ return origSourcePostMessage.call(event.source, msg, origin, transfer);
18541
+ } catch (err) {
18542
+ if (err.name === 'SyntaxError' && origin !== '*') {
18543
+ console.warn('[LMS Bridge Notice] Recovered SyntaxError on event.source.postMessage, fallback to "*"', err);
18544
+ return origSourcePostMessage.call(event.source, msg, '*', transfer);
18545
+ }
18546
+ throw err;
18547
+ }
18548
+ };
18461
18549
  }
18462
- }
18463
- } catch (e) {}
18464
-
18465
- let origin = targetOrigin;
18466
- if (origin === 'null') {
18467
- origin = '*';
18468
- }
18469
- try {
18470
- return parentPostMessage.call(window.parent, message, origin, transfer);
18471
- } catch (err) {
18472
- if (err.name === 'SyntaxError' && origin !== '*') {
18473
- return parentPostMessage.call(window.parent, message, '*', transfer);
18474
- }
18475
- throw err;
18476
- }
18477
- };
18478
- } catch (e) {}
18550
+ } catch (e) {}
18551
+ return listener.apply(this, arguments);
18552
+ };
18553
+ return origAddEventListener.call(this, type, wrappedListener, options);
18554
+ }
18555
+ return origAddEventListener.apply(this, arguments);
18556
+ };
18479
18557
  }
18480
18558
  } catch (e) {}
18481
18559
 
@@ -20001,7 +20079,8 @@ function registerBridgeRoutes(ctx) {
20001
20079
  res.setHeader("Access-Control-Allow-Origin", "*");
20002
20080
  res.send(BRIDGE_SDK_CODE);
20003
20081
  });
20004
- app.get("/runtime/:uuid", (req, res) => {
20082
+ app.get("/runtime/:uuid", (req, res, next) => {
20083
+ if (req.path.endsWith("/")) return next();
20005
20084
  res.redirect(`/runtime/${req.params.uuid}/`);
20006
20085
  });
20007
20086
  app.get("/runtime/:uuid/*", (req, res) => {
@@ -20061,6 +20140,7 @@ function registerBridgeRoutes(ctx) {
20061
20140
 
20062
20141
  // server/routes/lessons.ts
20063
20142
  var import_genai3 = require("@google/genai");
20143
+ var import_uuid16 = require("uuid");
20064
20144
  function registerLessonsRoutes(ctx) {
20065
20145
  const {
20066
20146
  app,
@@ -20470,6 +20550,72 @@ Provide a short, friendly, and helpful hint (1-2 sentences) directly related to
20470
20550
  res.status(500).json({ error: e.message });
20471
20551
  }
20472
20552
  });
20553
+ app.delete("/api/lessons/:id", async (req, res) => {
20554
+ try {
20555
+ const { id } = req.params;
20556
+ kernelContainer.db.prepare("DELETE FROM whiteboard_elements WHERE lesson_id = ?").run(id);
20557
+ kernelContainer.db.prepare("DELETE FROM student_lesson_progress WHERE lesson_id = ?").run(id);
20558
+ kernelContainer.db.prepare("DELETE FROM schedules WHERE lesson_id = ?").run(id);
20559
+ kernelContainer.db.prepare("DELETE FROM assignments WHERE lesson_id = ?").run(id);
20560
+ const result = kernelContainer.db.prepare("DELETE FROM lessons WHERE id = ?").run(id);
20561
+ if (result.changes === 0) {
20562
+ return res.status(404).json({ error: "Lesson not found" });
20563
+ }
20564
+ res.json({ success: true });
20565
+ } catch (e) {
20566
+ res.status(500).json({ error: e.message });
20567
+ }
20568
+ });
20569
+ app.get("/api/lessons/:id/stats", (req, res) => {
20570
+ try {
20571
+ const { id } = req.params;
20572
+ const whiteboardCount = kernelContainer.db.prepare(
20573
+ "SELECT COUNT(*) as count FROM whiteboard_elements WHERE lesson_id = ?"
20574
+ ).get(id).count;
20575
+ const scheduleCount = kernelContainer.db.prepare(
20576
+ "SELECT COUNT(*) as count FROM schedules WHERE lesson_id = ?"
20577
+ ).get(id).count;
20578
+ const enrollmentCount = kernelContainer.db.prepare(
20579
+ "SELECT COUNT(*) as count FROM student_lesson_progress WHERE lesson_id = ?"
20580
+ ).get(id).count;
20581
+ const assignmentCount = kernelContainer.db.prepare(
20582
+ "SELECT COUNT(*) as count FROM assignments WHERE lesson_id = ?"
20583
+ ).get(id).count;
20584
+ res.json({ whiteboardCount, scheduleCount, enrollmentCount, assignmentCount });
20585
+ } catch (e) {
20586
+ res.status(500).json({ error: e.message });
20587
+ }
20588
+ });
20589
+ app.post("/api/lessons/:id/clone", async (req, res) => {
20590
+ try {
20591
+ const { id } = req.params;
20592
+ const original = kernelContainer.db.prepare("SELECT * FROM lessons WHERE id = ?").get(id);
20593
+ if (!original) {
20594
+ return res.status(404).json({ error: "Lesson not found" });
20595
+ }
20596
+ const newId = (0, import_uuid16.v7)();
20597
+ const now = Date.now();
20598
+ const newTitle = `\u526F\u672C-${original.title}`;
20599
+ kernelContainer.db.prepare(
20600
+ "INSERT INTO lessons (id, title, content, timeline, progress_mode, progress_conditions, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
20601
+ ).run(newId, newTitle, original.content, original.timeline, original.progress_mode, original.progress_conditions, now, now);
20602
+ const whiteboardElements = kernelContainer.db.prepare(
20603
+ "SELECT * FROM whiteboard_elements WHERE lesson_id = ?"
20604
+ ).all(id);
20605
+ const insertElement = kernelContainer.db.prepare(
20606
+ "INSERT INTO whiteboard_elements (id, lesson_id, type, data, created_at) VALUES (?, ?, ?, ?, ?)"
20607
+ );
20608
+ for (const el of whiteboardElements) {
20609
+ insertElement.run((0, import_uuid16.v7)(), newId, el.type, el.data, now);
20610
+ }
20611
+ const cloned = kernelContainer.db.prepare(
20612
+ "SELECT l.*, 0 as enrollment_count FROM lessons l WHERE l.id = ?"
20613
+ ).get(newId);
20614
+ res.json({ success: true, lesson: cloned });
20615
+ } catch (e) {
20616
+ res.status(500).json({ error: e.message });
20617
+ }
20618
+ });
20473
20619
  }
20474
20620
 
20475
20621
  // server/routes/workspace.ts
@@ -22178,9 +22324,9 @@ Provide a grade score (0-100) and brief feedback. Ensure you output in this exac
22178
22324
  } catch (e) {
22179
22325
  }
22180
22326
  }
22181
- const { v7: uuidv716 } = await import("uuid");
22327
+ const { v7: uuidv717 } = await import("uuid");
22182
22328
  await kernelContainer.commandBus.execute({
22183
- id: uuidv716(),
22329
+ id: uuidv717(),
22184
22330
  type: "ai.apply_grade",
22185
22331
  actorId: "system",
22186
22332
  timestamp: Date.now(),
@@ -23877,7 +24023,7 @@ async function startServer() {
23877
24023
  imgSrc: ["'self'", "data:", "blob:", "https:"],
23878
24024
  connectSrc: ["'self'", "ws:", "wss:", "https:"],
23879
24025
  fontSrc: ["'self'", "data:", "https:", "https://fonts.gstatic.com"],
23880
- frameSrc: ["'self'"],
24026
+ frameSrc: ["'self'", "blob:", "data:", "http://localhost", "http://127.0.0.1", "http:", "https:"],
23881
24027
  objectSrc: ["'none'"]
23882
24028
  }
23883
24029
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openlearn-next",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "private": false,
5
5
  "description": "A plugin-driven Educational OS / LMS platform with AI agent teaching assistant",
6
6
  "keywords": [