Socket
Socket
Sign inDemoInstall

@medplum/core

Package Overview
Dependencies
Maintainers
1
Versions
186
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@medplum/core - npm Package Compare versions

Comparing version 0.0.26 to 0.0.28

dist/fhirpath/index.d.ts

30

dist/client.d.ts
import { EventTarget } from './eventtarget';
import { Binary, Bundle, OperationOutcome, Resource, User } from './fhir';
import { Binary, Bundle, OperationOutcome, Reference, Resource, User } from './fhir';
import { SearchRequest } from './search';

@@ -58,2 +58,8 @@ import { IndexedStructureDefinition } from './types';

fetch?: FetchLike;
/**
* Optional callback for when the client is unauthenticated.
* Default is do nothing.
* For client side applications, consider redirecting to a sign in page.
*/
onUnauthenticated?: () => void;
}

@@ -74,2 +80,3 @@ export interface FetchLike {

private readonly logoutUrl;
private readonly onUnauthenticated?;
private user?;

@@ -105,3 +112,3 @@ private profile?;

*/
signInWithRedirect(): Promise<User> | undefined;
signInWithRedirect(): Promise<User | void> | undefined;
/**

@@ -116,4 +123,4 @@ * Tries to sign out the user.

readCached(resourceType: string, id: string): Promise<Resource>;
readReference(reference: string): Promise<Resource>;
readCachedReference(reference: string): Promise<Resource>;
readReference(reference: Reference): Promise<Resource>;
readCachedReference(reference: Reference): Promise<Resource>;
getTypeDefinition(resourceType: string): Promise<IndexedStructureDefinition>;

@@ -123,4 +130,4 @@ readHistory(resourceType: string, id: string): Promise<Bundle>;

readBlob(url: string): Promise<Blob>;
readBlobAsImageUrl(url: string): Promise<string>;
readCachedBlobAsImageUrl(url: string): Promise<string>;
readBlobAsObjectUrl(url: string): Promise<string>;
readCachedBlobAsObjectUrl(url: string): Promise<string>;
readBinary(id: string): Promise<Blob>;

@@ -151,2 +158,13 @@ create<T extends Resource>(resource: T): Promise<T>;

/**
* Handles an unauthenticated response from the server.
* First, tries to refresh the access token and retry the request.
* Otherwise, calls unauthenticated callbacks and rejects.
* @param method The HTTP method of the original request.
* @param url The URL of the original request.
* @param contentType The content type of the original request.
* @param body The body of the original request.
* @param blob Optional blob flag of the original request.
*/
private handleUnauthenticated;
/**
* Redirects the user to the login screen for authorization.

@@ -153,0 +171,0 @@ * Clears all auth state including local storage and session storage.

87

dist/client.js

@@ -39,2 +39,3 @@ "use strict";

logoutUrl;
onUnauthenticated;
user;

@@ -65,2 +66,3 @@ profile;

this.logoutUrl = options.logoutUrl || this.baseUrl + 'oauth2/logout';
this.onUnauthenticated = options.onUnauthenticated;
}

@@ -146,3 +148,3 @@ /**

}
window.location.href = this.logoutUrl;
window.location.assign(this.logoutUrl);
}

@@ -177,7 +179,15 @@ fhirUrl(...path) {

readReference(reference) {
const [resourceType, id] = reference.split('/');
const refString = reference?.reference;
if (!refString) {
return Promise.reject('Missing reference');
}
const [resourceType, id] = refString.split('/');
return this.read(resourceType, id);
}
readCachedReference(reference) {
const [resourceType, id] = reference.split('/');
const refString = reference?.reference;
if (!refString) {
return Promise.reject('Missing reference');
}
const [resourceType, id] = refString.split('/');
return this.readCached(resourceType, id);

@@ -193,2 +203,3 @@ }

}
let typeDef;
return this.search('StructureDefinition?name=' + encodeURIComponent(resourceType))

@@ -203,3 +214,20 @@ .then((result) => {

}
const typeDef = types_1.indexStructureDefinition(resource);
typeDef = types_1.indexStructureDefinition(resource);
})
.then(() => this.search({
resourceType: 'SearchParameter',
count: 100,
filters: [{
code: 'base',
operator: search_1.Operator.EQUALS,
value: resourceType
}]
}))
.then((result) => {
const entries = result.entry;
if (entries) {
typeDef.types[resourceType].searchParams = entries
.map(e => e.resource)
.sort((a, b) => a.name?.localeCompare(b.name) ?? 0);
}
this.schema.set(resourceType, typeDef);

@@ -218,3 +246,3 @@ return typeDef;

}
readBlobAsImageUrl(url) {
readBlobAsObjectUrl(url) {
const promise = this.readBlob(url)

@@ -229,5 +257,5 @@ .then(imageBlob => {

}
readCachedBlobAsImageUrl(url) {
readCachedBlobAsObjectUrl(url) {
const cached = this.blobUrlCache.get(url);
return cached ? Promise.resolve(cached) : this.readBlobAsImageUrl(url);
return cached ? Promise.resolve(cached) : this.readBlobAsObjectUrl(url);
}

@@ -347,3 +375,3 @@ readBinary(id) {

// Refresh and try again
return this.refresh().then(() => this.request(method, url, contentType, body, blob));
return this.handleUnauthenticated(method, url, contentType, body, blob);
}

@@ -357,2 +385,23 @@ const obj = blob ? await response.blob() : await response.json();

/**
* Handles an unauthenticated response from the server.
* First, tries to refresh the access token and retry the request.
* Otherwise, calls unauthenticated callbacks and rejects.
* @param method The HTTP method of the original request.
* @param url The URL of the original request.
* @param contentType The content type of the original request.
* @param body The body of the original request.
* @param blob Optional blob flag of the original request.
*/
async handleUnauthenticated(method, url, contentType, body, blob) {
return this.refresh()
.then(() => this.request(method, url, contentType, body, blob))
.catch(error => {
this.clear();
if (this.onUnauthenticated) {
this.onUnauthenticated();
}
return Promise.reject(error);
});
}
/**
* Redirects the user to the login screen for authorization.

@@ -366,3 +415,2 @@ * Clears all auth state including local storage and session storage.

}
console.log('Requesting authorization...');
this.clear();

@@ -377,3 +425,3 @@ const pkceState = crypto_1.getRandomString();

const scope = 'launch/patient openid fhirUser offline_access user/*.*';
window.location.href = this.authorizeUrl +
window.location.assign(this.authorizeUrl +
'?response_type=code' +

@@ -385,3 +433,3 @@ '&state=' + encodeURIComponent(pkceState) +

'&code_challenge_method=S256' +
'&code_challenge=' + encodeURIComponent(codeChallenge);
'&code_challenge=' + encodeURIComponent(codeChallenge));
}

@@ -394,3 +442,2 @@ /**

processCode(code) {
console.log('Processing authorization code...');
const pkceState = this.storage.getString('pkceState');

@@ -420,3 +467,3 @@ if (!pkceState) {

this.clear();
throw new Error('Invalid refresh token');
return Promise.reject('Invalid refresh token');
}

@@ -436,3 +483,3 @@ await this.fetchTokens('grant_type=refresh_token' +

}
return fetch(this.tokenUrl, {
return this.fetch(this.tokenUrl, {
method: 'POST',

@@ -442,3 +489,8 @@ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },

})
.then(response => response.json())
.then(response => {
if (!response.ok) {
return Promise.reject('Failed to fetch tokens');
}
return response.json();
})
.then(tokens => this.verifyTokens(tokens))

@@ -454,3 +506,2 @@ .then(() => this.getUser());

async verifyTokens(tokens) {
console.log('Verifying authorization token...');
const token = tokens.access_token;

@@ -461,3 +512,3 @@ // Verify token has not expired

this.clear();
throw new Error('Token expired');
return Promise.reject('Token expired');
}

@@ -467,3 +518,3 @@ // Verify app_client_id

this.clear();
throw new Error('Token was not issued for this audience');
return Promise.reject('Token was not issued for this audience');
}

@@ -470,0 +521,0 @@ this.setAccessToken(token);

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const crypto_1 = require("crypto");
const util_1 = require("util");
const client_1 = require("./client");

@@ -9,4 +11,81 @@ const defaultOptions = {

};
let canRefresh = true;
let tokenExpired = false;
function mockFetch(url, options) {
const { method } = options;
let result;
if (method === 'POST' && url.endsWith('auth/login')) {
result = {
user: { resourceType: 'User', id: '123' },
accessToken: '123',
refreshToken: '456'
};
}
else if (method === 'GET' && url.endsWith('Patient/123')) {
result = {
resourceType: 'Patient',
id: '123'
};
}
else if (method === 'POST' && url.endsWith('oauth2/token')) {
if (canRefresh) {
result = {
access_token: 'header.' + window.btoa(JSON.stringify({ client_id: defaultOptions.clientId })) + '.signature',
refresh_token: 'header.' + window.btoa(JSON.stringify({ client_id: defaultOptions.clientId })) + '.signature'
};
}
else {
result = {
status: 400
};
}
}
else if (method === 'GET' && url.includes('expired')) {
if (tokenExpired) {
result = {
status: 401
};
tokenExpired = false;
}
else {
result = {
ok: true
};
}
}
else if (method === 'GET' && url.includes('/fhir/R4/StructureDefinition?name=Patient')) {
result = {
resourceType: 'Bundle',
entry: [{
resource: {
resourceType: 'StructureDefinition',
name: 'Patient',
snapshot: {
element: [
{
path: 'Patient.id',
type: [{
code: 'code'
}]
}
]
}
}
}]
};
}
else if (method === 'GET' && url.includes('/fhir/R4/SearchParameter?_count=100&base=Patient')) {
result = {
resourceType: 'Bundle',
entry: [{
resource: {
resourceType: 'SearchParameter',
id: 'Patient-name',
code: 'name',
name: 'name',
expression: 'Patient.name'
}
}]
};
}
const response = {

@@ -16,14 +95,8 @@ request: {

options
}
},
...result
};
if (method === 'POST' && url.endsWith('auth/login')) {
response.user = { resourceType: 'User', id: '123' };
response.accessToken = '123';
response.refreshToken = '456';
}
else if (method === 'GET' && url.endsWith('Patient/123')) {
response.resourceType = 'Patient';
response.id = '123';
}
return Promise.resolve({
ok: response.status === undefined,
status: response.status,
blob: () => Promise.resolve(response),

@@ -70,2 +143,66 @@ json: () => Promise.resolve(response)

});
test('Client signInWithRedirect', async () => {
// Mock window.crypto
Object.defineProperty(global.self, 'crypto', {
value: {
getRandomValues: (arr) => crypto_1.randomBytes(arr.length),
subtle: {
digest: () => 'test'
}
}
});
// Mock TextEncoder
global.TextEncoder = util_1.TextEncoder;
// Mock window.location.assign
global.window = Object.create(window);
Object.defineProperty(window, 'location', {
value: {
assign: jest.fn()
},
writable: true,
});
const client = new client_1.MedplumClient(defaultOptions);
// First, test the initial reidrect
const result1 = client.signInWithRedirect();
expect(result1).toBeUndefined();
// Mock response code
Object.defineProperty(window, 'location', {
value: {
assign: jest.fn(),
search: new URLSearchParams({ code: 'test-code' })
},
writable: true,
});
// Next, test processing the response code
const result2 = client.signInWithRedirect();
expect(result2).not.toBeUndefined();
});
test('Client signOutWithRedirect', async () => {
// Mock window.location.assign
global.window = Object.create(window);
Object.defineProperty(window, 'location', {
value: {
assign: jest.fn()
},
writable: true,
});
const client = new client_1.MedplumClient(defaultOptions);
client.signOutWithRedirect();
expect(window.location.assign).toBeCalled();
});
test('Client read expired and refresh', async () => {
tokenExpired = true;
const client = new client_1.MedplumClient(defaultOptions);
const result = await client.get('expired');
expect(result).not.toBeUndefined();
});
test('Client read expired and refresh with unAuthenticated callback', async () => {
tokenExpired = true;
canRefresh = false;
const onUnauthenticated = jest.fn();
const client = new client_1.MedplumClient({ ...defaultOptions, onUnauthenticated });
const result = client.get('expired');
await expect(result).rejects.toEqual('Failed to fetch tokens');
expect(onUnauthenticated).toBeCalled();
});
test('Client read resource', async () => {

@@ -81,3 +218,3 @@ const client = new client_1.MedplumClient(defaultOptions);

const client = new client_1.MedplumClient(defaultOptions);
const result = await client.readReference('Patient/123');
const result = await client.readReference({ reference: 'Patient/123' });
expect(result).not.toBeUndefined();

@@ -88,2 +225,7 @@ expect(result.request.url).toBe('https://x/fhir/R4/Patient/123');

});
test('Client read empty reference', async () => {
const client = new client_1.MedplumClient(defaultOptions);
const result = client.readReference({});
expect(result).rejects.toEqual('Missing reference');
});
test('Client read cached resource', async () => {

@@ -99,3 +241,3 @@ const client = new client_1.MedplumClient(defaultOptions);

const client = new client_1.MedplumClient(defaultOptions);
const result = await client.readCachedReference('Patient/123');
const result = await client.readCachedReference({ reference: 'Patient/123' });
expect(result).not.toBeUndefined();

@@ -106,2 +248,7 @@ expect(result.request.url).toBe('https://x/fhir/R4/Patient/123');

});
test('Client read cached empty reference', async () => {
const client = new client_1.MedplumClient(defaultOptions);
const result = client.readCachedReference({});
expect(result).rejects.toEqual('Missing reference');
});
test('Client read history', async () => {

@@ -146,2 +293,9 @@ const client = new client_1.MedplumClient(defaultOptions);

});
test('Client get schema', async () => {
const client = new client_1.MedplumClient(defaultOptions);
const schema = await client.getTypeDefinition('Patient');
expect(schema).not.toBeUndefined();
expect(schema.types['Patient']).not.toBeUndefined();
expect(schema.types['Patient'].searchParams).not.toBeUndefined();
});
//# sourceMappingURL=client.test.js.map

@@ -37,2 +37,6 @@ import { Meta } from './Meta';

/**
* A summary, characterization or explanation of the Client Application.
*/
readonly description?: string;
/**
* Client secret string used to verify the identity of a client.

@@ -39,0 +43,0 @@ */

@@ -35,3 +35,3 @@ import { Meta } from './Meta';

/**
* A name associated with the organization.
* A name associated with the Project.
*/

@@ -38,0 +38,0 @@ readonly name?: string;

@@ -49,2 +49,6 @@ import { Meta } from './Meta';

readonly practitioner?: Reference;
/**
* Projects that the user can access.
*/
readonly projects?: Reference[];
}
export * from './client';
export * from './fhir';
export * from './fhirpath';
export * from './format';

