timeback 0.0.0-alpha.2 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +378 -7
- package/dist/client/adapters/react/SignInButton.d.ts +60 -0
- package/dist/client/adapters/react/SignInButton.d.ts.map +1 -0
- package/dist/client/adapters/react/index.d.ts +43 -0
- package/dist/client/adapters/react/index.d.ts.map +1 -0
- package/dist/client/adapters/react/index.js +478 -0
- package/dist/client/adapters/react/provider.d.ts +74 -0
- package/dist/client/adapters/react/provider.d.ts.map +1 -0
- package/dist/client/adapters/solid/SignInButton.d.ts +52 -0
- package/dist/client/adapters/solid/SignInButton.d.ts.map +1 -0
- package/dist/client/adapters/solid/SignInButton.tsx +321 -0
- package/dist/client/adapters/solid/context.d.ts +73 -0
- package/dist/client/adapters/solid/context.d.ts.map +1 -0
- package/dist/client/adapters/solid/context.tsx +91 -0
- package/dist/client/adapters/solid/index.d.ts +42 -0
- package/dist/client/adapters/solid/index.d.ts.map +1 -0
- package/dist/client/adapters/solid/index.ts +46 -0
- package/dist/client/adapters/svelte/SignInButton.svelte +234 -0
- package/dist/client/adapters/svelte/SignInButton.svelte.d.ts +24 -0
- package/dist/client/adapters/svelte/index.d.ts +33 -0
- package/dist/client/adapters/svelte/index.d.ts.map +1 -0
- package/dist/client/adapters/svelte/index.ts +38 -0
- package/dist/client/adapters/svelte/stores.d.ts +62 -0
- package/dist/client/adapters/svelte/stores.d.ts.map +1 -0
- package/dist/client/adapters/svelte/stores.ts +139 -0
- package/dist/client/adapters/vue/SignInButton.vue +260 -0
- package/dist/client/adapters/vue/SignInButton.vue.d.ts +53 -0
- package/dist/client/adapters/vue/index.d.ts +43 -0
- package/dist/client/adapters/vue/index.d.ts.map +1 -0
- package/dist/client/adapters/vue/index.ts +48 -0
- package/dist/client/adapters/vue/provider.d.ts +94 -0
- package/dist/client/adapters/vue/provider.d.ts.map +1 -0
- package/dist/client/adapters/vue/provider.ts +147 -0
- package/dist/client/index.d.ts +9 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/lib/activity/activity.class.d.ts +73 -0
- package/dist/client/lib/activity/activity.class.d.ts.map +1 -0
- package/dist/client/lib/activity/activity.d.ts +16 -0
- package/dist/client/lib/activity/activity.d.ts.map +1 -0
- package/dist/client/lib/activity/index.d.ts +6 -0
- package/dist/client/lib/activity/index.d.ts.map +1 -0
- package/dist/client/lib/utils.d.ts +20 -0
- package/dist/client/lib/utils.d.ts.map +1 -0
- package/dist/client/namespaces/activity.d.ts +37 -0
- package/dist/client/namespaces/activity.d.ts.map +1 -0
- package/dist/client/namespaces/auth.d.ts +33 -0
- package/dist/client/namespaces/auth.d.ts.map +1 -0
- package/dist/client/namespaces/index.d.ts +7 -0
- package/dist/client/namespaces/index.d.ts.map +1 -0
- package/dist/client/namespaces/user.d.ts +29 -0
- package/dist/client/namespaces/user.d.ts.map +1 -0
- package/dist/client/timeback-client.class.d.ts +37 -0
- package/dist/client/timeback-client.class.d.ts.map +1 -0
- package/dist/client/timeback-client.d.ts +29 -0
- package/dist/client/timeback-client.d.ts.map +1 -0
- package/dist/client.d.ts +30 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +198 -0
- package/dist/index.d.ts +27 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1373 -11
- package/dist/server/adapters/express.d.ts +62 -0
- package/dist/server/adapters/express.d.ts.map +1 -0
- package/dist/server/adapters/express.js +565 -0
- package/dist/server/adapters/native.d.ts +45 -0
- package/dist/server/adapters/native.d.ts.map +1 -0
- package/dist/server/adapters/native.js +509 -0
- package/dist/server/adapters/nextjs.d.ts +30 -0
- package/dist/server/adapters/nextjs.d.ts.map +1 -0
- package/dist/server/adapters/nextjs.js +521 -0
- package/dist/server/adapters/nuxt.d.ts +96 -0
- package/dist/server/adapters/nuxt.d.ts.map +1 -0
- package/dist/server/adapters/nuxt.js +663 -0
- package/dist/server/adapters/solid-start.d.ts +61 -0
- package/dist/server/adapters/solid-start.d.ts.map +1 -0
- package/dist/server/adapters/solid-start.js +551 -0
- package/dist/server/adapters/svelte-kit.d.ts +82 -0
- package/dist/server/adapters/svelte-kit.d.ts.map +1 -0
- package/dist/server/adapters/svelte-kit.js +572 -0
- package/dist/server/adapters/tanstack-start.d.ts +40 -0
- package/dist/server/adapters/tanstack-start.d.ts.map +1 -0
- package/dist/server/adapters/tanstack-start.js +522 -0
- package/dist/server/adapters/types.d.ts +280 -0
- package/dist/server/adapters/types.d.ts.map +1 -0
- package/dist/server/adapters/utils.d.ts +15 -0
- package/dist/server/adapters/utils.d.ts.map +1 -0
- package/dist/server/handlers/activity.d.ts +28 -0
- package/dist/server/handlers/activity.d.ts.map +1 -0
- package/dist/server/handlers/identity.d.ts +24 -0
- package/dist/server/handlers/identity.d.ts.map +1 -0
- package/dist/server/handlers/index.d.ts +9 -0
- package/dist/server/handlers/index.d.ts.map +1 -0
- package/dist/server/handlers/user.d.ts +30 -0
- package/dist/server/handlers/user.d.ts.map +1 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/lib/index.d.ts +9 -0
- package/dist/server/lib/index.d.ts.map +1 -0
- package/dist/server/lib/logger.d.ts +21 -0
- package/dist/server/lib/logger.d.ts.map +1 -0
- package/dist/server/lib/oidc.d.ts +76 -0
- package/dist/server/lib/oidc.d.ts.map +1 -0
- package/dist/server/lib/utils.d.ts +39 -0
- package/dist/server/lib/utils.d.ts.map +1 -0
- package/dist/server/timeback.d.ts +48 -0
- package/dist/server/timeback.d.ts.map +1 -0
- package/dist/server/types.d.ts +300 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/shared/constants.d.ts +18 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/types.d.ts +100 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +104 -28
package/dist/index.js
CHANGED
|
@@ -1,17 +1,1379 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// ../../node_modules/.bun/colorette@2.0.20/node_modules/colorette/index.js
|
|
21
|
+
import * as tty from "tty";
|
|
22
|
+
var {
|
|
23
|
+
env = {},
|
|
24
|
+
argv = [],
|
|
25
|
+
platform = ""
|
|
26
|
+
} = typeof process === "undefined" ? {} : process;
|
|
27
|
+
var isDisabled = "NO_COLOR" in env || argv.includes("--no-color");
|
|
28
|
+
var isForced = "FORCE_COLOR" in env || argv.includes("--color");
|
|
29
|
+
var isWindows = platform === "win32";
|
|
30
|
+
var isDumbTerminal = env.TERM === "dumb";
|
|
31
|
+
var isCompatibleTerminal = tty && tty.isatty && tty.isatty(1) && env.TERM && !isDumbTerminal;
|
|
32
|
+
var isCI = "CI" in env && (("GITHUB_ACTIONS" in env) || ("GITLAB_CI" in env) || ("CIRCLECI" in env));
|
|
33
|
+
var isColorSupported = !isDisabled && (isForced || isWindows && !isDumbTerminal || isCompatibleTerminal || isCI);
|
|
34
|
+
var replaceClose = (index, string, close, replace, head = string.substring(0, index) + replace, tail = string.substring(index + close.length), next = tail.indexOf(close)) => head + (next < 0 ? tail : replaceClose(next, tail, close, replace));
|
|
35
|
+
var clearBleed = (index, string, open, close, replace) => index < 0 ? open + string + close : open + replaceClose(index, string, close, replace) + close;
|
|
36
|
+
var filterEmpty = (open, close, replace = open, at = open.length + 1) => (string) => string || !(string === "" || string === undefined) ? clearBleed(("" + string).indexOf(close, at), string, open, close, replace) : "";
|
|
37
|
+
var init = (open, close, replace) => filterEmpty(`\x1B[${open}m`, `\x1B[${close}m`, replace);
|
|
38
|
+
var colors = {
|
|
39
|
+
reset: init(0, 0),
|
|
40
|
+
bold: init(1, 22, "\x1B[22m\x1B[1m"),
|
|
41
|
+
dim: init(2, 22, "\x1B[22m\x1B[2m"),
|
|
42
|
+
italic: init(3, 23),
|
|
43
|
+
underline: init(4, 24),
|
|
44
|
+
inverse: init(7, 27),
|
|
45
|
+
hidden: init(8, 28),
|
|
46
|
+
strikethrough: init(9, 29),
|
|
47
|
+
black: init(30, 39),
|
|
48
|
+
red: init(31, 39),
|
|
49
|
+
green: init(32, 39),
|
|
50
|
+
yellow: init(33, 39),
|
|
51
|
+
blue: init(34, 39),
|
|
52
|
+
magenta: init(35, 39),
|
|
53
|
+
cyan: init(36, 39),
|
|
54
|
+
white: init(37, 39),
|
|
55
|
+
gray: init(90, 39),
|
|
56
|
+
bgBlack: init(40, 49),
|
|
57
|
+
bgRed: init(41, 49),
|
|
58
|
+
bgGreen: init(42, 49),
|
|
59
|
+
bgYellow: init(43, 49),
|
|
60
|
+
bgBlue: init(44, 49),
|
|
61
|
+
bgMagenta: init(45, 49),
|
|
62
|
+
bgCyan: init(46, 49),
|
|
63
|
+
bgWhite: init(47, 49),
|
|
64
|
+
blackBright: init(90, 39),
|
|
65
|
+
redBright: init(91, 39),
|
|
66
|
+
greenBright: init(92, 39),
|
|
67
|
+
yellowBright: init(93, 39),
|
|
68
|
+
blueBright: init(94, 39),
|
|
69
|
+
magentaBright: init(95, 39),
|
|
70
|
+
cyanBright: init(96, 39),
|
|
71
|
+
whiteBright: init(97, 39),
|
|
72
|
+
bgBlackBright: init(100, 49),
|
|
73
|
+
bgRedBright: init(101, 49),
|
|
74
|
+
bgGreenBright: init(102, 49),
|
|
75
|
+
bgYellowBright: init(103, 49),
|
|
76
|
+
bgBlueBright: init(104, 49),
|
|
77
|
+
bgMagentaBright: init(105, 49),
|
|
78
|
+
bgCyanBright: init(106, 49),
|
|
79
|
+
bgWhiteBright: init(107, 49)
|
|
80
|
+
};
|
|
81
|
+
var createColors = ({ useColor = isColorSupported } = {}) => useColor ? colors : Object.keys(colors).reduce((colors2, key) => ({ ...colors2, [key]: String }), {});
|
|
82
|
+
var {
|
|
83
|
+
reset,
|
|
84
|
+
bold,
|
|
85
|
+
dim,
|
|
86
|
+
italic,
|
|
87
|
+
underline,
|
|
88
|
+
inverse,
|
|
89
|
+
hidden,
|
|
90
|
+
strikethrough,
|
|
91
|
+
black,
|
|
92
|
+
red,
|
|
93
|
+
green,
|
|
94
|
+
yellow,
|
|
95
|
+
blue,
|
|
96
|
+
magenta,
|
|
97
|
+
cyan,
|
|
98
|
+
white,
|
|
99
|
+
gray,
|
|
100
|
+
bgBlack,
|
|
101
|
+
bgRed,
|
|
102
|
+
bgGreen,
|
|
103
|
+
bgYellow,
|
|
104
|
+
bgBlue,
|
|
105
|
+
bgMagenta,
|
|
106
|
+
bgCyan,
|
|
107
|
+
bgWhite,
|
|
108
|
+
blackBright,
|
|
109
|
+
redBright,
|
|
110
|
+
greenBright,
|
|
111
|
+
yellowBright,
|
|
112
|
+
blueBright,
|
|
113
|
+
magentaBright,
|
|
114
|
+
cyanBright,
|
|
115
|
+
whiteBright,
|
|
116
|
+
bgBlackBright,
|
|
117
|
+
bgRedBright,
|
|
118
|
+
bgGreenBright,
|
|
119
|
+
bgYellowBright,
|
|
120
|
+
bgBlueBright,
|
|
121
|
+
bgMagentaBright,
|
|
122
|
+
bgCyanBright,
|
|
123
|
+
bgWhiteBright
|
|
124
|
+
} = createColors();
|
|
125
|
+
|
|
126
|
+
// ../internal/cli-infra/src/config/timeback.ts
|
|
127
|
+
import { existsSync } from "node:fs";
|
|
128
|
+
import { basename, relative, resolve } from "node:path";
|
|
129
|
+
import { createJiti } from "jiti";
|
|
130
|
+
|
|
131
|
+
// ../types/src/zod/primitives.ts
|
|
132
|
+
import { z } from "zod/v4";
|
|
133
|
+
var TimebackSubject = z.enum([
|
|
134
|
+
"Reading",
|
|
135
|
+
"Language",
|
|
136
|
+
"Vocabulary",
|
|
137
|
+
"Social Studies",
|
|
138
|
+
"Writing",
|
|
139
|
+
"Science",
|
|
140
|
+
"FastMath",
|
|
141
|
+
"Math",
|
|
142
|
+
"None"
|
|
143
|
+
]);
|
|
144
|
+
var TimebackGrade = z.union([
|
|
145
|
+
z.literal(-1),
|
|
146
|
+
z.literal(0),
|
|
147
|
+
z.literal(1),
|
|
148
|
+
z.literal(2),
|
|
149
|
+
z.literal(3),
|
|
150
|
+
z.literal(4),
|
|
151
|
+
z.literal(5),
|
|
152
|
+
z.literal(6),
|
|
153
|
+
z.literal(7),
|
|
154
|
+
z.literal(8),
|
|
155
|
+
z.literal(9),
|
|
156
|
+
z.literal(10),
|
|
157
|
+
z.literal(11),
|
|
158
|
+
z.literal(12),
|
|
159
|
+
z.literal(13)
|
|
160
|
+
]);
|
|
161
|
+
var ScoreStatus = z.enum([
|
|
162
|
+
"exempt",
|
|
163
|
+
"fully graded",
|
|
164
|
+
"not submitted",
|
|
165
|
+
"partially graded",
|
|
166
|
+
"submitted"
|
|
167
|
+
]);
|
|
168
|
+
var OrganizationType = z.enum([
|
|
169
|
+
"department",
|
|
170
|
+
"school",
|
|
171
|
+
"district",
|
|
172
|
+
"local",
|
|
173
|
+
"state",
|
|
174
|
+
"national"
|
|
175
|
+
]);
|
|
176
|
+
var OneRosterUserRole = z.enum([
|
|
177
|
+
"administrator",
|
|
178
|
+
"aide",
|
|
179
|
+
"guardian",
|
|
180
|
+
"parent",
|
|
181
|
+
"proctor",
|
|
182
|
+
"relative",
|
|
183
|
+
"student",
|
|
184
|
+
"teacher"
|
|
185
|
+
]);
|
|
186
|
+
var EnrollmentRole = z.enum(["administrator", "proctor", "student", "teacher"]);
|
|
187
|
+
var LessonType = z.enum(["powerpath-100", "quiz", "test-out", "placement", "unit-test", "alpha-read-article"]).nullable();
|
|
188
|
+
var IMSErrorResponse = z.object({
|
|
189
|
+
imsx_codeMajor: z.enum(["failure", "success"]),
|
|
190
|
+
imsx_severity: z.enum(["error", "warning", "status"]),
|
|
191
|
+
imsx_description: z.string(),
|
|
192
|
+
imsx_CodeMinor: z.object({
|
|
193
|
+
imsx_codeMinorField: z.array(z.object({
|
|
194
|
+
imsx_codeMinorFieldName: z.string(),
|
|
195
|
+
imsx_codeMinorFieldValue: z.string()
|
|
196
|
+
}))
|
|
197
|
+
}).optional()
|
|
198
|
+
});
|
|
199
|
+
// ../types/src/zod/caliper.ts
|
|
200
|
+
import { z as z2 } from "zod/v4";
|
|
201
|
+
var TimebackUser = z2.object({
|
|
202
|
+
id: z2.string(),
|
|
203
|
+
type: z2.literal("TimebackUser"),
|
|
204
|
+
email: z2.string()
|
|
205
|
+
});
|
|
206
|
+
var TimebackActivityContext = z2.object({
|
|
207
|
+
id: z2.string(),
|
|
208
|
+
type: z2.literal("TimebackActivityContext"),
|
|
209
|
+
subject: TimebackSubject,
|
|
210
|
+
app: z2.object({ name: z2.string() }).optional(),
|
|
211
|
+
activity: z2.object({ id: z2.string().optional(), name: z2.string().optional() }).optional(),
|
|
212
|
+
course: z2.object({ id: z2.string(), name: z2.string() }),
|
|
213
|
+
process: z2.boolean().optional()
|
|
214
|
+
});
|
|
215
|
+
var CaliperEventBase = z2.object({
|
|
216
|
+
"@context": z2.literal("http://purl.imsglobal.org/ctx/caliper/v1p2"),
|
|
217
|
+
id: z2.string(),
|
|
218
|
+
type: z2.string(),
|
|
219
|
+
eventTime: z2.string(),
|
|
220
|
+
profile: z2.literal("TimebackProfile"),
|
|
221
|
+
actor: TimebackUser,
|
|
222
|
+
action: z2.string(),
|
|
223
|
+
object: TimebackActivityContext,
|
|
224
|
+
edApp: z2.object({ id: z2.string(), name: z2.string().optional() }).optional()
|
|
225
|
+
});
|
|
226
|
+
var TimebackActivityMetric = z2.object({
|
|
227
|
+
type: z2.enum(["totalQuestions", "correctQuestions", "xpEarned", "masteredUnits"]),
|
|
228
|
+
value: z2.number()
|
|
229
|
+
});
|
|
230
|
+
var TimebackActivityMetricsCollection = z2.object({
|
|
231
|
+
id: z2.string(),
|
|
232
|
+
type: z2.literal("TimebackActivityMetricsCollection"),
|
|
233
|
+
attempt: z2.number().optional(),
|
|
234
|
+
items: z2.array(TimebackActivityMetric),
|
|
235
|
+
extensions: z2.record(z2.string(), z2.unknown()).optional()
|
|
236
|
+
});
|
|
237
|
+
var TimebackActivityEvent = CaliperEventBase.extend({
|
|
238
|
+
type: z2.literal("ActivityEvent"),
|
|
239
|
+
action: z2.literal("Completed"),
|
|
240
|
+
generated: TimebackActivityMetricsCollection
|
|
241
|
+
});
|
|
242
|
+
var TimebackTimeMetric = z2.object({
|
|
243
|
+
type: z2.enum(["active", "inactive", "waste", "unknown", "anti-pattern"]),
|
|
244
|
+
value: z2.number(),
|
|
245
|
+
subType: z2.string().optional()
|
|
246
|
+
});
|
|
247
|
+
var TimebackTimeSpentMetricsCollection = z2.object({
|
|
248
|
+
id: z2.string(),
|
|
249
|
+
type: z2.literal("TimebackTimeSpentMetricsCollection"),
|
|
250
|
+
items: z2.array(TimebackTimeMetric)
|
|
251
|
+
});
|
|
252
|
+
var TimebackTimeSpentEvent = CaliperEventBase.extend({
|
|
253
|
+
type: z2.literal("TimeSpentEvent"),
|
|
254
|
+
action: z2.literal("SpentTime"),
|
|
255
|
+
generated: TimebackTimeSpentMetricsCollection
|
|
256
|
+
});
|
|
257
|
+
var TimebackEvent = z2.union([TimebackActivityEvent, TimebackTimeSpentEvent]);
|
|
258
|
+
var CaliperEnvelope = z2.object({
|
|
259
|
+
sensor: z2.string(),
|
|
260
|
+
sendTime: z2.string(),
|
|
261
|
+
dataVersion: z2.literal("http://purl.imsglobal.org/ctx/caliper/v1p2"),
|
|
262
|
+
data: z2.array(TimebackEvent)
|
|
263
|
+
});
|
|
264
|
+
// ../types/src/zod/config.ts
|
|
265
|
+
import { z as z3 } from "zod/v4";
|
|
266
|
+
var CourseIds = z3.object({
|
|
267
|
+
staging: z3.string().optional(),
|
|
268
|
+
production: z3.string().optional()
|
|
269
|
+
});
|
|
270
|
+
var CourseType = z3.enum(["base", "hole-filling", "optional"]);
|
|
271
|
+
var PublishStatus = z3.enum(["draft", "testing", "published", "deactivated"]);
|
|
272
|
+
var CourseGoals = z3.object({
|
|
273
|
+
dailyXp: z3.number().int().positive().optional(),
|
|
274
|
+
dailyLessons: z3.number().int().positive().optional(),
|
|
275
|
+
dailyActiveMinutes: z3.number().int().positive().optional(),
|
|
276
|
+
dailyAccuracy: z3.number().int().min(0).max(100).optional(),
|
|
277
|
+
dailyMasteredUnits: z3.number().int().positive().optional()
|
|
278
|
+
});
|
|
279
|
+
var CourseMetrics = z3.object({
|
|
280
|
+
totalXp: z3.number().int().positive().optional(),
|
|
281
|
+
totalLessons: z3.number().int().positive().optional(),
|
|
282
|
+
totalGrades: z3.number().int().positive().optional()
|
|
283
|
+
});
|
|
284
|
+
var CourseMetadata = z3.object({
|
|
285
|
+
courseType: CourseType.optional(),
|
|
286
|
+
isSupplemental: z3.boolean().optional(),
|
|
287
|
+
isCustom: z3.boolean().optional(),
|
|
288
|
+
publishStatus: PublishStatus.optional(),
|
|
289
|
+
contactEmail: z3.email().optional(),
|
|
290
|
+
primaryApp: z3.string().optional(),
|
|
291
|
+
goals: CourseGoals.optional(),
|
|
292
|
+
metrics: CourseMetrics.optional()
|
|
293
|
+
});
|
|
294
|
+
var CourseDefaults = z3.object({
|
|
295
|
+
courseCode: z3.string().optional(),
|
|
296
|
+
level: z3.string().optional(),
|
|
297
|
+
metadata: CourseMetadata.optional()
|
|
298
|
+
});
|
|
299
|
+
var CourseConfig = CourseDefaults.extend({
|
|
300
|
+
subject: TimebackSubject,
|
|
301
|
+
grade: TimebackGrade,
|
|
302
|
+
ids: CourseIds.nullable().optional()
|
|
303
|
+
});
|
|
304
|
+
var TimebackConfig = z3.object({
|
|
305
|
+
name: z3.string().min(1, "App name is required"),
|
|
306
|
+
defaults: CourseDefaults.optional(),
|
|
307
|
+
courses: z3.array(CourseConfig).min(1, "At least one course is required"),
|
|
308
|
+
sensors: z3.array(z3.string().url()).optional()
|
|
309
|
+
});
|
|
310
|
+
// ../types/src/zod/edubridge.ts
|
|
311
|
+
import { z as z4 } from "zod/v4";
|
|
312
|
+
var EduBridgeEnrollment = z4.object({
|
|
313
|
+
id: z4.string(),
|
|
314
|
+
role: z4.string(),
|
|
315
|
+
beginDate: z4.string().nullable(),
|
|
316
|
+
endDate: z4.string().nullable(),
|
|
317
|
+
metadata: z4.object({
|
|
318
|
+
goals: z4.object({ dailyXp: z4.number().optional() }).optional(),
|
|
319
|
+
metrics: z4.object({ totalXp: z4.number().optional(), totalLessons: z4.number().optional() }).optional()
|
|
320
|
+
}).optional(),
|
|
321
|
+
course: z4.object({
|
|
322
|
+
id: z4.string(),
|
|
323
|
+
title: z4.string(),
|
|
324
|
+
subjects: z4.array(z4.string()).nullable(),
|
|
325
|
+
grades: z4.array(z4.string()).nullable()
|
|
326
|
+
}),
|
|
327
|
+
school: z4.object({
|
|
328
|
+
id: z4.string(),
|
|
329
|
+
name: z4.string()
|
|
330
|
+
})
|
|
331
|
+
});
|
|
332
|
+
var SubjectMetrics = z4.object({
|
|
333
|
+
activityMetrics: z4.object({
|
|
334
|
+
xpEarned: z4.number(),
|
|
335
|
+
totalQuestions: z4.number(),
|
|
336
|
+
correctQuestions: z4.number(),
|
|
337
|
+
masteredUnits: z4.number()
|
|
338
|
+
}),
|
|
339
|
+
timeSpentMetrics: z4.object({
|
|
340
|
+
activeSeconds: z4.number(),
|
|
341
|
+
inactiveSeconds: z4.number(),
|
|
342
|
+
wasteSeconds: z4.number()
|
|
343
|
+
}),
|
|
344
|
+
apps: z4.array(z4.string())
|
|
345
|
+
});
|
|
346
|
+
var EduBridgeAnalyticsFacts = z4.record(z4.string(), z4.record(z4.string(), SubjectMetrics));
|
|
347
|
+
var EduBridgeEnrollmentAnalyticsResponse = z4.object({
|
|
348
|
+
message: z4.string(),
|
|
349
|
+
enrollmentId: z4.string(),
|
|
350
|
+
startDate: z4.string(),
|
|
351
|
+
endDate: z4.string(),
|
|
352
|
+
facts: EduBridgeAnalyticsFacts,
|
|
353
|
+
factsByApp: z4.unknown()
|
|
354
|
+
});
|
|
355
|
+
var SubjectTrackInput = z4.object({
|
|
356
|
+
subject: z4.string(),
|
|
357
|
+
gradeLevel: z4.string(),
|
|
358
|
+
targetCourseId: z4.string(),
|
|
359
|
+
metadata: z4.record(z4.string(), z4.unknown()).optional()
|
|
360
|
+
});
|
|
361
|
+
// ../types/src/zod/qti.ts
|
|
362
|
+
import { z as z5 } from "zod/v4";
|
|
363
|
+
var QtiAssessmentItemType = z5.enum([
|
|
364
|
+
"choice",
|
|
365
|
+
"text-entry",
|
|
366
|
+
"extended-text",
|
|
367
|
+
"inline-choice",
|
|
368
|
+
"match",
|
|
369
|
+
"order",
|
|
370
|
+
"associate",
|
|
371
|
+
"select-point",
|
|
372
|
+
"graphic-order",
|
|
373
|
+
"graphic-associate",
|
|
374
|
+
"graphic-gap-match",
|
|
375
|
+
"hotspot",
|
|
376
|
+
"hottext",
|
|
377
|
+
"slider",
|
|
378
|
+
"drawing",
|
|
379
|
+
"media",
|
|
380
|
+
"upload"
|
|
381
|
+
]);
|
|
382
|
+
var QtiCardinality = z5.enum(["single", "multiple", "ordered", "record"]);
|
|
383
|
+
var QtiBaseType = z5.enum([
|
|
384
|
+
"identifier",
|
|
385
|
+
"boolean",
|
|
386
|
+
"integer",
|
|
387
|
+
"float",
|
|
388
|
+
"string",
|
|
389
|
+
"point",
|
|
390
|
+
"pair",
|
|
391
|
+
"directedPair",
|
|
392
|
+
"duration",
|
|
393
|
+
"file",
|
|
394
|
+
"uri"
|
|
395
|
+
]);
|
|
396
|
+
var QtiDifficulty = z5.enum(["easy", "medium", "hard"]);
|
|
397
|
+
var QtiNavigationMode = z5.enum(["linear", "nonlinear"]);
|
|
398
|
+
var QtiSubmissionMode = z5.enum(["individual", "simultaneous"]);
|
|
399
|
+
var QtiShowHide = z5.enum(["show", "hide"]);
|
|
400
|
+
var QtiValidationSchema = z5.enum(["test", "item", "stimulus"]);
|
|
401
|
+
var QtiFeedbackType = z5.enum(["QUESTION", "LESSON"]);
|
|
402
|
+
var QtiCorrectResponse = z5.object({
|
|
403
|
+
value: z5.array(z5.string())
|
|
404
|
+
}).strict();
|
|
405
|
+
var QtiResponseDeclaration = z5.object({
|
|
406
|
+
identifier: z5.string().min(1),
|
|
407
|
+
cardinality: QtiCardinality,
|
|
408
|
+
baseType: QtiBaseType.optional(),
|
|
409
|
+
correctResponse: QtiCorrectResponse
|
|
410
|
+
}).strict();
|
|
411
|
+
var QtiOutcomeDeclaration = z5.object({
|
|
412
|
+
identifier: z5.string().min(1),
|
|
413
|
+
cardinality: QtiCardinality,
|
|
414
|
+
baseType: QtiBaseType.optional()
|
|
415
|
+
}).strict();
|
|
416
|
+
var QtiTestOutcomeDeclaration = z5.object({
|
|
417
|
+
identifier: z5.string().min(1),
|
|
418
|
+
cardinality: QtiCardinality.optional(),
|
|
419
|
+
baseType: QtiBaseType,
|
|
420
|
+
normalMaximum: z5.number().optional(),
|
|
421
|
+
normalMinimum: z5.number().optional(),
|
|
422
|
+
defaultValue: z5.object({
|
|
423
|
+
value: z5.unknown().optional()
|
|
424
|
+
}).strict().optional()
|
|
425
|
+
}).strict();
|
|
426
|
+
var QtiInlineFeedback = z5.object({
|
|
427
|
+
outcomeIdentifier: z5.string().min(1),
|
|
428
|
+
variableIdentifier: z5.string().min(1)
|
|
429
|
+
}).strict();
|
|
430
|
+
var QtiResponseProcessing = z5.object({
|
|
431
|
+
templateType: z5.enum(["match_correct", "map_response"]),
|
|
432
|
+
responseDeclarationIdentifier: z5.string().min(1),
|
|
433
|
+
outcomeIdentifier: z5.string().min(1),
|
|
434
|
+
correctResponseIdentifier: z5.string().min(1),
|
|
435
|
+
incorrectResponseIdentifier: z5.string().min(1),
|
|
436
|
+
inlineFeedback: QtiInlineFeedback.optional()
|
|
437
|
+
}).strict();
|
|
438
|
+
var QtiLearningObjectiveSet = z5.object({
|
|
439
|
+
source: z5.string().min(1),
|
|
440
|
+
learningObjectiveIds: z5.array(z5.string())
|
|
441
|
+
}).strict();
|
|
442
|
+
var QtiItemMetadata = z5.object({
|
|
443
|
+
subject: z5.string().optional(),
|
|
444
|
+
grade: TimebackGrade.optional(),
|
|
445
|
+
difficulty: QtiDifficulty.optional(),
|
|
446
|
+
learningObjectiveSet: z5.array(QtiLearningObjectiveSet).optional()
|
|
447
|
+
}).strict();
|
|
448
|
+
var QtiModalFeedback = z5.object({
|
|
449
|
+
outcomeIdentifier: z5.string().min(1),
|
|
450
|
+
identifier: z5.string().min(1),
|
|
451
|
+
showHide: QtiShowHide,
|
|
452
|
+
content: z5.string(),
|
|
453
|
+
title: z5.string()
|
|
454
|
+
}).strict();
|
|
455
|
+
var QtiFeedbackInline = z5.object({
|
|
456
|
+
outcomeIdentifier: z5.string().min(1),
|
|
457
|
+
identifier: z5.string().min(1),
|
|
458
|
+
showHide: QtiShowHide,
|
|
459
|
+
content: z5.string(),
|
|
460
|
+
class: z5.array(z5.string())
|
|
461
|
+
}).strict();
|
|
462
|
+
var QtiFeedbackBlock = z5.object({
|
|
463
|
+
outcomeIdentifier: z5.string().min(1),
|
|
464
|
+
identifier: z5.string().min(1),
|
|
465
|
+
showHide: QtiShowHide,
|
|
466
|
+
content: z5.string(),
|
|
467
|
+
class: z5.array(z5.string())
|
|
468
|
+
}).strict();
|
|
469
|
+
var QtiStylesheet = z5.object({
|
|
470
|
+
href: z5.string().min(1),
|
|
471
|
+
type: z5.string().min(1)
|
|
472
|
+
}).strict();
|
|
473
|
+
var QtiCatalogInfo = z5.object({
|
|
474
|
+
id: z5.string().min(1),
|
|
475
|
+
support: z5.string(),
|
|
476
|
+
content: z5.string()
|
|
477
|
+
}).strict();
|
|
478
|
+
var QtiAssessmentItemCreateInput = z5.object({
|
|
479
|
+
identifier: z5.string().min(1),
|
|
480
|
+
title: z5.string().min(1),
|
|
481
|
+
type: QtiAssessmentItemType,
|
|
482
|
+
qtiVersion: z5.string().optional(),
|
|
483
|
+
timeDependent: z5.boolean().optional(),
|
|
484
|
+
adaptive: z5.boolean().optional(),
|
|
485
|
+
responseDeclarations: z5.array(QtiResponseDeclaration).optional(),
|
|
486
|
+
outcomeDeclarations: z5.array(QtiOutcomeDeclaration).optional(),
|
|
487
|
+
responseProcessing: QtiResponseProcessing.optional(),
|
|
488
|
+
metadata: QtiItemMetadata.optional(),
|
|
489
|
+
modalFeedback: z5.array(QtiModalFeedback).optional(),
|
|
490
|
+
feedbackInline: z5.array(QtiFeedbackInline).optional(),
|
|
491
|
+
feedbackBlock: z5.array(QtiFeedbackBlock).optional()
|
|
492
|
+
}).strict();
|
|
493
|
+
var QtiAssessmentItemUpdateInput = z5.object({
|
|
494
|
+
title: z5.string().min(1),
|
|
495
|
+
type: QtiAssessmentItemType,
|
|
496
|
+
qtiVersion: z5.string().optional(),
|
|
497
|
+
timeDependent: z5.boolean().optional(),
|
|
498
|
+
adaptive: z5.boolean().optional(),
|
|
499
|
+
responseDeclarations: z5.array(QtiResponseDeclaration).optional(),
|
|
500
|
+
outcomeDeclarations: z5.array(QtiOutcomeDeclaration).optional(),
|
|
501
|
+
responseProcessing: QtiResponseProcessing.optional(),
|
|
502
|
+
metadata: QtiItemMetadata.optional(),
|
|
503
|
+
modalFeedback: z5.array(QtiModalFeedback).optional(),
|
|
504
|
+
feedbackInline: z5.array(QtiFeedbackInline).optional(),
|
|
505
|
+
feedbackBlock: z5.array(QtiFeedbackBlock).optional(),
|
|
506
|
+
rawXml: z5.string(),
|
|
507
|
+
content: z5.record(z5.string(), z5.unknown())
|
|
508
|
+
}).strict();
|
|
509
|
+
var QtiAssessmentItemProcessResponseInput = z5.object({
|
|
510
|
+
response: z5.union([z5.string(), z5.array(z5.string())])
|
|
511
|
+
}).strict();
|
|
512
|
+
var QtiAssessmentItemRef = z5.object({
|
|
513
|
+
identifier: z5.string().min(1),
|
|
514
|
+
href: z5.union([z5.string(), z5.array(z5.string()), z5.array(z5.array(z5.string()))]).optional(),
|
|
515
|
+
sequence: z5.number().optional()
|
|
516
|
+
}).strict();
|
|
517
|
+
var QtiAssessmentSection = z5.object({
|
|
518
|
+
identifier: z5.string().min(1),
|
|
519
|
+
title: z5.string().min(1),
|
|
520
|
+
visible: z5.boolean(),
|
|
521
|
+
required: z5.boolean().optional(),
|
|
522
|
+
fixed: z5.boolean().optional(),
|
|
523
|
+
sequence: z5.number().optional(),
|
|
524
|
+
"qti-assessment-item-ref": z5.array(QtiAssessmentItemRef).optional()
|
|
525
|
+
}).strict();
|
|
526
|
+
var QtiTestPart = z5.object({
|
|
527
|
+
identifier: z5.string().min(1),
|
|
528
|
+
navigationMode: QtiNavigationMode,
|
|
529
|
+
submissionMode: QtiSubmissionMode,
|
|
530
|
+
"qti-assessment-section": z5.union([QtiAssessmentSection, z5.array(QtiAssessmentSection)])
|
|
531
|
+
}).strict();
|
|
532
|
+
var QtiAssessmentTestCreateInput = z5.object({
|
|
533
|
+
identifier: z5.string().min(1),
|
|
534
|
+
title: z5.string().min(1),
|
|
535
|
+
qtiVersion: z5.string().optional(),
|
|
536
|
+
toolName: z5.string().optional(),
|
|
537
|
+
toolVersion: z5.string().optional(),
|
|
538
|
+
timeLimit: z5.number().optional(),
|
|
539
|
+
maxAttempts: z5.number().optional(),
|
|
540
|
+
toolsEnabled: z5.record(z5.string(), z5.boolean()).optional(),
|
|
541
|
+
metadata: z5.record(z5.string(), z5.unknown()).optional(),
|
|
542
|
+
"qti-test-part": QtiTestPart,
|
|
543
|
+
"qti-outcome-declaration": z5.array(QtiTestOutcomeDeclaration).optional()
|
|
544
|
+
}).strict();
|
|
545
|
+
var QtiAssessmentTestUpdateInput = z5.object({
|
|
546
|
+
title: z5.string().min(1),
|
|
547
|
+
qtiVersion: z5.string().optional(),
|
|
548
|
+
toolName: z5.string().optional(),
|
|
549
|
+
toolVersion: z5.string().optional(),
|
|
550
|
+
timeLimit: z5.number().optional(),
|
|
551
|
+
maxAttempts: z5.number().optional(),
|
|
552
|
+
toolsEnabled: z5.record(z5.string(), z5.boolean()).optional(),
|
|
553
|
+
metadata: z5.record(z5.string(), z5.unknown()).optional(),
|
|
554
|
+
"qti-test-part": QtiTestPart,
|
|
555
|
+
"qti-outcome-declaration": z5.array(QtiTestOutcomeDeclaration).optional()
|
|
556
|
+
}).strict();
|
|
557
|
+
var QtiStimulusCreateInput = z5.object({
|
|
558
|
+
identifier: z5.string().min(1),
|
|
559
|
+
title: z5.string().min(1),
|
|
560
|
+
label: z5.string().optional(),
|
|
561
|
+
language: z5.string().optional(),
|
|
562
|
+
stylesheet: QtiStylesheet.optional(),
|
|
563
|
+
content: z5.string(),
|
|
564
|
+
catalogInfo: z5.array(QtiCatalogInfo).optional(),
|
|
565
|
+
toolName: z5.string().optional(),
|
|
566
|
+
toolVersion: z5.string().optional(),
|
|
567
|
+
metadata: z5.record(z5.string(), z5.unknown()).optional()
|
|
568
|
+
}).strict();
|
|
569
|
+
var QtiStimulusUpdateInput = z5.object({
|
|
570
|
+
title: z5.string().min(1),
|
|
571
|
+
label: z5.string().optional(),
|
|
572
|
+
language: z5.string().optional(),
|
|
573
|
+
stylesheet: QtiStylesheet.optional(),
|
|
574
|
+
content: z5.string(),
|
|
575
|
+
catalogInfo: z5.array(QtiCatalogInfo).optional(),
|
|
576
|
+
toolName: z5.string().optional(),
|
|
577
|
+
toolVersion: z5.string().optional(),
|
|
578
|
+
metadata: z5.record(z5.string(), z5.unknown()).optional()
|
|
579
|
+
}).strict();
|
|
580
|
+
var QtiValidateInput = z5.object({
|
|
581
|
+
xml: z5.string().optional(),
|
|
582
|
+
schema: QtiValidationSchema,
|
|
583
|
+
entityId: z5.string().optional()
|
|
584
|
+
}).strict();
|
|
585
|
+
var QtiValidateBatchInput = z5.object({
|
|
586
|
+
xml: z5.array(z5.string()),
|
|
587
|
+
schema: QtiValidationSchema,
|
|
588
|
+
entityIds: z5.array(z5.string())
|
|
589
|
+
}).strict();
|
|
590
|
+
var QtiLessonFeedbackInput = z5.object({
|
|
591
|
+
questionId: z5.string().optional(),
|
|
592
|
+
userId: z5.string().min(1),
|
|
593
|
+
feedback: z5.string().min(1),
|
|
594
|
+
lessonId: z5.string().min(1),
|
|
595
|
+
humanApproved: z5.boolean().optional()
|
|
596
|
+
}).strict();
|
|
597
|
+
// ../internal/cli-infra/src/credentials/store.ts
|
|
598
|
+
import { homedir, platform as platform2 } from "node:os";
|
|
599
|
+
import { join } from "node:path";
|
|
600
|
+
|
|
601
|
+
// ../internal/cli-infra/src/credentials/schema.ts
|
|
602
|
+
import { z as z6 } from "zod";
|
|
603
|
+
var CredentialsSchema = z6.object({
|
|
604
|
+
clientId: z6.string().min(1, "Client ID is required"),
|
|
605
|
+
clientSecret: z6.string().min(1, "Client secret is required"),
|
|
606
|
+
email: z6.email("Valid email is required").optional()
|
|
607
|
+
});
|
|
608
|
+
var ClientIdSchema = z6.string().min(1, "Client ID is required").regex(/^[a-z0-9]+$/, "Client ID must contain only lowercase letters and numbers").length(26, "Client ID must be exactly 26 characters");
|
|
609
|
+
var ClientSecretSchema = z6.string().min(1, "Client secret is required").regex(/^[a-z0-9]+$/, "Client secret must contain only lowercase letters and numbers").max(53, "Client secret must be less than 53 characters");
|
|
610
|
+
|
|
611
|
+
// ../internal/cli-infra/src/credentials/store.ts
|
|
612
|
+
function getConfigDir() {
|
|
613
|
+
const home = homedir();
|
|
614
|
+
switch (platform2()) {
|
|
615
|
+
case "darwin":
|
|
616
|
+
return join(home, ".timeback");
|
|
617
|
+
case "win32":
|
|
618
|
+
return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "timeback");
|
|
619
|
+
default:
|
|
620
|
+
return join(process.env.XDG_CONFIG_HOME || join(home, ".config"), "timeback");
|
|
621
|
+
}
|
|
4
622
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
623
|
+
var CREDENTIALS_DIR = getConfigDir();
|
|
624
|
+
var CREDENTIALS_FILE = join(CREDENTIALS_DIR, "credentials.json");
|
|
625
|
+
|
|
626
|
+
// ../internal/cli-infra/src/config/timeback.ts
|
|
627
|
+
var FILE_PATTERNS = ["timeback.config.ts", "timeback.config.js", "timeback.config.mjs"];
|
|
628
|
+
var jiti = createJiti(import.meta.url, { fsCache: false });
|
|
629
|
+
async function importModule(fullPath) {
|
|
630
|
+
const module = await jiti.import(fullPath);
|
|
631
|
+
return module.default ?? module;
|
|
8
632
|
}
|
|
9
|
-
function
|
|
633
|
+
async function loadConfig(opts = {}) {
|
|
634
|
+
const cwd = process.cwd();
|
|
635
|
+
let rawConfig = null;
|
|
636
|
+
let foundPath = null;
|
|
637
|
+
let loadError = null;
|
|
638
|
+
if (opts.configPath) {
|
|
639
|
+
const fullPath = resolve(cwd, opts.configPath);
|
|
640
|
+
if (!existsSync(fullPath)) {
|
|
641
|
+
return {
|
|
642
|
+
success: false,
|
|
643
|
+
error: `Config file not found: ${opts.configPath}`
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
rawConfig = await importModule(fullPath);
|
|
648
|
+
foundPath = fullPath;
|
|
649
|
+
} catch (err) {
|
|
650
|
+
return {
|
|
651
|
+
success: false,
|
|
652
|
+
error: `Failed to load ${opts.configPath}:
|
|
653
|
+
${err instanceof Error ? err.message : String(err)}`
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
} else {
|
|
657
|
+
for (const filename of FILE_PATTERNS) {
|
|
658
|
+
const fullPath = resolve(cwd, filename);
|
|
659
|
+
if (!existsSync(fullPath))
|
|
660
|
+
continue;
|
|
661
|
+
try {
|
|
662
|
+
rawConfig = await importModule(fullPath);
|
|
663
|
+
foundPath = fullPath;
|
|
664
|
+
break;
|
|
665
|
+
} catch (err) {
|
|
666
|
+
loadError = err instanceof Error ? err : new Error(String(err));
|
|
667
|
+
foundPath = fullPath;
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (loadError && foundPath) {
|
|
673
|
+
return {
|
|
674
|
+
success: false,
|
|
675
|
+
error: `Failed to load ${basename(foundPath)}:
|
|
676
|
+
${loadError.message}`
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
if (!rawConfig || !foundPath) {
|
|
680
|
+
return {
|
|
681
|
+
success: false,
|
|
682
|
+
error: `No timeback config found. Run ${greenBright("timeback init")} to create one.`
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
const result = TimebackConfig.safeParse(rawConfig);
|
|
686
|
+
if (!result.success) {
|
|
687
|
+
const issues = result.error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join(`
|
|
688
|
+
`);
|
|
689
|
+
return {
|
|
690
|
+
success: false,
|
|
691
|
+
error: `Invalid config in ${basename(foundPath)}:
|
|
692
|
+
${issues}`
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
return {
|
|
696
|
+
success: true,
|
|
697
|
+
config: result.data,
|
|
698
|
+
configPath: foundPath
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
// src/shared/constants.ts
|
|
702
|
+
var ROUTES = {
|
|
703
|
+
ACTIVITY: "/activity",
|
|
704
|
+
IDENTITY: {
|
|
705
|
+
SIGNIN: "/identity/signin",
|
|
706
|
+
SIGNOUT: "/identity/signout",
|
|
707
|
+
CALLBACK: "/identity/callback"
|
|
708
|
+
},
|
|
709
|
+
USER: {
|
|
710
|
+
ME: "/user/me"
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
|
|
714
|
+
// ../internal/logger/src/debug.ts
|
|
715
|
+
var patterns = null;
|
|
716
|
+
var debugAll = false;
|
|
717
|
+
var debugEnvSet = false;
|
|
718
|
+
function patternToRegex(pattern) {
|
|
719
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
720
|
+
const regexStr = escaped.replace(/\*/g, ".*");
|
|
721
|
+
return new RegExp(`^${regexStr}$`);
|
|
722
|
+
}
|
|
723
|
+
function parseDebugEnv() {
|
|
724
|
+
if (patterns !== null)
|
|
725
|
+
return;
|
|
726
|
+
patterns = [];
|
|
727
|
+
if (typeof process === "undefined" || !process.env?.DEBUG) {
|
|
728
|
+
debugEnvSet = false;
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
debugEnvSet = true;
|
|
732
|
+
const debugValue = process.env.DEBUG.trim();
|
|
733
|
+
if (debugValue === "1" || debugValue === "true" || debugValue === "*") {
|
|
734
|
+
debugAll = true;
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const parts = debugValue.split(",").map((p) => p.trim()).filter(Boolean);
|
|
738
|
+
for (const part of parts) {
|
|
739
|
+
if (part.startsWith("-")) {
|
|
740
|
+
patterns.push({
|
|
741
|
+
regex: patternToRegex(part.slice(1)),
|
|
742
|
+
exclude: true
|
|
743
|
+
});
|
|
744
|
+
} else {
|
|
745
|
+
patterns.push({
|
|
746
|
+
regex: patternToRegex(part),
|
|
747
|
+
exclude: false
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
const hasInclude = patterns.some((p) => !p.exclude);
|
|
752
|
+
if (!hasInclude && patterns.length > 0) {
|
|
753
|
+
debugAll = true;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
function shouldShowDebug(scope) {
|
|
757
|
+
parseDebugEnv();
|
|
758
|
+
if (!debugEnvSet) {
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
if (debugAll) {
|
|
762
|
+
if (scope) {
|
|
763
|
+
for (const pattern of patterns) {
|
|
764
|
+
if (pattern.exclude && pattern.regex.test(scope)) {
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
if (!scope) {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
for (const pattern of patterns) {
|
|
775
|
+
if (pattern.exclude && pattern.regex.test(scope)) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
for (const pattern of patterns) {
|
|
780
|
+
if (!pattern.exclude && pattern.regex.test(scope)) {
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
10
784
|
return false;
|
|
11
785
|
}
|
|
786
|
+
|
|
787
|
+
// ../internal/logger/src/env.ts
|
|
788
|
+
function isBrowser() {
|
|
789
|
+
return typeof globalThis !== "undefined" && "window" in globalThis;
|
|
790
|
+
}
|
|
791
|
+
function detectEnvironment() {
|
|
792
|
+
if (isBrowser()) {
|
|
793
|
+
return "browser";
|
|
794
|
+
}
|
|
795
|
+
if (typeof process !== "undefined" && process.env) {
|
|
796
|
+
if (process.env["NODE_ENV"] === "test" || process.env["BUN_ENV"] === "test") {
|
|
797
|
+
return "test";
|
|
798
|
+
}
|
|
799
|
+
if (false) {}
|
|
800
|
+
if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.JENKINS_URL || process.env.BUILDKITE) {
|
|
801
|
+
return "ci";
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return "terminal";
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// ../internal/logger/src/formatters/terminal.ts
|
|
808
|
+
var nodeInspect;
|
|
809
|
+
if (!isBrowser()) {
|
|
810
|
+
try {
|
|
811
|
+
const util = await import("node:util");
|
|
812
|
+
nodeInspect = util.inspect;
|
|
813
|
+
} catch {}
|
|
814
|
+
}
|
|
815
|
+
var colors2 = {
|
|
816
|
+
reset: "\x1B[0m",
|
|
817
|
+
bold: "\x1B[1m",
|
|
818
|
+
dim: "\x1B[2m",
|
|
819
|
+
red: "\x1B[31m",
|
|
820
|
+
yellow: "\x1B[33m",
|
|
821
|
+
blue: "\x1B[34m",
|
|
822
|
+
cyan: "\x1B[36m"
|
|
823
|
+
};
|
|
824
|
+
function getLevelColor(level) {
|
|
825
|
+
switch (level) {
|
|
826
|
+
case "debug":
|
|
827
|
+
return colors2.blue;
|
|
828
|
+
case "info":
|
|
829
|
+
return colors2.cyan;
|
|
830
|
+
case "warn":
|
|
831
|
+
return colors2.yellow;
|
|
832
|
+
case "error":
|
|
833
|
+
return colors2.red;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
function getConsoleMethod(level) {
|
|
837
|
+
switch (level) {
|
|
838
|
+
case "debug":
|
|
839
|
+
return console.debug;
|
|
840
|
+
case "info":
|
|
841
|
+
return console.info;
|
|
842
|
+
case "warn":
|
|
843
|
+
return console.warn;
|
|
844
|
+
case "error":
|
|
845
|
+
return console.error;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
function formatContext(context) {
|
|
849
|
+
if (nodeInspect) {
|
|
850
|
+
return nodeInspect(context, {
|
|
851
|
+
depth: null,
|
|
852
|
+
colors: true,
|
|
853
|
+
breakLength: 80,
|
|
854
|
+
compact: false
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
return JSON.stringify(context, null, 2);
|
|
858
|
+
}
|
|
859
|
+
var terminalFormatter = (entry) => {
|
|
860
|
+
const levelColor = getLevelColor(entry.level);
|
|
861
|
+
const consoleMethod = getConsoleMethod(entry.level);
|
|
862
|
+
const levelUpper = entry.level.toUpperCase().padEnd(5);
|
|
863
|
+
const isoString = entry.timestamp.toISOString().replace(/\.\d{3}Z$/, "");
|
|
864
|
+
const timestamp = `${colors2.dim}[${isoString}]${colors2.reset}`;
|
|
865
|
+
const level = `${levelColor}${levelUpper}${colors2.reset}`;
|
|
866
|
+
const scope = entry.scope ? `${colors2.bold}[${entry.scope}]${colors2.reset} ` : "";
|
|
867
|
+
const prefix = `${timestamp} ${level} ${scope}${entry.message}`;
|
|
868
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
869
|
+
consoleMethod(prefix, formatContext(entry.context));
|
|
870
|
+
} else {
|
|
871
|
+
consoleMethod(prefix);
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
// ../internal/logger/src/formatters/ci.ts
|
|
875
|
+
var LEVEL_PREFIX = {
|
|
876
|
+
debug: "[DEBUG]",
|
|
877
|
+
info: "[INFO]",
|
|
878
|
+
warn: "[WARN]",
|
|
879
|
+
error: "[ERROR]"
|
|
880
|
+
};
|
|
881
|
+
function formatContext2(context) {
|
|
882
|
+
return Object.entries(context).map(([key, value]) => `${key}=${formatValue(value)}`).join(" ");
|
|
883
|
+
}
|
|
884
|
+
function formatValue(value) {
|
|
885
|
+
if (typeof value === "string")
|
|
886
|
+
return value;
|
|
887
|
+
if (typeof value === "number")
|
|
888
|
+
return String(value);
|
|
889
|
+
if (typeof value === "boolean")
|
|
890
|
+
return String(value);
|
|
891
|
+
if (value === null)
|
|
892
|
+
return "null";
|
|
893
|
+
if (value === undefined)
|
|
894
|
+
return "undefined";
|
|
895
|
+
return JSON.stringify(value);
|
|
896
|
+
}
|
|
897
|
+
var ciFormatter = (entry) => {
|
|
898
|
+
const parts = [];
|
|
899
|
+
parts.push(entry.timestamp.toISOString());
|
|
900
|
+
parts.push(LEVEL_PREFIX[entry.level]);
|
|
901
|
+
if (entry.scope) {
|
|
902
|
+
parts.push(`[${entry.scope}]`);
|
|
903
|
+
}
|
|
904
|
+
parts.push(entry.message);
|
|
905
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
906
|
+
parts.push(formatContext2(entry.context));
|
|
907
|
+
}
|
|
908
|
+
console.log(parts.join(" "));
|
|
909
|
+
};
|
|
910
|
+
// ../internal/logger/src/formatters/production.ts
|
|
911
|
+
var productionFormatter = (entry) => {
|
|
912
|
+
const output = {
|
|
913
|
+
timestamp: entry.timestamp.toISOString(),
|
|
914
|
+
level: entry.level,
|
|
915
|
+
...entry.scope && { scope: entry.scope },
|
|
916
|
+
msg: entry.message
|
|
917
|
+
};
|
|
918
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
919
|
+
Object.assign(output, entry.context);
|
|
920
|
+
}
|
|
921
|
+
console.log(JSON.stringify(output));
|
|
922
|
+
};
|
|
923
|
+
// ../internal/logger/src/formatters/browser.ts
|
|
924
|
+
var LEVEL_STYLES = {
|
|
925
|
+
debug: "color: gray",
|
|
926
|
+
info: "color: #0ea5e9",
|
|
927
|
+
warn: "color: #f59e0b",
|
|
928
|
+
error: "color: #ef4444; font-weight: bold"
|
|
929
|
+
};
|
|
930
|
+
var LEVEL_METHODS = {
|
|
931
|
+
debug: "log",
|
|
932
|
+
info: "info",
|
|
933
|
+
warn: "warn",
|
|
934
|
+
error: "error"
|
|
935
|
+
};
|
|
936
|
+
var browserFormatter = (entry) => {
|
|
937
|
+
const method = LEVEL_METHODS[entry.level];
|
|
938
|
+
const style = LEVEL_STYLES[entry.level];
|
|
939
|
+
const prefix = entry.scope ? `[${entry.scope}]` : "";
|
|
940
|
+
const label = `%c${prefix} ${entry.message}`;
|
|
941
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
942
|
+
console[method](label, style, entry.context);
|
|
943
|
+
} else {
|
|
944
|
+
console[method](label, style);
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
// ../internal/logger/src/logger.ts
|
|
948
|
+
var LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
949
|
+
function getFormatter(env2) {
|
|
950
|
+
switch (env2) {
|
|
951
|
+
case "terminal":
|
|
952
|
+
return terminalFormatter;
|
|
953
|
+
case "ci":
|
|
954
|
+
return ciFormatter;
|
|
955
|
+
case "production":
|
|
956
|
+
return productionFormatter;
|
|
957
|
+
case "browser":
|
|
958
|
+
return browserFormatter;
|
|
959
|
+
case "test":
|
|
960
|
+
return () => {};
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
function getDefaultMinLevel() {
|
|
964
|
+
if (typeof process !== "undefined" && process.env?.DEBUG) {
|
|
965
|
+
return "debug";
|
|
966
|
+
}
|
|
967
|
+
return "info";
|
|
968
|
+
}
|
|
969
|
+
function shouldLog(level, minLevel) {
|
|
970
|
+
return LOG_LEVELS.indexOf(level) >= LOG_LEVELS.indexOf(minLevel);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
class Logger {
|
|
974
|
+
scope;
|
|
975
|
+
minLevel;
|
|
976
|
+
environment;
|
|
977
|
+
formatter;
|
|
978
|
+
defaultContext;
|
|
979
|
+
constructor(options = {}) {
|
|
980
|
+
this.scope = options.scope;
|
|
981
|
+
this.minLevel = options.minLevel ?? getDefaultMinLevel();
|
|
982
|
+
this.defaultContext = options.defaultContext ?? {};
|
|
983
|
+
this.environment = options.environment ?? detectEnvironment();
|
|
984
|
+
this.formatter = getFormatter(this.environment);
|
|
985
|
+
}
|
|
986
|
+
child(scope) {
|
|
987
|
+
const childScope = this.scope ? `${this.scope}:${scope}` : scope;
|
|
988
|
+
return new Logger({
|
|
989
|
+
scope: childScope,
|
|
990
|
+
minLevel: this.minLevel,
|
|
991
|
+
environment: this.environment,
|
|
992
|
+
defaultContext: { ...this.defaultContext }
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
withContext(context) {
|
|
996
|
+
return new Logger({
|
|
997
|
+
scope: this.scope,
|
|
998
|
+
minLevel: this.minLevel,
|
|
999
|
+
environment: this.environment,
|
|
1000
|
+
defaultContext: { ...this.defaultContext, ...context }
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
debug(message, context) {
|
|
1004
|
+
this.log("debug", message, context);
|
|
1005
|
+
}
|
|
1006
|
+
info(message, context) {
|
|
1007
|
+
this.log("info", message, context);
|
|
1008
|
+
}
|
|
1009
|
+
warn(message, context) {
|
|
1010
|
+
this.log("warn", message, context);
|
|
1011
|
+
}
|
|
1012
|
+
error(message, context) {
|
|
1013
|
+
this.log("error", message, context);
|
|
1014
|
+
}
|
|
1015
|
+
log(level, message, context) {
|
|
1016
|
+
if (level === "debug" && !shouldShowDebug(this.scope)) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
if (!shouldLog(level, this.minLevel)) {
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const entry = {
|
|
1023
|
+
level,
|
|
1024
|
+
message,
|
|
1025
|
+
scope: this.scope,
|
|
1026
|
+
context: context || Object.keys(this.defaultContext).length > 0 ? { ...this.defaultContext, ...context } : undefined,
|
|
1027
|
+
timestamp: new Date
|
|
1028
|
+
};
|
|
1029
|
+
this.formatter(entry);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
function createLogger(options = {}) {
|
|
1033
|
+
return new Logger(options);
|
|
1034
|
+
}
|
|
1035
|
+
// src/server/lib/logger.ts
|
|
1036
|
+
function isDebug() {
|
|
1037
|
+
try {
|
|
1038
|
+
const debug = typeof process === "undefined" ? undefined : process.env.DEBUG;
|
|
1039
|
+
return debug === "1" || debug === "true";
|
|
1040
|
+
} catch {
|
|
1041
|
+
return false;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
function createScopedLogger(scope) {
|
|
1045
|
+
return createLogger({
|
|
1046
|
+
scope: `timeback:${scope}`,
|
|
1047
|
+
minLevel: isDebug() ? "debug" : "warn"
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
var ssoLog = createScopedLogger("sso");
|
|
1051
|
+
var oidcLog = createScopedLogger("oidc");
|
|
1052
|
+
|
|
1053
|
+
// src/server/lib/oidc.ts
|
|
1054
|
+
var discoveryCache = new Map;
|
|
1055
|
+
async function fetchDiscoveryDocument(issuer) {
|
|
1056
|
+
const cached = discoveryCache.get(issuer);
|
|
1057
|
+
if (cached) {
|
|
1058
|
+
return cached;
|
|
1059
|
+
}
|
|
1060
|
+
const url = `${issuer}/.well-known/openid-configuration`;
|
|
1061
|
+
const response = await fetch(url);
|
|
1062
|
+
if (!response.ok) {
|
|
1063
|
+
oidcLog.error("Discovery fetch failed", { status: response.status });
|
|
1064
|
+
throw new Error(`Failed to fetch OIDC discovery: ${response.statusText}`);
|
|
1065
|
+
}
|
|
1066
|
+
const doc = await response.json();
|
|
1067
|
+
oidcLog.debug("Fetched OIDC discovery document", {
|
|
1068
|
+
authEndpoint: doc.authorization_endpoint,
|
|
1069
|
+
tokenEndpoint: doc.token_endpoint
|
|
1070
|
+
});
|
|
1071
|
+
discoveryCache.set(issuer, doc);
|
|
1072
|
+
return doc;
|
|
1073
|
+
}
|
|
1074
|
+
function getIssuer(env2) {
|
|
1075
|
+
switch (env2) {
|
|
1076
|
+
case "production":
|
|
1077
|
+
return "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_3uhuoRM3R";
|
|
1078
|
+
case "staging":
|
|
1079
|
+
return "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_5EUwTP9XD";
|
|
1080
|
+
case "local":
|
|
1081
|
+
throw new Error("Local environment is not yet supported for OIDC");
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
async function buildAuthorizationUrl(params) {
|
|
1085
|
+
const discovery = await fetchDiscoveryDocument(params.issuer);
|
|
1086
|
+
const url = new URL(discovery.authorization_endpoint);
|
|
1087
|
+
url.searchParams.set("response_type", "code");
|
|
1088
|
+
url.searchParams.set("client_id", params.clientId);
|
|
1089
|
+
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
1090
|
+
url.searchParams.set("scope", "openid profile email");
|
|
1091
|
+
url.searchParams.set("state", params.state);
|
|
1092
|
+
return url.toString();
|
|
1093
|
+
}
|
|
1094
|
+
async function exchangeCodeForTokens(params) {
|
|
1095
|
+
const discovery = await fetchDiscoveryDocument(params.issuer);
|
|
1096
|
+
const response = await fetch(discovery.token_endpoint, {
|
|
1097
|
+
method: "POST",
|
|
1098
|
+
headers: {
|
|
1099
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1100
|
+
},
|
|
1101
|
+
body: new URLSearchParams({
|
|
1102
|
+
grant_type: "authorization_code",
|
|
1103
|
+
client_id: params.clientId,
|
|
1104
|
+
client_secret: params.clientSecret,
|
|
1105
|
+
code: params.code,
|
|
1106
|
+
redirect_uri: params.redirectUri
|
|
1107
|
+
})
|
|
1108
|
+
});
|
|
1109
|
+
if (!response.ok) {
|
|
1110
|
+
const text = await response.text();
|
|
1111
|
+
oidcLog.error("Token exchange failed", { status: response.status, body: text });
|
|
1112
|
+
throw new Error(`Token exchange failed: ${response.status} ${text}`);
|
|
1113
|
+
}
|
|
1114
|
+
const tokens = await response.json();
|
|
1115
|
+
oidcLog.debug("Received tokens from IdP", { expiresIn: tokens.expires_in });
|
|
1116
|
+
return tokens;
|
|
1117
|
+
}
|
|
1118
|
+
async function getUserInfo(params) {
|
|
1119
|
+
const discovery = await fetchDiscoveryDocument(params.issuer);
|
|
1120
|
+
const response = await fetch(discovery.userinfo_endpoint, {
|
|
1121
|
+
headers: {
|
|
1122
|
+
Authorization: `Bearer ${params.accessToken}`
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1125
|
+
if (!response.ok) {
|
|
1126
|
+
throw new Error(`UserInfo request failed: ${response.statusText}`);
|
|
1127
|
+
}
|
|
1128
|
+
return response.json();
|
|
1129
|
+
}
|
|
1130
|
+
// src/server/lib/utils.ts
|
|
1131
|
+
function jsonResponse(data, status = 200, headers) {
|
|
1132
|
+
const responseHeaders = new Headers(headers);
|
|
1133
|
+
responseHeaders.set("Content-Type", "application/json");
|
|
1134
|
+
return new Response(JSON.stringify(data), { status, headers: responseHeaders });
|
|
1135
|
+
}
|
|
1136
|
+
function redirectResponse(url, headers) {
|
|
1137
|
+
const responseHeaders = new Headers(headers);
|
|
1138
|
+
responseHeaders.set("Location", url);
|
|
1139
|
+
return new Response(null, { status: 302, headers: responseHeaders });
|
|
1140
|
+
}
|
|
1141
|
+
function encodeBase64Url(data) {
|
|
1142
|
+
const json = JSON.stringify(data);
|
|
1143
|
+
return btoa(json).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1144
|
+
}
|
|
1145
|
+
function decodeBase64Url(encoded) {
|
|
1146
|
+
const padded = encoded.replace(/-/g, "+").replace(/_/g, "/");
|
|
1147
|
+
const json = atob(padded);
|
|
1148
|
+
return JSON.parse(json);
|
|
1149
|
+
}
|
|
1150
|
+
// src/server/handlers/identity.ts
|
|
1151
|
+
function buildErrorContext(error, errorCode, state, req) {
|
|
1152
|
+
return {
|
|
1153
|
+
error,
|
|
1154
|
+
errorCode,
|
|
1155
|
+
state,
|
|
1156
|
+
req,
|
|
1157
|
+
redirect: redirectResponse,
|
|
1158
|
+
json: jsonResponse
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
async function handleSignIn(req, env2, identity) {
|
|
1162
|
+
if (identity.mode !== "sso") {
|
|
1163
|
+
ssoLog.warn("SSO not configured");
|
|
1164
|
+
return jsonResponse({ error: "SSO not configured" }, 400);
|
|
1165
|
+
}
|
|
1166
|
+
const issuer = identity.issuer ?? getIssuer(env2);
|
|
1167
|
+
const url = new URL(req.url);
|
|
1168
|
+
let redirectUri = identity.redirectUri;
|
|
1169
|
+
if (!redirectUri) {
|
|
1170
|
+
const basePath = url.pathname.replace(ROUTES.IDENTITY.SIGNIN, "");
|
|
1171
|
+
redirectUri = `${url.origin}${basePath}${ROUTES.IDENTITY.CALLBACK}`;
|
|
1172
|
+
}
|
|
1173
|
+
ssoLog.debug("SSO sign-in initiated", { env: env2, issuer, clientId: identity.clientId, redirectUri });
|
|
1174
|
+
const stateData = identity.buildState ? identity.buildState({ req, url }) : {};
|
|
1175
|
+
const state = encodeBase64Url(stateData);
|
|
1176
|
+
const authUrl = await buildAuthorizationUrl({
|
|
1177
|
+
issuer,
|
|
1178
|
+
clientId: identity.clientId,
|
|
1179
|
+
redirectUri,
|
|
1180
|
+
state
|
|
1181
|
+
});
|
|
1182
|
+
ssoLog.debug("Redirecting to IdP", { url: authUrl });
|
|
1183
|
+
return redirectResponse(authUrl);
|
|
1184
|
+
}
|
|
1185
|
+
async function handleCallback(req, env2, identity) {
|
|
1186
|
+
if (identity.mode !== "sso") {
|
|
1187
|
+
ssoLog.warn("SSO not configured");
|
|
1188
|
+
return jsonResponse({ error: "SSO not configured" }, 400);
|
|
1189
|
+
}
|
|
1190
|
+
const url = new URL(req.url);
|
|
1191
|
+
const code = url.searchParams.get("code");
|
|
1192
|
+
const errorParam = url.searchParams.get("error");
|
|
1193
|
+
const stateParam = url.searchParams.get("state");
|
|
1194
|
+
ssoLog.debug("Received callback from IdP", { hasCode: !!code, error: errorParam });
|
|
1195
|
+
const state = stateParam ? tryDecodeState(stateParam) : undefined;
|
|
1196
|
+
if (errorParam) {
|
|
1197
|
+
return await handleCallbackError(errorParam, url, state, req, identity);
|
|
1198
|
+
}
|
|
1199
|
+
if (!code) {
|
|
1200
|
+
return await handleMissingCode(state, req, identity);
|
|
1201
|
+
}
|
|
1202
|
+
return await exchangeAndComplete(code, url, state, req, env2, identity);
|
|
1203
|
+
}
|
|
1204
|
+
function tryDecodeState(stateParam) {
|
|
1205
|
+
try {
|
|
1206
|
+
return decodeBase64Url(stateParam);
|
|
1207
|
+
} catch {
|
|
1208
|
+
ssoLog.warn("Failed to decode state");
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
function handleCallbackError(errorParam, url, state, req, identity) {
|
|
1213
|
+
const errorDesc = url.searchParams.get("error_description");
|
|
1214
|
+
ssoLog.error("IdP returned error", { error: errorParam, description: errorDesc });
|
|
1215
|
+
const error = new Error(errorDesc ?? errorParam);
|
|
1216
|
+
if (identity.onCallbackError) {
|
|
1217
|
+
return identity.onCallbackError(buildErrorContext(error, errorParam, state, req));
|
|
1218
|
+
}
|
|
1219
|
+
return jsonResponse({ error: errorParam }, 400);
|
|
1220
|
+
}
|
|
1221
|
+
function handleMissingCode(state, req, identity) {
|
|
1222
|
+
ssoLog.error("Missing authorization code in callback");
|
|
1223
|
+
const error = new Error("Missing authorization code");
|
|
1224
|
+
if (identity.onCallbackError) {
|
|
1225
|
+
return identity.onCallbackError(buildErrorContext(error, "missing_code", state, req));
|
|
1226
|
+
}
|
|
1227
|
+
return jsonResponse({ error: "Missing authorization code" }, 400);
|
|
1228
|
+
}
|
|
1229
|
+
async function exchangeAndComplete(code, url, state, req, env2, identity) {
|
|
1230
|
+
try {
|
|
1231
|
+
const issuer = identity.issuer ?? getIssuer(env2);
|
|
1232
|
+
let redirectUri = identity.redirectUri;
|
|
1233
|
+
if (!redirectUri) {
|
|
1234
|
+
const basePath = url.pathname.replace(ROUTES.IDENTITY.CALLBACK, "");
|
|
1235
|
+
redirectUri = `${url.origin}${basePath}${ROUTES.IDENTITY.CALLBACK}`;
|
|
1236
|
+
}
|
|
1237
|
+
ssoLog.debug("Exchanging auth code for tokens", { issuer, clientId: identity.clientId });
|
|
1238
|
+
const tokens = await exchangeCodeForTokens({
|
|
1239
|
+
issuer,
|
|
1240
|
+
clientId: identity.clientId,
|
|
1241
|
+
clientSecret: identity.clientSecret,
|
|
1242
|
+
code,
|
|
1243
|
+
redirectUri
|
|
1244
|
+
});
|
|
1245
|
+
const userInfo = await getUserInfo({ issuer, accessToken: tokens.access_token });
|
|
1246
|
+
const identities = typeof userInfo.identities === "string" ? JSON.parse(userInfo.identities) : userInfo.identities;
|
|
1247
|
+
ssoLog.debug("SSO completed", { user: { ...userInfo, identities } });
|
|
1248
|
+
const ctx = {
|
|
1249
|
+
tokens,
|
|
1250
|
+
user: userInfo,
|
|
1251
|
+
state,
|
|
1252
|
+
req,
|
|
1253
|
+
redirect: redirectResponse,
|
|
1254
|
+
json: jsonResponse
|
|
1255
|
+
};
|
|
1256
|
+
return identity.onCallbackSuccess(ctx);
|
|
1257
|
+
} catch (err) {
|
|
1258
|
+
const error = err instanceof Error ? err : new Error("Unknown error");
|
|
1259
|
+
ssoLog.error("Token exchange failed", { error: error.message });
|
|
1260
|
+
if (identity.onCallbackError) {
|
|
1261
|
+
return identity.onCallbackError(buildErrorContext(error, undefined, state, req));
|
|
1262
|
+
}
|
|
1263
|
+
return jsonResponse({ error: error.message }, 500);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
function createIdentityHandlers(params) {
|
|
1267
|
+
const { env: env2, identity } = params;
|
|
1268
|
+
return {
|
|
1269
|
+
signIn: (req) => handleSignIn(req, env2, identity),
|
|
1270
|
+
callback: (req) => handleCallback(req, env2, identity),
|
|
1271
|
+
signOut: () => redirectResponse("/")
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
// src/server/handlers/activity.ts
|
|
1275
|
+
import * as z7 from "zod";
|
|
1276
|
+
var activityMetricsSchema = z7.object({
|
|
1277
|
+
totalQuestions: z7.number().int().nonnegative().optional(),
|
|
1278
|
+
correctQuestions: z7.number().int().nonnegative().optional(),
|
|
1279
|
+
xpEarned: z7.number().int().nonnegative().optional(),
|
|
1280
|
+
masteredUnits: z7.number().int().nonnegative().optional()
|
|
1281
|
+
});
|
|
1282
|
+
var activityEndPayloadSchema = z7.object({
|
|
1283
|
+
id: z7.string().min(1),
|
|
1284
|
+
name: z7.string().min(1),
|
|
1285
|
+
courseCode: z7.string().min(1),
|
|
1286
|
+
startedAt: z7.iso.datetime(),
|
|
1287
|
+
endedAt: z7.iso.datetime(),
|
|
1288
|
+
elapsedMs: z7.number().int().nonnegative(),
|
|
1289
|
+
pausedMs: z7.number().int().nonnegative(),
|
|
1290
|
+
metrics: activityMetricsSchema
|
|
1291
|
+
});
|
|
1292
|
+
function createActivityHandler(config2) {
|
|
1293
|
+
return async (req) => {
|
|
1294
|
+
try {
|
|
1295
|
+
const user = await config2.identity.getUser(req);
|
|
1296
|
+
if (!user) {
|
|
1297
|
+
return jsonResponse({ success: false, error: "Unauthorized" }, 401);
|
|
1298
|
+
}
|
|
1299
|
+
const body = await req.json();
|
|
1300
|
+
const parseResult = activityEndPayloadSchema.safeParse(body);
|
|
1301
|
+
if (!parseResult.success) {
|
|
1302
|
+
return jsonResponse({
|
|
1303
|
+
success: false,
|
|
1304
|
+
error: "Invalid payload",
|
|
1305
|
+
details: parseResult.error.flatten()
|
|
1306
|
+
}, 400);
|
|
1307
|
+
}
|
|
1308
|
+
const payload = parseResult.data;
|
|
1309
|
+
console.log("Activity received:", {
|
|
1310
|
+
user,
|
|
1311
|
+
payload,
|
|
1312
|
+
app: config2.appConfig
|
|
1313
|
+
});
|
|
1314
|
+
return jsonResponse({ success: true });
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1317
|
+
return jsonResponse({ success: false, error: message }, 500);
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
// src/server/handlers/user.ts
|
|
1322
|
+
function createUserHandler(config2) {
|
|
1323
|
+
return async (req) => {
|
|
1324
|
+
try {
|
|
1325
|
+
const user = await config2.identity.getUser(req);
|
|
1326
|
+
if (!user) {
|
|
1327
|
+
return jsonResponse({ error: "Unauthorized" }, 401);
|
|
1328
|
+
}
|
|
1329
|
+
const profile = {
|
|
1330
|
+
id: user.id,
|
|
1331
|
+
email: user.email,
|
|
1332
|
+
name: user.name
|
|
1333
|
+
};
|
|
1334
|
+
return jsonResponse(profile);
|
|
1335
|
+
} catch (error) {
|
|
1336
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1337
|
+
return jsonResponse({ error: message }, 500);
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
// src/server/timeback.ts
|
|
1342
|
+
async function createServer(config2) {
|
|
1343
|
+
const configResult = await loadConfig({ configPath: config2.configPath });
|
|
1344
|
+
if (!configResult.success) {
|
|
1345
|
+
throw new Error(`Failed to load timeback config: ${configResult.error}`);
|
|
1346
|
+
}
|
|
1347
|
+
const appConfig = configResult.config;
|
|
1348
|
+
const activity = createActivityHandler({
|
|
1349
|
+
env: config2.env,
|
|
1350
|
+
identity: config2.identity,
|
|
1351
|
+
appConfig: {
|
|
1352
|
+
name: appConfig.name,
|
|
1353
|
+
sensors: appConfig.sensors ?? []
|
|
1354
|
+
},
|
|
1355
|
+
apiCredentials: config2.api
|
|
1356
|
+
});
|
|
1357
|
+
const identity = createIdentityHandlers({
|
|
1358
|
+
env: config2.env,
|
|
1359
|
+
identity: config2.identity
|
|
1360
|
+
});
|
|
1361
|
+
const user = createUserHandler({
|
|
1362
|
+
env: config2.env,
|
|
1363
|
+
identity: config2.identity,
|
|
1364
|
+
apiCredentials: config2.api
|
|
1365
|
+
});
|
|
1366
|
+
return {
|
|
1367
|
+
config: config2,
|
|
1368
|
+
handle: {
|
|
1369
|
+
activity,
|
|
1370
|
+
identity,
|
|
1371
|
+
user: {
|
|
1372
|
+
me: user
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
12
1377
|
export {
|
|
13
|
-
|
|
14
|
-
noop,
|
|
15
|
-
no,
|
|
16
|
-
identity
|
|
1378
|
+
createServer
|
|
17
1379
|
};
|