UNPKG

@gw2me/client

Version:
289 lines (283 loc) 9.94 kB
//#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