politeshop 0.0.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.
Files changed (42) hide show
  1. package/README.md +33 -0
  2. package/dist/clients/brightspace.d.ts +75 -0
  3. package/dist/clients/brightspace.d.ts.map +1 -0
  4. package/dist/clients/brightspace.js +104 -0
  5. package/dist/clients/brightspace.js.map +1 -0
  6. package/dist/clients/polite.d.ts +117 -0
  7. package/dist/clients/polite.d.ts.map +1 -0
  8. package/dist/clients/polite.js +191 -0
  9. package/dist/clients/polite.js.map +1 -0
  10. package/dist/clients/politeshop.d.ts +119 -0
  11. package/dist/clients/politeshop.d.ts.map +1 -0
  12. package/dist/clients/politeshop.js +401 -0
  13. package/dist/clients/politeshop.js.map +1 -0
  14. package/dist/errors.d.ts +7 -0
  15. package/dist/errors.d.ts.map +1 -0
  16. package/dist/errors.js +9 -0
  17. package/dist/errors.js.map +1 -0
  18. package/dist/index.d.ts +5 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +4 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/schema/polite.d.ts +809 -0
  23. package/dist/schema/polite.d.ts.map +1 -0
  24. package/dist/schema/polite.js +197 -0
  25. package/dist/schema/polite.js.map +1 -0
  26. package/dist/schema/siren.d.ts +66 -0
  27. package/dist/schema/siren.d.ts.map +1 -0
  28. package/dist/schema/siren.js +112 -0
  29. package/dist/schema/siren.js.map +1 -0
  30. package/dist/types.d.ts +105 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +6 -0
  33. package/dist/types.js.map +1 -0
  34. package/dist/utils/array.d.ts +5 -0
  35. package/dist/utils/array.d.ts.map +1 -0
  36. package/dist/utils/array.js +13 -0
  37. package/dist/utils/array.js.map +1 -0
  38. package/dist/utils/url.d.ts +23 -0
  39. package/dist/utils/url.d.ts.map +1 -0
  40. package/dist/utils/url.js +53 -0
  41. package/dist/utils/url.js.map +1 -0
  42. package/package.json +42 -0
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # POLITEShop library
2
+
3
+ JS/TS library for interacting with reverse-engineered [POLITEMall](https://politemall.polite.edu.sg/) APIs (`*.polite.edu.sg` and `*.api.brightspace.com`). Made for the [POLITEShop browser extension](https://github.com/haziq21/politeshop).
4
+
5
+ ```typescript
6
+ import { POLITEShop } from "politeshop";
7
+
8
+ const ps = new POLITEShop({
9
+ d2lSessionVal: "sNtnzd...",
10
+ d2lSecureSessionVal: "eqoLoG...",
11
+ domain: "nplms",
12
+ });
13
+
14
+ // { id: '490586', name: 'HAZIQ DANISH BIN HAIRIL RIZAL' }
15
+ const user = await ps.getUser();
16
+
17
+ // [{ id: '803172', name: 'DATA STRUCTURES & ALGORITHMS (2_DSA_011791)', code: '25S2-2_DSA_011791' }, ...]
18
+ const modules = await ps.getModules();
19
+
20
+ const content = await ps.getModuleContent(modules[0].id);
21
+ ```
22
+
23
+ ## Terminology
24
+
25
+ There are some differences in terminology between Singapore's Polytechnics / ITE and the underlying APIs. This library follows Polytechnic / ITE terminology.
26
+
27
+ | POLITEShop | Underlying APIs | Definition |
28
+ | ------------------------- | ---------------------------- | ------------------------------------------------------------ |
29
+ | Course | Course | A course of study (e.g. Information Technology). A student can only be in one course. |
30
+ | Module | Enrollment / Course offering | A timetabled subject studied, e.g. Data Structures & Algorithms. |
31
+ | Activity | Activity / Topic | An individual POLITEMall page containing text, media embeds, submission dropboxes, interactive activities, etc. |
32
+ | Top-level activity folder | Module | A "root" group of POLITEMall content. By definition, these are not nested. |
33
+ | Activity folder | Unit | A group of POLITEMall content. These can be nested. |
@@ -0,0 +1,75 @@
1
+ import { type SirenEntity } from "../schema/siren";
2
+ /**
3
+ * Client for `*.api.brightspace.com`.
4
+ */
5
+ export declare class Brightspace {
6
+ #private;
7
+ userId: string;
8
+ /** The Brightspace tenant ID (a UUID). */
9
+ tenantId: string;
10
+ /** The expiration date of the current `d2lFetchToken`. */
11
+ tokenExpiry: Date;
12
+ constructor(config: {
13
+ /** Short-lived JWT for Brightspace API authentication. */
14
+ d2lFetchToken: string;
15
+ });
16
+ /**
17
+ * GET /{moduleId}/activity/{topicId}?filterOnDatesAndDepth=0
18
+ *
19
+ * Fetches a Siren entity representing a single activity (topic) in a module.
20
+ * Used for document-embed activities to discover the preview PDF URL.
21
+ */
22
+ getActivity({ moduleId, topicId, }: {
23
+ moduleId: string;
24
+ topicId: string | number;
25
+ }): Promise<SirenEntity>;
26
+ /**
27
+ * GET /old/activities/{orgId}_2000_{dropboxId}/usages/{moduleId}/users/{userId}
28
+ *
29
+ * Fetches submission information for a dropbox that is **closed** (past its
30
+ * availability end date). The POLITE API returns HTTP 403 for closed
31
+ * dropboxes, so this endpoint is used as a fallback.
32
+ *
33
+ * The constant `2000` in the path is the tool ID for dropbox activities.
34
+ */
35
+ getClosedDropboxSubmissions({ orgId, dropboxId, moduleId, }: {
36
+ orgId: string;
37
+ dropboxId: string;
38
+ moduleId: string;
39
+ }): Promise<SirenEntity>;
40
+ /**
41
+ * Fetch a submission entity from an **absolute** Brightspace URL.
42
+ *
43
+ * The href values surfaced by {@link getClosedDropboxSubmissions} point to
44
+ * the `assignments.api.brightspace.com` subdomain (or similar). Pass them
45
+ * here directly without modification.
46
+ */
47
+ getSubmissionDetails({ href }: {
48
+ href: string;
49
+ }): Promise<SirenEntity>;
50
+ /**
51
+ * GET /topics/{moduleId}/{activityId}
52
+ *
53
+ * Fetches thumbnail metadata for a ContentService topic (typically a video).
54
+ * The returned entity contains a `thumbnail` sub-entity whose `properties.src`
55
+ * is a time-limited thumbnail image URL.
56
+ */
57
+ getTopicThumbnail({ moduleId, activityId, }: {
58
+ moduleId: string;
59
+ activityId: string;
60
+ }): Promise<SirenEntity>;
61
+ /**
62
+ * GET /topics/{moduleId}/{activityId}/media
63
+ *
64
+ * Fetches media metadata for a ContentService topic (typically a video).
65
+ * The returned entity's `properties.src` is a time-limited URL for the
66
+ * video file itself.
67
+ */
68
+ getTopicMedia({ moduleId, activityId, }: {
69
+ moduleId: string;
70
+ activityId: string;
71
+ }): Promise<SirenEntity>;
72
+ /** Abort all in-flight requests made by this client. */
73
+ abort(): void;
74
+ }
75
+ //# sourceMappingURL=brightspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brightspace.d.ts","sourceRoot":"","sources":["../../clients/brightspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAIhE;;GAEG;AACH,qBAAa,WAAW;;IACtB,MAAM,EAAE,MAAM,CAAC;IAEf,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IAEjB,0DAA0D;IAC1D,WAAW,EAAE,IAAI,CAAC;gBAKN,MAAM,EAAE;QAClB,0DAA0D;QAC1D,aAAa,EAAE,MAAM,CAAC;KACvB;IAiBD;;;;;OAKG;IACG,WAAW,CAAC,EAChB,QAAQ,EACR,OAAO,GACR,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;KAC1B,GAAG,OAAO,CAAC,WAAW,CAAC;IASxB;;;;;;;;OAQG;IACG,2BAA2B,CAAC,EAChC,KAAK,EACL,SAAS,EACT,QAAQ,GACT,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,WAAW,CAAC;IAOxB;;;;;;OAMG;IACG,oBAAoB,CAAC,EAAE,IAAI,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAM5E;;;;;;OAMG;IACG,iBAAiB,CAAC,EACtB,QAAQ,EACR,UAAU,GACX,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,WAAW,CAAC;IAOxB;;;;;;OAMG;IACG,aAAa,CAAC,EAClB,QAAQ,EACR,UAAU,GACX,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,WAAW,CAAC;IASxB,wDAAwD;IACxD,KAAK,IAAI,IAAI;CAsCd"}
@@ -0,0 +1,104 @@
1
+ import { sirenEntity } from "../schema/siren";
2
+ import { decodeJwt } from "jose";
3
+ import z from "zod";
4
+ /**
5
+ * Client for `*.api.brightspace.com`.
6
+ */
7
+ export class Brightspace {
8
+ userId;
9
+ /** The Brightspace tenant ID (a UUID). */
10
+ tenantId;
11
+ /** The expiration date of the current `d2lFetchToken`. */
12
+ tokenExpiry;
13
+ #d2lFetchToken;
14
+ #abortController = new AbortController();
15
+ constructor(config) {
16
+ this.#d2lFetchToken = config.d2lFetchToken;
17
+ const { sub, exp, tenantid } = z
18
+ .object({
19
+ tenantid: z.string(),
20
+ sub: z.string(),
21
+ exp: z.date({ coerce: true }),
22
+ })
23
+ .parse(decodeJwt(this.#d2lFetchToken));
24
+ this.userId = sub;
25
+ this.tokenExpiry = exp;
26
+ this.tenantId = tenantid;
27
+ }
28
+ // ── Sequences API (sequences.api.brightspace.com) ────────────────────────────
29
+ /**
30
+ * GET /{moduleId}/activity/{topicId}?filterOnDatesAndDepth=0
31
+ *
32
+ * Fetches a Siren entity representing a single activity (topic) in a module.
33
+ * Used for document-embed activities to discover the preview PDF URL.
34
+ */
35
+ async getActivity({ moduleId, topicId, }) {
36
+ return this.#fetchSiren(`/${moduleId}/activity/${topicId}?filterOnDatesAndDepth=0`, "sequences");
37
+ }
38
+ // ── Activities API (activities.api.brightspace.com) ──────────────────────────
39
+ /**
40
+ * GET /old/activities/{orgId}_2000_{dropboxId}/usages/{moduleId}/users/{userId}
41
+ *
42
+ * Fetches submission information for a dropbox that is **closed** (past its
43
+ * availability end date). The POLITE API returns HTTP 403 for closed
44
+ * dropboxes, so this endpoint is used as a fallback.
45
+ *
46
+ * The constant `2000` in the path is the tool ID for dropbox activities.
47
+ */
48
+ async getClosedDropboxSubmissions({ orgId, dropboxId, moduleId, }) {
49
+ return this.#fetchSiren(`/old/activities/${orgId}_2000_${dropboxId}/usages/${moduleId}/users/${this.userId}`, "activities");
50
+ }
51
+ /**
52
+ * Fetch a submission entity from an **absolute** Brightspace URL.
53
+ *
54
+ * The href values surfaced by {@link getClosedDropboxSubmissions} point to
55
+ * the `assignments.api.brightspace.com` subdomain (or similar). Pass them
56
+ * here directly without modification.
57
+ */
58
+ async getSubmissionDetails({ href }) {
59
+ return this.#fetchSiren(href);
60
+ }
61
+ // ── Content Service API (content-service.api.brightspace.com) ────────────────
62
+ /**
63
+ * GET /topics/{moduleId}/{activityId}
64
+ *
65
+ * Fetches thumbnail metadata for a ContentService topic (typically a video).
66
+ * The returned entity contains a `thumbnail` sub-entity whose `properties.src`
67
+ * is a time-limited thumbnail image URL.
68
+ */
69
+ async getTopicThumbnail({ moduleId, activityId, }) {
70
+ return this.#fetchSiren(`/topics/${moduleId}/${activityId}`, "content-service");
71
+ }
72
+ /**
73
+ * GET /topics/{moduleId}/{activityId}/media
74
+ *
75
+ * Fetches media metadata for a ContentService topic (typically a video).
76
+ * The returned entity's `properties.src` is a time-limited URL for the
77
+ * video file itself.
78
+ */
79
+ async getTopicMedia({ moduleId, activityId, }) {
80
+ return this.#fetchSiren(`/topics/${moduleId}/${activityId}/media`, "content-service");
81
+ }
82
+ // ── Lifecycle ─────────────────────────────────────────────────────────────────
83
+ /** Abort all in-flight requests made by this client. */
84
+ abort() {
85
+ this.#abortController.abort();
86
+ }
87
+ async #fetchSiren(pathOrURL, api) {
88
+ const url = api
89
+ ? new URL(pathOrURL, `https://${this.tenantId}.${api}.api.brightspace.com`)
90
+ : new URL(pathOrURL);
91
+ const res = await fetch(url, {
92
+ headers: { Authorization: `Bearer ${this.#d2lFetchToken}` },
93
+ signal: this.#abortController.signal,
94
+ });
95
+ if (!res.ok) {
96
+ const body = res.headers.get("content-type")?.includes("json")
97
+ ? JSON.stringify(await res.json())
98
+ : await res.text();
99
+ throw new Error(`Brightspace API error ${res.status} at ${url}: ${body}`);
100
+ }
101
+ return sirenEntity.parse(await res.json());
102
+ }
103
+ }
104
+ //# sourceMappingURL=brightspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brightspace.js","sourceRoot":"","sources":["../../clients/brightspace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,117 @@
1
+ import * as schema from "../schema/polite";
2
+ /**
3
+ * Low-level client for the POLITE API (`*.polite.edu.sg`).
4
+ *
5
+ * Handles authentication via D2L session cookies and exposes one method per
6
+ * API endpoint. All response bodies are validated with Zod schemas before
7
+ * being returned, so callers receive fully-typed data.
8
+ */
9
+ export declare class POLITE {
10
+ #private;
11
+ constructor(config: {
12
+ /** `d2lSessionVal` cookie value. */
13
+ d2lSessionVal: string;
14
+ /** `d2lSecureSessionVal` cookie value. */
15
+ d2lSecureSessionVal: string;
16
+ /** Subdomain of the POLITEMall site (e.g. `"nplms"`). */
17
+ domain: string;
18
+ });
19
+ get baseURL(): string;
20
+ /**
21
+ * GET /d2l/lp/auth/oauth2/token
22
+ *
23
+ * Exchanges the current D2L session cookies for a short-lived Brightspace
24
+ * JWT that can be used with the `*.api.brightspace.com` APIs.
25
+ */
26
+ getNewFetchToken(): Promise<schema.BrightspaceToken>;
27
+ /**
28
+ * GET /d2l/api/lp/1.0/users/whoami
29
+ *
30
+ * Returns basic information about the currently authenticated user.
31
+ */
32
+ whoAmI(): Promise<schema.WhoAmIUser>;
33
+ /**
34
+ * GET /d2l/api/lp/1.46/organization/info
35
+ *
36
+ * Returns information about the organisation (institution).
37
+ */
38
+ getOrganizationInfo(): Promise<schema.Organization>;
39
+ /**
40
+ * GET /d2l/api/lp/1.46/enrollments/myenrollments/
41
+ *
42
+ * Returns one page of the authenticated user's org-unit enrollments.
43
+ * Pass `bookmark` to fetch subsequent pages.
44
+ */
45
+ getMyEnrollments({ bookmark }?: {
46
+ bookmark?: string;
47
+ }): Promise<schema.PagedResultSet<schema.MyOrgUnitInfo>>;
48
+ /**
49
+ * GET /d2l/api/lp/1.46/courses/parentorgunits?orgUnitIdsCSV=…
50
+ *
51
+ * Returns semester/department parent information for up to 25 course-offering
52
+ * org units at a time.
53
+ */
54
+ getParentOrgUnits({ orgUnitIdsCSV, }: {
55
+ orgUnitIdsCSV: string;
56
+ }): Promise<schema.CourseParent[]>;
57
+ /**
58
+ * GET /d2l/api/le/1.75/{moduleId}/content/toc
59
+ *
60
+ * Returns the table of contents for a module, including all nested folders
61
+ * (Modules) and topics (Activities).
62
+ */
63
+ getModuleTOC({ moduleId, }: {
64
+ moduleId: string;
65
+ }): Promise<schema.TableOfContents>;
66
+ /**
67
+ * Fetch a content URL as raw text (used for HTML-type activities).
68
+ *
69
+ * `urlOrPath` may be either an absolute URL or a path relative to
70
+ * `this.baseURL`.
71
+ */
72
+ getContentHTML({ urlOrPath }: {
73
+ urlOrPath: string;
74
+ }): Promise<string>;
75
+ /**
76
+ * Return the image URL for a module or organisation.
77
+ */
78
+ getImageURL(id: string, dim?: {
79
+ width: number;
80
+ height: number;
81
+ }): string;
82
+ /**
83
+ * GET /d2l/api/le/1.75/{moduleId}/dropbox/folders/
84
+ *
85
+ * Returns all submission dropbox folders in a module.
86
+ */
87
+ getDropboxFolders({ moduleId, }: {
88
+ moduleId: string;
89
+ }): Promise<schema.DropboxFolder[]>;
90
+ /**
91
+ * GET /d2l/api/le/1.75/{moduleId}/dropbox/folders/{dropboxId}/submissions/
92
+ *
93
+ * Returns the current user's submissions for a dropbox. Returns at most one
94
+ * `EntityDropbox` object (the user's own entry).
95
+ *
96
+ * > **Note:** This endpoint returns HTTP 403 for dropboxes whose availability
97
+ * > window has closed. In that case, use the Brightspace Activities API
98
+ * > instead (`Brightspace.getClosedDropboxSubmissions`).
99
+ */
100
+ getDropboxSubmissions({ moduleId, dropboxId, }: {
101
+ moduleId: string;
102
+ dropboxId: string;
103
+ }): Promise<schema.EntityDropbox[]>;
104
+ /**
105
+ * Fetch one page of quizzes from the given URL or path.
106
+ *
107
+ * For the first page, pass `/d2l/api/le/1.75/{moduleId}/quizzes/`.
108
+ * The `Next` field of the returned object is the URL for the next page
109
+ * (or `null` if there are no more pages).
110
+ */
111
+ getQuizzesPage({ urlOrPath, }: {
112
+ urlOrPath: string;
113
+ }): Promise<schema.ObjectListPage<schema.QuizReadData>>;
114
+ /** Abort all in-flight requests made by this client. */
115
+ abort(): void;
116
+ }
117
+ //# sourceMappingURL=polite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polite.d.ts","sourceRoot":"","sources":["../../clients/polite.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAM3C;;;;;;GAMG;AACH,qBAAa,MAAM;;gBAML,MAAM,EAAE;QAClB,oCAAoC;QACpC,aAAa,EAAE,MAAM,CAAC;QACtB,0CAA0C;QAC1C,mBAAmB,EAAE,MAAM,CAAC;QAC5B,yDAAyD;QACzD,MAAM,EAAE,MAAM,CAAC;KAChB;IAMD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAID;;;;;OAKG;IACG,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC;IAqB1D;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;IAQ1C;;;;OAIG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC;IAQzD;;;;;OAKG;IACG,gBAAgB,CAAC,EAAE,QAAQ,EAAE,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CACvE,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,aAAa,CAAC,CAC5C;IAQD;;;;;OAKG;IACG,iBAAiB,CAAC,EACtB,aAAa,GACd,EAAE;QACD,aAAa,EAAE,MAAM,CAAC;KACvB,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;IASlC;;;;;OAKG;IACG,YAAY,CAAC,EACjB,QAAQ,GACT,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC;IAMnC;;;;;OAKG;IACG,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAI3E;;OAEG;IACH,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM;IAOxE;;;;OAIG;IACG,iBAAiB,CAAC,EACtB,QAAQ,GACT,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;IAMnC;;;;;;;;;OASG;IACG,qBAAqB,CAAC,EAC1B,QAAQ,EACR,SAAS,GACV,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;IASnC;;;;;;OAMG;IACG,cAAc,CAAC,EACnB,SAAS,GACV,EAAE;QACD,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAQvD,wDAAwD;IACxD,KAAK,IAAI,IAAI;CA4Dd"}
@@ -0,0 +1,191 @@
1
+ import * as schema from "../schema/polite";
2
+ import { defaultToBaseURL } from "../utils/url";
3
+ import { UnexpectedResponseError } from "../errors";
4
+ // TODO: /d2l/api/le/manageCourses/courses-searches/490586/BySemester
5
+ /**
6
+ * Low-level client for the POLITE API (`*.polite.edu.sg`).
7
+ *
8
+ * Handles authentication via D2L session cookies and exposes one method per
9
+ * API endpoint. All response bodies are validated with Zod schemas before
10
+ * being returned, so callers receive fully-typed data.
11
+ */
12
+ export class POLITE {
13
+ #d2lSessionVal;
14
+ #d2lSecureSessionVal;
15
+ #domain;
16
+ #abortController = new AbortController();
17
+ constructor(config) {
18
+ this.#domain = config.domain;
19
+ this.#d2lSessionVal = config.d2lSessionVal;
20
+ this.#d2lSecureSessionVal = config.d2lSecureSessionVal;
21
+ }
22
+ get baseURL() {
23
+ return `https://${this.#domain}.polite.edu.sg`;
24
+ }
25
+ // ── Auth ────────────────────────────────────────────────────────────────────
26
+ /**
27
+ * GET /d2l/lp/auth/oauth2/token
28
+ *
29
+ * Exchanges the current D2L session cookies for a short-lived Brightspace
30
+ * JWT that can be used with the `*.api.brightspace.com` APIs.
31
+ */
32
+ async getNewFetchToken() {
33
+ // Fetch the homepage HTML to extract the XSRF token from it
34
+ const homepage = await this.#fetchText("/d2l/home");
35
+ const xsrfToken = homepage.match(/\.setItem\(['"]XSRF.Token['"],\s*['"](.+?)['"]\)/)?.[1];
36
+ if (!xsrfToken) {
37
+ throw new UnexpectedResponseError("No XSRF token found in homepage");
38
+ }
39
+ return this.#fetchJSON("/d2l/lp/auth/oauth2/token", {
40
+ method: "POST",
41
+ headers: { "X-Csrf-Token": xsrfToken },
42
+ body: new URLSearchParams({ scope: "*:*:*" }),
43
+ schema: schema.brightspaceToken,
44
+ });
45
+ }
46
+ // ── Users ───────────────────────────────────────────────────────────────────
47
+ /**
48
+ * GET /d2l/api/lp/1.0/users/whoami
49
+ *
50
+ * Returns basic information about the currently authenticated user.
51
+ */
52
+ async whoAmI() {
53
+ return this.#fetchJSON("/d2l/api/lp/1.0/users/whoami", {
54
+ schema: schema.whoAmIUser,
55
+ });
56
+ }
57
+ // ── Organization ────────────────────────────────────────────────────────────
58
+ /**
59
+ * GET /d2l/api/lp/1.46/organization/info
60
+ *
61
+ * Returns information about the organisation (institution).
62
+ */
63
+ async getOrganizationInfo() {
64
+ return this.#fetchJSON("/d2l/api/lp/1.46/organization/info", {
65
+ schema: schema.organization,
66
+ });
67
+ }
68
+ // ── Enrollments ─────────────────────────────────────────────────────────────
69
+ /**
70
+ * GET /d2l/api/lp/1.46/enrollments/myenrollments/
71
+ *
72
+ * Returns one page of the authenticated user's org-unit enrollments.
73
+ * Pass `bookmark` to fetch subsequent pages.
74
+ */
75
+ async getMyEnrollments({ bookmark } = {}) {
76
+ const query = bookmark ? `?bookmark=${encodeURIComponent(bookmark)}` : "";
77
+ return this.#fetchJSON(`/d2l/api/lp/1.46/enrollments/myenrollments/${query}`, { schema: schema.pagedResultSet(schema.myOrgUnitInfo) });
78
+ }
79
+ /**
80
+ * GET /d2l/api/lp/1.46/courses/parentorgunits?orgUnitIdsCSV=…
81
+ *
82
+ * Returns semester/department parent information for up to 25 course-offering
83
+ * org units at a time.
84
+ */
85
+ async getParentOrgUnits({ orgUnitIdsCSV, }) {
86
+ return this.#fetchJSON(`/d2l/api/lp/1.46/courses/parentorgunits?orgUnitIdsCSV=${encodeURIComponent(orgUnitIdsCSV)}`, { schema: schema.courseParent.array() });
87
+ }
88
+ // ── Content ─────────────────────────────────────────────────────────────────
89
+ /**
90
+ * GET /d2l/api/le/1.75/{moduleId}/content/toc
91
+ *
92
+ * Returns the table of contents for a module, including all nested folders
93
+ * (Modules) and topics (Activities).
94
+ */
95
+ async getModuleTOC({ moduleId, }) {
96
+ return this.#fetchJSON(`/d2l/api/le/1.75/${moduleId}/content/toc`, {
97
+ schema: schema.tableOfContents,
98
+ });
99
+ }
100
+ /**
101
+ * Fetch a content URL as raw text (used for HTML-type activities).
102
+ *
103
+ * `urlOrPath` may be either an absolute URL or a path relative to
104
+ * `this.baseURL`.
105
+ */
106
+ async getContentHTML({ urlOrPath }) {
107
+ return this.#fetchText(urlOrPath);
108
+ }
109
+ /**
110
+ * Return the image URL for a module or organisation.
111
+ */
112
+ getImageURL(id, dim) {
113
+ const query = dim ? `?width=${dim.width}&height=${dim.height}` : "";
114
+ return `${this.baseURL}/d2l/api/lp/1.46/courses/${id}/image${query}`;
115
+ }
116
+ // ── Dropbox / Submissions ────────────────────────────────────────────────────
117
+ /**
118
+ * GET /d2l/api/le/1.75/{moduleId}/dropbox/folders/
119
+ *
120
+ * Returns all submission dropbox folders in a module.
121
+ */
122
+ async getDropboxFolders({ moduleId, }) {
123
+ return this.#fetchJSON(`/d2l/api/le/1.75/${moduleId}/dropbox/folders/`, {
124
+ schema: schema.dropboxFolder.array(),
125
+ });
126
+ }
127
+ /**
128
+ * GET /d2l/api/le/1.75/{moduleId}/dropbox/folders/{dropboxId}/submissions/
129
+ *
130
+ * Returns the current user's submissions for a dropbox. Returns at most one
131
+ * `EntityDropbox` object (the user's own entry).
132
+ *
133
+ * > **Note:** This endpoint returns HTTP 403 for dropboxes whose availability
134
+ * > window has closed. In that case, use the Brightspace Activities API
135
+ * > instead (`Brightspace.getClosedDropboxSubmissions`).
136
+ */
137
+ async getDropboxSubmissions({ moduleId, dropboxId, }) {
138
+ return this.#fetchJSON(`/d2l/api/le/1.75/${moduleId}/dropbox/folders/${dropboxId}/submissions/`, { schema: schema.entityDropbox.array().max(1) });
139
+ }
140
+ // ── Quizzes ──────────────────────────────────────────────────────────────────
141
+ /**
142
+ * Fetch one page of quizzes from the given URL or path.
143
+ *
144
+ * For the first page, pass `/d2l/api/le/1.75/{moduleId}/quizzes/`.
145
+ * The `Next` field of the returned object is the URL for the next page
146
+ * (or `null` if there are no more pages).
147
+ */
148
+ async getQuizzesPage({ urlOrPath, }) {
149
+ return this.#fetchJSON(urlOrPath, {
150
+ schema: schema.objectListPage(schema.quizReadData),
151
+ });
152
+ }
153
+ // ── Lifecycle ────────────────────────────────────────────────────────────────
154
+ /** Abort all in-flight requests made by this client. */
155
+ abort() {
156
+ this.#abortController.abort();
157
+ }
158
+ async #fetchJSON(input, init) {
159
+ const res = await this.#fetch(input, init);
160
+ if (!res.ok) {
161
+ throw new UnexpectedResponseError(`Received ${res.status} for ${defaultToBaseURL(input, this.baseURL)}`, { response: res });
162
+ }
163
+ const data = await res.json();
164
+ if (init?.schema) {
165
+ return init.schema.parse(data);
166
+ }
167
+ return data;
168
+ }
169
+ async #fetchText(urlOrPath) {
170
+ const res = await this.#fetch(urlOrPath);
171
+ if (!res.ok) {
172
+ throw new Error(`POLITE API error ${res.status} at ${urlOrPath}`);
173
+ }
174
+ return res.text();
175
+ }
176
+ /**
177
+ * {@link fetch} wrapper that:
178
+ * - Defaults to {@link baseURL} as the base URL.
179
+ * - Adds authentication cookies.
180
+ */
181
+ async #fetch(input, init) {
182
+ const headers = new Headers(init?.headers);
183
+ headers.set("Cookie", `d2lSessionVal=${this.#d2lSessionVal}; d2lSecureSessionVal=${this.#d2lSecureSessionVal}`);
184
+ return fetch(defaultToBaseURL(input, this.baseURL), {
185
+ ...init,
186
+ headers,
187
+ signal: this.#abortController.signal,
188
+ });
189
+ }
190
+ }
191
+ //# sourceMappingURL=polite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polite.js","sourceRoot":"","sources":["../../clients/polite.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ import type { ActivityFolder, Module, Institution, Quiz, Semester, SubmissionDropbox, User, Submission } from "../types";
2
+ import { Brightspace } from "./brightspace";
3
+ import { POLITE } from "./polite";
4
+ /**
5
+ * High-level POLITEMall client.
6
+ *
7
+ * Composes {@link POLITE} (for `*.polite.edu.sg` APIs) and {@link Brightspace}
8
+ * (for `*.api.brightspace.com` APIs) into a single, convenient interface.
9
+ *
10
+ * Both lower-level clients are accessible so callers can reach endpoints not
11
+ * covered by the high-level methods.
12
+ */
13
+ export declare class POLITEShop {
14
+ #private;
15
+ /** Client for `*.polite.edu.sg` APIs. */
16
+ readonly polite: POLITE;
17
+ /**
18
+ * Initialize the underlying {@link POLITE} client. If `d2lFetchToken` is
19
+ * provided, the {@link Brightspace} client is also initialized immediately.
20
+ * Otherwise, it is initialized lazily via the {@link brightspace} getter.
21
+ */
22
+ constructor(config: {
23
+ /** `d2lSessionVal` cookie value. */
24
+ d2lSessionVal: string;
25
+ /** `d2lSecureSessionVal` cookie value. */
26
+ d2lSecureSessionVal: string;
27
+ /** Subdomain of the POLITEMall site (e.g. `"nplms"`). */
28
+ domain: string;
29
+ /**
30
+ * Brightspace JWT found in localStorage. If not provided,
31
+ * it will be fetched automatically the first time it is needed.
32
+ */
33
+ d2lFetchToken?: string;
34
+ });
35
+ /**
36
+ * Returns the initialized {@link Brightspace} client, fetching or refreshing
37
+ * the `d2lFetchToken` automatically when needed.
38
+ *
39
+ * Implemented as a Promise-returning getter rather than a plain `Brightspace`
40
+ * property because the token may be unavailable or expired at access time,
41
+ * requiring an asynchronous network request to obtain a fresh one.
42
+ */
43
+ get brightspace(): Promise<Brightspace>;
44
+ /** Fetch the user's ID and display name. */
45
+ getUser(): Promise<User>;
46
+ /** Fetch information about the educational institution the user is in. */
47
+ getInstitution(): Promise<Institution>;
48
+ /**
49
+ * Return the URL of the institution's image. The institution's ID is needed to
50
+ * construct the URL, so if not provided, this calls {@link getInstitution}.
51
+ * The URL construction itself is synchronous. */
52
+ getInstitutionImageURL(config: {
53
+ width: number;
54
+ height: number;
55
+ }): Promise<string>;
56
+ getInstitutionImageURL(config: {
57
+ width: number;
58
+ height: number;
59
+ institutionId: string;
60
+ }): string;
61
+ /**
62
+ * Fetch the modules the user is enrolled in.
63
+ */
64
+ getModules(): Promise<Module[]>;
65
+ /**
66
+ * Fetch all modules the user is enrolled in, together with the associated semesters.
67
+ */
68
+ getModulesAndSemesters(): Promise<{
69
+ modules: (Module & {
70
+ semesterId: string;
71
+ })[];
72
+ semesters: Semester[];
73
+ }>;
74
+ /**
75
+ * Fetch all activity folders and activities for a module.
76
+ *
77
+ * Retrieves the table of contents then recursively parses every folder and
78
+ * topic, making additional requests as needed to resolve activity details.
79
+ */
80
+ getModuleContent({ moduleId, }: {
81
+ moduleId: string;
82
+ }): Promise<ActivityFolder[]>;
83
+ /** Fetch all submission dropbox folders in a module. */
84
+ getSubmissionDropboxes({ moduleId, }: {
85
+ moduleId: string;
86
+ }): Promise<SubmissionDropbox[]>;
87
+ /**
88
+ * Fetch the authenticated user's submissions for a given dropbox.
89
+ *
90
+ * Two strategies are attempted in order:
91
+ *
92
+ * 1. **POLITE API** — A single request returns all submissions. This is the
93
+ * preferred path but returns HTTP 403 for dropboxes whose availability
94
+ * window has closed.
95
+ * 2. **Brightspace Activities API** — Used as a fallback when the POLITE API
96
+ * fails. Requires one additional request per submission. If `organizationId`
97
+ * is not provided, it is fetched automatically via {@link getInstitution}.
98
+ *
99
+ * @param params.moduleId - The module (course offering) ID.
100
+ * @param params.dropboxId - The dropbox folder ID.
101
+ * @param params.organizationId - The organisation ID, used by the Brightspace
102
+ * Activities API path. If omitted and the fallback is needed, it is fetched
103
+ * automatically via {@link getInstitution}.
104
+ */
105
+ getSubmissions({ moduleId, dropboxId, organizationId, }: {
106
+ moduleId: string;
107
+ dropboxId: string;
108
+ organizationId?: string;
109
+ }): Promise<Submission[]>;
110
+ /**
111
+ * Fetch all quizzes in a module.
112
+ */
113
+ getQuizzes({ moduleId }: {
114
+ moduleId: string;
115
+ }): Promise<Quiz[]>;
116
+ /** Abort all in-flight requests on both the POLITE and Brightspace clients. */
117
+ abort(): void;
118
+ }
119
+ //# sourceMappingURL=politeshop.d.ts.map