reddy-api-srm 1.0.12 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/src/auth/login.d.ts +14 -0
  2. package/dist/src/auth/login.d.ts.map +1 -0
  3. package/dist/src/auth/login.js +96 -0
  4. package/dist/src/auth/login.js.map +1 -0
  5. package/dist/src/auth/sessionSeed.d.ts +7 -0
  6. package/dist/src/auth/sessionSeed.d.ts.map +1 -0
  7. package/dist/src/auth/sessionSeed.js +40 -0
  8. package/dist/src/auth/sessionSeed.js.map +1 -0
  9. package/dist/src/auth/terminateSessions.d.ts +9 -24
  10. package/dist/src/auth/terminateSessions.d.ts.map +1 -0
  11. package/dist/src/auth/terminateSessions.js +99 -96
  12. package/dist/src/auth/terminateSessions.js.map +1 -0
  13. package/dist/src/auth/validatePassword.d.ts +1 -1
  14. package/dist/src/auth/validatePassword.d.ts.map +1 -1
  15. package/dist/src/auth/validatePassword.js +138 -87
  16. package/dist/src/auth/validatePassword.js.map +1 -1
  17. package/dist/src/auth/validateUser.d.ts +1 -1
  18. package/dist/src/auth/validateUser.d.ts.map +1 -1
  19. package/dist/src/auth/validateUser.js +14 -40
  20. package/dist/src/auth/validateUser.js.map +1 -1
  21. package/dist/src/fetch/fetchAttendance.d.ts +5 -1
  22. package/dist/src/fetch/fetchAttendance.d.ts.map +1 -1
  23. package/dist/src/fetch/fetchAttendance.js +3 -33
  24. package/dist/src/fetch/fetchAttendance.js.map +1 -1
  25. package/dist/src/fetch/fetchCalender.d.ts +5 -1
  26. package/dist/src/fetch/fetchCalender.d.ts.map +1 -1
  27. package/dist/src/fetch/fetchCalender.js +4 -37
  28. package/dist/src/fetch/fetchCalender.js.map +1 -1
  29. package/dist/src/fetch/fetchCourseDetails.d.ts +5 -1
  30. package/dist/src/fetch/fetchCourseDetails.d.ts.map +1 -1
  31. package/dist/src/fetch/fetchCourseDetails.js +5 -40
  32. package/dist/src/fetch/fetchCourseDetails.js.map +1 -1
  33. package/dist/src/fetch/fetchDayOrder.d.ts +5 -1
  34. package/dist/src/fetch/fetchDayOrder.d.ts.map +1 -1
  35. package/dist/src/fetch/fetchDayOrder.js +3 -33
  36. package/dist/src/fetch/fetchDayOrder.js.map +1 -1
  37. package/dist/src/fetch/fetchLogout.d.ts +2 -4
  38. package/dist/src/fetch/fetchLogout.d.ts.map +1 -1
  39. package/dist/src/fetch/fetchLogout.js +15 -9
  40. package/dist/src/fetch/fetchLogout.js.map +1 -1
  41. package/dist/src/fetch/fetchMarks.d.ts +5 -1
  42. package/dist/src/fetch/fetchMarks.d.ts.map +1 -1
  43. package/dist/src/fetch/fetchMarks.js +4 -33
  44. package/dist/src/fetch/fetchMarks.js.map +1 -1
  45. package/dist/src/fetch/fetchPage.d.ts +6 -0
  46. package/dist/src/fetch/fetchPage.d.ts.map +1 -0
  47. package/dist/src/fetch/fetchPage.js +58 -0
  48. package/dist/src/fetch/fetchPage.js.map +1 -0
  49. package/dist/src/fetch/fetchTimetable.d.ts +5 -1
  50. package/dist/src/fetch/fetchTimetable.d.ts.map +1 -1
  51. package/dist/src/fetch/fetchTimetable.js +5 -40
  52. package/dist/src/fetch/fetchTimetable.js.map +1 -1
  53. package/dist/src/fetch/fetchUserInfo.d.ts +5 -1
  54. package/dist/src/fetch/fetchUserInfo.d.ts.map +1 -1
  55. package/dist/src/fetch/fetchUserInfo.js +6 -35
  56. package/dist/src/fetch/fetchUserInfo.js.map +1 -1
  57. package/dist/src/index.d.ts +39 -5
  58. package/dist/src/index.d.ts.map +1 -1
  59. package/dist/src/index.js +86 -33
  60. package/dist/src/index.js.map +1 -1
  61. package/dist/src/parser/decodeHtml.d.ts +6 -0
  62. package/dist/src/parser/decodeHtml.d.ts.map +1 -0
  63. package/dist/src/parser/decodeHtml.js +18 -0
  64. package/dist/src/parser/decodeHtml.js.map +1 -0
  65. package/dist/src/parser/parseAttendance.d.ts +2 -2
  66. package/dist/src/parser/parseAttendance.d.ts.map +1 -1
  67. package/dist/src/parser/parseAttendance.js +68 -16
  68. package/dist/src/parser/parseAttendance.js.map +1 -1
  69. package/dist/src/parser/parseCalender.d.ts +2 -2
  70. package/dist/src/parser/parseCalender.d.ts.map +1 -1
  71. package/dist/src/parser/parseCalender.js +125 -43
  72. package/dist/src/parser/parseCalender.js.map +1 -1
  73. package/dist/src/parser/parseCourse.d.ts +2 -2
  74. package/dist/src/parser/parseCourse.d.ts.map +1 -1
  75. package/dist/src/parser/parseCourse.js +19 -8
  76. package/dist/src/parser/parseCourse.js.map +1 -1
  77. package/dist/src/parser/parseDayOrder.d.ts +2 -2
  78. package/dist/src/parser/parseDayOrder.d.ts.map +1 -1
  79. package/dist/src/parser/parseDayOrder.js +31 -12
  80. package/dist/src/parser/parseDayOrder.js.map +1 -1
  81. package/dist/src/parser/parseMarks.d.ts +2 -2
  82. package/dist/src/parser/parseMarks.d.ts.map +1 -1
  83. package/dist/src/parser/parseMarks.js +23 -11
  84. package/dist/src/parser/parseMarks.js.map +1 -1
  85. package/dist/src/parser/parseTimetable.d.ts +2 -2
  86. package/dist/src/parser/parseTimetable.d.ts.map +1 -1
  87. package/dist/src/parser/parseTimetable.js +51 -27
  88. package/dist/src/parser/parseTimetable.js.map +1 -1
  89. package/dist/src/parser/parseUserInfo.d.ts +2 -2
  90. package/dist/src/parser/parseUserInfo.d.ts.map +1 -1
  91. package/dist/src/parser/parseUserInfo.js +57 -25
  92. package/dist/src/parser/parseUserInfo.js.map +1 -1
  93. package/dist/src/type/attendance.d.ts +5 -5
  94. package/dist/src/type/attendance.d.ts.map +1 -1
  95. package/dist/src/type/auth.d.ts +33 -5
  96. package/dist/src/type/auth.d.ts.map +1 -1
  97. package/dist/src/type/calendar.d.ts.map +1 -1
  98. package/dist/src/type/common.d.ts.map +1 -1
  99. package/dist/src/type/course.d.ts.map +1 -1
  100. package/dist/src/type/dayOrder.d.ts.map +1 -1
  101. package/dist/src/type/index.d.ts +0 -1
  102. package/dist/src/type/index.d.ts.map +1 -1
  103. package/dist/src/type/index.js +0 -1
  104. package/dist/src/type/index.js.map +1 -1
  105. package/dist/src/type/marks.d.ts.map +1 -1
  106. package/dist/src/type/timetable.d.ts.map +1 -1
  107. package/dist/src/type/user.d.ts +1 -0
  108. package/dist/src/type/user.d.ts.map +1 -1
  109. package/dist/src/utils/attendanceStatus.d.ts +8 -0
  110. package/dist/src/utils/attendanceStatus.d.ts.map +1 -0
  111. package/dist/src/utils/attendanceStatus.js +20 -0
  112. package/dist/src/utils/attendanceStatus.js.map +1 -0
  113. package/dist/src/utils/data.d.ts +11 -0
  114. package/dist/src/utils/data.d.ts.map +1 -0
  115. package/dist/src/utils/data.js +36 -0
  116. package/dist/src/utils/data.js.map +1 -0
  117. package/dist/src/utils/dynamicUrl.d.ts +14 -0
  118. package/dist/src/utils/dynamicUrl.d.ts.map +1 -0
  119. package/dist/src/utils/dynamicUrl.js +105 -0
  120. package/dist/src/utils/dynamicUrl.js.map +1 -0
  121. package/package.json +2 -2
