halo-infinite-api 2.0.0 → 3.0.0

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.
package/README.md CHANGED
@@ -1,17 +1,21 @@
1
1
  # Halo Infinite API
2
2
 
3
- This is a simple typescript wrapper around 343's official Halo Infinite API (the same API that powers both the game and [halowaypoint.com](https://www.halowaypoint.com/)).I based it off the work of the now mysteriously deleted C# Grunt API (a defunct fork of which remains [here](https://github.com/seth-skocelas/grunt/)).
3
+ This is a simple typescript wrapper around 343's official Halo Infinite API (the same API that powers both the game and [halowaypoint.com](https://www.halowaypoint.com/)). I based it off the work of the now mysteriously deleted C# Grunt API (a defunct fork of which remains [here](https://github.com/seth-skocelas/grunt/)).
4
4
 
5
5
  The package is currently limited to the endpoints I've needed to use in other projects, however I do take requests (create an [issue](/issues)) and I welcome [PRs to extend the functionality](/pulls).
6
6
 
7
7
  ### Currently Supported Endpoints
8
8
 
9
9
  - GET https://profile.svc.halowaypoint.com/users/{gamerTag}
10
+ - GET https://profile.svc.halowaypoint.com/users?xuids={xuids}
10
11
  - GET https://skill.svc.halowaypoint.com/hi/playlist/{playlistId}/csrs?players={playerIds}
11
12
  - GET https://gamecms-hacs.svc.halowaypoint.com/hi/multiplayer/file/playlists/assets/{playlistId}.json
12
13
  - GET https://halostats.svc.halowaypoint.com/hi/playlist/{playlistId}/csrs?players={playerIds}
13
14
  - GET https://halostats.svc.halowaypoint.com/hi/players/xuid({playerId})/matches
14
15
  - GET https://skill.svc.halowaypoint.com/hi/matches/{matchId}/skill
16
+ - GET https://halostats.svc.halowaypoint.com/hi/matches/{matchId}/stats
17
+ - GET https://discovery-infiniteugc.svc.halowaypoint.com/hi/{assetType}/{assetId}
18
+ - GET https://discovery-infiniteugc.svc.halowaypoint.com/hi/{assetType}/{assetId}/versions/{versionId}
15
19
 
16
20
  ### Getting Started
17
21
 
@@ -19,46 +23,42 @@ The core requirement to use the endpoints in the library is to have a Spartan to
19
23
 
20
24
  > **⚠️ WARNING**
21
25
  >
22
- > The Spartan token is associated with _your identity_ and _your account_. **Do not share it** with anyone, under any circumstances. The API wrapper does not explicitly store it anywhere. It's your responsibility to make sure that it's secure and not available to anyone else.
26
+ > The Spartan token is associated with _your identity_ and _your account_. **Do not share it** with anyone, under any circumstances. The API wrapper does not explicitly store it anywhere (though you can configure it to store it somewhere of your choosing). It's your responsibility to make sure that it's secure and not available to anyone else.
23
27
 
24
- If you want to automatically generate the Spartan token, you can do so with the help of this package without having to worry about doing any of the REST API calls yourself. Before you get started, make sure that you [register an Azure Active Directory application](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app). You will need it in order to log in with your Microsoft account, that will be used to generate the token. Because this is just for you, you can use `https://localhost` as the redirect URI when you create the application, unless you're thinking of productizing whatever you're building.
28
+ This library does not provide a way to perform the first step of generating a spartan token, which is to get an Oauth2 access token from Microsoft. Microsoft provides a great set of npm packages for this purpose, [@azure/msal-node](https://www.npmjs.com/package/@azure/msal-node), [@azure/msal-browser](https://www.npmjs.com/package/@azure/msal-browser), [@azure/msal-react](https://www.npmjs.com/package/@azure/msal-react), etc. depending on the flavor of your application.
25
29
 
26
- Once you have the application registered, you can use the following code to automatically generate the token and call endpoints:
30
+ Make sure that you [register an Azure Active Directory application](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app), as that is how you will make use of the @azure/msal packages.
31
+
32
+ Below is a simple example of how this library might be used in a console application to get a user's profile:
27
33
 
28
34
  ```typescript
29
- import { HaloInfiniteClient } from "halo-infinite-api";
35
+ import open from "open"; // An npm package that opens a browser window
36
+ import * as msal from "@azure/msal-node";
37
+ import { HaloInfiniteClient, AutoXstsSpartanTokenProvider } from "halo-infinite-api";
38
+
39
+ const oauthApplication = new msal.PublicClientApplication({
40
+ auth: {
41
+ clientId: "42081d3d-4465-4c86-89ba-ea546f825335",
42
+ authority: "https://login.live.com", // Override the default authority with the xbox one
43
+ knownAuthorities: ["login.live.com"],
44
+ protocolMode: "OIDC", // Shit, I actually have no idea what this does, but Microsoft says I need it
45
+ },
46
+ });
30
47
 
31
48
  const client = new HaloInfiniteClient(
32
- "<YOUR_CLIENT_ID_FROM_AAD>",
33
- "<YOUR_REDIRECT_URI_FROM_AAD>",
34
- async (authorizeUrl: string) => {
35
- // A function that transforms the authorization URL into an authorization code.
36
- // The implementation will vary depending on your use case. This example is how
37
- // a firefox addon would accomplish the task
38
- const activeAuthUrl = await browser.identity.launchWebAuthFlow({
39
- url: authorizeUrl,
40
- interactive: true,
49
+ // Other choice for token providers is the StaticXstsTicketTokenSpartanTokenProvider,
50
+ // which uses a preset spartan token
51
+ new AutoXstsSpartanTokenProvider(async () => {
52
+ const token = await oauthApplication.acquireTokenInteractive({
53
+ // offline_access gives us a refresh token which we can use to continually
54
+ // get new credentials from Microsoft as the old ones expire.
55
+ scopes: ["Xboxlive.signin", "Xboxlive.offline_access"],
56
+ openBrowser: async (url) => {
57
+ await open(url);
58
+ },
41
59
  });
42
-
43
- return new URL(activeAuthUrl).searchParams.get("code")!;
44
- },
45
- // This final parameter is optional, but it allows your HaloInfiniteClient
46
- // to save tokens somewhere so that you don't need to refetch them every time
47
- // you restart your application.
48
- // Again, this is an example for a firefox addon using browser storage.
49
- {
50
- load: async (tokenName) => {
51
- const storageResponse = await browser.storage.local.get(tokenName);
52
- if (!storageResponse[tokenName]) {
53
- return null;
54
- } else {
55
- return storageResponse[tokenName];
56
- }
57
- },
58
- save: async (tokenName, token) => {
59
- await browser.storage.local.set({ [tokenName]: token });
60
- },
61
- }
60
+ return token.accessToken;
61
+ })
62
62
  );
63
63
 
64
64
  const user = await client.getUser("GravlLift");
@@ -1,5 +1,4 @@
1
1
  import { DateTime } from "luxon";
2
- import { XboxTicket } from "../models/xbox-ticket";
3
2
  import { TokenPersister } from "../core/token-persisters";
4
3
  export declare enum RelyingParty {
5
4
  Xbox = "http://xboxlive.com",
@@ -11,22 +10,28 @@ export interface XboxAuthenticationToken {
11
10
  refreshToken: string;
12
11
  }
13
12
  export declare class XboxAuthenticationClient {
14
- private readonly clientId;
15
- private readonly redirectUri;
16
- private readonly getAuthCode;
17
13
  private readonly tokenPersister?;
18
- private accessTokenPromise;
19
14
  private userTokenCache;
20
15
  private xstsTicketCache;
21
16
  private readonly httpClient;
22
- constructor(clientId: string, redirectUri: string, getAuthCode: (authorizeUrl: string) => Promise<string>, tokenPersister?: TokenPersister | undefined);
23
- private getPkce;
24
- getAccessToken(): Promise<string>;
25
- private fetchOauth2Token;
26
- private refreshOAuth2Token;
17
+ constructor(tokenPersister?: TokenPersister | undefined);
27
18
  getUserToken(accessToken: string): Promise<string>;
28
- getXstsTicket(userToken: string, relyingParty: RelyingParty): Promise<XboxTicket & {
19
+ getXstsTicket(userToken: string, relyingParty: RelyingParty): Promise<{
29
20
  expiresAt: DateTime;
21
+ IssueInstant: string;
22
+ NotAfter: string;
23
+ Token: string;
24
+ DisplayClaims: {
25
+ xui: [{
26
+ uhs: string;
27
+ gtg: string;
28
+ xid: string;
29
+ agg: string;
30
+ usr: string;
31
+ utr: string;
32
+ prv: string;
33
+ }];
34
+ };
30
35
  }>;
31
36
  getXboxLiveV3Token: (userHash: string, userToken: string) => string;
32
37
  }
@@ -1,25 +1,21 @@
1
1
  import axios from "axios";
2
- import pkceChallenge from "pkce-challenge";
3
2
  import { DateTime } from "luxon";
4
3
  import { coalesceDateTime } from "../util/date-time";
5
- import { ResolvablePromise } from "../util/resolvable-promise";
6
4
  import { ExpiryTokenCache } from "../util/expiry-token-cache";
7
- const SCOPES = ["Xboxlive.signin", "Xboxlive.offline_access"];
8
5
  export var RelyingParty;
9
6
  (function (RelyingParty) {
10
7
  RelyingParty["Xbox"] = "http://xboxlive.com";
11
8
  RelyingParty["Halo"] = "https://prod.xsts.halowaypoint.com/";
12
9
  })(RelyingParty || (RelyingParty = {}));
13
10
  export class XboxAuthenticationClient {
14
- clientId;
15
- redirectUri;
16
- getAuthCode;
17
11
  tokenPersister;
18
- accessTokenPromise = undefined;
19
12
  userTokenCache = new ExpiryTokenCache(async (accessToken) => {
20
13
  const persistedToken = await this.tokenPersister?.load("xbox.userToken");
21
- if (persistedToken && persistedToken.expiresAt > DateTime.now()) {
22
- return persistedToken;
14
+ if (persistedToken?.expiresAt) {
15
+ const expiresAt = coalesceDateTime(persistedToken.expiresAt);
16
+ if (expiresAt && expiresAt > DateTime.now()) {
17
+ return { ...persistedToken, expiresAt };
18
+ }
23
19
  }
24
20
  const response = await this.httpClient.post("https://user.auth.xboxlive.com/user/authenticate", {
25
21
  RelyingParty: "http://auth.xboxlive.com",
@@ -45,8 +41,11 @@ export class XboxAuthenticationClient {
45
41
  });
46
42
  xstsTicketCache = new ExpiryTokenCache(async (userToken, relyingParty) => {
47
43
  const persistedToken = await this.tokenPersister?.load("xbox.xstsTicket");
48
- if (persistedToken && persistedToken.expiresAt > DateTime.now()) {
49
- return persistedToken;
44
+ if (persistedToken?.expiresAt) {
45
+ const expiresAt = coalesceDateTime(persistedToken.expiresAt);
46
+ if (expiresAt && expiresAt > DateTime.now()) {
47
+ return { ...persistedToken, expiresAt };
48
+ }
50
49
  }
51
50
  const response = await this.httpClient.post("https://xsts.auth.xboxlive.com/xsts/authorize", {
52
51
  RelyingParty: relyingParty,
@@ -70,116 +69,10 @@ export class XboxAuthenticationClient {
70
69
  return result;
71
70
  });
72
71
  httpClient;
73
- constructor(clientId, redirectUri, getAuthCode, tokenPersister) {
74
- this.clientId = clientId;
75
- this.redirectUri = redirectUri;
76
- this.getAuthCode = getAuthCode;
72
+ constructor(tokenPersister) {
77
73
  this.tokenPersister = tokenPersister;
78
74
  this.httpClient = axios.create();
79
75
  }
80
- getPkce() {
81
- return pkceChallenge(43);
82
- }
83
- async getAccessToken() {
84
- if (this.accessTokenPromise) {
85
- // Someone either already has a token or is in the process of getting one
86
- // Wait for them to finish, then check for validity
87
- const currentToken = await this.accessTokenPromise;
88
- if (currentToken.expiresAt > DateTime.now()) {
89
- // Current token is valid, return it
90
- return currentToken.token;
91
- }
92
- else {
93
- // Current token expired, start a new promise
94
- this.accessTokenPromise =
95
- new ResolvablePromise();
96
- try {
97
- const newToken = await this.refreshOAuth2Token(currentToken.refreshToken);
98
- this.accessTokenPromise.resolve(newToken);
99
- await this.tokenPersister?.save("xbox.accessToken", newToken);
100
- return newToken.token;
101
- }
102
- catch (e) {
103
- this.accessTokenPromise.reject(e);
104
- throw e;
105
- }
106
- }
107
- }
108
- else {
109
- // We are the first caller, create a promise to block subsequent callers
110
- this.accessTokenPromise =
111
- new ResolvablePromise();
112
- try {
113
- const loadedToken = await this.tokenPersister?.load("xbox.accessToken");
114
- const currentToken = {
115
- ...loadedToken,
116
- token: loadedToken?.token ?? "",
117
- expiresAt: coalesceDateTime(loadedToken?.expiresAt),
118
- };
119
- if (currentToken.expiresAt && currentToken.expiresAt > DateTime.now()) {
120
- // Current token is valid, return it and alert other callers if applicable
121
- this.accessTokenPromise.resolve(currentToken);
122
- return currentToken.token;
123
- }
124
- else {
125
- const newToken = await this.fetchOauth2Token();
126
- this.accessTokenPromise.resolve(newToken);
127
- await this.tokenPersister?.save("xbox.accessToken", newToken);
128
- return newToken.token;
129
- }
130
- }
131
- catch (e) {
132
- this.accessTokenPromise.reject(e);
133
- throw e;
134
- }
135
- }
136
- }
137
- async fetchOauth2Token() {
138
- const { code_verifier, code_challenge } = this.getPkce();
139
- const authorizeUrl = `https://login.live.com/oauth20_authorize.srf?${new URLSearchParams({
140
- client_id: this.clientId,
141
- response_type: "code",
142
- redirect_uri: this.redirectUri,
143
- scope: SCOPES.join(" "),
144
- code_challenge_method: "S256",
145
- code_challenge,
146
- })}`;
147
- const code = await this.getAuthCode(authorizeUrl);
148
- const requestStart = DateTime.now();
149
- const response = await this.httpClient.post("https://login.live.com/oauth20_token.srf", new URLSearchParams({
150
- grant_type: "authorization_code",
151
- code,
152
- approval_prompt: "auto",
153
- scope: SCOPES.join(" "),
154
- redirect_uri: this.redirectUri,
155
- client_id: this.clientId,
156
- code_verifier,
157
- }), {
158
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
159
- });
160
- return {
161
- token: response.data.access_token,
162
- expiresAt: requestStart.plus({ seconds: response.data.expires_in }),
163
- refreshToken: response.data.refresh_token,
164
- };
165
- }
166
- async refreshOAuth2Token(refreshToken) {
167
- const response = await this.httpClient.post("https://login.live.com/oauth20_token.srf", new URLSearchParams({
168
- grant_type: "refresh_token",
169
- refresh_token: refreshToken,
170
- scope: SCOPES.join(" "),
171
- redirect_uri: this.redirectUri,
172
- client_id: this.clientId,
173
- }), {
174
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
175
- });
176
- const responseDate = DateTime.fromRFC2822(response.headers["date"]);
177
- return {
178
- token: response.data.access_token,
179
- expiresAt: responseDate.plus({ seconds: response.data.expires_in }),
180
- refreshToken: response.data.refresh_token,
181
- };
182
- }
183
76
  async getUserToken(accessToken) {
184
77
  const { Token } = await this.userTokenCache.getToken(accessToken);
185
78
  return Token;
@@ -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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"}
@@ -34,6 +34,10 @@ export declare class HaloInfiniteClient {
34
34
  * @param gamerTag - Gamertag to lookup.
35
35
  */
36
36
  getUser: (gamerTag: string) => Promise<UserInfo>;
37
+ /** Get gamertag info for several players.
38
+ * @param xuids - Xuids to lookup.
39
+ */
40
+ getUsers: (xuids: string[]) => Promise<UserInfo[]>;
37
41
  /** Get service record for a player.
38
42
  * @param gamerTag - Gamertag to lookup.
39
43
  */
@@ -17,6 +17,15 @@ function wrapPlayerId(playerId) {
17
17
  return `xuid(${playerId})`;
18
18
  }
19
19
  }
20
+ function unwrapPlayerId(playerId) {
21
+ const match = /^\w+\((\d+)\)$/.exec(playerId);
22
+ if (match) {
23
+ return match[1];
24
+ }
25
+ else {
26
+ return playerId;
27
+ }
28
+ }
20
29
  export class HaloInfiniteClient {
21
30
  spartanTokenProvider;
22
31
  constructor(spartanTokenProvider) {
@@ -64,6 +73,12 @@ export class HaloInfiniteClient {
64
73
  * @param gamerTag - Gamertag to lookup.
65
74
  */
66
75
  getUser = (gamerTag) => this.executeRequest(`https://${HaloCoreEndpoints.Profile}.${HaloCoreEndpoints.ServiceDomain}/users/gt(${gamerTag})`, "get");
