Init
This commit is contained in:
commit
8303ec0e16
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
24
LICENSE
Normal file
24
LICENSE
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Based on https://github.com/maple3142/GDIndex
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019-2020 maple3142, Simon Bruder
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
10
bili.config.js
Normal file
10
bili.config.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module.exports = {
|
||||||
|
input: 'index.js',
|
||||||
|
output: {
|
||||||
|
dir: 'dist',
|
||||||
|
fileName: 'worker.js',
|
||||||
|
format: 'iife'
|
||||||
|
},
|
||||||
|
minify: false,
|
||||||
|
target: 'browser'
|
||||||
|
}
|
171
googleDrive.js
Normal file
171
googleDrive.js
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import xf from './xfetch'
|
||||||
|
|
||||||
|
class GoogleDrive {
|
||||||
|
constructor(auth) {
|
||||||
|
this.auth = auth
|
||||||
|
this.expires = 0
|
||||||
|
this._getIdCache = new Map()
|
||||||
|
}
|
||||||
|
async initializeClient() {
|
||||||
|
// any method that do api call must call this beforehand
|
||||||
|
if (Date.now() < this.expires) return
|
||||||
|
const resp = await xf
|
||||||
|
.post('https://www.googleapis.com/oauth2/v4/token', {
|
||||||
|
urlencoded: {
|
||||||
|
client_id: this.auth.client_id,
|
||||||
|
client_secret: this.auth.client_secret,
|
||||||
|
refresh_token: this.auth.refresh_token,
|
||||||
|
grant_type: 'refresh_token'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json()
|
||||||
|
this.client = xf.extend({
|
||||||
|
baseURI: 'https://www.googleapis.com/drive/v3/',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${resp.access_token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.expires = Date.now() + 3500 * 1000 // normally, it should expiers after 3600 seconds
|
||||||
|
}
|
||||||
|
async listDrive() {
|
||||||
|
await this.initializeClient()
|
||||||
|
return this.client.get('drives').json()
|
||||||
|
}
|
||||||
|
async download(id, range = '') {
|
||||||
|
await this.initializeClient()
|
||||||
|
return this.client.get(`files/${id}`, {
|
||||||
|
qs: {
|
||||||
|
includeItemsFromAllDrives: true,
|
||||||
|
supportsAllDrives: true,
|
||||||
|
alt: 'media'
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
Range: range
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async downloadByPath(path, rootId = 'root', range = '') {
|
||||||
|
const id = await this.getId(path, rootId)
|
||||||
|
if (!id) return null
|
||||||
|
return this.download(id, range)
|
||||||
|
}
|
||||||
|
async getMeta(id) {
|
||||||
|
await this.initializeClient()
|
||||||
|
return this.client
|
||||||
|
.get(`files/${id}`, {
|
||||||
|
qs: {
|
||||||
|
includeItemsFromAllDrives: true,
|
||||||
|
supportsAllDrives: true,
|
||||||
|
fields: '*'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json()
|
||||||
|
}
|
||||||
|
async getMetaByPath(path, rootId = 'root') {
|
||||||
|
const id = await this.getId(path, rootId)
|
||||||
|
if (!id) return null
|
||||||
|
return this.getMeta(id)
|
||||||
|
}
|
||||||
|
async listFolder(id) {
|
||||||
|
await this.initializeClient()
|
||||||
|
const getList = pageToken => {
|
||||||
|
const qs = {
|
||||||
|
includeItemsFromAllDrives: true,
|
||||||
|
supportsAllDrives: true,
|
||||||
|
q: `'${id}' in parents and trashed = false`,
|
||||||
|
orderBy: 'folder,name,modifiedTime desc',
|
||||||
|
fields:
|
||||||
|
'files(id,name,mimeType,size,modifiedTime),nextPageToken',
|
||||||
|
pageSize: 1000
|
||||||
|
}
|
||||||
|
if (pageToken) {
|
||||||
|
qs.pageToken = pageToken
|
||||||
|
}
|
||||||
|
return this.client
|
||||||
|
.get('files', {
|
||||||
|
qs
|
||||||
|
})
|
||||||
|
.json()
|
||||||
|
}
|
||||||
|
const files = []
|
||||||
|
let pageToken
|
||||||
|
do {
|
||||||
|
const resp = await getList(pageToken)
|
||||||
|
files.push(...resp.files)
|
||||||
|
pageToken = resp.nextPageToken
|
||||||
|
} while (pageToken)
|
||||||
|
return { files }
|
||||||
|
}
|
||||||
|
async listFolderByPath(path, rootId = 'root') {
|
||||||
|
const id = await this.getId(path, rootId)
|
||||||
|
if (!id) return null
|
||||||
|
return this.listFolder(id)
|
||||||
|
}
|
||||||
|
async getId(path, rootId = 'root') {
|
||||||
|
const toks = path.split('/').filter(Boolean)
|
||||||
|
let id = rootId
|
||||||
|
for (const tok of toks) {
|
||||||
|
id = await this._getId(id, tok)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
async _getId(parentId, childName) {
|
||||||
|
if (this._getIdCache.has(parentId + childName)) {
|
||||||
|
return this._getIdCache.get(parentId + childName)
|
||||||
|
}
|
||||||
|
await this.initializeClient()
|
||||||
|
childName = childName.replace(/\'/g, `\\'`) // escape single quote
|
||||||
|
const resp = await this.client
|
||||||
|
.get('files', {
|
||||||
|
qs: {
|
||||||
|
includeItemsFromAllDrives: true,
|
||||||
|
supportsAllDrives: true,
|
||||||
|
q: `'${parentId}' in parents and name = '${childName}' and trashed = false`,
|
||||||
|
fields: 'files(id)'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json()
|
||||||
|
.catch(e => ({ files: [] })) // if error, make it empty
|
||||||
|
if (resp.files.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
this._getIdCache.has(parentId + childName)
|
||||||
|
return resp.files[0].id // when there are more than 1 items, simply return the first one
|
||||||
|
}
|
||||||
|
async upload(parentId, name, file) {
|
||||||
|
await this.initializeClient()
|
||||||
|
const createResp = await this.client.post(
|
||||||
|
'https://www.googleapis.com/upload/drive/v3/files',
|
||||||
|
{
|
||||||
|
qs: {
|
||||||
|
uploadType: 'resumable',
|
||||||
|
supportsAllDrives: true
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
name,
|
||||||
|
parents: [parentId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const putUrl = createResp.headers.get('Location')
|
||||||
|
return this.client
|
||||||
|
.put(putUrl, {
|
||||||
|
body: file
|
||||||
|
})
|
||||||
|
.json()
|
||||||
|
}
|
||||||
|
async uploadByPath(path, name, file, rootId = 'root') {
|
||||||
|
const id = await this.getId(path, rootId)
|
||||||
|
if (!id) return null
|
||||||
|
return this.upload(id, name, file)
|
||||||
|
}
|
||||||
|
async delete(fileId) {
|
||||||
|
return this.client.delete(`files/${fileId}`)
|
||||||
|
}
|
||||||
|
async deleteByPath(path, rootId = 'root') {
|
||||||
|
const id = await this.getId(path, rootId)
|
||||||
|
if (!id) return null
|
||||||
|
return this.delete(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default GoogleDrive
|
97
index.js
Normal file
97
index.js
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
// vim: set noexpandtab:
|
||||||
|
import mime from 'mime'
|
||||||
|
import GoogleDrive from './googleDrive'
|
||||||
|
|
||||||
|
self.props = {
|
||||||
|
root_id: typeof(ROOT_ID) !== 'undefined' ? ROOT_ID : 'root',
|
||||||
|
client_id: typeof(CLIENT_ID) !== 'undefined' ? CLIENT_ID : '202264815644.apps.googleusercontent.com',
|
||||||
|
client_secret: typeof(CLIENT_SECRET) !== 'undefined' ? CLIENT_SECRET : 'X4Z3ca8xfWDb1Voo-F9a7ZxJ',
|
||||||
|
refresh_token: typeof(REFRESH_TOKEN) !== 'undefined' ? REFRESH_TOKEN : '' // left here for debugging
|
||||||
|
}
|
||||||
|
|
||||||
|
const gd = new GoogleDrive(self.props)
|
||||||
|
|
||||||
|
async function onGet(request) {
|
||||||
|
let { pathname: path } = request
|
||||||
|
const rootId =
|
||||||
|
request.searchParams.get('rootId') || self.props.root_id
|
||||||
|
const result = await gd.getMetaByPath(path, rootId)
|
||||||
|
if (!result || result.mimeType.includes('vnd.google-apps')) {
|
||||||
|
return new Response('404 Not Found', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
},
|
||||||
|
status: 404
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const r = await gd.download(result.id, request.headers.get('Range'))
|
||||||
|
const h = new Headers(r.headers)
|
||||||
|
h.set(
|
||||||
|
'Content-Disposition',
|
||||||
|
`inline; filename*=UTF-8''${encodeURIComponent(result.name)}`
|
||||||
|
)
|
||||||
|
return new Response(r.body, {
|
||||||
|
status: r.status,
|
||||||
|
headers: h
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodePathComponent(path) {
|
||||||
|
return path
|
||||||
|
.split('/')
|
||||||
|
.map(encodeURIComponent)
|
||||||
|
.join('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRequest(request) {
|
||||||
|
if (request.method === 'OPTIONS')
|
||||||
|
// allow preflight request
|
||||||
|
return new Response('', {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Headers': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
request = Object.assign({}, request, new URL(request.url))
|
||||||
|
request.pathname = request.pathname
|
||||||
|
.split('/')
|
||||||
|
.map(decodeURIComponent)
|
||||||
|
.map(decodeURIComponent) // for some super special cases, browser will force encode it... eg: +αあるふぁきゅん。 - +♂.mp3
|
||||||
|
.join('/')
|
||||||
|
|
||||||
|
if (request.pathname.endsWith('/')) request.pathname += 'index.html'
|
||||||
|
|
||||||
|
let resp
|
||||||
|
if (request.method === 'GET') resp = await onGet(request)
|
||||||
|
else
|
||||||
|
resp = new Response('', {
|
||||||
|
status: 405
|
||||||
|
})
|
||||||
|
const obj = Object.create(null)
|
||||||
|
for (const [k, v] of resp.headers.entries()) {
|
||||||
|
obj[k] = v
|
||||||
|
}
|
||||||
|
return new Response(resp.body, {
|
||||||
|
status: resp.status,
|
||||||
|
statusText: resp.statusText,
|
||||||
|
headers: Object.assign(obj, {
|
||||||
|
'Access-Control-Allow-Origin': '*'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener('fetch', event => {
|
||||||
|
event.respondWith(
|
||||||
|
handleRequest(event.request).catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
new Response(JSON.stringify(err.stack), {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
16
package.json
Normal file
16
package.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "gdindex-worker",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "dist/worker.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime": "^2.4.4",
|
||||||
|
"path-to-regexp": "^3.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"bili": "^4.8.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "bili"
|
||||||
|
}
|
||||||
|
}
|
75
router.js
Normal file
75
router.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
const pathToRegexp = require('path-to-regexp')
|
||||||
|
|
||||||
|
class Router {
|
||||||
|
constructor() {
|
||||||
|
this.handlers = []
|
||||||
|
}
|
||||||
|
use(handler) {
|
||||||
|
this.handlers.push(handler)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
useRoute(path, handler) {
|
||||||
|
const keys = []
|
||||||
|
const re = pathToRegexp(path, keys)
|
||||||
|
this.use(async (req, res, next) => {
|
||||||
|
if (re.test(req.pathname)) {
|
||||||
|
const [_, ...result] = re.exec(req.pathname)
|
||||||
|
const params = {}
|
||||||
|
for (let i = 0; i < result.length; i++) {
|
||||||
|
params[keys[i].name] = result[i]
|
||||||
|
}
|
||||||
|
req.params = params
|
||||||
|
await handler(req, res, next)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
useRouteWithVerb(verb, path, handler) {
|
||||||
|
verb = verb.toUpperCase()
|
||||||
|
this.useRoute(path, async (req, res, next) => {
|
||||||
|
if (req.method === verb) {
|
||||||
|
await handler(req, res, next)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
useWithVerb(verb, handler) {
|
||||||
|
verb = verb.toUpperCase()
|
||||||
|
this.use(async (req, res, next) => {
|
||||||
|
if (req.method === verb) {
|
||||||
|
await handler(req, res, next)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
async handle(request) {
|
||||||
|
const responseCtx = {
|
||||||
|
body: '',
|
||||||
|
headers: {}
|
||||||
|
}
|
||||||
|
const requestCtx = Object.assign({}, request, new URL(request.url))
|
||||||
|
const createNext = n => async () => {
|
||||||
|
const fn = this.handlers[n]
|
||||||
|
if (!fn) return
|
||||||
|
let gotCalled = false
|
||||||
|
const next = createNext(n + 1)
|
||||||
|
await fn(requestCtx, responseCtx, () => {
|
||||||
|
gotCalled = true
|
||||||
|
return next()
|
||||||
|
})
|
||||||
|
if (!gotCalled) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await createNext(0)()
|
||||||
|
return responseCtx.response ? responseCtx.response : new Response(responseCtx.body, responseCtx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const verb of ['get', 'options', 'head']) {
|
||||||
|
Router.prototype[verb] = function(path, handler) {
|
||||||
|
if (handler) this.useRouteWithVerb(verb, path, handler)
|
||||||
|
else this.useWithVerb(verb, path) // when there is only 1 argument, path is handler
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.exports = Router
|
4
wrangler.toml
Normal file
4
wrangler.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
name = "gdcdn"
|
||||||
|
type = "javascript"
|
||||||
|
account_id = "8f78257f5eee59cdae86b89edc91082f"
|
||||||
|
workers_dev = "true"
|
116
xfetch.js
Normal file
116
xfetch.js
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* XFetch.js modified
|
||||||
|
* A extremely simple fetch extension inspired by sindresorhus/ky.
|
||||||
|
*/
|
||||||
|
const xf = (() => {
|
||||||
|
const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head']
|
||||||
|
class HTTPError extends Error {
|
||||||
|
constructor(res) {
|
||||||
|
super(res.statusText)
|
||||||
|
this.name = 'HTTPError'
|
||||||
|
this.response = res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class XResponsePromise extends Promise {}
|
||||||
|
for (const alias of ['arrayBuffer', 'blob', 'formData', 'json', 'text']) {
|
||||||
|
// alias for .json() .text() etc...
|
||||||
|
XResponsePromise.prototype[alias] = function(fn) {
|
||||||
|
return this.then(res => res[alias]()).then(fn || (x => x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { assign } = Object
|
||||||
|
function mergeDeep(target, source) {
|
||||||
|
const isObject = obj => obj && typeof obj === 'object'
|
||||||
|
|
||||||
|
if (!isObject(target) || !isObject(source)) {
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(source).forEach(key => {
|
||||||
|
const targetValue = target[key]
|
||||||
|
const sourceValue = source[key]
|
||||||
|
|
||||||
|
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
|
||||||
|
target[key] = targetValue.concat(sourceValue)
|
||||||
|
} else if (isObject(targetValue) && isObject(sourceValue)) {
|
||||||
|
target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue)
|
||||||
|
} else {
|
||||||
|
target[key] = sourceValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
const fromEntries = ent => ent.reduce((acc, [k, v]) => ((acc[k] = v), acc), {})
|
||||||
|
const typeis = (...types) => val =>
|
||||||
|
types.some(type => (typeof type === 'string' ? typeof val === type : val instanceof type))
|
||||||
|
const isstr = typeis('string')
|
||||||
|
const isobj = typeis('object')
|
||||||
|
const isstrorobj = v => isstr(v) || isobj(v)
|
||||||
|
const responseErrorThrower = res => {
|
||||||
|
if (!res.ok) throw new HTTPError(res)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
const extend = (defaultInit = {}) => {
|
||||||
|
const xfetch = (input, init = {}) => {
|
||||||
|
mergeDeep(init, defaultInit)
|
||||||
|
const createQueryString = o => new init.URLSearchParams(o).toString()
|
||||||
|
const parseQueryString = s => fromEntries([...new init.URLSearchParams(s).entries()])
|
||||||
|
const url = new init.URL(input, init.baseURI || undefined)
|
||||||
|
if (!init.headers) {
|
||||||
|
init.headers = {}
|
||||||
|
} else if (typeis(init.Headers)(init.headers)) {
|
||||||
|
// Transform into object if it is `Headers`
|
||||||
|
init.headers = fromEntries([...init.headers.entries()])
|
||||||
|
}
|
||||||
|
// Add json or form on body
|
||||||
|
if (init.json) {
|
||||||
|
init.body = JSON.stringify(init.json)
|
||||||
|
init.headers['Content-Type'] = 'application/json'
|
||||||
|
} else if (isstrorobj(init.urlencoded)) {
|
||||||
|
init.body = isstr(init.urlencoded) ? init.urlencoded : createQueryString(init.urlencoded)
|
||||||
|
init.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||||
|
} else if (typeis(init.FormData, 'object')(init.formData)) {
|
||||||
|
// init.formData is data passed by user, init.FormData is FormData constructor
|
||||||
|
if (!typeis(init.FormData)(init.formData)) {
|
||||||
|
const fd = new init.FormData()
|
||||||
|
for (const [k, v] of Object.entries(init.formData)) {
|
||||||
|
fd.append(k, v)
|
||||||
|
}
|
||||||
|
init.formData = fd
|
||||||
|
}
|
||||||
|
init.body = init.formData
|
||||||
|
}
|
||||||
|
// Querystring
|
||||||
|
if (init.qs) {
|
||||||
|
if (isstr(init.qs)) init.qs = parseQueryString(init.qs)
|
||||||
|
url.search = createQueryString(assign(fromEntries([...url.searchParams.entries()]), init.qs))
|
||||||
|
}
|
||||||
|
return XResponsePromise.resolve(init.fetch(url, init).then(responseErrorThrower))
|
||||||
|
}
|
||||||
|
for (const method of METHODS) {
|
||||||
|
xfetch[method] = (input, init = {}) => {
|
||||||
|
init.method = method.toUpperCase()
|
||||||
|
return xfetch(input, init)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Extra methods and classes
|
||||||
|
xfetch.extend = newDefaultInit => extend(assign({}, defaultInit, newDefaultInit))
|
||||||
|
xfetch.HTTPError = HTTPError
|
||||||
|
return xfetch
|
||||||
|
}
|
||||||
|
const isWindow = typeof document !== 'undefined'
|
||||||
|
const isBrowser = typeof self !== 'undefined' // works in both window & worker scope
|
||||||
|
return isBrowser
|
||||||
|
? extend({
|
||||||
|
fetch: fetch.bind(self),
|
||||||
|
URL,
|
||||||
|
Response,
|
||||||
|
URLSearchParams,
|
||||||
|
Headers,
|
||||||
|
FormData,
|
||||||
|
baseURI: isWindow ? document.baseURI : '' // since there is no document in webworkers
|
||||||
|
})
|
||||||
|
: extend()
|
||||||
|
})()
|
||||||
|
export default xf
|
Reference in a new issue