@@ -0,0 +1,14 @@
1
+ import type { LoginResult } from "../type/auth";
2
+ /**
3
+ * One-call login function that handles the complete authentication flow:
4
+ * 1. Verify user exists
5
+ * 2. Verify password
6
+ * 3. If concurrent session limit hit → auto-terminate → retry login
7
+ * 4. Return cookies or error
8
+ *
9
+ * @param email - SRM email address (e.g., hm3276@srmist.edu.in)
10
+ * @param password - Account password
11
+ * @param maxRetries - Max number of terminate + retry attempts (default: 2)
12
+ */
13
+ export declare function login(email: string, password: string, maxRetries?: number): Promise<LoginResult>;
14
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../../src/auth/login.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAKhD;;;;;;;;;;GAUG;AACH,wBAAsB,KAAK,CACzB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,UAAU,GAAE,MAAU,GACrB,OAAO,CAAC,WAAW,CAAC,CAgGtB"}
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.login = login;
4
+ const validateUser_1 = require("./validateUser");
5
+ const validatePassword_1 = require("./validatePassword");
6
+ const terminateSessions_1 = require("./terminateSessions");
7
+ /**
8
+ * One-call login function that handles the complete authentication flow:
9
+ * 1. Verify user exists
10
+ * 2. Verify password
11
+ * 3. If concurrent session limit hit → auto-terminate → retry login
12
+ * 4. Return cookies or error
13
+ *
14
+ * @param email - SRM email address (e.g., hm3276@srmist.edu.in)
15
+ * @param password - Account password
16
+ * @param maxRetries - Max number of terminate + retry attempts (default: 2)
17
+ */
18
+ async function login(email, password, maxRetries = 2) {
19
+ // Step 1: Verify user
20
+ const userResult = await (0, validateUser_1.validateUser)(email);
21
+ if (userResult.error || !userResult.data) {
22
+ return {
23
+ isAuthenticated: false,
24
+ error: userResult.error ?? "Failed to verify user",
25
+ errorReason: userResult.errorReason,
26
+ };
27
+ }
28
+ if (userResult.data.status_code !== 200 &&
29
+ userResult.data.status_code !== 201) {
30
+ return {
31
+ isAuthenticated: false,
32
+ error: userResult.data.message ?? "User not found",
33
+ };
34
+ }
35
+ const { identifier, digest } = userResult.data;
36
+ if (!identifier || !digest) {
37
+ return {
38
+ isAuthenticated: false,
39
+ error: "User verification failed - missing identifier or digest",
40
+ };
41
+ }
42
+ // Step 2: Attempt login (with retry on concurrent session limit)
43
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
44
+ const authResult = await (0, validatePassword_1.validatePassword)({
45
+ identifier,
46
+ digest,
47
+ password,
48
+ });
49
+ if (authResult.error) {
50
+ return {
51
+ isAuthenticated: false,
52
+ error: authResult.error,
53
+ errorReason: authResult.errorReason,
54
+ };
55
+ }
56
+ if (authResult.isAuthenticated && authResult.data?.cookies) {
57
+ return {
58
+ cookies: authResult.data.cookies,
59
+ isAuthenticated: true,
60
+ userInfo: { identifier, digest },
61
+ };
62
+ }
63
+ // Check for concurrent session limit
64
+ const isConcurrent = authResult.data?.isConcurrentLimit ||
65
+ authResult.data?.statusCode === 435;
66
+ if (isConcurrent && attempt < maxRetries) {
67
+ // Auto-terminate existing sessions
68
+ const termResult = await (0, terminateSessions_1.terminateSessions)({
69
+ flowId: authResult.data?.flowId ?? null,
70
+ identifier,
71
+ digest,
72
+ csrfToken: authResult.data?.csrfToken ?? undefined,
73
+ sessionCookie: authResult.data?.sessionCookie ?? undefined,
74
+ });
75
+ // Even if termination "fails", the server sometimes processes it
76
+ // Wait briefly before retrying
77
+ await new Promise((resolve) => setTimeout(resolve, 1000));
78
+ // If this is the last retry attempt, continue to fail gracefully
79
+ if (!termResult.success && attempt === maxRetries - 1) {
80
+ // One more attempt anyway — sometimes termination works server-side
81
+ }
82
+ continue;
83
+ }
84
+ // Not concurrent limit, and not authenticated — something else is wrong
85
+ return {
86
+ isAuthenticated: false,
87
+ error: authResult.data?.message ??
88
+ "Authentication failed",
89
+ };
90
+ }
91
+ return {
92
+ isAuthenticated: false,
93
+ error: "Failed to authenticate after multiple attempts - concurrent session limit could not be resolved",
94
+ };
95
+ }
96
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../../src/auth/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { SessionSeed } from "../type/auth";
2
+ /**
3
+ * Gets a fresh sign-in session from SRM's Zoho IAM.
4
+ * Returns cookies + CSRF token needed for subsequent auth requests.
5
+ */
6
+ export declare function getSigninSessionHeaders(): Promise<SessionSeed>;
7
+ //# sourceMappingURL=sessionSeed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionSeed.d.ts","sourceRoot":"","sources":["../../../src/auth/sessionSeed.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD;;;GAGG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,WAAW,CAAC,CAuCpE"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSigninSessionHeaders = getSigninSessionHeaders;
4
+ /**
5
+ * Gets a fresh sign-in session from SRM's Zoho IAM.
6
+ * Returns cookies + CSRF token needed for subsequent auth requests.
7
+ */
8
+ async function getSigninSessionHeaders() {
9
+ const signinUrl = "https://academia.srmist.edu.in/accounts/p/40-10002227248/signin?hide_fp=true&servicename=ZohoCreator&service_language=en&dcc=true&serviceurl=https%3A%2F%2Facademia.srmist.edu.in%2Fportal%2Facademia-academic-services%2FredirectFromLogin";
10
+ const seedRes = await fetch(signinUrl, {
11
+ method: "GET",
12
+ redirect: "manual",
13
+ headers: {
14
+ accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
15
+ "accept-language": "en-US,en;q=0.9",
16
+ },
17
+ });
18
+ const setCookieHeaders = typeof seedRes.headers.getSetCookie === "function"
19
+ ? seedRes.headers.getSetCookie()
20
+ : [seedRes.headers.get("set-cookie")].filter(Boolean);
21
+ const cookiePairs = [];
22
+ let csrfToken = null;
23
+ for (const header of setCookieHeaders) {
24
+ const pair = header.split(";")[0]?.trim();
25
+ if (!pair || !pair.includes("="))
26
+ continue;
27
+ cookiePairs.push(pair);
28
+ if (pair.startsWith("iamcsr=")) {
29
+ csrfToken = pair.slice("iamcsr=".length);
30
+ }
31
+ if (!csrfToken && pair.startsWith("_zcsr_tmp=")) {
32
+ csrfToken = pair.slice("_zcsr_tmp=".length);
33
+ }
34
+ }
35
+ return {
36
+ cookie: cookiePairs.join("; "),
37
+ csrfToken,
38
+ };
39
+ }
40
+ //# sourceMappingURL=sessionSeed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionSeed.js","sourceRoot":"","sources":["../../../src/auth/sessionSeed.ts"],"names":[],"mappings":";;AAMA,0DAuCC;AA3CD;;;GAGG;AACI,KAAK,UAAU,uBAAuB;IAC3C,MAAM,SAAS,GACb,6OAA6O,CAAC;IAEhP,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;QACrC,MAAM,EAAE,KAAK;QACb,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE;YACP,MAAM,EACJ,iEAAiE;YACnE,iBAAiB,EAAE,gBAAgB;SACpC;KACF,CAAC,CAAC;IAEH,MAAM,gBAAgB,GACpB,OAAO,OAAO,CAAC,OAAO,CAAC,YAAY,KAAK,UAAU;QAChD,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE;QAChC,CAAC,CAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAc,CAAC;IAExE,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,SAAS,GAAkB,IAAI,CAAC;IAEpC,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QAC1C,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAS;QAC3C,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEvB,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC/B,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YAChD,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,OAAO;QACL,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAC9B,SAAS;KACV,CAAC;AACJ,CAAC"}
@@ -1,24 +1,9 @@
1
- export interface TerminateSessionsInput {
2
- /** The concurrent session flow/ticket ID from validatePassword (isConcurrentLimit response) */
3
- flowId: string | null;
4
- /** Zoho identifier from verifyUser */
5
- identifier: string;
6
- /** Zoho digest from verifyUser */
7
- digest: string;
8
- /** Optional CSRF token (value of iamcsr cookie, without the prefix) */
9
- csrfToken?: string;
10
- /** Optional cookie string from validatePassword pre-announcement flow */
11
- sessionCookie?: string;
12
- }
13
-
14
- export interface TerminateSessionsResult {
15
- success: boolean;
16
- status?: number;
17
- /** Which strategy succeeded: "block-sessions" | "webclient-delete" | "none" */
18
- strategy?: string;
19
- data?: unknown;
20
- error?: string;
21
- errorReason?: unknown;
22
- }
23
-
24
- export declare function terminateSessions(params: TerminateSessionsInput): Promise<TerminateSessionsResult>;
1
+ import type { TerminateSessionsInput, TerminateSessionsResult } from "../type/auth";
2
+ /**
3
+ * Terminates all concurrent/existing sessions for a user on SRM Academia.
4
+ *
5
+ * Uses the Zoho preannouncement block-sessions DELETE endpoint (Strategy 3),
6
+ * which is the most reliable. Falls back to POST and webclient DELETE if needed.
7
+ */
8
+ export declare function terminateSessions({ flowId, identifier, digest, csrfToken, sessionCookie, }: TerminateSessionsInput): Promise<TerminateSessionsResult>;
9
+ //# sourceMappingURL=terminateSessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminateSessions.d.ts","sourceRoot":"","sources":["../../../src/auth/terminateSessions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,cAAc,CAAC;AAEtB;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,EACtC,MAAM,EACN,UAAU,EACV,MAAM,EACN,SAAS,EACT,aAAa,GACd,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA6K3D"}
@@ -1,44 +1,67 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.terminateSessions = terminateSessions;
4
-
5
4
  /**
6
5
  * Terminates all concurrent/existing sessions for a user on SRM Academia.
7
6
  *
8
- * SRM uses Zoho Accounts for IAM. When the 2-device limit is hit during login,
9
- * the server redirects to a "block-sessions" preannouncement page where the user
10
- * can terminate old sessions to continue with the new login.
11
- *
12
- * Two strategies are tried in sequence:
13
- *
14
- * Strategy 1 – Block-Sessions page POST (login-time termination):
15
- * POST https://academia.srmist.edu.in/accounts/p/40-10002227248/preannouncement/block-sessions
16
- * with mode=terminate and the current login's digest/identifier.
17
- * This is the endpoint hit when the user clicks "Terminate All Sessions" on the
18
- * preannouncement page during a login attempt.
19
- *
20
- * Strategy 2 – webclient DELETE (session-ticket termination, used in "My Account → Sessions"):
21
- * DELETE https://academia.srmist.edu.in/accounts/p/40-10002227248/webclient/v1/account/self/user/self/session/{SESSION_TICKET}
22
- * with x-zcsrf-token header. Used to delete individual known sessions.
23
- * flowId is used as the session ticket here if provided.
24
- *
25
- * @param {string} flowId - The flow/session ID from validatePassword (concurrent limit response)
26
- * @param {string} identifier - Zoho identifier from verifyUser
27
- * @param {string} digest - Zoho digest from verifyUser
28
- * @param {string} [csrfToken] - Optional x-zcsrf-token value (extracted from iamcsr cookie)
7
+ * Uses the Zoho preannouncement block-sessions DELETE endpoint (Strategy 3),
8
+ * which is the most reliable. Falls back to POST and webclient DELETE if needed.
29
9
  */
