@playcademy/sandbox 0.12.2-beta.4 → 0.12.2-beta.5

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/cli.js CHANGED
@@ -2517,7 +2517,7 @@ var package_default;
2517
2517
  var init_package = __esm(() => {
2518
2518
  package_default = {
2519
2519
  name: "@playcademy/sandbox",
2520
- version: "0.12.2-beta.4",
2520
+ version: "0.12.2-beta.5",
2521
2521
  description: "Local development server for Playcademy game development",
2522
2522
  type: "module",
2523
2523
  exports: {
@@ -20917,6 +20917,7 @@ var init_table8 = __esm(() => {
20917
20917
  gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
20918
20918
  parentOrigin: text("parent_origin").notNull(),
20919
20919
  timebackCourseId: text("timeback_course_id"),
20920
+ contextId: text("context_id"),
20920
20921
  passHash: text("pass_hash").unique(),
20921
20922
  passExpiresAt: timestamp("pass_expires_at", { withTimezone: true }).notNull(),
20922
20923
  redeemedAt: timestamp("redeemed_at", { withTimezone: true }),
@@ -50823,13 +50824,16 @@ function isCanonicalOrigin(value) {
50823
50824
  }
50824
50825
  return url.protocol === "https:" || url.protocol === "http:" && isLocalHostname(url.hostname);
50825
50826
  }
50826
- var LOCAL_HOSTNAMES, EmbedOriginSchema, EmbedPartnerClientIdSchema, EnsureEmbedPartnerSchema, AddEmbedPartnerOriginSchema, CreateEmbedLaunchSchema, RedeemEmbedPassSchema;
50827
+ var LOCAL_HOSTNAMES, EmbedOriginSchema, EmbedContextIdSchema, EmbedPartnerClientIdSchema, EnsureEmbedPartnerSchema, AddEmbedPartnerOriginSchema, CreateEmbedLaunchSchema, RedeemEmbedPassSchema;
50827
50828
  var init_schemas5 = __esm(() => {
50828
50829
  init_esm();
50829
50830
  LOCAL_HOSTNAMES = new Set(["localhost", "127.0.0.1"]);
50830
50831
  EmbedOriginSchema = stringType().refine(isCanonicalOrigin, {
50831
50832
  message: "Origin must be a bare https origin like https://example.com, with no path or slash"
50832
50833
  });
50834
+ EmbedContextIdSchema = stringType().regex(/^[A-Za-z0-9._~-]{1,128}$/, {
50835
+ message: "contextId must be 1-128 URL-safe characters (letters, digits, . _ ~ -)"
50836
+ });
50833
50837
  EmbedPartnerClientIdSchema = stringType().trim().min(1).max(200);
50834
50838
  EnsureEmbedPartnerSchema = objectType({
50835
50839
  name: stringType().trim().min(1).max(100)
@@ -50841,7 +50845,8 @@ var init_schemas5 = __esm(() => {
50841
50845
  timebackId: stringType().trim().min(1).max(200),
50842
50846
  gameSlug: stringType().trim().min(1).max(100),
50843
50847
  parentOrigin: EmbedOriginSchema,
50844
- timebackCourseId: stringType().trim().min(1).max(200).optional()
50848
+ timebackCourseId: stringType().trim().min(1).max(200).optional(),
50849
+ contextId: EmbedContextIdSchema.optional()
50845
50850
  }).strict();
50846
50851
  RedeemEmbedPassSchema = objectType({
50847
50852
  pass: stringType().min(1).max(200),
@@ -95683,6 +95688,13 @@ var init_embed_util = __esm(() => {
95683
95688
  });
95684
95689
 
95685
95690
  // ../api-core/src/services/embed.service.ts
95691
+ async function scopedContextId(partnerId, contextId) {
95692
+ return sha256Hex(`${partnerId}:${contextId}`);
95693
+ }
95694
+ function launchTokenClaims(launch) {
95695
+ return launch.contextId ? { contextId: launch.contextId } : {};
95696
+ }
95697
+
95686
95698
  class EmbedService {
95687
95699
  deps;
95688
95700
  constructor(deps) {
@@ -95726,6 +95738,7 @@ class EmbedService {
95726
95738
  gameId: game.id,
95727
95739
  parentOrigin: input.parentOrigin,
95728
95740
  timebackCourseId: input.timebackCourseId ?? null,
95741
+ contextId: input.contextId ? await scopedContextId(partner.id, input.contextId) : null,
95729
95742
  passHash: await sha256Hex(pass),
95730
95743
  passExpiresAt,
95731
95744
  expiresAt: new Date(now + EMBED_LAUNCH_TTL_MS)
@@ -95733,7 +95746,11 @@ class EmbedService {
95733
95746
  if (!launch) {
95734
95747
  throw new InternalError("Failed to create embed launch");
95735
95748
  }
95736
- setAttributes({ "app.launch.id": launch.id, "app.user.id": user.id });
95749
+ setAttributes({
95750
+ "app.launch.id": launch.id,
95751
+ "app.user.id": user.id,
95752
+ "app.embed.has_context": input.contextId !== undefined
95753
+ });
95737
95754
  return { launchId: launch.id, pass, passExpiresAt, gameSlug: game.slug };
95738
95755
  }
95739
95756
  async redeemLaunch(input, timeZone) {
@@ -95748,7 +95765,7 @@ class EmbedService {
95748
95765
  const [game, profile, token] = await Promise.all([
95749
95766
  this.deps.getGameForStudent(launch.gameId, user),
95750
95767
  this.deps.getGameProfile(user, launch.gameId, timeZone),
95751
- this.deps.mintGameToken(launch.gameId, user.id)
95768
+ this.deps.mintGameToken(launch.gameId, user.id, launchTokenClaims(launch))
95752
95769
  ]);
95753
95770
  await this.deps.db.update(embedLaunches).set({ redeemedAt: new Date }).where(eq(embedLaunches.id, launch.id));
95754
95771
  return {
@@ -95780,7 +95797,7 @@ class EmbedService {
95780
95797
  if (outcome) {
95781
95798
  throw new AccessDeniedError("This launch has ended");
95782
95799
  }
95783
- return this.deps.mintGameToken(launch.gameId, launch.userId);
95800
+ return this.deps.mintGameToken(launch.gameId, launch.userId, launchTokenClaims(launch));
95784
95801
  }
95785
95802
  async endLaunch(launchId) {
95786
95803
  await this.deps.db.update(embedLaunches).set({ endedAt: sql`coalesce(${embedLaunches.endedAt}, now())`, passHash: null }).where(eq(embedLaunches.id, launchId));
@@ -96882,10 +96899,10 @@ function createSandboxAuthProvider() {
96882
96899
  }
96883
96900
  return deleted;
96884
96901
  },
96885
- async mintGameToken(gameId, userId) {
96902
+ async mintGameToken(gameId, userId, claims) {
96886
96903
  const header = btoa(JSON.stringify({ alg: "none", typ: "sandbox" }));
96887
96904
  const exp = Math.floor(Date.now() / 1000) + 15 * 60;
96888
- const payload = btoa(JSON.stringify({ sub: gameId, uid: userId, exp }));
96905
+ const payload = btoa(JSON.stringify({ ...claims, sub: gameId, uid: userId, exp }));
96889
96906
  const token = `${header}.${payload}.sandbox`;
96890
96907
  return { token, exp: exp * 1000 };
96891
96908
  },
package/dist/server.js CHANGED
@@ -697,7 +697,7 @@ var package_default;
697
697
  var init_package = __esm(() => {
698
698
  package_default = {
699
699
  name: "@playcademy/sandbox",
700
- version: "0.12.2-beta.4",
700
+ version: "0.12.2-beta.5",
701
701
  description: "Local development server for Playcademy game development",
702
702
  type: "module",
703
703
  exports: {
@@ -19097,6 +19097,7 @@ var init_table8 = __esm(() => {
19097
19097
  gameId: uuid("game_id").notNull().references(() => games.id, { onDelete: "cascade" }),
19098
19098
  parentOrigin: text("parent_origin").notNull(),
19099
19099
  timebackCourseId: text("timeback_course_id"),
19100
+ contextId: text("context_id"),
19100
19101
  passHash: text("pass_hash").unique(),
19101
19102
  passExpiresAt: timestamp("pass_expires_at", { withTimezone: true }).notNull(),
19102
19103
  redeemedAt: timestamp("redeemed_at", { withTimezone: true }),
@@ -49003,13 +49004,16 @@ function isCanonicalOrigin(value) {
49003
49004
  }
49004
49005
  return url.protocol === "https:" || url.protocol === "http:" && isLocalHostname(url.hostname);
49005
49006
  }
49006
- var LOCAL_HOSTNAMES, EmbedOriginSchema, EmbedPartnerClientIdSchema, EnsureEmbedPartnerSchema, AddEmbedPartnerOriginSchema, CreateEmbedLaunchSchema, RedeemEmbedPassSchema;
49007
+ var LOCAL_HOSTNAMES, EmbedOriginSchema, EmbedContextIdSchema, EmbedPartnerClientIdSchema, EnsureEmbedPartnerSchema, AddEmbedPartnerOriginSchema, CreateEmbedLaunchSchema, RedeemEmbedPassSchema;
49007
49008
  var init_schemas5 = __esm(() => {
49008
49009
  init_esm();
49009
49010
  LOCAL_HOSTNAMES = new Set(["localhost", "127.0.0.1"]);
49010
49011
  EmbedOriginSchema = stringType().refine(isCanonicalOrigin, {
49011
49012
  message: "Origin must be a bare https origin like https://example.com, with no path or slash"
49012
49013
  });
49014
+ EmbedContextIdSchema = stringType().regex(/^[A-Za-z0-9._~-]{1,128}$/, {
49015
+ message: "contextId must be 1-128 URL-safe characters (letters, digits, . _ ~ -)"
49016
+ });
49013
49017
  EmbedPartnerClientIdSchema = stringType().trim().min(1).max(200);
49014
49018
  EnsureEmbedPartnerSchema = objectType({
49015
49019
  name: stringType().trim().min(1).max(100)
@@ -49021,7 +49025,8 @@ var init_schemas5 = __esm(() => {
49021
49025
  timebackId: stringType().trim().min(1).max(200),
49022
49026
  gameSlug: stringType().trim().min(1).max(100),
49023
49027
  parentOrigin: EmbedOriginSchema,
49024
- timebackCourseId: stringType().trim().min(1).max(200).optional()
49028
+ timebackCourseId: stringType().trim().min(1).max(200).optional(),
49029
+ contextId: EmbedContextIdSchema.optional()
49025
49030
  }).strict();
49026
49031
  RedeemEmbedPassSchema = objectType({
49027
49032
  pass: stringType().min(1).max(200),
@@ -93863,6 +93868,13 @@ var init_embed_util = __esm(() => {
93863
93868
  });
93864
93869
 
93865
93870
  // ../api-core/src/services/embed.service.ts
93871
+ async function scopedContextId(partnerId, contextId) {
93872
+ return sha256Hex(`${partnerId}:${contextId}`);
93873
+ }
93874
+ function launchTokenClaims(launch) {
93875
+ return launch.contextId ? { contextId: launch.contextId } : {};
93876
+ }
93877
+
93866
93878
  class EmbedService {
93867
93879
  deps;
93868
93880
  constructor(deps) {
@@ -93906,6 +93918,7 @@ class EmbedService {
93906
93918
  gameId: game.id,
93907
93919
  parentOrigin: input.parentOrigin,
93908
93920
  timebackCourseId: input.timebackCourseId ?? null,
93921
+ contextId: input.contextId ? await scopedContextId(partner.id, input.contextId) : null,
93909
93922
  passHash: await sha256Hex(pass),
93910
93923
  passExpiresAt,
93911
93924
  expiresAt: new Date(now + EMBED_LAUNCH_TTL_MS)
@@ -93913,7 +93926,11 @@ class EmbedService {
93913
93926
  if (!launch) {
93914
93927
  throw new InternalError("Failed to create embed launch");
93915
93928
  }
93916
- setAttributes({ "app.launch.id": launch.id, "app.user.id": user.id });
93929
+ setAttributes({
93930
+ "app.launch.id": launch.id,
93931
+ "app.user.id": user.id,
93932
+ "app.embed.has_context": input.contextId !== undefined
93933
+ });
93917
93934
  return { launchId: launch.id, pass, passExpiresAt, gameSlug: game.slug };
93918
93935
  }
93919
93936
  async redeemLaunch(input, timeZone) {
@@ -93928,7 +93945,7 @@ class EmbedService {
93928
93945
  const [game, profile, token] = await Promise.all([
93929
93946
  this.deps.getGameForStudent(launch.gameId, user),
93930
93947
  this.deps.getGameProfile(user, launch.gameId, timeZone),
93931
- this.deps.mintGameToken(launch.gameId, user.id)
93948
+ this.deps.mintGameToken(launch.gameId, user.id, launchTokenClaims(launch))
93932
93949
  ]);
93933
93950
  await this.deps.db.update(embedLaunches).set({ redeemedAt: new Date }).where(eq(embedLaunches.id, launch.id));
93934
93951
  return {
@@ -93960,7 +93977,7 @@ class EmbedService {
93960
93977
  if (outcome) {
93961
93978
  throw new AccessDeniedError("This launch has ended");
93962
93979
  }
93963
- return this.deps.mintGameToken(launch.gameId, launch.userId);
93980
+ return this.deps.mintGameToken(launch.gameId, launch.userId, launchTokenClaims(launch));
93964
93981
  }
93965
93982
  async endLaunch(launchId) {
93966
93983
  await this.deps.db.update(embedLaunches).set({ endedAt: sql`coalesce(${embedLaunches.endedAt}, now())`, passHash: null }).where(eq(embedLaunches.id, launchId));
@@ -95062,10 +95079,10 @@ function createSandboxAuthProvider() {
95062
95079
  }
95063
95080
  return deleted;
95064
95081
  },
95065
- async mintGameToken(gameId, userId) {
95082
+ async mintGameToken(gameId, userId, claims) {
95066
95083
  const header = btoa(JSON.stringify({ alg: "none", typ: "sandbox" }));
95067
95084
  const exp = Math.floor(Date.now() / 1000) + 15 * 60;
95068
- const payload = btoa(JSON.stringify({ sub: gameId, uid: userId, exp }));
95085
+ const payload = btoa(JSON.stringify({ ...claims, sub: gameId, uid: userId, exp }));
95069
95086
  const token = `${header}.${payload}.sandbox`;
95070
95087
  return { token, exp: exp * 1000 };
95071
95088
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.12.2-beta.4",
3
+ "version": "0.12.2-beta.5",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {