Compare commits

...

30 Commits

Author SHA1 Message Date
Eric Bailey 008cdf8d27 Bunch of UI 2024-10-31 10:31:59 -05:00
Eric Bailey f6e8b2f2f0 Exploring downgrades 2024-10-31 10:31:45 -05:00
Eric Bailey 3713ad399f Fix logic 2024-10-30 09:46:39 -05:00
Eric Bailey 987b073b79 Merge subscriptions 2024-10-29 21:47:51 -05:00
Eric Bailey 815709ceab Add envs 2024-10-29 19:43:19 -05:00
Eric Bailey 4b36e625bb Add support for ios products on server 2024-10-29 19:43:19 -05:00
Eric Bailey 1d8fed03bc Set up ios 2024-10-29 19:43:19 -05:00
Eric Bailey 05284a6584 Refactor for new API contract 2024-10-29 19:43:19 -05:00
Eric Bailey 3a8d1e229d Surface interval at a top level 2024-10-29 19:43:19 -05:00
Eric Bailey 8743a49c51 Clean up schema and improve data correctness 2024-10-29 19:43:19 -05:00
Eric Bailey e76fd17e34 Add token to workflow 2024-10-29 19:43:19 -05:00
Eric Bailey a62962ea5e Improve /subscriptions/:offering and /entitlements endpoints 2024-10-29 19:43:19 -05:00
Eric Bailey e9c6a8461c Bunch of contract updates 2024-10-29 19:43:19 -05:00
Eric Bailey 6d0c5078e9 Fix sub selection 2024-10-29 19:43:19 -05:00
Eric Bailey 51c29ba36a Add renewal status 2024-10-29 19:43:19 -05:00
Eric Bailey dcf1e774d2 Add active subscriptions to response 2024-10-29 19:43:19 -05:00
Eric Bailey 898503a554 Add internal links to subs screen 2024-10-29 19:43:19 -05:00
Eric Bailey 67eaf1baa7 Add route and basic UI 2024-10-29 19:43:19 -05:00
Eric Bailey 009e238c30 Identify purchases user in session reducer 2024-10-29 19:43:19 -05:00
Eric Bailey 5ac8c3d653 Add entitlements endpoint 2024-10-29 19:43:19 -05:00
Eric Bailey 89978efda3 Add comments 2024-10-29 19:43:19 -05:00
Eric Bailey ce76e81b84 Add toy server 2024-10-29 19:43:19 -05:00
Eric Bailey 1c913cad2d Clarify intent 2024-10-29 19:43:19 -05:00
Eric Bailey 4cfdd287e2 Clean up subs hooks and state 2024-10-29 19:43:19 -05:00
Eric Bailey 71524bb17b Update stripe product ids 2024-10-29 19:43:19 -05:00
Eric Bailey bf7dbc342d Add purchase linking 2024-10-29 19:43:19 -05:00
Eric Bailey f4beacb744 Set up other products, web done 2024-10-29 19:43:19 -05:00
Eric Bailey a6985a28ea Init state, Android and web 2024-10-29 19:43:19 -05:00
Eric Bailey cc1cda8bf6 Add Android billing perms 2024-10-29 19:43:19 -05:00
Eric Bailey d9ad3c2956 Install 2024-10-29 19:43:17 -05:00
42 changed files with 3911 additions and 7 deletions
@@ -64,6 +64,9 @@ jobs:
echo "${{ secrets.ENV_TOKEN }}" > .env
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
echo "EXPO_PUBLIC_RC_GOOGLE_PUBLIC_KEY=${{ secrets.EXPO_PUBLIC_RC_GOOGLE_PUBLIC_KEY }}" >> .env
echo "EXPO_PUBLIC_RC_APPLE_PUBLIC_KEY=${{ secrets.EXPO_PUBLIC_RC_APPLE_PUBLIC_KEY }}" >> .env
echo "EXPO_PUBLIC_BSKY_PURCHASES_API=${{ secrets.EXPO_PUBLIC_BSKY_PURCHASES_API }}" >> .env
echo "$json" > google-services.json
- name: 🏗️ EAS Build
+3
View File
@@ -76,6 +76,9 @@ jobs:
echo "${{ secrets.ENV_TOKEN }}" > .env
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
echo "EXPO_PUBLIC_RC_GOOGLE_PUBLIC_KEY=${{ secrets.EXPO_PUBLIC_RC_GOOGLE_PUBLIC_KEY }}" >> .env
echo "EXPO_PUBLIC_RC_APPLE_PUBLIC_KEY=${{ secrets.EXPO_PUBLIC_RC_APPLE_PUBLIC_KEY }}" >> .env
echo "EXPO_PUBLIC_BSKY_PURCHASES_API=${{ secrets.EXPO_PUBLIC_BSKY_PURCHASES_API }}" >> .env
echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json
- name: 🏗️ EAS Build
@@ -101,6 +101,9 @@ jobs:
echo "${{ secrets.ENV_TOKEN }}" > .env
echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse --short HEAD)" >> .env
echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env
echo "EXPO_PUBLIC_RC_GOOGLE_PUBLIC_KEY=${{ secrets.EXPO_PUBLIC_RC_GOOGLE_PUBLIC_KEY }}" >> .env
echo "EXPO_PUBLIC_RC_APPLE_PUBLIC_KEY=${{ secrets.EXPO_PUBLIC_RC_APPLE_PUBLIC_KEY }}" >> .env
echo "EXPO_PUBLIC_BSKY_PURCHASES_API=${{ secrets.EXPO_PUBLIC_BSKY_PURCHASES_API }}" >> .env
echo "$json" > google-services.json
- name: 🏗️ Create Bundle
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M6 4.667A2.667 2.667 0 0 1 8.667 2c1.34 0 2.538.608 3.333 1.564A4.32 4.32 0 0 1 15.333 2 2.667 2.667 0 0 1 18 4.667c0 .859-.25 1.66-.681 2.333H20a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-7V7h.667A2.333 2.333 0 0 0 16 4.667.667.667 0 0 0 15.333 4 2.333 2.333 0 0 0 13 6.333V7h-2v-.667A2.333 2.333 0 0 0 8.667 4 .667.667 0 0 0 8 4.667 2.333 2.333 0 0 0 10.333 7H11v4H4a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h2.681A4.3 4.3 0 0 1 6 4.667ZM11 13H4v7a1 1 0 0 0 1 1h6v-8Zm9 0h-7v8h6a1 1 0 0 0 1-1v-7Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 617 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M6 4.667A2.667 2.667 0 0 1 8.667 2c1.34 0 2.538.608 3.333 1.564A4.32 4.32 0 0 1 15.333 2 2.667 2.667 0 0 1 18 4.667c0 .859-.25 1.66-.681 2.333H20a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-7a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h2.681A4.3 4.3 0 0 1 6 4.667ZM10.333 7H11v-.667A2.333 2.333 0 0 0 8.667 4 .667.667 0 0 0 8 4.667 2.333 2.333 0 0 0 10.333 7ZM13 6.333V7h.667A2.333 2.333 0 0 0 16 4.667.667.667 0 0 0 15.333 4 2.333 2.333 0 0 0 13 6.333ZM11 9H5v2h6V9Zm2 2V9h6v2h-6Zm-2 2H6v6h5v-6Zm2 6v-6h5v6h-5Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 659 B

