Skip to content

Commit

Permalink
Make the fetch wrapper more compatible with fetch
Browse files Browse the repository at this point in the history
(closes #117)

This makes `oauth.fetch` work exactly like regular `fetch`.

Also adds an `authenticateAsync` function that works just like
 `authenticate`, but wraps in a Promise for chaining.
  • Loading branch information
bhousel committed Jul 12, 2023
1 parent 792c6f5 commit 0f48eb8
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 139 deletions.
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,15 @@ Param: `callback` An "errback"-style callback (`err`, `result`), called when
Returns: none<br/>


## `.authenticateAsync()`

Promisified version of `.authenticate()`<br/>
First logs out, then runs the authentication flow and resolves if successful, or rejects if not.<br/>
<br/>
Param: `callback` An "errback"-style callback (`err`, `result`), called when complete<br/>
Returns: `Promise` settled with whatever authenticate did.<br/>


## `.bringPopupWindowToFront()`

Tries to bring an existing authentication popup to the front.<br/>
Expand All @@ -205,18 +214,14 @@ Param: `callback` An "errback"-style callback (`err`, `result`), called when
Returns: none<br/>


## `.fetch(path, options)`
## `.fetch(resource, options)`

A `fetch` wrapper that does authenticated calls if the user has logged in.<br/>
A `fetch` wrapper that includes the Authorization header if the user is authenticated.<br/>
See: https://developer.mozilla.org/en-US/docs/Web/API/fetch<br/>
<br/>
Param: `path` The URL path (e.g. "/api/0.6/user/details") (or full url, if `options.prefix`=`false`)<br/>
Param: `options`:<br/>
`options.method` Passed to `fetch` (e.g. 'GET', 'POST')<br/>
`options.prefix` If `true` path contains a path, if `false` path contains the full url<br/>
`options.body` Passed to `fetch`<br/>
`options.headers` optional `Object` containing request headers<br/>
Return: `Promise` that resolves to a `Response` if authenticated, otherwise `null`<br/>
Param: `resource` Resource passed to `fetch`<br/>
Param: `options` Options passed to `fetch`<br/>
Return: `Promise` that wraps `authenticateAsync` then `fetch`<br/>


## `.xhr(options, callback)`
Expand Down
71 changes: 35 additions & 36 deletions dist/osm-auth.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,26 @@ function osmAuth(o) {
return;
}
oauth.logout();
generatePkceChallenge(function(pkce) {
_generatePkceChallenge(function(pkce) {
_authenticate(pkce, callback);
});
};
oauth.authenticateAsync = function() {
if (oauth.authenticated()) {
return Promise.resolve(oauth);
}
oauth.logout();
return new Promise((resolve, reject) => {
var errback = (err, result) => {
if (err) {
reject(new Error(err));

This comment has been minimized.

Copy link
@tyrasd

tyrasd Oct 6, 2023

Member

was it intentional to wrap the Error objects from err inside another layer of Error here?

This comment has been minimized.

Copy link
@bhousel

bhousel Oct 6, 2023

Author Member

no, of course not.

} else {
resolve(result);
}
};
_generatePkceChallenge((pkce) => _authenticate(pkce, errback));
});
};
function _authenticate(pkce, callback) {
var state = generateState();
var url = o.url + "/oauth2/authorize?" + utilQsString({
Expand Down Expand Up @@ -107,7 +123,7 @@ function osmAuth(o) {
callback(error);
return;
}
getAccessToken(params2.code, pkce.code_verifier, accessTokenDone);
_getAccessToken(params2.code, pkce.code_verifier, accessTokenDone);
delete window.authComplete;
};
function accessTokenDone(err, xhr) {
Expand All @@ -121,7 +137,7 @@ function osmAuth(o) {
callback(null, oauth);
}
}
function getAccessToken(auth_code, code_verifier, accessTokenDone) {
function _getAccessToken(auth_code, code_verifier, accessTokenDone) {
var url = o.url + "/oauth2/token?" + utilQsString({
client_id: o.client_id,
redirect_uri: o.redirect_uri,
Expand Down Expand Up @@ -155,7 +171,7 @@ function osmAuth(o) {
}
var code_verifier = token("oauth2_pkce_code_verifier");
token("oauth2_pkce_code_verifier", "");
getAccessToken(auth_code, code_verifier, accessTokenDone);
_getAccessToken(auth_code, code_verifier, accessTokenDone);
function accessTokenDone(err, xhr) {
o.done();
if (err) {
Expand All @@ -167,55 +183,38 @@ function osmAuth(o) {
callback(null, oauth);
}
};
oauth.fetch = function(path, options, callback) {
oauth.fetch = function(resource, options) {
if (oauth.authenticated()) {
return run();
return _doFetch();
} else {
if (o.auto) {
oauth.authenticate(run);
return;
return oauth.authenticateAsync().then(_doFetch);
} else {
callback("not authenticated", null);
return;
return Promise.reject(new Error("not authenticated"));
}
}
function run() {
var url = options.prefix !== false ? o.url + path : path;
var headers = options.headers || { "Content-Type": "application/x-www-form-urlencoded" };
headers.Authorization = "Bearer " + token("oauth2_access_token");
return fetch(url, {
method: options.method,
body: options.body,
headers
}).then((resp) => {
var contentType = resp.headers.get("content-type").split(";")[0];
switch (contentType) {
case "text/html":
case "text/xml":
return resp.text().then(
(txt) => new window.DOMParser().parseFromString(txt, contentType)
);
case "application/html":
return resp.json();
default:
return resp.text();
}
});
function _doFetch() {
options = options || {};
if (!options.headers) {
options.headers = { "Content-Type": "application/x-www-form-urlencoded" };
}
options.headers.Authorization = "Bearer " + token("oauth2_access_token");
return fetch(resource, options);
}
};
oauth.xhr = function(options, callback) {
if (oauth.authenticated()) {
return run();
return _doXHR();
} else {
if (o.auto) {
oauth.authenticate(run);
oauth.authenticate(_doXHR);
return;
} else {
callback("not authenticated", null);
return;
}
}
function run() {
function _doXHR() {
var url = options.prefix !== false ? o.url + options.path : options.path;
return oauth.rawxhr(
options.method,
Expand Down Expand Up @@ -322,7 +321,7 @@ function utilStringQs(str) {
function supportsWebCryptoAPI() {
return window && window.crypto && window.crypto.getRandomValues && window.crypto.subtle && window.crypto.subtle.digest;
}
function generatePkceChallenge(callback) {
function _generatePkceChallenge(callback) {
var code_verifier;
if (supportsWebCryptoAPI()) {
var random = window.crypto.getRandomValues(new Uint8Array(32));
Expand Down
Loading

0 comments on commit 0f48eb8

Please sign in to comment.