import { NotLoggedIn, SessionExpired, SessionError, AccountAPIError, LoginError, APIError } from "./exceptions.js";
import { RegisteredSubject, Registrations } from "./registration.js";
import { AttendanceMeta, AttendanceHeader, Semester } from "./attendance.js";
import { ExamEvent } from "./exam.js";
import { generate_local_name, serialize_payload } from "./encryption.js";
/**
* @module Wrapper
*/
/**
* Base API endpoint for the JIIT web portal
* @constant {string}
*/
export const API = "https://webportal.jiit.ac.in:6011/StudentPortalAPI";
/**
* Default CAPTCHA values used for login
* @constant {{captcha: string, hidden: string}}
*/
export const DEFCAPTCHA = { captcha: "phw5n", hidden: "gmBctEffdSg=" };
/**
* Class representing a session with the web portal
*/
export class WebPortalSession {
/**
* Creates a WebPortalSession instance from API response
* @param {Object} resp - Response object from login API
* @param {Object} resp.regdata - Registration data containing user details
* @param {Array} resp.regdata.institutelist - List of institutes user has access to
* @param {string} resp.regdata.memberid - Member ID of the user
* @param {string} resp.regdata.userid - User ID
* @param {string} resp.regdata.token - Token for authentication
* @param {string} resp.regdata.clientid - Client ID
* @param {string} resp.regdata.membertype - Type of member
* @param {string} resp.regdata.name - Name of the user
* @param {string} resp.regdata.enrollmentno - Enrollment number
*/
constructor(resp) {
this.raw_response = resp;
this.regdata = resp["regdata"];
let institute = this.regdata["institutelist"][0];
this.institute = institute["label"];
this.instituteid = institute["value"];
this.memberid = this.regdata["memberid"];
this.userid = this.regdata["userid"];
this.token = this.regdata["token"];
let expiry_timestamp = JSON.parse(atob(this.token.split(".")[1]))["exp"];
this.expiry = new Date(expiry_timestamp * 1000); // In JavaScript, Date expects milliseconds
this.clientid = this.regdata["clientid"];
this.membertype = this.regdata["membertype"];
this.name = this.regdata["name"];
this.enrollmentno = this.regdata["enrollmentno"];
}
/**
* Generates authentication headers for API requests
* @returns {Promise<Object>} Headers object containing Authorization and LocalName
*/
async get_headers() {
const localname = await generate_local_name();
return {
Authorization: `Bearer ${this.token}`,
LocalName: localname,
};
}
}
/**
* Main class for interacting with the JIIT web portal API
*/
export class WebPortal {
/**
* Creates a WebPortal instance
*/
constructor() {
this.session = null;
}
/**
* Internal method to make HTTP requests to the API
* @private
* @param {string} method - HTTP method (GET, POST etc)
* @param {string} url - API endpoint URL
* @param {Object} [options={}] - Request options
* @param {Object} [options.headers] - Additional headers
* @param {Object} [options.json] - JSON payload
* @param {string} [options.body] - Raw body payload
* @param {boolean} [options.authenticated] - Whether request needs authentication
* @param {Error} [options.exception] - Custom error class to throw
* @returns {Promise<Object>} API response
* @throws {APIError} On API or network errors
*/
async __hit(method, url, options = {}) {
let exception = APIError; // Default exception
if (options.exception) {
exception = options.exception;
delete options.exception;
}
console.log(options)
let header;
if (options.authenticated) {
header = await this.session.get_headers(); // Assumes calling method is authenticated
delete options.authenticated;
} else {
let localname = await generate_local_name();
header = { LocalName: localname };
}
if (options.headers) {
options.headers = { ...options.headers, ...header };
} else {
options.headers = header;
}
let fetchOptions = {
method: method,
headers: {
"Content-Type": "application/json",
...options.headers,
},
};
if (options.json) {
fetchOptions.body = JSON.stringify(options.json);
} else {
fetchOptions.body = options.body;
}
try {
console.log("fetching", url, "with options", fetchOptions);
const response = await fetch(url, fetchOptions);
if (response.status === 513) {
throw new exception("JIIT Web Portal server is temporarily unavailable (HTTP 513). Please try again later.");
}
if (response.status === 401) {
throw new SessionExpired(response.error);
}
const resp = await response.json();
if (resp.status && resp.status.responseStatus !== "Success") {
throw new exception(`status:\n${JSON.stringify(resp.status, null, 2)}`);
}
return resp;
} catch (error) {
// Handle CORS errors
if (error instanceof TypeError && error.message.includes('CORS')) {
throw new exception("JIIT Web Portal server is temporarily unavailable. Please try again later.");
}
throw new exception(error.message || "Unknown error");
}
}
/**
* Logs in a student user
* @param {string} username - Student username
* @param {string} password - Student password
* @param {{captcha: string, hidden: string}} [captcha=DEFCAPTCHA] - CAPTCHA
* @returns {Promise<WebPortalSession>} New session instance
* @throws {LoginError} On login failure
*/
async student_login(username, password, captcha = DEFCAPTCHA) {
let pretoken_endpoint = "/token/pretoken-check";
let token_endpoint = "/token/generate-token1";
let payload = { username: username, usertype: "S", captcha: captcha };
payload = await serialize_payload(payload);
let resp = await this.__hit("POST", API + pretoken_endpoint, { body: payload, exception: LoginError });
let payload2 = resp["response"];
delete payload2["rejectedData"];
payload2["Modulename"] = "STUDENTMODULE";
payload2["passwordotpvalue"] = password;
payload2 = await serialize_payload(payload2);
const resp2 = await this.__hit("POST", API + token_endpoint, { body: payload2, exception: LoginError });
this.session = new WebPortalSession(resp2["response"]);
return this.session;
}
/**
* Gets personal information of logged in student
* @returns {Promise<Object>} Student personal information
*/
async get_personal_info() {
const ENDPOINT = "/studentpersinfo/getstudent-personalinformation";
const payload = {
clinetid: "SOAU",
instituteid: this.session.instituteid,
};
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"];
}
/**
* Gets bank account information of logged in student
* @returns {Promise<Object>} Student bank information
*/
async get_student_bank_info() {
const ENDPOINT = "/studentbankdetails/getstudentbankinfo";
const payload = {
instituteid: this.session.instituteid,
studentid: this.session.memberid,
};
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"];
}
/**
* Changes password for logged in student
* @param {string} old_password - Current password
* @param {string} new_password - New password
* @returns {Promise<Object>} Response indicating success/failure
* @throws {AccountAPIError} On password change failure
*/
async change_password(old_password, new_password) {
const ENDPOINT = "/clxuser/changepassword";
const payload = {
membertype: this.session.membertype,
oldpassword: old_password,
newpassword: new_password,
confirmpassword: new_password,
};
const resp = await this.__hit("POST", API + ENDPOINT, {
json: payload,
authenticated: true,
exception: AccountAPIError,
});
return resp["response"];
}
/**
* Gets attendance metadata including headers and semesters
* @returns {Promise<AttendanceMeta>} Attendance metadata
*/
async get_attendance_meta() {
const ENDPOINT = "/StudentClassAttendance/getstudentInforegistrationforattendence";
const payload = {
clientid: this.session.clientid,
instituteid: this.session.instituteid,
membertype: this.session.membertype,
};
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return new AttendanceMeta(resp["response"]);
}
/**
* Gets attendance details for a semester
* @param {AttendanceHeader} header - Attendance header
* @param {Semester} semester - Semester object
* @returns {Promise<Object>} Attendance details
*/
async get_attendance(header, semester) {
const ENDPOINT = "/StudentClassAttendance/getstudentattendancedetail";
const payload = await serialize_payload({
clientid: this.session.clientid,
instituteid: this.session.instituteid,
registrationcode: semester.registration_code,
registrationid: semester.registration_id,
stynumber: header.stynumber,
});
// console.log(payload)
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"];
}
/**
* Gets attendance for every class of the subject for the semester.
* @param {Semester} semester - Semester object
* @param {string} subjectid - Subject ID
* @param {string} individualsubjectcode - Individual subject code
* @param {Array<string>} subjectcomponentids - Array of subject component IDs
* @returns {Promise<Object>} Subject attendance details
*/
async get_subject_daily_attendance(semester, subjectid, individualsubjectcode, subjectcomponentids) {
const ENDPOINT = "/StudentClassAttendance/getstudentsubjectpersentage";
const payload = await serialize_payload({
cmpidkey: subjectcomponentids.map((id) => ({ subjectcomponentid: id })),
clientid: this.session.clientid,
instituteid: this.session.instituteid,
registrationcode: semester.registration_code,
registrationid: semester.registration_id,
subjectcode: individualsubjectcode,
subjectid: subjectid,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"];
}
/**
* Gets list of registered semesters
* @returns {Promise<Array<Semester>>} Array of semester objects
*/
async get_registered_semesters() {
const ENDPOINT = "/reqsubfaculty/getregistrationList";
const payload = await serialize_payload({
instituteid: this.session.instituteid,
studentid: this.session.memberid,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"]["registrations"].map((i) => Semester.from_json(i));
}
/**
* Gets registered subjects and faculty details for a semester
* @param {Semester} semester - Semester object
* @returns {Promise<Registrations>} Registration details
*/
async get_registered_subjects_and_faculties(semester) {
const ENDPOINT = "/reqsubfaculty/getfaculties";
const payload = await serialize_payload({
instituteid: this.session.instituteid,
studentid: this.session.memberid,
registrationid: semester.registration_id,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return new Registrations(resp["response"]);
}
/**
* Gets semesters that have exam events
* @returns {Promise<Array<Semester>>} Array of semester objects
*/
async get_semesters_for_exam_events() {
const ENDPOINT = "/studentcommonsontroller/getsemestercode-withstudentexamevents";
const payload = await serialize_payload({
clientid: this.session.clientid,
instituteid: this.session.instituteid,
memberid: this.session.memberid,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"]["semesterCodeinfo"]["semestercode"].map((i) => Semester.from_json(i));
}
/**
* Gets exam events for a semester
* @param {Semester} semester - Semester object
* @returns {Promise<Array<ExamEvent>>} Array of exam event objects
*/
async get_exam_events(semester) {
const ENDPOINT = "/studentcommonsontroller/getstudentexamevents";
const payload = await serialize_payload({
instituteid: this.session.instituteid,
registationid: semester.registration_id, // not a typo
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"]["eventcode"]["examevent"].map((i) => ExamEvent.from_json(i));
}
/**
* Gets exam schedule for an exam event
* @param {ExamEvent} exam_event - Exam event object
* @returns {Promise<Object>} Exam schedule details
*/
async get_exam_schedule(exam_event) {
const ENDPOINT = "/studentsttattview/getstudent-examschedule";
const payload = await serialize_payload({
instituteid: this.session.instituteid,
registrationid: exam_event.registration_id,
exameventid: exam_event.exam_event_id,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"];
}
/**
* Gets semesters that have marks available
* @returns {Promise<Array<Semester>>} Array of semester objects
*/
async get_semesters_for_marks() {
const ENDPOINT = "/studentcommonsontroller/getsemestercode-exammarks";
const payload = await serialize_payload({
instituteid: this.session.instituteid,
studentid: this.session.memberid,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"]["semestercode"].map((i) => Semester.from_json(i));
}
/**
* Downloads marks PDF for a semester
* @param {Semester} semester - Semester object
* @throws {APIError} On download failure
*/
async download_marks(semester) {
const ENDPOINT =
"/studentsexamview/printstudent-exammarks/" +
// this.session.memberid +
// "/" +
this.session.instituteid +
"/" +
semester.registration_id +
"/" +
semester.registration_code;
const localname = await generate_local_name();
let _headers = await this.session.get_headers(localname);
const fetchOptions = {
method: "GET",
headers: _headers,
};
try {
const resp = await fetch(API + ENDPOINT, fetchOptions);
const blob = await resp.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `marks_${semester.registration_code}.pdf`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
a.remove();
} catch (error) {
throw new APIError(error);
}
}
/**
* Gets semesters that have grade cards available
* @returns {Promise<Array<Semester>>} Array of semester objects
*/
async get_semesters_for_grade_card() {
const ENDPOINT = "/studentgradecard/getregistrationList";
const payload = await serialize_payload({
instituteid: this.session.instituteid,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"]["registrations"].map((i) => Semester.from_json(i));
}
/**
* Gets program ID for grade card
* @private
* @returns {Promise<string>} Program ID
*/
async __get_program_id() {
const ENDPOINT = "/studentgradecard/getstudentinfo";
const payload = await serialize_payload({
instituteid: this.session.instituteid,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"]["programid"];
}
/**
* Gets grade card for a semester
* @param {Semester} semester - Semester object
* @returns {Promise<Object>} Grade card details
*/
async get_grade_card(semester) {
const programid = await this.__get_program_id();
const ENDPOINT = "/studentgradecard/showstudentgradecard";
const payload = await serialize_payload({
branchid: this.session.branch_id,
instituteid: this.session.instituteid,
programid: programid,
registrationid: semester.registration_id,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"];
}
/**
* Gets current semester number
* @private
* @returns {Promise<number>} Current semester number
*/
async __get_semester_number() {
const ENDPOINT = "/studentsgpacgpa/checkIfstudentmasterexist";
const payload = await serialize_payload({
instituteid: this.session.instituteid,
studentid: this.session.memberid,
name: this.session.name,
enrollmentno: this.session.enrollmentno,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"]["studentlov"]["currentsemester"];
}
/**
* Gets SGPA and CGPA details
* @returns {Promise<Object>} SGPA and CGPA details
*/
async get_sgpa_cgpa() {
const ENDPOINT = "/studentsgpacgpa/getallsemesterdata";
const stynumber = await this.__get_semester_number();
const payload = await serialize_payload({
instituteid: this.session.instituteid,
studentid: this.session.memberid,
stynumber: stynumber,
});
const resp = await this.__hit("POST", API + ENDPOINT, { json: payload, authenticated: true });
return resp["response"];
}
}
/**
* Decorator that checks if user is authenticated before executing method
* @param {Function} method - Method to decorate
* @returns {Function} Decorated method that checks authentication
* @throws {NotLoggedIn} If user is not logged in
*/
function authenticated(method) {
return function (...args) {
if (this.session == null) {
throw new NotLoggedIn();
}
return method.apply(this, args);
};
}
/**
* List of methods that require authentication
* @constant {Array<string>}
*/
const authenticatedMethods = [
"get_personal_info",
"get_student_bank_info",
"change_password",
"get_attendance_meta",
"get_attendance",
"get_subject_daily_attendance",
"get_registered_semesters",
"get_registered_subjects_and_faculties",
"get_semesters_for_exam_events",
"get_exam_events",
"get_exam_schedule",
"get_semesters_for_marks",
"download_marks",
"get_semesters_for_grade_card",
"__get_program_id",
"get_grade_card",
"__get_semester_number",
"get_sgpa_cgpa",
];
authenticatedMethods.forEach((methodName) => {
WebPortal.prototype[methodName] = authenticated(WebPortal.prototype[methodName]);
});