UNPKG

@i4mi/ionic-on-fhir

Version:

IONIC wrapper for the I4MI fhir resource library

609 lines 26.8 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; import { apiCall, HttpMethod, ApiMethods } from '@i4mi/fhir_r4'; import { InAppBrowser } from '@ionic-native/in-app-browser/ngx'; import { SecureStorage } from '@ionic-native/secure-storage/ngx'; import { AUTH_RES_KEY } from './ionic-on-fhir.types'; import { HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import * as i0 from "@angular/core"; import * as i1 from "@ionic-native/in-app-browser/ngx/index"; import * as i2 from "@ionic-native/secure-storage/ngx/index"; var IonicOnFhirService = /** @class */ (function () { function IonicOnFhirService(iab, secStorage) { var _this = this; this.iab = iab; this.secStorage = secStorage; this.authRequestParams = { client_id: '', auth_url: '', response_type: 'code', redirect_uri: '', state: '', scope: '', aud: '' }; this.authResponseParams = { access_token: '', expires_in: 0, patient: '', refresh_token: '', scope: '', state: '', token_type: 'Bearer' }; this.tokenExchangeParams = { client_id: '', code: '', redirect_uri: '', token_url: '' }; // params to get for user this.loggedIn = false; /** * function that interprets the result of the api request */ this.interpretConfirmantStatementResponse = function (response) { return new Promise(function (resolve, reject) { if (response.status === 200) { // override body with parsed response // todo --> try to map oject from lib response.body = JSON.parse(response.body); _this.tokenExchangeParams.token_url = response.body.rest['0'].security.extension['0'].extension['0'].valueUri; _this.authRequestParams.auth_url = response.body.rest['0'].security.extension['0'].extension['1'].valueUri; resolve(response); } else { reject(response); } }); }; /** * function that interprets the result of the api request */ this.interpretTokenResponse = function (response) { return new Promise(function (resolve, reject) { if (response.status === 200) { // todo --> try to map oject from lib resolve(response.body); } else { reject(response); } }); }; } /** * First to call. * Set values the library needs for the authentication process. * @param fhirServerUrl The url to the server (for example test.midata.coop) * @param clientId App name given to the auth request as client id */ IonicOnFhirService.prototype.initIonicOnFhir = function (fhirServerUrl, clientId) { this.fhirServerUrl = fhirServerUrl; this.authRequestParams.client_id = clientId; this.tokenExchangeParams.client_id = clientId; this.authRequestParams.scope = 'user/*.*'; this.authRequestParams.aud = '/fhir'; this.apiMethods = new ApiMethods(); }; /** * Checks if user is logged in. This is deprecated and will be removed, because * it's not reliable. Instead, you are encouraged to keep track of your login-state * in your application. * @returns boolean (true if logged in) * @deprecated */ IonicOnFhirService.prototype.isLoggedIn = function () { console.warn('ionic-on-fhir isLoggedIn(): Method is deprecated and will be removed because it\'s unreliable. Keep track of login-state in your application.'); return this.loggedIn; }; /** * Config params for in app browser as array<{key:value}> * Call when you do not want default settings * @param settings: Array<InAppBrowserSettings> Array of inappbrowser settings. Documented here: * https://github.com/apache/cordova-plugin-inappbrowser */ IonicOnFhirService.prototype.configInAppBrowser = function (settings) { this.iabSettings = settings; }; /** * Function that lets you define a different content type for you fhir server * than the default type of the lib. Default: "application/fhir+json;fhirVersion=4.0" * @param contentType content type for header param */ IonicOnFhirService.prototype.differentiateContentType = function (contentType) { this.apiMethods.differentiateContentType(contentType); }; /** * Function that lets you define a different conformance endpoint url * if it diverges from standard pattern, which is serverUrl + "/fhir/metadata" * @param url of the conformance statement endpoint */ IonicOnFhirService.prototype.differentiateConformanceStatementUrl = function (url) { this.conformanceStatementUrl = url; }; /** * Function to differentiate from the default scope * 'user/*.*' * @param scope Scope of logged in user */ IonicOnFhirService.prototype.differentiateScope = function (scope) { this.authRequestParams.scope = scope; }; /** * Function to differentiate from the default aud * '/fhir' * @param scope Scope of logged in user */ IonicOnFhirService.prototype.differentiateAud = function (aud) { this.authRequestParams.aud = aud; }; /** * Authenticate someone over oAuth2 * @param params (optional) an Object * @returns Promise<any> - * if success: returns auth response (type AuthResponse) and saves response to secure storage * else: error message */ IonicOnFhirService.prototype.authenticate = function (params) { var _this = this; this.authRequestParams.redirect_uri = 'http://localhost/callback'; // function that executes the authentication // according oAuth 2 from SMART on FHIR // @returns error on reject var doAuthentication = function (url) { return new Promise(function (resolve, reject) { if (typeof _this.fhirServerUrl === 'undefined') { reject('Please call initIonicOnFhir() first to define the necessary configurations'); } var autoClose = false; var effectiveIabSettings = 'location=no,clearcache=yes'; if (typeof _this.iabSettings !== 'undefined' && _this.iabSettings.length !== 0) { effectiveIabSettings = ''; _this.iabSettings.forEach(function (setting, index) { effectiveIabSettings += setting.key + "=" + setting.value; // if not last element, add ',' if (index !== _this.iabSettings.length - 1) { effectiveIabSettings += ','; } }); } _this.authWindow = _this.iab.create(url, '_blank', effectiveIabSettings); // subscribe loadstart event to show iab _this.authWindow.on('loadstart').subscribe(function (event) { _this.authWindow.show(); // if no redirect uri is given, close browser and reject if ((event.url).indexOf(_this.authRequestParams.redirect_uri) === 0) { var state = event.url.split('&')[0].split('=')[1]; autoClose = true; // if returned state same as request state, resolve if (state === _this.authRequestParams.state) { _this.tokenExchangeParams.code = event.url.split('&')[1].split('=')[1]; _this.authWindow.close(); resolve(); } else { _this.authWindow.close(); reject("State received by server not equals sent one."); } } }, function (error) { reject("An error occured on loadstart event: " + error); }); // subscribe exit event to check when browser gets closed _this.authWindow.on('exit').subscribe(function () { if (!autoClose) { reject("Someone or something caused the browser to close"); } }); }); }; return new Promise(function (resolve, reject) { // now fetches conformance statement _this.fetchConformanceStatement().then(function (response) { _this.initSession(); // creates auth url var authUrl = "" + _this.authRequestParams.auth_url + ("?response_type=" + _this.authRequestParams.response_type) + ("&client_id=" + _this.authRequestParams.client_id) + ("&redirect_uri=" + _this.authRequestParams.redirect_uri) + ("&aud=" + _this.authRequestParams.aud) + ("&scope=" + _this.authRequestParams.scope) + ("&state=" + _this.authRequestParams.state); if (typeof _this.authRequestParams.launch !== 'undefined') { authUrl += "&launch=" + _this.authRequestParams.launch; } if (params) { Object.keys(params).forEach(function (key) { authUrl = authUrl + '&' + key + '=' + params[key].toString(); }); } var encodedUrl = encodeURI(authUrl); // now execute effective authentication doAuthentication(encodedUrl).then(function () { return _this.exchangeTokenForCode(); }).then(function (resp) { _this.loggedIn = true; resolve(resp); }).catch(function (error) { _this.loggedIn = false; reject(error); }); }).catch(function (error) { _this.loggedIn = false; reject(error); }); }); }; /** * Refresh session and refreshes it, if user was logged in. * Tries to refresh the authentication token by authorizing with the help of the refresh token. * This will generate a new authentication as well as a new refresh token. On successful refresh, * the old refresh_token will be invalid and both the access_token and the refresh_token will be overwritten. * Previous access_tokens will remain valid until their expiration timestamp is exceeded. * @returns resolves the auth response if success * @returns reject every other case */ IonicOnFhirService.prototype.refreshSession = function () { var _this = this; // function to get the params for the refresh request var defineParameters = function () { return new Promise(function (resolve, reject) { var urlParams = new URLSearchParams(); urlParams.append('grant_type', 'refresh_token'); if (!_this.authResponseParams.refresh_token) { _this.getAuthResponse().then(function (result) { urlParams.append('refresh_token', result.refresh_token); resolve({ encodedParams: urlParams }); }).catch(function (error) { reject(error); }); } else { urlParams.append('refresh_token', _this.authResponseParams.refresh_token); resolve({ encodedParams: urlParams }); } }); }; // do refresh var doSessionRefresh = function (refeshParam) { return new Promise(function (resolve, reject) { apiCall({ url: _this.tokenExchangeParams.token_url, method: HttpMethod.POST, payload: refeshParam.encodedParams.toString(), jsonBody: true, jsonEncoded: false, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(function (response) { resolve(response); }).catch(function (error) { reject(error); }); }); }; return new Promise(function (resolve, reject) { // Gets conformance statement _this.fetchConformanceStatement().then(function () { return defineParameters(); }).then(function (params) { return doSessionRefresh(params); }).then(function (response) { if (response.status === 200) { var refreshResponse_1 = response.body; _this.saveAuthResponse(refreshResponse_1).then(function () { _this.loggedIn = true; resolve(refreshResponse_1); }).catch(function (error) { _this.loggedIn = false; reject(error); }); } else { _this.loggedIn = false; reject(response); } }).catch(function (error) { _this.loggedIn = false; reject(error); }); }); }; /** * Destroys all auth information from storage * and sets logged in to false */ IonicOnFhirService.prototype.logout = function () { var _this = this; this.loggedIn = false; return new Promise(function (resolve, reject) { _this.storage.remove(AUTH_RES_KEY).then(function (res) { resolve(res); }).catch(function (error) { reject(error); }); }); }; /** * Creates a resource * on the fhir server * @param resource resource to save * @returns resolve of resource as JSON if status 200 or 201 * @returns reject every other case with message */ IonicOnFhirService.prototype.create = function (resource) { var _this = this; return new Promise(function (resolve, reject) { _this.getAuthResponse().then(function (res) { // checks if logged in and has auth token if (!_this.loggedIn && !res) { reject('Not logged in'); } // configs parameters according apimethods var authParams = typeof res === 'object' ? res : JSON.parse(res); var config = { access_token: authParams.access_token, authorization_type: 'Bearer', base_url: _this.fhirServerUrl + "/fhir" }; // calls create of apimethods _this.apiMethods.create(resource, config).then(function (response) { if (response.status === 200 || response.status === 201) resolve(JSON.parse(response.body)); else reject(response); }).catch(function (error) { reject(error); }); }).catch(function (error) { reject(error); }); }); }; /** * Updates a resource * on the fhir server * @param resource resource to update * @returns resolve of resource as JSON if status 200 or 201 * @returns reject every other case with message */ IonicOnFhirService.prototype.update = function (resource) { var _this = this; return new Promise(function (resolve, reject) { // checks if resource has id if (typeof resource.id === 'undefined' && typeof resource._id === 'undefined') { reject('Resource has no id'); } _this.getAuthResponse().then(function (res) { // checks if logged in and has auth token if (!_this.loggedIn && !res) { reject('Not logged in'); } // configs parameters according apimethods var authParams = typeof res === 'object' ? res : JSON.parse(res); var config = { access_token: authParams.access_token, authorization_type: 'Bearer', base_url: _this.fhirServerUrl + "/fhir" }; // calls update of apimethods _this.apiMethods.update(resource, config).then(function (response) { if (response.status === 200 || response.status === 201) resolve(JSON.parse(response.body)); else reject(response); }).catch(function (error) { reject(error); }); }).catch(function (error) { reject(error); }); }); }; /** * Searches for one or multiple resources * @param resourceType resource type to look up * @param params search parameters according fhir resource guide$ * @returns resolve of resource as JSON if status 200 or 201 * @returns reject every other case with message */ IonicOnFhirService.prototype.search = function (resourceType, params) { var _this = this; return new Promise(function (resolve, reject) { _this.getAuthResponse().then(function (res) { // checks if logged in and has auth token if (!_this.loggedIn && !res) { reject('Not logged in'); } // configs parameters according apimethods var authParams = typeof res === 'object' ? res : JSON.parse(res); var config = { access_token: authParams.access_token, authorization_type: 'Bearer', base_url: _this.fhirServerUrl + "/fhir" }; // calls search of apimethods _this.apiMethods.search(params, resourceType, config).then(function (response) { if (response.status === 200 || response.status === 201) resolve(JSON.parse(response.body)); else reject(response); }).catch(function (error) { reject(error); }); }).catch(function (error) { reject(error); }); }); }; /** * Makes api call to get the auth and token url * from the fhir/midatata of the server. * Returns a json response with a resource in the .body * Rejects the original error if one occures */ IonicOnFhirService.prototype.fetchConformanceStatement = function () { var _this = this; return new Promise(function (resolve, reject) { var cfUrl = (typeof _this.conformanceStatementUrl !== 'undefined') ? _this.conformanceStatementUrl : _this.fhirServerUrl + "/fhir/metadata"; apiCall({ url: cfUrl, method: HttpMethod.GET }).then(function (response) { return _this.interpretConfirmantStatementResponse(response); }).then(function (resource) { resolve(resource); }).catch(function (error) { reject(error); }); }); }; /** * Inits a session: * State and state hash (jsSHA-256) */ IonicOnFhirService.prototype.initSession = function () { this.generateRandomState(128); }; /** * Generates random state string with given length * If lengts set to 0, it will take 122 * @param length length of the string to generate */ IonicOnFhirService.prototype.generateRandomState = function (length) { if (length <= 0) { length = 122; } var possibilities = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; this.authRequestParams.state = ''; for (var i = 0; i < length; i++) { this.authRequestParams.state += possibilities.charAt(Math.floor(Math.random() * possibilities.length)); } }; /** * After successful authentication on midata this method is invoked. It exchanges the authCode * obtained from midata with the access_token used to query the FHIR endpoint API. */ IonicOnFhirService.prototype.exchangeTokenForCode = function () { var _this = this; return new Promise(function (resolve, reject) { var addTokenExchangeRequestPayload = function () { var tokenRequestParams = new HttpParams(); if (_this.tokenExchangeParams.redirect_uri === '') { _this.tokenExchangeParams.redirect_uri = (_this.authRequestParams.redirect_uri) ? _this.authRequestParams.redirect_uri : 'http://localhost/callback'; } tokenRequestParams = tokenRequestParams.append('grant_type', 'authorization_code'); tokenRequestParams = tokenRequestParams.append('code', _this.tokenExchangeParams.code); tokenRequestParams = tokenRequestParams.append('redirect_uri', _this.tokenExchangeParams.redirect_uri); tokenRequestParams = tokenRequestParams.append('client_id', _this.tokenExchangeParams.client_id); return { encodedParams: tokenRequestParams }; }; var exchangeToken = function () { return new Promise(function (res, rej) { apiCall({ url: _this.tokenExchangeParams.token_url, method: HttpMethod.POST, headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, jsonBody: true, payload: addTokenExchangeRequestPayload().encodedParams.toString(), jsonEncoded: false }).then(function (response) { // returns body of response (so the AuthResponse) return _this.interpretTokenResponse(response); }).then(function (response) { res(response); }).catch(function (error) { rej(error); }); }); }; // from here on only response exchangeToken().then(function (response) { return _this.saveAuthResponse(response); }).then(function (response) { resolve(response); }).catch(function (error) { reject(error); }); }); }; /** * Saves given response body in the secure storage */ IonicOnFhirService.prototype.saveAuthResponse = function (response) { var _this = this; return new Promise(function (resolve, reject) { _this.checkIfDeviceSecure().then(function () { _this.storage.set(AUTH_RES_KEY, JSON.stringify(response)).then(function () { resolve(response); }).catch(function (error) { reject(error); }); }).catch(function (error) { reject(error); }); }); }; /** * Loads the auth response if there was one (for refresh token etc.) */ IonicOnFhirService.prototype.getAuthResponse = function () { var _this = this; return new Promise(function (resolve, reject) { _this.checkIfDeviceSecure().then(function () { _this.storage.get(AUTH_RES_KEY).then(function (response) { response = JSON.parse(response); resolve(response); }).catch(function (error) { reject(error); }); }).catch(function (error) { reject(error); }); }); }; /** * Checks if the device is secure or not */ IonicOnFhirService.prototype.checkIfDeviceSecure = function () { var _this = this; return new Promise(function (resolve, reject) { if (_this.storage) { resolve(); } else { _this.secStorage.create(_this.authRequestParams.client_id + "_auth").then(function (s) { _this.storage = s; resolve(); }).catch(function (error) { reject(error); }); } }); }; IonicOnFhirService.ngInjectableDef = i0.ɵɵdefineInjectable({ factory: function IonicOnFhirService_Factory() { return new IonicOnFhirService(i0.ɵɵinject(i1.InAppBrowser), i0.ɵɵinject(i2.SecureStorage)); }, token: IonicOnFhirService, providedIn: "root" }); IonicOnFhirService = __decorate([ Injectable({ providedIn: 'root' }), __metadata("design:paramtypes", [InAppBrowser, SecureStorage]) ], IonicOnFhirService); return IonicOnFhirService; }()); export { IonicOnFhirService }; //# sourceMappingURL=ionic-on-fhir.service.js.map