Skip to content

Commit

Permalink
Merge pull request #2 from moegirlwiki/Dragon-Fish/patch-handle-cookie
Browse files Browse the repository at this point in the history
feat: + cookie handler for nodejs
  • Loading branch information
dragon-fish authored Dec 9, 2022
2 parents 17013c9 + 3d41729 commit 022c360
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 68 deletions.
100 changes: 75 additions & 25 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class MediaWikiApi {
#defaultParams: Ref<ApiParams>
#tokens: Record<string, string>
#axiosInstance: ComputedRef<AxiosInstance>
cookies: Record<string, string> = {}

constructor(baseURL = '/api.php', options?: AxiosRequestConfig) {
// Init
Expand All @@ -34,14 +35,43 @@ export class MediaWikiApi {

// Init AxiosInstance
this.#axiosInstance = computed(() => {
return MediaWikiApi.createAxiosInstance({
const instance = MediaWikiApi.createAxiosInstance({
baseURL: this.baseURL.value,
params: this.#defaultParams.value,
options: this.#defaultOptions.value,
})
// Cookie handling for nodejs
if (!('document' in globalThis)) {
instance.interceptors.response.use((ctx) => {
const rawCookies = ctx.headers['set-cookie']
rawCookies?.forEach((i) => {
const [name, value = ''] = i.split(';')[0].split('=')
this.cookies[name] = value
})
return ctx
})
instance.interceptors.request.use((ctx) => {
ctx.headers = ctx.headers || {}
ctx.headers['cookie'] = ''
for (const name in this.cookies) {
ctx.headers['cookie'] += `${name}=${this.cookies[name]};`
}
return ctx
})
}
return instance
})
}

static adjustParamValue(item: ApiParams) {
if (Array.isArray(item)) {
return item.join('|')
} else if (typeof item === 'boolean') {
return item ? '' : undefined
} else {
return item
}
}
static createAxiosInstance({
baseURL,
params,
Expand All @@ -59,9 +89,7 @@ export class MediaWikiApi {
})
instance.interceptors.request.use((ctx) => {
Object.keys(ctx.params).forEach((item) => {
if (Array.isArray(ctx.params[item])) {
ctx.params[item] = ctx.params[item].join('|')
}
ctx.params[item] = MediaWikiApi.adjustParamValue(ctx.params[item])
})
return ctx
})
Expand All @@ -82,12 +110,9 @@ export class MediaWikiApi {
}
const formData = new FormData()
for (const key in ctx.data) {
formData.append(
key,
Array.isArray(ctx.data[key])
? ctx.data[key].join('|')
: ctx.data[key]
)
const data = MediaWikiApi.adjustParamValue(ctx.data[key])
if (typeof data === 'undefined') continue
formData.append(key, data.toString())
}
ctx.data = formData
}
Expand Down Expand Up @@ -139,6 +164,25 @@ export class MediaWikiApi {
return this.ajax.post('', data, config)
}

async login(
username: string,
password: string,
params?: ApiParams
): Promise<{ status: 'PASS' | 'FAIL'; username: string }> {
this.defaultOptions.withCredentials = true
const { data } = await this.postWithToken(
'login',
{
action: 'clientlogin',
loginreturnurl: this.baseURL.value,
username,
password,
...params,
},
{ tokenName: 'logintoken' }
)
return data.clientlogin
}
async getUserInfo(): Promise<{
id: number
name: string
Expand All @@ -161,6 +205,7 @@ export class MediaWikiApi {

/** Token Handler */
async getTokens(type: TokenType[] = ['csrf']) {
this.defaultOptions.withCredentials = true
const { data } = await this.get({
action: 'query',
meta: 'tokens',
Expand All @@ -180,9 +225,9 @@ export class MediaWikiApi {
async postWithToken(
tokenType: TokenType,
body: ApiParams,
options?: { assert?: string; retry?: number; noCache?: boolean }
options?: { tokenName?: string; retry?: number; noCache?: boolean }
): Promise<AxiosResponse<any>> {
const { assert = 'token', retry = 3, noCache = false } = options || {}
const { tokenName = 'token', retry = 3, noCache = false } = options || {}
if (retry < 1) {
return Promise.reject({
error: {
Expand All @@ -192,21 +237,23 @@ export class MediaWikiApi {
})
}
return this.post({
[assert]: await this.token(tokenType, noCache),
[tokenName]: await this.token(tokenType, noCache),
...body,
}).catch(({ data }) => {
if ([data?.errors?.[0].code, data?.error?.code].includes('badtoken')) {
delete this.#tokens[`${tokenType}token`]
return this.postWithToken(tokenType, body, {
assert,
retry: retry - 1,
noCache: true,
})
}
return Promise.reject(data)
})
.finally(() => {
delete this.#tokens[`${tokenType}token`]
})
.catch(({ data }) => {
if ([data?.errors?.[0].code, data?.error?.code].includes('badtoken')) {
return this.postWithToken(tokenType, body, {
tokenName,
retry: retry - 1,
noCache: true,
})
}
return Promise.reject(data)
})
}

postWithEditToken(body: Record<string, string | number | string[]>) {
return this.postWithToken('csrf', body)
}
Expand Down Expand Up @@ -256,7 +303,10 @@ export class MediaWikiForeignApi extends MediaWikiApi {
}

type ValueOf<T> = T[keyof T]
type ApiParams = Record<string, string | number | string[] | undefined>
type ApiParams = Record<
string,
string | number | string[] | undefined | boolean
>
type TokenType =
| 'createaccount'
| 'csrf'
Expand Down
58 changes: 15 additions & 43 deletions test/authorization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,54 +11,26 @@ const api = new MediaWikiApi('https://zh.moegirl.org.cn/api.php', {
},
})

function safePrintJSON(object: any) {
if (object && typeof object === 'object') {
object = copyWithoutCircularReferences([object], object)
}
return JSON.stringify(object)

function copyWithoutCircularReferences(references: any, object: any) {
var cleanObject: any = {}
Object.keys(object).forEach(function (key) {
var value = object[key]
if (value && typeof value === 'object') {
if (references.indexOf(value) < 0) {
references.push(value)
cleanObject[key] = copyWithoutCircularReferences(references, value)
references.pop()
} else {
cleanObject[key] = '[Circular]'
}
} else if (typeof value !== 'function') {
cleanObject[key] = value
}
})
return cleanObject
}
}
const username = env.MOEGIRL_USERNAME || ''
const password = env.MOEGIRL_PASSWORD || ''

describe('Authorization', () => {
it('Get token', async () => {
const token = await api.token('login')
expect(token).to.be.a('string')
})

// it('Login', async () => {
// const response = await api
// .postWithToken(
// 'login',
// {
// action: 'clientlogin',
// loginreturnurl: 'https://zh.moegirl.org.cn/',
// username: '',
// password: '',
// },
// { assert: 'logintoken' }
// )
// .catch((e) => {
// console.error('LOGIN FAIL', e.data)
// return Promise.reject(e)
// })
// console.info('LOGIN OK', response.data)
// })
it('Login', async () => {
const login = await api.login(username, password).catch((e) => {
console.error('LOGIN FAIL', e.data)
return Promise.reject(e)
})
expect(['PASS', 'FAIL']).to.includes(login.status)
if (login.status !== 'PASS') {
return
}
const userinfo = await api.getUserInfo()
expect(userinfo.id).not.to.equal(0)
expect(userinfo.name).to.equal(username)
})
})

0 comments on commit 022c360

Please sign in to comment.