Skip to content

Commit

Permalink
Avoid throwing an error when useFetch can't parse the `Content-Type…
Browse files Browse the repository at this point in the history
…` header of the response
  • Loading branch information
mathieudutour committed Aug 13, 2024
1 parent 0458341 commit 4c9ae47
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 67 deletions.
4 changes: 4 additions & 0 deletions docs/utils-reference/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ npm install --save @raycast/utils

## Changelog

### v1.16.4

- Avoid throwing an error when `useFetch` can't parse the `Content-Type` header of the response

### v1.16.3

- Fix an issue where `URLSearchParams` couldn't be passed as an option to `useFetch` or `useCachedPromise`, causing extensions to crash.
Expand Down
45 changes: 2 additions & 43 deletions package-lock.json

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

5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@raycast/utils",
"version": "1.16.3",
"version": "1.16.4",
"description": "Set of utilities to streamline building Raycast extensions",
"author": "Raycast Technologies Ltd.",
"homepage": "https://developers.raycast.com/utils-reference",
Expand Down Expand Up @@ -28,10 +28,8 @@
],
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"cross-fetch": "^3.1.6",
"dequal": "^2.0.3",
"media-typer": "^1.1.0",
"object-hash": "^3.0.0",
"signal-exit": "^4.0.2",
"stream-chain": "^2.2.5",
Expand All @@ -40,7 +38,6 @@
"devDependencies": {
"@raycast/api": "1.52.0",
"@types/content-type": "^1.1.6",
"@types/media-typer": "^1.1.1",
"@types/object-hash": "^3.0.4",
"@types/signal-exit": "^3.0.2",
"@types/stream-chain": "^2.0.4",
Expand Down
51 changes: 46 additions & 5 deletions src/fetch-utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import mediaTyper from "media-typer";
import contentType from "content-type";

export function isJSON(contentTypeHeader: string | null | undefined): boolean {
if (contentTypeHeader) {
const ct = contentType.parse(contentTypeHeader);
const mediaType = parseContentType(contentTypeHeader);

const mediaType = mediaTyper.parse(ct.type);
if (!mediaType) {
return false;
}

if (mediaType.subtype === "json") {
return true;
Expand All @@ -25,3 +24,45 @@ export function isJSON(contentTypeHeader: string | null | undefined): boolean {
}
return false;
}

/**
* RegExp to match type in RFC 6838 with an optional trailing `;` because some Apple APIs returns one...
*
* type-name = restricted-name
* subtype-name = restricted-name
* restricted-name = restricted-name-first *126restricted-name-chars
* restricted-name-first = ALPHA / DIGIT
* restricted-name-chars = ALPHA / DIGIT / "!" / "#" /
* "$" / "&" / "-" / "^" / "_"
* restricted-name-chars =/ "." ; Characters before first dot always
* ; specify a facet name
* restricted-name-chars =/ "+" ; Characters after last plus always
* ; specify a structured syntax suffix
* ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
* DIGIT = %x30-39 ; 0-9
*/
const MEDIA_TYPE_REGEXP = /^([A-Za-z0-9][A-Za-z0-9!#$&^_-]{0,126})\/([A-Za-z0-9][A-Za-z0-9!#$&^_.+-]{0,126});?$/;

function parseContentType(header: string) {
const headerDelimitationindex = header.indexOf(";");
const contentType = headerDelimitationindex !== -1 ? header.slice(0, headerDelimitationindex).trim() : header.trim();

const match = MEDIA_TYPE_REGEXP.exec(contentType.toLowerCase().toLowerCase());

if (!match) {
return;
}

const type = match[1];
let subtype = match[2];
let suffix;

// suffix after last +
const index = subtype.lastIndexOf("+");
if (index !== -1) {
suffix = subtype.substring(index + 1);
subtype = subtype.substring(0, index);
}

return { type, subtype, suffix };
}
10 changes: 5 additions & 5 deletions src/oauth/withAccessToken.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type WithAccessTokenParameters = {
/**
* The component (for a view/menu-bar commands) or function (for a no-view command) that is passed to withAccessToken.
*/
export type WithAccessTokenComponentOrFn<T = any> = ((params: T) => Promise<void> | void) | React.ComponentType<T>;
export type WithAccessTokenComponentOrFn<T = any, U = any> = ((params: T) => Promise<U> | U) | React.ComponentType<T>;

Check warning on line 46 in src/oauth/withAccessToken.tsx

View workflow job for this annotation

GitHub Actions / tests

Unexpected any. Specify a different type

Check warning on line 46 in src/oauth/withAccessToken.tsx

View workflow job for this annotation

GitHub Actions / tests

Unexpected any. Specify a different type

/**
* Higher-order component to wrap a given component or function and set an access token in a shared global variable.
Expand Down Expand Up @@ -72,11 +72,11 @@ export type WithAccessTokenComponentOrFn<T = any> = ((params: T) => Promise<void
* @param {string} options.personalAccessToken - An optional personal access token.
* @returns {React.ComponentType<T>} The wrapped component.
*/
export function withAccessToken<T = any>(
export function withAccessToken<T = any, U = any>(

Check warning on line 75 in src/oauth/withAccessToken.tsx

View workflow job for this annotation

GitHub Actions / tests

Unexpected any. Specify a different type

Check warning on line 75 in src/oauth/withAccessToken.tsx

View workflow job for this annotation

GitHub Actions / tests

Unexpected any. Specify a different type
options: WithAccessTokenParameters,
): <U extends WithAccessTokenComponentOrFn<T>>(
fnOrComponent: U,
) => U extends (props: T) => Promise<void> | void ? Promise<void> : React.FunctionComponent<T>;
): <V extends WithAccessTokenComponentOrFn<T, U>>(
fnOrComponent: V,
) => V extends React.ComponentType<T> ? React.FunctionComponent<T> : (props: T) => Promise<U>;
export function withAccessToken<T>(options: WithAccessTokenParameters) {
if (environment.commandMode === "no-view") {
return (fn: (props: T) => Promise<void> | (() => void)) => {
Expand Down
7 changes: 4 additions & 3 deletions src/usePromise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export function usePromise<T extends FunctionReturningPromise | FunctionReturnin
const latestOnError = useLatest(options?.onError);
const latestOnData = useLatest(options?.onData);
const latestOnWillExecute = useLatest(options?.onWillExecute);
const latestFailureToast = useLatest(options?.failureToastOptions);
const latestValue = useLatest(state.data);
const latestCallback = useRef<(...args: Parameters<T>) => Promise<UnwrapReturn<T>>>();

Expand Down Expand Up @@ -196,7 +197,7 @@ export function usePromise<T extends FunctionReturningPromise | FunctionReturnin
latestCallback.current?.(...((latestArgs.current || []) as Parameters<T>));
},
},
...options?.failureToastOptions,
...latestFailureToast.current,
});
}
}
Expand Down Expand Up @@ -266,8 +267,8 @@ export function usePromise<T extends FunctionReturningPromise | FunctionReturnin
latestCallback,
latestOnWillExecute,
paginationArgsRef,
latestFailureToast,
],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
);

latestCallback.current = callback;
Expand Down Expand Up @@ -321,7 +322,7 @@ export function usePromise<T extends FunctionReturningPromise | FunctionReturnin
paginationArgsRef.current.page += 1;
const args = (latestArgs.current || []) as Parameters<T>;
callback(...args);
}, [paginationArgsRef, latestValue, latestArgs, callback]);
}, [paginationArgsRef, latestArgs, callback]);

// revalidate when the args change
useEffect(() => {
Expand Down
8 changes: 1 addition & 7 deletions tests/package-lock.json

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

0 comments on commit 4c9ae47

Please sign in to comment.