@playcademy/sandbox 0.12.2-beta.6 → 0.13.0

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 (3) hide show
  1. package/dist/cli.js +55 -23
  2. package/dist/server.js +55 -23
  3. package/package.json +1 -1
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.6",
2520
+ version: "0.13.0",
2521
2521
  description: "Local development server for Playcademy game development",
2522
2522
  type: "module",
2523
2523
  exports: {
@@ -20906,11 +20906,14 @@ var init_table8 = __esm(() => {
20906
20906
  id: uuid("id").primaryKey().defaultRandom(),
20907
20907
  name: text("name").notNull(),
20908
20908
  clientId: text("client_id").notNull().unique(),
20909
+ handle: text("handle").unique(),
20909
20910
  allowedOrigins: text("allowed_origins").array().notNull().default([]),
20910
20911
  enabled: boolean("enabled").notNull().default(true),
20911
20912
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
20912
20913
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
20913
- });
20914
+ }, (table) => [
20915
+ check("embed_partners_handle_check", sql`${table.handle} ~ '^[a-z0-9]([a-z0-9-]*[a-z0-9])?([.][a-z0-9]([a-z0-9-]*[a-z0-9])?)+$'`)
20916
+ ]);
20914
20917
  embedLaunches = pgTable("embed_launches", {
20915
20918
  id: uuid("id").primaryKey().defaultRandom(),
20916
20919
  partnerId: uuid("partner_id").notNull().references(() => embedPartners.id, { onDelete: "restrict" }),
@@ -20920,6 +20923,7 @@ var init_table8 = __esm(() => {
20920
20923
  timebackCourseId: text("timeback_course_id"),
20921
20924
  contextId: text("context_id"),
20922
20925
  contextRole: embedContextRoleEnum("context_role"),
20926
+ contextAttributes: jsonb("context_attributes").$type(),
20923
20927
  passHash: text("pass_hash").unique(),
20924
20928
  passExpiresAt: timestamp("pass_expires_at", { withTimezone: true }).notNull(),
20925
20929
  redeemedAt: timestamp("redeemed_at", { withTimezone: true }),
@@ -50827,7 +50831,7 @@ function isCanonicalOrigin(value) {
50827
50831
  }
50828
50832
  return url.protocol === "https:" || url.protocol === "http:" && isLocalHostname(url.hostname);
50829
50833
  }
50830
- var LOCAL_HOSTNAMES, EmbedOriginSchema, EmbedContextIdSchema, EmbedContextSchema, EmbedPartnerClientIdSchema, EnsureEmbedPartnerSchema, AddEmbedPartnerOriginSchema, CreateEmbedLaunchSchema, RedeemEmbedPassSchema;
50834
+ var LOCAL_HOSTNAMES, EmbedOriginSchema, EmbedContextIdSchema, MAX_CONTEXT_ATTRIBUTES = 10, EmbedContextAttributesSchema, EmbedContextSchema, EmbedPartnerClientIdSchema, EmbedPartnerHandleSchema, EnsureEmbedPartnerSchema, AddEmbedPartnerOriginSchema, CreateEmbedLaunchSchema, RedeemEmbedPassSchema;
50831
50835
  var init_schemas5 = __esm(() => {
50832
50836
  init_esm();
50833
50837
  init_table8();
@@ -50838,13 +50842,23 @@ var init_schemas5 = __esm(() => {
50838
50842
  EmbedContextIdSchema = stringType().regex(/^[A-Za-z0-9._~-]{1,128}$/, {
50839
50843
  message: "context.id must be 1-128 URL-safe characters (letters, digits, . _ ~ -)"
50840
50844
  });
50845
+ EmbedContextAttributesSchema = recordType(stringType().regex(/^[A-Za-z][A-Za-z0-9_]{0,39}$/, {
50846
+ message: "context.attributes keys must be 1-40 characters: a letter, then letters, digits or _"
50847
+ }), stringType().regex(/^[A-Za-z0-9._~-]{1,100}$/, {
50848
+ message: "context.attributes values must be 1-100 URL-safe characters (letters, digits, . _ ~ -)"
50849
+ })).refine((attributes) => Object.keys(attributes).length <= MAX_CONTEXT_ATTRIBUTES, {
50850
+ message: `context.attributes takes at most ${MAX_CONTEXT_ATTRIBUTES} keys`
50851
+ });
50841
50852
  EmbedContextSchema = objectType({
50842
50853
  id: EmbedContextIdSchema,
50843
- role: enumType(embedContextRoleEnum.enumValues).optional()
50854
+ role: enumType(embedContextRoleEnum.enumValues).optional(),
50855
+ attributes: EmbedContextAttributesSchema.optional()
50844
50856
  }).strict();
50845
50857
  EmbedPartnerClientIdSchema = stringType().trim().min(1).max(200);
50858
+ EmbedPartnerHandleSchema = stringType().max(253).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?([.][a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/);
50846
50859
  EnsureEmbedPartnerSchema = objectType({
50847
- name: stringType().trim().min(1).max(100)
50860
+ name: stringType().trim().min(1).max(100),
50861
+ handle: EmbedPartnerHandleSchema.optional()
50848
50862
  }).strict();
50849
50863
  AddEmbedPartnerOriginSchema = objectType({
50850
50864
  origin: EmbedOriginSchema
@@ -95650,15 +95664,28 @@ class EmbedPartnerService {
95650
95664
  constructor(deps) {
95651
95665
  this.deps = deps;
95652
95666
  }
95653
- async ensurePartner(clientId, name2) {
95654
- const [partner] = await this.deps.db.insert(embedPartners).values({ clientId, name: name2 }).onConflictDoUpdate({
95667
+ async ensurePartner(clientId, name2, handle2) {
95668
+ const [partner] = await this.deps.db.insert(embedPartners).values({ clientId, name: name2, handle: handle2 }).onConflictDoUpdate({
95655
95669
  target: embedPartners.clientId,
95656
- set: { name: name2, updatedAt: new Date }
95657
- }).returning();
95658
- if (!partner) {
95659
- throw new InternalError("Failed to ensure embed partner");
95670
+ set: {
95671
+ name: name2,
95672
+ handle: sql`coalesce(${embedPartners.handle}, excluded.handle)`,
95673
+ updatedAt: new Date
95674
+ },
95675
+ setWhere: sql`excluded.handle IS NULL OR ${embedPartners.handle} IS NULL OR ${embedPartners.handle} = excluded.handle`
95676
+ }).returning().catch((error) => {
95677
+ if (isUniqueViolation(error)) {
95678
+ throw new ConflictError(`Another embed partner has the handle ${handle2}`);
95679
+ }
95680
+ throw error;
95681
+ });
95682
+ if (partner) {
95683
+ return partner;
95660
95684
  }
95661
- return partner;
95685
+ if (handle2) {
95686
+ throw new ConflictError("This embed partner already has a different handle, and it never changes");
95687
+ }
95688
+ throw new InternalError("Failed to ensure embed partner");
95662
95689
  }
95663
95690
  addOrigin(clientId, origin) {
95664
95691
  return this.setOrigins(clientId, sql`CASE WHEN ${origin} = ANY(${embedPartners.allowedOrigins})
@@ -95699,14 +95726,16 @@ var init_embed_util = __esm(() => {
95699
95726
  async function scopedContextId(partnerId, contextId) {
95700
95727
  return sha256Hex(`${partnerId}:${contextId}`);
95701
95728
  }
95702
- function launchTokenClaims(launch) {
95729
+ function launchTokenClaims(launch, partner) {
95703
95730
  if (!launch.contextId) {
95704
95731
  return {};
95705
95732
  }
95706
95733
  return {
95707
95734
  context: {
95708
95735
  id: launch.contextId,
95709
- ...launch.contextRole && { role: launch.contextRole }
95736
+ ...launch.contextRole && { role: launch.contextRole },
95737
+ ...launch.contextAttributes && { attributes: launch.contextAttributes },
95738
+ ...partner.handle && { partner: partner.handle }
95710
95739
  }
95711
95740
  };
95712
95741
  }
@@ -95756,6 +95785,7 @@ class EmbedService {
95756
95785
  timebackCourseId: input.timebackCourseId ?? null,
95757
95786
  contextId: input.context ? await scopedContextId(partner.id, input.context.id) : null,
95758
95787
  contextRole: input.context?.role ?? null,
95788
+ contextAttributes: input.context?.attributes ?? null,
95759
95789
  passHash: await sha256Hex(pass),
95760
95790
  passExpiresAt,
95761
95791
  expiresAt: new Date(now + EMBED_LAUNCH_TTL_MS)
@@ -95767,12 +95797,15 @@ class EmbedService {
95767
95797
  "app.launch.id": launch.id,
95768
95798
  "app.user.id": user.id,
95769
95799
  "app.embed.has_context": input.context !== undefined,
95770
- ...input.context?.role && { "app.embed.context_role": input.context.role }
95800
+ ...input.context?.role && { "app.embed.context_role": input.context.role },
95801
+ ...input.context?.attributes && {
95802
+ "app.embed.context_attribute_keys": Object.keys(input.context.attributes).toSorted()
95803
+ }
95771
95804
  });
95772
95805
  return { launchId: launch.id, pass, passExpiresAt, gameSlug: game.slug };
95773
95806
  }
95774
95807
  async redeemLaunch(input, timeZone) {
95775
- const launch = await this.consumePass(input);
95808
+ const { launch, partner } = await this.consumePass(input);
95776
95809
  try {
95777
95810
  const user = await this.deps.db.query.users.findFirst({
95778
95811
  where: eq(users.id, launch.userId)
@@ -95783,7 +95816,7 @@ class EmbedService {
95783
95816
  const [game, profile, token] = await Promise.all([
95784
95817
  this.deps.getGameForStudent(launch.gameId, user),
95785
95818
  this.deps.getGameProfile(user, launch.gameId, timeZone),
95786
- this.deps.mintGameToken(launch.gameId, user.id, launchTokenClaims(launch))
95819
+ this.deps.mintGameToken(launch.gameId, user.id, launchTokenClaims(launch, partner))
95787
95820
  ]);
95788
95821
  await this.deps.db.update(embedLaunches).set({ redeemedAt: new Date }).where(eq(embedLaunches.id, launch.id));
95789
95822
  return {
@@ -95815,7 +95848,7 @@ class EmbedService {
95815
95848
  if (outcome) {
95816
95849
  throw new AccessDeniedError("This launch has ended");
95817
95850
  }
95818
- return this.deps.mintGameToken(launch.gameId, launch.userId, launchTokenClaims(launch));
95851
+ return this.deps.mintGameToken(launch.gameId, launch.userId, launchTokenClaims(launch, partner));
95819
95852
  }
95820
95853
  async endLaunch(launchId) {
95821
95854
  await this.deps.db.update(embedLaunches).set({ endedAt: sql`coalesce(${embedLaunches.endedAt}, now())`, passHash: null }).where(eq(embedLaunches.id, launchId));
@@ -95835,18 +95868,17 @@ class EmbedService {
95835
95868
  });
95836
95869
  const partner = await this.deps.db.query.embedPartners.findFirst({
95837
95870
  where: eq(embedPartners.id, launch.partnerId),
95838
- columns: { enabled: true, allowedOrigins: true }
95871
+ columns: { enabled: true, allowedOrigins: true, handle: true }
95839
95872
  });
95840
95873
  const originMatches = launch.parentOrigin === input.parentOrigin;
95841
- const stillAllowed = partner !== undefined && canPartnerLaunch(partner, launch.parentOrigin);
95842
- if (!originMatches || !stillAllowed) {
95874
+ if (!originMatches || !partner || !canPartnerLaunch(partner, launch.parentOrigin)) {
95843
95875
  await this.endLaunch(launch.id);
95844
95876
  setAttributes({
95845
95877
  "app.embed.outcome": originMatches ? "partner_disallowed" : "origin_mismatch"
95846
95878
  });
95847
95879
  throw new UnauthorizedError("This launch pass is not valid");
95848
95880
  }
95849
- return launch;
95881
+ return { launch, partner };
95850
95882
  }
95851
95883
  }
95852
95884
  function renewalRefusal(launch, isAllowed) {
@@ -152662,7 +152694,7 @@ async function ensurePartner(ctx) {
152662
152694
  await requireEmbedAdmin(ctx);
152663
152695
  const clientId = requireClientIdParam(ctx);
152664
152696
  const body2 = await parseRequestBody(ctx.request, EnsureEmbedPartnerSchema);
152665
- return ctx.services.embedPartner.ensurePartner(clientId, body2.name);
152697
+ return ctx.services.embedPartner.ensurePartner(clientId, body2.name, body2.handle);
152666
152698
  }
152667
152699
  async function addPartnerOrigin(ctx) {
152668
152700
  await requireEmbedAdmin(ctx);
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.6",
700
+ version: "0.13.0",
701
701
  description: "Local development server for Playcademy game development",
702
702
  type: "module",
703
703
  exports: {
@@ -19086,11 +19086,14 @@ var init_table8 = __esm(() => {
19086
19086
  id: uuid("id").primaryKey().defaultRandom(),
19087
19087
  name: text("name").notNull(),
19088
19088
  clientId: text("client_id").notNull().unique(),
19089
+ handle: text("handle").unique(),
19089
19090
  allowedOrigins: text("allowed_origins").array().notNull().default([]),
19090
19091
  enabled: boolean("enabled").notNull().default(true),
19091
19092
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
19092
19093
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
19093
- });
19094
+ }, (table) => [
19095
+ check("embed_partners_handle_check", sql`${table.handle} ~ '^[a-z0-9]([a-z0-9-]*[a-z0-9])?([.][a-z0-9]([a-z0-9-]*[a-z0-9])?)+$'`)
19096
+ ]);
19094
19097
  embedLaunches = pgTable("embed_launches", {
19095
19098
  id: uuid("id").primaryKey().defaultRandom(),
19096
19099
  partnerId: uuid("partner_id").notNull().references(() => embedPartners.id, { onDelete: "restrict" }),
@@ -19100,6 +19103,7 @@ var init_table8 = __esm(() => {
19100
19103
  timebackCourseId: text("timeback_course_id"),
19101
19104
  contextId: text("context_id"),
19102
19105
  contextRole: embedContextRoleEnum("context_role"),
19106
+ contextAttributes: jsonb("context_attributes").$type(),
19103
19107
  passHash: text("pass_hash").unique(),
19104
19108
  passExpiresAt: timestamp("pass_expires_at", { withTimezone: true }).notNull(),
19105
19109
  redeemedAt: timestamp("redeemed_at", { withTimezone: true }),
@@ -49007,7 +49011,7 @@ function isCanonicalOrigin(value) {
49007
49011
  }
49008
49012
  return url.protocol === "https:" || url.protocol === "http:" && isLocalHostname(url.hostname);
49009
49013
  }
49010
- var LOCAL_HOSTNAMES, EmbedOriginSchema, EmbedContextIdSchema, EmbedContextSchema, EmbedPartnerClientIdSchema, EnsureEmbedPartnerSchema, AddEmbedPartnerOriginSchema, CreateEmbedLaunchSchema, RedeemEmbedPassSchema;
49014
+ var LOCAL_HOSTNAMES, EmbedOriginSchema, EmbedContextIdSchema, MAX_CONTEXT_ATTRIBUTES = 10, EmbedContextAttributesSchema, EmbedContextSchema, EmbedPartnerClientIdSchema, EmbedPartnerHandleSchema, EnsureEmbedPartnerSchema, AddEmbedPartnerOriginSchema, CreateEmbedLaunchSchema, RedeemEmbedPassSchema;
49011
49015
  var init_schemas5 = __esm(() => {
49012
49016
  init_esm();
49013
49017
  init_table8();
@@ -49018,13 +49022,23 @@ var init_schemas5 = __esm(() => {
49018
49022
  EmbedContextIdSchema = stringType().regex(/^[A-Za-z0-9._~-]{1,128}$/, {
49019
49023
  message: "context.id must be 1-128 URL-safe characters (letters, digits, . _ ~ -)"
49020
49024
  });
49025
+ EmbedContextAttributesSchema = recordType(stringType().regex(/^[A-Za-z][A-Za-z0-9_]{0,39}$/, {
49026
+ message: "context.attributes keys must be 1-40 characters: a letter, then letters, digits or _"
49027
+ }), stringType().regex(/^[A-Za-z0-9._~-]{1,100}$/, {
49028
+ message: "context.attributes values must be 1-100 URL-safe characters (letters, digits, . _ ~ -)"
49029
+ })).refine((attributes) => Object.keys(attributes).length <= MAX_CONTEXT_ATTRIBUTES, {
49030
+ message: `context.attributes takes at most ${MAX_CONTEXT_ATTRIBUTES} keys`
49031
+ });
49021
49032
  EmbedContextSchema = objectType({
49022
49033
  id: EmbedContextIdSchema,
49023
- role: enumType(embedContextRoleEnum.enumValues).optional()
49034
+ role: enumType(embedContextRoleEnum.enumValues).optional(),
49035
+ attributes: EmbedContextAttributesSchema.optional()
49024
49036
  }).strict();
49025
49037
  EmbedPartnerClientIdSchema = stringType().trim().min(1).max(200);
49038
+ EmbedPartnerHandleSchema = stringType().max(253).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?([.][a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/);
49026
49039
  EnsureEmbedPartnerSchema = objectType({
49027
- name: stringType().trim().min(1).max(100)
49040
+ name: stringType().trim().min(1).max(100),
49041
+ handle: EmbedPartnerHandleSchema.optional()
49028
49042
  }).strict();
49029
49043
  AddEmbedPartnerOriginSchema = objectType({
49030
49044
  origin: EmbedOriginSchema
@@ -93830,15 +93844,28 @@ class EmbedPartnerService {
93830
93844
  constructor(deps) {
93831
93845
  this.deps = deps;
93832
93846
  }
93833
- async ensurePartner(clientId, name2) {
93834
- const [partner] = await this.deps.db.insert(embedPartners).values({ clientId, name: name2 }).onConflictDoUpdate({
93847
+ async ensurePartner(clientId, name2, handle2) {
93848
+ const [partner] = await this.deps.db.insert(embedPartners).values({ clientId, name: name2, handle: handle2 }).onConflictDoUpdate({
93835
93849
  target: embedPartners.clientId,
93836
- set: { name: name2, updatedAt: new Date }
93837
- }).returning();
93838
- if (!partner) {
93839
- throw new InternalError("Failed to ensure embed partner");
93850
+ set: {
93851
+ name: name2,
93852
+ handle: sql`coalesce(${embedPartners.handle}, excluded.handle)`,
93853
+ updatedAt: new Date
93854
+ },
93855
+ setWhere: sql`excluded.handle IS NULL OR ${embedPartners.handle} IS NULL OR ${embedPartners.handle} = excluded.handle`
93856
+ }).returning().catch((error) => {
93857
+ if (isUniqueViolation(error)) {
93858
+ throw new ConflictError(`Another embed partner has the handle ${handle2}`);
93859
+ }
93860
+ throw error;
93861
+ });
93862
+ if (partner) {
93863
+ return partner;
93840
93864
  }
93841
- return partner;
93865
+ if (handle2) {
93866
+ throw new ConflictError("This embed partner already has a different handle, and it never changes");
93867
+ }
93868
+ throw new InternalError("Failed to ensure embed partner");
93842
93869
  }
93843
93870
  addOrigin(clientId, origin) {
93844
93871
  return this.setOrigins(clientId, sql`CASE WHEN ${origin} = ANY(${embedPartners.allowedOrigins})
@@ -93879,14 +93906,16 @@ var init_embed_util = __esm(() => {
93879
93906
  async function scopedContextId(partnerId, contextId) {
93880
93907
  return sha256Hex(`${partnerId}:${contextId}`);
93881
93908
  }
93882
- function launchTokenClaims(launch) {
93909
+ function launchTokenClaims(launch, partner) {
93883
93910
  if (!launch.contextId) {
93884
93911
  return {};
93885
93912
  }
93886
93913
  return {
93887
93914
  context: {
93888
93915
  id: launch.contextId,
93889
- ...launch.contextRole && { role: launch.contextRole }
93916
+ ...launch.contextRole && { role: launch.contextRole },
93917
+ ...launch.contextAttributes && { attributes: launch.contextAttributes },
93918
+ ...partner.handle && { partner: partner.handle }
93890
93919
  }
93891
93920
  };
93892
93921
  }
@@ -93936,6 +93965,7 @@ class EmbedService {
93936
93965
  timebackCourseId: input.timebackCourseId ?? null,
93937
93966
  contextId: input.context ? await scopedContextId(partner.id, input.context.id) : null,
93938
93967
  contextRole: input.context?.role ?? null,
93968
+ contextAttributes: input.context?.attributes ?? null,
93939
93969
  passHash: await sha256Hex(pass),
93940
93970
  passExpiresAt,
93941
93971
  expiresAt: new Date(now + EMBED_LAUNCH_TTL_MS)
@@ -93947,12 +93977,15 @@ class EmbedService {
93947
93977
  "app.launch.id": launch.id,
93948
93978
  "app.user.id": user.id,
93949
93979
  "app.embed.has_context": input.context !== undefined,
93950
- ...input.context?.role && { "app.embed.context_role": input.context.role }
93980
+ ...input.context?.role && { "app.embed.context_role": input.context.role },
93981
+ ...input.context?.attributes && {
93982
+ "app.embed.context_attribute_keys": Object.keys(input.context.attributes).toSorted()
93983
+ }
93951
93984
  });
93952
93985
  return { launchId: launch.id, pass, passExpiresAt, gameSlug: game.slug };
93953
93986
  }
93954
93987
  async redeemLaunch(input, timeZone) {
93955
- const launch = await this.consumePass(input);
93988
+ const { launch, partner } = await this.consumePass(input);
93956
93989
  try {
93957
93990
  const user = await this.deps.db.query.users.findFirst({
93958
93991
  where: eq(users.id, launch.userId)
@@ -93963,7 +93996,7 @@ class EmbedService {
93963
93996
  const [game, profile, token] = await Promise.all([
93964
93997
  this.deps.getGameForStudent(launch.gameId, user),
93965
93998
  this.deps.getGameProfile(user, launch.gameId, timeZone),
93966
- this.deps.mintGameToken(launch.gameId, user.id, launchTokenClaims(launch))
93999
+ this.deps.mintGameToken(launch.gameId, user.id, launchTokenClaims(launch, partner))
93967
94000
  ]);
93968
94001
  await this.deps.db.update(embedLaunches).set({ redeemedAt: new Date }).where(eq(embedLaunches.id, launch.id));
93969
94002
  return {
@@ -93995,7 +94028,7 @@ class EmbedService {
93995
94028
  if (outcome) {
93996
94029
  throw new AccessDeniedError("This launch has ended");
93997
94030
  }
93998
- return this.deps.mintGameToken(launch.gameId, launch.userId, launchTokenClaims(launch));
94031
+ return this.deps.mintGameToken(launch.gameId, launch.userId, launchTokenClaims(launch, partner));
93999
94032
  }
94000
94033
  async endLaunch(launchId) {
94001
94034
  await this.deps.db.update(embedLaunches).set({ endedAt: sql`coalesce(${embedLaunches.endedAt}, now())`, passHash: null }).where(eq(embedLaunches.id, launchId));
@@ -94015,18 +94048,17 @@ class EmbedService {
94015
94048
  });
94016
94049
  const partner = await this.deps.db.query.embedPartners.findFirst({
94017
94050
  where: eq(embedPartners.id, launch.partnerId),
94018
- columns: { enabled: true, allowedOrigins: true }
94051
+ columns: { enabled: true, allowedOrigins: true, handle: true }
94019
94052
  });
94020
94053
  const originMatches = launch.parentOrigin === input.parentOrigin;
94021
- const stillAllowed = partner !== undefined && canPartnerLaunch(partner, launch.parentOrigin);
94022
- if (!originMatches || !stillAllowed) {
94054
+ if (!originMatches || !partner || !canPartnerLaunch(partner, launch.parentOrigin)) {
94023
94055
  await this.endLaunch(launch.id);
94024
94056
  setAttributes({
94025
94057
  "app.embed.outcome": originMatches ? "partner_disallowed" : "origin_mismatch"
94026
94058
  });
94027
94059
  throw new UnauthorizedError("This launch pass is not valid");
94028
94060
  }
94029
- return launch;
94061
+ return { launch, partner };
94030
94062
  }
94031
94063
  }
94032
94064
  function renewalRefusal(launch, isAllowed) {
@@ -150842,7 +150874,7 @@ async function ensurePartner(ctx) {
150842
150874
  await requireEmbedAdmin(ctx);
150843
150875
  const clientId = requireClientIdParam(ctx);
150844
150876
  const body2 = await parseRequestBody(ctx.request, EnsureEmbedPartnerSchema);
150845
- return ctx.services.embedPartner.ensurePartner(clientId, body2.name);
150877
+ return ctx.services.embedPartner.ensurePartner(clientId, body2.name, body2.handle);
150846
150878
  }
150847
150879
  async function addPartnerOrigin(ctx) {
150848
150880
  await requireEmbedAdmin(ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.12.2-beta.6",
3
+ "version": "0.13.0",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {