@timeback/sdk 0.1.6 → 0.1.7
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/client/adapters/react/hooks/types.d.ts +15 -0
- package/dist/client/adapters/react/hooks/types.d.ts.map +1 -0
- package/dist/client/adapters/react/hooks/useTimebackVerification.d.ts +18 -0
- package/dist/client/adapters/react/hooks/useTimebackVerification.d.ts.map +1 -0
- package/dist/client/adapters/react/index.d.ts +2 -0
- package/dist/client/adapters/react/index.d.ts.map +1 -1
- package/dist/client/adapters/react/index.js +139 -9
- package/dist/client/auth/bearer.d.ts +17 -0
- package/dist/client/auth/bearer.d.ts.map +1 -0
- package/dist/client/auth/index.d.ts +3 -0
- package/dist/client/auth/index.d.ts.map +1 -0
- package/dist/client/auth/types.d.ts +39 -0
- package/dist/client/auth/types.d.ts.map +1 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/lib/fetch.d.ts +19 -0
- package/dist/client/lib/fetch.d.ts.map +1 -0
- package/dist/client/namespaces/user.d.ts +25 -2
- package/dist/client/namespaces/user.d.ts.map +1 -1
- package/dist/client/timeback-client.class.d.ts +15 -0
- package/dist/client/timeback-client.class.d.ts.map +1 -1
- package/dist/client/timeback-client.d.ts +3 -0
- package/dist/client/timeback-client.d.ts.map +1 -1
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +69 -6
- package/dist/edge.js +85291 -169
- package/dist/identity.js +85186 -74
- package/dist/index.js +773 -493
- package/dist/server/adapters/express.d.ts.map +1 -1
- package/dist/server/adapters/express.js +169 -193
- package/dist/server/adapters/native.d.ts.map +1 -1
- package/dist/server/adapters/native.js +32 -1
- package/dist/server/adapters/nextjs.js +32 -1
- package/dist/server/adapters/nuxt.d.ts.map +1 -1
- package/dist/server/adapters/nuxt.js +173 -193
- package/dist/server/adapters/solid-start.d.ts.map +1 -1
- package/dist/server/adapters/solid-start.js +173 -193
- package/dist/server/adapters/svelte-kit.d.ts.map +1 -1
- package/dist/server/adapters/svelte-kit.js +37 -1
- package/dist/server/adapters/tanstack-start.d.ts.map +1 -1
- package/dist/server/adapters/tanstack-start.js +168 -193
- package/dist/server/adapters/utils.d.ts +1 -1
- package/dist/server/adapters/utils.d.ts.map +1 -1
- package/dist/server/{lib/build-activity-events.d.ts → handlers/activity/caliper.d.ts} +29 -4
- package/dist/server/handlers/activity/caliper.d.ts.map +1 -0
- package/dist/server/handlers/activity/gradebook.d.ts +56 -0
- package/dist/server/handlers/activity/gradebook.d.ts.map +1 -0
- package/dist/server/handlers/activity/handler.d.ts +15 -0
- package/dist/server/handlers/activity/handler.d.ts.map +1 -0
- package/dist/server/handlers/activity/index.d.ts +9 -0
- package/dist/server/handlers/activity/index.d.ts.map +1 -0
- package/dist/server/handlers/activity/resolve.d.ts +39 -0
- package/dist/server/handlers/activity/resolve.d.ts.map +1 -0
- package/dist/server/handlers/activity/schema.d.ts +52 -0
- package/dist/server/handlers/activity/schema.d.ts.map +1 -0
- package/dist/server/handlers/activity/types.d.ts +52 -0
- package/dist/server/handlers/activity/types.d.ts.map +1 -0
- package/dist/server/handlers/identity/handler.d.ts +14 -0
- package/dist/server/handlers/identity/handler.d.ts.map +1 -0
- package/dist/server/handlers/identity/index.d.ts +8 -0
- package/dist/server/handlers/identity/index.d.ts.map +1 -0
- package/dist/server/handlers/identity/oidc.d.ts +43 -0
- package/dist/server/handlers/identity/oidc.d.ts.map +1 -0
- package/dist/server/handlers/identity/types.d.ts +24 -0
- package/dist/server/handlers/identity/types.d.ts.map +1 -0
- package/dist/server/handlers/identity-only/handler.d.ts +15 -0
- package/dist/server/handlers/identity-only/handler.d.ts.map +1 -0
- package/dist/server/handlers/identity-only/index.d.ts +8 -0
- package/dist/server/handlers/identity-only/index.d.ts.map +1 -0
- package/dist/server/handlers/identity-only/oidc.d.ts +26 -0
- package/dist/server/handlers/identity-only/oidc.d.ts.map +1 -0
- package/dist/server/handlers/identity-only/types.d.ts +19 -0
- package/dist/server/handlers/identity-only/types.d.ts.map +1 -0
- package/dist/server/handlers/index.d.ts +5 -2
- package/dist/server/handlers/index.d.ts.map +1 -1
- package/dist/server/{lib/build-user-profile.d.ts → handlers/user/enrollments.d.ts} +7 -2
- package/dist/server/handlers/user/enrollments.d.ts.map +1 -0
- package/dist/server/handlers/user/handler.d.ts +17 -0
- package/dist/server/handlers/user/handler.d.ts.map +1 -0
- package/dist/server/handlers/user/index.d.ts +10 -0
- package/dist/server/handlers/user/index.d.ts.map +1 -0
- package/dist/server/handlers/user/profile.d.ts +22 -0
- package/dist/server/handlers/user/profile.d.ts.map +1 -0
- package/dist/server/handlers/user/types.d.ts +35 -0
- package/dist/server/handlers/user/types.d.ts.map +1 -0
- package/dist/server/handlers/user/verify.d.ts +25 -0
- package/dist/server/handlers/user/verify.d.ts.map +1 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/lib/index.d.ts +4 -5
- package/dist/server/lib/index.d.ts.map +1 -1
- package/dist/server/lib/resolve.d.ts +4 -42
- package/dist/server/lib/resolve.d.ts.map +1 -1
- package/dist/server/lib/sso.d.ts +86 -0
- package/dist/server/lib/sso.d.ts.map +1 -0
- package/dist/server/lib/utils.d.ts +32 -1
- package/dist/server/lib/utils.d.ts.map +1 -1
- package/dist/server/timeback-identity.d.ts.map +1 -1
- package/dist/server/timeback.d.ts.map +1 -1
- package/dist/server/types.d.ts +16 -9
- package/dist/server/types.d.ts.map +1 -1
- package/dist/shared/constants.d.ts +1 -0
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/types.d.ts +15 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/package.json +6 -2
- package/dist/server/handlers/activity.d.ts +0 -25
- package/dist/server/handlers/activity.d.ts.map +0 -1
- package/dist/server/handlers/identity-full.d.ts +0 -28
- package/dist/server/handlers/identity-full.d.ts.map +0 -1
- package/dist/server/handlers/identity-only.d.ts +0 -22
- package/dist/server/handlers/identity-only.d.ts.map +0 -1
- package/dist/server/handlers/user.d.ts +0 -31
- package/dist/server/handlers/user.d.ts.map +0 -1
- package/dist/server/lib/build-activity-events.d.ts.map +0 -1
- package/dist/server/lib/build-user-profile.d.ts.map +0 -1
|
@@ -434,12 +434,32 @@ async function getUserInfo(params) {
|
|
|
434
434
|
return response.json();
|
|
435
435
|
}
|
|
436
436
|
// src/server/lib/utils.ts
|
|
437
|
+
function safeIdSegment(value) {
|
|
438
|
+
return encodeURIComponent(value).replace(/%/g, "_");
|
|
439
|
+
}
|
|
440
|
+
function hashSuffix64Base36(value) {
|
|
441
|
+
let hash = 0xcbf29ce484222325n;
|
|
442
|
+
const prime = 0x100000001b3n;
|
|
443
|
+
const mod64 = 0xffffffffffffffffn;
|
|
444
|
+
for (let i = 0;i < value.length; i++) {
|
|
445
|
+
hash ^= BigInt(value.charCodeAt(i));
|
|
446
|
+
hash = hash * prime & mod64;
|
|
447
|
+
}
|
|
448
|
+
const base36 = hash.toString(36);
|
|
449
|
+
return base36.length > 12 ? base36.slice(-12) : base36;
|
|
450
|
+
}
|
|
437
451
|
function mapEnvForApi(env) {
|
|
438
452
|
if (env === "local" || env === "staging") {
|
|
439
453
|
return "staging";
|
|
440
454
|
}
|
|
441
455
|
return "production";
|
|
442
456
|
}
|
|
457
|
+
function normalizeEnv(env) {
|
|
458
|
+
if (env === "production" || env === "local" || env === "staging") {
|
|
459
|
+
return env;
|
|
460
|
+
}
|
|
461
|
+
return "staging";
|
|
462
|
+
}
|
|
443
463
|
function jsonResponse(data, status = 200, headers) {
|
|
444
464
|
const responseHeaders = new Headers(headers);
|
|
445
465
|
responseHeaders.set("Content-Type", "application/json");
|
|
@@ -15739,14 +15759,20 @@ var TimebackConfig = exports_external.object({
|
|
|
15739
15759
|
message: "Duplicate courseCode found; each must be unique",
|
|
15740
15760
|
path: ["courses"]
|
|
15741
15761
|
}).refine((config2) => {
|
|
15742
|
-
return config2.courses.every((c) =>
|
|
15743
|
-
|
|
15744
|
-
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
15762
|
+
return config2.courses.every((c) => {
|
|
15763
|
+
if (c.sensor !== undefined || config2.sensor !== undefined) {
|
|
15764
|
+
return true;
|
|
15765
|
+
}
|
|
15766
|
+
const launchUrls = [
|
|
15767
|
+
c.launchUrl,
|
|
15768
|
+
config2.launchUrl,
|
|
15769
|
+
c.overrides?.staging?.launchUrl,
|
|
15770
|
+
c.overrides?.production?.launchUrl
|
|
15771
|
+
].filter(Boolean);
|
|
15772
|
+
return launchUrls.length > 0;
|
|
15773
|
+
});
|
|
15748
15774
|
}, {
|
|
15749
|
-
message: "Each course must have an effective
|
|
15775
|
+
message: "Each course must have an effective sensor. Either set `sensor` explicitly (top-level or per-course), or provide a `launchUrl` so sensor can be derived from its origin.",
|
|
15750
15776
|
path: ["courses"]
|
|
15751
15777
|
});
|
|
15752
15778
|
var EdubridgeDateString = exports_external.union([IsoDateString, IsoDateTimeString]);
|
|
@@ -32142,14 +32168,20 @@ var TimebackConfig2 = exports_external2.object({
|
|
|
32142
32168
|
message: "Duplicate courseCode found; each must be unique",
|
|
32143
32169
|
path: ["courses"]
|
|
32144
32170
|
}).refine((config22) => {
|
|
32145
|
-
return config22.courses.every((c) =>
|
|
32146
|
-
|
|
32147
|
-
|
|
32148
|
-
|
|
32149
|
-
|
|
32150
|
-
|
|
32171
|
+
return config22.courses.every((c) => {
|
|
32172
|
+
if (c.sensor !== undefined || config22.sensor !== undefined) {
|
|
32173
|
+
return true;
|
|
32174
|
+
}
|
|
32175
|
+
const launchUrls = [
|
|
32176
|
+
c.launchUrl,
|
|
32177
|
+
config22.launchUrl,
|
|
32178
|
+
c.overrides?.staging?.launchUrl,
|
|
32179
|
+
c.overrides?.production?.launchUrl
|
|
32180
|
+
].filter(Boolean);
|
|
32181
|
+
return launchUrls.length > 0;
|
|
32182
|
+
});
|
|
32151
32183
|
}, {
|
|
32152
|
-
message: "Each course must have an effective
|
|
32184
|
+
message: "Each course must have an effective sensor. Either set `sensor` explicitly (top-level or per-course), or provide a `launchUrl` so sensor can be derived from its origin.",
|
|
32153
32185
|
path: ["courses"]
|
|
32154
32186
|
});
|
|
32155
32187
|
var EdubridgeDateString2 = exports_external2.union([IsoDateString2, IsoDateTimeString2]);
|
|
@@ -49575,14 +49607,20 @@ var TimebackConfig3 = exports_external3.object({
|
|
|
49575
49607
|
message: "Duplicate courseCode found; each must be unique",
|
|
49576
49608
|
path: ["courses"]
|
|
49577
49609
|
}).refine((config22) => {
|
|
49578
|
-
return config22.courses.every((c) =>
|
|
49579
|
-
|
|
49580
|
-
|
|
49581
|
-
|
|
49582
|
-
|
|
49583
|
-
|
|
49610
|
+
return config22.courses.every((c) => {
|
|
49611
|
+
if (c.sensor !== undefined || config22.sensor !== undefined) {
|
|
49612
|
+
return true;
|
|
49613
|
+
}
|
|
49614
|
+
const launchUrls = [
|
|
49615
|
+
c.launchUrl,
|
|
49616
|
+
config22.launchUrl,
|
|
49617
|
+
c.overrides?.staging?.launchUrl,
|
|
49618
|
+
c.overrides?.production?.launchUrl
|
|
49619
|
+
].filter(Boolean);
|
|
49620
|
+
return launchUrls.length > 0;
|
|
49621
|
+
});
|
|
49584
49622
|
}, {
|
|
49585
|
-
message: "Each course must have an effective
|
|
49623
|
+
message: "Each course must have an effective sensor. Either set `sensor` explicitly (top-level or per-course), or provide a `launchUrl` so sensor can be derived from its origin.",
|
|
49586
49624
|
path: ["courses"]
|
|
49587
49625
|
});
|
|
49588
49626
|
var EdubridgeDateString3 = exports_external3.union([IsoDateString3, IsoDateTimeString3]);
|
|
@@ -67207,14 +67245,20 @@ var TimebackConfig4 = exports_external4.object({
|
|
|
67207
67245
|
message: "Duplicate courseCode found; each must be unique",
|
|
67208
67246
|
path: ["courses"]
|
|
67209
67247
|
}).refine((config22) => {
|
|
67210
|
-
return config22.courses.every((c) =>
|
|
67211
|
-
|
|
67212
|
-
|
|
67213
|
-
|
|
67214
|
-
|
|
67215
|
-
|
|
67248
|
+
return config22.courses.every((c) => {
|
|
67249
|
+
if (c.sensor !== undefined || config22.sensor !== undefined) {
|
|
67250
|
+
return true;
|
|
67251
|
+
}
|
|
67252
|
+
const launchUrls = [
|
|
67253
|
+
c.launchUrl,
|
|
67254
|
+
config22.launchUrl,
|
|
67255
|
+
c.overrides?.staging?.launchUrl,
|
|
67256
|
+
c.overrides?.production?.launchUrl
|
|
67257
|
+
].filter(Boolean);
|
|
67258
|
+
return launchUrls.length > 0;
|
|
67259
|
+
});
|
|
67216
67260
|
}, {
|
|
67217
|
-
message: "Each course must have an effective
|
|
67261
|
+
message: "Each course must have an effective sensor. Either set `sensor` explicitly (top-level or per-course), or provide a `launchUrl` so sensor can be derived from its origin.",
|
|
67218
67262
|
path: ["courses"]
|
|
67219
67263
|
});
|
|
67220
67264
|
var EdubridgeDateString4 = exports_external4.union([IsoDateString4, IsoDateTimeString4]);
|
|
@@ -83869,14 +83913,20 @@ var TimebackConfig5 = exports_external5.object({
|
|
|
83869
83913
|
message: "Duplicate courseCode found; each must be unique",
|
|
83870
83914
|
path: ["courses"]
|
|
83871
83915
|
}).refine((config22) => {
|
|
83872
|
-
return config22.courses.every((c) =>
|
|
83873
|
-
|
|
83874
|
-
|
|
83875
|
-
|
|
83876
|
-
|
|
83877
|
-
|
|
83916
|
+
return config22.courses.every((c) => {
|
|
83917
|
+
if (c.sensor !== undefined || config22.sensor !== undefined) {
|
|
83918
|
+
return true;
|
|
83919
|
+
}
|
|
83920
|
+
const launchUrls = [
|
|
83921
|
+
c.launchUrl,
|
|
83922
|
+
config22.launchUrl,
|
|
83923
|
+
c.overrides?.staging?.launchUrl,
|
|
83924
|
+
c.overrides?.production?.launchUrl
|
|
83925
|
+
].filter(Boolean);
|
|
83926
|
+
return launchUrls.length > 0;
|
|
83927
|
+
});
|
|
83878
83928
|
}, {
|
|
83879
|
-
message: "Each course must have an effective
|
|
83929
|
+
message: "Each course must have an effective sensor. Either set `sensor` explicitly (top-level or per-course), or provide a `launchUrl` so sensor can be derived from its origin.",
|
|
83880
83930
|
path: ["courses"]
|
|
83881
83931
|
});
|
|
83882
83932
|
var EdubridgeDateString5 = exports_external5.union([IsoDateString5, IsoDateTimeString5]);
|
|
@@ -85634,163 +85684,6 @@ async function lookupTimebackIdByEmail(params) {
|
|
|
85634
85684
|
throw new TimebackUserResolutionError(`Failed to lookup Timeback user: ${message}`, "timeback_user_lookup_failed");
|
|
85635
85685
|
}
|
|
85636
85686
|
}
|
|
85637
|
-
|
|
85638
|
-
class ActivityCourseResolutionError extends Error {
|
|
85639
|
-
code;
|
|
85640
|
-
selector;
|
|
85641
|
-
count;
|
|
85642
|
-
constructor(code, selector, count) {
|
|
85643
|
-
super(code);
|
|
85644
|
-
this.code = code;
|
|
85645
|
-
this.selector = selector;
|
|
85646
|
-
this.count = count;
|
|
85647
|
-
}
|
|
85648
|
-
get selectorDescription() {
|
|
85649
|
-
if ("grade" in this.selector) {
|
|
85650
|
-
return `${this.selector.subject} grade ${this.selector.grade}`;
|
|
85651
|
-
}
|
|
85652
|
-
return `code "${this.selector.code}"`;
|
|
85653
|
-
}
|
|
85654
|
-
}
|
|
85655
|
-
function resolveActivityCourse(courses, courseRef) {
|
|
85656
|
-
let matches;
|
|
85657
|
-
if ("grade" in courseRef) {
|
|
85658
|
-
matches = courses.filter((c) => c.subject === courseRef.subject && c.grade === courseRef.grade);
|
|
85659
|
-
} else {
|
|
85660
|
-
matches = courses.filter((c) => c.courseCode === courseRef.code);
|
|
85661
|
-
}
|
|
85662
|
-
if (matches.length === 0) {
|
|
85663
|
-
throw new ActivityCourseResolutionError("unknown_course", courseRef);
|
|
85664
|
-
}
|
|
85665
|
-
if (matches.length > 1) {
|
|
85666
|
-
throw new ActivityCourseResolutionError("ambiguous_course", courseRef, matches.length);
|
|
85667
|
-
}
|
|
85668
|
-
return matches[0];
|
|
85669
|
-
}
|
|
85670
|
-
// src/server/lib/build-activity-events.ts
|
|
85671
|
-
class MissingSyncedCourseIdError extends Error {
|
|
85672
|
-
course;
|
|
85673
|
-
env;
|
|
85674
|
-
constructor(course, env) {
|
|
85675
|
-
const identifier = course.grade === undefined ? course.courseCode ?? course.subject : `${course.subject} grade ${course.grade}`;
|
|
85676
|
-
super(`Course "${identifier}" is missing a synced ID for ${env}. Run \`timeback sync\` first.`);
|
|
85677
|
-
this.name = "MissingSyncedCourseIdError";
|
|
85678
|
-
this.course = course;
|
|
85679
|
-
this.env = env;
|
|
85680
|
-
}
|
|
85681
|
-
}
|
|
85682
|
-
function buildCourseId(course, apiEnv) {
|
|
85683
|
-
const courseId = course.ids?.[apiEnv];
|
|
85684
|
-
if (!courseId) {
|
|
85685
|
-
throw new MissingSyncedCourseIdError(course, apiEnv);
|
|
85686
|
-
}
|
|
85687
|
-
return courseId;
|
|
85688
|
-
}
|
|
85689
|
-
function buildCourseName(course) {
|
|
85690
|
-
if (course.courseCode) {
|
|
85691
|
-
return course.courseCode;
|
|
85692
|
-
}
|
|
85693
|
-
if (course.grade !== undefined) {
|
|
85694
|
-
return `${course.subject} G${String(course.grade)}`;
|
|
85695
|
-
}
|
|
85696
|
-
return course.subject;
|
|
85697
|
-
}
|
|
85698
|
-
|
|
85699
|
-
class InvalidSensorUrlError extends Error {
|
|
85700
|
-
sensor;
|
|
85701
|
-
constructor(sensor) {
|
|
85702
|
-
super(`Invalid sensor URL "${sensor}". Sensor must be a valid absolute URL (e.g., "https://sensor.example.com") to support slug-based activity IDs.`);
|
|
85703
|
-
this.name = "InvalidSensorUrlError";
|
|
85704
|
-
this.sensor = sensor;
|
|
85705
|
-
}
|
|
85706
|
-
}
|
|
85707
|
-
function buildCanonicalActivityUrl(sensor, selector, slug) {
|
|
85708
|
-
let base;
|
|
85709
|
-
try {
|
|
85710
|
-
base = new URL(sensor);
|
|
85711
|
-
} catch {
|
|
85712
|
-
throw new InvalidSensorUrlError(sensor);
|
|
85713
|
-
}
|
|
85714
|
-
const pathSegment = "grade" in selector ? `${selector.subject}/g${String(selector.grade)}` : selector.code;
|
|
85715
|
-
const basePath = base.pathname.replace(/\/+$/, "");
|
|
85716
|
-
base.pathname = `${basePath}/activities/${pathSegment}/${encodeURIComponent(slug)}`;
|
|
85717
|
-
return base.toString();
|
|
85718
|
-
}
|
|
85719
|
-
function buildActivityContext(payload, course, appName, apiEnv, sensor) {
|
|
85720
|
-
return {
|
|
85721
|
-
id: buildCanonicalActivityUrl(sensor, payload.course, payload.id),
|
|
85722
|
-
type: "TimebackActivityContext",
|
|
85723
|
-
subject: course.subject,
|
|
85724
|
-
app: { name: appName },
|
|
85725
|
-
activity: { name: payload.name },
|
|
85726
|
-
course: {
|
|
85727
|
-
id: buildCourseId(course, apiEnv),
|
|
85728
|
-
name: buildCourseName(course)
|
|
85729
|
-
}
|
|
85730
|
-
};
|
|
85731
|
-
}
|
|
85732
|
-
function buildActivityMetrics(metrics) {
|
|
85733
|
-
const result = [];
|
|
85734
|
-
if (metrics.totalQuestions !== undefined) {
|
|
85735
|
-
result.push({ type: "totalQuestions", value: metrics.totalQuestions });
|
|
85736
|
-
}
|
|
85737
|
-
if (metrics.correctQuestions !== undefined) {
|
|
85738
|
-
result.push({ type: "correctQuestions", value: metrics.correctQuestions });
|
|
85739
|
-
}
|
|
85740
|
-
if (metrics.xpEarned !== undefined) {
|
|
85741
|
-
result.push({ type: "xpEarned", value: metrics.xpEarned });
|
|
85742
|
-
}
|
|
85743
|
-
if (metrics.masteredUnits !== undefined) {
|
|
85744
|
-
result.push({ type: "masteredUnits", value: metrics.masteredUnits });
|
|
85745
|
-
}
|
|
85746
|
-
return result;
|
|
85747
|
-
}
|
|
85748
|
-
function buildTimeSpentMetrics(elapsedMs, pausedMs) {
|
|
85749
|
-
const result = [{ type: "active", value: Math.max(0, elapsedMs) / 1000 }];
|
|
85750
|
-
if (pausedMs > 0) {
|
|
85751
|
-
result.push({ type: "inactive", value: Math.max(0, pausedMs) / 1000 });
|
|
85752
|
-
}
|
|
85753
|
-
return result;
|
|
85754
|
-
}
|
|
85755
|
-
async function sendCaliperEnvelope(client, sensor, activityEvent, timeSpentEvent) {
|
|
85756
|
-
await client.caliper.events.send(sensor, [activityEvent, timeSpentEvent]);
|
|
85757
|
-
}
|
|
85758
|
-
// src/server/lib/build-user-profile.ts
|
|
85759
|
-
function buildCourseLookup(courses, apiEnv) {
|
|
85760
|
-
const courseById = new Map;
|
|
85761
|
-
for (const course of courses) {
|
|
85762
|
-
const courseId = course.ids?.[apiEnv];
|
|
85763
|
-
if (courseId) {
|
|
85764
|
-
courseById.set(courseId, course);
|
|
85765
|
-
}
|
|
85766
|
-
}
|
|
85767
|
-
return courseById;
|
|
85768
|
-
}
|
|
85769
|
-
function mapEnrollmentsToCourses(enrollments, courseById) {
|
|
85770
|
-
return enrollments.map((enrollment) => {
|
|
85771
|
-
const configuredCourse = courseById.get(enrollment.course.id);
|
|
85772
|
-
return {
|
|
85773
|
-
id: enrollment.course.id,
|
|
85774
|
-
code: configuredCourse?.courseCode ?? enrollment.course.id,
|
|
85775
|
-
name: enrollment.course.title
|
|
85776
|
-
};
|
|
85777
|
-
});
|
|
85778
|
-
}
|
|
85779
|
-
function pickGoalsFromEnrollments(enrollments) {
|
|
85780
|
-
return enrollments.map((enrollment) => enrollment.metadata?.goals).find(Boolean);
|
|
85781
|
-
}
|
|
85782
|
-
function getUtcDayRange(date6) {
|
|
85783
|
-
const start = new Date(Date.UTC(date6.getUTCFullYear(), date6.getUTCMonth(), date6.getUTCDate()));
|
|
85784
|
-
const end = new Date(Date.UTC(date6.getUTCFullYear(), date6.getUTCMonth(), date6.getUTCDate(), 23, 59, 59, 999));
|
|
85785
|
-
return { start, end };
|
|
85786
|
-
}
|
|
85787
|
-
function sumXp(facts) {
|
|
85788
|
-
return Object.values(facts).reduce((dateTotal, subjects) => {
|
|
85789
|
-
return dateTotal + Object.values(subjects).reduce((subjectTotal, metrics) => {
|
|
85790
|
-
return subjectTotal + (metrics.activityMetrics?.xpEarned ?? 0);
|
|
85791
|
-
}, 0);
|
|
85792
|
-
}, 0);
|
|
85793
|
-
}
|
|
85794
85687
|
// src/shared/constants.ts
|
|
85795
85688
|
var ROUTES = {
|
|
85796
85689
|
ACTIVITY: "/activity",
|
|
@@ -85800,10 +85693,83 @@ var ROUTES = {
|
|
|
85800
85693
|
CALLBACK: "/identity/callback"
|
|
85801
85694
|
},
|
|
85802
85695
|
USER: {
|
|
85803
|
-
ME: "/user/me"
|
|
85696
|
+
ME: "/user/me",
|
|
85697
|
+
VERIFY: "/user/verify"
|
|
85804
85698
|
}
|
|
85805
85699
|
};
|
|
85806
85700
|
|
|
85701
|
+
// src/server/lib/sso.ts
|
|
85702
|
+
function buildErrorContext(error57, errorCode, state, req) {
|
|
85703
|
+
return {
|
|
85704
|
+
error: error57,
|
|
85705
|
+
errorCode,
|
|
85706
|
+
state,
|
|
85707
|
+
req,
|
|
85708
|
+
redirect: redirectResponse,
|
|
85709
|
+
json: jsonResponse
|
|
85710
|
+
};
|
|
85711
|
+
}
|
|
85712
|
+
function tryDecodeState(stateParam) {
|
|
85713
|
+
try {
|
|
85714
|
+
return decodeBase64Url(stateParam);
|
|
85715
|
+
} catch {
|
|
85716
|
+
ssoLog.warn("Failed to decode state");
|
|
85717
|
+
return;
|
|
85718
|
+
}
|
|
85719
|
+
}
|
|
85720
|
+
function handleIdpError(errorParam, url6, state, req, onCallbackError) {
|
|
85721
|
+
const errorDesc = url6.searchParams.get("error_description");
|
|
85722
|
+
ssoLog.error("IdP returned error", { error: errorParam, description: errorDesc });
|
|
85723
|
+
const error57 = new Error(errorDesc ?? errorParam);
|
|
85724
|
+
if (onCallbackError) {
|
|
85725
|
+
return onCallbackError(buildErrorContext(error57, errorParam, state, req));
|
|
85726
|
+
}
|
|
85727
|
+
return jsonResponse({ error: errorParam }, 400);
|
|
85728
|
+
}
|
|
85729
|
+
function handleMissingCode(state, req, onCallbackError) {
|
|
85730
|
+
ssoLog.error("Missing authorization code in callback");
|
|
85731
|
+
const error57 = new Error("Missing authorization code");
|
|
85732
|
+
if (onCallbackError) {
|
|
85733
|
+
return onCallbackError(buildErrorContext(error57, "missing_code", state, req));
|
|
85734
|
+
}
|
|
85735
|
+
return jsonResponse({ error: "Missing authorization code" }, 400);
|
|
85736
|
+
}
|
|
85737
|
+
async function initiateSignIn(params) {
|
|
85738
|
+
const { req, env, clientId, buildState } = params;
|
|
85739
|
+
const issuer = params.issuer ?? getIssuer(env);
|
|
85740
|
+
const url6 = new URL(req.url);
|
|
85741
|
+
let redirectUri = params.redirectUri;
|
|
85742
|
+
if (!redirectUri) {
|
|
85743
|
+
const basePath = url6.pathname.replace(ROUTES.IDENTITY.SIGNIN, "");
|
|
85744
|
+
redirectUri = `${url6.origin}${basePath}${ROUTES.IDENTITY.CALLBACK}`;
|
|
85745
|
+
}
|
|
85746
|
+
ssoLog.debug("SSO sign-in initiated", { env, issuer, clientId, redirectUri });
|
|
85747
|
+
const stateData = buildState ? buildState({ req, url: url6 }) : {};
|
|
85748
|
+
const state = encodeBase64Url(stateData);
|
|
85749
|
+
const authUrl = await buildAuthorizationUrl({
|
|
85750
|
+
issuer,
|
|
85751
|
+
clientId,
|
|
85752
|
+
redirectUri,
|
|
85753
|
+
state
|
|
85754
|
+
});
|
|
85755
|
+
return redirectResponse(authUrl);
|
|
85756
|
+
}
|
|
85757
|
+
function parseCallback(req) {
|
|
85758
|
+
const url6 = new URL(req.url);
|
|
85759
|
+
const code = url6.searchParams.get("code");
|
|
85760
|
+
const errorParam = url6.searchParams.get("error");
|
|
85761
|
+
const stateParam = url6.searchParams.get("state");
|
|
85762
|
+
ssoLog.debug("Received callback from IdP", { hasCode: !!code, error: errorParam });
|
|
85763
|
+
const state = stateParam ? tryDecodeState(stateParam) : undefined;
|
|
85764
|
+
return { url: url6, code, errorParam, state };
|
|
85765
|
+
}
|
|
85766
|
+
function computeRedirectUri(url6, configuredRedirectUri) {
|
|
85767
|
+
if (configuredRedirectUri) {
|
|
85768
|
+
return configuredRedirectUri;
|
|
85769
|
+
}
|
|
85770
|
+
const basePath = url6.pathname.replace(ROUTES.IDENTITY.CALLBACK, "");
|
|
85771
|
+
return `${url6.origin}${basePath}${ROUTES.IDENTITY.CALLBACK}`;
|
|
85772
|
+
}
|
|
85807
85773
|
// src/server/adapters/utils.ts
|
|
85808
85774
|
function normalizePathname(path) {
|
|
85809
85775
|
const raw = path.trim();
|
|
@@ -85853,6 +85819,8 @@ function matchTimebackRoute(params) {
|
|
|
85853
85819
|
return "identity.signOut";
|
|
85854
85820
|
if (relative === ROUTES.USER.ME)
|
|
85855
85821
|
return "user.me";
|
|
85822
|
+
if (relative === ROUTES.USER.VERIFY)
|
|
85823
|
+
return "user.verify";
|
|
85856
85824
|
}
|
|
85857
85825
|
if (method === "POST") {
|
|
85858
85826
|
if (relative === ROUTES.ACTIVITY)
|
|
@@ -85878,6 +85846,8 @@ function matchTimebackRoute(params) {
|
|
|
85878
85846
|
return "identity.signOut";
|
|
85879
85847
|
if (pathname.endsWith(ROUTES.USER.ME))
|
|
85880
85848
|
return "user.me";
|
|
85849
|
+
if (pathname.endsWith(ROUTES.USER.VERIFY))
|
|
85850
|
+
return "user.verify";
|
|
85881
85851
|
}
|
|
85882
85852
|
if (method === "POST") {
|
|
85883
85853
|
if (pathname.endsWith(ROUTES.ACTIVITY))
|
|
@@ -85918,6 +85888,11 @@ function toTanStackStartHandler(input) {
|
|
|
85918
85888
|
return jsonResponse({ error: "Not found" }, 404);
|
|
85919
85889
|
return handle.user.me(event.request);
|
|
85920
85890
|
}
|
|
85891
|
+
if (route === "user.verify") {
|
|
85892
|
+
if (!hasUserHandler(handle))
|
|
85893
|
+
return jsonResponse({ error: "Not found" }, 404);
|
|
85894
|
+
return handle.user.verify(event.request);
|
|
85895
|
+
}
|
|
85921
85896
|
if (route === "activity") {
|
|
85922
85897
|
if (!hasActivityHandler(handle))
|
|
85923
85898
|
return jsonResponse({ error: "Not found" }, 404);
|
|
@@ -12,7 +12,7 @@ type AnyHandlers = Handlers | IdentityOnlyHandlers;
|
|
|
12
12
|
/**
|
|
13
13
|
* A canonical route identifier for Timeback's built-in endpoints.
|
|
14
14
|
*/
|
|
15
|
-
type TimebackRouteId = 'identity.signIn' | 'identity.callback' | 'identity.signOut' | 'user.me' | 'activity';
|
|
15
|
+
type TimebackRouteId = 'identity.signIn' | 'identity.callback' | 'identity.signOut' | 'user.me' | 'user.verify' | 'activity';
|
|
16
16
|
/**
|
|
17
17
|
* Normalize a path-like string to a pathname only (no query string).
|
|
18
18
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C;;GAEG;AACH,KAAK,WAAW,GAAG,QAAQ,GAAG,oBAAoB,CAAA;AAElD;;GAEG;AACH,KAAK,eAAe,GACjB,iBAAiB,GACjB,mBAAmB,GACnB,kBAAkB,GAClB,SAAS,GACT,UAAU,CAAA;AAEb;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqBtD;AA8BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,eAAe,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/server/adapters/utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAC9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C;;GAEG;AACH,KAAK,WAAW,GAAG,QAAQ,GAAG,oBAAoB,CAAA;AAElD;;GAEG;AACH,KAAK,eAAe,GACjB,iBAAiB,GACjB,mBAAmB,GACnB,kBAAkB,GAClB,SAAS,GACT,aAAa,GACb,UAAU,CAAA;AAEb;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqBtD;AA8BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,eAAe,GAAG,IAAI,CAwDzB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,WAAW,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,GAAG,QAAQ,IAAI,QAAQ,CAE9E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,GAAG,QAAQ,IAAI,QAAQ,CAE1E"}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { ActivityCompletedEvent, TimebackActivityContext, TimebackActivityMetric, TimeSpentEvent, TimeSpentMetric } from '@timeback/caliper';
|
|
2
2
|
import type { TimebackClient } from '@timeback/core';
|
|
3
|
-
import type {
|
|
4
|
-
import type { AppConfig } from '
|
|
3
|
+
import type { ActivityCourseRef, ActivityMetrics } from '../../../shared/types';
|
|
4
|
+
import type { ActivityBeforeSendData, AppConfig } from '../../types';
|
|
5
|
+
import type { ValidatedActivityPayload } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* Activity Caliper Events
|
|
8
|
+
*
|
|
9
|
+
* Build and send Caliper events for activity submissions.
|
|
10
|
+
*/
|
|
5
11
|
/**
|
|
6
12
|
* Error thrown when a course is missing a synced ID for the target environment.
|
|
7
13
|
*/
|
|
@@ -39,7 +45,11 @@ export declare class InvalidSensorUrlError extends Error {
|
|
|
39
45
|
* @param sensor - Sensor URL for building the canonical activity URL
|
|
40
46
|
* @returns Caliper activity context payload
|
|
41
47
|
*/
|
|
42
|
-
export declare function buildActivityContext(payload:
|
|
48
|
+
export declare function buildActivityContext(payload: {
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
course: ActivityCourseRef;
|
|
52
|
+
}, course: AppConfig['courses'][number], appName: string, apiEnv: 'staging' | 'production', sensor: string): TimebackActivityContext;
|
|
43
53
|
/**
|
|
44
54
|
* Build Caliper activity metrics from the client payload.
|
|
45
55
|
*
|
|
@@ -55,6 +65,21 @@ export declare function buildActivityMetrics(metrics: ActivityMetrics): Timeback
|
|
|
55
65
|
* @returns Normalized Caliper time spent metrics
|
|
56
66
|
*/
|
|
57
67
|
export declare function buildTimeSpentMetrics(elapsedMs: number, pausedMs: number): TimeSpentMetric[];
|
|
68
|
+
/**
|
|
69
|
+
* Build Caliper events for an activity.
|
|
70
|
+
*
|
|
71
|
+
* @param params - Event building parameters
|
|
72
|
+
* @returns Built events and context
|
|
73
|
+
*/
|
|
74
|
+
export declare function buildActivityEvents(params: {
|
|
75
|
+
sensor: string;
|
|
76
|
+
timebackId: string;
|
|
77
|
+
email: string;
|
|
78
|
+
payload: ValidatedActivityPayload;
|
|
79
|
+
course: AppConfig['courses'][number];
|
|
80
|
+
appName: string;
|
|
81
|
+
apiEnv: 'staging' | 'production';
|
|
82
|
+
}): ActivityBeforeSendData;
|
|
58
83
|
/**
|
|
59
84
|
* Send a batch of Caliper events as a single envelope.
|
|
60
85
|
*
|
|
@@ -64,4 +89,4 @@ export declare function buildTimeSpentMetrics(elapsedMs: number, pausedMs: numbe
|
|
|
64
89
|
* @param timeSpentEvent - TimeSpentEvent payload
|
|
65
90
|
*/
|
|
66
91
|
export declare function sendCaliperEnvelope(client: TimebackClient, sensor: string, activityEvent: ActivityCompletedEvent, timeSpentEvent: TimeSpentEvent): Promise<void>;
|
|
67
|
-
//# sourceMappingURL=
|
|
92
|
+
//# sourceMappingURL=caliper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caliper.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/activity/caliper.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACX,sBAAsB,EACtB,uBAAuB,EACvB,sBAAsB,EAEtB,cAAc,EACd,eAAe,EACf,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC/E,OAAO,KAAK,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACpE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAEvD;;;;GAIG;AAMH;;GAEG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;IACpD,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;IAC7C,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,YAAY,CAAA;IAEtC,YAAY,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,SAAS,GAAG,YAAY,EAW9E;CACD;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IAEvB,YAAY,MAAM,EAAE,MAAM,EAMzB;CACD;AAiHD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,EAChE,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EACpC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,GAAG,YAAY,EAChC,MAAM,EAAE,MAAM,GACZ,uBAAuB,CAmBzB;AAMD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,eAAe,GAAG,sBAAsB,EAAE,CAoBvF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,eAAe,EAAE,CAQ5F;AAMD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC3C,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,wBAAwB,CAAA;IACjC,MAAM,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,SAAS,GAAG,YAAY,CAAA;CAChC,GAAG,sBAAsB,CA4CzB;AAMD;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACxC,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,sBAAsB,EACrC,cAAc,EAAE,cAAc,GAC5B,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Gradebook (OneRoster)
|
|
3
|
+
*
|
|
4
|
+
* Write assessment line items and results for activity submissions.
|
|
5
|
+
*
|
|
6
|
+
* Attempt semantics:
|
|
7
|
+
* - `attempt` is the primary identity (1-based) stored in `metadata.attempt`
|
|
8
|
+
* - Results are written via PUT upsert using a deterministic `sourcedId`
|
|
9
|
+
* - Retry detection: if `payload.endedAt` matches an existing result's `scoreDate` (and `metadata.endedAt`),
|
|
10
|
+
* the same attempt number is reused to make retries idempotent
|
|
11
|
+
* - New attempt: if no matching `endedAt` is found, attempt is `max(existingAttempt) + 1`
|
|
12
|
+
*
|
|
13
|
+
* Concurrency note:
|
|
14
|
+
* - Two concurrent "new attempts" for the same (lineItem, student) can race.
|
|
15
|
+
* - We avoid silent overwrites by including `endedAt` in the result `sourcedId` so two distinct submissions
|
|
16
|
+
* never share an identifier even if they compute the same attempt number.
|
|
17
|
+
*/
|
|
18
|
+
import type { TimebackClient } from '@timeback/core';
|
|
19
|
+
import type { ValidatedActivityPayload } from './types';
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the attempt number for a gradebook result.
|
|
22
|
+
*
|
|
23
|
+
* - If an existing result has `scoreDate === endedAt` or `metadata.endedAt === endedAt`,
|
|
24
|
+
* return its `metadata.attempt` (retry of the same attempt).
|
|
25
|
+
* - Otherwise, return `max(metadata.attempt) + 1` (new attempt).
|
|
26
|
+
*
|
|
27
|
+
* @param client - Timeback client
|
|
28
|
+
* @param lineItemId - The assessment line item sourcedId
|
|
29
|
+
* @param timebackId - The student's Timeback user sourcedId
|
|
30
|
+
* @param endedAt - The activity end timestamp from the client payload
|
|
31
|
+
* @returns The attempt number to use (1-based)
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveAttemptNumber(client: TimebackClient, lineItemId: string, timebackId: string, endedAt: string): Promise<number>;
|
|
34
|
+
/**
|
|
35
|
+
* Write gradebook entry (assessment line item + result) for an activity.
|
|
36
|
+
*
|
|
37
|
+
* This is best-effort: if the write fails, we log and continue.
|
|
38
|
+
* Gradebook writes are only performed when both totalQuestions and correctQuestions are present.
|
|
39
|
+
*
|
|
40
|
+
* Result identity is based on attempt number:
|
|
41
|
+
* - `resultId = lineItemId:studentId:attempt-N`
|
|
42
|
+
* - Retries of the same attempt (same `endedAt`) reuse the same result ID
|
|
43
|
+
* - New attempts get an incremented attempt number
|
|
44
|
+
*
|
|
45
|
+
* @param params - Gradebook write parameters
|
|
46
|
+
*/
|
|
47
|
+
export declare function writeGradebookEntry(params: {
|
|
48
|
+
client: TimebackClient;
|
|
49
|
+
courseId: string;
|
|
50
|
+
activityId: string;
|
|
51
|
+
activityName: string;
|
|
52
|
+
timebackId: string;
|
|
53
|
+
payload: ValidatedActivityPayload;
|
|
54
|
+
appName: string;
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
//# sourceMappingURL=gradebook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gradebook.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/activity/gradebook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAGpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAkEvD;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CACzC,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAuCjB;AAyMD;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE,cAAc,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,wBAAwB,CAAA;IACjC,OAAO,EAAE,MAAM,CAAA;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDhB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Handler
|
|
3
|
+
*
|
|
4
|
+
* HTTP route handler for activity submissions.
|
|
5
|
+
* Orchestrates validation, user resolution, gradebook writes, and Caliper event sending.
|
|
6
|
+
*/
|
|
7
|
+
import type { ActivityHandler, ActivityHandlerConfig } from './types';
|
|
8
|
+
/**
|
|
9
|
+
* Create the activity POST handler.
|
|
10
|
+
*
|
|
11
|
+
* @param config - Handler configuration
|
|
12
|
+
* @returns The activity request handler
|
|
13
|
+
*/
|
|
14
|
+
export declare function createActivityHandler(config: ActivityHandlerConfig): ActivityHandler;
|
|
15
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/activity/handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAoB,MAAM,SAAS,CAAA;AAmFvF;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAuGpF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Handler
|
|
3
|
+
*
|
|
4
|
+
* Route handler for activity tracking and submission.
|
|
5
|
+
*/
|
|
6
|
+
export { createActivityHandler } from './handler';
|
|
7
|
+
export { buildActivityContext, buildActivityMetrics, buildTimeSpentMetrics, InvalidSensorUrlError, MissingSyncedCourseIdError, sendCaliperEnvelope, } from './caliper';
|
|
8
|
+
export type { ActivityHandler, ActivityHandlerConfig, ActivityUserInfo, ValidatedActivityPayload, ValidationResult, } from './types';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/activity/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAEjD,OAAO,EACN,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,0BAA0B,EAC1B,mBAAmB,GACnB,MAAM,WAAW,CAAA;AAElB,YAAY,EACX,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,wBAAwB,EACxB,gBAAgB,GAChB,MAAM,SAAS,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity Course Resolution
|
|
3
|
+
*
|
|
4
|
+
* Resolves course selectors from activity payloads to configured courses.
|
|
5
|
+
*/
|
|
6
|
+
import type { ActivityCourseRef } from '../../../shared/types';
|
|
7
|
+
import type { AppConfig } from '../../types';
|
|
8
|
+
/**
|
|
9
|
+
* Error thrown when a course selector cannot be resolved against config.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ActivityCourseResolutionError extends Error {
|
|
12
|
+
readonly code: 'unknown_course' | 'ambiguous_course';
|
|
13
|
+
readonly selector: ActivityCourseRef;
|
|
14
|
+
readonly count?: number;
|
|
15
|
+
constructor(code: ActivityCourseResolutionError['code'], selector: ActivityCourseRef, count?: number);
|
|
16
|
+
/**
|
|
17
|
+
* Get a human-readable description of the selector.
|
|
18
|
+
*
|
|
19
|
+
* @returns Human-readable selector description
|
|
20
|
+
*/
|
|
21
|
+
get selectorDescription(): string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a course config entry from an activity course selector.
|
|
25
|
+
*
|
|
26
|
+
* **Use case:** Activity handler — match the client's course selector
|
|
27
|
+
* to a configured course in `timeback.config.json`.
|
|
28
|
+
*
|
|
29
|
+
* Supports two selector modes:
|
|
30
|
+
* - **subjectGrade**: Match by `(subject, grade)`
|
|
31
|
+
* - **courseCode**: Match by `courseCode`
|
|
32
|
+
*
|
|
33
|
+
* @param courses - Configured courses from `timeback.config.json`
|
|
34
|
+
* @param courseRef - Course selector from the client payload
|
|
35
|
+
* @returns Matched course config entry
|
|
36
|
+
* @throws {ActivityCourseResolutionError} When selector is unknown or ambiguous
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveActivityCourse(courses: AppConfig['courses'], courseRef: ActivityCourseRef): AppConfig['courses'][number];
|
|
39
|
+
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/activity/resolve.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C;;GAEG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;IACvD,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,kBAAkB,CAAA;IACpD,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IAEvB,YACC,IAAI,EAAE,6BAA6B,CAAC,MAAM,CAAC,EAC3C,QAAQ,EAAE,iBAAiB,EAC3B,KAAK,CAAC,EAAE,MAAM,EAMd;IAED;;;;OAIG;IACH,IAAI,mBAAmB,IAAI,MAAM,CAKhC;CACD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,EAC7B,SAAS,EAAE,iBAAiB,GAC1B,SAAS,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAoB9B"}
|