diff --git a/packages/app-root/.eslintrc.json b/packages/app-root/.eslintrc.json
new file mode 100644
index 0000000000..5522f86278
--- /dev/null
+++ b/packages/app-root/.eslintrc.json
@@ -0,0 +1,20 @@
+{
+ "extends": [
+ "next/core-web-vitals",
+ "plugin:jsx-a11y/recommended",
+ "plugin:@next/next/recommended"
+ ],
+ "rules": {
+ "consistent-return": "error"
+ },
+ "overrides": [
+ {
+ "files": [
+ "src/**/*.stories.js"
+ ],
+ "rules": {
+ "import/no-anonymous-default-export": "off"
+ }
+ }
+ ]
+}
diff --git a/packages/app-root/next.config.mjs b/packages/app-root/next.config.mjs
index a2a6e99052..026a6646f5 100644
--- a/packages/app-root/next.config.mjs
+++ b/packages/app-root/next.config.mjs
@@ -4,6 +4,10 @@ const bundleAnalyzer = withBundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
})
-const nextConfig = {}
+const nextConfig = {
+ experimental: {
+ optimizePackageImports: ['@zooniverse/react-components', 'grommet', 'grommet-icons'],
+ }
+}
export default bundleAnalyzer(nextConfig)
diff --git a/packages/app-root/package.json b/packages/app-root/package.json
index 7f7bc2adea..370d1c37db 100644
--- a/packages/app-root/package.json
+++ b/packages/app-root/package.json
@@ -6,9 +6,9 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev",
+ "dev": "APP_ENV=${APP_ENV:-development} PANOPTES_ENV=${PANOPTES_ENV:-staging} node server/server.js",
"build": "next build",
- "start": "next start",
+ "start": "NODE_ENV=${NODE_ENV:-production} PANOPTES_ENV=${PANOPTES_ENV:-production} node server/server.js",
"lint": "next lint"
},
"type": "module",
@@ -17,17 +17,24 @@
"@zooniverse/grommet-theme": "~3.1.1",
"@zooniverse/panoptes-js": "~0.4.1",
"@zooniverse/react-components": "~1.6.1",
+ "express": "~4.18.2",
"grommet": "~2.33.2",
"grommet-icons": "~4.11.0",
+ "newrelic": "~11.2.0",
"next": "~13.5.5",
+ "panoptes-client": "~5.5.6",
"react": "~18.2.0",
"react-dom": "~18.2.0",
- "styled-components": "~5.3.10"
+ "styled-components": "~5.3.10",
+ "swr": "~2.2.4"
},
"engines": {
"node": ">=20.5"
},
"devDependencies": {
- "@next/bundle-analyzer": "~13.5.4"
+ "@next/bundle-analyzer": "~13.5.5",
+ "eslint-config-next": "~13.5.5",
+ "eslint-plugin-jsx-a11y": "~6.7.0",
+ "selfsigned": "~2.1.1"
}
}
diff --git a/packages/app-root/server/server.js b/packages/app-root/server/server.js
new file mode 100644
index 0000000000..e69efba623
--- /dev/null
+++ b/packages/app-root/server/server.js
@@ -0,0 +1,53 @@
+if (process.env.NEWRELIC_LICENSE_KEY) {
+ await import('newrelic')
+}
+
+import express from 'express'
+import next from 'next'
+
+const port = parseInt(process.env.PORT, 10) || 3000
+const dev = process.env.NODE_ENV !== 'production'
+
+const APP_ENV = process.env.APP_ENV || 'development'
+
+const hostnames = {
+ development: 'local.zooniverse.org',
+ branch: 'fe-project-branch.preview.zooniverse.org',
+ staging: 'frontend.preview.zooniverse.org',
+ production : 'www.zooniverse.org'
+}
+const hostname = hostnames[APP_ENV]
+
+const app = next({ dev, hostname, port })
+const handle = app.getRequestHandler()
+
+app.prepare().then(async () => {
+ const server = express()
+
+ server.get('*', (req, res) => {
+ return handle(req, res)
+ })
+
+ let selfsigned
+ try {
+ selfsigned = await import('selfsigned')
+ } catch (error) {
+ console.error(error)
+ }
+ if (APP_ENV === 'development' && selfsigned) {
+ const https = await import('https')
+
+ const attrs = [{ name: 'commonName', value: hostname }];
+ const { cert, private: key } = selfsigned.generate(attrs, { days: 365 })
+ return https.createServer({ cert, key }, server)
+ .listen(port, err => {
+ if (err) throw err
+ console.log(`> Ready on https://${hostname}:${port}`)
+ })
+ } else {
+ return server.listen(port, err => {
+ if (err) throw err
+ console.log(`> Ready on http://${hostname}:${port}`)
+ })
+ }
+})
diff --git a/packages/app-root/src/app/about/page.js b/packages/app-root/src/app/about/page.js
index 197074ea7e..ad09698f91 100644
--- a/packages/app-root/src/app/about/page.js
+++ b/packages/app-root/src/app/about/page.js
@@ -1,5 +1,8 @@
export default function AboutPage() {
return (
+
This is lib-content-pages
diff --git a/packages/app-root/src/app/projects/page.js b/packages/app-root/src/app/projects/page.js
index f4575f3406..f7fc4be5b3 100644
--- a/packages/app-root/src/app/projects/page.js
+++ b/packages/app-root/src/app/projects/page.js
@@ -1,5 +1,8 @@
export default function ProjectPage() {
return (
+
diff --git a/packages/app-root/src/components/PageContextProviders.js b/packages/app-root/src/components/PageContextProviders.js
new file mode 100644
index 0000000000..a0dfffddc7
--- /dev/null
+++ b/packages/app-root/src/components/PageContextProviders.js
@@ -0,0 +1,42 @@
+'use client'
+
+import zooTheme from '@zooniverse/grommet-theme'
+import { Grommet } from 'grommet'
+import { createGlobalStyle } from 'styled-components'
+
+import { PanoptesAuthContext } from '../contexts'
+import { useAdminMode, usePanoptesUser } from '../hooks'
+
+const GlobalStyle = createGlobalStyle`
+ body {
+ margin: 0;
+ }
+`
+
+/**
+ Context for every page:
+ - global page styles.
+ - Zooniverse Grommet theme.
+ - Panoptes auth (user account and admin mode.)
+*/
+export default function PageContextProviders({ children }) {
+ const { data: user, error, isLoading } = usePanoptesUser()
+ const { adminMode, toggleAdmin } = useAdminMode(user)
+ const authContext = { adminMode, error, isLoading, toggleAdmin, user }
+
+ return (
+
+
+
+ {children}
+
+
+ )
+
+}
\ No newline at end of file
diff --git a/packages/app-root/src/components/PageFooter.js b/packages/app-root/src/components/PageFooter.js
new file mode 100644
index 0000000000..934dd72a4c
--- /dev/null
+++ b/packages/app-root/src/components/PageFooter.js
@@ -0,0 +1,15 @@
+'use client'
+import { AdminCheckbox, ZooFooter } from '@zooniverse/react-components'
+import { useContext } from 'react'
+
+import { PanoptesAuthContext } from '../contexts'
+
+export default function PageFooter() {
+ const { adminMode, toggleAdmin, user } = useContext(PanoptesAuthContext)
+
+ return (
+ : null}
+ />
+ )
+}
\ No newline at end of file
diff --git a/packages/app-root/src/components/PageHeader.js b/packages/app-root/src/components/PageHeader.js
new file mode 100644
index 0000000000..82e1119a13
--- /dev/null
+++ b/packages/app-root/src/components/PageHeader.js
@@ -0,0 +1,27 @@
+'use client'
+import { ZooHeader } from '@zooniverse/react-components'
+import { useContext } from 'react'
+
+import {
+ useUnreadMessages,
+ useUnreadNotifications
+} from '../hooks'
+
+import { PanoptesAuthContext } from '../contexts'
+
+export default function PageHeader() {
+ const { adminMode, user } = useContext(PanoptesAuthContext)
+ const { data: unreadMessages }= useUnreadMessages(user)
+ const { data: unreadNotifications }= useUnreadNotifications(user)
+
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/packages/app-root/src/components/RootLayout.js b/packages/app-root/src/components/RootLayout.js
index 6d7d3e1471..8a30856aa6 100644
--- a/packages/app-root/src/components/RootLayout.js
+++ b/packages/app-root/src/components/RootLayout.js
@@ -1,37 +1,15 @@
-'use client'
-/**
- * Note that all child components are now client components.
- * If we want children of RootLayout to be server components
- * a ZooHeaderContainer and ZooFooterContainer could be created instead.
- */
-
-import { createGlobalStyle } from 'styled-components'
-import { Grommet } from 'grommet'
-import zooTheme from '@zooniverse/grommet-theme'
-import ZooHeader from '@zooniverse/react-components/ZooHeader'
-import ZooFooter from '@zooniverse/react-components/ZooFooter'
-
-const GlobalStyle = createGlobalStyle`
- body {
- margin: 0;
- }
-`
+import PageContextProviders from './PageContextProviders.js'
+import PageHeader from './PageHeader.js'
+import PageFooter from './PageFooter.js'
export default function RootLayout({ children }) {
return (
-
-
-
+
+
{children}
-
-
+
+
)
}
diff --git a/packages/app-root/src/contexts/PanoptesAuthContext.js b/packages/app-root/src/contexts/PanoptesAuthContext.js
new file mode 100644
index 0000000000..c39b6e57f6
--- /dev/null
+++ b/packages/app-root/src/contexts/PanoptesAuthContext.js
@@ -0,0 +1,5 @@
+import { createContext } from 'react'
+
+const PanoptesAuthContext = createContext({})
+
+export default PanoptesAuthContext
diff --git a/packages/app-root/src/contexts/index.js b/packages/app-root/src/contexts/index.js
new file mode 100644
index 0000000000..5c21818a37
--- /dev/null
+++ b/packages/app-root/src/contexts/index.js
@@ -0,0 +1 @@
+export { default as PanoptesAuthContext } from './PanoptesAuthContext.js'
diff --git a/packages/app-root/src/helpers/fetchPanoptesUser.js b/packages/app-root/src/helpers/fetchPanoptesUser.js
new file mode 100644
index 0000000000..caa89f3676
--- /dev/null
+++ b/packages/app-root/src/helpers/fetchPanoptesUser.js
@@ -0,0 +1,38 @@
+import auth from 'panoptes-client/lib/auth'
+import { auth as authHelpers } from '@zooniverse/panoptes-js'
+
+/**
+ Get a Panoptes user from a Panoptes JSON Web Token (JWT), if we have one, or from
+ the Panoptes API otherwise.
+*/
+export default async function fetchPanoptesUser({ user: storedUser }) {
+ try {
+ const jwt = await auth.checkBearerToken()
+ /*
+ `crypto.subtle` is needed to decrypt the Panoptes JWT.
+ It will only exist for https:// URLs.
+ */
+ const isSecure = crypto?.subtle
+ if (jwt && isSecure) {
+ /*
+ avatar_src isn't encoded in the Panoptes JWT, so we need to add it.
+ https://github.com/zooniverse/panoptes/issues/4217
+ */
+ const { user, error } = await authHelpers.decodeJWT(jwt)
+ if (user) {
+ const { admin, display_name, id, login } = user
+ return {
+ avatar_src: storedUser.avatar_src,
+ ...user
+ }
+ }
+ if (error) {
+ throw error
+ }
+ }
+ } catch (error) {
+ console.log(error)
+ }
+ const { admin, avatar_src, display_name, id, login } = await auth.checkCurrent()
+ return { admin, avatar_src, display_name, id, login }
+}
diff --git a/packages/app-root/src/helpers/index.js b/packages/app-root/src/helpers/index.js
new file mode 100644
index 0000000000..025ab766d9
--- /dev/null
+++ b/packages/app-root/src/helpers/index.js
@@ -0,0 +1 @@
+export { default as fetchPanoptesUser } from './fetchPanoptesUser.js'
diff --git a/packages/app-root/src/hooks/index.js b/packages/app-root/src/hooks/index.js
new file mode 100644
index 0000000000..e6419b719c
--- /dev/null
+++ b/packages/app-root/src/hooks/index.js
@@ -0,0 +1,4 @@
+export { default as useAdminMode } from './useAdminMode.js'
+export { default as usePanoptesUser } from './usePanoptesUser.js'
+export { default as useUnreadMessages } from './useUnreadMessages.js'
+export { default as useUnreadNotifications } from './useUnreadNotifications.js'
diff --git a/packages/app-root/src/hooks/useAdminMode.js b/packages/app-root/src/hooks/useAdminMode.js
new file mode 100644
index 0000000000..627cc485d3
--- /dev/null
+++ b/packages/app-root/src/hooks/useAdminMode.js
@@ -0,0 +1,44 @@
+import { useEffect, useState } from 'react'
+
+const isBrowser = typeof window !== 'undefined'
+const localStorage = isBrowser ? window.localStorage : null
+const storedAdminFlag = !!localStorage?.getItem('adminFlag')
+const adminBorderImage = 'repeating-linear-gradient(45deg,#000,#000 25px,#ff0 25px,#ff0 50px) 5'
+
+export default function useAdminMode(user) {
+ const [adminState, setAdminState] = useState(storedAdminFlag)
+ const adminMode = user?.admin && adminState
+
+ useEffect(function onUserChange() {
+ const isAdmin = user?.admin
+ if (isAdmin) {
+ const adminFlag = !!localStorage?.getItem('adminFlag')
+ setAdminState(adminFlag)
+ } else {
+ localStorage?.removeItem('adminFlag')
+ }
+ }, [user?.admin])
+
+ useEffect(function onAdminChange() {
+ if (adminMode) {
+ document.body.style.border = '5px solid'
+ document.body.style.borderImage = adminBorderImage
+ }
+ return () => {
+ document.body.style.border = ''
+ document.body.style.borderImage = ''
+ }
+ }, [adminMode])
+
+ function toggleAdmin() {
+ let newAdminState = !adminState
+ setAdminState(newAdminState)
+ if (newAdminState) {
+ localStorage?.setItem('adminFlag', true)
+ } else {
+ localStorage?.removeItem('adminFlag')
+ }
+ }
+
+ return { adminMode, toggleAdmin }
+}
\ No newline at end of file
diff --git a/packages/app-root/src/hooks/usePanoptesUser.js b/packages/app-root/src/hooks/usePanoptesUser.js
new file mode 100644
index 0000000000..4203507a95
--- /dev/null
+++ b/packages/app-root/src/hooks/usePanoptesUser.js
@@ -0,0 +1,66 @@
+import auth from 'panoptes-client/lib/auth'
+import { useEffect } from 'react'
+import useSWR from 'swr'
+
+import { fetchPanoptesUser } from '../helpers'
+
+const isBrowser = typeof window !== 'undefined'
+
+const SWROptions = {
+ revalidateIfStale: true,
+ revalidateOnMount: true,
+ revalidateOnFocus: true,
+ revalidateOnReconnect: true,
+ refreshInterval: 0
+}
+
+if (isBrowser) {
+ auth.checkCurrent()
+}
+
+const localStorage = isBrowser ? window.localStorage : null
+const storedUserJSON = localStorage?.getItem('panoptes-user')
+let storedUser = storedUserJSON && JSON.parse(storedUserJSON)
+/*
+ Null users crash the ZooHeader component.
+ Set them to undefined for now.
+*/
+if (storedUser === null) {
+ storedUser = undefined
+}
+
+export default function usePanoptesUser() {
+ const key = {
+ user: storedUser,
+ endpoint: '/me'
+ }
+
+ /*
+ `useSWR` here will always return the same stale user object.
+ See https://github.com/zooniverse/panoptes-javascript-client/issues/207
+ */
+ const { data, error, isLoading } = useSWR(key, fetchPanoptesUser, SWROptions)
+ if (data) {
+ storedUser = data
+ }
+
+ useEffect(function subscribeToAuthChanges() {
+ auth.listen('change', auth.checkCurrent)
+
+ return function () {
+ auth.stopListening('change', auth.checkCurrent)
+ }
+ }, [])
+
+ useEffect(function persistUserInStorage() {
+ if (data) {
+ localStorage?.setItem('panoptes-user', JSON.stringify(data))
+ }
+
+ return () => {
+ localStorage?.removeItem('panoptes-user')
+ }
+ }, [data])
+
+ return { data: storedUser, error, isLoading }
+}
diff --git a/packages/app-root/src/hooks/useUnreadMessages.js b/packages/app-root/src/hooks/useUnreadMessages.js
new file mode 100644
index 0000000000..b92af111f3
--- /dev/null
+++ b/packages/app-root/src/hooks/useUnreadMessages.js
@@ -0,0 +1,55 @@
+import { talkAPI } from '@zooniverse/panoptes-js'
+import auth from 'panoptes-client/lib/auth'
+import useSWR from 'swr'
+
+const SWROptions = {
+ revalidateIfStale: true,
+ revalidateOnMount: true,
+ revalidateOnFocus: true,
+ revalidateOnReconnect: true,
+ refreshInterval: 0
+}
+
+async function fetchUnreadMessageCount({ endpoint = '/conversations' }) {
+ const token = await auth.checkBearerToken()
+ const authorization = `Bearer ${token}`
+ if (!authorization) return undefined
+
+ let unreadConversationsIds = []
+
+ async function getConversations (page = 1) {
+ const query = {
+ unread: true,
+ page: page
+ }
+
+ const response = await talkAPI.get(endpoint, query, { authorization })
+ const { meta, conversations } = response?.body || {}
+
+ if (conversations && conversations.length) {
+ unreadConversationsIds = unreadConversationsIds.concat(
+ conversations.map(conversation => conversation.id)
+ )
+ }
+
+ if (meta?.next_page) {
+ return getConversations(meta.next_page)
+ }
+
+ return unreadConversationsIds
+ }
+
+ await getConversations(1)
+ return unreadConversationsIds.length
+}
+
+export default function useUnreadMessages(user) {
+ let key = null
+ if (user) {
+ key = {
+ user,
+ endpoint: '/conversations'
+ }
+ }
+ return useSWR(key, fetchUnreadMessageCount, SWROptions)
+}
diff --git a/packages/app-root/src/hooks/useUnreadNotifications.js b/packages/app-root/src/hooks/useUnreadNotifications.js
new file mode 100644
index 0000000000..ab098060da
--- /dev/null
+++ b/packages/app-root/src/hooks/useUnreadNotifications.js
@@ -0,0 +1,36 @@
+import { talkAPI } from '@zooniverse/panoptes-js'
+import auth from 'panoptes-client/lib/auth'
+import useSWR from 'swr'
+
+const SWROptions = {
+ revalidateIfStale: true,
+ revalidateOnMount: true,
+ revalidateOnFocus: true,
+ revalidateOnReconnect: true,
+ refreshInterval: 0
+}
+
+async function fetchUnreadNotificationsCount({ endpoint = '/notifications' }) {
+ const token = await auth.checkBearerToken()
+ const authorization = `Bearer ${token}`
+ if (!authorization) return undefined
+
+ const query = {
+ delivered: false,
+ page_size: 1
+ }
+
+ const response = await talkAPI.get(endpoint, query, { authorization })
+ return response?.body?.meta?.notifications?.count
+}
+
+export default function useUnreadNotifications(user) {
+ let key = null
+ if (user) {
+ key = {
+ user,
+ endpoint: '/notifications'
+ }
+ }
+ return useSWR(key, fetchUnreadNotificationsCount, SWROptions)
+}
diff --git a/yarn.lock b/yarn.lock
index a6918894d0..6edc4d73fd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1983,7 +1983,7 @@
resolved "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz"
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
-"@grpc/grpc-js@^1.9.4":
+"@grpc/grpc-js@^1.8.10", "@grpc/grpc-js@^1.9.4":
version "1.9.7"
resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.7.tgz#7d0e29bc162287bee2523901c9bc9320d8402397"
integrity sha512-yMaA/cIsRhGzW3ymCNpdlPcInXcovztlgu/rirThj2b87u3RzWUszliOqZ/pldy7yhmJPS8uwog+kZSTa4A0PQ==
@@ -2260,12 +2260,12 @@
pump "^3.0.0"
tar-fs "^2.1.1"
-"@newrelic/aws-sdk@^7.0.2":
+"@newrelic/aws-sdk@^7.0.0", "@newrelic/aws-sdk@^7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@newrelic/aws-sdk/-/aws-sdk-7.0.2.tgz#e93f1796c89be8323a75f3d7ec45b1bdd5a29292"
integrity sha512-nT19hzId0MbjR3v1ks5YetvNfrwIEgMfeai+T2pQkuWkjCsYm3z+OybLOYMCN66gueqOOqGTq60qhM4dFu5s5w==
-"@newrelic/koa@^8.0.1":
+"@newrelic/koa@^8.0.0", "@newrelic/koa@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@newrelic/koa/-/koa-8.0.1.tgz#26c1c6a69b15ad4b64a148b6be537ec2ca734206"
integrity sha512-GyeZGKPllpUu6gWXRwVP/FlvE9+tU2lOprRiTdoXNM8jdVGL02IfHnvAzrIANoZoUdf3+Vev8NNeCup2Eojcvg==
@@ -2307,12 +2307,17 @@
uuid "^9.0.0"
ws "^7.5.9"
+"@newrelic/superagent@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/@newrelic/superagent/-/superagent-7.0.0.tgz"
+ integrity sha512-fNB4NC+pJYYrFZRLcXaTb4Z7XFEfHi7fVQ3O9Qh10m/9CBM2W+Qc/6yyK9M1liRfgUGo5NOILRdjA23SS7720A==
+
"@newrelic/superagent@^7.0.1":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@newrelic/superagent/-/superagent-7.0.1.tgz#8d5bb92579cf0b291e1298f480c4939a3d70ec09"
integrity sha512-QZlW0VxHSVOXcMAtlkg+Mth0Nz3vFku8rfzTEmoI/pXcckHXGEYuiVUhhboCTD3xTKVgnZRUp9BWF6SOggGUSw==
-"@next/bundle-analyzer@~13.5.4":
+"@next/bundle-analyzer@~13.5.5":
version "13.5.5"
resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-13.5.5.tgz#301edbfe05ff910ce3c9ba691ea2a6257e0032cb"
integrity sha512-v69BJm8ONM/e6l39Ao0ar8TwZyFnhI5s6id8LGayNq/3JaqkbzW97bIcBkTI0H9RiX3zZNIiaIyMgdKcbJqvsw==
@@ -9254,7 +9259,7 @@ execa@^5.0.0, execa@^5.1.1:
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
-express@^4.17.1, express@^4.17.3:
+express@^4.17.1, express@^4.17.3, express@~4.18.2:
version "4.18.2"
resolved "https://registry.npmjs.org/express/-/express-4.18.2.tgz"
integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
@@ -12972,6 +12977,33 @@ newrelic@^11.0.0, newrelic@~11.4.0:
"@newrelic/native-metrics" "^10.0.0"
"@prisma/prisma-fmt-wasm" "^4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085"
+newrelic@~11.2.0:
+ version "11.2.0"
+ resolved "https://registry.yarnpkg.com/newrelic/-/newrelic-11.2.0.tgz#eded32c7b7d97cae36e45396e8926a201e441793"
+ integrity sha512-gkt6c5nphsKTRBmKd0H12xELwnhdV9Xph5CL8IXT7nj0C1gL/xxfuTrwj6g+JqDvVz983iNNfdfXBEhIUJC4nQ==
+ dependencies:
+ "@grpc/grpc-js" "^1.8.10"
+ "@grpc/proto-loader" "^0.7.5"
+ "@newrelic/aws-sdk" "^7.0.0"
+ "@newrelic/koa" "^8.0.0"
+ "@newrelic/security-agent" "0.3.0"
+ "@newrelic/superagent" "^7.0.0"
+ "@tyriar/fibonacci-heap" "^2.0.7"
+ concat-stream "^2.0.0"
+ https-proxy-agent "^7.0.1"
+ import-in-the-middle "^1.4.2"
+ json-bigint "^1.0.0"
+ json-stringify-safe "^5.0.0"
+ module-details-from-path "^1.0.3"
+ readable-stream "^3.6.1"
+ require-in-the-middle "^7.2.0"
+ semver "^7.5.2"
+ winston-transport "^4.5.0"
+ optionalDependencies:
+ "@contrast/fn-inspect" "^3.3.0"
+ "@newrelic/native-metrics" "^10.0.0"
+ "@prisma/prisma-fmt-wasm" "^4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085"
+
next-absolute-url@~1.2.2:
version "1.2.2"
resolved "https://registry.npmjs.org/next-absolute-url/-/next-absolute-url-1.2.2.tgz"
@@ -13814,7 +13846,7 @@ pako@~1.0.5:
resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
-panoptes-client@~5.5.1:
+panoptes-client@~5.5.1, panoptes-client@~5.5.6:
version "5.5.6"
resolved "https://registry.npmjs.org/panoptes-client/-/panoptes-client-5.5.6.tgz"
integrity sha512-TvcKIS7ggrfuh8dA+9ORgHw53lWCoRjyIZWtSjOGOlIIBB2QF+3dPEgyDUltQ6Kpo49TV7PRAYNczJI3GGn07w==
@@ -15590,6 +15622,13 @@ selfsigned@^2.1.1, selfsigned@~2.4.1:
"@types/node-forge" "^1.3.0"
node-forge "^1"
+selfsigned@~2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.2.tgz#9f9a4b0d472a5f29f892eb52358056c61a7387e3"
+ integrity sha512-xc6ZKMc9owNuU3uEPuW45RnSPylOlRK5Brj8oWf/2+BQV2gD1c+/eJaHFCcTG8w8kRkEfb5mzn/yIpie6gJ1tA==
+ dependencies:
+ node-forge "^1"
+
"semver@2 || 3 || 4 || 5", semver@^5.6.0:
version "5.7.2"
resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz"
@@ -16401,7 +16440,7 @@ swc-loader@^0.2.3:
resolved "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.3.tgz"
integrity sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==
-swr@~2.2.0:
+swr@~2.2.0, swr@~2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.4.tgz#03ec4c56019902fbdc904d78544bd7a9a6fa3f07"
integrity sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==