@@ -4,0 +5,0 @@ export * from './search';

@@ -15,2 +15,3 @@ "use strict";

__exportStar(require("./fhir"), exports);
__exportStar(require("./fhirpath"), exports);
__exportStar(require("./format"), exports);

@@ -17,0 +18,0 @@ __exportStar(require("./search"), exports);

@@ -1,2 +0,2 @@

import { StructureDefinition } from './fhir';
import { SearchParameter, StructureDefinition } from './fhir';
/**

@@ -45,2 +45,3 @@ * An IndexedStructureDefinition is a lookup-optimized version of a StructureDefinition.

};
searchParams?: SearchParameter[];
description?: string;

@@ -47,0 +48,0 @@ backboneElement?: boolean;

@@ -11,6 +11,5 @@ "use strict";

function createReference(resource) {
return {
reference: getReferenceString(resource),
display: getDisplayString(resource)
};
const reference = getReferenceString(resource);
const display = getDisplayString(resource);
return (display === reference) ? { reference } : { reference, display };
}

@@ -17,0 +16,0 @@ exports.createReference = createReference;

{
"name": "@medplum/core",
"version": "0.0.26",
"version": "0.0.28",
"description": "Medplum TS/JS Library",

@@ -20,7 +20,8 @@ "author": "Medplum <hello@medplum.com>",

"devDependencies": {
"@medplum/definitions": "0.0.28",
"@types/jest": "26.0.24",
"@types/node": "16.4.1",
"@typescript-eslint/eslint-plugin": "4.28.4",
"@typescript-eslint/parser": "4.28.4",
"eslint": "7.31.0",
"@types/node": "16.4.10",
"@typescript-eslint/eslint-plugin": "4.29.0",
"@typescript-eslint/parser": "4.29.0",
"eslint": "7.32.0",
"jest": "27.0.6",

@@ -27,0 +28,0 @@ "rimraf": "3.0.2",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc