forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import React from 'react'
2
3import * as persisted from '#/state/persisted'
4
5type StateContext = persisted.Schema['imageCdnHost']
6type SetContext = (v: persisted.Schema['imageCdnHost']) => void
7
8const stateContext = React.createContext<StateContext>(
9 persisted.defaults.imageCdnHost,
10)
11const setContext = React.createContext<SetContext>(
12 (_: persisted.Schema['imageCdnHost']) => {},
13)
14
15const DEFAULT_IMAGE_CDN_ORIGIN = normalizeOrigin(
16 persisted.defaults.imageCdnHost ?? '',
17)
18
19export function Provider({children}: React.PropsWithChildren<{}>) {
20 const [state, setState] = React.useState(persisted.get('imageCdnHost'))
21
22 const setStateWrapped = React.useCallback(
23 (imageCdnHost: persisted.Schema['imageCdnHost']) => {
24 setState(imageCdnHost)
25 persisted.write('imageCdnHost', imageCdnHost)
26 },
27 [setState],
28 )
29
30 React.useEffect(() => {
31 return persisted.onUpdate('imageCdnHost', nextImageCdnHost => {
32 setState(nextImageCdnHost)
33 })
34 }, [setStateWrapped])
35
36 return (
37 <stateContext.Provider value={state}>
38 <setContext.Provider value={setStateWrapped}>
39 {children}
40 </setContext.Provider>
41 </stateContext.Provider>
42 )
43}
44
45export function useImageCdnHost() {
46 return React.useContext(stateContext) ?? persisted.defaults.imageCdnHost!
47}
48
49export function useSetImageCdnHost() {
50 return React.useContext(setContext)
51}
52
53function normalizeOrigin(input: string) {
54 try {
55 return new URL(input).origin
56 } catch {
57 return null
58 }
59}
60
61function modifyImageCdnHost(src: string, imageCdnHost: string) {
62 try {
63 const srcUrl = new URL(src)
64 if (srcUrl.protocol !== 'https:' && srcUrl.protocol !== 'http:') {
65 return null
66 }
67 if (!srcUrl.pathname.startsWith('/img/')) {
68 return null
69 }
70
71 const cdnUrl = new URL(imageCdnHost)
72 srcUrl.protocol = cdnUrl.protocol
73 srcUrl.hostname = cdnUrl.hostname
74 srcUrl.port = cdnUrl.port
75
76 return srcUrl.toString()
77 } catch {
78 return null
79 }
80}
81
82export function maybeModifyImageCdnHost(src: string, imageCdnHost?: string) {
83 if (!imageCdnHost) {
84 return src
85 }
86
87 const nextOrigin = normalizeOrigin(imageCdnHost)
88 if (!nextOrigin || nextOrigin === DEFAULT_IMAGE_CDN_ORIGIN) {
89 return src
90 }
91
92 return modifyImageCdnHost(src, nextOrigin) ?? src
93}
94
95/**
96 * Combined image transformation pipeline: applies high-quality image format
97 * transformation first (on original CDN), then applies CDN host rewrite.
98 */
99export function applyImageTransforms(
100 src: string,
101 options: {
102 imageCdnHost?: string
103 highQualityImages?: boolean
104 },
105) {
106 // Import is deferred to avoid circular dependency
107 const {maybeModifyHighQualityImage} =
108 require('./high-quality-images') as typeof import('./high-quality-images')
109
110 // First apply quality transformation on original CDN
111 const withQuality = maybeModifyHighQualityImage(
112 src,
113 options.highQualityImages,
114 )
115 // Then apply CDN host replacement
116 return maybeModifyImageCdnHost(withQuality, options.imageCdnHost)
117}