76
+ /** Get gamertag info for several players.
77
+ * @param xuids - Xuids to lookup.
78
+ */
79
+ getUsers = (xuids) => {
80
+ return this.executeRequest(`https://${HaloCoreEndpoints.Profile}.${HaloCoreEndpoints.ServiceDomain}/users?xuids=${xuids.map((x) => unwrapPlayerId(x)).join(",")}`, "get");
81
+ };
67
82
  /** Get service record for a player.
68
83
  * @param gamerTag - Gamertag to lookup.
69
84
  */
@@ -1 +1 @@
1
- {"version":3,"file":"halo-infinite-client.js","sourceRoot":"","sources":["../../src/core/halo-infinite-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"halo-infinite-client.js","sourceRoot":"","sources":["../../src/core/halo-infinite-client.ts"],"names":[],"mappings":"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"}
@@ -7,5 +7,5 @@ import { SpartanTokenProvider } from ".";
7
7
  */
8
8
  export declare class AutoXstsSpartanTokenProvider implements SpartanTokenProvider {
9
9
  readonly getSpartanToken: () => Promise<string>;
10
- constructor(clientId: string, redirectUri: string, getAuthCode: (authorizeUrl: string) => Promise<string>, tokenPersister?: TokenPersister);
10
+ constructor(getOauth2AccessToken: () => Promise<string>, tokenPersister?: TokenPersister);
11
11
  }
@@ -1,5 +1,6 @@
1
1
  import { RelyingParty, XboxAuthenticationClient, } from "../../authentication/xbox-authentication-client";
2
2
  import { HaloAuthenticationClient } from "../../authentication/halo-authentication-client";
3
+ import { inMemoryTokenPersister } from "../token-persisters/in-memory-token-persister";
3
4
  /**
4
5
  * A SpartanTokenProvider that fetches both the Xbox and Halo tokens in the same
5
6
  * process. This is useful for applications that do not need to contend with
@@ -7,24 +8,21 @@ import { HaloAuthenticationClient } from "../../authentication/halo-authenticati
7
8
  */
8
9
  export class AutoXstsSpartanTokenProvider {
9
10
  getSpartanToken;
10
- constructor(clientId, redirectUri, getAuthCode, tokenPersister) {
11
- const xboxAuthClient = new XboxAuthenticationClient(clientId, redirectUri, getAuthCode, tokenPersister);
11
+ constructor(getOauth2AccessToken, tokenPersister) {
12
+ let actualTokenPersister;
13
+ if (tokenPersister) {
14
+ actualTokenPersister = tokenPersister;
15
+ }
16
+ else {
17
+ actualTokenPersister = inMemoryTokenPersister;
18
+ }
19
+ const xboxAuthClient = new XboxAuthenticationClient(tokenPersister);
12
20
  const haloAuthClient = new HaloAuthenticationClient(async () => {
13
- const accessToken = await xboxAuthClient.getAccessToken();
14
- const userToken = await xboxAuthClient.getUserToken(accessToken);
21
+ const userToken = await xboxAuthClient.getUserToken(await getOauth2AccessToken());
15
22
  const xstsTicket = await xboxAuthClient.getXstsTicket(userToken, RelyingParty.Halo);
16
23
  return xstsTicket.Token;
17
- }, async () => {
18
- if (tokenPersister) {
19
- return await tokenPersister.load("halo.authToken");
20
- }
21
- else {
22
- return null;
23
- }
24
- }, async (token) => {
25
- if (tokenPersister) {
26
- await tokenPersister.save("halo.authToken", token);
27
- }
24
+ }, async () => await actualTokenPersister.load("halo.authToken"), async (token) => {
25
+ await actualTokenPersister.save("halo.authToken", token);
28
26
  });
29
27
  this.getSpartanToken = () => haloAuthClient.getSpartanToken();
30
28
  }
@@ -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;AAG3F;;;;GAIG;AACH,MAAM,OAAO,4BAA4B;IACvB,eAAe,CAAwB;IAEvD,YACE,QAAgB,EAChB,WAAmB,EACnB,WAAsD,EACtD,cAA+B;QAE/B,MAAM,cAAc,GAAG,IAAI,wBAAwB,CACjD,QAAQ,EACR,WAAW,EACX,WAAW,EACX,cAAc,CACf,CAAC;QACF,MAAM,cAAc,GAAG,IAAI,wBAAwB,CACjD,KAAK,IAAI,EAAE;YACT,MAAM,WAAW,GAAG,MAAM,cAAc,CAAC,cAAc,EAAE,CAAC;YAC1D,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;YACjE,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;YACT,IAAI,cAAc,EAAE;gBAClB,OAAO,MAAM,cAAc,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;aACpD;iBAAM;gBACL,OAAO,IAAI,CAAC;aACb;QACH,CAAC,EACD,KAAK,EAAE,KAAK,EAAE,EAAE;YACd,IAAI,cAAc,EAAE;gBAClB,MAAM,cAAc,CAAC,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;aACpD;QACH,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,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,4 +1,5 @@
1
1
  import { HaloAuthenticationClient } from "../../authentication/halo-authentication-client";
2
+ import { inMemoryTokenPersister } from "../token-persisters/in-memory-token-persister";
2
3
  /**
3
4
  * A SpartanTokenProvider that fetches uses a pre-fetched XSTS ticket token.
4
5
  * Since requests to the Halo API are subject to CORS restrictions a
@@ -8,17 +9,15 @@ import { HaloAuthenticationClient } from "../../authentication/halo-authenticati
8
9
  export class StaticXstsTicketTokenSpartanTokenProvider {
9
10
  getSpartanToken;
10
11
  constructor(xstsTicketToken, tokenPersister) {
11
- const haloAuthClient = new HaloAuthenticationClient(() => xstsTicketToken, async () => {
12
- if (tokenPersister) {
13
- return await tokenPersister.load("halo.authToken");
14
- }
15
- else {
16
- return null;
17
- }
18
- }, async (token) => {
19
- if (tokenPersister) {
20
- await tokenPersister.save("halo.authToken", token);
21
- }
12
+ let actualTokenPersister;
13
+ if (tokenPersister) {
14
+ actualTokenPersister = tokenPersister;
15
+ }
16
+ else {
17
+ actualTokenPersister = inMemoryTokenPersister;
18
+ }
19
+ const haloAuthClient = new HaloAuthenticationClient(() => xstsTicketToken, async () => await actualTokenPersister.load("halo.authToken"), async (token) => {
20
+ await actualTokenPersister.save("halo.authToken", token);
22
21
  });
23
22
  this.getSpartanToken = () => haloAuthClient.getSpartanToken();
24
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"static-xsts-ticket-token-spartan-token-provider.js","sourceRoot":"","sources":["../../../src/core/spartan-token-providers/static-xsts-ticket-token-spartan-token-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,iDAAiD,CAAC;AAG3F;;;;;GAKG;AAEH,MAAM,OAAO,yCAAyC;IAGpC,eAAe,CAAwB;IAEvD,YAAY,eAAuB,EAAE,cAA+B;QAClE,MAAM,cAAc,GAAG,IAAI,wBAAwB,CACjD,GAAG,EAAE,CAAC,eAAe,EACrB,KAAK,IAAI,EAAE;YACT,IAAI,cAAc,EAAE;gBAClB,OAAO,MAAM,cAAc,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;aACpD;iBAAM;gBACL,OAAO,IAAI,CAAC;aACb;QACH,CAAC,EACD,KAAK,EAAE,KAAK,EAAE,EAAE;YACd,IAAI,cAAc,EAAE;gBAClB,MAAM,cAAc,CAAC,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;aACpD;QACH,CAAC,CACF,CAAC;QAEF,IAAI,CAAC,eAAe,GAAG,GAAG,EAAE,CAAC,cAAc,CAAC,eAAe,EAAE,CAAC;IAChE,CAAC;CACF"}
1
+ {"version":3,"file":"static-xsts-ticket-token-spartan-token-provider.js","sourceRoot":"","sources":["../../../src/core/spartan-token-providers/static-xsts-ticket-token-spartan-token-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,iDAAiD,CAAC;AAE3F,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AAEvF;;;;;GAKG;AACH,MAAM,OAAO,yCAAyC;IAGpC,eAAe,CAAwB;IAEvD,YAAY,eAAuB,EAAE,cAA+B;QAClE,IAAI,oBAAoC,CAAC;QACzC,IAAI,cAAc,EAAE;YAClB,oBAAoB,GAAG,cAAc,CAAC;SACvC;aAAM;YACL,oBAAoB,GAAG,sBAAsB,CAAC;SAC/C;QAED,MAAM,cAAc,GAAG,IAAI,wBAAwB,CACjD,GAAG,EAAE,CAAC,eAAe,EACrB,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"}
@@ -0,0 +1,2 @@
1
+ import { TokenPersister } from ".";
2
+ export declare const inMemoryTokenPersister: TokenPersister;
@@ -0,0 +1,10 @@
1
+ const tokens = new Map();
2
+ export const inMemoryTokenPersister = {
3
+ load: (tokenName) => {
4
+ return tokens.get(tokenName);
5
+ },
6
+ save: (tokenName, token) => {
7
+ tokens.set(tokenName, token);
8
+ },
9
+ };
10
+ //# sourceMappingURL=in-memory-token-persister.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-token-persister.js","sourceRoot":"","sources":["../../../src/core/token-persisters/in-memory-token-persister.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,IAAI,GAAG,EAAe,CAAC;AAEtC,MAAM,CAAC,MAAM,sBAAsB,GAAmB;IACpD,IAAI,EAAE,CAAC,SAAS,EAAE,EAAE;QAClB,OAAO,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC;IACD,IAAI,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE;QACzB,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;CACF,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "halo-infinite-api",
3
3
  "type": "module",
4
- "version": "2.0.0",
4
+ "version": "3.0.0",
5
5
  "description": "An NPM package for accessing the official Halo Infinite API.",
6
6
  "main": "dist/index.js",
7
7
  "scripts": {
@@ -33,6 +33,7 @@
33
33
  "yargs": "^17.7.1"
34
34
  },
35
35
  "dependencies": {
36
+ "@azure/msal-node": "^2.2.0",
36
37
  "axios": "^1.3.5",
37
38
  "expiry-map": "^2.0.0",
38
39
  "luxon": "^3.3.0",
@@ -1,13 +1,9 @@
1
1
  import axios, { AxiosInstance } from "axios";
2
- import pkceChallenge from "pkce-challenge";
3
2
  import { DateTime } from "luxon";
3
+ import { TokenPersister } from "../core/token-persisters";
4
4
  import { XboxTicket } from "../models/xbox-ticket";
5
5
  import { coalesceDateTime } from "../util/date-time";
6
- import { ResolvablePromise } from "../util/resolvable-promise";
7
6
  import { ExpiryTokenCache } from "../util/expiry-token-cache";
8
- import { TokenPersister } from "../core/token-persisters";
9
-
10
- const SCOPES = ["Xboxlive.signin", "Xboxlive.offline_access"];
11
7
 
12
8
  export enum RelyingParty {
13
9
  Xbox = "http://xboxlive.com",
@@ -21,16 +17,16 @@ export interface XboxAuthenticationToken {
21
17
  }
22
18
 
23
19
  export class XboxAuthenticationClient {
24
- private accessTokenPromise:
25
- | ResolvablePromise<XboxAuthenticationToken>
26
- | undefined = undefined;
27
20
  private userTokenCache = new ExpiryTokenCache(async (accessToken: string) => {
28
21
  const persistedToken = await this.tokenPersister?.load<
29
- XboxTicket & { expiresAt: DateTime }
22
+ XboxTicket & { expiresAt?: unknown }
30
23
  >("xbox.userToken");
31
24
 
32
- if (persistedToken && persistedToken.expiresAt > DateTime.now()) {
33
- return persistedToken;
25
+ if (persistedToken?.expiresAt) {
26
+ const expiresAt = coalesceDateTime(persistedToken.expiresAt);
27
+ if (expiresAt && expiresAt > DateTime.now()) {
28
+ return { ...persistedToken, expiresAt };
29
+ }
34
30
  }
35
31
 
36
32
  const response = await this.httpClient.post<XboxTicket>(
@@ -66,8 +62,11 @@ export class XboxAuthenticationClient {
66
62
  XboxTicket & { expiresAt: DateTime }
67
63
  >("xbox.xstsTicket");
68
64
 
69
- if (persistedToken && persistedToken.expiresAt > DateTime.now()) {
70
- return persistedToken;
65
+ if (persistedToken?.expiresAt) {
66
+ const expiresAt = coalesceDateTime(persistedToken.expiresAt);
67
+ if (expiresAt && expiresAt > DateTime.now()) {
68
+ return { ...persistedToken, expiresAt };
69
+ }
71
70
  }
72
71
 
73
72
  const response = await this.httpClient.post<XboxTicket>(
@@ -100,159 +99,10 @@ export class XboxAuthenticationClient {
100
99
 
101
100
  private readonly httpClient: AxiosInstance;
102
101
 
103
- constructor(
104
- private readonly clientId: string,
105
- private readonly redirectUri: string,
106
- private readonly getAuthCode: (authorizeUrl: string) => Promise<string>,
107
- private readonly tokenPersister?: TokenPersister
108
- ) {
102
+ constructor(private readonly tokenPersister?: TokenPersister) {
109
103
  this.httpClient = axios.create();
110
104
  }
111
105
 
112
- private getPkce() {
113
- return pkceChallenge(43);
114
- }
115
-
116
- public async getAccessToken() {
117
- if (this.accessTokenPromise) {
118
- // Someone either already has a token or is in the process of getting one
119
- // Wait for them to finish, then check for validity
120
- const currentToken = await this.accessTokenPromise;
121
-
122
- if (currentToken.expiresAt > DateTime.now()) {
123
- // Current token is valid, return it
124
- return currentToken.token;
125
- } else {
126
- // Current token expired, start a new promise
127
- this.accessTokenPromise =
128
- new ResolvablePromise<XboxAuthenticationToken>();
129
-
130
- try {
131
- const newToken = await this.refreshOAuth2Token(
132
- currentToken.refreshToken
133
- );
134
- this.accessTokenPromise.resolve(newToken);
135
- await this.tokenPersister?.save("xbox.accessToken", newToken);
136
- return newToken.token;
137
- } catch (e) {
138
- this.accessTokenPromise.reject(e);
139
- throw e;
140
- }
141
- }
142
- } else {
143
- // We are the first caller, create a promise to block subsequent callers
144
- this.accessTokenPromise =
145
- new ResolvablePromise<XboxAuthenticationToken>();
146
-
147
- try {
148
- const loadedToken =
149
- await this.tokenPersister?.load<XboxAuthenticationToken>(
150
- "xbox.accessToken"
151
- );
152
- const currentToken = {
153
- ...loadedToken,
154
- token: loadedToken?.token ?? "",
155
- expiresAt: coalesceDateTime(loadedToken?.expiresAt),
156
- };
157
-
158
- if (currentToken.expiresAt && currentToken.expiresAt > DateTime.now()) {
159
- // Current token is valid, return it and alert other callers if applicable
160
- this.accessTokenPromise.resolve(
161
- currentToken as XboxAuthenticationToken
162
- );
163
- return currentToken.token;
164
- } else {
165
- const newToken = await this.fetchOauth2Token();
166
- this.accessTokenPromise.resolve(newToken);
167
- await this.tokenPersister?.save("xbox.accessToken", newToken);
168
- return newToken.token;
169
- }
170
- } catch (e) {
171
- this.accessTokenPromise.reject(e);
172
- throw e;
173
- }
174
- }
175
- }
176
-
177
- private async fetchOauth2Token(): Promise<XboxAuthenticationToken> {
178
- const { code_verifier, code_challenge } = this.getPkce();
179
-
180
- const authorizeUrl = `https://login.live.com/oauth20_authorize.srf?${new URLSearchParams(
181
- {
182
- client_id: this.clientId,
183
- response_type: "code",
184
- redirect_uri: this.redirectUri,
185
- scope: SCOPES.join(" "),
186
- code_challenge_method: "S256",
187
- code_challenge,
188
- }
189
- )}`;
190
-
191
- const code = await this.getAuthCode(authorizeUrl);
192
-
193
- const requestStart = DateTime.now();
194
- const response = await this.httpClient.post<{
195
- access_token: string;
196
- expires_in: number;
197
- scope: string;
198
- token_type: string;
199
- user_id: string;
200
- refresh_token: string;
201
- }>(
202
- "https://login.live.com/oauth20_token.srf",
203
- new URLSearchParams({
204
- grant_type: "authorization_code",
205
- code,
206
- approval_prompt: "auto",
207
- scope: SCOPES.join(" "),
208
- redirect_uri: this.redirectUri,
209
- client_id: this.clientId,
210
- code_verifier,
211
- }),
212
- {
213
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
214
- }
215
- );
216
-
217
- return {
218
- token: response.data.access_token,
219
- expiresAt: requestStart.plus({ seconds: response.data.expires_in }),
220
- refreshToken: response.data.refresh_token,
221
- };
222
- }
223
-
224
- private async refreshOAuth2Token(
225
- refreshToken: string
226
- ): Promise<XboxAuthenticationToken> {
227
- const response = await this.httpClient.post<{
228
- access_token: string;
229
- expires_in: number;
230
- scope: string;
231
- token_type: string;
232
- user_id: string;
233
- refresh_token: string;
234
- }>(
235
- "https://login.live.com/oauth20_token.srf",
236
- new URLSearchParams({
237
- grant_type: "refresh_token",
238
- refresh_token: refreshToken,
239
- scope: SCOPES.join(" "),
240
- redirect_uri: this.redirectUri,
241
- client_id: this.clientId,
242
- }),
243
- {
244
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
245
- }
246
- );
247
-
248
- const responseDate = DateTime.fromRFC2822(response.headers["date"]);
249
- return {
250
- token: response.data.access_token,
251
- expiresAt: responseDate.plus({ seconds: response.data.expires_in }),
252
- refreshToken: response.data.refresh_token,
253
- };
254
- }
255
-
256
106
  public async getUserToken(accessToken: string) {
257
107
  const { Token } = await this.userTokenCache.getToken(accessToken);
258
108
  return Token;
@@ -57,6 +57,15 @@ function wrapPlayerId(playerId: string) {
57
57
  }
58
58
  }
59
59
 
60
+ function unwrapPlayerId(playerId: string) {
61
+ const match = /^\w+\((\d+)\)$/.exec(playerId);
62
+ if (match) {
63
+ return match[1];
64
+ } else {
65
+ return playerId;
66
+ }
67
+ }
68
+
60
69
  export class HaloInfiniteClient {
61
70
  constructor(private spartanTokenProvider: SpartanTokenProvider) {}
62
71
 
@@ -142,6 +151,18 @@ export class HaloInfiniteClient {
142
151
  "get"
143
152
  );
144
153
 
154
+ /** Get gamertag info for several players.
155
+ * @param xuids - Xuids to lookup.
156
+ */
157
+ public getUsers = (xuids: string[]) => {
158
+ return this.executeRequest<UserInfo[]>(
159
+ `https://${HaloCoreEndpoints.Profile}.${
160
+ HaloCoreEndpoints.ServiceDomain
161
+ }/users?xuids=${xuids.map((x) => unwrapPlayerId(x)).join(",")}`,
162
+ "get"
163
+ );
164
+ };
165
+
145
166
  /** Get service record for a player.
146
167
  * @param gamerTag - Gamertag to lookup.
147
168
  */
@@ -5,6 +5,7 @@ import {
5
5
  import { TokenPersister } from "../token-persisters";
6
6
  import { HaloAuthenticationClient } from "../../authentication/halo-authentication-client";
7
7
  import { SpartanTokenProvider } from ".";
8
+ import { inMemoryTokenPersister } from "../token-persisters/in-memory-token-persister";
8
9
 
9
10
  /**
10
11
  * A SpartanTokenProvider that fetches both the Xbox and Halo tokens in the same
@@ -15,38 +16,30 @@ export class AutoXstsSpartanTokenProvider implements SpartanTokenProvider {
15
16
  public readonly getSpartanToken: () => Promise<string>;
16
17
 
17
18
  constructor(
18
- clientId: string,
19
- redirectUri: string,
20
- getAuthCode: (authorizeUrl: string) => Promise<string>,
19
+ getOauth2AccessToken: () => Promise<string>,
21
20
  tokenPersister?: TokenPersister
22
21
  ) {
23
- const xboxAuthClient = new XboxAuthenticationClient(
24
- clientId,
25
- redirectUri,
26
- getAuthCode,
27
- tokenPersister
28
- );
22
+ let actualTokenPersister: TokenPersister;
23
+ if (tokenPersister) {
24
+ actualTokenPersister = tokenPersister;
25
+ } else {
26
+ actualTokenPersister = inMemoryTokenPersister;
27
+ }
28
+ const xboxAuthClient = new XboxAuthenticationClient(tokenPersister);
29
29
  const haloAuthClient = new HaloAuthenticationClient(
30
30
  async () => {
31
- const accessToken = await xboxAuthClient.getAccessToken();
32
- const userToken = await xboxAuthClient.getUserToken(accessToken);
31
+ const userToken = await xboxAuthClient.getUserToken(
32
+ await getOauth2AccessToken()
33
+ );
33
34
  const xstsTicket = await xboxAuthClient.getXstsTicket(
34
35
  userToken,
35
36
  RelyingParty.Halo
36
37
  );
37
38
  return xstsTicket.Token;
38
39
  },
39
- async () => {
40
- if (tokenPersister) {
41
- return await tokenPersister.load("halo.authToken");
42
- } else {
43
- return null;
44
- }
45
- },
40
+ async () => await actualTokenPersister.load("halo.authToken"),
46
41
  async (token) => {
47
- if (tokenPersister) {
48
- await tokenPersister.save("halo.authToken", token);
49
- }
42
+ await actualTokenPersister.save("halo.authToken", token);
50
43
  }
51
44
  );
52
45
 
@@ -1,6 +1,7 @@
1
1
  import { TokenPersister } from "../token-persisters";
2
2
  import { HaloAuthenticationClient } from "../../authentication/halo-authentication-client";
3
3
  import { SpartanTokenProvider } from ".";
4
+ import { inMemoryTokenPersister } from "../token-persisters/in-memory-token-persister";
4
5
 
5
6
  /**
6
7
  * A SpartanTokenProvider that fetches uses a pre-fetched XSTS ticket token.
@@ -8,26 +9,24 @@ import { SpartanTokenProvider } from ".";
8
9
  * HaloAuthenticationClient can be instantitated with a pre-fetched XSTS ticket
9
10
  * and run on a server (such as one provided by the user).
10
11
  */
11
-
12
12
  export class StaticXstsTicketTokenSpartanTokenProvider
13
13
  implements SpartanTokenProvider
14
14
  {
15
15
  public readonly getSpartanToken: () => Promise<string>;
16
16
 
17
17
  constructor(xstsTicketToken: string, tokenPersister?: TokenPersister) {
18
+ let actualTokenPersister: TokenPersister;
19
+ if (tokenPersister) {
20
+ actualTokenPersister = tokenPersister;
21
+ } else {
22
+ actualTokenPersister = inMemoryTokenPersister;
23
+ }
24
+
18
25
  const haloAuthClient = new HaloAuthenticationClient(
19
26
  () => xstsTicketToken,
20
- async () => {
21
- if (tokenPersister) {
22
- return await tokenPersister.load("halo.authToken");
23
- } else {
24
- return null;
25
- }
26
- },
27
+ async () => await actualTokenPersister.load("halo.authToken"),
27
28
  async (token) => {
28
- if (tokenPersister) {
29
- await tokenPersister.save("halo.authToken", token);
30
- }
29
+ await actualTokenPersister.save("halo.authToken", token);
31
30
  }
32
31
  );
33
32
 
@@ -0,0 +1,12 @@
1
+ import { TokenPersister } from ".";
2
+
3
+ const tokens = new Map<string, any>();
4
+
5
+ export const inMemoryTokenPersister: TokenPersister = {
6
+ load: (tokenName) => {
7
+ return tokens.get(tokenName);
8
+ },
9
+ save: (tokenName, token) => {
10
+ tokens.set(tokenName, token);
11
+ },
12
+ };