halo-infinite-api 3.2.2 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,11 @@
1
1
  import axios from "axios";
2
2
  import { DateTime } from "luxon";
3
- import { coalesceDateTime } from "../util/date-time";
4
3
  import { ExpiryTokenCache } from "../util/expiry-token-cache";
5
4
  export class HaloAuthenticationClient {
6
5
  fetchXstsToken;
7
6
  loadToken;
8
7
  saveToken;
9
8
  spartanTokenCache = new ExpiryTokenCache(async () => {
10
- const persistedToken = await this.loadToken();
11
- if (persistedToken?.expiresAt) {
12
- const currentToken = {
13
- token: persistedToken.token,
14
- expiresAt: coalesceDateTime(persistedToken.expiresAt),
15
- };
16
- if (currentToken.expiresAt && currentToken.expiresAt > DateTime.now()) {
17
- return currentToken;
18
- }
19
- }
20
9
  const xstsToken = await this.fetchXstsToken();
21
10
  const tokenRequest = {
22
11
  Audience: "urn:343:s3:services",
@@ -40,7 +29,7 @@ export class HaloAuthenticationClient {
40
29
  };
41
30
  await this.saveToken(newToken);
42
31
  return newToken;
43
- });
32
+ }, () => this.loadToken());
44
33
  constructor(fetchXstsToken, loadToken, saveToken) {
45
34
  this.fetchXstsToken = fetchXstsToken;
46
35
  this.loadToken = loadToken;
@@ -1 +1 @@
1
- {"version":3,"file":"halo-authentication-client.js","sourceRoot":"","sources":["../../src/authentication/halo-authentication-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAO9D,MAAM,OAAO,wBAAwB;IA+ChB;IACA;IAIA;IAnDX,iBAAiB,GAAG,IAAI,gBAAgB,CAAC,KAAK,IAAI,EAAE;QAC1D,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QAE9C,IAAI,cAAc,EAAE,SAAS,EAAE;YAC7B,MAAM,YAAY,GAAG;gBACnB,KAAK,EAAE,cAAc,CAAC,KAAK;gBAC3B,SAAS,EAAE,gBAAgB,CAAC,cAAc,CAAC,SAAS,CAAa;aAClE,CAAC;YACF,IAAI,YAAY,CAAC,SAAS,IAAI,YAAY,CAAC,SAAS,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE;gBACrE,OAAO,YAAY,CAAC;aACrB;SACF;QAED,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAE9C,MAAM,YAAY,GAAwB;YACxC,QAAQ,EAAE,qBAAqB;YAC/B,UAAU,EAAE,GAAG;YACf,KAAK,EAAE;gBACL;oBACE,KAAK,EAAE,SAAS;oBAChB,SAAS,EAAE,aAAa;iBACzB;aACF;SACF,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAC/B,qDAAqD,EACrD,YAAY,EACZ;YACE,OAAO,EAAE;gBACP,YAAY,EACV,gEAAgE;gBAClE,cAAc,EAAE,iCAAiC;aAClD;SACF,CACF,CAAC;QAEF,MAAM,QAAQ,GAAG;YACf,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,YAAY;YACjC,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;SAClE,CAAC;QACF,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC/B,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,YACmB,cAA8C,EAC9C,SAGR,EACQ,SAA0C;QAL1C,mBAAc,GAAd,cAAc,CAAgC;QAC9C,cAAS,GAAT,SAAS,CAGjB;QACQ,cAAS,GAAT,SAAS,CAAiC;IAC1D,CAAC;IAEG,KAAK,CAAC,eAAe;QAC1B,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
1
+ {"version":3,"file":"halo-authentication-client.js","sourceRoot":"","sources":["../../src/authentication/halo-authentication-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAIjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAO9D,MAAM,OAAO,wBAAwB;IAsChB;IACA;IAIA;IA1CX,iBAAiB,GAAG,IAAI,gBAAgB,CAC9C,KAAK,IAAI,EAAE;QACT,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAE9C,MAAM,YAAY,GAAwB;YACxC,QAAQ,EAAE,qBAAqB;YAC/B,UAAU,EAAE,GAAG;YACf,KAAK,EAAE;gBACL;oBACE,KAAK,EAAE,SAAS;oBAChB,SAAS,EAAE,aAAa;iBACzB;aACF;SACF,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAC/B,qDAAqD,EACrD,YAAY,EACZ;YACE,OAAO,EAAE;gBACP,YAAY,EACV,gEAAgE;gBAClE,cAAc,EAAE,iCAAiC;aAClD;SACF,CACF,CAAC;QAEF,MAAM,QAAQ,GAAG;YACf,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,YAAY;YACjC,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;SAClE,CAAC;QACF,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC/B,OAAO,QAAQ,CAAC;IAClB,CAAC,EACD,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CACvB,CAAC;IAEF,YACmB,cAA8C,EAC9C,SAGR,EACQ,SAA0C;QAL1C,mBAAc,GAAd,cAAc,CAAgC;QAC9C,cAAS,GAAT,SAAS,CAGjB;QACQ,cAAS,GAAT,SAAS,CAAiC;IAC1D,CAAC;IAEG,KAAK,CAAC,eAAe;QAC1B,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,CAAC;QAC1D,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
@@ -15,8 +15,7 @@ export declare class XboxAuthenticationClient {
15
15
  private xstsTicketCache;
16
16
  private readonly httpClient;
17
17
  constructor(tokenPersister?: TokenPersister | undefined);
18
- getUserToken(accessToken: string): Promise<string>;
19
- getXstsTicket(userToken: string, relyingParty: RelyingParty): Promise<{
18
+ getXstsTicket(getOauth2AccessToken: () => Promise<string>): Promise<Omit<{
20
19
  expiresAt: DateTime;
21
20
  IssueInstant: string;
22
21
  NotAfter: string;
@@ -32,6 +31,8 @@ export declare class XboxAuthenticationClient {
32
31
  prv: string;
33
32
  }];
34
33
  };
34
+ }, "expiresAt"> & {
35
+ expiresAt: unknown;
35
36
  }>;
36
37
  getXboxLiveV3Token: (userHash: string, userToken: string) => string;
37
38
  }
@@ -1,6 +1,5 @@
1
1
  import axios from "axios";
2
2
  import { DateTime } from "luxon";
3
- import { coalesceDateTime } from "../util/date-time";
4
3
  import { ExpiryTokenCache } from "../util/expiry-token-cache";
5
4
  export var RelyingParty;
6
5
  (function (RelyingParty) {
@@ -10,13 +9,6 @@ export var RelyingParty;
10
9
  export class XboxAuthenticationClient {
11
10
  tokenPersister;
12
11
  userTokenCache = new ExpiryTokenCache(async (accessToken) => {
13
- const persistedToken = await this.tokenPersister?.load("xbox.userToken");
14
- if (persistedToken?.expiresAt) {
15
- const expiresAt = coalesceDateTime(persistedToken.expiresAt);
16
- if (expiresAt && expiresAt > DateTime.now()) {
17
- return { ...persistedToken, expiresAt };
18
- }
19
- }
20
12
  const response = await this.httpClient.post("https://user.auth.xboxlive.com/user/authenticate", {
21
13
  RelyingParty: "http://auth.xboxlive.com",
22
14
  TokenType: "JWT",
@@ -38,15 +30,8 @@ export class XboxAuthenticationClient {
38
30
  };
39
31
  await this.tokenPersister?.save("xbox.userToken", result);
40
32
  return result;
41
- });
33
+ }, async () => (await this.tokenPersister?.load("xbox.userToken")) ?? null);
42
34
  xstsTicketCache = new ExpiryTokenCache(async (userToken, relyingParty) => {
43
- const persistedToken = await this.tokenPersister?.load("xbox.xstsTicket");
44
- if (persistedToken?.expiresAt) {
45
- const expiresAt = coalesceDateTime(persistedToken.expiresAt);
46
- if (expiresAt && expiresAt > DateTime.now()) {
47
- return { ...persistedToken, expiresAt };
48
- }
49
- }
50
35
  const response = await this.httpClient.post("https://xsts.auth.xboxlive.com/xsts/authorize", {
51
36
  RelyingParty: relyingParty,
52
37
  TokenType: "JWT",
@@ -67,18 +52,24 @@ export class XboxAuthenticationClient {
67
52
  };
68
53
  await this.tokenPersister?.save("xbox.xstsTicket", result);
69
54
  return result;
70
- });
55
+ }, async () => (await this.tokenPersister?.load("xbox.xstsTicket")) ?? null);
71
56
  httpClient;
72
57
  constructor(tokenPersister) {
73
58
  this.tokenPersister = tokenPersister;
74
59
  this.httpClient = axios.create();
75
60
  }
76
- async getUserToken(accessToken) {
77
- const { Token } = await this.userTokenCache.getToken(accessToken);
78
- return Token;
79
- }
80
- getXstsTicket(userToken, relyingParty) {
81
- return this.xstsTicketCache.getToken(userToken, relyingParty);
61
+ async getXstsTicket(getOauth2AccessToken) {
62
+ let xstsTicket = await this.xstsTicketCache.getExistingToken();
63
+ if (!xstsTicket) {
64
+ let userToken = await this.userTokenCache.getExistingToken();
65
+ if (!userToken) {
66
+ // Ouath2 token depends on nothing, so we can fetch it without
67
+ // worrying if it is expired.
68
+ userToken = await this.userTokenCache.getToken(await getOauth2AccessToken());
69
+ }
70
+ xstsTicket = await this.xstsTicketCache.getToken(userToken.Token, RelyingParty.Halo);
71
+ }
72
+ return xstsTicket;
82
73
  }
83
74
  getXboxLiveV3Token = (userHash, userToken) => `XBL3.0 x=${userHash};${userToken}`;
84
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"xbox-authentication-client.js","sourceRoot":"","sources":["../../src/authentication/xbox-authentication-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"xbox-authentication-client.js","sourceRoot":"","sources":["../../src/authentication/xbox-authentication-client.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { RelyingParty, XboxAuthenticationClient, } from "../../authentication/xbox-authentication-client";
1
+ import { XboxAuthenticationClient, } from "../../authentication/xbox-authentication-client";
2
2
  import { HaloAuthenticationClient } from "../../authentication/halo-authentication-client";
3
3
  import { inMemoryTokenPersister } from "../token-persisters/in-memory-token-persister";
4
4
  /**
@@ -18,8 +18,7 @@ export class AutoXstsSpartanTokenProvider {
18
18
  }
19
19
  const xboxAuthClient = new XboxAuthenticationClient(tokenPersister);
20
20
  const haloAuthClient = new HaloAuthenticationClient(async () => {
21
- const userToken = await xboxAuthClient.getUserToken(await getOauth2AccessToken());
22
- const xstsTicket = await xboxAuthClient.getXstsTicket(userToken, RelyingParty.Halo);
21
+ const xstsTicket = await xboxAuthClient.getXstsTicket(getOauth2AccessToken);
23
22
  return xstsTicket.Token;
24
23
  }, async () => await actualTokenPersister.load("halo.authToken"), async (token) => {
25
24
  await actualTokenPersister.save("halo.authToken", token);
@@ -1 +1 @@
1
- {"version":3,"file":"auto-xsts-spartan-token-provider.js","sourceRoot":"","sources":["../../../src/core/spartan-token-providers/auto-xsts-spartan-token-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,wBAAwB,GACzB,MAAM,iDAAiD,CAAC;AAEzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,iDAAiD,CAAC;AAE3F,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AAEvF;;;;GAIG;AACH,MAAM,OAAO,4BAA4B;IACvB,eAAe,CAAwB;IAEvD,YACE,oBAA2C,EAC3C,cAA+B;QAE/B,IAAI,oBAAoC,CAAC;QACzC,IAAI,cAAc,EAAE;YAClB,oBAAoB,GAAG,cAAc,CAAC;SACvC;aAAM;YACL,oBAAoB,GAAG,sBAAsB,CAAC;SAC/C;QACD,MAAM,cAAc,GAAG,IAAI,wBAAwB,CAAC,cAAc,CAAC,CAAC;QACpE,MAAM,cAAc,GAAG,IAAI,wBAAwB,CACjD,KAAK,IAAI,EAAE;YACT,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,YAAY,CACjD,MAAM,oBAAoB,EAAE,CAC7B,CAAC;YACF,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,CACnD,SAAS,EACT,YAAY,CAAC,IAAI,CAClB,CAAC;YACF,OAAO,UAAU,CAAC,KAAK,CAAC;QAC1B,CAAC,EACD,KAAK,IAAI,EAAE,CAAC,MAAM,oBAAoB,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAC7D,KAAK,EAAE,KAAK,EAAE,EAAE;YACd,MAAM,oBAAoB,CAAC,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;QAC3D,CAAC,CACF,CAAC;QAEF,IAAI,CAAC,eAAe,GAAG,GAAG,EAAE,CAAC,cAAc,CAAC,eAAe,EAAE,CAAC;IAChE,CAAC;CACF"}
1
+ {"version":3,"file":"auto-xsts-spartan-token-provider.js","sourceRoot":"","sources":["../../../src/core/spartan-token-providers/auto-xsts-spartan-token-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,wBAAwB,GACzB,MAAM,iDAAiD,CAAC;AAEzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,iDAAiD,CAAC;AAE3F,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AAEvF;;;;GAIG;AACH,MAAM,OAAO,4BAA4B;IACvB,eAAe,CAAwB;IAEvD,YACE,oBAA2C,EAC3C,cAA+B;QAE/B,IAAI,oBAAoC,CAAC;QACzC,IAAI,cAAc,EAAE;YAClB,oBAAoB,GAAG,cAAc,CAAC;SACvC;aAAM;YACL,oBAAoB,GAAG,sBAAsB,CAAC;SAC/C;QACD,MAAM,cAAc,GAAG,IAAI,wBAAwB,CAAC,cAAc,CAAC,CAAC;QACpE,MAAM,cAAc,GAAG,IAAI,wBAAwB,CACjD,KAAK,IAAI,EAAE;YACT,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,aAAa,CACnD,oBAAoB,CACrB,CAAC;YACF,OAAO,UAAU,CAAC,KAAK,CAAC;QAC1B,CAAC,EACD,KAAK,IAAI,EAAE,CAAC,MAAM,oBAAoB,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAC7D,KAAK,EAAE,KAAK,EAAE,EAAE;YACd,MAAM,oBAAoB,CAAC,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;QAC3D,CAAC,CACF,CAAC;QAEF,IAAI,CAAC,eAAe,GAAG,GAAG,EAAE,CAAC,cAAc,CAAC,eAAe,EAAE,CAAC;IAChE,CAAC;CACF"}
@@ -2,8 +2,13 @@ import { DateTime } from "luxon";
2
2
  export declare class ExpiryTokenCache<TToken extends {
3
3
  expiresAt: DateTime;
4
4
  }, TArgs extends any[]> {
5
- private readonly tokenFetcher;
5
+ private readonly generateNewToken;
6
+ readonly getExistingToken: () => Promise<(Omit<TToken, "expiresAt"> & {
7
+ expiresAt: unknown;
8
+ }) | null>;
6
9
  private tokenFetchPromise;
7
- constructor(tokenFetcher: (...args: TArgs) => Promise<TToken>);
10
+ constructor(generateNewToken: (...args: TArgs) => Promise<TToken>, getExistingToken: () => Promise<(Omit<TToken, "expiresAt"> & {
11
+ expiresAt: unknown;
12
+ }) | null>);
8
13
  getToken(...args: TArgs): Promise<TToken>;
9
14
  }
@@ -1,10 +1,13 @@
1
1
  import { DateTime } from "luxon";
2
2
  import { ResolvablePromise } from "./resolvable-promise";
3
+ import { coalesceDateTime } from "./date-time";
3
4
  export class ExpiryTokenCache {
4
- tokenFetcher;
5
+ generateNewToken;
6
+ getExistingToken;
5
7
  tokenFetchPromise = undefined;
6
- constructor(tokenFetcher) {
7
- this.tokenFetcher = tokenFetcher;
8
+ constructor(generateNewToken, getExistingToken) {
9
+ this.generateNewToken = generateNewToken;
10
+ this.getExistingToken = getExistingToken;
8
11
  }
9
12
  // TODO: Compare args and separate cache entries based on input
10
13
  async getToken(...args) {
@@ -20,7 +23,7 @@ export class ExpiryTokenCache {
20
23
  // Current token expired, start a new promise
21
24
  this.tokenFetchPromise = new ResolvablePromise();
22
25
  try {
23
- const newToken = await this.tokenFetcher(...args);
26
+ const newToken = await this.generateNewToken(...args);
24
27
  this.tokenFetchPromise.resolve(newToken);
25
28
  return newToken;
26
29
  }
@@ -35,7 +38,16 @@ export class ExpiryTokenCache {
35
38
  // No one has a token, start a new promise
36
39
  this.tokenFetchPromise = new ResolvablePromise();
37
40
  try {
38
- const newToken = await this.tokenFetcher(...args);
41
+ const existingToken = await this.getExistingToken();
42
+ if (existingToken?.expiresAt) {
43
+ const expiresAt = coalesceDateTime(existingToken.expiresAt);
44
+ if (expiresAt && expiresAt > DateTime.now()) {
45
+ const newToken = { ...existingToken, expiresAt };
46
+ this.tokenFetchPromise.resolve(newToken);
47
+ return newToken;
48
+ }
49
+ }
50
+ const newToken = await this.generateNewToken(...args);
39
51
  this.tokenFetchPromise.resolve(newToken);
40
52
  return newToken;
41
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"expiry-token-cache.js","sourceRoot":"","sources":["../../src/util/expiry-token-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,OAAO,gBAAgB;IAOR;IAHX,iBAAiB,GAA0C,SAAS,CAAC;IAE7E,YACmB,YAAiD;QAAjD,iBAAY,GAAZ,YAAY,CAAqC;IACjE,CAAC;IAEJ,+DAA+D;IAC/D,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAW;QAC3B,IAAI,IAAI,CAAC,iBAAiB,EAAE;YAC1B,yEAAyE;YACzE,mDAAmD;YACnD,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC;YAElD,IAAI,YAAY,CAAC,SAAS,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE;gBAC3C,oCAAoC;gBACpC,OAAO,YAAY,CAAC;aACrB;iBAAM;gBACL,6CAA6C;gBAC7C,IAAI,CAAC,iBAAiB,GAAG,IAAI,iBAAiB,EAAU,CAAC;gBAEzD,IAAI;oBACF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,CAAC;oBAClD,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;oBACzC,OAAO,QAAQ,CAAC;iBACjB;gBAAC,OAAO,CAAC,EAAE;oBACV,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;oBACjC,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;oBACnC,MAAM,CAAC,CAAC;iBACT;aACF;SACF;aAAM;YACL,0CAA0C;YAC1C,IAAI,CAAC,iBAAiB,GAAG,IAAI,iBAAiB,EAAU,CAAC;YAEzD,IAAI;gBACF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,CAAC;gBAClD,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACzC,OAAO,QAAQ,CAAC;aACjB;YAAC,OAAO,CAAC,EAAE;gBACV,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACjC,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;gBACnC,MAAM,CAAC,CAAC;aACT;SACF;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"expiry-token-cache.js","sourceRoot":"","sources":["../../src/util/expiry-token-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,OAAO,gBAAgB;IAOR;IACD;IAJV,iBAAiB,GAA0C,SAAS,CAAC;IAE7E,YACmB,gBAAqD,EACtD,gBAEf;QAHgB,qBAAgB,GAAhB,gBAAgB,CAAqC;QACtD,qBAAgB,GAAhB,gBAAgB,CAE/B;IACA,CAAC;IAEJ,+DAA+D;IAC/D,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAW;QAC3B,IAAI,IAAI,CAAC,iBAAiB,EAAE;YAC1B,yEAAyE;YACzE,mDAAmD;YACnD,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC;YAElD,IAAI,YAAY,CAAC,SAAS,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE;gBAC3C,oCAAoC;gBACpC,OAAO,YAAY,CAAC;aACrB;iBAAM;gBACL,6CAA6C;gBAC7C,IAAI,CAAC,iBAAiB,GAAG,IAAI,iBAAiB,EAAU,CAAC;gBAEzD,IAAI;oBACF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,CAAC;oBACtD,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;oBACzC,OAAO,QAAQ,CAAC;iBACjB;gBAAC,OAAO,CAAC,EAAE;oBACV,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;oBACjC,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;oBACnC,MAAM,CAAC,CAAC;iBACT;aACF;SACF;aAAM;YACL,0CAA0C;YAC1C,IAAI,CAAC,iBAAiB,GAAG,IAAI,iBAAiB,EAAU,CAAC;YAEzD,IAAI;gBACF,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAEpD,IAAI,aAAa,EAAE,SAAS,EAAE;oBAC5B,MAAM,SAAS,GAAG,gBAAgB,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;oBAC5D,IAAI,SAAS,IAAI,SAAS,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE;wBAC3C,MAAM,QAAQ,GAAG,EAAE,GAAG,aAAa,EAAE,SAAS,EAAY,CAAC;wBAC3D,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;wBACzC,OAAO,QAAQ,CAAC;qBACjB;iBACF;gBAED,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,CAAC;gBACtD,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACzC,OAAO,QAAQ,CAAC;aACjB;YAAC,OAAO,CAAC,EAAE;gBACV,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACjC,IAAI,CAAC,iBAAiB,GAAG,SAAS,CAAC;gBACnC,MAAM,CAAC,CAAC;aACT;SACF;IACH,CAAC;CACF"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "halo-infinite-api",
3
3
  "type": "module",
4
- "version": "3.2.2",
4
+ "version": "4.0.1",
5
5
  "description": "An NPM package for accessing the official Halo Infinite API.",
6
6
  "main": "dist/index.js",
7
7
  "scripts": {
@@ -11,50 +11,41 @@ export interface Token {
11
11
  }
12
12
 
13
13
  export class HaloAuthenticationClient {
14
- private spartanTokenCache = new ExpiryTokenCache(async () => {
15
- const persistedToken = await this.loadToken();
16
-
17
- if (persistedToken?.expiresAt) {
18
- const currentToken = {
19
- token: persistedToken.token,
20
- expiresAt: coalesceDateTime(persistedToken.expiresAt) as DateTime,
14
+ private spartanTokenCache = new ExpiryTokenCache(
15
+ async () => {
16
+ const xstsToken = await this.fetchXstsToken();
17
+
18
+ const tokenRequest: SpartanTokenRequest = {
19
+ Audience: "urn:343:s3:services",
20
+ MinVersion: "4",
21
+ Proof: [
22
+ {
23
+ Token: xstsToken,
24
+ TokenType: "Xbox_XSTSv3",
25
+ },
26
+ ],
21
27
  };
22
- if (currentToken.expiresAt && currentToken.expiresAt > DateTime.now()) {
23
- return currentToken;
24
- }
25
- }
26
-
27
- const xstsToken = await this.fetchXstsToken();
28
-
29
- const tokenRequest: SpartanTokenRequest = {
30
- Audience: "urn:343:s3:services",
31
- MinVersion: "4",
32
- Proof: [
28
+ const response = await axios.post<SpartanToken>(
29
+ "https://settings.svc.halowaypoint.com/spartan-token",
30
+ tokenRequest,
33
31
  {
34
- Token: xstsToken,
35
- TokenType: "Xbox_XSTSv3",
36
- },
37
- ],
38
- };
39
- const response = await axios.post<SpartanToken>(
40
- "https://settings.svc.halowaypoint.com/spartan-token",
41
- tokenRequest,
42
- {
43
- headers: {
44
- "User-Agent":
45
- "HaloWaypoint/2021112313511900 CFNetwork/1327.0.4 Darwin/21.2.0",
46
- "Content-Type": "application/json; charset=utf-8",
47
- },
48
- }
49
- );
50
-
51
- const newToken = {
52
- token: response.data.SpartanToken,
53
- expiresAt: DateTime.fromISO(response.data.ExpiresUtc.ISO8601Date),
54
- };
55
- await this.saveToken(newToken);
56
- return newToken;
57
- });
32
+ headers: {
33
+ "User-Agent":
34
+ "HaloWaypoint/2021112313511900 CFNetwork/1327.0.4 Darwin/21.2.0",
35
+ "Content-Type": "application/json; charset=utf-8",
36
+ },
37
+ }
38
+ );
39
+
40
+ const newToken = {
41
+ token: response.data.SpartanToken,
42
+ expiresAt: DateTime.fromISO(response.data.ExpiresUtc.ISO8601Date),
43
+ };
44
+ await this.saveToken(newToken);
45
+ return newToken;
46
+ },
47
+ () => this.loadToken()
48
+ );
58
49
 
59
50
  constructor(
60
51
  private readonly fetchXstsToken: () => Promise<string> | string,
@@ -17,58 +17,42 @@ export interface XboxAuthenticationToken {
17
17
  }
18
18
 
19
19
  export class XboxAuthenticationClient {
20
- private userTokenCache = new ExpiryTokenCache(async (accessToken: string) => {
21
- const persistedToken = await this.tokenPersister?.load<
22
- XboxTicket & { expiresAt?: unknown }
23
- >("xbox.userToken");
24
-
25
- if (persistedToken?.expiresAt) {
26
- const expiresAt = coalesceDateTime(persistedToken.expiresAt);
27
- if (expiresAt && expiresAt > DateTime.now()) {
28
- return { ...persistedToken, expiresAt };
29
- }
30
- }
31
-
32
- const response = await this.httpClient.post<XboxTicket>(
33
- "https://user.auth.xboxlive.com/user/authenticate",
34
- {
35
- RelyingParty: "http://auth.xboxlive.com",
36
- TokenType: "JWT",
37
- Properties: {
38
- AuthMethod: "RPS",
39
- SiteName: "user.auth.xboxlive.com",
40
- RpsTicket: `d=${accessToken}`,
41
- },
42
- },
43
- {
44
- headers: {
45
- "Content-Type": "application/json",
46
- Accept: "application/json",
47
- "x-xbl-contract-version": "1",
20
+ private userTokenCache = new ExpiryTokenCache(
21
+ async (accessToken: string) => {
22
+ const response = await this.httpClient.post<XboxTicket>(
23
+ "https://user.auth.xboxlive.com/user/authenticate",
24
+ {
25
+ RelyingParty: "http://auth.xboxlive.com",
26
+ TokenType: "JWT",
27
+ Properties: {
28
+ AuthMethod: "RPS",
29
+ SiteName: "user.auth.xboxlive.com",
30
+ RpsTicket: `d=${accessToken}`,
31
+ },
48
32
  },
49
- }
50
- );
33
+ {
34
+ headers: {
35
+ "Content-Type": "application/json",
36
+ Accept: "application/json",
37
+ "x-xbl-contract-version": "1",
38
+ },
39
+ }
40
+ );
51
41
 
52
- const result = {
53
- ...response.data,
54
- expiresAt: DateTime.fromISO(response.data.NotAfter),
55
- };
56
- await this.tokenPersister?.save("xbox.userToken", result);
57
- return result;
58
- });
42
+ const result = {
43
+ ...response.data,
44
+ expiresAt: DateTime.fromISO(response.data.NotAfter),
45
+ };
46
+ await this.tokenPersister?.save("xbox.userToken", result);
47
+ return result;
48
+ },
49
+ async () =>
50
+ (await this.tokenPersister?.load<XboxTicket & { expiresAt: unknown }>(
51
+ "xbox.userToken"
52
+ )) ?? null
53
+ );
59
54
  private xstsTicketCache = new ExpiryTokenCache(
60
55
  async (userToken: string, relyingParty: RelyingParty) => {
61
- const persistedToken = await this.tokenPersister?.load<
62
- XboxTicket & { expiresAt: DateTime }
63
- >("xbox.xstsTicket");
64
-
65
- if (persistedToken?.expiresAt) {
66
- const expiresAt = coalesceDateTime(persistedToken.expiresAt);
67
- if (expiresAt && expiresAt > DateTime.now()) {
68
- return { ...persistedToken, expiresAt };
69
- }
70
- }
71
-
72
56
  const response = await this.httpClient.post<XboxTicket>(
73
57
  "https://xsts.auth.xboxlive.com/xsts/authorize",
74
58
  {
@@ -94,7 +78,11 @@ export class XboxAuthenticationClient {
94
78
  };
95
79
  await this.tokenPersister?.save("xbox.xstsTicket", result);
96
80
  return result;
97
- }
81
+ },
82
+ async () =>
83
+ (await this.tokenPersister?.load<XboxTicket & { expiresAt: unknown }>(
84
+ "xbox.xstsTicket"
85
+ )) ?? null
98
86
  );
99
87
 
100
88
  private readonly httpClient: AxiosInstance;
@@ -103,13 +91,23 @@ export class XboxAuthenticationClient {
103
91
  this.httpClient = axios.create();
104
92
  }
105
93
 
106
- public async getUserToken(accessToken: string) {
107
- const { Token } = await this.userTokenCache.getToken(accessToken);
108
- return Token;
109
- }
110
-
111
- public getXstsTicket(userToken: string, relyingParty: RelyingParty) {
112
- return this.xstsTicketCache.getToken(userToken, relyingParty);
94
+ public async getXstsTicket(getOauth2AccessToken: () => Promise<string>) {
95
+ let xstsTicket = await this.xstsTicketCache.getExistingToken();
96
+ if (!xstsTicket) {
97
+ let userToken = await this.userTokenCache.getExistingToken();
98
+ if (!userToken) {
99
+ // Ouath2 token depends on nothing, so we can fetch it without
100
+ // worrying if it is expired.
101
+ userToken = await this.userTokenCache.getToken(
102
+ await getOauth2AccessToken()
103
+ );
104
+ }
105
+ xstsTicket = await this.xstsTicketCache.getToken(
106
+ userToken.Token,
107
+ RelyingParty.Halo
108
+ );
109
+ }
110
+ return xstsTicket;
113
111
  }
114
112
 
115
113
  public getXboxLiveV3Token = (userHash: string, userToken: string) =>
@@ -28,12 +28,8 @@ export class AutoXstsSpartanTokenProvider implements SpartanTokenProvider {
28
28
  const xboxAuthClient = new XboxAuthenticationClient(tokenPersister);
29
29
  const haloAuthClient = new HaloAuthenticationClient(
30
30
  async () => {
31
- const userToken = await xboxAuthClient.getUserToken(
32
- await getOauth2AccessToken()
33
- );
34
31
  const xstsTicket = await xboxAuthClient.getXstsTicket(
35
- userToken,
36
- RelyingParty.Halo
32
+ getOauth2AccessToken
37
33
  );
38
34
  return xstsTicket.Token;
39
35
  },
@@ -1,5 +1,6 @@
1
1
  import { DateTime } from "luxon";
2
2
  import { ResolvablePromise } from "./resolvable-promise";
3
+ import { coalesceDateTime } from "./date-time";
3
4
 
4
5
  export class ExpiryTokenCache<
5
6
  TToken extends { expiresAt: DateTime },
@@ -8,7 +9,10 @@ export class ExpiryTokenCache<
8
9
  private tokenFetchPromise: ResolvablePromise<TToken> | undefined = undefined;
9
10
 
10
11
  constructor(
11
- private readonly tokenFetcher: (...args: TArgs) => Promise<TToken>
12
+ private readonly generateNewToken: (...args: TArgs) => Promise<TToken>,
13
+ public readonly getExistingToken: () => Promise<
14
+ (Omit<TToken, "expiresAt"> & { expiresAt: unknown }) | null
15
+ >
12
16
  ) {}
13
17
 
14
18
  // TODO: Compare args and separate cache entries based on input
@@ -26,7 +30,7 @@ export class ExpiryTokenCache<
26
30
  this.tokenFetchPromise = new ResolvablePromise<TToken>();
27
31
 
28
32
  try {
29
- const newToken = await this.tokenFetcher(...args);
33
+ const newToken = await this.generateNewToken(...args);
30
34
  this.tokenFetchPromise.resolve(newToken);
31
35
  return newToken;
32
36
  } catch (e) {
@@ -40,7 +44,18 @@ export class ExpiryTokenCache<
40
44
  this.tokenFetchPromise = new ResolvablePromise<TToken>();
41
45
 
42
46
  try {
43
- const newToken = await this.tokenFetcher(...args);
47
+ const existingToken = await this.getExistingToken();
48
+
49
+ if (existingToken?.expiresAt) {
50
+ const expiresAt = coalesceDateTime(existingToken.expiresAt);
51
+ if (expiresAt && expiresAt > DateTime.now()) {
52
+ const newToken = { ...existingToken, expiresAt } as TToken;
53
+ this.tokenFetchPromise.resolve(newToken);
54
+ return newToken;
55
+ }
56
+ }
57
+
58
+ const newToken = await this.generateNewToken(...args);
44
59
  this.tokenFetchPromise.resolve(newToken);
45
60
  return newToken;
46
61
  } catch (e) {