Skip to content
This repository has been archived by the owner on Jan 10, 2024. It is now read-only.

Commit

Permalink
feat: #8 replace XMLHttpRequest with fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
pamapa committed Jul 21, 2021
1 parent 3b1bed5 commit 4fec4d7
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 248 deletions.
224 changes: 96 additions & 128 deletions src/JsonService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import { Log } from './utils';

export class JsonService {
private _contentTypes: string[];
private _XMLHttpRequest: typeof XMLHttpRequest;
private _jwtHandler: any;

constructor(
additionalContentTypes: string[] | null = null,
XMLHttpRequestCtor = XMLHttpRequest,
jwtHandler: any = null
) {
if (additionalContentTypes && Array.isArray(additionalContentTypes))
Expand All @@ -26,167 +24,137 @@ export class JsonService {
this._contentTypes.push('application/jwt');
}

this._XMLHttpRequest = XMLHttpRequestCtor;
this._jwtHandler = jwtHandler;
}

getJson(url: string, token?: string): Promise<any> {
async getJson(url: string, token?: string): Promise<any> {
if (!url){
Log.error("JsonService.getJson: No url passed");
throw new Error("url");
}

Log.debug("JsonService.getJson, url: ", url);

return new Promise((resolve, reject) => {

var req = new this._XMLHttpRequest();
req.open('GET', url);

var allowedContentTypes = this._contentTypes;
var jwtHandler = this._jwtHandler;

req.onload = function() {
Log.debug("JsonService.getJson: HTTP response received, status", req.status);
const headers: HeadersInit = {};
if (token) {
Log.debug("JsonService.getJson: token passed, setting Authorization header");
headers["Authorization"] = "Bearer " + token;
}

if (req.status === 200) {
const contentType = req.getResponseHeader("Content-Type");
if (contentType) {
var found = allowedContentTypes.find(item => contentType.startsWith(item));
let response: Response;
try {
Log.debug("JsonService.getJson, url: ", url);
response = await fetch(url, { method: 'GET', headers });
} catch (err) {
Log.error("JsonService.getJson: network error");
throw new Error("Network Error");
}

if (found == "application/jwt") {
jwtHandler(req).then(resolve, reject);
return;
}
const allowedContentTypes = this._contentTypes;
const jwtHandler = this._jwtHandler

Log.debug("JsonService.getJson: HTTP response received, status", response.status);
if (response.status === 200) {
const contentType = response.headers.get("Content-Type");
if (contentType) {
var found = allowedContentTypes.find(item => contentType.startsWith(item));
if (found === "application/jwt") {
const text = await response.text();
return await jwtHandler(text);
}

if (found) {
try {
resolve(JSON.parse(req.responseText));
return;
}
catch (e) {
Log.error("JsonService.getJson: Error parsing JSON response", e.message);
reject(e);
return;
}
}
if (found) {
try {
const json = await response.json();
return json;
}
catch (e) {
Log.error("JsonService.getJson: Error parsing JSON response", e.message);
throw e;
}

reject(Error("Invalid response Content-Type: " + contentType + ", from URL: " + url));
}
else {
reject(Error(req.statusText + " (" + req.status + ")"));
}
};

req.onerror = function() {
Log.error("JsonService.getJson: network error");
reject(Error("Network Error"));
};

if (token) {
Log.debug("JsonService.getJson: token passed, setting Authorization header");
req.setRequestHeader("Authorization", "Bearer " + token);
}

req.send();
});
throw new Error("Invalid response Content-Type: " + contentType + ", from URL: " + url);
}

throw new Error(response.statusText + " (" + response.status + ")");
}

postForm(url: string, payload: any, basicAuth?: string): Promise<any> {
async postForm(url: string, payload: any, basicAuth?: string): Promise<any> {
if (!url){
Log.error("JsonService.postForm: No url passed");
throw new Error("url");
}

Log.debug("JsonService.postForm, url: ", url);

return new Promise((resolve, reject) => {
const headers: HeadersInit = {
"Content-Type": "application/x-www-form-urlencoded",
};
if (basicAuth !== undefined) {
headers["Authorization"] = "Basic " + btoa(basicAuth);
}

var req = new this._XMLHttpRequest();
req.open('POST', url);
const body = new URLSearchParams();
for (let key in payload) {
let value = payload[key];

var allowedContentTypes = this._contentTypes;
if (value) {
body.set(key, value);
}
}

req.onload = function() {
Log.debug("JsonService.postForm: HTTP response received, status", req.status);
let response: Response;
try {
Log.debug("JsonService.postForm, url: ", url);
response = await fetch(url, { method: 'POST', headers, body });
} catch (err) {
Log.error("JsonService.postForm: network error");
throw new Error("Network Error");
}

if (req.status === 200) {
const contentType = req.getResponseHeader("Content-Type");
if (contentType) {
var found = allowedContentTypes.find(item => contentType.startsWith(item));
if (found) {
try {
resolve(JSON.parse(req.responseText));
return;
}
catch (e) {
Log.error("JsonService.postForm: Error parsing JSON response", e.message);
reject(e);
return;
}
}
const allowedContentTypes = this._contentTypes;

Log.debug("JsonService.postForm: HTTP response received, status", response.status);
if (response.status === 200) {
const contentType = response.headers.get("Content-Type");
if (contentType) {
var found = allowedContentTypes.find(item => contentType.startsWith(item));
if (found) {
try {
const json = await response.json();
return json;
}

reject(Error("Invalid response Content-Type: " + contentType + ", from URL: " + url));
return;
}

if (req.status === 400) {
const contentType = req.getResponseHeader("Content-Type");
if (contentType) {
var found = allowedContentTypes.find(item => contentType.startsWith(item));
if (found) {
try {
var payload = JSON.parse(req.responseText);
if (payload && payload.error) {
Log.error("JsonService.postForm: Error from server: ", payload.error);
reject(new Error(payload.error));
return;
}
}
catch (e) {
Log.error("JsonService.postForm: Error parsing JSON response", e.message);
reject(e);
return;
}
}
catch (e) {
Log.error("JsonService.postForm: Error parsing JSON response", e.message);
throw e;
}
}
}

reject(Error(req.statusText + " (" + req.status + ")"));
};

req.onerror = function() {
Log.error("JsonService.postForm: network error");
reject(Error("Network Error"));
};

let body = "";
for(let key in payload) {

let value = payload[key];

if (value) {
throw new Error("Invalid response Content-Type: " + contentType + ", from URL: " + url);
}
else if (response.status === 400) {
const contentType = response.headers.get("Content-Type");
if (contentType) {
var found = allowedContentTypes.find(item => contentType.startsWith(item));
if (found) {
try {
const json = await response.json();
if (json && json.error) {
Log.error("JsonService.postForm: Error from server: ", json.error);
throw new Error(payload.error);
}

if (body.length > 0) {
body += "&";
return json;
}
catch (e) {
Log.error("JsonService.postForm: Error parsing JSON response", e.message);
throw e;
}

body += encodeURIComponent(key);
body += "=";
body += encodeURIComponent(value);
}
}

req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

if (basicAuth !== undefined)
{
req.setRequestHeader("Authorization", "Basic " + btoa(basicAuth));
}
throw new Error("Invalid response Content-Type: " + contentType + ", from URL: " + url);
}

req.send(body);
});
throw new Error(response.statusText + " (" + response.status + ")");
}
}
58 changes: 25 additions & 33 deletions src/TokenRevocationClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,15 @@ const RefreshTokenTypeHint = "refresh_token";

export class TokenRevocationClient {
private _settings: OidcClientSettingsStore
private _XMLHttpRequestCtor: typeof XMLHttpRequest;
private _metadataService: MetadataService;

constructor(settings: OidcClientSettingsStore, XMLHttpRequestCtor = XMLHttpRequest, MetadataServiceCtor = MetadataService) {
constructor(settings: OidcClientSettingsStore, MetadataServiceCtor = MetadataService) {
if (!settings) {
Log.error("TokenRevocationClient.ctor: No settings provided");
throw new Error("No settings provided.");
}

this._settings = settings;
this._XMLHttpRequestCtor = XMLHttpRequestCtor;
this._metadataService = new MetadataServiceCtor(this._settings);
}

Expand Down Expand Up @@ -52,37 +50,31 @@ export class TokenRevocationClient {
return this._revoke(url, client_id, client_secret, token, type);
}

_revoke(url: string, client_id: string, client_secret: string | undefined, token: string, type: string) {
async _revoke(url: string, client_id: string, client_secret: string | undefined, token: string, type: string) {
const headers: HeadersInit = {
"Content-Type": "application/x-www-form-urlencoded",
};

return new Promise<void>((resolve, reject) => {

var xhr = new this._XMLHttpRequestCtor();
xhr.open("POST", url);

xhr.onload = () => {
Log.debug("TokenRevocationClient.revoke: HTTP response received, status", xhr.status);

if (xhr.status === 200) {
resolve();
}
else {
reject(Error(xhr.statusText + " (" + xhr.status + ")"));
}
};
xhr.onerror = () => {
Log.debug("TokenRevocationClient.revoke: Network Error.")
reject("Network Error");
};

var body = "client_id=" + encodeURIComponent(client_id);
if (client_secret) {
body += "&client_secret=" + encodeURIComponent(client_secret);
}
body += "&token_type_hint=" + encodeURIComponent(type);
body += "&token=" + encodeURIComponent(token);
const body = new URLSearchParams();
body.set("client_id", client_id);
if (client_secret) {
body.set("client_secret", client_secret);
}
body.set("token_type_hint", type);
body.set("token", token);

let response: Response;
try {
Log.debug("TokenRevocationClient.revoke, url: ", url);
response = await fetch(url, { method: 'POST', headers, body });
} catch (err) {
Log.error("TokenRevocationClient.revoke: network error");
throw new Error("Network Error");
}

xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(body);
});
Log.debug("TokenRevocationClient.revoke: HTTP response received, status", response.status);
if (response.status !== 200) {
throw new Error(response.statusText + " (" + response.status + ")");
}
}
}
8 changes: 4 additions & 4 deletions src/UserInfoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class UserInfoService {
}

this._settings = settings;
this._jsonService = new JsonServiceCtor(undefined, undefined, this._getClaimsFromJwt.bind(this));
this._jsonService = new JsonServiceCtor(undefined, this._getClaimsFromJwt.bind(this));
this._metadataService = new MetadataServiceCtor(this._settings);
}

Expand All @@ -41,9 +41,9 @@ export class UserInfoService {
return claims;
}

async _getClaimsFromJwt(req: any) {
async _getClaimsFromJwt(responseText: string) {
try {
const jwt = JoseUtil.parseJwt(req.responseText);
const jwt = JoseUtil.parseJwt(responseText);
if (!jwt || !jwt.header || !jwt.payload) {
Log.error("UserInfoService._getClaimsFromJwt: Failed to parse JWT", jwt);
throw new Error("Failed to parse id_token");
Expand Down Expand Up @@ -104,7 +104,7 @@ export class UserInfoService {
let clockSkewInSeconds = this._settings.clockSkew;
Log.debug("UserInfoService._getClaimsFromJwt: Validaing JWT; using clock skew (in seconds) of: ", clockSkewInSeconds);

await JoseUtil.validateJwt(req.responseText, key, issuer, audience, clockSkewInSeconds, undefined, true);
await JoseUtil.validateJwt(responseText, key, issuer, audience, clockSkewInSeconds, undefined, true);
Log.debug("UserInfoService._getClaimsFromJwt: JWT validation successful");
return payload;
}
Expand Down
Loading

0 comments on commit 4fec4d7

Please sign in to comment.