@burdenoff/microfe-technospam 2026.1101.5 → 2026.1102.2
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/generated/wspace-types.d.ts +7468 -141
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/node_modules/@codemirror/autocomplete/dist/index.js +527 -0
- package/dist/node_modules/@codemirror/autocomplete/dist/index.js.map +1 -0
- package/dist/node_modules/@codemirror/commands/dist/index.js +1108 -0
- package/dist/node_modules/@codemirror/commands/dist/index.js.map +1 -0
- package/dist/node_modules/@codemirror/lang-go/dist/index.js +206 -0
- package/dist/node_modules/@codemirror/lang-go/dist/index.js.map +1 -0
- package/dist/node_modules/@codemirror/lang-javascript/dist/index.js +321 -0
- package/dist/node_modules/@codemirror/lang-javascript/dist/index.js.map +1 -0
- package/dist/node_modules/@codemirror/lang-python/dist/index.js +265 -0
- package/dist/node_modules/@codemirror/lang-python/dist/index.js.map +1 -0
- package/dist/node_modules/@codemirror/language/dist/index.js +957 -0
- package/dist/node_modules/@codemirror/language/dist/index.js.map +1 -0
- package/dist/node_modules/@codemirror/legacy-modes/mode/ruby.js +164 -0
- package/dist/node_modules/@codemirror/legacy-modes/mode/ruby.js.map +1 -0
- package/dist/node_modules/@codemirror/legacy-modes/mode/shell.js +109 -0
- package/dist/node_modules/@codemirror/legacy-modes/mode/shell.js.map +1 -0
- package/dist/node_modules/@codemirror/state/dist/index.js +1739 -0
- package/dist/node_modules/@codemirror/state/dist/index.js.map +1 -0
- package/dist/node_modules/@codemirror/streamparser/dist/index.js +70 -0
- package/dist/node_modules/@codemirror/streamparser/dist/index.js.map +1 -0
- package/dist/node_modules/@codemirror/view/dist/index.js +5427 -0
- package/dist/node_modules/@codemirror/view/dist/index.js.map +1 -0
- package/dist/node_modules/@lezer/common/dist/index.js +896 -0
- package/dist/node_modules/@lezer/common/dist/index.js.map +1 -0
- package/dist/node_modules/@lezer/go/dist/index.js +215 -0
- package/dist/node_modules/@lezer/go/dist/index.js.map +1 -0
- package/dist/node_modules/@lezer/highlight/dist/index.js +395 -0
- package/dist/node_modules/@lezer/highlight/dist/index.js.map +1 -0
- package/dist/node_modules/@lezer/javascript/dist/index.js +437 -0
- package/dist/node_modules/@lezer/javascript/dist/index.js.map +1 -0
- package/dist/node_modules/@lezer/lr/dist/index.js +842 -0
- package/dist/node_modules/@lezer/lr/dist/index.js.map +1 -0
- package/dist/node_modules/@lezer/python/dist/index.js +352 -0
- package/dist/node_modules/@lezer/python/dist/index.js.map +1 -0
- package/dist/node_modules/@marijn/find-cluster-break/src/index.js +66 -0
- package/dist/node_modules/@marijn/find-cluster-break/src/index.js.map +1 -0
- package/dist/node_modules/style-mod/src/style-mod.js +74 -0
- package/dist/node_modules/style-mod/src/style-mod.js.map +1 -0
- package/dist/node_modules/w3c-keyname/index.js +90 -0
- package/dist/node_modules/w3c-keyname/index.js.map +1 -0
- package/dist/stories/fixtures/technospamFixtures.d.ts.map +1 -1
- package/dist/stories/journeys/Problems.stories.d.ts.map +1 -1
- package/dist/technospam/api/technospamApi.d.ts +39 -0
- package/dist/technospam/api/technospamApi.d.ts.map +1 -1
- package/dist/technospam/api/technospamApi.js +120 -86
- package/dist/technospam/api/technospamApi.js.map +1 -1
- package/dist/technospam/components/CertificateVerifyLink.d.ts +11 -0
- package/dist/technospam/components/CertificateVerifyLink.d.ts.map +1 -0
- package/dist/technospam/components/CertificateVerifyLink.js +63 -0
- package/dist/technospam/components/CertificateVerifyLink.js.map +1 -0
- package/dist/technospam/components/SubmissionViews.d.ts +6 -2
- package/dist/technospam/components/SubmissionViews.d.ts.map +1 -1
- package/dist/technospam/components/SubmissionViews.js +6 -5
- package/dist/technospam/components/SubmissionViews.js.map +1 -1
- package/dist/technospam/components/TechnoSpamErrorBoundary.d.ts +6 -0
- package/dist/technospam/components/TechnoSpamErrorBoundary.d.ts.map +1 -1
- package/dist/technospam/components/TechnoSpamErrorBoundary.js +2 -2
- package/dist/technospam/components/TechnoSpamErrorBoundary.js.map +1 -1
- package/dist/technospam/components/editor/CodeEditor.d.ts +26 -0
- package/dist/technospam/components/editor/CodeEditor.d.ts.map +1 -0
- package/dist/technospam/components/editor/CodeEditor.js +102 -0
- package/dist/technospam/components/editor/CodeEditor.js.map +1 -0
- package/dist/technospam/components/editor/RunOutputPanel.d.ts +14 -0
- package/dist/technospam/components/editor/RunOutputPanel.d.ts.map +1 -0
- package/dist/technospam/components/editor/RunOutputPanel.js +127 -0
- package/dist/technospam/components/editor/RunOutputPanel.js.map +1 -0
- package/dist/technospam/components/editor/editorLanguages.d.ts +11 -0
- package/dist/technospam/components/editor/editorLanguages.d.ts.map +1 -0
- package/dist/technospam/components/editor/editorLanguages.js +30 -0
- package/dist/technospam/components/editor/editorLanguages.js.map +1 -0
- package/dist/technospam/components/editor/editorTheme.d.ts +11 -0
- package/dist/technospam/components/editor/editorTheme.d.ts.map +1 -0
- package/dist/technospam/components/editor/editorTheme.js +120 -0
- package/dist/technospam/components/editor/editorTheme.js.map +1 -0
- package/dist/technospam/components/index.d.ts +1 -0
- package/dist/technospam/components/index.d.ts.map +1 -1
- package/dist/technospam/components/index.js +1 -0
- package/dist/technospam/hooks/useSolutionRunner.d.ts +35 -0
- package/dist/technospam/hooks/useSolutionRunner.d.ts.map +1 -0
- package/dist/technospam/hooks/useSolutionRunner.js +66 -0
- package/dist/technospam/hooks/useSolutionRunner.js.map +1 -0
- package/dist/technospam/hooks/useTechnoSpamAnalytics.d.ts +1 -1
- package/dist/technospam/hooks/useTechnoSpamAnalytics.d.ts.map +1 -1
- package/dist/technospam/hooks/useTechnoSpamAnalytics.js.map +1 -1
- package/dist/technospam/index.d.ts +2 -0
- package/dist/technospam/index.d.ts.map +1 -1
- package/dist/technospam/index.js +2 -0
- package/dist/technospam/lib/certificateVerify.d.ts +71 -0
- package/dist/technospam/lib/certificateVerify.d.ts.map +1 -0
- package/dist/technospam/lib/certificateVerify.js +55 -0
- package/dist/technospam/lib/certificateVerify.js.map +1 -0
- package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js +416 -263
- package/dist/technospam/pages/ApplicationsProblemDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +236 -231
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.js +104 -96
- package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
- package/dist/technospam/pages/public/CertificateVerifyPage.d.ts +4 -0
- package/dist/technospam/pages/public/CertificateVerifyPage.d.ts.map +1 -0
- package/dist/technospam/pages/public/CertificateVerifyPage.js +257 -0
- package/dist/technospam/pages/public/CertificateVerifyPage.js.map +1 -0
- package/package.json +14 -4
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { userMessageFromError as e } from "../lib/errorMessage.js";
|
|
2
|
+
import { useRunSolution as t } from "../api/technospamApi.js";
|
|
3
|
+
import { useCallback as n, useEffect as r, useRef as i, useState as a } from "react";
|
|
4
|
+
//#region src/technospam/hooks/useSolutionRunner.ts
|
|
5
|
+
var o = 4e3;
|
|
6
|
+
function s() {
|
|
7
|
+
let [s] = t(), [c, l] = a(null), [u, d] = a(!1), [f, p] = a(!1), [m, h] = a(null), g = i(null), _ = i(null), v = i(0), y = () => {
|
|
8
|
+
g.current && clearTimeout(g.current), g.current = null;
|
|
9
|
+
};
|
|
10
|
+
r(() => () => {
|
|
11
|
+
y(), v.current += 1;
|
|
12
|
+
}, []);
|
|
13
|
+
let b = n((t, n) => {
|
|
14
|
+
let r = async () => {
|
|
15
|
+
d(!0);
|
|
16
|
+
try {
|
|
17
|
+
let i = await s({ variables: { input: {
|
|
18
|
+
problemId: t.problemId,
|
|
19
|
+
language: t.language,
|
|
20
|
+
code: t.code,
|
|
21
|
+
...t.stdin == null ? {} : { stdin: t.stdin }
|
|
22
|
+
} } });
|
|
23
|
+
if (n !== v.current) return;
|
|
24
|
+
let a = i.data?.tecRunSolution;
|
|
25
|
+
if (!a) {
|
|
26
|
+
p(!1), _.current = null, l(null), h(e(i.error, "Your code could not be run. Please try again."));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (a.status === "WARMING") {
|
|
30
|
+
if (_.current ??= Date.now(), Date.now() - _.current < 24e4) {
|
|
31
|
+
p(!0), g.current = setTimeout(() => void r(), o);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
p(!1), _.current = null, l({
|
|
35
|
+
...a,
|
|
36
|
+
status: "UNAVAILABLE"
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
p(!1), _.current = null, l(a);
|
|
41
|
+
} catch (t) {
|
|
42
|
+
if (n !== v.current) return;
|
|
43
|
+
p(!1), _.current = null, l(null), h(e(t, "Your code could not be run. Please try again."));
|
|
44
|
+
} finally {
|
|
45
|
+
n === v.current && d(!1);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
return r();
|
|
49
|
+
}, [s]);
|
|
50
|
+
return {
|
|
51
|
+
run: c,
|
|
52
|
+
running: u,
|
|
53
|
+
warming: f,
|
|
54
|
+
error: m,
|
|
55
|
+
execute: n((e) => {
|
|
56
|
+
y(), v.current += 1, _.current = null, h(null), b(e, v.current);
|
|
57
|
+
}, [b]),
|
|
58
|
+
reset: n(() => {
|
|
59
|
+
y(), v.current += 1, _.current = null, l(null), h(null), p(!1), d(!1);
|
|
60
|
+
}, [])
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { s as useSolutionRunner };
|
|
65
|
+
|
|
66
|
+
//# sourceMappingURL=useSolutionRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSolutionRunner.js","names":[],"sources":["../../../src/technospam/hooks/useSolutionRunner.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useRunSolution, type TecSolutionRun } from '../api/technospamApi';\nimport { userMessageFromError } from '../lib/errorMessage';\n\n/**\n * A cold code sandbox can take a few minutes to start. While the API answers\n * WARMING the run is retried on this interval, giving up after the budget so\n * the learner can retry by hand.\n */\nexport const RUN_WARMING_RETRY_MS = 4_000;\nexport const RUN_WARMING_BUDGET_MS = 4 * 60_000;\n\nexport interface SolutionRunRequest {\n problemId: string;\n language: string;\n code: string;\n /** Custom stdin; omit to run the sample cases. */\n stdin?: string | null;\n}\n\nexport interface SolutionRunner {\n /** The latest finished (or given-up) run. */\n run: TecSolutionRun | null;\n /** A request is in flight. */\n running: boolean;\n /** The sandbox is starting; the run is being retried automatically. */\n warming: boolean;\n /** Why the last run was refused (quota, bad input…), shown inline. */\n error: string | null;\n execute: (request: SolutionRunRequest) => void;\n reset: () => void;\n}\n\n/**\n * Drives `tecRunSolution` for the problem editor (BOFF-4078): sends the run,\n * keeps retrying while the sandbox warms up, and exposes the result. Starting\n * a new run cancels any pending warm-up retry.\n */\nexport function useSolutionRunner(): SolutionRunner {\n const [runSolution] = useRunSolution();\n const [run, setRun] = useState<TecSolutionRun | null>(null);\n const [running, setRunning] = useState(false);\n const [warming, setWarming] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const warmingSince = useRef<number | null>(null);\n /** Bumped per user-initiated run so a stale retry never overwrites a newer run. */\n const generation = useRef(0);\n\n const clearTimer = () => {\n if (timer.current) clearTimeout(timer.current);\n timer.current = null;\n };\n\n // On unmount, cancel any pending warm-up retry and invalidate the in-flight\n // request so a late WARMING answer cannot schedule another run after the\n // learner has left the page.\n useEffect(\n () => () => {\n clearTimer();\n generation.current += 1;\n },\n []\n );\n\n const attempt = useCallback(\n (request: SolutionRunRequest, gen: number): Promise<void> => {\n // A local recursive function keeps the warm-up retry inside one closure,\n // so the scheduled retry never references the hook binding itself.\n const go = async (): Promise<void> => {\n setRunning(true);\n try {\n const res = await runSolution({\n variables: {\n input: {\n problemId: request.problemId,\n language: request.language,\n code: request.code,\n ...(request.stdin != null ? { stdin: request.stdin } : {}),\n },\n },\n });\n if (gen !== generation.current) return;\n const result = res.data?.tecRunSolution;\n if (!result) {\n setWarming(false);\n warmingSince.current = null;\n setRun(null);\n setError(\n userMessageFromError(res.error, 'Your code could not be run. Please try again.')\n );\n return;\n }\n if (result.status === 'WARMING') {\n warmingSince.current ??= Date.now();\n if (Date.now() - warmingSince.current < RUN_WARMING_BUDGET_MS) {\n setWarming(true);\n timer.current = setTimeout(() => void go(), RUN_WARMING_RETRY_MS);\n return;\n }\n setWarming(false);\n warmingSince.current = null;\n setRun({ ...result, status: 'UNAVAILABLE' });\n return;\n }\n setWarming(false);\n warmingSince.current = null;\n setRun(result);\n } catch (err: unknown) {\n // Apollo reports GraphQL/network failures via res.error, but anything\n // unexpected must still end the warming state instead of spinning forever.\n if (gen !== generation.current) return;\n setWarming(false);\n warmingSince.current = null;\n setRun(null);\n setError(userMessageFromError(err, 'Your code could not be run. Please try again.'));\n } finally {\n if (gen === generation.current) setRunning(false);\n }\n };\n return go();\n },\n [runSolution]\n );\n\n const execute = useCallback(\n (request: SolutionRunRequest) => {\n clearTimer();\n generation.current += 1;\n warmingSince.current = null;\n setError(null);\n void attempt(request, generation.current);\n },\n [attempt]\n );\n\n const reset = useCallback(() => {\n clearTimer();\n generation.current += 1;\n warmingSince.current = null;\n setRun(null);\n setError(null);\n setWarming(false);\n setRunning(false);\n }, []);\n\n return { run, running, warming, error, execute, reset };\n}\n"],"mappings":";;;;AASA,IAAa,IAAuB;AA6BpC,SAAgB,IAAoC;CAClD,IAAM,CAAC,KAAe,GAAgB,EAChC,CAAC,GAAK,KAAU,EAAgC,KAAK,EACrD,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAQ,EAA6C,KAAK,EAC1D,IAAe,EAAsB,KAAK,EAE1C,IAAa,EAAO,EAAE,EAEtB,UAAmB;AAEvB,EADI,EAAM,WAAS,aAAa,EAAM,QAAQ,EAC9C,EAAM,UAAU;;AAMlB,eACc;AAEV,EADA,GAAY,EACZ,EAAW,WAAW;IAExB,EAAE,CACH;CAED,IAAM,IAAU,GACb,GAA6B,MAA+B;EAG3D,IAAM,IAAK,YAA2B;AACpC,KAAW,GAAK;AAChB,OAAI;IACF,IAAM,IAAM,MAAM,EAAY,EAC5B,WAAW,EACT,OAAO;KACL,WAAW,EAAQ;KACnB,UAAU,EAAQ;KAClB,MAAM,EAAQ;KACd,GAAI,EAAQ,SAAS,OAAkC,EAAE,GAA7B,EAAE,OAAO,EAAQ,OAAO;KACrD,EACF,EACF,CAAC;AACF,QAAI,MAAQ,EAAW,QAAS;IAChC,IAAM,IAAS,EAAI,MAAM;AACzB,QAAI,CAAC,GAAQ;AAIX,KAHA,EAAW,GAAM,EACjB,EAAa,UAAU,MACvB,EAAO,KAAK,EACZ,EACE,EAAqB,EAAI,OAAO,gDAAgD,CACjF;AACD;;AAEF,QAAI,EAAO,WAAW,WAAW;AAE/B,SADA,EAAa,YAAY,KAAK,KAAK,EAC/B,KAAK,KAAK,GAAG,EAAa,UAAA,MAAiC;AAE7D,MADA,EAAW,GAAK,EAChB,EAAM,UAAU,iBAAiB,KAAK,GAAI,EAAE,EAAqB;AACjE;;AAIF,KAFA,EAAW,GAAM,EACjB,EAAa,UAAU,MACvB,EAAO;MAAE,GAAG;MAAQ,QAAQ;MAAe,CAAC;AAC5C;;AAIF,IAFA,EAAW,GAAM,EACjB,EAAa,UAAU,MACvB,EAAO,EAAO;YACP,GAAc;AAGrB,QAAI,MAAQ,EAAW,QAAS;AAIhC,IAHA,EAAW,GAAM,EACjB,EAAa,UAAU,MACvB,EAAO,KAAK,EACZ,EAAS,EAAqB,GAAK,gDAAgD,CAAC;aAC5E;AACR,IAAI,MAAQ,EAAW,WAAS,EAAW,GAAM;;;AAGrD,SAAO,GAAI;IAEb,CAAC,EAAY,CACd;AAuBD,QAAO;EAAE;EAAK;EAAS;EAAS;EAAO,SArBvB,GACb,MAAgC;AAK1B,GAJL,GAAY,EACZ,EAAW,WAAW,GACtB,EAAa,UAAU,MACvB,EAAS,KAAK,EACT,EAAQ,GAAS,EAAW,QAAQ;KAE3C,CAAC,EAAQ,CACV;EAY+C,OAVlC,QAAkB;AAO9B,GANA,GAAY,EACZ,EAAW,WAAW,GACtB,EAAa,UAAU,MACvB,EAAO,KAAK,EACZ,EAAS,KAAK,EACd,EAAW,GAAM,EACjB,EAAW,GAAM;KAChB,EAAE,CAAC;EAEiD"}
|
|
@@ -13,7 +13,7 @@ export interface EntityEventPayload {
|
|
|
13
13
|
entityId?: string;
|
|
14
14
|
entityType?: string;
|
|
15
15
|
}
|
|
16
|
-
export type TechnoSpamEventName = 'technospam.page.viewed' | 'technospam.course.viewed' | 'technospam.course.enroll_clicked' | 'technospam.course.enrolled' | 'technospam.course.progress_updated' | 'technospam.course.certificate_claimed' | 'technospam.lesson.viewed' | 'technospam.lesson.completed' | 'technospam.problem.viewed' | 'technospam.problem.submit_clicked' | 'technospam.problem.submitted' | 'technospam.mentor.viewed' | 'technospam.mentor.book_clicked' | 'technospam.mentor.booked' | 'technospam.mentor.become_mentor_clicked' | 'technospam.mentor.became_mentor' | 'technospam.session.cancel_clicked' | 'technospam.session.cancelled' | 'technospam.session.complete_clicked' | 'technospam.session.completed' | 'technospam.hackathon.viewed' | 'technospam.hackathon.register_team_clicked' | 'technospam.hackathon.team_registered' | 'technospam.hackathon.submit_project_clicked' | 'technospam.hackathon.project_submitted' | 'technospam.search.performed';
|
|
16
|
+
export type TechnoSpamEventName = 'technospam.page.viewed' | 'technospam.course.viewed' | 'technospam.course.enroll_clicked' | 'technospam.course.enrolled' | 'technospam.course.progress_updated' | 'technospam.course.certificate_claimed' | 'technospam.lesson.viewed' | 'technospam.lesson.completed' | 'technospam.problem.viewed' | 'technospam.problem.run_clicked' | 'technospam.problem.submit_clicked' | 'technospam.problem.submitted' | 'technospam.mentor.viewed' | 'technospam.mentor.book_clicked' | 'technospam.mentor.booked' | 'technospam.mentor.become_mentor_clicked' | 'technospam.mentor.became_mentor' | 'technospam.session.cancel_clicked' | 'technospam.session.cancelled' | 'technospam.session.complete_clicked' | 'technospam.session.completed' | 'technospam.hackathon.viewed' | 'technospam.hackathon.register_team_clicked' | 'technospam.hackathon.team_registered' | 'technospam.hackathon.submit_project_clicked' | 'technospam.hackathon.project_submitted' | 'technospam.search.performed';
|
|
17
17
|
export declare function useTechnoSpamAnalytics(): {
|
|
18
18
|
emit: (name: TechnoSpamEventName, payload?: EntityEventPayload & Record<string, unknown>) => void;
|
|
19
19
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTechnoSpamAnalytics.d.ts","sourceRoot":"","sources":["../../../src/technospam/hooks/useTechnoSpamAnalytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAC3B,wBAAwB,GACxB,0BAA0B,GAC1B,kCAAkC,GAClC,4BAA4B,GAC5B,oCAAoC,GACpC,uCAAuC,GACvC,0BAA0B,GAC1B,6BAA6B,GAC7B,2BAA2B,GAC3B,mCAAmC,GACnC,8BAA8B,GAC9B,0BAA0B,GAC1B,gCAAgC,GAChC,0BAA0B,GAC1B,yCAAyC,GACzC,iCAAiC,GACjC,mCAAmC,GACnC,8BAA8B,GAC9B,qCAAqC,GACrC,8BAA8B,GAC9B,6BAA6B,GAC7B,4CAA4C,GAC5C,sCAAsC,GACtC,6CAA6C,GAC7C,wCAAwC,GACxC,6BAA6B,CAAC;AAElC,wBAAgB,sBAAsB;iBAI3B,mBAAmB,YAAW,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;EAOpF"}
|
|
1
|
+
{"version":3,"file":"useTechnoSpamAnalytics.d.ts","sourceRoot":"","sources":["../../../src/technospam/hooks/useTechnoSpamAnalytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAC3B,wBAAwB,GACxB,0BAA0B,GAC1B,kCAAkC,GAClC,4BAA4B,GAC5B,oCAAoC,GACpC,uCAAuC,GACvC,0BAA0B,GAC1B,6BAA6B,GAC7B,2BAA2B,GAC3B,gCAAgC,GAChC,mCAAmC,GACnC,8BAA8B,GAC9B,0BAA0B,GAC1B,gCAAgC,GAChC,0BAA0B,GAC1B,yCAAyC,GACzC,iCAAiC,GACjC,mCAAmC,GACnC,8BAA8B,GAC9B,qCAAqC,GACrC,8BAA8B,GAC9B,6BAA6B,GAC7B,4CAA4C,GAC5C,sCAAsC,GACtC,6CAA6C,GAC7C,wCAAwC,GACxC,6BAA6B,CAAC;AAElC,wBAAgB,sBAAsB;iBAI3B,mBAAmB,YAAW,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;EAOpF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTechnoSpamAnalytics.js","names":[],"sources":["../../../src/technospam/hooks/useTechnoSpamAnalytics.ts"],"sourcesContent":["/**\n * TechnoSpam product analytics helper.\n *\n * Emits events on the shared event bus. The shell's RybbitSink forwards\n * allow-listed fact events to Rybbit Cloud; until TechnoSpam has a site ID,\n * events still flow to the bus and to backend audit / telemetry sinks.\n *\n * Pattern: emit an intent event before a mutation, then a fact event when the\n * server confirms the change.\n */\n\nimport { useCallback } from 'react';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\n\nexport interface EntityEventPayload {\n workspaceId?: string;\n entityId?: string;\n entityType?: string;\n}\n\nexport type TechnoSpamEventName =\n | 'technospam.page.viewed'\n | 'technospam.course.viewed'\n | 'technospam.course.enroll_clicked'\n | 'technospam.course.enrolled'\n | 'technospam.course.progress_updated'\n | 'technospam.course.certificate_claimed'\n | 'technospam.lesson.viewed'\n | 'technospam.lesson.completed'\n | 'technospam.problem.viewed'\n | 'technospam.problem.submit_clicked'\n | 'technospam.problem.submitted'\n | 'technospam.mentor.viewed'\n | 'technospam.mentor.book_clicked'\n | 'technospam.mentor.booked'\n | 'technospam.mentor.become_mentor_clicked'\n | 'technospam.mentor.became_mentor'\n | 'technospam.session.cancel_clicked'\n | 'technospam.session.cancelled'\n | 'technospam.session.complete_clicked'\n | 'technospam.session.completed'\n | 'technospam.hackathon.viewed'\n | 'technospam.hackathon.register_team_clicked'\n | 'technospam.hackathon.team_registered'\n | 'technospam.hackathon.submit_project_clicked'\n | 'technospam.hackathon.project_submitted'\n | 'technospam.search.performed';\n\nexport function useTechnoSpamAnalytics() {\n const bus = useEventBus();\n\n const emit = useCallback(\n (name: TechnoSpamEventName, payload: EntityEventPayload & Record<string, unknown> = {}) => {\n bus.emitCustom(name, payload, { component: 'microfe-technospam' });\n },\n [bus]\n );\n\n return { emit };\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"useTechnoSpamAnalytics.js","names":[],"sources":["../../../src/technospam/hooks/useTechnoSpamAnalytics.ts"],"sourcesContent":["/**\n * TechnoSpam product analytics helper.\n *\n * Emits events on the shared event bus. The shell's RybbitSink forwards\n * allow-listed fact events to Rybbit Cloud; until TechnoSpam has a site ID,\n * events still flow to the bus and to backend audit / telemetry sinks.\n *\n * Pattern: emit an intent event before a mutation, then a fact event when the\n * server confirms the change.\n */\n\nimport { useCallback } from 'react';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\n\nexport interface EntityEventPayload {\n workspaceId?: string;\n entityId?: string;\n entityType?: string;\n}\n\nexport type TechnoSpamEventName =\n | 'technospam.page.viewed'\n | 'technospam.course.viewed'\n | 'technospam.course.enroll_clicked'\n | 'technospam.course.enrolled'\n | 'technospam.course.progress_updated'\n | 'technospam.course.certificate_claimed'\n | 'technospam.lesson.viewed'\n | 'technospam.lesson.completed'\n | 'technospam.problem.viewed'\n | 'technospam.problem.run_clicked'\n | 'technospam.problem.submit_clicked'\n | 'technospam.problem.submitted'\n | 'technospam.mentor.viewed'\n | 'technospam.mentor.book_clicked'\n | 'technospam.mentor.booked'\n | 'technospam.mentor.become_mentor_clicked'\n | 'technospam.mentor.became_mentor'\n | 'technospam.session.cancel_clicked'\n | 'technospam.session.cancelled'\n | 'technospam.session.complete_clicked'\n | 'technospam.session.completed'\n | 'technospam.hackathon.viewed'\n | 'technospam.hackathon.register_team_clicked'\n | 'technospam.hackathon.team_registered'\n | 'technospam.hackathon.submit_project_clicked'\n | 'technospam.hackathon.project_submitted'\n | 'technospam.search.performed';\n\nexport function useTechnoSpamAnalytics() {\n const bus = useEventBus();\n\n const emit = useCallback(\n (name: TechnoSpamEventName, payload: EntityEventPayload & Record<string, unknown> = {}) => {\n bus.emitCustom(name, payload, { component: 'microfe-technospam' });\n },\n [bus]\n );\n\n return { emit };\n}\n"],"mappings":";;;AAiDA,SAAgB,IAAyB;CACvC,IAAM,IAAM,GAAa;AASzB,QAAO,EAAE,MAPI,GACV,GAA2B,IAAwD,EAAE,KAAK;AACzF,IAAI,WAAW,GAAM,GAAS,EAAE,WAAW,sBAAsB,CAAC;IAEpE,CAAC,EAAI,CACN,EAEc"}
|
|
@@ -4,4 +4,6 @@ export { useTechnoSpam, useTechnoSpamNavigate } from './providers/TechnoSpamProv
|
|
|
4
4
|
export * from './services/createSandboxAssistantTransport';
|
|
5
5
|
export { useTr } from './hooks/useTr';
|
|
6
6
|
export { useTechnoSpamAnalytics } from './hooks/useTechnoSpamAnalytics';
|
|
7
|
+
export { CertificateVerifyPage } from './pages/public/CertificateVerifyPage';
|
|
8
|
+
export { CERTIFICATE_VERIFY_BASE_PATH, CERTIFICATE_VERIFY_ROUTE_PATH, certificateVerifyLink, certificateVerifyPath, } from './lib/certificateVerify';
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/technospam/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAOtF,cAAc,4CAA4C,CAAC;AAG3D,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/technospam/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAOtF,cAAc,4CAA4C,CAAC;AAG3D,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAIxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,EAC7B,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,yBAAyB,CAAC"}
|
package/dist/technospam/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import "./providers/TechnoSpamProvider.js";
|
|
2
2
|
import "./hooks/useTechnoSpamAnalytics.js";
|
|
3
3
|
import "./hooks/useTr.js";
|
|
4
|
+
import "./lib/certificateVerify.js";
|
|
4
5
|
import "./TechnoSpamRoot.js";
|
|
5
6
|
import "./services/createSandboxAssistantTransport.js";
|
|
7
|
+
import "./pages/public/CertificateVerifyPage.js";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public certificate verification page helpers (BOFF-10576).
|
|
3
|
+
*
|
|
4
|
+
* The page lets a recruiter open the link printed on a TechnoSpam certificate
|
|
5
|
+
* and confirm it without a Burdenoff account. It calls the public
|
|
6
|
+
* `tecVerifyCertificate` field (`@rbac(public: true)`, BOFF-4430), which
|
|
7
|
+
* returns the course, score, dates and status but never the learner's
|
|
8
|
+
* identity or a revoke reason.
|
|
9
|
+
*
|
|
10
|
+
* Unlike the route constants that are relative to this MFE's `basePath`, the
|
|
11
|
+
* verification route is absolute: the host shell mounts the page on its own
|
|
12
|
+
* top-level router, outside the auth guard. It lives under `/public/` because
|
|
13
|
+
* fe-libs' shell-route table already treats every `/public/*` path as a
|
|
14
|
+
* public, sidebar-less page.
|
|
15
|
+
*/
|
|
16
|
+
/** Base path of the public verification page. */
|
|
17
|
+
export declare const CERTIFICATE_VERIFY_BASE_PATH = "/public/verify";
|
|
18
|
+
/** For the host's `<Route path={CERTIFICATE_VERIFY_ROUTE_PATH} ... />`. */
|
|
19
|
+
export declare const CERTIFICATE_VERIFY_ROUTE_PATH = "/public/verify/:serial";
|
|
20
|
+
/** Mirrors the service's MAX_SERIAL_LENGTH guard; longer input is never sent. */
|
|
21
|
+
export declare const MAX_SERIAL_LENGTH = 120;
|
|
22
|
+
export type CertificateVerificationStatus = 'VALID' | 'REVOKED' | 'NOT_FOUND';
|
|
23
|
+
export interface CertificateVerification {
|
|
24
|
+
serial: string;
|
|
25
|
+
status: CertificateVerificationStatus;
|
|
26
|
+
valid: boolean;
|
|
27
|
+
issuer: string;
|
|
28
|
+
courseTitle: string | null;
|
|
29
|
+
courseLevel: string | null;
|
|
30
|
+
score: number | null;
|
|
31
|
+
issuedAt: string | null;
|
|
32
|
+
revokedAt: string | null;
|
|
33
|
+
verifiedAt: string;
|
|
34
|
+
}
|
|
35
|
+
export type VerifyOutcome = {
|
|
36
|
+
kind: 'result';
|
|
37
|
+
verification: CertificateVerification;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'invalid';
|
|
40
|
+
} | {
|
|
41
|
+
kind: 'error';
|
|
42
|
+
};
|
|
43
|
+
/** Normalize a serial the way the service does: trim and upper-case. */
|
|
44
|
+
export declare function normalizeSerial(raw: string | null | undefined): string;
|
|
45
|
+
/** True when a normalized serial is worth sending to the service at all. */
|
|
46
|
+
export declare function isPlausibleSerial(serial: string): boolean;
|
|
47
|
+
/** Relative path of the verification page for one serial. */
|
|
48
|
+
export declare function certificateVerifyPath(serial: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* The full, shareable verification link for a serial. Defaults to the current
|
|
51
|
+
* origin, which is the product's app shell (for example
|
|
52
|
+
* `https://app.technospam.com`).
|
|
53
|
+
*/
|
|
54
|
+
export declare function certificateVerifyLink(serial: string, origin?: string): string;
|
|
55
|
+
export declare const VERIFY_QUERY = "\n query PublicTecVerifyCertificate($serial: String!) {\n tecVerifyCertificate(serial: $serial) {\n serial\n status\n valid\n issuer\n courseTitle\n courseLevel\n score\n issuedAt\n revokedAt\n verifiedAt\n }\n }\n";
|
|
56
|
+
/**
|
|
57
|
+
* The GraphQL endpoint for a gateway URL. Hosts pass either the workspace
|
|
58
|
+
* gateway origin (the app shell's `workspaceBaseUrl`) or a complete GraphQL
|
|
59
|
+
* endpoint (the dev harness and stories); a URL that already ends in
|
|
60
|
+
* `/graphql` is used as is.
|
|
61
|
+
*/
|
|
62
|
+
export declare function verificationEndpoint(apiGatewayUrl: string | undefined): string;
|
|
63
|
+
type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
|
|
64
|
+
/**
|
|
65
|
+
* Verify one serial against the public workspace gateway. The request is
|
|
66
|
+
* deliberately anonymous: no Authorization header and no cookies, so the
|
|
67
|
+
* viewer's own session (if any) is never attached to a public lookup.
|
|
68
|
+
*/
|
|
69
|
+
export declare function fetchCertificateVerification(apiGatewayUrl: string | undefined, rawSerial: string, fetchImpl?: FetchLike): Promise<VerifyOutcome>;
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=certificateVerify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certificateVerify.d.ts","sourceRoot":"","sources":["../../../src/technospam/lib/certificateVerify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,iDAAiD;AACjD,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAE7D,2EAA2E;AAC3E,eAAO,MAAM,6BAA6B,2BAA4C,CAAC;AAEvF,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,MAAM,MAAM,6BAA6B,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;AAE9E,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,6BAA6B,CAAC;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,YAAY,EAAE,uBAAuB,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAEtE;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED,6DAA6D;AAC7D,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,eAAO,MAAM,YAAY,qRAexB,CAAC;AAgBF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG9E;AAED,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEzE;;;;GAIG;AACH,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,SAA+C,GACzD,OAAO,CAAC,aAAa,CAAC,CAuBxB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//#region src/technospam/lib/certificateVerify.ts
|
|
2
|
+
var e = "/public/verify", t = `${e}/:serial`;
|
|
3
|
+
function n(e) {
|
|
4
|
+
return (e ?? "").trim().toUpperCase();
|
|
5
|
+
}
|
|
6
|
+
function r(e) {
|
|
7
|
+
return e.length > 0 && e.length <= 120 && /^[A-Z0-9][A-Z0-9._-]*$/.test(e);
|
|
8
|
+
}
|
|
9
|
+
function i(t) {
|
|
10
|
+
return `${e}/${encodeURIComponent(n(t))}`;
|
|
11
|
+
}
|
|
12
|
+
function a(e, t) {
|
|
13
|
+
return `${(t ?? (typeof window < "u" ? window.location.origin : "")).replace(/\/+$/, "")}${i(e)}`;
|
|
14
|
+
}
|
|
15
|
+
var o = "\n query PublicTecVerifyCertificate($serial: String!) {\n tecVerifyCertificate(serial: $serial) {\n serial\n status\n valid\n issuer\n courseTitle\n courseLevel\n score\n issuedAt\n revokedAt\n verifiedAt\n }\n }\n", s = new Set([
|
|
16
|
+
"VALID",
|
|
17
|
+
"REVOKED",
|
|
18
|
+
"NOT_FOUND"
|
|
19
|
+
]);
|
|
20
|
+
function c(e) {
|
|
21
|
+
if (!e || typeof e != "object") return !1;
|
|
22
|
+
let t = e;
|
|
23
|
+
return typeof t.serial == "string" && typeof t.status == "string" && s.has(t.status) && typeof t.valid == "boolean" && typeof t.verifiedAt == "string";
|
|
24
|
+
}
|
|
25
|
+
function l(e) {
|
|
26
|
+
let t = (e ?? "").replace(/\/+$/, "");
|
|
27
|
+
return /\/graphql$/.test(t) ? t : `${t}/workspaces/graphql`;
|
|
28
|
+
}
|
|
29
|
+
async function u(e, t, i = (e, t) => fetch(e, t)) {
|
|
30
|
+
let a = n(t);
|
|
31
|
+
if (!r(a)) return { kind: "invalid" };
|
|
32
|
+
let s = l(e), u;
|
|
33
|
+
try {
|
|
34
|
+
u = await i(s, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "Content-Type": "application/json" },
|
|
37
|
+
credentials: "omit",
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
query: o,
|
|
40
|
+
variables: { serial: a }
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
} catch {
|
|
44
|
+
return { kind: "error" };
|
|
45
|
+
}
|
|
46
|
+
let d = (await u.json().catch(() => null))?.data?.tecVerifyCertificate;
|
|
47
|
+
return !u.ok || !c(d) ? { kind: "error" } : {
|
|
48
|
+
kind: "result",
|
|
49
|
+
verification: d
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { e as CERTIFICATE_VERIFY_BASE_PATH, t as CERTIFICATE_VERIFY_ROUTE_PATH, a as certificateVerifyLink, i as certificateVerifyPath, u as fetchCertificateVerification, r as isPlausibleSerial, n as normalizeSerial };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=certificateVerify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certificateVerify.js","names":[],"sources":["../../../src/technospam/lib/certificateVerify.ts"],"sourcesContent":["/**\n * Public certificate verification page helpers (BOFF-10576).\n *\n * The page lets a recruiter open the link printed on a TechnoSpam certificate\n * and confirm it without a Burdenoff account. It calls the public\n * `tecVerifyCertificate` field (`@rbac(public: true)`, BOFF-4430), which\n * returns the course, score, dates and status but never the learner's\n * identity or a revoke reason.\n *\n * Unlike the route constants that are relative to this MFE's `basePath`, the\n * verification route is absolute: the host shell mounts the page on its own\n * top-level router, outside the auth guard. It lives under `/public/` because\n * fe-libs' shell-route table already treats every `/public/*` path as a\n * public, sidebar-less page.\n */\n\n/** Base path of the public verification page. */\nexport const CERTIFICATE_VERIFY_BASE_PATH = '/public/verify';\n\n/** For the host's `<Route path={CERTIFICATE_VERIFY_ROUTE_PATH} ... />`. */\nexport const CERTIFICATE_VERIFY_ROUTE_PATH = `${CERTIFICATE_VERIFY_BASE_PATH}/:serial`;\n\n/** Mirrors the service's MAX_SERIAL_LENGTH guard; longer input is never sent. */\nexport const MAX_SERIAL_LENGTH = 120;\n\nexport type CertificateVerificationStatus = 'VALID' | 'REVOKED' | 'NOT_FOUND';\n\nexport interface CertificateVerification {\n serial: string;\n status: CertificateVerificationStatus;\n valid: boolean;\n issuer: string;\n courseTitle: string | null;\n courseLevel: string | null;\n score: number | null;\n issuedAt: string | null;\n revokedAt: string | null;\n verifiedAt: string;\n}\n\nexport type VerifyOutcome =\n | { kind: 'result'; verification: CertificateVerification }\n | { kind: 'invalid' }\n | { kind: 'error' };\n\n/** Normalize a serial the way the service does: trim and upper-case. */\nexport function normalizeSerial(raw: string | null | undefined): string {\n return (raw ?? '').trim().toUpperCase();\n}\n\n/** True when a normalized serial is worth sending to the service at all. */\nexport function isPlausibleSerial(serial: string): boolean {\n return (\n serial.length > 0 && serial.length <= MAX_SERIAL_LENGTH && /^[A-Z0-9][A-Z0-9._-]*$/.test(serial)\n );\n}\n\n/** Relative path of the verification page for one serial. */\nexport function certificateVerifyPath(serial: string): string {\n return `${CERTIFICATE_VERIFY_BASE_PATH}/${encodeURIComponent(normalizeSerial(serial))}`;\n}\n\n/**\n * The full, shareable verification link for a serial. Defaults to the current\n * origin, which is the product's app shell (for example\n * `https://app.technospam.com`).\n */\nexport function certificateVerifyLink(serial: string, origin?: string): string {\n const base = origin ?? (typeof window !== 'undefined' ? window.location.origin : '');\n return `${base.replace(/\\/+$/, '')}${certificateVerifyPath(serial)}`;\n}\n\nexport const VERIFY_QUERY = `\n query PublicTecVerifyCertificate($serial: String!) {\n tecVerifyCertificate(serial: $serial) {\n serial\n status\n valid\n issuer\n courseTitle\n courseLevel\n score\n issuedAt\n revokedAt\n verifiedAt\n }\n }\n`;\n\nconst STATUSES: ReadonlySet<string> = new Set(['VALID', 'REVOKED', 'NOT_FOUND']);\n\nfunction isVerification(value: unknown): value is CertificateVerification {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.serial === 'string' &&\n typeof v.status === 'string' &&\n STATUSES.has(v.status) &&\n typeof v.valid === 'boolean' &&\n typeof v.verifiedAt === 'string'\n );\n}\n\n/**\n * The GraphQL endpoint for a gateway URL. Hosts pass either the workspace\n * gateway origin (the app shell's `workspaceBaseUrl`) or a complete GraphQL\n * endpoint (the dev harness and stories); a URL that already ends in\n * `/graphql` is used as is.\n */\nexport function verificationEndpoint(apiGatewayUrl: string | undefined): string {\n const base = (apiGatewayUrl ?? '').replace(/\\/+$/, '');\n return /\\/graphql$/.test(base) ? base : `${base}/workspaces/graphql`;\n}\n\ntype FetchLike = (input: string, init: RequestInit) => Promise<Response>;\n\n/**\n * Verify one serial against the public workspace gateway. The request is\n * deliberately anonymous: no Authorization header and no cookies, so the\n * viewer's own session (if any) is never attached to a public lookup.\n */\nexport async function fetchCertificateVerification(\n apiGatewayUrl: string | undefined,\n rawSerial: string,\n fetchImpl: FetchLike = (input, init) => fetch(input, init)\n): Promise<VerifyOutcome> {\n const serial = normalizeSerial(rawSerial);\n if (!isPlausibleSerial(serial)) return { kind: 'invalid' };\n\n const endpoint = verificationEndpoint(apiGatewayUrl);\n let response: Response;\n try {\n response = await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'omit',\n body: JSON.stringify({ query: VERIFY_QUERY, variables: { serial } }),\n });\n } catch {\n return { kind: 'error' };\n }\n\n const body = (await response.json().catch(() => null)) as {\n data?: { tecVerifyCertificate?: unknown } | null;\n } | null;\n const verification = body?.data?.tecVerifyCertificate;\n if (!response.ok || !isVerification(verification)) return { kind: 'error' };\n return { kind: 'result', verification };\n}\n"],"mappings":";AAiBA,IAAa,IAA+B,kBAG/B,IAAgC,GAAG,EAA6B;AA0B7E,SAAgB,EAAgB,GAAwC;AACtE,SAAQ,KAAO,IAAI,MAAM,CAAC,aAAa;;AAIzC,SAAgB,EAAkB,GAAyB;AACzD,QACE,EAAO,SAAS,KAAK,EAAO,UAAA,OAA+B,yBAAyB,KAAK,EAAO;;AAKpG,SAAgB,EAAsB,GAAwB;AAC5D,QAAO,GAAG,EAA6B,GAAG,mBAAmB,EAAgB,EAAO,CAAC;;AAQvF,SAAgB,EAAsB,GAAgB,GAAyB;AAE7E,QAAO,IADM,MAAW,OAAO,SAAW,MAAc,OAAO,SAAS,SAAS,KAClE,QAAQ,QAAQ,GAAG,GAAG,EAAsB,EAAO;;AAGpE,IAAa,IAAe,oRAiBtB,IAAgC,IAAI,IAAI;CAAC;CAAS;CAAW;CAAY,CAAC;AAEhF,SAAS,EAAe,GAAkD;AACxE,KAAI,CAAC,KAAS,OAAO,KAAU,SAAU,QAAO;CAChD,IAAM,IAAI;AACV,QACE,OAAO,EAAE,UAAW,YACpB,OAAO,EAAE,UAAW,YACpB,EAAS,IAAI,EAAE,OAAO,IACtB,OAAO,EAAE,SAAU,aACnB,OAAO,EAAE,cAAe;;AAU5B,SAAgB,EAAqB,GAA2C;CAC9E,IAAM,KAAQ,KAAiB,IAAI,QAAQ,QAAQ,GAAG;AACtD,QAAO,aAAa,KAAK,EAAK,GAAG,IAAO,GAAG,EAAK;;AAUlD,eAAsB,EACpB,GACA,GACA,KAAwB,GAAO,MAAS,MAAM,GAAO,EAAK,EAClC;CACxB,IAAM,IAAS,EAAgB,EAAU;AACzC,KAAI,CAAC,EAAkB,EAAO,CAAE,QAAO,EAAE,MAAM,WAAW;CAE1D,IAAM,IAAW,EAAqB,EAAc,EAChD;AACJ,KAAI;AACF,MAAW,MAAM,EAAU,GAAU;GACnC,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,aAAa;GACb,MAAM,KAAK,UAAU;IAAE,OAAO;IAAc,WAAW,EAAE,WAAQ;IAAE,CAAC;GACrE,CAAC;SACI;AACN,SAAO,EAAE,MAAM,SAAS;;CAM1B,IAAM,KAHQ,MAAM,EAAS,MAAM,CAAC,YAAY,KAAK,GAG1B,MAAM;AAEjC,QADI,CAAC,EAAS,MAAM,CAAC,EAAe,EAAa,GAAS,EAAE,MAAM,SAAS,GACpE;EAAE,MAAM;EAAU;EAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApplicationsProblemDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/ApplicationsProblemDetailsPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ApplicationsProblemDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/ApplicationsProblemDetailsPagePage.tsx"],"names":[],"mappings":"AAsIA,wBAAgB,kCAAkC,gCAkkBjD"}
|