Skip to content

Commit

Permalink
feat(UserSession): multiple requests to getToken for similar URLs now…
Browse files Browse the repository at this point in the history
… return the same Promise

When called many times in quick succession getToken would make multiple requests to
token
endpoints. Now only 1 request is made and the existing Promise is returned.

AFFECTS PACKAGES:
@esri/arcgis-rest-auth
  • Loading branch information
patrickarlt committed Sep 25, 2017
1 parent e64bca6 commit 751e5f1
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 2 deletions.
23 changes: 21 additions & 2 deletions packages/arcgis-rest-auth/src/UserSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ export class UserSession implements IAuthenticationManager {
private _tokenExpires: Date;
private _refreshToken: string;
private _refreshTokenExpires: Date;
private _pendingTokenRequests: {
[key: string]: Promise<string>;
};

/**
* Internal list of trusted 3rd party servers (federated servers) that have
Expand Down Expand Up @@ -229,6 +232,7 @@ export class UserSession implements IAuthenticationManager {
this.redirectUri = options.redirectUri;
this.refreshTokenDuration = options.refreshTokenDuration || 20160;
this.trustedServers = {};
this._pendingTokenRequests = {};
}

/**
Expand Down Expand Up @@ -504,7 +508,11 @@ export class UserSession implements IAuthenticationManager {
return Promise.resolve(existingToken.token);
}

return request(`${root}/rest/info`)
if (this._pendingTokenRequests[root]) {
return this._pendingTokenRequests[root];
}

this._pendingTokenRequests[root] = request(`${root}/rest/info`)
.then((response: any) => {
return response.owningSystemUrl;
})
Expand Down Expand Up @@ -538,6 +546,8 @@ export class UserSession implements IAuthenticationManager {
};
return response.token;
});

return this._pendingTokenRequests[root];
}

/**
Expand All @@ -552,7 +562,16 @@ export class UserSession implements IAuthenticationManager {
return Promise.resolve(this.token);
}

return this.refreshSession().then(session => session.token);
if (!this._pendingTokenRequests[this.portal]) {
this._pendingTokenRequests[
this.portal
] = this.refreshSession().then(session => {
this._pendingTokenRequests[this.portal] = null;
return session.token;
});
}

return this._pendingTokenRequests[this.portal];
}

/**
Expand Down
99 changes: 99 additions & 0 deletions packages/arcgis-rest-auth/test/UserSession.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import * as fetchMock from "fetch-mock";
import { YESTERDAY, TOMORROW } from "./utils";

describe("UserSession", () => {
afterEach(fetchMock.restore);

it("should serialze to and from JSON", () => {
const session = new UserSession({
clientId: "clientId",
Expand Down Expand Up @@ -155,6 +157,67 @@ describe("UserSession", () => {
});
});

it("should only make 1 token request to an untrusted portal for similar URLs", done => {
const session = new UserSession({
clientId: "id",
token: "token",
refreshToken: "refresh",
tokenExpires: TOMORROW,
portal: "https://gis.city.gov/sharing/rest"
});

fetchMock.mock(
"https://gisservices.city.gov/public/rest/info",
{
currentVersion: 10.51,
fullVersion: "10.5.1.120",
owningSystemUrl: "https://gis.city.gov",
authInfo: {
isTokenBasedSecurity: true,
tokenServicesUrl: "https://gis.city.gov/sharing/generateToken"
}
},
{ times: 1, method: "POST" }
);

fetchMock.mock(
"https://gis.city.gov/sharing/rest/info",
{
owningSystemUrl: "http://gis.city.gov",
authInfo: {
tokenServicesUrl: "https://gis.city.gov/sharing/generateToken",
isTokenBasedSecurity: true
}
},
{ times: 1, method: "POST" }
);

fetchMock.mock(
"https://gis.city.gov/sharing/generateToken",
{
token: "serverToken",
expires: TOMORROW
},
{ times: 1, method: "POST" }
);

Promise.all([
session.getToken(
"https://gisservices.city.gov/public/rest/services/trees/FeatureServer/0/query"
),
session.getToken(
"https://gisservices.city.gov/public/rest/services/trees/FeatureServer/0/query"
)
]).then(([token1, token2]) => {
expect(token1).toBe("serverToken");
expect(token2).toBe("serverToken");
expect(
fetchMock.calls("https://gis.city.gov/sharing/generateToken").length
).toBe(1);
done();
});
});

it("should throw an ArcGISAuthError when the owning system doesn't match", done => {
const session = new UserSession({
clientId: "id",
Expand Down Expand Up @@ -291,6 +354,42 @@ describe("UserSession", () => {
done();
});
});

it("should only make 1 token request to the portal for similar URLs", done => {
const session = new UserSession({
clientId: "id",
token: "token",
refreshToken: "refresh",
tokenExpires: YESTERDAY
});

fetchMock.mock(
"https://www.arcgis.com/sharing/rest/oauth2/token",
{
access_token: "new",
expires_in: 1800,
username: "casey"
},
{ times: 1, method: "POST" }
);

Promise.all([
session.getToken("https://www.arcgis.com/sharing/rest/portals/self"),
session.getToken("https://www.arcgis.com/sharing/rest/portals/self")
])
.then(([token1, token2]) => {
expect(token1).toBe("new");
expect(token2).toBe("new");
expect(
fetchMock.calls("https://www.arcgis.com/sharing/rest/oauth2/token")
.length
).toBe(1);
done();
})
.catch(e => {
fail(e);
});
});
});

describe(".beginOAuth2()", () => {
Expand Down

0 comments on commit 751e5f1

Please sign in to comment.