halo-infinite-api 2.1.0 → 3.1.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 +34 -34
- package/dist/authentication/xbox-authentication-client.d.ts +1 -9
- package/dist/authentication/xbox-authentication-client.js +1 -119
- package/dist/authentication/xbox-authentication-client.js.map +1 -1
- package/dist/core/spartan-token-providers/auto-xsts-spartan-token-provider.d.ts +1 -1
- package/dist/core/spartan-token-providers/auto-xsts-spartan-token-provider.js +3 -4
- package/dist/core/spartan-token-providers/auto-xsts-spartan-token-provider.js.map +1 -1
- package/dist/models/halo-infinite/game-variant-category.d.ts +2 -1
- package/dist/models/halo-infinite/game-variant-category.js +1 -0
- package/dist/models/halo-infinite/game-variant-category.js.map +1 -1
- package/package.json +2 -1
- package/src/authentication/xbox-authentication-client.ts +2 -163
- package/src/core/spartan-token-providers/auto-xsts-spartan-token-provider.ts +5 -11
- package/src/models/halo-infinite/game-variant-category.ts +1 -0
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
async (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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");
|
|
@@ -10,19 +10,11 @@ export interface XboxAuthenticationToken {
|
|
|
10
10
|
refreshToken: string;
|
|
11
11
|
}
|
|
12
12
|
export declare class XboxAuthenticationClient {
|
|
13
|
-
private readonly clientId;
|
|
14
|
-
private readonly redirectUri;
|
|
15
|
-
private readonly getAuthCode;
|
|
16
13
|
private readonly tokenPersister?;
|
|
17
|
-
private accessTokenPromise;
|
|
18
14
|
private userTokenCache;
|
|
19
15
|
private xstsTicketCache;
|
|
20
16
|
private readonly httpClient;
|
|
21
|
-
constructor(
|
|
22
|
-
private getPkce;
|
|
23
|
-
getAccessToken(): Promise<string>;
|
|
24
|
-
private fetchOauth2Token;
|
|
25
|
-
private refreshOAuth2Token;
|
|
17
|
+
constructor(tokenPersister?: TokenPersister | undefined);
|
|
26
18
|
getUserToken(accessToken: string): Promise<string>;
|
|
27
19
|
getXstsTicket(userToken: string, relyingParty: RelyingParty): Promise<{
|
|
28
20
|
expiresAt: DateTime;
|
|
@@ -1,21 +1,14 @@
|
|
|
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
14
|
if (persistedToken?.expiresAt) {
|
|
@@ -76,121 +69,10 @@ export class XboxAuthenticationClient {
|
|
|
76
69
|
return result;
|
|
77
70
|
});
|
|
78
71
|
httpClient;
|
|
79
|
-
constructor(
|
|
80
|
-
this.clientId = clientId;
|
|
81
|
-
this.redirectUri = redirectUri;
|
|
82
|
-
this.getAuthCode = getAuthCode;
|
|
72
|
+
constructor(tokenPersister) {
|
|
83
73
|
this.tokenPersister = tokenPersister;
|
|
84
74
|
this.httpClient = axios.create();
|
|
85
75
|
}
|
|
86
|
-
getPkce() {
|
|
87
|
-
if (typeof pkceChallenge === "function") {
|
|
88
|
-
return pkceChallenge(43);
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
return pkceChallenge.default(43);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
async getAccessToken() {
|
|
95
|
-
if (this.accessTokenPromise) {
|
|
96
|
-
// Someone either already has a token or is in the process of getting one
|
|
97
|
-
// Wait for them to finish, then check for validity
|
|
98
|
-
const currentToken = await this.accessTokenPromise;
|
|
99
|
-
if (currentToken.expiresAt > DateTime.now()) {
|
|
100
|
-
// Current token is valid, return it
|
|
101
|
-
return currentToken.token;
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
// Current token expired, start a new promise
|
|
105
|
-
this.accessTokenPromise =
|
|
106
|
-
new ResolvablePromise();
|
|
107
|
-
try {
|
|
108
|
-
const newToken = await this.refreshOAuth2Token(currentToken.refreshToken);
|
|
109
|
-
this.accessTokenPromise.resolve(newToken);
|
|
110
|
-
await this.tokenPersister?.save("xbox.accessToken", newToken);
|
|
111
|
-
return newToken.token;
|
|
112
|
-
}
|
|
113
|
-
catch (e) {
|
|
114
|
-
this.accessTokenPromise.reject(e);
|
|
115
|
-
throw e;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
// We are the first caller, create a promise to block subsequent callers
|
|
121
|
-
this.accessTokenPromise =
|
|
122
|
-
new ResolvablePromise();
|
|
123
|
-
try {
|
|
124
|
-
const loadedToken = await this.tokenPersister?.load("xbox.accessToken");
|
|
125
|
-
const currentToken = {
|
|
126
|
-
...loadedToken,
|
|
127
|
-
token: loadedToken?.token ?? "",
|
|
128
|
-
expiresAt: coalesceDateTime(loadedToken?.expiresAt),
|
|
129
|
-
};
|
|
130
|
-
if (currentToken.expiresAt && currentToken.expiresAt > DateTime.now()) {
|
|
131
|
-
// Current token is valid, return it and alert other callers if applicable
|
|
132
|
-
this.accessTokenPromise.resolve(currentToken);
|
|
133
|
-
return currentToken.token;
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
const newToken = await this.fetchOauth2Token();
|
|
137
|
-
this.accessTokenPromise.resolve(newToken);
|
|
138
|
-
await this.tokenPersister?.save("xbox.accessToken", newToken);
|
|
139
|
-
return newToken.token;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
catch (e) {
|
|
143
|
-
this.accessTokenPromise.reject(e);
|
|
144
|
-
throw e;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
async fetchOauth2Token() {
|
|
149
|
-
const { code_verifier, code_challenge } = this.getPkce();
|
|
150
|
-
const authorizeUrl = `https://login.live.com/oauth20_authorize.srf?${new URLSearchParams({
|
|
151
|
-
client_id: this.clientId,
|
|
152
|
-
response_type: "code",
|
|
153
|
-
redirect_uri: this.redirectUri,
|
|
154
|
-
scope: SCOPES.join(" "),
|
|
155
|
-
code_challenge_method: "S256",
|
|
156
|
-
code_challenge,
|
|
157
|
-
})}`;
|
|
158
|
-
const code = await this.getAuthCode(authorizeUrl);
|
|
159
|
-
const requestStart = DateTime.now();
|
|
160
|
-
const response = await this.httpClient.post("https://login.live.com/oauth20_token.srf", new URLSearchParams({
|
|
161
|
-
grant_type: "authorization_code",
|
|
162
|
-
code,
|
|
163
|
-
approval_prompt: "auto",
|
|
164
|
-
scope: SCOPES.join(" "),
|
|
165
|
-
redirect_uri: this.redirectUri,
|
|
166
|
-
client_id: this.clientId,
|
|
167
|
-
code_verifier,
|
|
168
|
-
}), {
|
|
169
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
170
|
-
});
|
|
171
|
-
return {
|
|
172
|
-
token: response.data.access_token,
|
|
173
|
-
expiresAt: requestStart.plus({ seconds: response.data.expires_in }),
|
|
174
|
-
refreshToken: response.data.refresh_token,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
async refreshOAuth2Token(refreshToken) {
|
|
178
|
-
const requestStart = DateTime.now();
|
|
179
|
-
const response = await this.httpClient.post("https://login.live.com/oauth20_token.srf", new URLSearchParams({
|
|
180
|
-
grant_type: "refresh_token",
|
|
181
|
-
refresh_token: refreshToken,
|
|
182
|
-
scope: SCOPES.join(" "),
|
|
183
|
-
redirect_uri: this.redirectUri,
|
|
184
|
-
client_id: this.clientId,
|
|
185
|
-
}), {
|
|
186
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
187
|
-
});
|
|
188
|
-
return {
|
|
189
|
-
token: response.data.access_token,
|
|
190
|
-
expiresAt: requestStart.plus({ seconds: response.data.expires_in }),
|
|
191
|
-
refreshToken: response.data.refresh_token,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
76
|
async getUserToken(accessToken) {
|
|
195
77
|
const { Token } = await this.userTokenCache.getToken(accessToken);
|
|
196
78
|
return Token;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xbox-authentication-client.js","sourceRoot":"","sources":["../../src/authentication/xbox-authentication-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAwB,MAAM,OAAO,CAAC;AAC7C,OAAO,
|
|
1
|
+
{"version":3,"file":"xbox-authentication-client.js","sourceRoot":"","sources":["../../src/authentication/xbox-authentication-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(
|
|
10
|
+
constructor(getOauth2AccessToken: () => Promise<string>, tokenPersister?: TokenPersister);
|
|
11
11
|
}
|
|
@@ -8,7 +8,7 @@ import { inMemoryTokenPersister } from "../token-persisters/in-memory-token-pers
|
|
|
8
8
|
*/
|
|
9
9
|
export class AutoXstsSpartanTokenProvider {
|
|
10
10
|
getSpartanToken;
|
|
11
|
-
constructor(
|
|
11
|
+
constructor(getOauth2AccessToken, tokenPersister) {
|
|
12
12
|
let actualTokenPersister;
|
|
13
13
|
if (tokenPersister) {
|
|
14
14
|
actualTokenPersister = tokenPersister;
|
|
@@ -16,10 +16,9 @@ export class AutoXstsSpartanTokenProvider {
|
|
|
16
16
|
else {
|
|
17
17
|
actualTokenPersister = inMemoryTokenPersister;
|
|
18
18
|
}
|
|
19
|
-
const xboxAuthClient = new XboxAuthenticationClient(
|
|
19
|
+
const xboxAuthClient = new XboxAuthenticationClient(tokenPersister);
|
|
20
20
|
const haloAuthClient = new HaloAuthenticationClient(async () => {
|
|
21
|
-
const
|
|
22
|
-
const userToken = await xboxAuthClient.getUserToken(accessToken);
|
|
21
|
+
const userToken = await xboxAuthClient.getUserToken(await getOauth2AccessToken());
|
|
23
22
|
const xstsTicket = await xboxAuthClient.getXstsTicket(userToken, RelyingParty.Halo);
|
|
24
23
|
return xstsTicket.Token;
|
|
25
24
|
}, async () => await actualTokenPersister.load("halo.authToken"), async (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,
|
|
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"}
|
|
@@ -9,5 +9,6 @@ export var GameVariantCategory;
|
|
|
9
9
|
GameVariantCategory[GameVariantCategory["MultiplayerOddball"] = 18] = "MultiplayerOddball";
|
|
10
10
|
GameVariantCategory[GameVariantCategory["MultiplayerGrifball"] = 25] = "MultiplayerGrifball";
|
|
11
11
|
GameVariantCategory[GameVariantCategory["MultiplayerLandGrab"] = 39] = "MultiplayerLandGrab";
|
|
12
|
+
GameVariantCategory[GameVariantCategory["MultiplayerExtraction"] = 17] = "MultiplayerExtraction";
|
|
12
13
|
})(GameVariantCategory || (GameVariantCategory = {}));
|
|
13
14
|
//# sourceMappingURL=game-variant-category.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-variant-category.js","sourceRoot":"","sources":["../../../src/models/halo-infinite/game-variant-category.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,
|
|
1
|
+
{"version":3,"file":"game-variant-category.js","sourceRoot":"","sources":["../../../src/models/halo-infinite/game-variant-category.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,mBAWX;AAXD,WAAY,mBAAmB;IAC7B,uFAAqB,CAAA;IACrB,6FAAwB,CAAA;IACxB,uFAAqB,CAAA;IACrB,kGAA2B,CAAA;IAC3B,sGAA6B,CAAA;IAC7B,kFAAmB,CAAA;IACnB,0FAAuB,CAAA;IACvB,4FAAwB,CAAA;IACxB,4FAAwB,CAAA;IACxB,gGAA0B,CAAA;AAC5B,CAAC,EAXW,mBAAmB,KAAnB,mBAAmB,QAW9B"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "halo-infinite-api",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.1.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,9 +17,6 @@ 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
22
|
XboxTicket & { expiresAt?: unknown }
|
|
@@ -106,164 +99,10 @@ export class XboxAuthenticationClient {
|
|
|
106
99
|
|
|
107
100
|
private readonly httpClient: AxiosInstance;
|
|
108
101
|
|
|
109
|
-
constructor(
|
|
110
|
-
private readonly clientId: string,
|
|
111
|
-
private readonly redirectUri: string,
|
|
112
|
-
private readonly getAuthCode: (authorizeUrl: string) => Promise<string>,
|
|
113
|
-
private readonly tokenPersister?: TokenPersister
|
|
114
|
-
) {
|
|
102
|
+
constructor(private readonly tokenPersister?: TokenPersister) {
|
|
115
103
|
this.httpClient = axios.create();
|
|
116
104
|
}
|
|
117
105
|
|
|
118
|
-
private getPkce() {
|
|
119
|
-
// Some sort of module issue here, we work around it
|
|
120
|
-
type PkceChallenge = typeof pkceChallenge;
|
|
121
|
-
if (typeof pkceChallenge === "function") {
|
|
122
|
-
return pkceChallenge(43);
|
|
123
|
-
} else {
|
|
124
|
-
return (pkceChallenge as { default: PkceChallenge }).default(43);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
public async getAccessToken() {
|
|
129
|
-
if (this.accessTokenPromise) {
|
|
130
|
-
// Someone either already has a token or is in the process of getting one
|
|
131
|
-
// Wait for them to finish, then check for validity
|
|
132
|
-
const currentToken = await this.accessTokenPromise;
|
|
133
|
-
|
|
134
|
-
if (currentToken.expiresAt > DateTime.now()) {
|
|
135
|
-
// Current token is valid, return it
|
|
136
|
-
return currentToken.token;
|
|
137
|
-
} else {
|
|
138
|
-
// Current token expired, start a new promise
|
|
139
|
-
this.accessTokenPromise =
|
|
140
|
-
new ResolvablePromise<XboxAuthenticationToken>();
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const newToken = await this.refreshOAuth2Token(
|
|
144
|
-
currentToken.refreshToken
|
|
145
|
-
);
|
|
146
|
-
this.accessTokenPromise.resolve(newToken);
|
|
147
|
-
await this.tokenPersister?.save("xbox.accessToken", newToken);
|
|
148
|
-
return newToken.token;
|
|
149
|
-
} catch (e) {
|
|
150
|
-
this.accessTokenPromise.reject(e);
|
|
151
|
-
throw e;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
// We are the first caller, create a promise to block subsequent callers
|
|
156
|
-
this.accessTokenPromise =
|
|
157
|
-
new ResolvablePromise<XboxAuthenticationToken>();
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const loadedToken =
|
|
161
|
-
await this.tokenPersister?.load<XboxAuthenticationToken>(
|
|
162
|
-
"xbox.accessToken"
|
|
163
|
-
);
|
|
164
|
-
const currentToken = {
|
|
165
|
-
...loadedToken,
|
|
166
|
-
token: loadedToken?.token ?? "",
|
|
167
|
-
expiresAt: coalesceDateTime(loadedToken?.expiresAt),
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
if (currentToken.expiresAt && currentToken.expiresAt > DateTime.now()) {
|
|
171
|
-
// Current token is valid, return it and alert other callers if applicable
|
|
172
|
-
this.accessTokenPromise.resolve(
|
|
173
|
-
currentToken as XboxAuthenticationToken
|
|
174
|
-
);
|
|
175
|
-
return currentToken.token;
|
|
176
|
-
} else {
|
|
177
|
-
const newToken = await this.fetchOauth2Token();
|
|
178
|
-
this.accessTokenPromise.resolve(newToken);
|
|
179
|
-
await this.tokenPersister?.save("xbox.accessToken", newToken);
|
|
180
|
-
return newToken.token;
|
|
181
|
-
}
|
|
182
|
-
} catch (e) {
|
|
183
|
-
this.accessTokenPromise.reject(e);
|
|
184
|
-
throw e;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
private async fetchOauth2Token(): Promise<XboxAuthenticationToken> {
|
|
190
|
-
const { code_verifier, code_challenge } = this.getPkce();
|
|
191
|
-
|
|
192
|
-
const authorizeUrl = `https://login.live.com/oauth20_authorize.srf?${new URLSearchParams(
|
|
193
|
-
{
|
|
194
|
-
client_id: this.clientId,
|
|
195
|
-
response_type: "code",
|
|
196
|
-
redirect_uri: this.redirectUri,
|
|
197
|
-
scope: SCOPES.join(" "),
|
|
198
|
-
code_challenge_method: "S256",
|
|
199
|
-
code_challenge,
|
|
200
|
-
}
|
|
201
|
-
)}`;
|
|
202
|
-
|
|
203
|
-
const code = await this.getAuthCode(authorizeUrl);
|
|
204
|
-
|
|
205
|
-
const requestStart = DateTime.now();
|
|
206
|
-
const response = await this.httpClient.post<{
|
|
207
|
-
access_token: string;
|
|
208
|
-
expires_in: number;
|
|
209
|
-
scope: string;
|
|
210
|
-
token_type: string;
|
|
211
|
-
user_id: string;
|
|
212
|
-
refresh_token: string;
|
|
213
|
-
}>(
|
|
214
|
-
"https://login.live.com/oauth20_token.srf",
|
|
215
|
-
new URLSearchParams({
|
|
216
|
-
grant_type: "authorization_code",
|
|
217
|
-
code,
|
|
218
|
-
approval_prompt: "auto",
|
|
219
|
-
scope: SCOPES.join(" "),
|
|
220
|
-
redirect_uri: this.redirectUri,
|
|
221
|
-
client_id: this.clientId,
|
|
222
|
-
code_verifier,
|
|
223
|
-
}),
|
|
224
|
-
{
|
|
225
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
226
|
-
}
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
return {
|
|
230
|
-
token: response.data.access_token,
|
|
231
|
-
expiresAt: requestStart.plus({ seconds: response.data.expires_in }),
|
|
232
|
-
refreshToken: response.data.refresh_token,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
private async refreshOAuth2Token(
|
|
237
|
-
refreshToken: string
|
|
238
|
-
): Promise<XboxAuthenticationToken> {
|
|
239
|
-
const requestStart = DateTime.now();
|
|
240
|
-
const response = await this.httpClient.post<{
|
|
241
|
-
access_token: string;
|
|
242
|
-
expires_in: number;
|
|
243
|
-
scope: string;
|
|
244
|
-
token_type: string;
|
|
245
|
-
user_id: string;
|
|
246
|
-
refresh_token: string;
|
|
247
|
-
}>(
|
|
248
|
-
"https://login.live.com/oauth20_token.srf",
|
|
249
|
-
new URLSearchParams({
|
|
250
|
-
grant_type: "refresh_token",
|
|
251
|
-
refresh_token: refreshToken,
|
|
252
|
-
scope: SCOPES.join(" "),
|
|
253
|
-
redirect_uri: this.redirectUri,
|
|
254
|
-
client_id: this.clientId,
|
|
255
|
-
}),
|
|
256
|
-
{
|
|
257
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
258
|
-
}
|
|
259
|
-
);
|
|
260
|
-
return {
|
|
261
|
-
token: response.data.access_token,
|
|
262
|
-
expiresAt: requestStart.plus({ seconds: response.data.expires_in }),
|
|
263
|
-
refreshToken: response.data.refresh_token,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
106
|
public async getUserToken(accessToken: string) {
|
|
268
107
|
const { Token } = await this.userTokenCache.getToken(accessToken);
|
|
269
108
|
return Token;
|
|
@@ -16,9 +16,7 @@ export class AutoXstsSpartanTokenProvider implements SpartanTokenProvider {
|
|
|
16
16
|
public readonly getSpartanToken: () => Promise<string>;
|
|
17
17
|
|
|
18
18
|
constructor(
|
|
19
|
-
|
|
20
|
-
redirectUri: string,
|
|
21
|
-
getAuthCode: (authorizeUrl: string) => Promise<string>,
|
|
19
|
+
getOauth2AccessToken: () => Promise<string>,
|
|
22
20
|
tokenPersister?: TokenPersister
|
|
23
21
|
) {
|
|
24
22
|
let actualTokenPersister: TokenPersister;
|
|
@@ -27,16 +25,12 @@ export class AutoXstsSpartanTokenProvider implements SpartanTokenProvider {
|
|
|
27
25
|
} else {
|
|
28
26
|
actualTokenPersister = inMemoryTokenPersister;
|
|
29
27
|
}
|
|
30
|
-
const xboxAuthClient = new XboxAuthenticationClient(
|
|
31
|
-
clientId,
|
|
32
|
-
redirectUri,
|
|
33
|
-
getAuthCode,
|
|
34
|
-
tokenPersister
|
|
35
|
-
);
|
|
28
|
+
const xboxAuthClient = new XboxAuthenticationClient(tokenPersister);
|
|
36
29
|
const haloAuthClient = new HaloAuthenticationClient(
|
|
37
30
|
async () => {
|
|
38
|
-
const
|
|
39
|
-
|
|
31
|
+
const userToken = await xboxAuthClient.getUserToken(
|
|
32
|
+
await getOauth2AccessToken()
|
|
33
|
+
);
|
|
40
34
|
const xstsTicket = await xboxAuthClient.getXstsTicket(
|
|
41
35
|
userToken,
|
|
42
36
|
RelyingParty.Halo
|