"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Openmagicline: () => Openmagicline, _log: () => _log }); module.exports = __toCommonJS(src_exports); var import_axios_auth_refresh = __toESM(require("axios-auth-refresh")); var import_axios = __toESM(require("axios")); var import_once = __toESM(require("lodash/once")); var import_debug = __toESM(require("debug")); // src/util.ts var import_form_data = __toESM(require("form-data")); // src/constants.ts var DEFAULT_UNIT_ID = 1; // src/util.ts var Util = class { constructor(axios, mgl) { this.axios = axios; this.mgl = mgl; } async getDefaultUnitID() { const data = await this.mgl.organization.permitted(); return data.listChildren[0].databaseId ?? DEFAULT_UNIT_ID; } /** * check if the login token works. */ async testLogin() { try { await this.mgl.locale.currentLocale(); return true; } catch { return false; } } }; var headers = (mgl) => { const u = new URL(mgl.baseUrl); const returnValue = { authority: u.hostname, "sec-ch-ua": `"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"`, accept: `application/json, text/javascript, */*; q=0.01`, "x-requested-with": `XMLHttpRequest`, "sec-ch-ua-mobile": `?0`, "user-agent": `Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36`, origin: u.href, "sec-fetch-site": `same-origin`, "sec-fetch-mode": `cors`, "sec-fetch-dest": `empty`, referer: u.href, "accept-language": `en-CA,en-US;q=0.9,en;q=0.8,de-DE;q=0.7,de;q=0.6,en-GB;q=0.5` }; if (mgl.cookies) returnValue.cookie = mgl.cookies.join(""); return returnValue; }; var websocketHeaders = (mgl) => { const u = new URL(mgl.baseUrl); const returnValue = { Pragma: "no-cache", Origin: u.href, "Accept-Language": "en-CA,en-US;q=0.9,en;q=0.8,de-DE;q=0.7,de;q=0.6,en-GB;q=0.5", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36", "Cache-Control": "no-cache" }; if (mgl.cookies) returnValue.cookie = mgl.cookies.join(""); return returnValue; }; var searchParameters = (data) => { const parameters = new URLSearchParams(); for (const [key, value] of Object.entries(data)) parameters.set(key, value); return parameters; }; // src/locale.ts var Locale = class { constructor(axios) { this.axios = axios; } async currentLocale() { const { data } = await this.axios.get("currentLocale"); return data; } async supportedLocales() { const { data } = await this.axios.get("supportedLocales"); return data; } }; // src/organization.ts var Organization = class { constructor(axios, mgl) { this.axios = axios; this.mgl = mgl; } async permitted() { const { data } = await this.axios("organizationunit/permitted"); return data; } async accountInfo() { const { data } = await this.axios("me/info"); return data; } async apps(unitID) { if (!unitID) unitID = await this.mgl.util.getDefaultUnitID(); const { data } = await this.axios("app", { params: { organizationUnitId: unitID } }); return data; } }; // src/customer.ts var Customer = class { constructor(axios, mgl) { this.axios = axios; this.mgl = mgl; } defaultSearchOptions = { facility: 0, searchInName: true, searchInCustomerNumber: true, searchInAddress: false, searchInBankAccount: false, searchInCardNumber: false, searchInLockerKey: false, searchInPurchasedContingentCode: false, showAllFacilities: true, showCheckedIn: false, showOnlyMembers: false }; async search(searchString, options) { const { data } = await this.axios.post("customersearch", { ...this.defaultSearchOptions, ...options, searchString }); return data; } async getCards(customerID) { const { data } = await this.axios(`customer/${customerID}/accessidentification`); return data; } /** * get contracts of a customer * @param customerId customer id * @param isActive get only active contracts (default: `true`) */ contract = async (customerId, isActive = true) => { const { data } = await this.axios.get("contract", { params: { customerId, isActive } }); return data; }; checkinConditions = async (customerId, organizationUnitId) => { if (typeof organizationUnitId !== "number") { organizationUnitId = await this.mgl.util.getDefaultUnitID(); } const { data } = await this.axios.get(`customer/${customerId}/conditions/checkin`, { params: { organizationUnitId } }); return data; }; benefits = async (customerId, active = "both") => { const returnList = []; if (active === true || active === "both") { const { data } = await this.axios.get("benefitaccount", { params: { customerId, active: true } }); returnList.push(...data); } if (active === false || active === "both") { const { data } = await this.axios.get("benefitaccount", { params: { customerId, active: false } }); returnList.push(...data); } return returnList; }; detailedBalance = async (customerId) => { const { data } = await this.axios.get(`customer/${customerId}/balance/detailed`); return data; }; // todo: implement card methods //? removed until more card methods are implemented // removeCard( // customerID: Openmagicline.Customer.CustomerID, // AccessIdentificationID: Openmagicline.Customer.AccessIdentificationID // ): Promise { // return this.got(`customer/${customerID}/accessidentification/${AccessIdentificationID}`, { // method: "DELETE", // searchParams: { // optLockRemote: 0, // }, // }).json() // } }; // src/checkin.ts var Checkin = class { constructor(axios, mgl) { this.axios = axios; this.mgl = mgl; } defaultListParams = { organizationUnitId: DEFAULT_UNIT_ID, checkouts: false, offset: 0, maxResults: 25, search: "", filter: "", sortedby: "checkinTime", direction: "DESCENDING" }; /** * list all checked-in customers * @param options filter, sort, etc. */ async list(options) { let organizationUnitId = options?.organizationUnitId; if (typeof organizationUnitId !== "number") { organizationUnitId = await this.mgl.util.getDefaultUnitID(); } const { data } = await this.axios("checkin", { params: { ...this.defaultListParams, organizationUnitId, ...options } }); return data; } defaultCheckinParams = { customerCardNumber: void 0, customerUUID: "", fkCustomer: 0, fkDevice: void 0, fkOrganizationUnit: DEFAULT_UNIT_ID, lockerKey: "", purchasedContingentCode: void 0, databaseId: void 0, optlock: 0, requiredOrganizationUnitId: DEFAULT_UNIT_ID }; /** * check-in a customer */ async checkin(options) { let unitID = options.requiredOrganizationUnitId ?? options.fkOrganizationUnit; if (typeof unitID !== "number") { unitID = await this.mgl.util.getDefaultUnitID(); } const { data } = await this.axios.post("checkin", { ...this.defaultCheckinParams, fkOrganizationUnit: unitID, requiredOrganizationUnitId: unitID, ...options }); return data; } /** * check-out a customer * @param checkinId the ID of the checkin, **not** the customer ID * @param options optional object containing optLockRemote, not sure what it does */ async checkout(checkinId, options) { const { data } = await this.axios.delete(`checkin/${checkinId}`, { params: { ...options } }); return data; } defaultLockerKeyParams = { databaseId: void 0, optlock: 0 }; async lockerKey(checkinId, lockerKey, options) { const { data } = await this.axios.put(`checkin/lockerkey/${checkinId}`, { ...this.defaultLockerKeyParams, checkinId, lockerKey, ...options }); return data; } }; // src/sales.ts var Sales = class { constructor(axios, mgl) { this.axios = axios; this.mgl = mgl; } async products(options) { const organizationUnitId = options?.organizationUnitId ?? await this.mgl.util.getDefaultUnitID(); const { data } = await this.axios("sales/productoverview", { params: { organizationUnitId, ...options } }); return data; } }; // src/socket.ts var import_stompjs = require("@stomp/stompjs"); var import_ws = __toESM(require("ws")); var MagicSocket = class { constructor(mgl, unitID) { this.mgl = mgl; this.client = new import_stompjs.Client({ debug: this.log, heartbeatIncoming: 1e4, heartbeatOutgoing: 1e4, webSocketFactory: this.webSocketFactory(mgl.baseUrl, unitID), onWebSocketError: ( /* istanbul ignore next */ (error) => { console.log(error.target._req.res); throw error; } ), onStompError: ( /* istanbul ignore next */ (error) => { throw error; } ) }); } client; log = _log.extend("socket"); isActive = false; webSocketFactory = (baseUrl, unitID) => () => { const socketURL = new URL(baseUrl); socketURL.protocol = "wss:"; socketURL.pathname = "ws"; socketURL.searchParams.set("organizationUnitId", unitID.toString()); this.log(`connecting to ${socketURL.href}`); return new import_ws.default(socketURL.href, [], { headers: websocketHeaders(this.mgl) }); }; subscriptions = {}; activate() { if (this.isActive) return this.isActive; this.isActive = new Promise((resolve) => { this.client.onConnect = resolve; this.client.activate(); }); return this.isActive; } unsubscribeAll() { for (const [key, unsubscribe] of Object.entries(this.subscriptions)) { unsubscribe(); delete this.subscriptions[key]; } return this.deactivate(); } deactivate() { this.log("deactivating STOMP"); this.isActive = false; return this.client.deactivate(); } deactivateAutomatically() { if (Object.entries(this.subscriptions).length === 0) this.deactivate(); else this.log("there are still active subscriptions, keeping stomp active"); } /* eslint-disable @typescript-eslint/indent */ subscribeFactory = (topic) => async (callback) => { await this.activate(); this.client.subscribe(topic, ({ body }) => callback(JSON.parse(body))); this.log(`subscribed to "${topic}"`); const unsubscribe = () => { this.client.unsubscribe(topic); delete this.subscriptions[topic]; this.deactivateAutomatically(); }; this.subscriptions[topic] = unsubscribe; return this.subscriptions[topic]; }; /* eslint-enable @typescript-eslint/indent */ onCheckin = this.subscribeFactory("/user/topic/checkin"); }; // src/index.ts var _log = (0, import_debug.default)("openmagicline"); var Openmagicline = class { // TODO: check version and warn if openmagicline is outdated constructor(config, axios) { this.config = config; this.log = _log.extend(config.gym); this.baseUrl = `https://${this.config.gym}.web.magicline.com`; const prefixUrl = `${this.baseUrl}/rest-api`; const httpAxiosLog = this.log.extend("http"); if (axios) this.axios = axios; else { this.axios = import_axios.default.create({ baseURL: prefixUrl, headers: headers(this) }); } this.axios.interceptors.request.use((config2) => { if (this.cookies) config2.headers.cookie = this.cookies; return config2; }); this.axios.interceptors.response.use((response) => { let log = `[${response.config.method}](${response.status}) `; log += response.config.url; if (response.status > 200) log += ` ${response.data}`; httpAxiosLog(log); return response; }); (0, import_axios_auth_refresh.default)(this.axios, () => { console.log("request failed, refreshing token"); return this.login(); }); this.customer = new Customer(this.axios, this); this.locale = new Locale(this.axios); this.organization = new Organization(this.axios, this); this.checkin = new Checkin(this.axios, this); this.util = new Util(this.axios, this); this.sales = new Sales(this.axios, this); this.disposal = this.sales; this.socket = (unitID) => new MagicSocket(this, unitID); } log; axios; baseUrl; cookies; customer; locale; organization; /** everything related to the checkin process */ checkin; /** miscellaneous helpers and thingies */ util; /** everything related to retail sales (magicline calls this disposal in some places) */ sales; /** reference to this.sales */ disposal; /** event handler for magiclines websockets */ socket; _login = async (cookies) => { if (cookies) { this.cookies = cookies; this.login = (0, import_once.default)(this._login); if (await this.util.testLogin()) { return this; } else { this.cookies = void 0; throw new Error("invalid token"); } } try { const { username, password } = this.config; if (!username || !password) throw new Error("username and password need to be set when cookies aren't provided"); const response = await this.axios.post( "login", searchParameters({ username, password, client: "webclient" }), // @ts-expect-error i am too lazy to fix these types ngl { baseURL: this.baseUrl, skipAuthRefresh: true } ); this.login = (0, import_once.default)(this._login); this.cookies = response.headers["set-cookie"]; } catch (error_) { this.cookies = void 0; const error = error_?.response?.data?.error_description ? new Error(error_.response.data.error_description) : error_; throw error; } return this; }; /** * authenticate the Openmagicline instance using username/password from the instance config. * * if a token is passed, it will be validated and the request to `/login` will be skipped. * @param cookies existing cookies, available after login at `.cookies` * @returns instance for chaining * @throws when not authenticated */ login = (0, import_once.default)(this._login); }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Openmagicline, _log });