···603603 IP string `json:"ip"`
604604}
605605type IPCCResponse struct {
606606- CC string `json:"countryCode"`
606606+ CC string `json:"countryCode"`
607607+ AgeRestrictedGeo string `json:"isAgeRestrictedGeo"`
607608}
608609609610func (srv *Server) WebIpCC(c echo.Context) error {
+1-1
src/App.native.tsx
···1717import * as Sentry from '@sentry/react-native'
18181919import {KeyboardControllerProvider} from '#/lib/hooks/useEnableKeyboardController'
2020+import {Provider as HideBottomBarBorderProvider} from '#/lib/hooks/useHideBottomBarBorder'
2021import {QueryProvider} from '#/lib/react-query'
2122import {Provider as StatsigProvider, tryFetchGates} from '#/lib/statsig/statsig'
2223import {s} from '#/lib/styles'
···7374import {Splash} from '#/Splash'
7475import {BottomSheetProvider} from '../modules/bottom-sheet'
7576import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
7676-import {Provider as HideBottomBarBorderProvider} from './lib/hooks/useHideBottomBarBorder'
77777878SplashScreen.preventAutoHideAsync()
7979if (isIOS) {
+124-22
src/Navigation.tsx
···11-import * as React from 'react'
11+import {useCallback, useRef} from 'react'
22+import * as Notifications from 'expo-notifications'
23import {i18n, type MessageDescriptor} from '@lingui/core'
34import {msg} from '@lingui/macro'
45import {
···1011 createNavigationContainerRef,
1112 DarkTheme,
1213 DefaultTheme,
1414+ type LinkingOptions,
1315 NavigationContainer,
1416 StackActions,
1517} from '@react-navigation/native'
16181719import {timeout} from '#/lib/async/timeout'
1820import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
2121+import {
2222+ getNotificationPayload,
2323+ type NotificationPayload,
2424+ notificationToURL,
2525+ storePayloadForAccountSwitch,
2626+} from '#/lib/hooks/useNotificationHandler'
1927import {useWebScrollRestoration} from '#/lib/hooks/useWebScrollRestoration'
2828+import {logger as notyLogger} from '#/lib/notifications/util'
2029import {buildStateObject} from '#/lib/routes/helpers'
2130import {
2231 type AllNavigatorParams,
···7180import {ModerationScreen} from '#/screens/Moderation'
7281import {Screen as ModerationVerificationSettings} from '#/screens/Moderation/VerificationSettings'
7382import {Screen as ModerationInteractionSettings} from '#/screens/ModerationInteractionSettings'
8383+import {NotificationsActivityListScreen} from '#/screens/Notifications/ActivityList'
7484import {PostLikedByScreen} from '#/screens/Post/PostLikedBy'
7585import {PostQuotesScreen} from '#/screens/Post/PostQuotes'
7686import {PostRepostedByScreen} from '#/screens/Post/PostRepostedBy'
···94104import {FollowingFeedPreferencesScreen} from '#/screens/Settings/FollowingFeedPreferences'
95105import {InterestsSettingsScreen} from '#/screens/Settings/InterestsSettings'
96106import {LanguageSettingsScreen} from '#/screens/Settings/LanguageSettings'
107107+import {LegacyNotificationSettingsScreen} from '#/screens/Settings/LegacyNotificationSettings'
108108+import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings'
109109+import {ActivityNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/ActivityNotificationSettings'
110110+import {LikeNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/LikeNotificationSettings'
111111+import {LikesOnRepostsNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings'
112112+import {MentionNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/MentionNotificationSettings'
113113+import {MiscellaneousNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/MiscellaneousNotificationSettings'
114114+import {NewFollowerNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/NewFollowerNotificationSettings'
115115+import {QuoteNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/QuoteNotificationSettings'
116116+import {ReplyNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/ReplyNotificationSettings'
117117+import {RepostNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/RepostNotificationSettings'
118118+import {RepostsOnRepostsNotificationSettingsScreen} from '#/screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings'
97119import {PrivacyAndSecuritySettingsScreen} from '#/screens/Settings/PrivacyAndSecuritySettings'
98120import {SettingsScreen} from '#/screens/Settings/Settings'
99121import {ThreadPreferencesScreen} from '#/screens/Settings/ThreadPreferences'
···111133} from '#/components/dialogs/EmailDialog'
112134import {router} from '#/routes'
113135import {Referrer} from '../modules/expo-bluesky-swiss-army'
114114-import {NotificationsActivityListScreen} from './screens/Notifications/ActivityList'
115115-import {LegacyNotificationSettingsScreen} from './screens/Settings/LegacyNotificationSettings'
116116-import {NotificationSettingsScreen} from './screens/Settings/NotificationSettings'
117117-import {ActivityNotificationSettingsScreen} from './screens/Settings/NotificationSettings/ActivityNotificationSettings'
118118-import {LikeNotificationSettingsScreen} from './screens/Settings/NotificationSettings/LikeNotificationSettings'
119119-import {LikesOnRepostsNotificationSettingsScreen} from './screens/Settings/NotificationSettings/LikesOnRepostsNotificationSettings'
120120-import {MentionNotificationSettingsScreen} from './screens/Settings/NotificationSettings/MentionNotificationSettings'
121121-import {MiscellaneousNotificationSettingsScreen} from './screens/Settings/NotificationSettings/MiscellaneousNotificationSettings'
122122-import {NewFollowerNotificationSettingsScreen} from './screens/Settings/NotificationSettings/NewFollowerNotificationSettings'
123123-import {QuoteNotificationSettingsScreen} from './screens/Settings/NotificationSettings/QuoteNotificationSettings'
124124-import {ReplyNotificationSettingsScreen} from './screens/Settings/NotificationSettings/ReplyNotificationSettings'
125125-import {RepostNotificationSettingsScreen} from './screens/Settings/NotificationSettings/RepostNotificationSettings'
126126-import {RepostsOnRepostsNotificationSettingsScreen} from './screens/Settings/NotificationSettings/RepostsOnRepostsNotificationSettings'
136136+import {useAccountSwitcher} from './lib/hooks/useAccountSwitcher'
137137+import {useNonReactiveCallback} from './lib/hooks/useNonReactiveCallback'
138138+import {useLoggedOutViewControls} from './state/shell/logged-out'
139139+import {useCloseAllActiveElements} from './state/util'
127140128141const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
129142···604617 * in 3 distinct tab-stacks with a different root screen on each.
605618 */
606619function TabsNavigator() {
607607- const tabBar = React.useCallback(
620620+ const tabBar = useCallback(
608621 (props: JSX.IntrinsicAttributes & BottomTabBarProps) => (
609622 <BottomBar {...props} />
610623 ),
···771784772785const LINKING = {
773786 // TODO figure out what we are going to use
787787+ // note: `bluesky://` is what is used in app.config.js
774788 prefixes: ['bsky://', 'bluesky://', 'https://bsky.app'],
775789776790 getPathFromState(state: State) {
···827841 return res
828842 }
829843 },
830830-}
844844+} satisfies LinkingOptions<AllNavigatorParams>
845845+846846+/**
847847+ * Used to ensure we don't handle the same notification twice
848848+ */
849849+let lastHandledNotificationDateDedupe: number | undefined
831850832851function RoutesContainer({children}: React.PropsWithChildren<{}>) {
833852 const theme = useColorSchemeStyle(DefaultTheme, DarkTheme)
834834- const {currentAccount} = useSession()
835835- const prevLoggedRouteName = React.useRef<string | undefined>(undefined)
853853+ const {currentAccount, accounts} = useSession()
854854+ const {onPressSwitchAccount} = useAccountSwitcher()
855855+ const {setShowLoggedOut} = useLoggedOutViewControls()
856856+ const prevLoggedRouteName = useRef<string | undefined>(undefined)
836857 const emailDialogControl = useEmailDialogControl()
858858+ const closeAllActiveElements = useCloseAllActiveElements()
859859+860860+ /**
861861+ * Handle navigation to a conversation, or prepares for account switch.
862862+ *
863863+ * Non-reactive because we need the latest data from some hooks
864864+ * after an async call - sfn
865865+ */
866866+ const handleChatMessage = useNonReactiveCallback(
867867+ (payload: Extract<NotificationPayload, {reason: 'chat-message'}>) => {
868868+ notyLogger.debug(`handleChatMessage`, {payload})
869869+870870+ if (payload.recipientDid !== currentAccount?.did) {
871871+ // handled in useNotificationHandler after account switch finishes
872872+ storePayloadForAccountSwitch(payload)
873873+ closeAllActiveElements()
874874+875875+ const account = accounts.find(a => a.did === payload.recipientDid)
876876+ if (account) {
877877+ onPressSwitchAccount(account, 'Notification')
878878+ } else {
879879+ setShowLoggedOut(true)
880880+ }
881881+ } else {
882882+ // @ts-expect-error nested navigators aren't typed -sfn
883883+ navigate('MessagesTab', {
884884+ screen: 'MessagesConversation',
885885+ params: {
886886+ conversation: payload.convoId,
887887+ },
888888+ })
889889+ }
890890+ },
891891+ )
892892+893893+ async function handlePushNotificationEntry() {
894894+ if (!isNative) return
895895+896896+ /**
897897+ * The notification that caused the app to open, if applicable
898898+ */
899899+ const response = await Notifications.getLastNotificationResponseAsync()
900900+901901+ if (response) {
902902+ notyLogger.debug(`handlePushNotificationEntry: response`, {response})
903903+904904+ if (response.notification.date === lastHandledNotificationDateDedupe)
905905+ return
906906+ lastHandledNotificationDateDedupe = response.notification.date
907907+908908+ const payload = getNotificationPayload(response.notification)
909909+910910+ if (payload) {
911911+ notyLogger.metric(
912912+ 'notifications:openApp',
913913+ {reason: payload.reason, causedBoot: true},
914914+ {statsig: false},
915915+ )
916916+917917+ if (payload.reason === 'chat-message') {
918918+ handleChatMessage(payload)
919919+ } else {
920920+ const path = notificationToURL(payload)
921921+922922+ if (path === '/notifications') {
923923+ resetToTab('NotificationsTab')
924924+ notyLogger.debug(`handlePushNotificationEntry: default navigate`)
925925+ } else if (path) {
926926+ const [screen, params] = router.matchPath(path)
927927+ // @ts-expect-error nested navigators aren't typed -sfn
928928+ navigate('HomeTab', {screen, params})
929929+ notyLogger.debug(`handlePushNotificationEntry: navigate`, {
930930+ screen,
931931+ params,
932932+ })
933933+ }
934934+ }
935935+ }
936936+ }
937937+ }
837938838939 function onReady() {
839940 prevLoggedRouteName.current = getCurrentRouteName()
···854955 onStateChange={() => {
855956 logger.metric(
856957 'router:navigate',
857857- {
858858- from: prevLoggedRouteName.current,
859859- },
958958+ {from: prevLoggedRouteName.current},
860959 {statsig: false},
861960 )
862961 prevLoggedRouteName.current = getCurrentRouteName()
···866965 logModuleInitTime()
867966 onReady()
868967 logger.metric('router:navigate', {}, {statsig: false})
968968+ handlePushNotificationEntry()
869969 }}
870970 // WARNING: Implicit navigation to nested navigators is depreciated in React Navigation 7.x
871971 // However, there's a fair amount of places we do that, especially in when popping to the top of stacks.
···9151015 return Promise.resolve()
9161016}
9171017918918-function resetToTab(tabName: 'HomeTab' | 'SearchTab' | 'NotificationsTab') {
10181018+function resetToTab(
10191019+ tabName: 'HomeTab' | 'SearchTab' | 'MessagesTab' | 'NotificationsTab',
10201020+) {
9191021 if (navigationRef.isReady()) {
9201022 navigate(tabName)
9211023 if (navigationRef.canGoBack()) {
+136-114
src/lib/hooks/useNotificationHandler.ts
···77import {useQueryClient} from '@tanstack/react-query'
8899import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
1010+import {logger as notyLogger} from '#/lib/notifications/util'
1011import {type NavigationProp} from '#/lib/routes/types'
1111-import {Logger} from '#/logger'
1212-import {isAndroid} from '#/platform/detection'
1212+import {isAndroid, isIOS} from '#/platform/detection'
1313import {useCurrentConvoId} from '#/state/messages/current-convo-id'
1414import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
1515import {invalidateCachedUnreadPage} from '#/state/queries/notifications/unread'
···1818import {useLoggedOutViewControls} from '#/state/shell/logged-out'
1919import {useCloseAllActiveElements} from '#/state/util'
2020import {resetToTab} from '#/Navigation'
2121+import {router} from '#/routes'
21222223export type NotificationReason =
2324 | 'like'
···3940 * `notification.request.trigger.payload` being `undefined`, as specified in
4041 * the source types.
4142 */
4242-type NotificationPayload =
4343+export type NotificationPayload =
4344 | undefined
4445 | {
4546 reason: Exclude<NotificationReason, 'chat-message'>
4647 uri: string
4748 subject: string
4949+ recipientDid: string
4850 }
4951 | {
5052 reason: 'chat-message'
···6062 shouldSetBadge: true,
6163} satisfies Notifications.NotificationBehavior
62646363-// These need to stay outside the hook to persist between account switches
6464-let storedPayload: NotificationPayload
6565-let prevDate = 0
6565+/**
6666+ * Cached notification payload if we handled a notification while the user was
6767+ * using a different account. This is consumed after we finish switching
6868+ * accounts.
6969+ */
7070+let storedAccountSwitchPayload: NotificationPayload
66716767-const logger = Logger.create(Logger.Context.Notifications)
7272+/**
7373+ * Used to ensure we don't handle the same notification twice
7474+ */
7575+let lastHandledNotificationDateDedupe = 0
68766977export function useNotificationsHandler() {
7078 const queryClient = useQueryClient()
···182190 if (!payload) return
183191184192 if (payload.reason === 'chat-message') {
185185- if (payload.recipientDid !== currentAccount?.did && !storedPayload) {
186186- storedPayload = payload
193193+ notyLogger.debug(`useNotificationsHandler: handling chat message`, {
194194+ payload,
195195+ })
196196+197197+ if (
198198+ payload.recipientDid !== currentAccount?.did &&
199199+ !storedAccountSwitchPayload
200200+ ) {
201201+ storePayloadForAccountSwitch(payload)
187202 closeAllActiveElements()
188203189204 const account = accounts.find(a => a.did === payload.recipientDid)
···227242 })
228243 }
229244 } else {
230230- switch (payload.reason) {
231231- case 'subscribed-post':
232232- const urip = new AtUri(payload.uri)
233233- if (urip.collection === 'app.bsky.feed.post') {
234234- setTimeout(() => {
235235- // @ts-expect-error types are weird here
236236- navigation.navigate('HomeTab', {
237237- screen: 'PostThread',
238238- params: {
239239- name: urip.host,
240240- rkey: urip.rkey,
241241- },
242242- })
243243- }, 500)
244244- } else {
245245- resetToTab('NotificationsTab')
246246- }
247247- break
248248- case 'like':
249249- case 'repost':
250250- case 'follow':
251251- case 'mention':
252252- case 'quote':
253253- case 'reply':
254254- case 'starterpack-joined':
255255- case 'like-via-repost':
256256- case 'repost-via-repost':
257257- case 'verified':
258258- case 'unverified':
259259- default:
260260- resetToTab('NotificationsTab')
261261- break
262262- // TODO implement these after we have an idea of how to handle each individual case
263263- // case 'follow':
264264- // const uri = new AtUri(payload.uri)
265265- // setTimeout(() => {
266266- // // @ts-expect-error types are weird here
267267- // navigation.navigate('HomeTab', {
268268- // screen: 'Profile',
269269- // params: {
270270- // name: uri.host,
271271- // },
272272- // })
273273- // }, 500)
274274- // break
275275- // case 'mention':
276276- // case 'reply':
277277- // const urip = new AtUri(payload.uri)
278278- // setTimeout(() => {
279279- // // @ts-expect-error types are weird here
280280- // navigation.navigate('HomeTab', {
281281- // screen: 'PostThread',
282282- // params: {
283283- // name: urip.host,
284284- // rkey: urip.rkey,
285285- // },
286286- // })
287287- // }, 500)
245245+ const url = notificationToURL(payload)
246246+247247+ if (url === '/notifications') {
248248+ resetToTab('NotificationsTab')
249249+ } else if (url) {
250250+ const [screen, params] = router.matchPath(url)
251251+ // @ts-expect-error router is not typed :/ -sfn
252252+ navigation.navigate('HomeTab', {screen, params})
253253+ notyLogger.debug(`useNotificationsHandler: navigate`, {
254254+ screen,
255255+ params,
256256+ })
288257 }
289258 }
290259 }
291260292261 Notifications.setNotificationHandler({
293262 handleNotification: async e => {
294294- if (
295295- e.request.trigger == null ||
296296- typeof e.request.trigger !== 'object' ||
297297- !('type' in e.request.trigger) ||
298298- e.request.trigger.type !== 'push'
299299- ) {
300300- return DEFAULT_HANDLER_OPTIONS
301301- }
263263+ const payload = getNotificationPayload(e)
302264303303- logger.debug('Notifications: received', {e})
304304-305305- const payload = e.request.trigger.payload as NotificationPayload
265265+ if (!payload) return DEFAULT_HANDLER_OPTIONS
306266307307- if (!payload) {
308308- return DEFAULT_HANDLER_OPTIONS
309309- }
267267+ notyLogger.debug('useNotificationsHandler: incoming', {e, payload})
310268311269 if (
312270 payload.reason === 'chat-message' &&
···329287330288 const responseReceivedListener =
331289 Notifications.addNotificationResponseReceivedListener(e => {
332332- if (e.notification.date === prevDate) {
333333- return
334334- }
335335- prevDate = e.notification.date
290290+ if (e.notification.date === lastHandledNotificationDateDedupe) return
291291+ lastHandledNotificationDateDedupe = e.notification.date
336292337337- logger.debug('Notifications: response received', {
293293+ notyLogger.debug('useNotificationsHandler: response received', {
338294 actionIdentifier: e.actionIdentifier,
339295 })
340296341341- if (
342342- e.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER &&
343343- e.notification.request.trigger != null &&
344344- typeof e.notification.request.trigger === 'object' &&
345345- 'type' in e.notification.request.trigger &&
346346- e.notification.request.trigger.type === 'push'
347347- ) {
348348- const payload = e.notification.request.trigger
349349- .payload as NotificationPayload
297297+ if (e.actionIdentifier !== Notifications.DEFAULT_ACTION_IDENTIFIER) {
298298+ return
299299+ }
350300351351- if (!payload) {
352352- logger.error('useNotificationsHandler: received no payload', {
353353- identifier: e.notification.request.identifier,
354354- })
355355- return
356356- }
301301+ const payload = getNotificationPayload(e.notification)
302302+303303+ if (payload) {
357304 if (!payload.reason) {
358358- logger.error('useNotificationsHandler: received unknown payload', {
359359- payload,
360360- identifier: e.notification.request.identifier,
361361- })
305305+ notyLogger.error(
306306+ 'useNotificationsHandler: received unknown payload',
307307+ {
308308+ payload,
309309+ identifier: e.notification.request.identifier,
310310+ },
311311+ )
362312 return
363313 }
364314365365- logger.debug(
315315+ notyLogger.debug(
366316 'User pressed a notification, opening notifications tab',
367317 {},
368318 )
369369- logger.metric(
319319+ notyLogger.metric(
370320 'notifications:openApp',
371371- {reason: payload.reason},
321321+ {reason: payload.reason, causedBoot: false},
372322 {statsig: false},
373323 )
374324···383333 truncateAndInvalidate(queryClient, RQKEY_NOTIFS('mentions'))
384334 }
385335386386- logger.debug('Notifications: handleNotification', {
336336+ notyLogger.debug('Notifications: handleNotification', {
387337 content: e.notification.request.content,
388388- payload: e.notification.request.trigger.payload,
338338+ payload: payload,
389339 })
390340391341 handleNotification(payload)
392342 Notifications.dismissAllNotificationsAsync()
343343+ } else {
344344+ notyLogger.error('useNotificationsHandler: received no payload', {
345345+ identifier: e.notification.request.identifier,
346346+ })
393347 }
394348 })
395349396350 // Whenever there's a stored payload, that means we had to switch accounts before handling the notification.
397351 // Whenever currentAccount changes, we should try to handle it again.
398352 if (
399399- storedPayload?.reason === 'chat-message' &&
400400- currentAccount?.did === storedPayload.recipientDid
353353+ storedAccountSwitchPayload?.reason === 'chat-message' &&
354354+ currentAccount?.did === storedAccountSwitchPayload.recipientDid
401355 ) {
402402- handleNotification(storedPayload)
403403- storedPayload = undefined
356356+ handleNotification(storedAccountSwitchPayload)
357357+ storedAccountSwitchPayload = undefined
404358 }
405359406360 return () => {
···418372 setShowLoggedOut,
419373 ])
420374}
375375+376376+export function storePayloadForAccountSwitch(payload: NotificationPayload) {
377377+ storedAccountSwitchPayload = payload
378378+}
379379+380380+export function getNotificationPayload(
381381+ e: Notifications.Notification,
382382+): NotificationPayload | null {
383383+ if (
384384+ e.request.trigger == null ||
385385+ typeof e.request.trigger !== 'object' ||
386386+ !('type' in e.request.trigger) ||
387387+ e.request.trigger.type !== 'push'
388388+ ) {
389389+ return null
390390+ }
391391+392392+ const payload = (
393393+ isIOS ? e.request.trigger.payload : e.request.content.data
394394+ ) as NotificationPayload
395395+396396+ if (payload) {
397397+ return payload
398398+ } else {
399399+ return null
400400+ }
401401+}
402402+403403+export function notificationToURL(
404404+ payload: NotificationPayload,
405405+): string | undefined {
406406+ switch (payload?.reason) {
407407+ case 'like':
408408+ case 'repost':
409409+ case 'like-via-repost':
410410+ case 'repost-via-repost': {
411411+ const urip = new AtUri(payload.subject)
412412+ if (urip.collection === 'app.bsky.feed.post') {
413413+ return `/profile/${urip.host}/post/${urip.rkey}`
414414+ } else {
415415+ return '/notifications'
416416+ }
417417+ }
418418+ case 'reply':
419419+ case 'quote':
420420+ case 'mention':
421421+ case 'subscribed-post': {
422422+ const urip = new AtUri(payload.uri)
423423+ if (urip.collection === 'app.bsky.feed.post') {
424424+ return `/profile/${urip.host}/post/${urip.rkey}`
425425+ } else {
426426+ return '/notifications'
427427+ }
428428+ }
429429+ case 'follow':
430430+ case 'starterpack-joined': {
431431+ const urip = new AtUri(payload.uri)
432432+ return `/profile/${urip.host}`
433433+ }
434434+ case 'chat-message':
435435+ // should be handled separately
436436+ return undefined
437437+ case 'verified':
438438+ case 'unverified':
439439+ default:
440440+ return '/notifications'
441441+ }
442442+}
···5959 enabled: enabled && !!moderationOpts,
6060 filter,
6161 })
6262- const isEmpty = !isFetching && !data?.pages[0]?.items.length
6262+ // previously, this was `!isFetching && !data?.pages[0]?.items.length`
6363+ // however, if the first page had no items (can happen in the mentions tab!)
6464+ // it would flicker the empty state whenever it was loading.
6565+ // therefore, we need to find if *any* page has items. in 99.9% of cases,
6666+ // the `.find()` won't need to go any further than the first page -sfn
6767+ const isEmpty =
6868+ !isFetching && !data?.pages.find(page => page.items.length > 0)
63696470 const items = React.useMemo(() => {
6571 let arr: any[] = []