src/index.js
- import Encode from './DSBEncoding';
- import Decode from './DSBDecode';
- import percentage from 'percentage-calc';
-
- /**
- * Main Library class
- */
- export default class DSB {
- /**
- *
- * @param {String|Number} username
- * @param {String|Number} password
- * @param {String} [cookies=""] If you already have session cookies, you can add them here.
- * @param {String|Boolean} [cache=false] In the browser just a boolean and in node a path string. If you don't want to use any cache just use undefined, null or false.
- * @param {Axios} [axios=require('axios')] Pass your custom axios instance if you want.
- */
- constructor(
- username,
- password,
- cookies = '',
- cache = false,
- axios = require('axios')
- ) {
- /**
- * @private
- */
- this.username = username;
- /**
- * @private
- */
- this.password = password;
- /**
- * @private
- */
- this.axios = axios;
- /**
- * @private
- */
- this.urls = {
- login: 'https://mobile.dsbcontrol.de/dsbmobilepage.aspx',
- main: 'https://www.dsbmobile.de/',
- Data: 'http://www.dsbmobile.de/JsonHandlerWeb.ashx/GetData',
- default: 'https://www.dsbmobile.de/default.aspx',
- loginV1: `https://iphone.dsbcontrol.de/iPhoneService.svc/DSB/authid/${
- this.username
- }/${this.password}`,
- timetables:
- 'https://iphone.dsbcontrol.de/iPhoneService.svc/DSB/timetables/',
- news: 'https://iphone.dsbcontrol.de/iPhoneService.svc/DSB/news/'
- };
- /**
- * @private
- */
- this.cookies = cookies;
- /**
- * @private
- */
- this.axios.defaults.headers.common['User-Agent'] =
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.32 Safari/537.36';
- if (cache) {
- /**
- * @private
- */
- this.cache = new DSBSessionStorageManager(cache, this.cookies);
- }
- }
-
- /**
- * @callback ProgressCallback
- * @param {Number} progress - A number between 0 and 100
- */
-
- /**
- * Fetch data
- * @param {ProgressCallback} [progress]
- * @returns {Promise.<Object>}
- */
- async fetch(progress = () => {}) {
- const cookies = await this._getSession(progress);
- // Progress State: 3
- const response = await this.axios({
- method: 'POST',
- data: {
- req: {
- Data: Encode({
- UserId: '',
- UserPw: '',
- Abos: [],
- AppVersion: '2.3',
- Language: 'de',
- AppId: '',
- Device: 'WebApp',
- PushId: '',
- BundleId: 'de.heinekingmedia.inhouse.dsbmobile.web',
- Date: new Date(),
- LastUpdate: new Date(),
- OsVersion:
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36'
- }),
- DataType: 1
- }
- },
- url: this.urls.Data,
- headers: {
- Bundle_ID: 'de.heinekingmedia.inhouse.dsbmobile.web',
- Referer: this.urls.main,
- Cookie: cookies,
- 'X-Requested-With': 'XMLHttpRequest'
- },
- onUploadProgress(e) {
- console.log(JSON.stringify(e));
- },
- onDownloadProgress(e) {
- console.log(JSON.stringify(e));
- }
- });
- if (!response.data.d) throw new Error('Invalid data.');
- progress(percentage.from(4, 5));
- const decoded = Decode(response.data.d);
- progress(percentage.from(5, 5));
- return decoded;
- }
-
- /**
- * Fetch data from the original iphone api (Only news and timetables supported)
- * @param {ProgressCallback} [progress]
- * @returns {Promise.<Object>}
- */
- async fetchV1(progress = () => {}) {
- let currentProgress = 0;
- const loginV1Response = await this.axios({
- method: 'GET',
- url: this.urls.loginV1
- });
- if (loginV1Response.data === '00000000-0000-0000-0000-000000000000')
- throw new Error('Login failed.');
- const id = loginV1Response.data;
- currentProgress++;
- progress(percentage.from(currentProgress, 5));
- const data = await Promise.all([
- this.axios(this.urls.timetables + id).then(response => {
- currentProgress++;
- progress(percentage.from(currentProgress, 5));
- return Promise.resolve({ timetables: response.data });
- }),
- this.axios(this.urls.news + id).then(response => {
- currentProgress++;
- progress(percentage.from(currentProgress, 5));
- return Promise.resolve({ news: response.data });
- })
- ]);
- currentProgress++;
- progress(percentage.from(currentProgress, 5));
- let newData = {};
- for (let fragment of data) {
- for (let key in fragment) {
- if (fragment.hasOwnProperty(key)) {
- newData[key] = fragment[key];
- }
- }
- }
- currentProgress++;
- progress(percentage.from(currentProgress, 5));
- return newData;
- }
-
- /**
- * Login with username and password
- * @param {String|Number} [username=this.username]
- * @param {String|Number} [password=this.password]
- * @returns {Promise.<String>}
- * @private
- */
- async _login(username = this.username, password = this.password) {
- const response = await this.axios({
- method: 'GET',
- url: this.urls.login,
- params: {
- user: username,
- password: password
- },
- validateStatus(status) {
- return status === 200 || status === 302;
- },
- maxRedirects: 0,
- onUploadProgress(e) {
- console.log(JSON.stringify(e));
- },
- onDownloadProgress(e) {
- console.log(JSON.stringify(e));
- }
- });
- if (!response.headers['set-cookie'])
- throw new Error('Login failed. Returned no cookies.');
- this.cookies = response.headers['set-cookie'].join('; ');
- return this.cookies;
- }
-
- /**
- * Checks if dsb session cookie is valid
- * @param {String} [cookies=this.cookies]
- * @returns {Promise.<boolean>}
- * @private
- */
- async _checkCookies(cookies = this.cookies) {
- let returnValue = false;
- try {
- returnValue = !!(await this.axios({
- method: 'GET',
- url: this.urls.default,
- validateStatus(status) {
- return status === 200;
- },
- maxRedirects: 0,
- headers: {
- Cookie: cookies,
- 'Cache-Control': 'no-cache',
- Pragma: 'no-cache'
- }
- }));
- } catch (e) {
- return false;
- } finally {
- return returnValue;
- }
- }
-
- /**
- * Get a valid session
- * @param {Function} [progress]
- * @returns {Promise.<String>}
- * @private
- */
- async _getSession(progress = () => {}) {
- let returnValue;
- try {
- const cookies = this.cookies
- ? this.cookies
- : await this.cache.get();
- progress(percentage.from(1, 5));
- if (await this._checkCookies(cookies)) {
- returnValue = cookies;
- progress(percentage.from(3, 5));
- } else {
- returnValue = await this._login();
- progress(percentage.from(2, 5));
- this.cache
- ? await this.cache.set(returnValue)
- : (this.cookies = returnValue);
- progress(percentage.from(3, 5));
- }
- } catch (e) {
- returnValue = await this._login();
- progress(percentage.from(2, 5));
- this.cache
- ? await this.cache.set(returnValue)
- : (this.cookies = returnValue);
- progress(percentage.from(3, 5));
- } finally {
- return returnValue;
- }
- }
-
- /**
- * [Experimental] Try to get just the important data from the data you get back from fetch()
- * @param {String} method - The method name to search for (z.B tiles or timetable)
- * @param {Object} data - Data returned by fetch()
- * @returns {Object}
- */
- static findMethodInData(method, data) {
- for (let key in data) {
- if (!data.hasOwnProperty(key)) continue;
- if (key === 'MethodName') {
- if (data[key] === method) {
- if (
- typeof data['Root'] === 'object' &&
- Array.isArray(data['Root']['Childs'])
- ) {
- let transformData = [];
- for (let o of data['Root']['Childs']) {
- let newObject = {};
- newObject.title = o.Title;
- newObject.id = o.Id;
- newObject.date = o.Date;
- if (o['Childs'].length === 1) {
- newObject.url = o['Childs'][0]['Detail'];
- newObject.preview = o['Childs'][0]['Preview'];
- newObject.secondTitle = o['Childs'][0]['Title'];
- } else {
- newObject.objects = [];
- for (let objectOfArray of o['Childs']) {
- newObject.objects.push({
- id: objectOfArray.Id,
- url: objectOfArray.Detail,
- preview: objectOfArray.Preview,
- title: objectOfArray.Title,
- date: objectOfArray.Date
- });
- }
- }
- transformData.push(newObject);
- }
- return {
- method: method,
- data: transformData
- };
- }
- }
- }
- if (Array.isArray(data[key]) || typeof data[key] === 'object') {
- const find = DSB.findMethodInData(method, data[key]);
- if (find) return find;
- }
- }
- }
- }
-
- class DSBSessionStorageManager {
- /**
- * Class to store the dsb session
- * @param [path=""]
- * @param [cookies=""]
- */
- constructor(path = '', cookies = '') {
- this.path = path;
- this.cookies = cookies;
- this.fs = DSBSessionStorageManager.isNode() ? require('fs') : undefined;
- }
-
- /**
- * Retrieves session from cache.
- * @returns {Promise.<String>}
- */
- async get() {
- if (DSBSessionStorageManager.isNode()) {
- this.cookies = await new Promise((resolve, reject) => {
- this.fs.readFile(this.path, (err, data) => {
- if (err) return reject(err);
- if (typeof data !== 'string') {
- let value;
- try {
- value = data.toString();
- } catch (e) {
- return reject(e);
- } finally {
- return resolve(value);
- }
- } else {
- return resolve(data);
- }
- });
- });
- return this.cookies;
- } else {
- if (window.localStorage) {
- return window.localStorage.getItem('DSBSession');
- } else {
- return this.cookies;
- }
- }
- }
-
- /**
- * Sets the session in the cache.
- * @param value
- * @returns {Promise.<void>}
- */
- async set(value = '') {
- this.cookies = value;
- if (DSBSessionStorageManager.isNode()) {
- try {
- await new Promise((resolve, reject) => {
- this.fs.writeFile(this.path, value, err => {
- if (err) return reject(err);
- return resolve();
- });
- });
- } catch (e) {}
- } else {
- if (window.localStorage) {
- return window.localStorage.setItem('DSBSession', value);
- }
- }
- }
-
- /**
- * Checks if this module is running in a browser env or node env.
- * @returns {boolean}
- */
- static isNode() {
- return (
- Object.prototype.toString.call(global.process) ===
- '[object process]'
- );
- }
- }