@superbuilders/primer-tives 2.0.0 → 2.2.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 (39) hide show
  1. package/README.md +742 -540
  2. package/dist/client/create.d.ts +22 -6
  3. package/dist/client/create.d.ts.map +1 -1
  4. package/dist/client/index.js +89 -51
  5. package/dist/client/index.js.map +14 -11
  6. package/dist/client/session.d.ts +1 -1
  7. package/dist/client/session.d.ts.map +1 -1
  8. package/dist/client/transport.d.ts +4 -5
  9. package/dist/client/transport.d.ts.map +1 -1
  10. package/dist/contracts/index.d.ts +3 -2
  11. package/dist/contracts/index.d.ts.map +1 -1
  12. package/dist/contracts/index.js +42 -36
  13. package/dist/contracts/index.js.map +6 -5
  14. package/dist/contracts/pci-schemas.d.ts +24 -20
  15. package/dist/contracts/pci-schemas.d.ts.map +1 -1
  16. package/dist/contracts/pci.d.ts +26 -27
  17. package/dist/contracts/pci.d.ts.map +1 -1
  18. package/dist/contracts/validation.d.ts +34 -23
  19. package/dist/contracts/validation.d.ts.map +1 -1
  20. package/dist/errors.d.ts +2 -6
  21. package/dist/errors.d.ts.map +1 -1
  22. package/dist/errors.js +3 -11
  23. package/dist/errors.js.map +3 -3
  24. package/dist/server/create-server.d.ts +3 -33
  25. package/dist/server/create-server.d.ts.map +1 -1
  26. package/dist/server/exchange.d.ts +7 -14
  27. package/dist/server/exchange.d.ts.map +1 -1
  28. package/dist/server/index.d.ts +1 -3
  29. package/dist/server/index.d.ts.map +1 -1
  30. package/dist/server/index.js +31 -407
  31. package/dist/server/index.js.map +6 -9
  32. package/dist/subject-pcis.d.ts +13 -0
  33. package/dist/subject-pcis.d.ts.map +1 -0
  34. package/dist/version.d.ts +1 -1
  35. package/package.json +5 -1
  36. package/dist/server/hints.d.ts +0 -25
  37. package/dist/server/hints.d.ts.map +0 -1
  38. package/dist/server/students.d.ts +0 -12
  39. package/dist/server/students.d.ts.map +0 -1
@@ -1,20 +1,36 @@
1
1
  import type { PrimerLogger } from "../logger";
2
2
  import type { PciId } from "../contracts/pci";
3
- import type { SubjectScope } from "../subject";
4
3
  import type { PrimerState } from "./types";
5
- interface Config<Pcis extends PciId = PciId> {
4
+ import type { SubjectScope } from "../subject";
5
+ import { type RequiredPciForSubject } from "../subject-pcis";
6
+ type SupportedPciForSubject<S extends SubjectScope, Pcis extends PciId> = Pcis | RequiredPciForSubject<S>;
7
+ type ConfigFields<S extends SubjectScope> = {
6
8
  readonly accessToken: string;
7
- readonly supportedPcis: readonly Pcis[];
9
+ readonly subject: S;
8
10
  readonly origin: string;
9
- readonly subject: SubjectScope;
10
11
  readonly fetch?: typeof globalThis.fetch;
11
12
  readonly abort?: AbortController;
12
13
  readonly logger: PrimerLogger;
13
- }
14
+ };
15
+ type PciConfigForSubject<S extends SubjectScope, Pcis extends PciId> = [
16
+ RequiredPciForSubject<S>
17
+ ] extends [never] ? {
18
+ readonly supportedPcis?: readonly Pcis[];
19
+ } : {
20
+ readonly supportedPcis: readonly [RequiredPciForSubject<S>, ...Pcis[]];
21
+ };
22
+ type Config<S extends SubjectScope, Pcis extends PciId = never> = ConfigFields<S> & PciConfigForSubject<S, Pcis>;
23
+ type DynamicSubjectConfig<Pcis extends PciId = never> = ConfigFields<SubjectScope> & {
24
+ readonly supportedPcis: readonly [RequiredPciForSubject<"all">, ...Pcis[]];
25
+ };
14
26
  interface Client<Pcis extends PciId = PciId> {
15
27
  start(): Promise<PrimerState<Pcis>>;
16
28
  }
17
- declare function create<const Pcis extends PciId>(config: Config<Pcis>): Client<Pcis>;
29
+ type SubjectWithoutRequiredPcis = Exclude<SubjectScope, "all" | "math">;
30
+ declare function create<const Pcis extends PciId = never>(config: Config<"math", Pcis>): Client<SupportedPciForSubject<"math", Pcis>>;
31
+ declare function create<const Pcis extends PciId = never>(config: Config<"all", Pcis>): Client<SupportedPciForSubject<"all", Pcis>>;
32
+ declare function create<const S extends SubjectWithoutRequiredPcis, const Pcis extends PciId = never>(config: Config<S, Pcis>): Client<SupportedPciForSubject<S, Pcis>>;
33
+ declare function create<const Pcis extends PciId = never>(config: DynamicSubjectConfig<Pcis>): Client<SupportedPciForSubject<"all", Pcis>>;
18
34
  export { create };
19
35
  export type { Client, Config };
20
36
  //# sourceMappingURL=create.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/client/create.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AACtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAA;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAA;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAA;AAM3E,UAAU,MAAM,CAAC,IAAI,SAAS,KAAK,GAAG,KAAK;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,aAAa,EAAE,SAAS,IAAI,EAAE,CAAA;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAC7B;AAED,UAAU,MAAM,CAAC,IAAI,SAAS,KAAK,GAAG,KAAK;IAC1C,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAA;CACnC;AAUD,iBAAS,MAAM,CAAC,KAAK,CAAC,IAAI,SAAS,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CA2C5E;AAED,OAAO,EAAE,MAAM,EAAE,CAAA;AACjB,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/client/create.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AACtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2CAA2C,CAAA;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0CAA0C,CAAA;AAG3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAA;AACvE,OAAO,EAEN,KAAK,qBAAqB,EAC1B,MAAM,0CAA0C,CAAA;AAIjD,KAAK,sBAAsB,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,SAAS,KAAK,IACnE,IAAI,GACJ,qBAAqB,CAAC,CAAC,CAAC,CAAA;AAE3B,KAAK,YAAY,CAAC,CAAC,SAAS,YAAY,IAAI;IAC3C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IACxC,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAA;CAC7B,CAAA;AAED,KAAK,mBAAmB,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,SAAS,KAAK,IAAI;IACtE,qBAAqB,CAAC,CAAC,CAAC;CACxB,SAAS,CAAC,KAAK,CAAC,GACd;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,IAAI,EAAE,CAAA;CAAE,GAC5C;IAAE,QAAQ,CAAC,aAAa,EAAE,SAAS,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;CAAE,CAAA;AAE7E,KAAK,MAAM,CAAC,CAAC,SAAS,YAAY,EAAE,IAAI,SAAS,KAAK,GAAG,KAAK,IAAI,YAAY,CAAC,CAAC,CAAC,GAChF,mBAAmB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;AAE7B,KAAK,oBAAoB,CAAC,IAAI,SAAS,KAAK,GAAG,KAAK,IAAI,YAAY,CAAC,YAAY,CAAC,GAAG;IACpF,QAAQ,CAAC,aAAa,EAAE,SAAS,CAAC,qBAAqB,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;CAC1E,CAAA;AAMD,UAAU,MAAM,CAAC,IAAI,SAAS,KAAK,GAAG,KAAK;IAC1C,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAA;CACnC;AAED,KAAK,0BAA0B,GAAG,OAAO,CAAC,YAAY,EAAE,KAAK,GAAG,MAAM,CAAC,CAAA;AAUvE,iBAAS,MAAM,CAAC,KAAK,CAAC,IAAI,SAAS,KAAK,GAAG,KAAK,EAC/C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAC1B,MAAM,CAAC,sBAAsB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;AAC/C,iBAAS,MAAM,CAAC,KAAK,CAAC,IAAI,SAAS,KAAK,GAAG,KAAK,EAC/C,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,GACzB,MAAM,CAAC,sBAAsB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;AAC9C,iBAAS,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,0BAA0B,EAAE,KAAK,CAAC,IAAI,SAAS,KAAK,GAAG,KAAK,EAC3F,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,GACrB,MAAM,CAAC,sBAAsB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;AAC1C,iBAAS,MAAM,CAAC,KAAK,CAAC,IAAI,SAAS,KAAK,GAAG,KAAK,EAC/C,MAAM,EAAE,oBAAoB,CAAC,IAAI,CAAC,GAChC,MAAM,CAAC,sBAAsB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;AAuD9C,OAAO,EAAE,MAAM,EAAE,CAAA;AACjB,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA"}
@@ -3,6 +3,7 @@ import * as errors from "@superbuilders/errors";
3
3
  var ErrNetwork = errors.new("network");
4
4
  var ErrJsonParse = errors.new("json parse");
5
5
  var ErrUnsupportedPci = errors.new("unsupported pci");
6
+ var ErrMissingRequiredPci = errors.new("missing required pci");
6
7
  var ErrInvalidAccessToken = errors.new("invalid access token");
7
8
  var ErrMalformedAccessToken = errors.new("malformed access token");
8
9
  var ErrTokenExpired = errors.new("access token expired");
@@ -12,16 +13,11 @@ var ErrTimeout = errors.new("timeout");
12
13
  var ErrForbidden = errors.new("forbidden");
13
14
  var ErrNotFound = errors.new("not found");
14
15
  var ErrConflict = errors.new("conflict");
15
- var ErrExternalAuthorityRequired = errors.new("external authority required");
16
16
  var ErrRateLimited = errors.new("rate limited");
17
17
  var ErrServiceUnavailable = errors.new("service unavailable");
18
18
  var ErrNotSerializable = errors.new("PrimerState is live in-memory state and must not be serialized or stored");
19
19
  var ErrInvalidSubmission = errors.new("invalid submission");
20
20
  var ErrInvalidSecretKey = errors.new("invalid secret key");
21
- var ErrStudentNotFound = errors.new("student not found");
22
- var ErrUnsupportedGrade = errors.new("unsupported grade");
23
- var ErrTimebackUnavailable = errors.new("timeback unavailable");
24
- var ErrNeedsHints = errors.new("student needs hints set before /advance");
25
21
  var ErrSdkUpgradeRequired = errors.new("sdk upgrade required");
26
22
  // src/contracts/content.ts
27
23
  function inlinesToPlainText(nodes) {
@@ -47,31 +43,44 @@ function blocksToPlainText(blocks) {
47
43
  }).join(`
48
44
  `);
49
45
  }
46
+ // src/contracts/pci.ts
47
+ var PCI_IDS = ["urn:primer:pci:fraction-input"];
48
+ function isPciId(value) {
49
+ for (const id of PCI_IDS) {
50
+ if (id === value) {
51
+ return true;
52
+ }
53
+ }
54
+ return false;
55
+ }
50
56
  // src/contracts/validation.ts
51
57
  import { z as z2 } from "zod";
52
58
 
53
59
  // src/contracts/pci-schemas.ts
54
60
  import { z } from "zod";
