Giter Club home page Giter Club logo

xior's People

Contributors

suhaotian avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

xior's Issues

Current interceptor chains are not compatible with Axios interceptors

See: https://github.com/axios/axios?tab=readme-ov-file#multiple-interceptors

Currently with xior - if the response interceptor throws, the chain ends: https://github.com/suhaotian/xior/blob/main/src/xior.ts#L239

I think it would be ideal to follow the same logic that axios uses to aid with drop-in replacements. In the case of the xior-auth-refresh plugin, I've noticed that any subsequent response error interceptors won't run.

Remove undefined values from query/body

Looks like the behavior isn't same as axios which removes undefined values from request body and query string. This needs to be fixed for drop in replacement for axios.

Type checks failing after upgrading to version 0.5.1

Hi

First of all, great work on the package!

I've been using it, but after upgrading to version 0.5.1, all the typechecks that were previously passing started failing and not sure if this was expected behaviour and I need to migrate something or it there's bug/other issue.

I'm receiving the typescript error: Error: Unsafe argument of type 'any' assigned to a parameter of type 'Xior'. @typescript-eslint/no-unsafe-argument everywhere.

An example of what was working before was that I created an instance:

import xior from "xior";

const neonInstance = xior.create({
  baseURL: "https://console.neon.tech/api/v2",
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.NEON_API_KEY}`,
  },
});

export default neonInstance;

And then add the type as follows when performing a request:

import {
  type NeonApiProjectOperation,
} from "@/types/neonApi";
import neonInstance from "@/utils/neon/neonInstance";

const neonProjectOperation =
        await neonInstance.get<NeonApiProjectOperation>(
          `/projects/${projectId}/operations/${operation.id}`,
        );

const operationData = neonProjectOperation.data.operation;

From the typescript definitions in the project, my guess is that this should still work, but maybe I'm missing that I needed to update something else?

Named export xior cause problems in VS Code

When you type axios, VS Code auto-import like this:

import axios from 'axios';

axios.post()

However, when you type xior, VS code auto-import like this:

import { xior } from 'xior';

axios.post()

That is because xior has a named export called xior, which has higher priority than the default export. And they are different things?

import xior from 'xior'; // instance
import { xior } from 'xior'; // class

In my opinion, class should always be CapitalCase. It should be:

import xior from 'xior'; // instance
import { Xior } from 'xior'; // class

transformRequest method is missing

Hello!
First of all, thank you for the perfect fetch library, but the only thing missing for me is transformRequest method from Axios to modify per-request options.

For example, if I need to completely remove some headers from specific requests, like Content-Type.
In axios we can do it like so:

axiosInstance.post('/endpoint', { ... }, {
  transformRequest: [
    (data, headers) => {
      delete headers.common['Content-Type'];
      return JSON.stringify(data);
    },
  ],
});

Instead of creating separate clients or modifying defaults.
https://stackoverflow.com/questions/46656474/axios-remove-headers-authorization-in-1-call-only

The Modern Axios: Migrate to xior.js if you would like use fetch and why: lightweight, similar API to Axios, and plugins support

Migrate axios to xior

Guys, give xior.js a try, if have problems or questions, please feel free to create issue.

xior doesn't support proxy yet, if you need proxy feature, keep use axios

According:

axios/axios#1219

FC-InnerCircle/fe1-library#11

axios/axios#4965

axios/axios#5145

axios/axios#6008

axios/axios#5523

vercel/next.js#43120

openai/openai-node#30

openai/openai-node#30

slackapi/node-slack-sdk#1335

contentful/contentful.js#395

fzsirius/api_project#3

cboard-org/cboard-ai-engine#2

M4-Park-Checklist/m4-parks-frontend#6

AlexandreBellas/bling-erp-api-js#23

zerodevapp/sdk#81

taraswww777/taraswww777.dev#58

incyclist/desktop#13

axios/axios#4793

FlipsideCrypto/sdk#32

strapi/community-content#1269

alchemyplatform/alchemy-sdk-js#398

nasa8x/rss-to-json#65

bancorprotocol/carbon-app#1040

vespaiach/axios-fetch-adapter#31

vespaiach/axios-fetch-adapter#18

vespaiach/axios-fetch-adapter#15

vespaiach/axios-fetch-adapter#2

developit/redaxios#11

developit/redaxios#19

haverstack/axios-fetch-adapter#7

valantic/vue-template#323

Uniswap/smart-order-router#434

axios/axios#6262

mailerlite/mailerlite-nodejs#31

medusajs/medusa#6133

remix-run/remix#7094

kuitos/axios-extensions#75

axios/axios#6283

Azure/static-web-apps#1254

appwrite/sdk-for-node#47

kabuto-sh/ns#2

ge-tracker/ge-tracker-api#7

ugho49/wishlist#74

gloddy-dev/gloddy-client#551

ThalaLabs/thala-sdk#6

openfga/js-sdk#18

3forges/poc-preact-rtk-flowbite#9

axios/axios#4935

vespaiach/axios-fetch-adapter#20

vespaiach/axios-fetch-adapter#27

ory/sdk#289 (comment)

axios/axios#6272

developit/redaxios#88

remix-run/remix#8940

axios/axios#5380

remix-run/remix#1318

axios/axios#2968

aptos-labs/aptos-core#11936

stamp-labs/stamp#206

missuo/FreeGPT35#43

Magatte805/ProjetWeb#1

ghostdevv/jellycommands#192

EnesAkkal/SWE-573-Ismail-Enes-Akkal#12

SetuHQ/upi-deeplinks-node-sdk#25

vespaiach/axios-fetch-adapter#31

vespaiach/axios-fetch-adapter#27

googleapis/gaxios#602

googleapis/gaxios#490

googleapis/gaxios#429

googleapis/gaxios#446

GwaoeTeukgang/jy-dev-blog#29

getlarge/nestjs-ory-integration#40

twilio/twilio-node#898

cqframework/cql-tests-runner#10

kookmin-sw/capstone-2024-04#114

axios/axios#5563

balzack/databag#88

balzack/databag#95

lifeomic/axios-fetch#129

sindresorhus/ky#569

lifeomic/axios-fetch#83

axios/axios#6384

ftmh-frydrs/Apple-Tv#1

syonosuke743/basic-vision-builder#10

topi0247/Project-AStoryer#123

vespaiach/axios-fetch-adapter#29

lifeomic/axios-fetch#84

NOW-SOPT-CDSP-8/Spotify-FE#3

softonic/axios-retry#267

softonic/axios-retry#262

axios/axios#6372

haverstack/axios-fetch-adapter#7

axios/axios#6390

lifeomic/axios-fetch#81

oven-sh/bun#8996

oven-sh/bun#9264

oven-sh/bun#5013

oven-sh/bun#4871

oven-sh/bun#2673

fac31/mohammed-anxhela-discord-bot#37

ArielleWaks/fetch-a-friend-frontend#14

OpenFn/adaptors#549

LucaVas/recruiter#11

axios/axios#6371

ashishbhintade/cntsc#2

GSA/Challenge_gov#1309

mammalai/mvp#30

axios/axios#6475

hankliu62/interview#917

svoi-fr/mirai#78

OHCRN/platform#702

ethanent/phin#95

ethanent/phin#94

axios/axios#6464

facebook/react-native#38025

axios/axios#6462

axios/axios#6445

developit/redaxios#86

developit/redaxios#52

vercel/next.js#41531

FC-InnerCircle/fe1-library#10

thecorecity/cognitum#196

adobe/aio-lib-console-project-installation#29

unjs/ofetch#425

unjs/ofetch#406

unjs/ofetch#355

edlink/edlink-node-sdk#28

Error handling

A fetch() promise will reject with a TypeError when a network error is encountered or CORS is misconfigured on the server-side, other situations are not. is this library the same?

Add https.agent to xior.create() and interceptors

Hi there. I'm giving xior a try on Next.js 14, but the issue is I need to send client certificates (certificate.crt, certificate.key) in all requests along with access_token acquired during authentication.

I've messed around with axios on Next.js API Routes where I have access to node and I got something along the following lines working.

I'm setting up the certificates with axios as bellow:

/lib/banking-api.ts

import https from 'https'
import fs from 'fs'
import axios from 'axios'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/auth'

const baseURL = process.env.BANKING_API_BASE_URL

const certFile = fs.readFileSync('certificates/api-cert.crt')
const keyFile = fs.readFileSync('certificates/api-cert.key')

export const httpsAgent = new https.Agent({
  cert: certFile,
  key: keyFile,
  //passphrase: ''
})

Here I finish axios configuration such as passing token to headers config...

export const httpClient = axios.create({
  baseURL: baseURL,
  headers: {
    'x-account': process.env.BANKING_API_ACCOUNT,
    withCredentials: true
  },
  httpsAgent: httpsAgent
})

httpClient.interceptors.request.use(
  async config => {
    const session = await getServerSession(authOptions)
    const token = session?.user?.access_token
    config.headers.Authorization = token ? `Bearer ${token}` : ''
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

const api = httpClient

export default api

Within API routes I've tried to use Next.js 14 fetch() but it seems not to support the https.agent configuration so I couldn't set up the certificates. I've tried something along the lines bellow:

import { httpsAgent } from '@/lib/banking-api'
    
const result = await fetch('https://api.banking.com', {
    (...)
    agent: httpsAgent
})

Bellow is an API route that successfuly works as expected, but given the way I've set up axios as httpClient under /lib/banking-api.ts, a few issues arrise as I explain ahead.

/api/banking/balance/route.ts

'use server'
import type { NextApiHandler } from 'next'
import { NextApiRequest, NextApiResponse } from 'next'
import { NextResponse } from 'next/server'
import { httpClient } from '@/lib/banking-api'

const dateSimple = () => new Date().toISOString().slice(0, 10) // 2024-04-19

export const GET: NextApiHandler = async (request: NextApiRequest, response: NextApiResponse) => {
  try {
    // calling external api service with custom axios config
    const response = await httpClient.get('https://api.banking.com/v2/balance', {
      params: {
        date: dateSimple()
      }
    })

    const data = response.data

    return NextResponse.json(data, { status: 200 })
  } catch (error) {
    return NextResponse.json(error, { status: 500 })
  }
})

On client components or pages explicity setting 'use client' directive, if I make a request to https://localhost:3000/api/banking/balance using Next.js fetch() I successfully get a response data from the API route above. I then use React.useState() to manage the data for the component or page...

However, If I try to import the custom axios httpClient and use it to make requests within client components, 'fs' and 'https' modules won't load giving "module not found" error as long as they're used server side only.

On server components or pages if I make a request to https://localhost:3000/api/banking/balance using Next.js fetch() I get a 401 unauthorized response as long as fetch() is not sending the client certificates in the request.

[Bug] Date objects not supported in query parameters

Let's consider the following code:

import xior, { type XiorResponse, type XiorRequestConfig } from 'xior';

const filter = {
  ids: [1, 2, 3],
  dates: [new Date(), new Date()],
  dateFrom: new Date(),
  dateTo: new Date(),
};

http.request<FilterTestResponse>({
    url: url,
    method: 'GET',
    params: {
      filter: filter,
    },
    ...$config,
});

If I execute it in a browser then only ids property will be serialized and present in the query, but dates, dateFrom and dateTo won't be there because dates are objects and they will be handled by encodeParams through a if (typeof value === 'object') branch which is not expected.

The fix I produced for my ASPNET custom serialized looks like this and it's working fine with both cases:

function encodeParams<T = any>(
  params: T,
  encodeURI = true,
  parentKey: string | null = null
): string {
  if (params === undefined || params === null) return '';
  const encodedParams: string[] = [];
  const encodeURIFunc = encodeURI ? encodeURIComponent : (v: string) => v;
  const encodeValue = (value: any) =>
    encodeURIFunc(value instanceof Date && !Number.isNaN(value) ? value.toISOString() : value);

  for (const key in params) {
    if (Object.prototype.hasOwnProperty.call(params, key)) {
      const value = (params as any)[key];
      if (value !== undefined) {
        const encodedKey = parentKey ? `${parentKey}.${encodeURIFunc(key)}` : encodeURIFunc(key);

        if (Array.isArray(value)) {
          // If the value is an array, encode each element individually
          value.forEach((element) => {
            encodedParams.push(`${encodeURIFunc(encodedKey)}=${encodeValue(element)}`);
          });
        } else if (value instanceof Date && !Number.isNaN(value)) {
          // If the value is a Date, convert it to ISO format
          encodedParams.push(`${encodeURIFunc(encodedKey)}=${encodeValue(value)}`);
        } else if (typeof value === 'object') {
          // If the value is an object or array, recursively encode its contents
          const result = encodeParams(value, encodeURI, encodedKey);
          if (result !== '') encodedParams.push(result);
        } else {
          // Otherwise, encode the key-value pair
          encodedParams.push(`${encodeURIFunc(encodedKey)}=${encodeValue(value)}`);
        }
      }
    }
  }

  return encodedParams.join('&');
}

export const http = xior.create({
  baseURL: '',
  paramsSerializer: encodeParams,
});

But please note to not use this code, because it already has support for different approach for query params serialization (i.e. filter.ids=1&filter.ids=2&... as opposed to your previous mechanism filter[ids][0]=1&filter[ids][1]=2&...

withCredentials is not working

withCredentials is xhr option, so axios has:

axios.defaults.withCredentials = true;

However, xior uses fetch, fetch has different option credentials for the same functionality:

xior.defaults.credentials = 'include';

This makes xior ignoring withCredentials.

`undefined` in response.body when responseType is 'blob' or 'arraybuffer'

When I set the responseType: 'blob' for a request, the response.body is undefined, similar to the following axios issue
axios/axios#1392

Example something like this

xior.post(
  'http://assets.xxxxx.org:3000/uploads/3ba6921c-031e-426b-94a3-a4b966fc145f/documents/0f6a1d1166.pdf',
{
  headers: {
    Accept: 'application/pdf',
  },
  responseType: 'blob',
},
)
.then(response => {
  console.log(response.data); //undefined
})

Encoding query parameters question

I was wondering why arrays are encoded in a way that my backend server does not understood them and I stumbled upon code for handling the params:

https://github.com/suhaotian/xior/blob/3a235b8babd0e6ecdb9b3eaa68e14ef702bf862d/src/utils.ts#L23C11-L23C12

        if (typeof value === 'object') {
          // If the value is an object or array, recursively encode its contents
          const result = encodeParams(value, encodeURI, encodedKey);
          if (result !== '') encodedParams.push(result);
        } else if (Array.isArray(value)) { // This will never be triggered because arrays are objects and they are handled in previous `if` statement
          // If the value is an array, encode each element individually
          value.forEach((element, index) => {
            const arrayKey = `${encodedKey}[${index}]`;
            encodedParams.push(`${encodeURIFunc(arrayKey)}=${encodeURIFunc(element)}`);
          });
        } else { ... }

Shouldn't be if (Array.isArray(value)) be placed before check for if (typeof value === 'object')? Or maybe this check can be removed altogether?

Either way it won't particularly help me with my case (ASPNET uses totally different approach at encoding arrays...), but I just found it and I was intrigued by it.

Response interceptor is not working if you define a custom plugin

In the following example, I added a request interceptor, a response interceptor and a plugin. However, the response interceptor is not working as long as here is the plugin. Remove the plugin and the response interceptor is working again. Request interceptor is not affected.

import xior from "xior";

xior.interceptors.request.use((request) => {
  document.getElementById("request").textContent =
    "Request Interceptor Is Working!";
  return request;
});

xior.interceptors.response.use((response) => {
  document.getElementById("response").textContent =
    "Response Interceptor Is Working!";
  return response;
});

xior.plugins.use((adaptor) => async (request) => ({
  status: 200,
  statusText: "OK",
  data: {
    foo: "bar",
  },
}));

xior.get("/");

https://codesandbox.io/p/sandbox/xior-plugin-interceptor-24xfhk

credentials not working with POST method

I config xior with:

xior.defaults.credentials = 'include';

CORS GET requests works fine (copy request as fetch):

fetch('xxxx', {
  headers: {
    accept: '*/*',
    'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
    'cache-control': 'no-cache',
    pragma: 'no-cache',
    priority: 'u=1, i',
    'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"macOS"',
    'sec-fetch-dest': 'empty',
    'sec-fetch-mode': 'cors',
    'sec-fetch-site': 'same-site',
  },
  referrer: 'xxx',
  referrerPolicy: 'strict-origin-when-cross-origin',
  body: null,
  method: 'GET',
  mode: 'cors',
  credentials: 'include',
});

However, CORS POST request doesn't work (copy request as fetch):

fetch('xxxx', {
  headers: {
    'content-type': 'application/json',
    'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"macOS"',
  },
  referrer: 'xxx',
  referrerPolicy: 'strict-origin-when-cross-origin',
  body: '{}',
  method: 'POST',
  mode: 'cors',
  credentials: 'omit',
});

POST with URI encoded data instead of JSON

There are some really old back-end systems that don't support JSON API. Instead, they support encoded URI data as body.

axios supports a usage like this:

axios.post('/foobar', new URLSearchParams({
  foo: 'bar',
}));

axios will automatically set content-type header to application/x-www-form-urlencoded;charset=UTF-8;

However, xior doesn't seem to support this. When I write:

xior.post('/foobar', new URLSearchParams({
  foo: 'bar',
}));

xior will send a request with no content-type header, and back-end will reject it.

I can fix my code by manually adding the missing header. But maybe it makes sense that xior can do this automatically for users.

Auth Refresh

Hi,

First off, thank you for doing this brilliant port - we were stuck migrating to NextJS app router (SSR bits) because of our dependency on Axios. We generate our strongly typed axios clients via swagger-typescript-api. Although this could generate fetch clients, they would require substantial work to function with our existing stack which is axios-centric. We have converted generator that to generate xior clients via templating, so the last bit for us was making sure all our existing token refresh/login/logout stuff still worked.

Anyway, I digress, but long story short, your method for refreshing auth tokens we found didn't suit our use case, even with throttling and deduping, we would get a lot of refresh calls, and in addition, the failed requests (although they would retry) would show loads of errors while failing before retrying.

There is this brilliant library - https://github.com/Flyrell/axios-auth-refresh - which handles pausing the request pipeline, queuing them up, refreshing the token, and then letting the queue run afterwards which solves these problems.

I did a messy monkey patch to convert it to work with xior as a proof of concept, and so far it has slotted right in as an alternative and works (so far) exactly the way we found the axios version of the library to work for us.

Anyway, I just wanted to give you a heads up: https://github.com/Audiu/xior-auth-refresh

Once again, thank you so much for this library!

Memory Leak and Performance Issue Due to Uncleared Interceptors in xior

Problem

Each time an interceptor is added via xior.interceptors.request.use and xior.interceptors.response.use, it is pushed to an array and remains there indefinitely. There is no automatic cleanup mechanism for these interceptors, leading to a potential memory leak and performance issue when making multiple requests that add interceptors dynamically.

Example code snippet:

const apiClient = xior.create({
  baseURL: "https://api.restful-api.dev/objects?id=3&id=5&id=10",
});

document.getElementById("btn").addEventListener("click", async (e) => {
  await getData();
  console.log(xior); // Here you can see how many interceptors are inside of both REQI and RESI arrays.
});

async function getData() {
  xior.interceptors.request.use((config) => {
    return config;
  });
  xior.interceptors.response.use((res) => {
    return res;
  });
  const data = await xior.get("/");
  return data;
}

Interceptors accumulate in the interceptor arrays (REQI and RESI) and are not cleaned up, leading to increased memory usage and potential performance impacts.

Workaround Solution

I found that by manually ejecting the interceptors after their use within a request, the issue with interceptor accumulation can be temporarily resolved. Here's the code snippet:

const apiClient = xior.create({
  baseUrl: "https://api.restful-api.dev/objects?id=3&id=5&id=10",
});

document.getElementById("btn").addEventListener("click", async (e) => {
  await getData();
  console.log(xior);
});

async function getData() {
  // Adding interceptors
  const f1 = xior.interceptors.request.use((config) => {
    return config;
  });
  const f2 = xior.interceptors.response.use((res) => {
    return res;
  });

  // Performing the request
  const data = await xior.get("/");

  // Ejecting interceptors after their use
  xior.interceptors.request.eject(f1);
  xior.interceptors.response.eject(f2);

  return data;
}

Proposed Solution

  • Automatic Cleanup: Implement an automatic cleanup mechanism for interceptors that are no longer needed or used.
  • Improved Documentation: Enhance documentation to guide users on proper management of interceptors, including the necessity and usage of the eject function to prevent memory leaks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.