@playcademy/vite-plugin 0.2.1 → 0.2.2

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.
Files changed (2) hide show
  1. package/dist/index.js +209 -53
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -176083,6 +176083,21 @@ async function mintGameJwt(gameId, userId) {
176083
176083
  const token = await signJwt({ uid: userId }, "game", expirationTimeSeconds, gameId);
176084
176084
  return { token, exp: expirationTimestampMillis };
176085
176085
  }
176086
+ async function mintRealtimeJwt(userId, gameId, username, role) {
176087
+ const payload = {
176088
+ sub: userId
176089
+ };
176090
+ if (gameId) {
176091
+ payload.gameId = gameId;
176092
+ }
176093
+ if (username) {
176094
+ payload.username = username;
176095
+ }
176096
+ if (role) {
176097
+ payload.role = role;
176098
+ }
176099
+ return await signJwt(payload, "realtime", "30m");
176100
+ }
176086
176101
  function formatValidationErrors(error2) {
176087
176102
  const flattened = error2.flatten();
176088
176103
  const fieldErrors = Object.entries(flattened.fieldErrors).map(([field, errors2]) => `${field}: ${errors2?.join(", ") || "Invalid"}`).join("; ");
@@ -186477,6 +186492,29 @@ async function seedCurrencies(db) {
186477
186492
  });
186478
186493
  }
186479
186494
  }
186495
+ var customLogger;
186496
+ function setLogger(logger3) {
186497
+ customLogger = logger3;
186498
+ }
186499
+ function getLogger() {
186500
+ if (customLogger) {
186501
+ return customLogger;
186502
+ }
186503
+ return {
186504
+ info: (msg) => console.log(msg),
186505
+ warn: (msg) => console.warn(msg),
186506
+ error: (msg) => console.error(msg)
186507
+ };
186508
+ }
186509
+ var logger3 = {
186510
+ info: (msg) => {
186511
+ if (customLogger || !config.embedded) {
186512
+ getLogger().info(msg);
186513
+ }
186514
+ },
186515
+ warn: (msg) => getLogger().warn(msg),
186516
+ error: (msg) => getLogger().error(msg)
186517
+ };
186480
186518
  function generateMockStudentId(userId) {
186481
186519
  return `mock-student-${userId.slice(-8)}`;
186482
186520
  }
@@ -186538,7 +186576,7 @@ async function seedCoreGames(db) {
186538
186576
  try {
186539
186577
  await db.insert(games).values(gameData).onConflictDoNothing();
186540
186578
  } catch (error2) {
186541
- console.error(`Error seeding core game '${gameData.slug}':`, error2);
186579
+ logger3.error(`Error seeding core game '${gameData.slug}': ${error2}`);
186542
186580
  }
186543
186581
  }
186544
186582
  }
@@ -186579,7 +186617,7 @@ async function seedCurrentProjectGame(db, project) {
186579
186617
  }
186580
186618
  return newGame;
186581
186619
  } catch (error2) {
186582
- console.error("❌ Error seeding project game:", error2);
186620
+ logger3.error(`❌ Error seeding project game: ${error2}`);
186583
186621
  throw error2;
186584
186622
  }
186585
186623
  }
@@ -186696,10 +186734,6 @@ async function setupServerDatabase(processedOptions, project) {
186696
186734
  return db;
186697
186735
  }
186698
186736
  var import_json_colorizer = __toESM2(require_dist22(), 1);