55
- var DivisionRemainderPropsSchema = z.object({
56
- dividend: z.number(),
57
- divisor: z.number()
58
- });
59
- var DivisionRemainderSubmissionSchema = z.object({
60
- quotient: z.string(),
61
- remainder: z.string()
62
- });
63
- var FractionOperandSchema = z.object({
64
- numerator: z.number(),
65
- denominator: z.number()
66
- });
67
- var FractionAdditionPropsSchema = z.object({
68
- left: FractionOperandSchema,
69
- right: FractionOperandSchema
70
- });
71
- var FractionAdditionSubmissionSchema = z.object({
72
- numerator: z.string(),
73
- denominator: z.string()
61
+ var FractionInputPropsSchema = z.object({
62
+ form: z.enum(["whole", "proper", "improper", "mixed"]),
63
+ requireSimplified: z.boolean()
74
64
  });
65
+ var FractionInputSubmissionSchema = z.discriminatedUnion("form", [
66
+ z.object({ form: z.literal("whole"), whole: z.string() }),
67
+ z.object({
68
+ form: z.literal("proper"),
69
+ numerator: z.string(),
70
+ denominator: z.string()
71
+ }),
72
+ z.object({
73
+ form: z.literal("improper"),
74
+ numerator: z.string(),
75
+ denominator: z.string()
76
+ }),
77
+ z.object({
78
+ form: z.literal("mixed"),
79
+ whole: z.string(),
80
+ numerator: z.string(),
81
+ denominator: z.string()
82
+ })
83
+ ]);
75
84
 
76
85
  // src/contracts/validation.ts
77
86
  var MatchPairSchema = z2.object({
@@ -98,15 +107,10 @@ var MatchSubmissionSchema = z2.object({
98
107
  type: z2.literal("match"),
99
108
  pairs: z2.array(MatchPairSchema)
100
109
  });
101
- var DivisionRemainderPciSubmissionSchema = z2.object({
110
+ var FractionInputPciSubmissionSchema = z2.object({
102
111
  type: z2.literal("portable-custom"),
103
- pciId: z2.literal("urn:primer:pci:division-remainder"),
104
- value: DivisionRemainderSubmissionSchema
105
- });
106
- var FractionAdditionPciSubmissionSchema = z2.object({
107
- type: z2.literal("portable-custom"),
108
- pciId: z2.literal("urn:primer:pci:fraction-addition"),
109
- value: FractionAdditionSubmissionSchema
112
+ pciId: z2.literal("urn:primer:pci:fraction-input"),
113
+ value: FractionInputSubmissionSchema
110
114
  });
111
115
  var RendererSubmissionSchema = z2.union([
112
116
  ChoiceSubmissionSchema,
@@ -114,8 +118,7 @@ var RendererSubmissionSchema = z2.union([
114
118
  ExtendedTextSubmissionSchema,
115
119
  OrderSubmissionSchema,
116
120
  MatchSubmissionSchema,
117
- DivisionRemainderPciSubmissionSchema,
118
- FractionAdditionPciSubmissionSchema
121
+ FractionInputPciSubmissionSchema
119
122
  ]);
120
123
  function duplicates(values, keyOf) {
121
124
  const seen = new Set;
@@ -167,10 +170,8 @@ function validateUsageBounds(choices, counts, side) {
167
170
  }
168
171
  function pciSubmissionSchema(pciId) {
169
172
  switch (pciId) {
170
- case "urn:primer:pci:division-remainder":
171
- return DivisionRemainderSubmissionSchema;
172
- case "urn:primer:pci:fraction-addition":
173
- return FractionAdditionSubmissionSchema;
173
+ case "urn:primer:pci:fraction-input":
174
+ return FractionInputSubmissionSchema;
174
175
  }
175
176
  const exhaustiveCheck = pciId;
176
177
  return exhaustiveCheck;
@@ -332,6 +333,8 @@ function validateSubmissionForInteraction(interaction, submission) {
332
333
  function submissionValidationMessage(result) {
333
334
  return result.issues.join("; ");
334
335
  }
336
+ // src/subject.ts
337
+ var SUBJECTS = ["math", "vocabulary", "science"];
335
338
  // src/client/create.ts
336
339
  import * as errors10 from "@superbuilders/errors";
337
340
 
@@ -339,7 +342,7 @@ import * as errors10 from "@superbuilders/errors";
339
342
  import * as errors2 from "@superbuilders/errors";
340
343
 
341
344
  // src/version.ts
342
- var SDK_VERSION = "2.0.0";
345
+ var SDK_VERSION = "2.2.0";
343
346
  var NPM_PACKAGE_URL = "https://www.npmjs.com/package/@superbuilders/primer-tives";
344
347
 
345
348
  // src/client/transport.ts
@@ -415,9 +418,6 @@ function httpSentinel(status, body) {
415
418
  if (status === 409) {
416
419
  return ErrConflict;
417
420
  }
418
- if (status === 412) {
419
- return ErrNeedsHints;
420
- }
421
421
  if (status === 429) {
422
422
  return ErrRateLimited;
423
423
  }
@@ -504,7 +504,7 @@ function createTransport(tc) {
504
504
  const text = await res.text().catch(function fallback() {
505
505
  return "";
506
506
  });
507
- const sentinel = res.status === 422 ? ErrUnsupportedPci : httpSentinel(res.status, text);
507
+ const sentinel = httpSentinel(res.status, text);
508
508
  if (errors2.is(sentinel, ErrSdkUpgradeRequired)) {
509
509
  return { ok: false, error: buildSdkUpgradeRequiredError(sentinel, text, logger) };
510
510
  }
@@ -984,7 +984,6 @@ var FATAL_SENTINELS = [
984
984
  ErrTokenExpired,
985
985
  ErrForbidden,
986
986
  ErrNotFound,
987
- ErrNeedsHints,
988
987
  ErrSdkUpgradeRequired,
989
988
  ErrUnsupportedPci
990
989
  ];
@@ -1047,9 +1046,8 @@ function makeSession(sc) {
1047
1046
  }
1048
1047
  async function execute(intent, phase) {
1049
1048
  const body = {
1050
- supportedPcis: sc.supportedPcis,
1051
- intent,
1052
- subject: sc.subject
1049
+ subject: sc.subject,
1050
+ intent
1053
1051
  };
1054
1052
  const result = await sc.transport(body);
1055
1053
  if (!result.ok) {
@@ -1106,6 +1104,37 @@ function makeSession(sc) {
1106
1104
  return { execute };
1107
1105
  }
1108
1106
 
1107
+ // src/subject-pcis.ts
1108
+ var REQUIRED_PCIS_BY_SUBJECT = {
1109
+ math: ["urn:primer:pci:fraction-input"],
1110
+ vocabulary: [],
1111
+ science: []
1112
+ };
1113
+ function requiredPcisForSubject(subject) {
1114
+ if (subject === "all") {
1115
+ const all = [];
1116
+ for (const s of SUBJECTS) {
1117
+ for (const pci of REQUIRED_PCIS_BY_SUBJECT[s]) {
1118
+ if (!all.includes(pci)) {
1119
+ all.push(pci);
1120
+ }
1121
+ }
1122
+ }
1123
+ return all;
1124
+ }
1125
+ return REQUIRED_PCIS_BY_SUBJECT[subject];
1126
+ }
1127
+ function missingPcisForSubject(subject, provided) {
1128
+ const required = requiredPcisForSubject(subject);
1129
+ const missing = [];
1130
+ for (const pci of required) {
1131
+ if (provided === undefined || !provided.includes(pci)) {
1132
+ missing.push(pci);
1133
+ }
1134
+ }
1135
+ return missing;
1136
+ }
1137
+
1109
1138
  // src/client/create.ts
1110
1139
  var ACCESS_TOKEN_PREFIX = "eyJ";
1111
1140
  function isMalformedJws(token) {
@@ -1117,13 +1146,22 @@ function isMalformedJws(token) {
1117
1146
  }
1118
1147
  function create(config) {
1119
1148
  const logger = config.logger;
1149
+ let supportedPcis = [];
1150
+ if (config.supportedPcis !== undefined) {
1151
+ supportedPcis = config.supportedPcis;
1152
+ }
1153
+ const missingPcis = missingPcisForSubject(config.subject, supportedPcis);
1154
+ if (missingPcis.length > 0) {
1155
+ logger.error("renderer missing required pcis", { subject: config.subject, missingPcis });
1156
+ throw errors10.wrap(ErrMissingRequiredPci, `subject '${config.subject}'`);
1157
+ }
1120
1158
  if (isMalformedJws(config.accessToken)) {
1121
1159
  logger.error("malformed access token", { prefix: ACCESS_TOKEN_PREFIX });
1122
1160
  throw errors10.wrap(ErrMalformedAccessToken, `token must start with '${ACCESS_TOKEN_PREFIX}' and contain two dots`);
1123
1161
  }
1124
1162
  const transport = createTransport({
1125
1163
  accessToken: config.accessToken,
1126
- supportedPcis: config.supportedPcis,
1164
+ subject: config.subject,
1127
1165
  origin: config.origin,
1128
1166
  fetch: config.fetch,
1129
1167
  abort: config.abort,
@@ -1131,10 +1169,10 @@ function create(config) {
1131
1169
  });
1132
1170
  let startPromise;
1133
1171
  async function doStart() {
1134
- logger.debug("start", { subject: config.subject });
1172
+ logger.debug("start");
1135
1173
  const s = makeSession({
1136
- supportedPcis: config.supportedPcis,
1137
1174
  subject: config.subject,
1175
+ supportedPcis,
1138
1176
  logger,
1139
1177
  transport
1140
1178
  });
@@ -1155,4 +1193,4 @@ export {
1155
1193
  create
1156
1194
  };
1157
1195
 
1158
- //# debugId=5A77D78E02028F9D64756E2164756E21
1196
+ //# debugId=81BED1C3D30435C964756E2164756E21