@i4mi/ionic-on-fhir
Version:
IONIC wrapper for the I4MI fhir resource library
609 lines • 26.8 kB
JavaScript
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