From 2ea1ca6a0fc58aca59ff4fbc080dfeb7f739ff88 Mon Sep 17 00:00:00 2001 From: "Jules Sam. Randolph" Date: Sat, 28 Aug 2021 16:52:47 -0300 Subject: [PATCH] feat: new `provideEmbeddedHeaders` prop to pass headers to img, iframes The function takes `uri` and `tagName` parameters, and returns either `null`, nothing, or a headers object. Could also be used for video, audio... and actually any embeddable element which has a remote source attribute. --- .../src/RenderHTMLConfigProvider.tsx | 3 ++- .../__tests__/component.render-html.test.tsx | 26 ++++++++++++++++++- .../src/context/defaultSharedProps.ts | 1 + .../src/elements/useIMGElementState.ts | 18 +++++++++++-- packages/render-html/src/shared-types.ts | 23 ++++++++++++++++ 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/packages/render-html/src/RenderHTMLConfigProvider.tsx b/packages/render-html/src/RenderHTMLConfigProvider.tsx index cd6fdb839..7d2e53b10 100644 --- a/packages/render-html/src/RenderHTMLConfigProvider.tsx +++ b/packages/render-html/src/RenderHTMLConfigProvider.tsx @@ -37,7 +37,8 @@ export const renderHTMLConfigPropTypes: RenderHTMLConfigPropTypes = { defaultWebViewProps: PropTypes.object, pressableHightlightColor: PropTypes.string, customListStyleSpecs: PropTypes.object, - renderers: PropTypes.object + renderers: PropTypes.object, + provideEmbeddedHeaders: PropTypes.func }; /** diff --git a/packages/render-html/src/__tests__/component.render-html.test.tsx b/packages/render-html/src/__tests__/component.render-html.test.tsx index c1c16990e..78b31516b 100644 --- a/packages/render-html/src/__tests__/component.render-html.test.tsx +++ b/packages/render-html/src/__tests__/component.render-html.test.tsx @@ -11,7 +11,7 @@ import { defaultHTMLElementModels, HTMLContentModel } from '@native-html/transient-render-engine'; -import { StyleSheet, Text } from 'react-native'; +import { Image, StyleSheet, Text } from 'react-native'; import { useRendererProps } from '../context/RenderersPropsProvider'; import TNodeChildrenRenderer from '../TNodeChildrenRenderer'; import OLElement from '../elements/OLElement'; @@ -646,4 +646,28 @@ describe('RenderHTML', () => { }); }); }); + describe('regarding provideEmbeddedHeaders prop', () => { + it('should apply returned headers to IMG tags', async () => { + const headers = { + Authorization: 'Bearer XXX' + }; + function provideEmbeddedHeaders(uri: string, tag: string) { + expect(tag).toBe('img'); + return headers; + } + const { UNSAFE_getByType, findByTestId } = render( + ' + }} + debug={false} + contentWidth={100} + provideEmbeddedHeaders={provideEmbeddedHeaders} + /> + ); + await findByTestId('image-success'); + const image = UNSAFE_getByType(Image); + expect(image.props.source.headers).toBe(headers); + }); + }); }); diff --git a/packages/render-html/src/context/defaultSharedProps.ts b/packages/render-html/src/context/defaultSharedProps.ts index db798c1f5..f9a30118c 100644 --- a/packages/render-html/src/context/defaultSharedProps.ts +++ b/packages/render-html/src/context/defaultSharedProps.ts @@ -22,6 +22,7 @@ const defaultSharedProps: Required = { defaultViewProps: {}, enableExperimentalMarginCollapsing: false, computeEmbeddedMaxWidth: (contentWidth) => contentWidth, + provideEmbeddedHeaders: () => null, GenericPressable: undefined as any, WebView: WebViewPlaceholder, defaultWebViewProps: {}, diff --git a/packages/render-html/src/elements/useIMGElementState.ts b/packages/render-html/src/elements/useIMGElementState.ts index e467e5778..2781133d4 100644 --- a/packages/render-html/src/elements/useIMGElementState.ts +++ b/packages/render-html/src/elements/useIMGElementState.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; import { Image } from 'react-native'; import type { UseIMGElementStateProps, IMGElementState } from './img-types'; import useImageNaturalDimensions from './useImageNaturalDimensions'; @@ -6,6 +6,7 @@ import useImageConcreteDimensions from './useImageConcreteDimensions'; import defaultImageInitialDimensions from './defaultInitialImageDimensions'; import { ImageDimensions } from '../shared-types'; import { getIMGState } from './getIMGState'; +import { useSharedProps } from '../context/SharedPropsProvider'; function getImageSizeAsync({ uri, @@ -88,6 +89,19 @@ export default function useIMGElementState( } = props; const { naturalDimensions, specifiedDimensions, flatStyle, onError, error } = useFetchedNaturalDimensions(props); + const { provideEmbeddedHeaders } = useSharedProps(); + const nomalizedSource = useMemo(() => { + if (source.uri) { + const headers = provideEmbeddedHeaders(source.uri, 'img'); + if (headers) { + return { + headers, + ...source + }; + } + } + return source; + }, [provideEmbeddedHeaders, source]); const concreteDimensions = useImageConcreteDimensions({ flatStyle, naturalDimensions, @@ -104,6 +118,6 @@ export default function useIMGElementState( initialDimensions, objectFit, onError, - source + source: nomalizedSource }); } diff --git a/packages/render-html/src/shared-types.ts b/packages/render-html/src/shared-types.ts index d279e6f5d..1969a12e9 100644 --- a/packages/render-html/src/shared-types.ts +++ b/packages/render-html/src/shared-types.ts @@ -385,6 +385,29 @@ export interface RenderHTMLSharedProps { * @defaultValue rgba(38, 132, 240, 0.2) */ pressableHightlightColor?: string; + + /** + * Provide headers for specific embedded elements, such as images, iframes... + * + * @example + * + * ```tsx + * function provideEmbeddedHeaders(uri: string, tagName: string) { + * if (tagName === "img" && uri.startsWith("https://example.com")) { + * return { + * Authorization: "Bearer daem6QuaeloopheiD7Oh" + * } + * } + * + * // ... + * + * + * ``` + */ + provideEmbeddedHeaders?: ( + embeddedUri: string, + tagName: string + ) => Record | null | void; } /**