@playcademy/vite-plugin 0.2.1 → 0.2.2-alpha.1
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.js +259 -103
- package/package.json +3 -3
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("; ");
|
|
@@ -186438,6 +186453,55 @@ async function verifyGameAccessBySlug(slug2, user) {
|
|
|
186438
186453
|
function getGameWorkerApiKeyName(slug2) {
|
|
186439
186454
|
return `game-worker-${slug2}`.substring(0, 32);
|
|
186440
186455
|
}
|
|
186456
|
+
var import_client_s32 = __toESM2(require_dist_cjs81(), 1);
|
|
186457
|
+
function getSecretsConfig() {
|
|
186458
|
+
const bucketName = process.env.GAME_SECRETS_BUCKET;
|
|
186459
|
+
const masterKey = process.env.GAME_SECRETS_MASTER_KEY;
|
|
186460
|
+
if (!bucketName || !masterKey) {
|
|
186461
|
+
throw new Error("Secrets storage not configured");
|
|
186462
|
+
}
|
|
186463
|
+
const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
|
|
186464
|
+
const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
|
|
186465
|
+
const endpoint = process.env.CLOUDFLARE_R2_DEFAULT_ENDPOINT;
|
|
186466
|
+
if (!accessKeyId || !secretAccessKey || !endpoint) {
|
|
186467
|
+
throw new Error("R2 credentials not configured");
|
|
186468
|
+
}
|
|
186469
|
+
return {
|
|
186470
|
+
bucketName,
|
|
186471
|
+
credentials: {
|
|
186472
|
+
accessKeyId,
|
|
186473
|
+
secretAccessKey,
|
|
186474
|
+
endpoint
|
|
186475
|
+
},
|
|
186476
|
+
masterKey
|
|
186477
|
+
};
|
|
186478
|
+
}
|
|
186479
|
+
function getSecretsKey(gameId) {
|
|
186480
|
+
return `${gameId}.json.enc`;
|
|
186481
|
+
}
|
|
186482
|
+
function createR2Client(config2) {
|
|
186483
|
+
return new import_client_s32.S3Client({
|
|
186484
|
+
region: "auto",
|
|
186485
|
+
endpoint: config2.credentials.endpoint,
|
|
186486
|
+
credentials: {
|
|
186487
|
+
accessKeyId: config2.credentials.accessKeyId,
|
|
186488
|
+
secretAccessKey: config2.credentials.secretAccessKey
|
|
186489
|
+
}
|
|
186490
|
+
});
|
|
186491
|
+
}
|
|
186492
|
+
async function deleteSecrets(gameId, config2) {
|
|
186493
|
+
const client = createR2Client(config2);
|
|
186494
|
+
const key = getSecretsKey(gameId);
|
|
186495
|
+
try {
|
|
186496
|
+
await client.send(new import_client_s32.DeleteObjectCommand({
|
|
186497
|
+
Bucket: config2.bucketName,
|
|
186498
|
+
Key: key
|
|
186499
|
+
}));
|
|
186500
|
+
} catch (error2) {
|
|
186501
|
+
log2.error("[SecretsStorage] Failed to delete secrets", { gameId, error: error2 });
|
|
186502
|
+
throw new Error(`Failed to delete secrets for game ${gameId}`);
|
|
186503
|
+
}
|
|
186504
|
+
}
|
|
186441
186505
|
async function seedAchievements(db) {
|
|
186442
186506
|
const now2 = new Date;
|
|
186443
186507
|
for (const def of ACHIEVEMENT_DEFINITIONS) {
|
|
@@ -186477,6 +186541,29 @@ async function seedCurrencies(db) {
|
|
|
186477
186541
|
});
|
|
186478
186542
|
}
|
|
186479
186543
|
}
|
|
186544
|
+
var customLogger;
|
|
186545
|
+
function setLogger(logger3) {
|
|
186546
|
+
customLogger = logger3;
|
|
186547
|
+
}
|
|
186548
|
+
function getLogger() {
|
|
186549
|
+
if (customLogger) {
|
|
186550
|
+
return customLogger;
|
|
186551
|
+
}
|
|
186552
|
+
return {
|
|
186553
|
+
info: (msg) => console.log(msg),
|
|
186554
|
+
warn: (msg) => console.warn(msg),
|
|
186555
|
+
error: (msg) => console.error(msg)
|
|
186556
|
+
};
|
|
186557
|
+
}
|
|
186558
|
+
var logger3 = {
|
|
186559
|
+
info: (msg) => {
|
|
186560
|
+
if (customLogger || !config.embedded) {
|
|
186561
|
+
getLogger().info(msg);
|
|
186562
|
+
}
|
|
186563
|
+
},
|
|
186564
|
+
warn: (msg) => getLogger().warn(msg),
|
|
186565
|
+
error: (msg) => getLogger().error(msg)
|
|
186566
|
+
};
|
|
186480
186567
|
function generateMockStudentId(userId) {
|
|
186481
186568
|
return `mock-student-${userId.slice(-8)}`;
|
|
186482
186569
|
}
|
|
@@ -186538,7 +186625,7 @@ async function seedCoreGames(db) {
|
|
|
186538
186625
|
try {
|
|
186539
186626
|
await db.insert(games).values(gameData).onConflictDoNothing();
|
|
186540
186627
|
} catch (error2) {
|
|
186541
|
-
|
|
186628
|
+
logger3.error(`Error seeding core game '${gameData.slug}': ${error2}`);
|
|
186542
186629
|
}
|
|
186543
186630
|
}
|
|
186544
186631
|
}
|
|
@@ -186579,7 +186666,7 @@ async function seedCurrentProjectGame(db, project) {
|
|
|
186579
186666
|
}
|
|
186580
186667
|
return newGame;
|
|
186581
186668
|
} catch (error2) {
|
|
186582
|
-
|
|
186669
|
+
logger3.error(`❌ Error seeding project game: ${error2}`);
|
|
186583
186670
|
throw error2;
|
|
186584
186671
|
}
|
|
186585
186672
|
}
|
|
@@ -186696,10 +186783,6 @@ async function setupServerDatabase(processedOptions, project) {
|
|
|
186696
186783
|
return db;
|
|
186697
186784
|
}
|
|
186698
186785
|
var import_json_colorizer = __toESM2(require_dist22(), 1);
|
|
186699
|
-
var customLogger;
|
|
186700
|
-
function setLogger(logger3) {
|
|
186701
|
-
customLogger = logger3;
|
|
186702
|
-
}
|
|
186703
186786
|
function processServerOptions(_port, options) {
|
|
186704
186787
|
const {
|
|
186705
186788
|
verbose = false,
|
|
@@ -186824,6 +186907,8 @@ manifestRouter.get("/", async (c3) => {
|
|
|
186824
186907
|
{ method: "GET", url: `${baseUrl}/api/notifications/stats/:userId` },
|
|
186825
186908
|
{ method: "POST", url: `${baseUrl}/api/notifications/deliver` },
|
|
186826
186909
|
{ method: "POST", url: `${baseUrl}/api/timeback/populate-student` },
|
|
186910
|
+
{ method: "GET", url: `${baseUrl}/api/timeback/user` },
|
|
186911
|
+
{ method: "GET", url: `${baseUrl}/api/timeback/user/:timebackId` },
|
|
186827
186912
|
{ method: "GET", url: `${baseUrl}/api/timeback/xp/today` },
|
|
186828
186913
|
{ method: "PUT", url: `${baseUrl}/api/timeback/xp/today` },
|
|
186829
186914
|
{ method: "GET", url: `${baseUrl}/api/timeback/xp/total` },
|
|
@@ -192046,6 +192131,10 @@ async function getMockTimebackData(db, timebackId, gameId) {
|
|
|
192046
192131
|
});
|
|
192047
192132
|
return { id: timebackId, role, enrollments, organizations };
|
|
192048
192133
|
}
|
|
192134
|
+
async function getMockTimebackUser(db, gameId) {
|
|
192135
|
+
const timebackId = config.timeback.timebackId || "mock-student-00000001";
|
|
192136
|
+
return getMockTimebackData(db, timebackId, gameId);
|
|
192137
|
+
}
|
|
192049
192138
|
async function buildMockUserResponse(db, user, gameId) {
|
|
192050
192139
|
const timeback3 = user.timebackId ? await getMockTimebackData(db, user.timebackId, gameId) : undefined;
|
|
192051
192140
|
if (gameId) {
|
|
@@ -192174,55 +192263,6 @@ usersRouter.all("/", async (c3) => {
|
|
|
192174
192263
|
const error2 = ApiError.methodNotAllowed("Method not allowed");
|
|
192175
192264
|
return c3.json(createErrorResponse(error2), 405);
|
|
192176
192265
|
});
|
|
192177
|
-
var import_client_s32 = __toESM2(require_dist_cjs81(), 1);
|
|
192178
|
-
function getSecretsConfig() {
|
|
192179
|
-
const bucketName = process.env.GAME_SECRETS_BUCKET;
|
|
192180
|
-
const masterKey = process.env.GAME_SECRETS_MASTER_KEY;
|
|
192181
|
-
if (!bucketName || !masterKey) {
|
|
192182
|
-
throw new Error("Secrets storage not configured");
|
|
192183
|
-
}
|
|
192184
|
-
const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
|
|
192185
|
-
const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
|
|
192186
|
-
const endpoint = process.env.CLOUDFLARE_R2_DEFAULT_ENDPOINT;
|
|
192187
|
-
if (!accessKeyId || !secretAccessKey || !endpoint) {
|
|
192188
|
-
throw new Error("R2 credentials not configured");
|
|
192189
|
-
}
|
|
192190
|
-
return {
|
|
192191
|
-
bucketName,
|
|
192192
|
-
credentials: {
|
|
192193
|
-
accessKeyId,
|
|
192194
|
-
secretAccessKey,
|
|
192195
|
-
endpoint
|
|
192196
|
-
},
|
|
192197
|
-
masterKey
|
|
192198
|
-
};
|
|
192199
|
-
}
|
|
192200
|
-
function getSecretsKey(gameId) {
|
|
192201
|
-
return `${gameId}.json.enc`;
|
|
192202
|
-
}
|
|
192203
|
-
function createR2Client(config2) {
|
|
192204
|
-
return new import_client_s32.S3Client({
|
|
192205
|
-
region: "auto",
|
|
192206
|
-
endpoint: config2.credentials.endpoint,
|
|
192207
|
-
credentials: {
|
|
192208
|
-
accessKeyId: config2.credentials.accessKeyId,
|
|
192209
|
-
secretAccessKey: config2.credentials.secretAccessKey
|
|
192210
|
-
}
|
|
192211
|
-
});
|
|
192212
|
-
}
|
|
192213
|
-
async function deleteSecrets(gameId, config2) {
|
|
192214
|
-
const client = createR2Client(config2);
|
|
192215
|
-
const key = getSecretsKey(gameId);
|
|
192216
|
-
try {
|
|
192217
|
-
await client.send(new import_client_s32.DeleteObjectCommand({
|
|
192218
|
-
Bucket: config2.bucketName,
|
|
192219
|
-
Key: key
|
|
192220
|
-
}));
|
|
192221
|
-
} catch (error2) {
|
|
192222
|
-
log2.error("[SecretsStorage] Failed to delete secrets", { gameId, error: error2 });
|
|
192223
|
-
throw new Error(`Failed to delete secrets for game ${gameId}`);
|
|
192224
|
-
}
|
|
192225
|
-
}
|
|
192226
192266
|
function assertError(err2) {
|
|
192227
192267
|
if (!isError(err2)) {
|
|
192228
192268
|
throw new Error("Parameter was not an error");
|
|
@@ -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.
|
|
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
|
|
197453
|
+
async function getTimebackUser(ctx) {
|
|
197454
|
+
const user = ctx.user;
|
|
197455
|
+
if (!user) {
|
|
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) {
|
|
197364
197484
|
const user = ctx.user;
|
|
197365
197485
|
if (!user) {
|
|
197366
|
-
throw ApiError.unauthorized("Must be logged in to get
|
|
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
|
|
197373
|
-
|
|
197374
|
-
|
|
197375
|
-
|
|
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
|
-
|
|
197500
|
+
role: timeback3.role,
|
|
197501
|
+
enrollmentCount: timeback3.enrollments.length,
|
|
197502
|
+
organizationCount: timeback3.organizations.length
|
|
197378
197503
|
});
|
|
197379
|
-
return
|
|
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("/
|
|
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
|
|
197574
|
-
if (!
|
|
197575
|
-
const error2 = ApiError.unauthorized("Must be logged in to get
|
|
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
|
|
197582
|
-
return c3.json(
|
|
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
|
|
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
|
|
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
|
|
197786
|
+
const user3 = await db.query.users.findFirst({
|
|
197632
197787
|
where: eq(users.id, existingAccount.userId)
|
|
197633
197788
|
});
|
|
197634
|
-
if (
|
|
197789
|
+
if (user3) {
|
|
197635
197790
|
log2.info("[lti-timeback] Found existing user by LTI account linkage", {
|
|
197636
|
-
userId:
|
|
197791
|
+
userId: user3.id,
|
|
197637
197792
|
ltiTimebackId
|
|
197638
197793
|
});
|
|
197639
|
-
return
|
|
197794
|
+
return user3;
|
|
197640
197795
|
}
|
|
197641
197796
|
}
|
|
197642
|
-
const
|
|
197797
|
+
const user2 = await db.query.users.findFirst({
|
|
197643
197798
|
where: eq(users.email, email)
|
|
197644
197799
|
});
|
|
197645
|
-
if (
|
|
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,
|
|
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:
|
|
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:
|
|
197665
|
-
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
|
|
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
|
|
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:
|
|
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(
|
|
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:
|
|
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
|
|
197759
|
-
if (!
|
|
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,
|
|
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,
|
|
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 ||
|
|
197774
|
-
email:
|
|
197775
|
-
userId:
|
|
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
|
|
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
|
-
|
|
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-alpha.1",
|
|
198756
198912
|
type: "module",
|
|
198757
198913
|
exports: {
|
|
198758
198914
|
".": {
|
|
@@ -198771,7 +198927,6 @@ var package_default2 = {
|
|
|
198771
198927
|
pub: "bun publish.ts"
|
|
198772
198928
|
},
|
|
198773
198929
|
dependencies: {
|
|
198774
|
-
"@playcademy/utils": "workspace:*",
|
|
198775
198930
|
archiver: "^7.0.1",
|
|
198776
198931
|
picocolors: "^1.1.1",
|
|
198777
198932
|
playcademy: "workspace:*"
|
|
@@ -198779,6 +198934,7 @@ var package_default2 = {
|
|
|
198779
198934
|
devDependencies: {
|
|
198780
198935
|
"@inquirer/prompts": "^7.8.6",
|
|
198781
198936
|
"@playcademy/sandbox": "workspace:*",
|
|
198937
|
+
"@playcademy/utils": "workspace:*",
|
|
198782
198938
|
"@types/archiver": "^6.0.3",
|
|
198783
198939
|
"@types/bun": "latest"
|
|
198784
198940
|
},
|
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-alpha.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"pub": "bun publish.ts"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@playcademy/utils": "0.0.1",
|
|
23
22
|
"archiver": "^7.0.1",
|
|
24
23
|
"picocolors": "^1.1.1",
|
|
25
|
-
"playcademy": "0.14.
|
|
24
|
+
"playcademy": "0.14.28"
|
|
26
25
|
},
|
|
27
26
|
"devDependencies": {
|
|
28
27
|
"@inquirer/prompts": "^7.8.6",
|
|
29
28
|
"@playcademy/sandbox": "0.3.6",
|
|
29
|
+
"@playcademy/utils": "0.0.1",
|
|
30
30
|
"@types/archiver": "^6.0.3",
|
|
31
31
|
"@types/bun": "latest"
|
|
32
32
|
},
|