+3
View File
@@ -294,6 +294,9 @@ func serve(cctx *cli.Context) error {
e.GET("/starter-pack-short/:code", server.WebGeneric)
e.GET("/start/:handleOrDID/:rkey", server.WebStarterPack)
// subscription
e.GET("/subscriptions", server.WebGeneric)
// ipcc
e.GET("/ipcc", server.WebIpCC)
+1
View File
@@ -181,6 +181,7 @@
"react-native-pager-view": "6.2.3",
"react-native-picker-select": "^9.1.3",
"react-native-progress": "bluesky-social/react-native-progress",
"react-native-purchases": "^8.2.3",
"react-native-qrcode-styled": "^0.3.1",
"react-native-reanimated": "^3.11.0",
"react-native-root-siblings": "^4.1.1",
+10
View File
@@ -6,6 +6,16 @@ module.exports = function withAndroidManifestPlugin(appConfig) {
decoratedAppConfig.modResults.manifest.application[0].$[
'android:largeHeap'
] = 'true'
decoratedAppConfig.modResults.manifest['uses-permission'] =
decoratedAppConfig.modResults.manifest['uses-permission'] || []
/**
* @see https://www.revenuecat.com/docs/getting-started/installation/reactnative#import-purchases
*/
decoratedAppConfig.modResults.manifest['uses-permission'].push({
$: {
'android:name': 'com.android.vending.BILLING',
},
})
} catch (e) {
console.error(`withAndroidManifestPlugin failed`, e)
}
+11
View File
@@ -5,6 +5,7 @@ import '#/view/icons'
import React, {useEffect, useState} from 'react'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
import {KeyboardProvider} from 'react-native-keyboard-controller'
import Purchases from 'react-native-purchases'
import {RootSiblingParent} from 'react-native-root-siblings'
import {
initialWindowMetrics,
@@ -24,6 +25,7 @@ import {s} from '#/lib/styles'
import {ThemeProvider} from '#/lib/ThemeContext'
import I18nProvider from '#/locale/i18nProvider'
import {logger} from '#/logger'
import {isAndroid, isIOS} from '#/platform/detection'
import {Provider as A11yProvider} from '#/state/a11y'
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
import {Provider as DialogStateProvider} from '#/state/dialogs'
@@ -66,6 +68,7 @@ import {NuxDialogs} from '#/components/dialogs/nuxs'
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PortalProvider} from '#/components/Portal'
import {RC_APPLE_PUBLIC_KEY,RC_GOOGLE_PUBLIC_KEY} from '#/env'
import {Splash} from '#/Splash'
import {BottomSheetProvider} from '../modules/bottom-sheet'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
@@ -173,6 +176,14 @@ function App() {
Promise.all([initPersistedState(), ensureGeolocationResolved()]).then(() =>
setReady(true),
)
Purchases.setLogLevel(Purchases.LOG_LEVEL.DEBUG)
if (isIOS) {
Purchases.configure({apiKey: RC_APPLE_PUBLIC_KEY})
} else if (isAndroid) {
Purchases.configure({apiKey: RC_GOOGLE_PUBLIC_KEY})
}
}, [])
if (!isReady) {
+6
View File
@@ -92,6 +92,7 @@ import {
StarterPackScreenShort,
} from '#/screens/StarterPack/StarterPackScreen'
import {Wizard} from '#/screens/StarterPack/Wizard'
import {Subscriptions} from '#/screens/Subscriptions'
import {useTheme} from '#/alf'
import {router} from '#/routes'
import {Referrer} from '../modules/expo-bluesky-swiss-army'
@@ -407,6 +408,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => Wizard}
options={{title: title(msg`Edit your starter pack`), requireAuth: true}}
/>
<Stack.Screen
name="Subscriptions"
getComponent={() => Subscriptions}
options={{title: title(msg`Subscriptions`), requireAuth: true}}
/>
</>
)
}
+7 -7
View File
@@ -41,7 +41,7 @@ const GroupContext = React.createContext<{
disabled: boolean
type: 'radio' | 'checkbox'
maxSelectionsReached: boolean
setFieldValue: (props: {name: string; value: boolean}) => void
setFieldValue: (props: {name: any; value: boolean}) => void
}>({
type: 'checkbox',
values: [],
@@ -50,12 +50,12 @@ const GroupContext = React.createContext<{
setFieldValue: () => {},
})
export type GroupProps = React.PropsWithChildren<{
export type GroupProps<V> = React.PropsWithChildren<{
type?: 'radio' | 'checkbox'
values: string[]
values: V[]
maxSelections?: number
disabled?: boolean
onChange: (value: string[]) => void
onChange: (value: V[]) => void
label: string
}>
@@ -74,7 +74,7 @@ export function useItemContext() {
return React.useContext(ItemContext)
}
export function Group({
export function Group<V extends string>({
children,
values: providedValues,
onChange,
@@ -82,13 +82,13 @@ export function Group({
type = 'checkbox',
maxSelections,
label,
}: GroupProps) {
}: GroupProps<V>) {
const groupRole = type === 'radio' ? 'radiogroup' : undefined
const values = type === 'radio' ? providedValues.slice(0, 1) : providedValues
const [maxReached, setMaxReached] = React.useState(false)
const setFieldValue = React.useCallback<
(props: {name: string; value: boolean}) => void
(props: {name: V; value: boolean}) => void
>(
({name, value}) => {
if (type === 'checkbox') {
+9
View File
@@ -0,0 +1,9 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Gift1_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M6 4.667A2.667 2.667 0 0 1 8.667 2c1.34 0 2.538.608 3.333 1.564A4.324 4.324 0 0 1 15.333 2 2.667 2.667 0 0 1 18 4.667c0 .859-.25 1.66-.681 2.333H20a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-7a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h2.681A4.313 4.313 0 0 1 6 4.667ZM10.333 7H11v-.667A2.333 2.333 0 0 0 8.667 4 .667.667 0 0 0 8 4.667 2.333 2.333 0 0 0 10.333 7ZM13 6.333V7h.667A2.333 2.333 0 0 0 16 4.667.667.667 0 0 0 15.333 4 2.333 2.333 0 0 0 13 6.333ZM11 9H5v2h6V9Zm2 2V9h6v2h-6Zm-2 2H6v6h5v-6Zm2 6v-6h5v6h-5Z',
})
export const Gift1_Filled_Corner0_Rounded = createSinglePathSVG({
path: 'M6 4.667A2.667 2.667 0 0 1 8.667 2c1.34 0 2.538.608 3.333 1.564A4.324 4.324 0 0 1 15.333 2 2.667 2.667 0 0 1 18 4.667c0 .859-.25 1.66-.681 2.333H20a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-7V7h.667A2.333 2.333 0 0 0 16 4.667.667.667 0 0 0 15.333 4 2.333 2.333 0 0 0 13 6.333V7h-2v-.667A2.333 2.333 0 0 0 8.667 4 .667.667 0 0 0 8 4.667 2.333 2.333 0 0 0 10.333 7H11v4H4a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h2.681A4.313 4.313 0 0 1 6 4.667ZM11 13H4v7a1 1 0 0 0 1 1h6v-8Zm9 0h-7v8h6a1 1 0 0 0 1-1v-7Z',
})
@@ -0,0 +1,21 @@
import React from 'react'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {TierId} from '#/state/purchases/subscriptions/types'
export function useMainSubscriptionTiersCopy() {
const {_} = useLingui()
return {
[TierId.Main0]: {
title: _(msg`Bluesky Supporter`),
},
[TierId.Main1]: {
title: _(msg`Bluesky Friend`),
},
[TierId.Main2]: {
title: _(msg`Bluesky Besty`),
},
}
}
+7
View File
@@ -7,3 +7,10 @@ export const LOG_LEVEL = (process.env.EXPO_PUBLIC_LOG_LEVEL || 'info') as
| 'info'
| 'warn'
| 'error'
export const RC_GOOGLE_PUBLIC_KEY =
process.env.EXPO_PUBLIC_RC_GOOGLE_PUBLIC_KEY || ''
export const RC_APPLE_PUBLIC_KEY =
process.env.EXPO_PUBLIC_RC_APPLE_PUBLIC_KEY || ''
export const BSKY_PURCHASES_API =
process.env.EXPO_PUBLIC_BSKY_PURCHASES_API || ''
+302
View File
@@ -0,0 +1,302 @@
import React from 'react'
import {deviceLocales} from '#/locale/deviceLocales'
import {useGeolocation} from '#/state/geolocation'
import {useLanguagePrefs} from '#/state/preferences'
/**
* From react-native-localize
*
* MIT License
* Copyright (c) 2017-present, Mathieu Acthernoene
*
* @see https://github.com/zoontek/react-native-localize/blob/master/LICENSE
* @see https://github.com/zoontek/react-native-localize/blob/ee5bf25e0bb8f3b8e4f3fd055f67ad46269c81ea/src/constants.ts
*/
export const countryCodeToCurrency: Record<string, string> = {
ad: 'eur',
ae: 'aed',
af: 'afn',
ag: 'xcd',
ai: 'xcd',
al: 'all',
am: 'amd',
an: 'ang',
ao: 'aoa',
ar: 'ars',
as: 'usd',
at: 'eur',
au: 'aud',
aw: 'awg',
ax: 'eur',
az: 'azn',
ba: 'bam',
bb: 'bbd',
bd: 'bdt',
be: 'eur',
bf: 'xof',
bg: 'bgn',
bh: 'bhd',
bi: 'bif',
bj: 'xof',
bl: 'eur',
bm: 'bmd',
bn: 'bnd',
bo: 'bob',
bq: 'usd',
br: 'brl',
bs: 'bsd',
bt: 'btn',
bv: 'nok',
bw: 'bwp',
by: 'byn',
bz: 'bzd',
ca: 'cad',
cc: 'aud',
cd: 'cdf',
cf: 'xaf',
cg: 'xaf',
ch: 'chf',
ci: 'xof',
ck: 'nzd',
cl: 'clp',
cm: 'xaf',
cn: 'cny',
co: 'cop',
cr: 'crc',
cu: 'cup',
cv: 'cve',
cw: 'ang',
cx: 'aud',
cy: 'eur',
cz: 'czk',
de: 'eur',
dj: 'djf',
dk: 'dkk',
dm: 'xcd',
do: 'dop',
dz: 'dzd',
ec: 'usd',
ee: 'eur',
eg: 'egp',
eh: 'mad',
er: 'ern',
es: 'eur',
et: 'etb',
fi: 'eur',
fj: 'fjd',
fk: 'fkp',
fm: 'usd',
fo: 'dkk',
fr: 'eur',
ga: 'xaf',
gb: 'gbp',
gd: 'xcd',
ge: 'gel',
gf: 'eur',
gg: 'gbp',
gh: 'ghs',
gi: 'gip',
gl: 'dkk',
gm: 'gmd',
gn: 'gnf',
gp: 'eur',
gq: 'xaf',
gr: 'eur',
gs: 'gbp',
gt: 'gtq',
gu: 'usd',
gw: 'xof',
gy: 'gyd',
hk: 'hkd',
hm: 'aud',
hn: 'hnl',
hr: 'hrk',
ht: 'htg',
hu: 'huf',
id: 'idr',
ie: 'eur',
il: 'ils',
im: 'gbp',
in: 'inr',
io: 'usd',
iq: 'iqd',
ir: 'irr',
is: 'isk',
it: 'eur',
je: 'gbp',
jm: 'jmd',
jo: 'jod',
jp: 'jpy',
ke: 'kes',
kg: 'kgs',
kh: 'khr',
ki: 'aud',
km: 'kmf',
kn: 'xcd',
kp: 'kpw',
kr: 'krw',
kw: 'kwd',
ky: 'kyd',
kz: 'kzt',
la: 'lak',
lb: 'lbp',
lc: 'xcd',
li: 'chf',
lk: 'lkr',
lr: 'lrd',
ls: 'lsl',
lt: 'eur',
lu: 'eur',
lv: 'eur',
ly: 'lyd',
ma: 'mad',
mc: 'eur',
md: 'mdl',
me: 'eur',
mf: 'eur',
mg: 'mga',
mh: 'usd',
mk: 'mkd',
ml: 'xof',
mm: 'mmk',
mn: 'mnt',
mo: 'mop',
mp: 'usd',
mq: 'eur',
mr: 'mro',
ms: 'xcd',
mt: 'eur',
mu: 'mur',
mv: 'mvr',
mw: 'mwk',
mx: 'mxn',
my: 'myr',
mz: 'mzn',
na: 'nad',
nc: 'xpf',
ne: 'xof',
nf: 'aud',
ng: 'ngn',
ni: 'nio',
nl: 'eur',
no: 'nok',
np: 'npr',
nr: 'aud',
nu: 'nzd',
nz: 'nzd',
om: 'omr',
pa: 'pab',
pe: 'pen',
pf: 'xpf',
pg: 'pgk',
ph: 'php',
pk: 'pkr',
pl: 'pln',
pm: 'eur',
pn: 'nzd',
pr: 'usd',
ps: 'ils',
pt: 'eur',
pw: 'usd',
py: 'pyg',
qa: 'qar',
re: 'eur',
ro: 'ron',
rs: 'rsd',
ru: 'rub',
rw: 'rwf',
sa: 'sar',
sb: 'sbd',
sc: 'scr',
sd: 'sdg',
se: 'sek',
sg: 'sgd',
sh: 'shp',
si: 'eur',
sj: 'nok',
sk: 'eur',
sl: 'sll',
sm: 'eur',
sn: 'xof',
so: 'sos',
sr: 'srd',
ss: 'ssp',
st: 'std',
sv: 'svc',
sx: 'ang',
sy: 'syp',
sz: 'szl',
tc: 'usd',
td: 'xaf',
tf: 'eur',
tg: 'xof',
th: 'thb',
tj: 'tjs',
tk: 'nzd',
tl: 'usd',
tm: 'tmt',
tn: 'tnd',
to: 'top',
tr: 'try',
tt: 'ttd',
tv: 'aud',
tw: 'twd',
tz: 'tzs',
ua: 'uah',
ug: 'ugx',
um: 'usd',
us: 'usd',
uy: 'uyu',
uz: 'uzs',
va: 'eur',
vc: 'xcd',
ve: 'vef',
vg: 'usd',
vi: 'usd',
vn: 'vnd',
vu: 'vuv',
wf: 'xpf',
ws: 'wst',
ye: 'yer',
yt: 'eur',
za: 'zar',
zm: 'zmw',
zw: 'zwl',
}
/**
* Best-guess currency formatting.
*
* Attempts to use `getLocales` from `expo-localization` if available,
* otherwise falls back to the `persisted.appLanguage` setting, and geolocation
* API for region.
*/
export function useCurrencyFormatter(
options?: Parameters<typeof Intl.NumberFormat>[1],
) {
const {geolocation} = useGeolocation()
const {appLanguage} = useLanguagePrefs()
return React.useMemo(() => {
const locale = deviceLocales.at(0)
const languageTag = locale?.languageTag || appLanguage || 'en-US'
const countryCode = (
locale?.regionCode ||
geolocation?.countryCode ||
'us'
).toLowerCase()
const currency = countryCodeToCurrency[countryCode]
const format = new Intl.NumberFormat(languageTag, {
...(options || {}),
style: 'currency',
currency: currency,
}).format
return {
format,
currency,
countryCode,
languageTag,
}
}, [geolocation, appLanguage, options])
}
+1
View File
@@ -55,6 +55,7 @@ export type CommonNavigatorParams = {
StarterPackShort: {code: string}
StarterPackWizard: undefined
StarterPackEdit: {rkey?: string}
Subscriptions: undefined
}
export type BottomTabNavigatorParams = CommonNavigatorParams & {
+1
View File
@@ -62,4 +62,5 @@ export const router = new Router({
StarterPack: '/starter-pack/:name/:rkey',
StarterPackShort: '/starter-pack-short/:code',
StarterPackWizard: '/starter-pack/create',
Subscriptions: '/subscriptions',
})
+382
View File
@@ -0,0 +1,382 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {isAndroid, isIOS} from '#/platform/detection'
import {CommonNavigatorParams} from '#/lib/routes/types'
import {
useMainSubscriptions,
usePurchaseSubscription,
useChangeSubscription,
useCancelSubscription,
} from '#/state/purchases/subscriptions'
// import {useEntitlements} from '#/state/purchases/subscriptions/useEntitlements'
import {
SubscriptionTier,
Subscription,
SubscriptionId,
TierId,
} from '#/state/purchases/subscriptions/types'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {CenteredView} from '#/view/com/util/Views'
import {atoms as a, useTheme, useBreakpoints, tokens} from '#/alf'
import {Button, ButtonText, ButtonIcon} from '#/components/Button'
import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {useMainSubscriptionTiersCopy} from '#/components/subscriptions/localization'
import {GradientFill} from '#/components/GradientFill'
import {Divider} from '#/components/Divider'
import {CheckThick_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import * as Dialog from '#/components/Dialog'
import * as Toggle from '#/components/forms/Toggle'
import {Admonition} from '#/components/Admonition'
export type ScreenProps = NativeStackScreenProps<
CommonNavigatorParams,
'Subscriptions'
>
export function Subscriptions(_props: ScreenProps) {
const {_} = useLingui()
// const {data: entitlements} = useEntitlements()
const {data: subscriptions, isError} = useMainSubscriptions()
const activeSubscription = React.useMemo(() => {
if (!subscriptions) return
for (const sub of subscriptions) {
if (sub.monthly.subscription.state?.active) {
return sub.monthly
} else if (sub.annual.subscription.state?.active) {
return sub.annual
}
}
}, [subscriptions])
const currentPlatform = isAndroid ? 'android' : isIOS ? 'ios' : 'web'
const activeSubscriptionPlatform =
activeSubscription?.subscription?.state?.platform
const isManageable =
!activeSubscriptionPlatform ||
activeSubscriptionPlatform === currentPlatform
return (
<Layout.Screen>
<CenteredView sideBorders={true}>
<ViewHeader title={_(msg`Subscriptions`)} />
<View style={[a.px_xl, a.py_xl, a.gap_lg]}>
<Text style={[a.text_2xl]}>Subs</Text>
{isError ? (
<Text>Error</Text>
) : !subscriptions ? (
<Loader />
) : (
<>
{subscriptions.map(s => (
<SubscriptionCard
key={s.id}
subscription={s}
activeSubscription={activeSubscription}
isManageable={isManageable}
/>
))}
{!isManageable ? (
<Admonition type="info">
<Trans>
You purchased this subscription on{' '}
{activeSubscriptionPlatform}. Please access this screen on{' '}
{activeSubscriptionPlatform} to manage your plan.
</Trans>
</Admonition>
) : null}
</>
)}
</View>
</CenteredView>
</Layout.Screen>
)
}
export function SubscriptionCard({
subscription: s,
activeSubscription,
isManageable,
}: {
subscription: SubscriptionTier
activeSubscription?: Subscription
isManageable: boolean
}) {
const t = useTheme()
const {i18n} = useLingui()
const copy = useMainSubscriptionTiersCopy()
const activePlan = React.useMemo(() => {
if (s.monthly.subscription.state?.active) {
return s.monthly
} else if (s.annual.subscription.state?.active) {
return s.annual
}
}, [s])
const isMonthly = s.monthly.subscription.state?.active
const isAnnual = s.annual.subscription.state?.active
const isActive = isMonthly || isAnnual
const gradient = React.useMemo(() => {
return {
[TierId.Main0]: tokens.gradients.sky,
[TierId.Main1]: tokens.gradients.sunrise,
[TierId.Main2]: tokens.gradients.bonfire,
}[s.id]
}, [s.id])
const control = Dialog.useDialogControl()
return (
<View style={[a.w_full]}>
<Button
disabled={!isManageable}
label={copy[s.id].title}
style={[a.w_full]}
onPress={() => control.open()}>
{ctx => (
<View
style={[
a.w_full,
a.rounded_md,
a.overflow_hidden,
a.border,
ctx.hovered
? t.atoms.border_contrast_high
: t.atoms.border_contrast_low,
]}>
<View
style={[
a.relative,
a.w_full,
{
paddingTop: '10%',
},
]}>
<GradientFill gradient={gradient} />
{isActive && (
<View
style={[
a.absolute,
a.inset_0,
a.align_center,
a.justify_center,
a.pr_lg,
{
left: 'auto',
},
]}>
<Check fill="white" />
</View>
)}
</View>
<View style={[a.p_lg]}>
<Text style={[a.text_2xl, a.font_heavy]}>{copy[s.id].title}</Text>
{activePlan && activePlan.subscription.state && (
<View style={[a.gap_xs, a.pt_md]}>
<Text style={[a.text_md, a.font_bold]}>Current</Text>
<Text> {activePlan.subscription.interval}</Text>
<Text>
renews{' '}
{i18n.date(
new Date(activePlan.subscription.state?.periodEnd),
{dateStyle: 'medium'},
)}
</Text>
</View>
)}
{isManageable && (
<>
<Divider style={[a.my_md]} />
<View style={[a.flex_row, a.align_center]}>
{s.annual.price && s.monthly.price ? (
<Text>
<Trans>
<Text style={[a.text_xl, a.font_heavy]}>
{s.monthly.price.formatted}
</Text>
<Text style={[a.text_sm, a.font_bold]}>
/month{' • '}
<Text
style={[
a.text_sm,
a.font_normal,
t.atoms.text_contrast_medium,
]}>
<Trans>or {s.annual.price?.formatted}/year</Trans>
</Text>
</Text>
</Trans>
</Text>
) : null}
</View>
</>
)}
</View>
</View>
)}
</Button>
<Dialog.Outer control={control}>
<Dialog.Handle />
<SubscriptionDialog
subscription={s}
activeSubscription={activeSubscription}
/>
</Dialog.Outer>
</View>
)
}
function SubscriptionDialog({
subscription: s,
activeSubscription,
}: {
subscription: SubscriptionTier
activeSubscription?: Subscription
}) {
const t = useTheme()
const {_} = useLingui()
const {close} = Dialog.useDialogContext()
const {gtMobile} = useBreakpoints()
const copy = useMainSubscriptionTiersCopy()
const activeSubscriptionId = activeSubscription?.id
const tierSubscriptions = [s.monthly, s.annual]
console.log('tierSubscriptions', tierSubscriptions)
const initiallySelectedSubscription = tierSubscriptions.find(
s => s.id === activeSubscriptionId,
)
const isTierActive = !!initiallySelectedSubscription
const [values, setValues] = React.useState<SubscriptionId[]>(
initiallySelectedSubscription ? [initiallySelectedSubscription.id] : [],
)
const selectedSubscription = tierSubscriptions.find(s => s.id === values[0])
const isEditing =
activeSubscription && selectedSubscription?.id !== activeSubscription.id
const purchaseDisabled =
!selectedSubscription ||
(activeSubscription && selectedSubscription?.id === activeSubscription.id)
const {mutateAsync: purchaseSubscription, isPending: isPurchasePending} =
usePurchaseSubscription()
const {mutateAsync: changeSubscription, isPending: isChangePending} =
useChangeSubscription()
const {mutateAsync: cancelSubscription, isPending: isCancelPending} =
useCancelSubscription()
const purchase = React.useCallback(async () => {
if (selectedSubscription) {
console.log(selectedSubscription)
await purchaseSubscription(selectedSubscription)
}
}, [selectedSubscription, purchaseSubscription])
const change = React.useCallback(async () => {
const prev = activeSubscription
const next = selectedSubscription
if (!prev || !next) {
throw new Error('Invalid subscription change')
}
await changeSubscription({
prev,
next,
})
close()
}, [activeSubscription, selectedSubscription, close])
const cancel = React.useCallback(async () => {
if (activeSubscription) {
await cancelSubscription(activeSubscription)
close()
}
}, [activeSubscription, cancelSubscription, close])
const action = isEditing ? change : purchase
const isPending = isPurchasePending || isChangePending
const canCancel = isTierActive && activeSubscription?.platform === 'web'
return (
<Dialog.ScrollableInner
label={copy[s.id].title}
style={[
gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full,
]}>
<Text style={[a.text_2xl, a.font_heavy, a.pb_md]}>
{copy[s.id].title}
</Text>
<Toggle.Group
label={_(msg`Select plan`)}
type="radio"
values={values}
onChange={setValues}>
<View style={[a.flex_row, a.gap_md, a.pb_md]}>
{tierSubscriptions.map(s => (
<Toggle.Item
key={s.id}
label={_(msg`Billed annually`)}
type="radio"
name={s.id}
style={[a.flex_1]}>
{state => (
<View
style={[
a.w_full,
a.p_md,
a.rounded_sm,
a.border,
t.atoms.border_contrast_low,
{
borderColor: state.selected
? t.palette.primary_500
: t.atoms.border_contrast_low.borderColor,
},
]}>
<Text style={[a.text_2xl, a.font_heavy]}>
{s.price?.formatted}
</Text>
</View>
)}
</Toggle.Item>
))}
</View>
</Toggle.Group>
<View style={[a.gap_md]}>
<Button
label={_(msg`Purchase`)}
disabled={purchaseDisabled}
size="large"
variant="solid"
color="primary"
onPress={action}>
<ButtonText>{isEditing ? 'Update' : 'Purchase'}</ButtonText>
{isPending && <ButtonIcon icon={Loader} />}
</Button>
{canCancel ? (
<Button
label={_(msg`Cancel subscription`)}
size="large"
variant="solid"
color="secondary"
onPress={cancel}>
<ButtonText>Cancel</ButtonText>
{isCancelPending && <ButtonIcon icon={Loader} />}
</Button>
) : null}
</View>
</Dialog.ScrollableInner>
)
}
+11
View File
@@ -0,0 +1,11 @@
import Purchases from 'react-native-purchases'
import {SessionAccount} from '#/state/session/types'
export function identifyUser(account: SessionAccount) {
Purchases.logIn(account.did)
// TODO is this ever not defined?
if (account.email) {
Purchases.setEmail(account.email)
}
}
@@ -0,0 +1,2 @@
// no-op on web
export function identifyUser() {}
@@ -0,0 +1,80 @@
import {Subscription} from '#/state/purchases/subscriptions/api/types'
import {Entitlement} from '#/state/purchases/subscriptions/types'
export async function getMainSubscriptions({
did,
platform,
currency,
}: {
did: string
platform: 'web' | 'ios' | 'android'
currency: string
}) {
const res = await fetch(
`https://bsky-purchases.ngrok.io/subscriptions/main?user=${did}&platform=${platform}&currency=${currency}`,
)
if (!res.ok) {
console.error('error fetching subscriptions', res.status, await res.text())
return {
subscriptions: [],
}
}
const json = await res.json()
return json as {
subscriptions: Subscription[]
}
}
export async function getEntitlements({did}: {did: string}) {
const res = await fetch(
`https://bsky-purchases.ngrok.io/entitlements?user=${did}`,
)
if (!res.ok) {
console.error('error fetching entitlements', res.status, await res.text())
return []
}
const {entitlements} = await res.json()
return entitlements as Entitlement[]
}
export async function updateSubscription({subscriptionId, newPriceId}: {subscriptionId: string, newPriceId: string}) {
const res = await fetch(
`https://bsky-purchases.ngrok.io/subscriptions/update`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({stripeSubscriptionId: subscriptionId, newPriceId}),
}
)
if (!res.ok) {
console.error('error updating subscription', res.status, await res.text())
return
}
}
export async function cancelSubscription({subscriptionId}: {subscriptionId: string}) {
const res = await fetch(
`https://bsky-purchases.ngrok.io/subscriptions/cancel`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({stripeSubscriptionId: subscriptionId}),
}
)
if (!res.ok) {
console.error('error cancelling subscription', res.status, await res.text())
return
}
}
@@ -0,0 +1,71 @@
import {
TierId,
SubscriptionId,
} from '#/state/purchases/subscriptions/types'
export type SubscriptionState = {
platform: 'android' | 'ios' | 'web'
active: boolean
entitlements: {id: TierId}[]
status:
| 'trialing'
| 'active'
| 'expired'
| 'in_grace_period'
| 'in_billing_retry'
| 'paused'
| 'unknown'
| 'incomplete'
renewalStatus:
| 'will_renew'
| 'will_not_renew'
| 'will_change_product'
| 'will_pause'
| 'requires_price_increase_consent'
| 'has_already_renewed'
startedAt: number
periodStart: number
periodEnd: number
}
/**
* Response from our API
*/
export type Subscription =
| {
id: SubscriptionId
platform: 'android' | 'ios'
interval: 'monthly' | 'annual'
state?: SubscriptionState
store: {
type: 'play_store' | 'app_store'
productId: string
productLookupKey: string
}
}
| {
id: SubscriptionId
platform: 'web'
interval: 'monthly' | 'annual'
state: undefined
store: {
type: 'stripe'
productId: string
price: number
priceId: string
subscriptionId: undefined
}
}
| {
id: SubscriptionId
platform: 'web'
interval: 'monthly' | 'annual'
state: SubscriptionState
store: {
type: 'stripe'
productId: string
price: number
priceId: string
subscriptionId: string
}
}
+116
View File
@@ -0,0 +1,116 @@
import Purchases from 'react-native-purchases'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {useCurrencyFormatter} from '#/lib/currency'
import {isAndroid} from '#/platform/detection'
import {getMainSubscriptions} from '#/state/purchases/subscriptions/api'
import {Subscription as APISubscription} from '#/state/purchases/subscriptions/api/types'
import {
Subscription,
SubscriptionTier,
} from '#/state/purchases/subscriptions/types'
import {organizeMainSubscriptionsByTier} from '#/state/purchases/subscriptions/util'
import {useSession} from '#/state/session'
export function useMainSubscriptions() {
const {currentAccount} = useSession()
const did = currentAccount!.did
const currencyFormatter = useCurrencyFormatter()
return useQuery<SubscriptionTier[]>({
queryKey: ['availableSubscriptions', did],
async queryFn() {
const platform = isAndroid ? 'android' : 'ios'
const rawSubscriptions = await getMainSubscriptions({
did: currentAccount!.did,
platform,
})
const platformSubscriptions = rawSubscriptions.subscriptions
.filter(s => s.platform !== 'web')
.filter(s => s.platform === platform)
const lookupKeys = Array.from(
new Set(platformSubscriptions.map(s => s.store.productLookupKey)),
)
const products = await Purchases.getProducts(lookupKeys)
function decorateSubscription(
sub: APISubscription,
): Subscription | undefined {
if (sub.platform === 'web') {
return {
id: sub.id,
platform: sub.platform,
interval: sub.interval,
subscription: sub,
price: {
value: sub.store.price, // convert to cents
formatted: currencyFormatter.format(sub.store.price / 100),
},
product: sub.store.priceId,
}
}
const productData = products.find(
p => p.identifier === sub.store.productId,
)
if (!productData) return undefined
return {
id: sub.id,
platform: sub.platform,
interval: sub.interval,
subscription: sub,
price: {
value: productData.price * 100, // convert to cents
formatted: productData.priceString,
},
product: productData,
}
}
const subscriptions = rawSubscriptions.subscriptions
.map(decorateSubscription)
.filter(Boolean) as Subscription[]
return organizeMainSubscriptionsByTier(subscriptions)
},
})
}
export function usePurchaseSubscription() {
const {currentAccount} = useSession()
const queryClient = useQueryClient()
return useMutation({
async mutationFn(subscription: Subscription) {
if (subscription.platform === 'web') {
throw new Error('Cannot purchase web subscription on native')
}
if (!subscription.product) {
throw new Error('Subscription product not found')
}
// TODO don't do this here
Purchases.addCustomerInfoUpdateListener(_info => {
queryClient.invalidateQueries({
queryKey: ['availableSubscriptions', currentAccount!.did],
})
})
return Purchases.purchaseStoreProduct(subscription.product)
},
})
}
export function useChangeSubscription() {
return useMutation({
async mutationFn({prev, next}: {prev: Subscription; next: Subscription}) {},
})
}
export function useCancelSubscription() {
return useMutation({
async mutationFn(subscription: Subscription) {},
})
}
@@ -0,0 +1,147 @@
import {Linking} from 'react-native'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {useCurrencyFormatter} from '#/lib/currency'
import {
getMainSubscriptions,
updateSubscription,
cancelSubscription,
} from '#/state/purchases/subscriptions/api'
import {
Subscription,
SubscriptionTier,
} from '#/state/purchases/subscriptions/types'
import {organizeMainSubscriptionsByTier} from '#/state/purchases/subscriptions/util'
import {useSession} from '#/state/session'
import {BSKY_PURCHASES_API} from '#/env'
const createMainSubscriptionsQueryKey = (did: string) => [
'availableSubscriptions',
did,
]
export function useMainSubscriptions() {
const {currentAccount} = useSession()
const currencyFormatter = useCurrencyFormatter({
trailingZeroDisplay: 'stripIfInteger',
})
return useQuery<SubscriptionTier[]>({
refetchOnWindowFocus: true,
queryKey: createMainSubscriptionsQueryKey(currentAccount!.did),
async queryFn() {
const rawSubscriptions = await getMainSubscriptions({
did: currentAccount!.did,
platform: 'web',
currency: currencyFormatter.currency,
})
const platformSubscriptions = rawSubscriptions.subscriptions.filter(
s => s.platform === 'web',
)
const subscriptions: Subscription[] = platformSubscriptions.map(sub => {
return {
id: sub.id,
platform: 'web',
interval: sub.interval,
subscription: sub,
price: {
value: sub.store.price,
formatted: currencyFormatter.format(sub.store.price / 100),
},
// TODO may need to have si_id too
product: sub.store.priceId,
}
})
return organizeMainSubscriptionsByTier(subscriptions)
},
})
}
export function usePurchaseSubscription() {
const {currentAccount} = useSession()
return useMutation({
async mutationFn(subscription: Subscription) {
if (subscription.platform !== 'web') {
throw new Error('Cannot purchase native subscription on web')
}
if (!currentAccount || !currentAccount.email) {
throw new Error('No account or email')
}
const {checkoutUrl} = await fetch(
`${BSKY_PURCHASES_API}/createCheckout`,
{
method: 'POST',
body: JSON.stringify({
price: subscription.product,
// TODO should NOT use query, use auth and our db state
user: currentAccount.did,
email: currentAccount.email,
}),
headers: {
'Content-Type': 'application/json',
},
},
).then(res => res.json())
Linking.openURL(checkoutUrl)
},
})
}
export function useChangeSubscription() {
const {currentAccount} = useSession()
const queryClient = useQueryClient()
return useMutation({
async mutationFn({prev, next}: {prev: Subscription; next: Subscription}) {
if (
prev.subscription.platform !== 'web' ||
next.subscription.platform !== 'web'
) {
throw new Error('Cannot purchase native subscription on web')
}
if (!prev.subscription.store.subscriptionId) {
throw new Error(`Cannot change a subscription that doesn't exist`)
}
await updateSubscription({
subscriptionId: prev.subscription.store.subscriptionId,
newPriceId: next.subscription.store.priceId,
})
},
onSuccess() {
queryClient.invalidateQueries({
queryKey: createMainSubscriptionsQueryKey(currentAccount!.did),
})
},
})
}
export function useCancelSubscription() {
const {currentAccount} = useSession()
const queryClient = useQueryClient()
return useMutation({
async mutationFn(subscription: Subscription) {
if (subscription.subscription.platform !== 'web') {
throw new Error('Cannot cancel native subscription on web')
}
if (!subscription.subscription.store.subscriptionId) {
throw new Error(`Cannot cancel a subscription that doesn't exist`)
}
await cancelSubscription({
subscriptionId: subscription.subscription.store.subscriptionId,
})
},
onSuccess() {
queryClient.invalidateQueries({
queryKey: createMainSubscriptionsQueryKey(currentAccount!.did),
})
},
})
}
@@ -0,0 +1,77 @@
import {PurchasesStoreProduct} from 'react-native-purchases'
import {Subscription as APISubscription} from '#/state/purchases/subscriptions/api/types'
export enum SubscriptionId {
Main0MonthlyAuto = 'main:0:monthly:auto',
Main0AnnualAuto = 'main:0:annual:auto',
Main1MonthlyAuto = 'main:1:monthly:auto',
Main1AnnualAuto = 'main:1:annual:auto',
Main2MonthlyAuto = 'main:2:monthly:auto',
Main2AnnualAuto = 'main:2:annual:auto',
}
export enum TierId {
Main0 = 'main:0',
Main1 = 'main:1',
Main2 = 'main:2',
}
export enum PaymentInterval {
Monthly = 'monthly',
Annual = 'annual',
}
export type Entitlement = {
id: TierId
}
export type Subscription =
| {
id: SubscriptionId
platform: 'android'
interval: 'monthly' | 'annual'
/**
* Response from our API
*/
subscription: APISubscription
price?: {
value: number
formatted: string
}
product?: PurchasesStoreProduct
}
| {
id: SubscriptionId
platform: 'ios'
interval: 'monthly' | 'annual'
/**
* Response from our API
*/
subscription: APISubscription
price?: {
value: number
formatted: string
}
product?: PurchasesStoreProduct
}
| {
id: SubscriptionId
platform: 'web'
interval: 'monthly' | 'annual'
/**
* Response from our API
*/
subscription: APISubscription
price: {
value: number
formatted: string
}
product: string
}
export type SubscriptionTier = {
id: TierId
[PaymentInterval.Monthly]: Subscription
[PaymentInterval.Annual]: Subscription
}
@@ -0,0 +1,14 @@
import {useQuery} from '@tanstack/react-query'
import {getEntitlements} from '#/state/purchases/subscriptions/api'
import {useSession} from '#/state/session'
export function useEntitlements() {
const {currentAccount} = useSession()
return useQuery({
queryKey: ['entitlements', currentAccount!.did],
async queryFn() {
return getEntitlements({did: currentAccount!.did})
},
})
}
+18
View File
@@ -0,0 +1,18 @@
import {Subscription, SubscriptionTier} from './types'
export function organizeMainSubscriptionsByTier(
subscriptions: Subscription[],
): SubscriptionTier[] {
const interim: Record<string, SubscriptionTier> = {}
for (const sub of subscriptions) {
const [_, tier, interval] = sub.id.split(':')
interim[tier] = interim[tier] || {
id: `main:${tier}`,
}
interim[tier][interval as 'monthly' | 'annual'] = sub
}
return Object.values(interim)
}
+2
View File
@@ -1,5 +1,6 @@
import {AtpSessionEvent} from '@atproto/api'
import {identifyUser as identifyPurchasesUser} from '#/state/purchases/identifyUser'
import {createPublicAgent} from './agent'
import {wrapSessionReducerForLogging} from './logging'
import {SessionAccount} from './types'
@@ -118,6 +119,7 @@ let reducer = (state: State, action: Action): State => {
}
case 'switched-to-account': {
const {newAccount, newAgent} = action
identifyPurchasesUser(newAccount)
return {
accounts: [
newAccount,
@@ -16,6 +16,7 @@ import {atoms} from '#/alf'
import {useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette'
import {Gift1_Stroke2_Corner0_Rounded as Gift} from '#/components/icons/Gift1'
import {Hashtag_Stroke2_Corner0_Rounded as FeedsIcon} from '#/components/icons/Hashtag'
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import {Link} from '#/components/Link'
@@ -73,6 +74,9 @@ export function HomeHeaderLayoutMobile({
]}>
{IS_DEV && (
<>
<Link label="Jump to subscriptions" to="/subscriptions">
<Gift size="md" />
</Link>
<Link
label="View storybook"
to="/sys/debug"
+38
View File
@@ -30,6 +30,10 @@ import {
Bell_Stroke2_Corner0_Rounded as Bell,
} from '#/components/icons/Bell'
import {BulletList_Stroke2_Corner0_Rounded as List} from '#/components/icons/BulletList'
import {
Gift1_Filled_Corner0_Rounded as GiftFilled,
Gift1_Stroke2_Corner0_Rounded as Gift,
} from '#/components/icons/Gift1'
import {
Hashtag_Filled_Corner0_Rounded as HashtagFilled,
Hashtag_Stroke2_Corner0_Rounded as Hashtag,
@@ -205,6 +209,11 @@ let DrawerContent = ({}: {}): React.ReactNode => {
setDrawerOpen(false)
}, [navigation, setDrawerOpen])
const onPressSubscriptions = React.useCallback(() => {
navigation.navigate('Subscriptions')
setDrawerOpen(false)
}, [navigation, setDrawerOpen])
const onPressFeedback = React.useCallback(() => {
Linking.openURL(
FEEDBACK_FORM_URL({
@@ -266,6 +275,10 @@ let DrawerContent = ({}: {}): React.ReactNode => {
onPress={onPressProfile}
/>
<SettingsMenuItem onPress={onPressSettings} />
<SubscriptionsMenuItem
isActive={false}
onPress={onPressSubscriptions}
/>
</>
) : (
<>
@@ -562,6 +575,31 @@ let SettingsMenuItem = ({onPress}: {onPress: () => void}): React.ReactNode => {
}
SettingsMenuItem = React.memo(SettingsMenuItem)
let SubscriptionsMenuItem = ({
isActive,
onPress,
}: {
isActive: boolean
onPress: () => void
}): React.ReactNode => {
const {_} = useLingui()
const t = useTheme()
return (
<MenuItem
icon={
isActive ? (
<GiftFilled style={[t.atoms.text]} width={iconWidth} />
) : (
<Gift style={[t.atoms.text]} width={iconWidth} />
)
}
label={_(msg`Support Bluesky`)}
onPress={onPress}
/>
)
}
SubscriptionsMenuItem = React.memo(SubscriptionsMenuItem)
function MenuItem({icon, label, count, bold, onPress}: MenuItemProps) {
const t = useTheme()
return (
+2
View File
@@ -0,0 +1,2 @@
.dev.vars
.wrangler
+9
View File
@@ -0,0 +1,9 @@
The MIT License (MIT)
Copyright (c) 2021 Front of House
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.
+15
View File
@@ -0,0 +1,15 @@
# Cloudflare Workers Site
Minimalist approach to making a website on the Cloudflare Supercloud.
## Getting Started
Clone this repo, then:
```
pnpm i && pnpm client && pnpm push
```
## License
MIT License © [Front of House](https://github.com/front-of-house)
+1
View File
@@ -0,0 +1 @@
declare const ENV: 'development' | 'production'
+22
View File
@@ -0,0 +1,22 @@
{
"private": true,
"name": "workers-site",
"scripts": {
"start": "wrangler dev",
"push": "wrangler publish",
"dry": "wrangler deploy --dry-run --outdir dist",
"typecheck": "tsc --noEmit",
"format": "prettier --write ."
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240903.0",
"prettier": "^3.3.3",
"typescript": "^5.5.4",
"wrangler": "^3.75.0"
},
"dependencies": {
"hono": "^4.5.11",
"ky": "^1.7.2",
"stripe": "^17.2.1"
}
}
+855
View File
@@ -0,0 +1,855 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
hono:
specifier: ^4.5.11
version: 4.5.11
devDependencies:
'@cloudflare/workers-types':
specifier: ^4.20240903.0
version: 4.20240903.0
prettier:
specifier: ^3.3.3
version: 3.3.3
typescript:
specifier: ^5.5.4
version: 5.5.4
wrangler:
specifier: ^3.75.0
version: 3.75.0(@cloudflare/workers-types@4.20240903.0)
workers/site:
dependencies:
'@presta/html':
specifier: ^0.2.5
version: 0.2.5
'@types/react':
specifier: ^18.0.27
version: 18.0.27
hyposcript:
specifier: ^1.2.0
version: 1.2.0
devDependencies:
'@cloudflare/workers-types':
specifier: ^4.20230115.0
version: 4.20230115.0
wrangler:
specifier: ^2.9.0
version: 2.9.1
packages:
/@cloudflare/kv-asset-handler@0.3.4:
resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==}
engines: {node: '>=16.13'}
dependencies:
mime: 3.0.0
dev: true
/@cloudflare/workerd-darwin-64@1.20240821.1:
resolution: {integrity: sha512-CDBpfZKrSy4YrIdqS84z67r3Tzal2pOhjCsIb63IuCnvVes59/ft1qhczBzk9EffeOE2iTCrA4YBT7Sbn7USew==}
engines: {node: '>=16'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@cloudflare/workerd-darwin-arm64@1.20240821.1:
resolution: {integrity: sha512-Q+9RedvNbPcEt/dKni1oN94OxbvuNAeJkgHmrLFTGF8zu21wzOhVkQeRNxcYxrMa9mfStc457NAg13OVCj2kHQ==}
engines: {node: '>=16'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@cloudflare/workerd-linux-64@1.20240821.1:
resolution: {integrity: sha512-j6z3KsPtawrscoLuP985LbqFrmsJL6q1mvSXOXTqXGODAHIzGBipHARdOjms3UQqovzvqB2lQaQsZtLBwCZxtA==}
engines: {node: '>=16'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@cloudflare/workerd-linux-arm64@1.20240821.1:
resolution: {integrity: sha512-I9bHgZOxJQW0CV5gTdilyxzTG7ILzbTirehQWgfPx9X77E/7eIbR9sboOMgyeC69W4he0SKtpx0sYZuTJu4ERw==}
engines: {node: '>=16'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@cloudflare/workerd-windows-64@1.20240821.1:
resolution: {integrity: sha512-keC97QPArs6LWbPejQM7/Y8Jy8QqyaZow4/ZdsGo+QjlOLiZRDpAenfZx3CBUoWwEeFwQTl2FLO+8hV1SWFFYw==}
engines: {node: '>=16'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@cloudflare/workers-shared@0.4.1:
resolution: {integrity: sha512-nYh4r8JwOOjYIdH2zub++CmIKlkYFlpxI1nBHimoiHcytJXD/b7ldJ21TtfzUZMCgI78mxVlymMHA/ReaOxKlA==}
engines: {node: '>=16.7.0'}
dev: true
/@cloudflare/workers-types@4.20240903.0:
resolution: {integrity: sha512-a4mqgtVsPWg3JNNlQdLRE0Z6/mHr/uXa1ANDw6Zd7in438UCbeb+j7Z954Sf93G24jExpAn9VZ8kUUml0RwZbQ==}
dev: true
/@cspotcode/source-map-support@0.8.1:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19):
resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==}
peerDependencies:
esbuild: '*'
dependencies:
esbuild: 0.17.19
dev: true
/@esbuild-plugins/node-modules-polyfill@0.2.2(esbuild@0.17.19):
resolution: {integrity: sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==}
peerDependencies:
esbuild: '*'
dependencies:
esbuild: 0.17.19
escape-string-regexp: 4.0.0
rollup-plugin-node-polyfills: 0.2.1
dev: true
/@esbuild/android-arm64@0.17.19:
resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-arm@0.17.19:
resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/android-x64@0.17.19:
resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-arm64@0.17.19:
resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/darwin-x64@0.17.19:
resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-arm64@0.17.19:
resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/freebsd-x64@0.17.19:
resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm64@0.17.19:
resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-arm@0.17.19:
resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ia32@0.17.19:
resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-loong64@0.17.19:
resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-mips64el@0.17.19:
resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-ppc64@0.17.19:
resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-riscv64@0.17.19:
resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-s390x@0.17.19:
resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-x64@0.17.19:
resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@esbuild/netbsd-x64@0.17.19:
resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/openbsd-x64@0.17.19:
resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/@esbuild/sunos-x64@0.17.19:
resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-arm64@0.17.19:
resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-ia32@0.17.19:
resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@esbuild/win32-x64@0.17.19:
resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@fastify/busboy@2.1.1:
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
engines: {node: '>=14'}
dev: true
/@jridgewell/resolve-uri@3.1.2:
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec@1.5.0:
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
dev: true
/@jridgewell/trace-mapping@0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
dev: true
/acorn-walk@8.3.3:
resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==}
engines: {node: '>=0.4.0'}
dependencies:
acorn: 8.12.1
dev: true
/acorn@8.12.1:
resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
dev: true
/as-table@1.0.55:
resolution: {integrity: sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==}
dependencies:
printable-characters: 1.0.42
dev: true
/binary-extensions@2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: true
/blake3-wasm@2.1.5:
resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==}
dev: true
/braces@3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
dev: true
/capnp-ts@0.7.0:
resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==}
dependencies:
debug: 4.3.7
tslib: 2.7.0
transitivePeerDependencies:
- supports-color
dev: true
/chokidar@3.5.3:
resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.3
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/cookie@0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
dev: true
/data-uri-to-buffer@2.0.2:
resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==}
dev: true
/date-fns@3.6.0:
resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==}
dev: true
/debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.3
dev: true
/defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
dev: true
/esbuild@0.17.19:
resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/android-arm': 0.17.19
'@esbuild/android-arm64': 0.17.19
'@esbuild/android-x64': 0.17.19
'@esbuild/darwin-arm64': 0.17.19
'@esbuild/darwin-x64': 0.17.19
'@esbuild/freebsd-arm64': 0.17.19
'@esbuild/freebsd-x64': 0.17.19
'@esbuild/linux-arm': 0.17.19
'@esbuild/linux-arm64': 0.17.19
'@esbuild/linux-ia32': 0.17.19
'@esbuild/linux-loong64': 0.17.19
'@esbuild/linux-mips64el': 0.17.19
'@esbuild/linux-ppc64': 0.17.19
'@esbuild/linux-riscv64': 0.17.19
'@esbuild/linux-s390x': 0.17.19
'@esbuild/linux-x64': 0.17.19
'@esbuild/netbsd-x64': 0.17.19
'@esbuild/openbsd-x64': 0.17.19
'@esbuild/sunos-x64': 0.17.19
'@esbuild/win32-arm64': 0.17.19
'@esbuild/win32-ia32': 0.17.19
'@esbuild/win32-x64': 0.17.19
dev: true
/escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
dev: true
/estree-walker@0.6.1:
resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==}
dev: true
/exit-hook@2.2.1:
resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==}
engines: {node: '>=6'}
dev: true
/fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
dev: true
/fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
dev: true
/get-source@2.0.12:
resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==}
dependencies:
data-uri-to-buffer: 2.0.2
source-map: 0.6.1
dev: true
/glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.3
dev: true
/glob-to-regexp@0.4.1:
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
dev: true
/hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
dependencies:
function-bind: 1.1.2
dev: true
/hono@4.5.11:
resolution: {integrity: sha512-62FcjLPtjAFwISVBUshryl+vbHOjg8rE4uIK/dxyR8GpLztunZpwFmfEvmJCUI7xoGh/Sr3CGCDPCmYxVw7wUQ==}
engines: {node: '>=16.0.0'}
dev: false
/is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.2.0
dev: true
/is-core-module@2.15.1:
resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
engines: {node: '>= 0.4'}
dependencies:
hasown: 2.0.2
dev: true
/is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
dev: true
/is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
dev: true
/is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
dev: true
/magic-string@0.25.9:
resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
dependencies:
sourcemap-codec: 1.4.8
dev: true
/mime@3.0.0:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'}
hasBin: true
dev: true
/miniflare@3.20240821.1:
resolution: {integrity: sha512-81qdiryDG7VXzZuoa0EwhkaIYYrn7+StRIrd/2i7SPqPUNICUBjbhFFKqTnvE1+fqIPPB6l8ShKFaFvmnZOASg==}
engines: {node: '>=16.13'}
hasBin: true
dependencies:
'@cspotcode/source-map-support': 0.8.1
acorn: 8.12.1
acorn-walk: 8.3.3
capnp-ts: 0.7.0
exit-hook: 2.2.1
glob-to-regexp: 0.4.1
stoppable: 1.1.0
undici: 5.28.4
workerd: 1.20240821.1
ws: 8.18.0
youch: 3.3.3
zod: 3.23.8
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
dev: true
/ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: true
/mustache@4.2.0:
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
hasBin: true
dev: true
/nanoid@3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/node-forge@1.3.1:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'}
dev: true
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: true
/ohash@1.1.3:
resolution: {integrity: sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==}
dev: true
/path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/path-to-regexp@6.2.1:
resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==}
dev: true
/pathe@1.1.2:
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
dev: true
/picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
dev: true
/prettier@3.3.3:
resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
engines: {node: '>=14'}
hasBin: true
dev: true
/printable-characters@1.0.42:
resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==}
dev: true
/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
dev: true
/resolve.exports@2.0.2:
resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==}
engines: {node: '>=10'}
dev: true
/resolve@1.22.8:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
dependencies:
is-core-module: 2.15.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
dev: true
/rollup-plugin-inject@3.0.2:
resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==}
deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.
dependencies:
estree-walker: 0.6.1
magic-string: 0.25.9
rollup-pluginutils: 2.8.2
dev: true
/rollup-plugin-node-polyfills@0.2.1:
resolution: {integrity: sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==}
dependencies:
rollup-plugin-inject: 3.0.2
dev: true
/rollup-pluginutils@2.8.2:
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
dependencies:
estree-walker: 0.6.1
dev: true
/selfsigned@2.1.1:
resolution: {integrity: sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==}
engines: {node: '>=10'}
dependencies:
node-forge: 1.3.1
dev: true
/source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'}
dev: true
/sourcemap-codec@1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
deprecated: Please use @jridgewell/sourcemap-codec instead
dev: true
/stacktracey@2.1.8:
resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==}
dependencies:
as-table: 1.0.55
get-source: 2.0.12
dev: true
/stoppable@1.1.0:
resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==}
engines: {node: '>=4', npm: '>=6'}
dev: true
/supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
dev: true
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
dependencies:
is-number: 7.0.0
dev: true
/tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
dev: true
/typescript@5.5.4:
resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
/ufo@1.5.4:
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
dev: true
/undici@5.28.4:
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
engines: {node: '>=14.0'}
dependencies:
'@fastify/busboy': 2.1.1
dev: true
/unenv-nightly@2.0.0-1724863496.70db6f1:
resolution: {integrity: sha512-r+VIl1gnsI4WQxluruSQhy8alpAf1AsLRLm4sEKp3otCyTIVD6I6wHEYzeQnwsyWgaD4+3BD4A/eqrgOpdTzhw==}
dependencies:
defu: 6.1.4
ohash: 1.1.3
pathe: 1.1.2
ufo: 1.5.4
dev: true
/workerd@1.20240821.1:
resolution: {integrity: sha512-y4phjCnEG96u8ZkgkkHB+gSw0i6uMNo23rBmixylWpjxDklB+LWD8dztasvsu7xGaZbLoTxQESdEw956F7VJDA==}
engines: {node: '>=16'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@cloudflare/workerd-darwin-64': 1.20240821.1
'@cloudflare/workerd-darwin-arm64': 1.20240821.1
'@cloudflare/workerd-linux-64': 1.20240821.1
'@cloudflare/workerd-linux-arm64': 1.20240821.1
'@cloudflare/workerd-windows-64': 1.20240821.1
dev: true
/wrangler@3.75.0(@cloudflare/workers-types@4.20240903.0):
resolution: {integrity: sha512-CitNuNj0O1z6qbonUXmpUbxeWpU3nx28Kc4ZT33tMdeooQssb063Ie7+ZCdfS3kPhRHSwGdtOV22xFYytHON8w==}
engines: {node: '>=16.17.0'}
hasBin: true
peerDependencies:
'@cloudflare/workers-types': ^4.20240821.1
peerDependenciesMeta:
'@cloudflare/workers-types':
optional: true
dependencies:
'@cloudflare/kv-asset-handler': 0.3.4
'@cloudflare/workers-shared': 0.4.1
'@cloudflare/workers-types': 4.20240903.0
'@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19)
'@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19)
blake3-wasm: 2.1.5
chokidar: 3.5.3
date-fns: 3.6.0
esbuild: 0.17.19
miniflare: 3.20240821.1
nanoid: 3.3.4
path-to-regexp: 6.2.1
resolve: 1.22.8
resolve.exports: 2.0.2
selfsigned: 2.1.1
source-map: 0.6.1
unenv: /unenv-nightly@2.0.0-1724863496.70db6f1
workerd: 1.20240821.1
xxhash-wasm: 1.0.2
optionalDependencies:
fsevents: 2.3.2
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
dev: true
/ws@8.18.0:
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: true
/xxhash-wasm@1.0.2:
resolution: {integrity: sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==}
dev: true
/youch@3.3.3:
resolution: {integrity: sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==}
dependencies:
cookie: 0.5.0
mustache: 4.2.0
stacktracey: 2.1.8
dev: true
/zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
dev: true
+389
View File
@@ -0,0 +1,389 @@
import {Hono} from 'hono'
import {cors} from 'hono/cors'
import {HTTPException} from 'hono/http-exception'
import ky, {KyInstance} from 'ky'
import Stripe from 'stripe'
import {
mergeSubscriptions,
normalizeEntitlements,
normalizeSubscriptions,
RevenueCatSubscription,
Subscription,
} from './util'
type Bindings = {
STRIPE_API_KEY: string
RC_API_KEY_V2: string
RC_API_KEY_V1: string
RC_PROJECT_ID: string
}
type Variables = {
rcv1: KyInstance
rcv2: KyInstance
stripe: Stripe
}
export default new Hono<{Bindings: Bindings; Variables: Variables}>()
.use(
cors({
origin: [
'http://localhost:19006',
'https://main.bsky.dev',
'https://bsky.app',
],
allowMethods: ['GET'],
exposeHeaders: ['Content-Length'],
maxAge: 600,
credentials: true,
}),
)
.use(async (c, next) => {
const rcv1 = ky.create({
prefixUrl: 'https://api.revenuecat.com/v1',
headers: {
Authorization: `Bearer ${c.env.RC_API_KEY_V1}`,
},
})
const rcv2 = ky.create({
prefixUrl: `https://api.revenuecat.com/v2/projects/${c.env.RC_PROJECT_ID}`,
headers: {
Authorization: `Bearer ${c.env.RC_API_KEY_V2}`,
},
})
const stripe = new Stripe(c.env.STRIPE_API_KEY)
c.set('rcv1', rcv1)
c.set('rcv2', rcv2)
c.set('stripe', stripe)
await next()
})
/**
* Update a subscription (web only)
*/
.post(`/subscriptions/update`, async c => {
const stripe = c.get('stripe')
const {stripeSubscriptionId, newPriceId} = await c.req.json()
const subscriptionItem = await stripe.subscriptionItems.retrieve(
stripeSubscriptionId,
)
const subscription = await stripe.subscriptions.retrieve(
subscriptionItem.subscription,
)
const price = await stripe.prices.retrieve(newPriceId)
if (!subscription.schedule) {
throw new HTTPException(400, {
message: `Subscription does not have an associated schedule`,
})
}
const schedule = await stripe.subscriptionSchedules.retrieve(
typeof subscription.schedule === 'string'
? subscription.schedule
: subscription.schedule.id,
)
const now = Date.now() / 1000
const activeSubscriptionPhases = schedule.phases.filter(p => {
return p.end_date > now && p.start_date < now
})
const updatedSchedule = await stripe.subscriptionSchedules.update(
schedule.id,
{
phases: [
...activeSubscriptionPhases.map(p => ({
items: [
{
price:
typeof p.items[0].price === 'string'
? p.items[0].price
: p.items[0].price.id,
quantity: p.items[0].quantity,
},
],
start_date: p.start_date,
end_date: p.end_date,
})),
{
items: [
{
price: newPriceId,
quantity: 1,
},
],
iterations: 1,
},
],
},
)
return c.json({subscription: s, price, schedule: updatedSchedule})
// TODO get customer and compare DID to auth state to ensure it's legit
/*
const subscription = await stripe.subscriptions.update(
subscriptionItem.subscription,
{
items: [
{
id: stripeSubscriptionId,
deleted: true,
},
{
price: newPriceId,
},
],
proration_behavior: 'always_invoice',
},
)
return c.json({
oldItem: subscriptionItem,
subscription,
})
*/
})
/**
* Cancel a subscription (web only)
*/
.post(`/subscriptions/cancel`, async c => {
const stripe = c.get('stripe')
const {stripeSubscriptionId} = await c.req.json()
const subscriptionItem = await stripe.subscriptionItems.retrieve(
stripeSubscriptionId,
)
// TODO get customer and compare DID to auth state to ensure it's legit
const subscription = await stripe.subscriptions.update(
subscriptionItem.subscription,
{
cancel_at_period_end: true,
},
)
return c.json({
oldItem: subscriptionItem,
subscription,
})
})
/**
* Get subscription products for a given offering
*/
.get('/subscriptions/:offering_lookup_key', async c => {
const rc = c.get('rcv2')
const {user, platform, currency} = c.req.query()
const {offering_lookup_key} = c.req.param()
const {items} = await rc
.get<{items: any[]}>(`offerings?expand=items.package.product`)
.json()
const offering = items.find(i => i.lookup_key == offering_lookup_key)
if (!offering) {
throw new HTTPException(404, {message: `Offering not found`})
}
const offeringProducts = offering.packages.items
.flatMap((i: any) => i.products.items)
.map((i: any) => i.product)
let userSubscriptions: any[] = []
try {
const {items} = await rc
.get<{
items: RevenueCatSubscription[]
}>(`customers/${user}/subscriptions`)
.json()
userSubscriptions = items
} catch (e) {}
let normalizedSubscriptions = normalizeSubscriptions(
offeringProducts,
userSubscriptions,
)
let activeSubscriptions = normalizedSubscriptions.filter(
p => p.state?.active,
)
let platformSubscriptions = normalizedSubscriptions.filter(
p => p.platform === platform,
)
async function decorateWebProducts(subscriptions: Subscription[]) {
const stripe = c.get('stripe')
const ids = subscriptions
.filter(p => p.platform === 'web')
.map(p => p.store.productId)
const {data} = await stripe.products.list({ids})
const prices = await Promise.all(
data.map(p => {
if (typeof p.default_price === 'string') {
return stripe.prices.retrieve(p.default_price, {
expand: ['currency_options'],
})
} else {
return p.default_price
}
}),
)
for (const price of prices) {
if (!price) continue
// mutates for now
for (const sub of subscriptions) {
if (sub.platform !== 'web') continue
if (price.product === sub.store.productId) {
const localizedPrice =
price?.currency_options?.[currency || 'usd']?.unit_amount ||
price.unit_amount
sub.store.price = localizedPrice ?? 0
sub.store.priceId = price.id
break
}
}
}
return subscriptions
}
if (platform === 'web') {
activeSubscriptions = await decorateWebProducts(activeSubscriptions)
platformSubscriptions = await decorateWebProducts(platformSubscriptions)
} else if (platform === 'ios') {
activeSubscriptions = await decorateWebProducts(activeSubscriptions)
} else if (platform === 'android') {
activeSubscriptions = await decorateWebProducts(activeSubscriptions)
}
return c.json({
subscriptions: mergeSubscriptions(
platformSubscriptions,
activeSubscriptions,
),
})
})
/**
* Get entitlements for a user
*/
.get('/entitlements', async c => {
const rc = c.get('rcv2')
const user = c.req.query('user')
const {items: activeEntitlements} = await rc
.get<{
items: {entitlement_id: string}[]
}>(`customers/${user}/active_entitlements`)
.json()
const entitlements = await Promise.all(
activeEntitlements.map(e => {
return rc.get(`entitlements/${e.entitlement_id}?expand=product`).json()
}),
)
const normalized = normalizeEntitlements(entitlements)
return c.json({entitlements: normalized})
})
/**
* Create a Stripe checkout session (web only)
*/
.post('/createCheckout', async c => {
const DOMAIN = `http://localhost:19006`
const stripe = c.get('stripe')
const {price, user, email} = await c.req.json()
// TODO should store this in a db
const customers = await stripe.customers.search({
query: `email:"${email}"`,
})
let customer: Stripe.Customer | undefined =
customers.data.at(0)?.email === email ? customers.data[0] : undefined
if (!customer) {
customer = await stripe.customers.create({
email,
metadata: {
did: user,
},
})
}
const session = await stripe.checkout.sessions.create({
client_reference_id: user,
customer: customer.id,
customer_update: {
address: 'auto',
name: 'auto',
shipping: 'auto',
},
saved_payment_method_options: {
payment_method_save: 'enabled',
},
line_items: [
{
price: price,
quantity: 1,
},
],
mode: 'subscription',
success_url: `${DOMAIN}/subscriptions?success=true`,
cancel_url: `${DOMAIN}/subscriptions?canceled=true`,
automatic_tax: {enabled: true},
})
if (!session.url) {
throw new HTTPException(400, {message: `Failed to create checkout`})
}
return c.json({checkoutUrl: session.url})
})
/**
* Listen for Stripe webhooks
* @see https://docs.stripe.com/webhooks#local-listener
*/
.post('/webhooks/stripe', async c => {
const stripe = c.get('stripe')
const rc = c.get('rcv1')
const {type, data} = await c.req.json()
if (type === 'customer.subscription.created') {
const {object: sub} = data
const customer = await stripe.customers.retrieve(sub.customer)
// immediately attach a schedule for later manipulation
await stripe.subscriptionSchedules.create({
from_subscription: sub.id,
})
if (!customer.deleted) {
const payload = {
fetch_token: sub.id,
app_user_id: customer.metadata.did,
}
try {
const res = await rc
.post(`receipts`, {
json: payload,
headers: {
'X-Platform': 'stripe',
},
})
.json()
console.log(`success`, res)
} catch (e) {
console.error(`failed to send receipt`, e)
}
}
}
return c.text('ok')
})
+400
View File
@@ -0,0 +1,400 @@
/**
* @see https://www.revenuecat.com/docs/api-v2#tag/Product/operation/list-products
*/
export type RevenueCatProduct = {
id: string
object: 'product'
store_identifier: string
type: 'subscription'
app:
| {
type: 'stripe'
id: string
project_id: string
stripe: {
stripe_account_id: string
}
}
| {
type: 'play_store'
id: string
project_id: string
play_store: {
package_name: string
}
}
| {
type: 'app_store'
id: string
project_id: string
app_store: {
bundle_id: string
}
}
}
/**
* @see https://www.revenuecat.com/docs/api-v2#tag/Customer/operation/list-subscriptions
*/
export type RevenueCatSubscription = {
id: string
product_id: 'prodc783cb31d8'
environment: 'sandbox'
status:
| 'trialing'
| 'active'
| 'expired'
| 'in_grace_period'
| 'in_billing_retry'
| 'paused'
| 'unknown'
| 'incomplete'
store: 'stripe' | 'play_store' | 'app_store'
object: 'subscription'
auto_renewal_status:
| 'will_renew'
| 'will_not_renew'
| 'will_change_product'
| 'will_pause'
| 'requires_price_increase_consent'
| 'has_already_renewed'
starts_at: number
current_period_ends_at: number
current_period_starts_at: number
customer_id: string // DID
original_customer_id: string // should be DID, may be anon
entitlements: {
items: {
created_at: number
display_name: string
id: string
lookup_key: string
object: 'entitlement'
project_id: string
}[]
next_page: null
object: 'list'
url: string
}
management_url: unknown
gives_access: boolean
store_subscription_identifier: string
}
export enum SubscriptionId {
Main0MonthlyAuto = 'main:0:monthly:auto',
Main0AnnualAuto = 'main:0:annual:auto',
Main1MonthlyAuto = 'main:1:monthly:auto',
Main1AnnualAuto = 'main:1:annual:auto',
Main2MonthlyAuto = 'main:2:monthly:auto',
Main2AnnualAuto = 'main:2:annual:auto',
}
export enum EntitlementId {
Main0 = 'main:0',
Main1 = 'main:1',
Main2 = 'main:2',
}
export type Entitlement = {
id: EntitlementId
}
export type SubscriptionState = {
platform: 'web' | 'android' | 'ios'
active: boolean
entitlements: {id: EntitlementId}[]
status:
| 'trialing'
| 'active'
| 'expired'
| 'in_grace_period'
| 'in_billing_retry'
| 'paused'
| 'unknown'
| 'incomplete'
renewalStatus:
| 'will_renew'
| 'will_not_renew'
| 'will_change_product'
| 'will_pause'
| 'requires_price_increase_consent'
| 'has_already_renewed'
startedAt: number
periodStart: number
periodEnd: number
}
export type Subscription =
| {
id: SubscriptionId
platform: 'android' | 'ios'
interval: 'monthly' | 'annual'
state?: SubscriptionState
store: {
type: 'play_store' | 'app_store'
productId: string
productLookupKey: string
}
}
| {
id: SubscriptionId
platform: 'web'
interval: 'monthly' | 'annual'
state: undefined
store: {
type: 'stripe'
productId: string
price: number
priceId: string
subscriptionId: undefined
}
}
| {
id: SubscriptionId
platform: 'web'
interval: 'monthly' | 'annual'
state: SubscriptionState
store: {
type: 'stripe'
productId: string
price: number
priceId: string
subscriptionId: string
}
}
export function normalizeSubscriptions(
products: RevenueCatProduct[],
subscriptions: RevenueCatSubscription[],
): Subscription[] {
// should always be subscriptions, but guard here
const subscriptionProducts = products.filter(
product => product.type === 'subscription',
)
const normalized: Subscription[] = []
for (const product of subscriptionProducts) {
/**
* Matches product ID and gives user access (is active)
* @see https://www.revenuecat.com/docs/api-v2#tag/Customer/operation/list-subscriptions
*/
const subscription = subscriptions.find(
s => s.product_id === product.id && s.gives_access,
)
const id = normalizeSubscriptionId(product.store_identifier)
if (!id) continue
const platform = getSubscriptionPlatform(product.store_identifier)
const state = subscription
? getSubscriptionState(subscription, {platform})
: undefined
const interval = getSubscriptionInterval(id)
if (platform === 'web') {
if (state) {
normalized.push({
id,
platform,
interval,
state,
store: {
type: 'stripe',
price: 0,
productId: product.store_identifier,
priceId: '',
subscriptionId: subscription?.store_subscription_identifier!,
},
})
} else {
normalized.push({
id,
platform,
interval,
state,
store: {
type: 'stripe',
productId: product.store_identifier,
price: 0,
priceId: '',
subscriptionId: undefined,
},
})
}
} else {
normalized.push({
id,
platform,
interval,
state,
store: {
type: platform === 'android' ? 'play_store' : 'app_store',
productId: product.store_identifier,
productLookupKey: getProductLookupKey(product.store_identifier),
},
})
}
}
return normalized
}
export function mergeSubscriptions(
all: Subscription[],
active: Subscription[],
): Subscription[] {
const merged: Subscription[] = []
for (const s of all) {
const a = active.find(p => p.id === s.id)
if (a) {
// @ts-ignore
merged.push({...s, state: a.state})
} else {
merged.push(s)
}
}
return merged
}
export function getSubscriptionInterval(
id: SubscriptionId,
): 'monthly' | 'annual' {
switch (id) {
case SubscriptionId.Main0MonthlyAuto:
case SubscriptionId.Main1MonthlyAuto:
case SubscriptionId.Main2MonthlyAuto:
return 'monthly'
case SubscriptionId.Main0AnnualAuto:
case SubscriptionId.Main1AnnualAuto:
case SubscriptionId.Main2AnnualAuto:
return 'annual'
}
}
export function getSubscriptionState(
subscription: RevenueCatSubscription,
options: {
platform: 'web' | 'android' | 'ios'
},
): SubscriptionState {
return {
platform: options.platform,
active: subscription.gives_access,
status: subscription.status,
renewalStatus: subscription.auto_renewal_status,
entitlements: normalizeEntitlements(subscription.entitlements.items),
startedAt: subscription.starts_at,
periodStart: subscription.current_period_starts_at,
periodEnd: subscription.current_period_ends_at,
}
}
export function getSubscriptionPlatform(id: string): 'web' | 'android' | 'ios' {
switch (id) {
case 'prod_R2eNjNa6mB1Jlu':
case 'prod_R37Zg28EeQ9XAz':
case 'prod_R67eiEMuIf59w7':
case 'prod_R67fjfexQ7THQ0':
case 'prod_R67ftsSVu2U1D6':
case 'prod_R67gClEZy1cry2': {
return 'web'
}
case 'bsky_tier_0:monthly-auto':
case 'bsky_tier_0:annual-auto':
case 'bsky_tier_1:monthly-auto':
case 'bsky_tier_1:annual-auto':
case 'bsky_tier_2:monthly-auto':
case 'bsky_tier_2:annual-auto': {
return 'android'
}
case 'bsky_tier_0_monthly':
case 'bsky_tier_0_annual':
case 'bsky_tier_1_monthly':
case 'bsky_tier_1_annual':
case 'bsky_tier_2_monthly':
case 'bsky_tier_2_annual': {
return 'ios'
}
default: {
throw new Error(`Unknown product ID: ${id}`)
}
}
}
export function getProductLookupKey(id: string): string {
switch (id) {
case 'bsky_tier_0:monthly-auto':
case 'bsky_tier_0:annual-auto':
return 'bsky_tier_0'
case 'bsky_tier_1:monthly-auto':
case 'bsky_tier_1:annual-auto':
return 'bsky_tier_1'
case 'bsky_tier_2:monthly-auto':
case 'bsky_tier_2:annual-auto':
return 'bsky_tier_2'
default:
return id
}
}
export function normalizeSubscriptionId(
id: string,
): SubscriptionId | undefined {
switch (id) {
case 'bsky_tier_0_monthly':
case 'bsky_tier_0:monthly-auto':
case 'prod_R2eNjNa6mB1Jlu':
return SubscriptionId.Main0MonthlyAuto
case 'bsky_tier_0_annual':
case 'bsky_tier_0:annual-auto':
case 'prod_R37Zg28EeQ9XAz':
return SubscriptionId.Main0AnnualAuto
case 'bsky_tier_1_monthly':
case 'bsky_tier_1:monthly-auto':
case 'prod_R67eiEMuIf59w7':
return SubscriptionId.Main1MonthlyAuto
case 'bsky_tier_1_annual':
case 'bsky_tier_1:annual-auto':
case 'prod_R67fjfexQ7THQ0':
return SubscriptionId.Main1AnnualAuto
case 'bsky_tier_2_monthly':
case 'bsky_tier_2:monthly-auto':
case 'prod_R67ftsSVu2U1D6':
return SubscriptionId.Main2MonthlyAuto
case 'bsky_tier_2_annual':
case 'bsky_tier_2:annual-auto':
case 'prod_R67gClEZy1cry2':
return SubscriptionId.Main2AnnualAuto
default: {
return undefined
}
}
}
export function normalizeEntitlements(entitlements: any[]) {
return entitlements.map(entitlement => {
let id = EntitlementId.Main0
switch (entitlement.lookup_key) {
case 'main:0': {
id = EntitlementId.Main0
break
}
case 'main:1': {
id = EntitlementId.Main1
break
}
case 'main:2': {
id = EntitlementId.Main2
break
}
}
return {
id,
}
})
}
+21
View File
@@ -0,0 +1,21 @@
{
"include": ["src", "env.d.ts"],
"compilerOptions": {
"types": ["@cloudflare/workers-types"],
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
},
"strict": true,
"target": "es2021",
"lib": ["es2021"],
"module": "es2022",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"skipLibCheck": true,
"noEmit": true
}
}
+6
View File
@@ -0,0 +1,6 @@
name = "purchases"
main = "src/index.ts"
account_id = '8443bac20f1f475b04c034a0d98c7ed9'
compatibility_date = "2024-09-06"
# route = 'worker.com/*'
+827
View File
@@ -0,0 +1,827 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@cloudflare/kv-asset-handler@0.3.4":
version "0.3.4"
resolved "https://registry.yarnpkg.com/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz#5cc152847c8ae4d280ec5d7f4f6ba8c976b585c3"
integrity sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==
dependencies:
mime "^3.0.0"
"@cloudflare/workerd-darwin-64@1.20241022.0":
version "1.20241022.0"
resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20241022.0.tgz#1d22149152ad32672971e3be48ab1491ff236eb3"
integrity sha512-1NNYun37myMTgCUiPQEJ0cMal4mKZVTpkD0b2tx9hV70xji+frVJcSK8YVLeUm1P+Rw1d/ct8DMgQuCpsz3Fsw==
"@cloudflare/workerd-darwin-arm64@1.20241022.0":
version "1.20241022.0"
resolved "https://registry.yarnpkg.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20241022.0.tgz#6ec4e4fd1427ac09d9cfea38db849799755029e5"
integrity sha512-FOO/0P0U82EsTLTdweNVgw+4VOk5nghExLPLSppdOziq6IR5HVgP44Kmq5LdsUeHUhwUmfOh9hzaTpkNzUqKvw==
"@cloudflare/workerd-linux-64@1.20241022.0":
version "1.20241022.0"
resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20241022.0.tgz#0297222e46ce8b2c55b591ae87ee2bbb85830e20"
integrity sha512-RsNc19BQJG9yd+ngnjuDeG9ywZG+7t1L4JeglgceyY5ViMNMKVO7Zpbsu69kXslU9h6xyQG+lrmclg3cBpnhYA==
"@cloudflare/workerd-linux-arm64@1.20241022.0":
version "1.20241022.0"
resolved "https://registry.yarnpkg.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20241022.0.tgz#7637092b14129c94ddb87990221d38623e4fd498"
integrity sha512-x5mUXpKxfsosxcFmcq5DaqLs37PejHYVRsNz1cWI59ma7aC4y4Qn6Tf3i0r9MwQTF/MccP4SjVslMU6m4W7IaA==
"@cloudflare/workerd-windows-64@1.20241022.0":
version "1.20241022.0"
resolved "https://registry.yarnpkg.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20241022.0.tgz#64f05a2bc475e1450154942c060555184e6350de"
integrity sha512-eBCClx4szCOgKqOlxxbdNszMqQf3MRG1B9BRIqEM/diDfdR9IrZ8l3FaEm+l9gXgPmS6m1NBn40aWuGBl8UTSw==
"@cloudflare/workers-shared@0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@cloudflare/workers-shared/-/workers-shared-0.7.0.tgz#ecb6a3a1f483989c1ca10d5ed0592d01c7eff0c0"
integrity sha512-LLQRTqx7lKC7o2eCYMpyc5FXV8d0pUX6r3A+agzhqS9aoR5A6zCPefwQGcvbKx83ozX22ATZcemwxQXn12UofQ==
dependencies:
mime "^3.0.0"
zod "^3.22.3"
"@cloudflare/workers-types@^4.20240903.0":
version "4.20241022.0"
resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-4.20241022.0.tgz#4f751ec29b9a2a0cef83bdc797b02089462ee48d"
integrity sha512-1zOAw5QIDKItzGatzCrEpfLOB1AuMTwVqKmbw9B9eBfCUGRFNfJYMrJxIwcse9EmKahsQt2GruqU00pY/GyXgg==
"@cspotcode/source-map-support@0.8.1":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
"@esbuild-plugins/node-globals-polyfill@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz#0e4497a2b53c9e9485e149bc92ddb228438d6bcf"
integrity sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==
"@esbuild-plugins/node-modules-polyfill@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz#cefa3dc0bd1c16277a8338b52833420c94987327"
integrity sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==
dependencies:
escape-string-regexp "^4.0.0"
rollup-plugin-node-polyfills "^0.2.1"
"@esbuild/android-arm64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd"
integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==
"@esbuild/android-arm@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d"
integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==
"@esbuild/android-x64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1"
integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==
"@esbuild/darwin-arm64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276"
integrity sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==
"@esbuild/darwin-x64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb"
integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==
"@esbuild/freebsd-arm64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2"
integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==
"@esbuild/freebsd-x64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4"
integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==
"@esbuild/linux-arm64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb"
integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==
"@esbuild/linux-arm@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a"
integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==
"@esbuild/linux-ia32@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a"
integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==
"@esbuild/linux-loong64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72"
integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==
"@esbuild/linux-mips64el@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289"
integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==
"@esbuild/linux-ppc64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7"
integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==
"@esbuild/linux-riscv64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09"
integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==
"@esbuild/linux-s390x@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829"
integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==
"@esbuild/linux-x64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4"
integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==
"@esbuild/netbsd-x64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462"
integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==
"@esbuild/openbsd-x64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691"
integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==
"@esbuild/sunos-x64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273"
integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==
"@esbuild/win32-arm64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f"
integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==
"@esbuild/win32-ia32@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03"
integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==
"@esbuild/win32-x64@0.17.19":
version "0.17.19"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061"
integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==
"@fastify/busboy@^2.0.0":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
"@jridgewell/trace-mapping@0.3.9":
version "0.3.9"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@types/node-forge@^1.3.0":
version "1.3.11"
resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da"
integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@>=8.1.0":
version "22.8.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.8.1.tgz#b39d4b98165e2ae792ce213f610c7c6108ccfa16"
integrity sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==
dependencies:
undici-types "~6.19.8"
acorn-walk@^8.2.0:
version "8.3.4"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7"
integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==
dependencies:
acorn "^8.11.0"
acorn@^8.11.0, acorn@^8.8.0:
version "8.13.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.13.0.tgz#2a30d670818ad16ddd6a35d3842dacec9e5d7ca3"
integrity sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==
anymatch@~3.1.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
as-table@^1.0.36:
version "1.0.55"
resolved "https://registry.yarnpkg.com/as-table/-/as-table-1.0.55.tgz#dc984da3937745de902cea1d45843c01bdbbec4f"
integrity sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==
dependencies:
printable-characters "^1.0.42"
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
blake3-wasm@^2.1.5:
version "2.1.5"
resolved "https://registry.yarnpkg.com/blake3-wasm/-/blake3-wasm-2.1.5.tgz#b22dbb84bc9419ed0159caa76af4b1b132e6ba52"
integrity sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==
braces@~3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
dependencies:
fill-range "^7.1.1"
call-bind@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
dependencies:
es-define-property "^1.0.0"
es-errors "^1.3.0"
function-bind "^1.1.2"
get-intrinsic "^1.2.4"
set-function-length "^1.2.1"
capnp-ts@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/capnp-ts/-/capnp-ts-0.7.0.tgz#16fd8e76b667d002af8fcf4bf92bf15d1a7b54a9"
integrity sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==
dependencies:
debug "^4.3.1"
tslib "^2.2.0"
chokidar@^3.5.3:
version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
cookie@^0.7.1:
version "0.7.2"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7"
integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==
data-uri-to-buffer@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz#d296973d5a4897a5dbe31716d118211921f04770"
integrity sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==
date-fns@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14"
integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==
debug@^4.3.1:
version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies:
ms "^2.1.3"
define-data-property@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
dependencies:
es-define-property "^1.0.0"
es-errors "^1.3.0"
gopd "^1.0.1"
defu@^6.1.4:
version "6.1.4"
resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479"
integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==
es-define-property@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
dependencies:
get-intrinsic "^1.2.4"
es-errors@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
esbuild@0.17.19:
version "0.17.19"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.19.tgz#087a727e98299f0462a3d0bcdd9cd7ff100bd955"
integrity sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==
optionalDependencies:
"@esbuild/android-arm" "0.17.19"
"@esbuild/android-arm64" "0.17.19"
"@esbuild/android-x64" "0.17.19"
"@esbuild/darwin-arm64" "0.17.19"
"@esbuild/darwin-x64" "0.17.19"
"@esbuild/freebsd-arm64" "0.17.19"
"@esbuild/freebsd-x64" "0.17.19"
"@esbuild/linux-arm" "0.17.19"
"@esbuild/linux-arm64" "0.17.19"
"@esbuild/linux-ia32" "0.17.19"
"@esbuild/linux-loong64" "0.17.19"
"@esbuild/linux-mips64el" "0.17.19"
"@esbuild/linux-ppc64" "0.17.19"
"@esbuild/linux-riscv64" "0.17.19"
"@esbuild/linux-s390x" "0.17.19"
"@esbuild/linux-x64" "0.17.19"
"@esbuild/netbsd-x64" "0.17.19"
"@esbuild/openbsd-x64" "0.17.19"
"@esbuild/sunos-x64" "0.17.19"
"@esbuild/win32-arm64" "0.17.19"
"@esbuild/win32-ia32" "0.17.19"
"@esbuild/win32-x64" "0.17.19"
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
estree-walker@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
exit-hook@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-2.2.1.tgz#007b2d92c6428eda2b76e7016a34351586934593"
integrity sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"
fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
dependencies:
es-errors "^1.3.0"
function-bind "^1.1.2"
has-proto "^1.0.1"
has-symbols "^1.0.3"
hasown "^2.0.0"
get-source@^2.0.12:
version "2.0.12"
resolved "https://registry.yarnpkg.com/get-source/-/get-source-2.0.12.tgz#0b47d57ea1e53ce0d3a69f4f3d277eb8047da944"
integrity sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==
dependencies:
data-uri-to-buffer "^2.0.0"
source-map "^0.6.1"
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob-to-regexp@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
dependencies:
get-intrinsic "^1.1.3"
has-property-descriptors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
dependencies:
es-define-property "^1.0.0"
has-proto@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
hasown@^2.0.0, hasown@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
dependencies:
function-bind "^1.1.2"
hono@^4.5.11:
version "4.6.7"
resolved "https://registry.yarnpkg.com/hono/-/hono-4.6.7.tgz#5389be797be4e049991d29ad7aea499e70d28085"
integrity sha512-wX4ZbOnzfNO61hUjuQbJ7OPGs1fWXXVVJ8VTPDb2Ls/x9HjCbVTm80Je6VTHMz5n5RGDtBgV9d9ZFZxBqx56ng==
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.13.0:
version "2.15.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37"
integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==
dependencies:
hasown "^2.0.2"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
itty-time@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/itty-time/-/itty-time-1.0.6.tgz#a6eeda619f19d2f4c480ceddd013b93acb05714d"
integrity sha512-+P8IZaLLBtFv8hCkIjcymZOp4UJ+xW6bSlQsXGqrkmJh7vSiMFSlNne0mCYagEE0N7HDNR5jJBRxwN0oYv61Rw==
ky@^1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/ky/-/ky-1.7.2.tgz#b97d9b997ba51ff1e152f0815d3d27b86513eb1c"
integrity sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==
magic-string@^0.25.3:
version "0.25.9"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
dependencies:
sourcemap-codec "^1.4.8"
mime@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
miniflare@3.20241022.0:
version "3.20241022.0"
resolved "https://registry.yarnpkg.com/miniflare/-/miniflare-3.20241022.0.tgz#31b8a2bc53b411ac814b55db9c31aebfe475f344"
integrity sha512-x9Fbq1Hmz1f0osIT9Qmj78iX4UpCP2EqlZnA/tzj/3+I49vc3Kq0fNqSSKplcdf6HlCHdL3fOBicmreQF4BUUQ==
dependencies:
"@cspotcode/source-map-support" "0.8.1"
acorn "^8.8.0"
acorn-walk "^8.2.0"
capnp-ts "^0.7.0"
exit-hook "^2.2.1"
glob-to-regexp "^0.4.1"
stoppable "^1.1.0"
undici "^5.28.4"
workerd "1.20241022.0"
ws "^8.17.1"
youch "^3.2.2"
zod "^3.22.3"
ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
mustache@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
nanoid@^3.3.3:
version "3.3.7"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
node-forge@^1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
object-inspect@^1.13.1:
version "1.13.2"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==
ohash@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.4.tgz#ae8d83014ab81157d2c285abf7792e2995fadd72"
integrity sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-to-regexp@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4"
integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==
pathe@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
picomatch@^2.0.4, picomatch@^2.2.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
prettier@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==
printable-characters@^1.0.42:
version "1.0.42"
resolved "https://registry.yarnpkg.com/printable-characters/-/printable-characters-1.0.42.tgz#3f18e977a9bd8eb37fcc4ff5659d7be90868b3d8"
integrity sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==
qs@^6.11.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
dependencies:
side-channel "^1.0.6"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
resolve.exports@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==
resolve@^1.22.8:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
dependencies:
is-core-module "^2.13.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
rollup-plugin-inject@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz#e4233855bfba6c0c12a312fd6649dff9a13ee9f4"
integrity sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==
dependencies:
estree-walker "^0.6.1"
magic-string "^0.25.3"
rollup-pluginutils "^2.8.1"
rollup-plugin-node-polyfills@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz#53092a2744837164d5b8a28812ba5f3ff61109fd"
integrity sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==
dependencies:
rollup-plugin-inject "^3.0.0"
rollup-pluginutils@^2.8.1:
version "2.8.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==
dependencies:
estree-walker "^0.6.1"
selfsigned@^2.0.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0"
integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==
dependencies:
"@types/node-forge" "^1.3.0"
node-forge "^1"
set-function-length@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
dependencies:
define-data-property "^1.1.4"
es-errors "^1.3.0"
function-bind "^1.1.2"
get-intrinsic "^1.2.4"
gopd "^1.0.1"
has-property-descriptors "^1.0.2"
side-channel@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
dependencies:
call-bind "^1.0.7"
es-errors "^1.3.0"
get-intrinsic "^1.2.4"
object-inspect "^1.13.1"
source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
stacktracey@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/stacktracey/-/stacktracey-2.1.8.tgz#bf9916020738ce3700d1323b32bd2c91ea71199d"
integrity sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==
dependencies:
as-table "^1.0.36"
get-source "^2.0.12"
stoppable@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b"
integrity sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==
stripe@^17.2.1:
version "17.2.1"
resolved "https://registry.yarnpkg.com/stripe/-/stripe-17.2.1.tgz#8f1ea25c31df92245e5747e121ca0fe1e90f1c2e"
integrity sha512-Mwb0SitV/UjxTLSUiTlUQ0VDE/gfyQ7OHDGdvDdvKIH4CNNqobPKlzlWhoZHFFU0OA/YsZyBtPULK4JG86BAAg==
dependencies:
"@types/node" ">=8.1.0"
qs "^6.11.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
tslib@^2.2.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b"
integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==
typescript@^5.5.4:
version "5.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b"
integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==
ufo@^1.5.4:
version "1.5.4"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754"
integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==
undici-types@~6.19.8:
version "6.19.8"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
undici@^5.28.4:
version "5.28.4"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068"
integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==
dependencies:
"@fastify/busboy" "^2.0.0"
"unenv@npm:unenv-nightly@2.0.0-20241018-011344-e666fcf":
version "2.0.0-20241018-011344-e666fcf"
resolved "https://registry.yarnpkg.com/unenv-nightly/-/unenv-nightly-2.0.0-20241018-011344-e666fcf.tgz#75b6d1ab37e6b5edeec80fe8f403e3de8e9bc50e"
integrity sha512-D00bYn8rzkCBOlLx+k1iHQlc69jvtJRT7Eek4yIGQ6461a2tUBjngGZdRpqsoXAJCz/qBW0NgPting7Zvg+ysg==
dependencies:
defu "^6.1.4"
ohash "^1.1.4"
pathe "^1.1.2"
ufo "^1.5.4"
workerd@1.20241022.0:
version "1.20241022.0"
resolved "https://registry.yarnpkg.com/workerd/-/workerd-1.20241022.0.tgz#45ab10642009b4573ae78df784e0a8c5b338914e"
integrity sha512-jyGXsgO9DRcJyx6Ovv7gUyDPc3UYC2i/E0p9GFUg6GUzpldw4Y93y9kOmdfsOnKZ3+lY53veSiUniiBPE6Q2NQ==
optionalDependencies:
"@cloudflare/workerd-darwin-64" "1.20241022.0"
"@cloudflare/workerd-darwin-arm64" "1.20241022.0"
"@cloudflare/workerd-linux-64" "1.20241022.0"
"@cloudflare/workerd-linux-arm64" "1.20241022.0"
"@cloudflare/workerd-windows-64" "1.20241022.0"
wrangler@^3.75.0:
version "3.83.0"
resolved "https://registry.yarnpkg.com/wrangler/-/wrangler-3.83.0.tgz#bebe3d0067f3430cf6f0b2e8e4ab648c208e002b"
integrity sha512-qDzdUuTngKqmm2OJUZm7Gk4+Hv37F2nNNAHuhIgItEIhxBdOVDsgKmvpd+f41MFxyuGg3fbGWYANHI+0V2Z5yw==
dependencies:
"@cloudflare/kv-asset-handler" "0.3.4"
"@cloudflare/workers-shared" "0.7.0"
"@esbuild-plugins/node-globals-polyfill" "^0.2.3"
"@esbuild-plugins/node-modules-polyfill" "^0.2.2"
blake3-wasm "^2.1.5"
chokidar "^3.5.3"
date-fns "^4.1.0"
esbuild "0.17.19"
itty-time "^1.0.6"
miniflare "3.20241022.0"
nanoid "^3.3.3"
path-to-regexp "^6.3.0"
resolve "^1.22.8"
resolve.exports "^2.0.2"
selfsigned "^2.0.1"
source-map "^0.6.1"
unenv "npm:unenv-nightly@2.0.0-20241018-011344-e666fcf"
workerd "1.20241022.0"
xxhash-wasm "^1.0.1"
optionalDependencies:
fsevents "~2.3.2"
ws@^8.17.1:
version "8.18.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
xxhash-wasm@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz#ecc0f813219b727af4d5f3958ca6becee2f2f1ff"
integrity sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==
youch@^3.2.2:
version "3.3.4"
resolved "https://registry.yarnpkg.com/youch/-/youch-3.3.4.tgz#f13ee0966846c6200e7fb9ece89306d95df5e489"
integrity sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==
dependencies:
cookie "^0.7.1"
mustache "^4.2.0"
stacktracey "^2.1.8"
zod@^3.22.3:
version "3.23.8"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
+12
View File
@@ -5942,6 +5942,11 @@
resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-2.0.2.tgz#f05eccdc69e3a65e7d524b52548f567904a11a1a"
integrity sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==
"@revenuecat/purchases-typescript-internal@13.4.0":
version "13.4.0"
resolved "https://registry.yarnpkg.com/@revenuecat/purchases-typescript-internal/-/purchases-typescript-internal-13.4.0.tgz#e95c29e7c44c281022d1d2eb6fef67f62249f945"
integrity sha512-y3+tUEhBs7kcqd6pAQnX1x2rGfztCYAPzyzGR7gHgVKeBsGIu18GpokhjqzzxyJD/bNXJICwhnj4R4omf8KHmw==
"@rnx-kit/chromium-edge-launcher@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@rnx-kit/chromium-edge-launcher/-/chromium-edge-launcher-1.0.0.tgz#c0df8ea00a902c7a417cd9655aab06de398b939c"
@@ -18153,6 +18158,13 @@ react-native-progress@bluesky-social/react-native-progress:
dependencies:
prop-types "^15.7.2"
react-native-purchases@^8.2.3:
version "8.2.3"
resolved "https://registry.yarnpkg.com/react-native-purchases/-/react-native-purchases-8.2.3.tgz#8a4cb39ba5e15a8bc43cc33313b360e3dfa35139"
integrity sha512-5iWhfhebOHekqskrvL22xiJPiED+260JakMDtApA8XekkbEo+ozUEDUZ56x+Z/Q5t4uJo4SPylKxSpT6nHQ/rg==
dependencies:
"@revenuecat/purchases-typescript-internal" "13.4.0"
react-native-qrcode-styled@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/react-native-qrcode-styled/-/react-native-qrcode-styled-0.3.1.tgz#be6a0fab173511b0d3d8d71588771c2230982dbf"