@gw2me/client
Version:
gw2.me client library
289 lines (283 loc) • 9.94 kB
JavaScript
//#region src/types.ts
let Scope = /* @__PURE__ */ function(Scope$1) {
Scope$1["Identify"] = "identify";
Scope$1["Email"] = "email";
Scope$1["Accounts"] = "accounts";
Scope$1["Accounts_Verified"] = "accounts.verified";
Scope$1["Accounts_DisplayName"] = "accounts.displayName";
Scope$1["GW2_Account"] = "gw2:account";
Scope$1["GW2_Inventories"] = "gw2:inventories";
Scope$1["GW2_Characters"] = "gw2:characters";
Scope$1["GW2_Tradingpost"] = "gw2:tradingpost";
Scope$1["GW2_Wallet"] = "gw2:wallet";
Scope$1["GW2_Unlocks"] = "gw2:unlocks";
Scope$1["GW2_Pvp"] = "gw2:pvp";
Scope$1["GW2_Wvw"] = "gw2:wvw";
Scope$1["GW2_Builds"] = "gw2:builds";
Scope$1["GW2_Progression"] = "gw2:progression";
Scope$1["GW2_Guilds"] = "gw2:guilds";
return Scope$1;
}({});
//#endregion
//#region src/error.ts
var Gw2MeError = class extends Error {};
var Gw2MeOAuthError = class extends Gw2MeError {
constructor(error, error_description, error_uri) {
super(`Received ${error}` + (error_description ? `: ${error_description}` : "") + (error_uri ? ` (${error_uri})` : ""));
this.error = error;
this.error_description = error_description;
this.error_uri = error_uri;
}
};
//#endregion
//#region src/util.ts
async function jsonOrError(response) {
await okOrError(response);
if (!(response.headers.get("Content-Type") === "application/json")) throw new Gw2MeError("gw2.me did not return a valid JSON response");
return response.json();
}
async function okOrError(response) {
if (!response.ok) {
let errorMessage;
if (response.headers.get("Content-Type") === "application/json") errorMessage = (await response.json()).error_description;
throw new Gw2MeError(`gw2.me returned an error: ${errorMessage ?? "Unknown error"}`);
}
}
//#endregion
//#region src/api.ts
var Gw2MeApi = class {
constructor(access_token, options) {
this.access_token = access_token;
this.options = options;
}
user() {
return this.#requestWithDpop("api/user").then((request) => fetch(request)).then(jsonOrError);
}
saveSettings(settings) {
return this.#requestWithDpop("api/user/settings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(settings)
}).then((request) => fetch(request)).then(okOrError);
}
accounts() {
return this.#requestWithDpop("api/accounts").then((request) => fetch(request)).then(jsonOrError);
}
subtoken(accountId, options) {
const url = this.#getUrl(`api/accounts/${accountId}/subtoken`);
if (options?.permissions) url.searchParams.set("permissions", options.permissions.join(","));
return this.#requestWithDpop(url).then((request) => fetch(request)).then(jsonOrError);
}
#getUrl(url) {
return new URL(url, this.options?.url || "https://gw2.me/");
}
async #requestWithDpop(endpoint, init) {
const url = endpoint instanceof URL ? endpoint : this.#getUrl(endpoint);
const dpop = this.options?.dpop;
const headers = new Headers(init?.headers);
headers.set("Authorization", `${dpop ? "DPoP" : "Bearer"} ${this.access_token}`);
if (dpop) headers.set("DPoP", await dpop({
htm: init?.method ?? "GET",
htu: url.toString(),
accessToken: this.access_token
}));
return new Request(url, {
cache: "no-cache",
...init,
headers
});
}
};
//#endregion
//#region src/fed-cm.ts
var Gw2MeFedCM = class {
#configUrl;
#clientId;
constructor(configUrl, clientId) {
this.#configUrl = configUrl;
this.#clientId = clientId;
}
isSupported() {
return typeof window !== "undefined" && "IdentityCredential" in window;
}
request({ scopes, mediation, signal, mode, code_challenge, code_challenge_method, include_granted_scopes }) {
if (!this.isSupported()) throw new Gw2MeError("FedCM is not supported");
return navigator.credentials.get({
mediation,
signal,
identity: {
providers: [{
configURL: this.#configUrl,
clientId: this.#clientId,
fields: [scopes.includes(Scope.Identify) && "name", scopes.includes(Scope.Email) && "email"].filter(Boolean),
nonce: `${code_challenge_method}:${code_challenge}`,
params: {
scope: scopes.join(" "),
code_challenge,
code_challenge_method,
include_granted_scopes
}
}],
mode
}
});
}
};
//#endregion
//#region src/client.ts
var Gw2MeClient = class {
#client;
#fedCM;
constructor(client, options) {
this.options = options;
this.#client = client;
this.#fedCM = new Gw2MeFedCM(this.#getUrl("/fed-cm/config.json"), client.client_id);
}
#getUrl(url) {
return new URL(url, this.options?.url || "https://gw2.me/");
}
#getAuthorizationHeader() {
if (!this.#client.client_secret) throw new Gw2MeError("client_secret is required");
return `Basic ${btoa(`${this.#client.client_id}:${this.#client.client_secret}`)}`;
}
getAuthorizationUrl(params) {
const urlParams = "request_uri" in params ? new URLSearchParams({
client_id: this.#client.client_id,
response_type: "code",
request_uri: params.request_uri
}) : constructAuthorizationParams(this.#client.client_id, params);
return this.#getUrl(`/oauth2/authorize?${urlParams.toString()}`).toString();
}
async pushAuthorizationRequest(params) {
const url = this.#getUrl("/oauth2/par");
const headers = { "Content-Type": "application/x-www-form-urlencoded" };
if (params.dpop) headers.DPoP = await params.dpop({
htm: "POST",
htu: url.toString()
});
const urlParams = constructAuthorizationParams(this.#client.client_id, params);
if (this.#client.client_secret) headers.Authorization = this.#getAuthorizationHeader();
return await fetch(url, {
method: "POST",
headers,
body: urlParams,
cache: "no-store"
}).then(jsonOrError);
}
async getAccessToken({ code, token_type, redirect_uri, code_verifier, dpop }) {
const data = new URLSearchParams({
grant_type: "authorization_code",
code,
client_id: this.#client.client_id,
redirect_uri
});
const headers = { "Content-Type": "application/x-www-form-urlencoded" };
if (this.#client.client_secret) headers.Authorization = this.#getAuthorizationHeader();
if (code_verifier) data.set("code_verifier", code_verifier);
const url = this.#getUrl("/api/token");
if (dpop) headers.DPoP = await dpop({
htm: "POST",
htu: url.toString(),
accessToken: token_type === "DPoP" ? code : void 0
});
return await fetch(url, {
method: "POST",
headers,
body: data,
cache: "no-store"
}).then(jsonOrError);
}
async refreshToken({ refresh_token, refresh_token_type, dpop }) {
const data = new URLSearchParams({
grant_type: "refresh_token",
refresh_token,
client_id: this.#client.client_id
});
const headers = { "Content-Type": "application/x-www-form-urlencoded" };
if (this.#client.client_secret) headers["Authorization"] = this.#getAuthorizationHeader();
const url = this.#getUrl("/api/token");
if (dpop) headers.DPoP = await dpop({
htm: "POST",
htu: url.toString(),
accessToken: refresh_token_type === "DPoP" ? refresh_token : void 0
});
return await fetch(url, {
method: "POST",
headers,
body: data,
cache: "no-store"
}).then(jsonOrError);
}
async revokeToken({ token }) {
const body = new URLSearchParams({ token });
const headers = { "Content-Type": "application/x-www-form-urlencoded" };
if (this.#client.client_secret) headers.Authorization = this.#getAuthorizationHeader();
await fetch(this.#getUrl("/api/token/revoke"), {
method: "POST",
cache: "no-store",
headers,
body
}).then(jsonOrError);
}
async introspectToken({ token }) {
const body = new URLSearchParams({ token });
const headers = { "Content-Type": "application/x-www-form-urlencoded" };
if (this.#client.client_secret) headers.Authorization = this.#getAuthorizationHeader();
return await fetch(this.#getUrl("/api/token/introspect"), {
method: "POST",
cache: "no-store",
headers,
body
}).then(jsonOrError);
}
/**
* Parses the search params received from gw2.me on the redirect url (code and state).
* If gw2.me returned an error response, this will throw an error.
*
* @returns The code and optional state.
*/
parseAuthorizationResponseSearchParams(searchParams) {
const expectedIssuer = this.#getUrl("/").origin;
const receivedIssuer = searchParams.get("iss");
if (!receivedIssuer) throw new Gw2MeError("Issuer Identifier verification failed: parameter `iss` is missing");
if (receivedIssuer !== expectedIssuer) throw new Gw2MeError(`Issuer Identifier verification failed: expected "${expectedIssuer}", got "${receivedIssuer}"`);
const error = searchParams.get("error");
if (error) throw new Gw2MeOAuthError(error, searchParams.get("error_description") ?? void 0, searchParams.get("error_uri") ?? void 0);
const code = searchParams.get("code");
if (!code) throw new Gw2MeError("Parameter `code` is missing");
return {
code,
state: searchParams.get("state") || void 0
};
}
api(access_token, options) {
return new Gw2MeApi(access_token, {
...this.options,
...options
});
}
get fedCM() {
return this.#fedCM;
}
};
function constructAuthorizationParams(client_id, { redirect_uri, scopes, state, code_challenge, code_challenge_method, dpop_jkt, prompt, include_granted_scopes, verified_accounts_only }) {
const params = new URLSearchParams({
client_id,
response_type: "code",
redirect_uri,
scope: scopes.join(" ")
});
if (state) params.append("state", state);
if (code_challenge && code_challenge_method) {
params.append("code_challenge", code_challenge);
params.append("code_challenge_method", code_challenge_method);
}
if (dpop_jkt) params.append("dpop_jkt", dpop_jkt);
if (prompt) params.append("prompt", prompt);
if (include_granted_scopes) params.append("include_granted_scopes", "true");
if (verified_accounts_only) params.append("verified_accounts_only", "true");
return params;
}
//#endregion
export { Gw2MeApi, Gw2MeClient, Gw2MeError, Gw2MeOAuthError, Scope };
//# sourceMappingURL=index.mjs.map