30
- async function terminateSessions({ flowId, identifier, digest, csrfToken, sessionCookie }) {
10
+ async function terminateSessions({ flowId, identifier, digest, csrfToken, sessionCookie, }) {
31
11
  const normalizedCsrfToken = csrfToken ??
32
12
  (((sessionCookie ?? "").match(/(?:^|;\s*)iamcsr=([^;]+)/) ??
33
13
  (sessionCookie ?? "").match(/(?:^|;\s*)_zcsr_tmp=([^;]+)/) ??
34
- [])[1] ?? null);
35
- // ── Strategy 1: Block-Sessions preannouncement POST ────────────────────────
36
- // This is what fires when the user clicks "Terminate All Sessions" on the
37
- // SRM concurrent-sessions warning page during a fresh login attempt.
14
+ [])?.[1] ??
15
+ null);
16
+ // ── Strategy 1: Announcement pre-blocksessions DELETE (most reliable) ──
17
+ if (sessionCookie) {
18
+ try {
19
+ const preBlockUrl = "https://academia.srmist.edu.in/accounts/p/40-10002227248/webclient/v1/announcement/pre/blocksessions";
20
+ const res = await fetch(preBlockUrl, {
21
+ method: "DELETE",
22
+ headers: {
23
+ accept: "application/json, text/javascript, */*; q=0.01",
24
+ "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
25
+ ...(normalizedCsrfToken
26
+ ? {
27
+ "X-ZCSRF-TOKEN": `iamcsrcoo=${encodeURIComponent(normalizedCsrfToken)}`,
28
+ }
29
+ : {}),
30
+ cookie: sessionCookie,
31
+ Referer: "https://academia.srmist.edu.in/accounts/p/40-10002227248/preannouncement/block-sessions",
32
+ "Referrer-Policy": "strict-origin-when-cross-origin",
33
+ },
34
+ });
35
+ let data = null;
36
+ try {
37
+ data = (await res.json());
38
+ }
39
+ catch {
40
+ // Empty body is fine
41
+ }
42
+ if (res.ok ||
43
+ res.status === 200 ||
44
+ res.status === 202 ||
45
+ res.status === 204 ||
46
+ (data &&
47
+ (String(data.status_code) === "204" ||
48
+ data.code === "SUCCESS" ||
49
+ data.code === "SES200"))) {
50
+ return {
51
+ success: true,
52
+ status: res.status,
53
+ strategy: "announcement-pre-blocksessions",
54
+ data,
55
+ };
56
+ }
57
+ }
58
+ catch {
59
+ // Continue to next strategy
60
+ }
61
+ }
62
+ // ── Strategy 2: Block-Sessions preannouncement POST ───────────────────
38
63
  try {
39
- const blockSessionsUrl =
40
- "https://academia.srmist.edu.in/accounts/p/40-10002227248/preannouncement/block-sessions";
41
-
64
+ const blockSessionsUrl = "https://academia.srmist.edu.in/accounts/p/40-10002227248/preannouncement/block-sessions";
42
65
  const body = new URLSearchParams({
43
66
  mode: "terminate",
44
67
  identifier: identifier ?? "",
@@ -46,14 +69,11 @@ async function terminateSessions({ flowId, identifier, digest, csrfToken, sessio
46
69
  cli_time: String(Date.now()),
47
70
  servicename: "ZohoCreator",
48
71
  service_language: "en",
49
- serviceurl:
50
- "https://academia.srmist.edu.in/portal/academia-academic-services/redirectFromLogin",
72
+ serviceurl: "https://academia.srmist.edu.in/portal/academia-academic-services/redirectFromLogin",
51
73
  });
52
-
53
- // Append flowId if we have it (some Zoho versions need it in the body)
54
- if (flowId) body.append("flowId", flowId);
55
-
56
- const res1 = await fetch(blockSessionsUrl, {
74
+ if (flowId)
75
+ body.append("flowId", flowId);
76
+ const res = await fetch(blockSessionsUrl, {
57
77
  method: "POST",
58
78
  headers: {
59
79
  accept: "*/*",
@@ -66,32 +86,38 @@ async function terminateSessions({ flowId, identifier, digest, csrfToken, sessio
66
86
  ? { "x-zcsrf-token": `iamcsrcoo=${normalizedCsrfToken}` }
67
87
  : {}),
68
88
  ...(sessionCookie ? { cookie: sessionCookie } : {}),
69
- Referer:
70
- "https://academia.srmist.edu.in/accounts/p/40-10002227248/signin?hide_fp=true&servicename=ZohoCreator&service_language=en&dcc=true&serviceurl=https%3A%2F%2Facademia.srmist.edu.in%2Fportal%2Facademia-academic-services%2FredirectFromLogin",
89
+ Referer: "https://academia.srmist.edu.in/accounts/p/40-10002227248/signin?hide_fp=true&servicename=ZohoCreator&service_language=en&dcc=true&serviceurl=https%3A%2F%2Facademia.srmist.edu.in%2Fportal%2Facademia-academic-services%2FredirectFromLogin",
71
90
  "Referrer-Policy": "strict-origin-when-cross-origin",
72
91
  },
73
92
  body: body.toString(),
74
93
  });
75
-
76
- if (res1.ok || res1.status === 200 || res1.status === 201 || res1.redirected) {
94
+ if (res.ok ||
95
+ res.status === 200 ||
96
+ res.status === 201 ||
97
+ res.redirected) {
77
98
  let data = null;
78
- try { data = await res1.json(); } catch { /* redirect or empty body is fine */ }
79
- return { success: true, status: res1.status, strategy: "block-sessions", data };
99
+ try {
100
+ data = await res.json();
101
+ }
102
+ catch {
103
+ // redirect or empty body is fine
104
+ }
105
+ return {
106
+ success: true,
107
+ status: res.status,
108
+ strategy: "block-sessions",
109
+ data,
110
+ };
80
111
  }
81
-
82
- console.warn("[terminateSessions] Strategy 1 (block-sessions POST) returned:", res1.status);
83
- } catch (e1) {
84
- console.warn("[terminateSessions] Strategy 1 error:", e1);
85
112
  }
86
-
87
- // ── Strategy 2: webclient DELETE by session ticket ─────────────────────────
88
- // Used when we have a specific session ticket (flowId acts as session ticket).
89
- // This is what the "My Account Sessions" page uses to delete individual sessions.
113
+ catch {
114
+ // Continue to next strategy
115
+ }
116
+ // ── Strategy 3: webclient DELETE by session ticket ─────────────────────
90
117
  if (flowId) {
91
118
  try {
92
119
  const deleteUrl = `https://academia.srmist.edu.in/accounts/p/40-10002227248/webclient/v1/account/self/user/self/session/${encodeURIComponent(flowId)}`;
93
-
94
- const res2 = await fetch(deleteUrl, {
120
+ const res = await fetch(deleteUrl, {
95
121
  method: "DELETE",
96
122
  headers: {
97
123
  accept: "application/json, text/javascript, */*; q=0.01",
@@ -105,56 +131,33 @@ async function terminateSessions({ flowId, identifier, digest, csrfToken, sessio
105
131
  "Referrer-Policy": "strict-origin-when-cross-origin",
106
132
  },
107
133
  });
108
-
109
- if (res2.ok || res2.status === 200 || res2.status === 202 || res2.status === 204) {
134
+ if (res.ok ||
135
+ res.status === 200 ||
136
+ res.status === 202 ||
137
+ res.status === 204) {
110
138
  let data = null;
111
- try { data = await res2.json(); } catch { /* 204 No Content is fine */ }
112
- return { success: true, status: res2.status, strategy: "webclient-delete", data };
139
+ try {
140
+ data = await res.json();
141
+ }
142
+ catch {
143
+ // 204 No Content is fine
144
+ }
145
+ return {
146
+ success: true,
147
+ status: res.status,
148
+ strategy: "webclient-delete",
149
+ data,
150
+ };
113
151
  }
114
-
115
- console.warn("[terminateSessions] Strategy 2 (webclient DELETE) returned:", res2.status);
116
- } catch (e2) {
117
- console.warn("[terminateSessions] Strategy 2 error:", e2);
118
152
  }
119
- }
120
- // ── Strategy 3: Announcement pre-blocksessions DELETE ───────────────────────
121
- // This is what current SRM preannouncement page JS calls.
122
- if (sessionCookie) {
123
- try {
124
- const preBlockUrl = "https://academia.srmist.edu.in/accounts/p/40-10002227248/webclient/v1/announcement/pre/blocksessions";
125
- const res3 = await fetch(preBlockUrl, {
126
- method: "DELETE",
127
- headers: {
128
- accept: "application/json, text/javascript, */*; q=0.01",
129
- "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
130
- ...(normalizedCsrfToken
131
- ? { "X-ZCSRF-TOKEN": `iamcsrcoo=${encodeURIComponent(normalizedCsrfToken)}` }
132
- : {}),
133
- cookie: sessionCookie,
134
- Referer: "https://academia.srmist.edu.in/accounts/p/40-10002227248/preannouncement/block-sessions",
135
- "Referrer-Policy": "strict-origin-when-cross-origin",
136
- },
137
- });
138
- let data = null;
139
- try {
140
- data = await res3.json();
141
- } catch (_) { }
142
- if (res3.ok || res3.status === 200 || res3.status === 202 || res3.status === 204 ||
143
- (data && (String(data.status_code) === "204" || data.code === "SUCCESS"))) {
144
- return { success: true, status: res3.status, strategy: "announcement-pre-blocksessions", data };
145
- }
146
- console.warn("[terminateSessions] Strategy 3 (announcement pre-blocksessions DELETE) returned:", res3.status, data);
147
- } catch (e3) {
148
- console.warn("[terminateSessions] Strategy 3 error:", e3);
153
+ catch {
154
+ // All strategies exhausted
149
155
  }
150
156
  }
151
-
152
- // Both strategies failed — return a non-fatal "best-effort" result.
153
- // The caller (handleTerminateAndRetry) will still attempt to re-login because
154
- // sometimes the termination succeeds server-side even if we get an odd HTTP response.
155
157
  return {
156
158
  success: false,
157
- error: "Both termination strategies failed — re-login will still be attempted",
159
+ error: "All termination strategies failed — re-login will still be attempted",
158
160
  strategy: "none",
159
161
  };
160
162
  }
163
+ //# sourceMappingURL=terminateSessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminateSessions.js","sourceRoot":"","sources":["../../../src/auth/terminateSessions.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { PasswordInput, AuthResult } from "../type/auth";
1
+ import type { AuthResult, PasswordInput } from "../type/auth";
2
2
  export declare function validatePassword({ identifier, digest, password, }: PasswordInput): Promise<AuthResult>;
3
3
  //# sourceMappingURL=validatePassword.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validatePassword.d.ts","sourceRoot":"","sources":["../../../src/auth/validatePassword.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAoB,UAAU,EAAE,MAAM,cAAc,CAAC;AAE3E,wBAAsB,gBAAgB,CAAC,EACrC,UAAU,EACV,MAAM,EACN,QAAQ,GACT,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CA8DrC"}
1
+ {"version":3,"file":"validatePassword.d.ts","sourceRoot":"","sources":["../../../src/auth/validatePassword.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAgE9D,wBAAsB,gBAAgB,CAAC,EACrC,UAAU,EACV,MAAM,EACN,QAAQ,GACT,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAgUrC"}