186699
- var customLogger;
186700
- function setLogger(logger3) {
186701
- customLogger = logger3;
186702
- }
186703
186737
  function processServerOptions(_port, options) {
186704
186738
  const {
186705
186739
  verbose = false,
@@ -186824,6 +186858,8 @@ manifestRouter.get("/", async (c3) => {
186824
186858
  { method: "GET", url: `${baseUrl}/api/notifications/stats/:userId` },
186825
186859
  { method: "POST", url: `${baseUrl}/api/notifications/deliver` },
186826
186860
  { method: "POST", url: `${baseUrl}/api/timeback/populate-student` },
186861
+ { method: "GET", url: `${baseUrl}/api/timeback/user` },
186862
+ { method: "GET", url: `${baseUrl}/api/timeback/user/:timebackId` },
186827
186863
  { method: "GET", url: `${baseUrl}/api/timeback/xp/today` },
186828
186864
  { method: "PUT", url: `${baseUrl}/api/timeback/xp/today` },
186829
186865
  { method: "GET", url: `${baseUrl}/api/timeback/xp/total` },
@@ -192046,6 +192082,10 @@ async function getMockTimebackData(db, timebackId, gameId) {
192046
192082
  });
192047
192083
  return { id: timebackId, role, enrollments, organizations };
192048
192084
  }
192085
+ async function getMockTimebackUser(db, gameId) {
192086
+ const timebackId = config.timeback.timebackId || "mock-student-00000001";
192087
+ return getMockTimebackData(db, timebackId, gameId);
192088
+ }
192049
192089
  async function buildMockUserResponse(db, user, gameId) {
192050
192090
  const timeback3 = user.timebackId ? await getMockTimebackData(db, user.timebackId, gameId) : undefined;
192051
192091
  if (gameId) {
@@ -196725,7 +196765,7 @@ async function deliverNotifications(ctx) {
196725
196765
  if (!user) {
196726
196766
  throw ApiError.unauthorized("Must be logged in to deliver notifications");
196727
196767
  }
196728
- log2.info("[API] Delivering pending notifications", {
196768
+ log2.debug("[API] Delivering pending notifications", {
196729
196769
  userId: user.id
196730
196770
  });
196731
196771
  try {
@@ -196831,6 +196871,56 @@ notificationsRouter.post("/deliver", async (c3) => {
196831
196871
  return c3.json(createUnknownErrorResponse(error2), 500);
196832
196872
  }
196833
196873
  });
196874
+ async function generateRealtimeToken(ctx) {
196875
+ const user = ctx.user;
196876
+ const gameId = ctx.params?.gameId;
196877
+ log2.debug("[API] generating realtime token", {
196878
+ userId: user?.id || "anonymous",
196879
+ gameId: gameId || "none"
196880
+ });
196881
+ if (!user) {
196882
+ throw ApiError.unauthorized("Valid session or bearer token required");
196883
+ }
196884
+ try {
196885
+ if (gameId) {
196886
+ const db = getDatabase();
196887
+ const game = await db.query.games.findFirst({
196888
+ where: eq(games.id, gameId),
196889
+ columns: { id: true }
196890
+ });
196891
+ if (!game) {
196892
+ throw ApiError.notFound("Game not found");
196893
+ }
196894
+ }
196895
+ const displayName = user.username || (user.name ? user.name.split(" ")[0] : undefined) || undefined;
196896
+ const token2 = await mintRealtimeJwt(user.id, gameId, displayName, user.role || undefined);
196897
+ return { token: token2 };
196898
+ } catch (error2) {
196899
+ if (error2 instanceof ApiError)
196900
+ throw error2;
196901
+ log2.error("[API /realtime/token] Failed to generate realtime token:", { error: error2 });
196902
+ throw ApiError.internal("Internal server error", error2);
196903
+ }
196904
+ }
196905
+ var realtimeRouter = new Hono2;
196906
+ realtimeRouter.post("/token", async (c3) => {
196907
+ const ctx = {
196908
+ user: c3.get("user"),
196909
+ params: {},
196910
+ url: new URL(c3.req.url),
196911
+ request: c3.req.raw
196912
+ };
196913
+ try {
196914
+ const result = await generateRealtimeToken(ctx);
196915
+ return c3.json(result);
196916
+ } catch (error2) {
196917
+ if (error2 instanceof ApiError) {
196918
+ return c3.json(createErrorResponse(error2), error2.statusCode);
196919
+ }
196920
+ console.error("Error in realtime/token:", error2);
196921
+ return c3.json(createUnknownErrorResponse(error2), 500);
196922
+ }
196923
+ });
196834
196924
  function isTimebackApiError(error2) {
196835
196925
  return typeof error2 === "object" && error2 !== null && "name" in error2 && error2.name === "TimebackApiError" && "status" in error2 && "details" in error2;
196836
196926
  }
@@ -197360,23 +197450,58 @@ async function getTimeBackXpHistory(ctx) {
197360
197450
  throw ApiError.internal("Failed to get TimeBack XP history", error2);
197361
197451
  }
197362
197452
  }
197363
- async function getStudentEnrollments(ctx) {
197453
+ async function getTimebackUser(ctx) {
197364
197454
  const user = ctx.user;
197365
197455
  if (!user) {
197366
- throw ApiError.unauthorized("Must be logged in to get enrollments");
197456
+ throw ApiError.unauthorized("Must be logged in to get timeback data");
197457
+ }
197458
+ const db = getDatabase();
197459
+ const userData = await db.query.users.findFirst({
197460
+ where: eq(users.id, user.id)
197461
+ });
197462
+ if (!userData) {
197463
+ throw ApiError.notFound("User not found");
197464
+ }
197465
+ if (!userData.timebackId) {
197466
+ throw ApiError.notFound("User does not have a TimeBack account");
197467
+ }
197468
+ log2.debug("[API] Getting timeback user data", {
197469
+ userId: user.id,
197470
+ timebackId: userData.timebackId,
197471
+ gameId: ctx.gameId
197472
+ });
197473
+ const timeback3 = await fetchUserTimebackData(userData.timebackId, ctx.gameId);
197474
+ log2.info("[API] Retrieved timeback user data", {
197475
+ userId: user.id,
197476
+ timebackId: userData.timebackId,
197477
+ role: timeback3.role,
197478
+ enrollmentCount: timeback3.enrollments.length,
197479
+ organizationCount: timeback3.organizations.length
197480
+ });
197481
+ return timeback3;
197482
+ }
197483
+ async function getTimebackUserById(ctx) {
197484
+ const user = ctx.user;
197485
+ if (!user) {
197486
+ throw ApiError.unauthorized("Must be logged in to get timeback data");
197367
197487
  }
197368
197488
  const timebackId = ctx.params.timebackId;
197369
197489
  if (!timebackId) {
197370
197490
  throw ApiError.badRequest("Missing timebackId parameter");
197371
197491
  }
197372
- log2.debug("[API] Getting student enrollments", { userId: user.id, timebackId });
197373
- const enrollments = await fetchEnrollmentsFromEduBridge(timebackId);
197374
- log2.info("[API] Retrieved student enrollments", {
197375
- userId: user.id,
197492
+ log2.debug("[API] Getting timeback user by ID", {
197493
+ requesterId: user.id,
197494
+ timebackId
197495
+ });
197496
+ const timeback3 = await fetchUserTimebackData(timebackId);
197497
+ log2.info("[API] Retrieved timeback user by ID", {
197498
+ requesterId: user.id,
197376
197499
  timebackId,
197377
- enrollmentCount: enrollments.length
197500
+ role: timeback3.role,
197501
+ enrollmentCount: timeback3.enrollments.length,
197502
+ organizationCount: timeback3.organizations.length
197378
197503
  });
197379
- return { enrollments };
197504
+ return timeback3;
197380
197505
  }
197381
197506
  var timebackRouter = new Hono2;
197382
197507
  timebackRouter.post("/populate-student", async (c3) => {
@@ -197568,32 +197693,62 @@ timebackRouter.post("/end-activity", async (c3) => {
197568
197693
  return c3.json(createUnknownErrorResponse(error2), 500);
197569
197694
  }
197570
197695
  });
197571
- timebackRouter.get("/enrollments/:timebackId", async (c3) => {
197696
+ timebackRouter.get("/user", async (c3) => {
197697
+ const user2 = c3.get("user");
197698
+ const gameId = c3.get("gameId");
197699
+ if (!user2) {
197700
+ const error2 = ApiError.unauthorized("Must be logged in to get timeback data");
197701
+ return c3.json(createErrorResponse(error2), error2.statusCode);
197702
+ }
197703
+ try {
197704
+ if (shouldMockTimeback()) {
197705
+ const db = c3.get("db");
197706
+ const timeback3 = await getMockTimebackUser(db, gameId);
197707
+ return c3.json(timeback3);
197708
+ }
197709
+ const ctx = {
197710
+ user: user2,
197711
+ params: {},
197712
+ url: new URL(c3.req.url),
197713
+ request: c3.req.raw,
197714
+ gameId
197715
+ };
197716
+ const result = await getTimebackUser(ctx);
197717
+ return c3.json(result);
197718
+ } catch (error2) {
197719
+ if (error2 instanceof ApiError) {
197720
+ return c3.json(createErrorResponse(error2), error2.statusCode);
197721
+ }
197722
+ console.error("Error in getTimebackUser:", error2);
197723
+ return c3.json(createUnknownErrorResponse(error2), 500);
197724
+ }
197725
+ });
197726
+ timebackRouter.get("/user/:timebackId", async (c3) => {
197572
197727
  const timebackId = c3.req.param("timebackId");
197573
- const user = c3.get("user");
197574
- if (!user) {
197575
- const error2 = ApiError.unauthorized("Must be logged in to get enrollments");
197728
+ const user2 = c3.get("user");
197729
+ if (!user2) {
197730
+ const error2 = ApiError.unauthorized("Must be logged in to get timeback data");
197576
197731
  return c3.json(createErrorResponse(error2), error2.statusCode);
197577
197732
  }
197578
197733
  try {
197579
197734
  if (shouldMockTimeback()) {
197580
197735
  const db = c3.get("db");
197581
- const enrollments2 = await getMockEnrollments(db);
197582
- return c3.json({ enrollments: enrollments2 });
197736
+ const timeback3 = await getMockTimebackUser(db);
197737
+ return c3.json(timeback3);
197583
197738
  }
197584
197739
  const ctx = {
197585
- user,
197740
+ user: user2,
197586
197741
  params: { timebackId },
197587
197742
  url: new URL(c3.req.url),
197588
197743
  request: c3.req.raw
197589
197744
  };
197590
- const result = await getStudentEnrollments(ctx);
197745
+ const result = await getTimebackUserById(ctx);
197591
197746
  return c3.json(result);
197592
197747
  } catch (error2) {
197593
197748
  if (error2 instanceof ApiError) {
197594
197749
  return c3.json(createErrorResponse(error2), error2.statusCode);
197595
197750
  }
197596
- console.error("Error in getStudentEnrollments:", error2);
197751
+ console.error("Error in getTimebackUserById:", error2);
197597
197752
  return c3.json(createUnknownErrorResponse(error2), 500);
197598
197753
  }
197599
197754
  });
@@ -197628,29 +197783,29 @@ async function provisionUserFromLti(claims) {
197628
197783
  where: and(eq(accounts.accountId, ltiTimebackId), eq(accounts.providerId, providerId))
197629
197784
  });
197630
197785
  if (existingAccount) {
197631
- const user2 = await db.query.users.findFirst({
197786
+ const user3 = await db.query.users.findFirst({
197632
197787
  where: eq(users.id, existingAccount.userId)
197633
197788
  });
197634
- if (user2) {
197789
+ if (user3) {
197635
197790
  log2.info("[lti-timeback] Found existing user by LTI account linkage", {
197636
- userId: user2.id,
197791
+ userId: user3.id,
197637
197792
  ltiTimebackId
197638
197793
  });
197639
- return user2;
197794
+ return user3;
197640
197795
  }
197641
197796
  }
197642
- const user = await db.query.users.findFirst({
197797
+ const user2 = await db.query.users.findFirst({
197643
197798
  where: eq(users.email, email)
197644
197799
  });
197645
- if (user) {
197800
+ if (user2) {
197646
197801
  await db.transaction(async (tx) => {
197647
197802
  const existingLtiAccount = await tx.query.accounts.findFirst({
197648
- where: and(eq(accounts.userId, user.id), eq(accounts.providerId, providerId))
197803
+ where: and(eq(accounts.userId, user2.id), eq(accounts.providerId, providerId))
197649
197804
  });
197650
197805
  if (!existingLtiAccount) {
197651
197806
  await tx.insert(accounts).values({
197652
197807
  id: crypto7.randomUUID(),
197653
- userId: user.id,
197808
+ userId: user2.id,
197654
197809
  accountId: ltiTimebackId,
197655
197810
  providerId,
197656
197811
  accessToken: null,
@@ -197661,14 +197816,14 @@ async function provisionUserFromLti(claims) {
197661
197816
  updatedAt: new Date
197662
197817
  });
197663
197818
  log2.info("[lti-timeback] Linked existing user to LTI provider", {
197664
- userId: user.id,
197665
- email: user.email,
197819
+ userId: user2.id,
197820
+ email: user2.email,
197666
197821
  ltiTimebackId,
197667
197822
  note: "User may have different TimeBack ID from OAuth flow"
197668
197823
  });
197669
197824
  }
197670
197825
  });
197671
- return user;
197826
+ return user2;
197672
197827
  }
197673
197828
  const newUserId = crypto7.randomUUID();
197674
197829
  const createdUser = await db.transaction(async (tx) => {
@@ -197711,10 +197866,10 @@ async function processLtiLaunch(idToken) {
197711
197866
  try {
197712
197867
  const claims = await verifyLtiToken(idToken);
197713
197868
  validateLtiClaims(claims);
197714
- const user = await provisionUserFromLti(claims);
197869
+ const user2 = await provisionUserFromLti(claims);
197715
197870
  const targetUri = claims["https://purl.imsglobal.org/spec/lti/claim/target_link_uri"];
197716
197871
  return {
197717
- user,
197872
+ user: user2,
197718
197873
  targetUri,
197719
197874
  claims
197720
197875
  };
@@ -197734,45 +197889,45 @@ async function processTimeBackLtiLaunch(ctx) {
197734
197889
  }
197735
197890
  const currentHost = ctx.url.hostname;
197736
197891
  const launchResult = await processLtiLaunch(idToken);
197737
- const { user, targetUri, claims } = launchResult;
197892
+ const { user: user2, targetUri, claims } = launchResult;
197738
197893
  log2.info("[lti-timeback] User roles", {
197739
- userId: user.id,
197894
+ userId: user2.id,
197740
197895
  isLearner: LtiRoleChecks.isLearner(claims),
197741
197896
  isInstructor: LtiRoleChecks.isInstructor(claims),
197742
197897
  isAdministrator: LtiRoleChecks.isAdministrator(claims),
197743
197898
  allRoles: claims["https://purl.imsglobal.org/spec/lti/claim/roles"]
197744
197899
  });
197745
- const sessionToken = await createLtiSession(user.id);
197900
+ const sessionToken = await createLtiSession(user2.id);
197746
197901
  const redirectPath = extractRedirectPath(targetUri, currentHost);
197747
197902
  log2.info("[lti-timeback] LTI launch successful", {
197748
- userId: user.id,
197903
+ userId: user2.id,
197749
197904
  redirectPath
197750
197905
  });
197751
197906
  return {
197752
- user,
197907
+ user: user2,
197753
197908
  redirectPath,
197754
197909
  sessionToken
197755
197910
  };
197756
197911
  }
197757
197912
  async function getTimeBackLtiStatus(ctx) {
197758
- const user = ctx.user;
197759
- if (!user) {
197913
+ const user2 = ctx.user;
197914
+ if (!user2) {
197760
197915
  throw ApiError.unauthorized("Must be logged in");
197761
197916
  }
197762
197917
  const db = getDatabase();
197763
197918
  const ltiAccount = await db.query.accounts.findFirst({
197764
- where: and(eq(accounts.userId, user.id), eq(accounts.providerId, AUTH_PROVIDER_IDS.TIMEBACK_LTI))
197919
+ where: and(eq(accounts.userId, user2.id), eq(accounts.providerId, AUTH_PROVIDER_IDS.TIMEBACK_LTI))
197765
197920
  });
197766
197921
  const oauthAccount = await db.query.accounts.findFirst({
197767
- where: and(eq(accounts.userId, user.id), eq(accounts.providerId, AUTH_PROVIDER_IDS.TIMEBACK))
197922
+ where: and(eq(accounts.userId, user2.id), eq(accounts.providerId, AUTH_PROVIDER_IDS.TIMEBACK))
197768
197923
  });
197769
197924
  return {
197770
197925
  hasLtiAccount: !!ltiAccount,
197771
197926
  ltiTimebackId: ltiAccount?.accountId || undefined,
197772
197927
  hasOAuthAccount: !!oauthAccount,
197773
- oauthTimebackId: oauthAccount?.accountId || user.timebackId || undefined,
197774
- email: user.email,
197775
- userId: user.id
197928
+ oauthTimebackId: oauthAccount?.accountId || user2.timebackId || undefined,
197929
+ email: user2.email,
197930
+ userId: user2.id
197776
197931
  };
197777
197932
  }
197778
197933
  var ltiRouter = new Hono2;
@@ -197809,20 +197964,20 @@ ltiRouter.all("/launch", async (c3) => {
197809
197964
  }
197810
197965
  });
197811
197966
  ltiRouter.get("/status", async (c3) => {
197812
- let user = undefined;
197967
+ let user2 = undefined;
197813
197968
  const authHeader = c3.req.header("Authorization");
197814
197969
  if (authHeader?.startsWith("Bearer ")) {
197815
197970
  const token2 = authHeader.substring(7);
197816
197971
  const demoUser = DEMO_TOKENS[token2];
197817
197972
  if (demoUser) {
197818
197973
  const db = c3.get("db");
197819
- user = await db.query.users.findFirst({
197974
+ user2 = await db.query.users.findFirst({
197820
197975
  where: eq(users.id, demoUser.id)
197821
197976
  });
197822
197977
  }
197823
197978
  }
197824
197979
  const ctx = {
197825
- user,
197980
+ user: user2,
197826
197981
  params: {},
197827
197982
  url: new URL(c3.req.url),
197828
197983
  request: c3.req.raw
@@ -197856,6 +198011,7 @@ function registerRoutes(app) {
197856
198011
  app.route("/api/sprites", spriteRouter);
197857
198012
  app.route("/api/achievements", achievementsRouter);
197858
198013
  app.route("/api/notifications", notificationsRouter);
198014
+ app.route("/api/realtime", realtimeRouter);
197859
198015
  app.route("/api/dev", devRouter);
197860
198016
  app.route("/api/timeback", timebackRouter);
197861
198017
  app.route("/api/lti", ltiRouter);
@@ -198752,7 +198908,7 @@ var import_picocolors11 = __toESM(require_picocolors(), 1);
198752
198908
  // package.json
198753
198909
  var package_default2 = {
198754
198910
  name: "@playcademy/vite-plugin",
198755
- version: "0.2.1",
198911
+ version: "0.2.2",
198756
198912
  type: "module",
198757
198913
  exports: {
198758
198914
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/vite-plugin",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -22,7 +22,7 @@
22
22
  "@playcademy/utils": "0.0.1",
23
23
  "archiver": "^7.0.1",
24
24
  "picocolors": "^1.1.1",
25
- "playcademy": "0.14.27"
25
+ "playcademy": "0.14.28"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@inquirer/prompts": "^7.8.6",