Skip to content

Commit

Permalink
[Client] Improve client functionality and dogfood @blizzard-api/wow
Browse files Browse the repository at this point in the history
  • Loading branch information
Pewtro committed Mar 28, 2024
1 parent 3ea04fa commit 2d6dbeb
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 49 deletions.
5 changes: 5 additions & 0 deletions .changeset/lucky-feet-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@blizzard-api/client': patch
---

Improve client functionality and dogfood @blizzard-api/wow
3 changes: 2 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
"@blizzard-api/core": "0.0.2"
},
"devDependencies": {
"@blizzard-api/core": "workspace:*"
"@blizzard-api/core": "workspace:*",
"@blizzard-api/wow": "workspace:*"
},
"scripts": {
"build": "tsup",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,15 @@
import { stringify } from 'node:querystring';
import { getEndpoint } from '@blizzard-api/core';
import type { Origins, Locales } from '@blizzard-api/core';
import type { Origins, Locales, ClientOptions, ResourceResponse, Resource } from '@blizzard-api/core';
import type { AxiosResponse } from 'axios';
import axios from 'axios';

export interface ClientOptions {
key: string;
secret: string;
origin: Origins;
locale?: Locales;
token?: string;
}

interface AccessToken {
access_token: string;
token_type: 'bearer';
expires_in: number;
sub?: string;
}

interface AccessTokenRequestArguments {
origin?: Origins;
key?: string;
secret?: string;
}

interface ValidateAccessTokenArguments {
origin?: Origins;
token?: string;
}

interface ValidateAccessTokenResponse {
scope: Array<string>;
account_authorities: Array<unknown>;
exp: number;
client_authorities: Array<unknown>;
authorities: Array<string>;
client_id: string;
}

interface IBlizzardApiClient {
getAccessToken: (options: AccessTokenRequestArguments) => Promise<AxiosResponse<AccessToken>>;
setAccessToken: (token: string) => void;
refreshAccessToken: (options: AccessTokenRequestArguments) => Promise<AxiosResponse<AccessToken>>;
validateAccessToken: (options: ValidateAccessTokenArguments) => Promise<AxiosResponse<ValidateAccessTokenResponse>>;
}
import type {
AccessToken,
AccessTokenRequestArguments,
IBlizzardApiClient,
ValidateAccessTokenArguments,
ValidateAccessTokenResponse,
} from './types';

export class BlizzardApiClient implements IBlizzardApiClient {
public defaults: {
Expand All @@ -68,6 +33,52 @@ export class BlizzardApiClient implements IBlizzardApiClient {

public axios = axios.create();

public getRequestUrl<T = unknown>(resource: Resource<T>, options?: Partial<ClientOptions>) {
const config = { ...this.defaults, ...options };
const endpoint = getEndpoint(config.origin, config.locale);

const backslashSeparator = resource.path.startsWith('/') ? '' : '/';

return `${endpoint.hostname}${backslashSeparator}${resource.path}`;
}

public getRequestConfig<T = unknown>(
resource: Resource<T>,
options?: Partial<ClientOptions>,
headers?: Record<string, string>,
) {
const config = { ...this.defaults, ...options };
const endpoint = getEndpoint(config.origin, config.locale);

const namespace = resource.namespace
? { 'Battlenet-Namespace': `${resource.namespace}-${endpoint.origin}` }
: undefined;

return {
headers: {
...headers,
...namespace,
'Content-Type': 'application/json',
Authorization: `Bearer ${config.token}`,
},
params: {
...resource.params,
locale: endpoint.locale,
},
};
}

public sendRequest<T = unknown>(
resource: Resource<T>,
options?: Partial<ClientOptions> & T,
headers?: Record<string, string>,
): ResourceResponse<AxiosResponse<T>> {
const url = this.getRequestUrl(resource, options);
const config = this.getRequestConfig(resource, options, headers);

return this.axios.get<T>(url, config);
}

public getAccessToken = async (options?: AccessTokenRequestArguments): Promise<AxiosResponse<AccessToken>> => {
const { key, secret, origin } = { ...this.defaults, ...options };
return this.axios.post<AccessToken>(`https://${origin}.battle.net/oauth/token`, undefined, {
Expand Down
36 changes: 36 additions & 0 deletions packages/client/src/client/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Origins } from '@blizzard-api/core';
import type { AxiosResponse } from 'axios';

export interface AccessToken {
access_token: string;
token_type: 'bearer';
expires_in: number;
sub?: string;
}

export interface AccessTokenRequestArguments {
origin?: Origins;
key?: string;
secret?: string;
}

export interface ValidateAccessTokenArguments {
origin?: Origins;
token?: string;
}

export interface ValidateAccessTokenResponse {
scope: Array<string>;
account_authorities: Array<unknown>;
exp: number;
client_authorities: Array<unknown>;
authorities: Array<string>;
client_id: string;
}

export interface IBlizzardApiClient {
getAccessToken: (options: AccessTokenRequestArguments) => Promise<AxiosResponse<AccessToken>>;
setAccessToken: (token: string) => void;
refreshAccessToken: (options: AccessTokenRequestArguments) => Promise<AxiosResponse<AccessToken>>;
validateAccessToken: (options: ValidateAccessTokenArguments) => Promise<AxiosResponse<ValidateAccessTokenResponse>>;
}
3 changes: 1 addition & 2 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export type { ClientOptions } from './client.js';
export { BlizzardApiClient } from './client.js';
export { BlizzardApiClient } from './client/index.js';
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { environment } from '../../../environment';
import { BlizzardApiClient } from '../src';
import { environment } from '../../../../environment';
import { BlizzardApiClient } from '../client';

describe('client', () => {
const client = new BlizzardApiClient({
Expand Down
23 changes: 23 additions & 0 deletions packages/client/src/tests/wow/achievement.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { wow } from '@blizzard-api/wow';
import { describe, expect, it } from 'vitest';
import { environment } from '~/environment';
import { BlizzardApiClient } from '../../client';

describe('achievement', async () => {
const client = new BlizzardApiClient({
key: environment.blizzardClientId,
secret: environment.blizzardClientSecret,
origin: 'eu',
});
const access = await client.getAccessToken();
client.setAccessToken(access.data.access_token);

it('should be able to fetch an achievement', async () => {
const response = await client.sendRequest<{ id: number }>(wow.achievement.achievement(16_542)).catch((error) => {
console.error('error', error);
throw new Error('Failed to fetch achievement');
});

expect(response.data.id).toBe(16_542);
});
});
9 changes: 8 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@
/* Vite specific settings */
"types": ["vitest/importMeta"],

/* Advanced Options */
"baseUrl": ".",
"paths": {
"~/*": ["./*"]
},

/* We transpile with tsup */
"noEmit": true,
"module": "Preserve",
Expand Down

0 comments on commit 2d6dbeb

Please sign in to comment.