Convert worker to ESM (#500)

* Convert worker to ESM

To use ESM for the worker, I created a package.json file in worker/ with `{ type: "module" }` as its sole content.

I then rewrote every import to use ESM syntax.

I also tried to set `{ type: "module" }` in the root package.json file to also use ESM in next.config.js.

However, this resulted in a weird problem: default imports were now getting imported as objects in this shape: `{ default: <defaultImport> }`.

Afaik, this should only be the case if you use "import * as foo from 'bar'" syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#default_import

This is fixed by not using `{ type: "module" }` for some reason. However, then, next.config.js also doesn't support ESM import syntax anymore.

The documentation says that if you want to use ESM, you can use next.config.mjs: https://nextjs.org/docs/pages/api-reference/next-config-js

But I didn't want to use MJS extension since I don't have any experience with it. For example, not sure if it's good style to mix JS with MJS etc. So I kept the CJS import syntax there.

* Ignore worker/ during linting

I wasn't able to fix the following errors:

/home/runner/work/stacker.news/stacker.news/worker/auction.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/auction.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)
/home/runner/work/stacker.news/stacker.news/worker/earn.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/earn.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)
/home/runner/work/stacker.news/stacker.news/worker/index.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/index.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)
/home/runner/work/stacker.news/stacker.news/worker/nostr.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/nostr.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)
/home/runner/work/stacker.news/stacker.news/worker/ots.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/ots.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)
/home/runner/work/stacker.news/stacker.news/worker/repin.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/repin.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)
/home/runner/work/stacker.news/stacker.news/worker/search.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/search.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)
/home/runner/work/stacker.news/stacker.news/worker/streak.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/streak.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)
/home/runner/work/stacker.news/stacker.news/worker/trust.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/trust.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)
/home/runner/work/stacker.news/stacker.news/worker/views.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/views.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)
/home/runner/work/stacker.news/stacker.news/worker/wallet.js:0:0: Parsing error: No Babel config file detected for /home/runner/work/stacker.news/stacker.news/worker/wallet.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. (null)

I tried to tell babel where to find the babel config file (.babelrc), specifying the babel config in worker/package.json under "babel", using babel.config.json etc. to no avail.

However, afaict, we don't need babel for the worker since it won't run in a browser. Babel is only used to transpile code to target browsers.

But it still would be nice to lint the worker code with standard.

But we can figure this out later.

* Fix worker imports from lib/ and api/

This fixes the issue that we can't use `{ "type": "module" }` in the root package.json since it breaks the app with this error:

  app  | TypeError: next_auth_providers_credentials__WEBPACK_IMPORTED_MODULE_2__ is not a function
  app  |     at eval (webpack-internal:///./pages/api/auth/[...nextauth].js:218:20)
  app  |     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
  app  | LND GRPC connection successful
  app  | - error pages/api/auth/[...nextauth].js (139:2) @ CredentialsProvider
  app  | - error Error [TypeError]: next_auth_providers_credentials__WEBPACK_IMPORTED_MODULE_2__ is not a function
  app  |     at eval (webpack-internal:///./pages/api/auth/[...nextauth].js:218:20) {
  app  |   digest: undefined
  app  | }
  app  |   137 |
  app  |   138 | const providers = [
  app  | > 139 |   CredentialsProvider({
  app  |       |  ^
  app  |   140 |     id: 'lightning',
  app  |   141 |     name: 'Lightning',
  app  |   142 |     credentials: {
  app  | TypeError: next_auth_providers_credentials__WEBPACK_IMPORTED_MODULE_2__ is not a function
  app  |     at eval (webpack-internal:///./pages/api/auth/[...nextauth].js:218:20)
  app  |     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

build but we need to tell the worker that the files are MJS, else we get this error:

  worker  | file:///app/worker/wallet.js:3
  worker  | import { datePivot } from '../lib/time.js'
  worker  |          ^^^^^^^^^
  worker  | SyntaxError: Named export 'datePivot' not found. The requested module '../lib/time.js' is a CommonJS module, which may not support all module.exports as named exports.
  worker  | CommonJS modules can always be imported via the default export, for example using:
  worker  |
  worker  | import pkg from '../lib/time.js';
  worker  | const { datePivot } = pkg;
  worker  |
  worker  |     at ModuleJob._instantiate (node:internal/modules/esm/module_job:124:21)
  worker  |     at async ModuleJob.run (node:internal/modules/esm/module_job:190:5)
  worker  |
  worker  | Node.js v18.17.0
  worker  |
  worker exited with code 1

* Fix global not defined in browser context

* Also ignore api/ and lib/ during linting

I did not want to do this but I was not able to fix this error in any other way I tried:

  /home/ekzyis/programming/stacker.news/api/lnd/index.js:0:0: Parsing error: No Babel config file detected for /home/ekzyis/programming/stacker.news/api/lnd/index.js. Either disable config file checking with requ
ireConfigFile: false, or configure Babel so that it can find the config files. (null)

Did not want to look deeper into all this standard, eslint, babel configuration stuff ...

---------

Co-authored-by: ekzyis <ek@stacker.news>
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
ekzyis 2023-09-24 03:19:35 +02:00 committed by GitHub
parent d60a589bc0
commit dde82e25a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 229 additions and 243 deletions

3
api/package.json Normal file
View File

@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@ -1,6 +1,6 @@
const { GraphQLError } = require('graphql') import { GraphQLError } from 'graphql'
const retry = require('async-retry') import retry from 'async-retry'
const Prisma = require('@prisma/client') import Prisma from '@prisma/client'
async function serialize (models, ...calls) { async function serialize (models, ...calls) {
return await retry(async bail => { return await retry(async bail => {
@ -56,4 +56,4 @@ async function serialize (models, ...calls) {
}) })
} }
module.exports = serialize export default serialize

View File

@ -1,4 +1,4 @@
const os = require('@opensearch-project/opensearch') import os from '@opensearch-project/opensearch'
const options = process.env.NODE_ENV === 'development' const options = process.env.NODE_ENV === 'development'
? { node: 'http://localhost:9200' } ? { node: 'http://localhost:9200' }
@ -12,4 +12,4 @@ const options = process.env.NODE_ENV === 'development'
global.os = global.os || new os.Client(options) global.os = global.os || new os.Client(options)
module.exports = global.os export default global.os

View File

@ -20,8 +20,8 @@ export default function getApolloClient () {
if (SSR) { if (SSR) {
return getClient(`${process.env.SELF_URL}/api/graphql`) return getClient(`${process.env.SELF_URL}/api/graphql`)
} else { } else {
global.apolloClient ||= getClient('/api/graphql') window.apolloClient ||= getClient('/api/graphql')
return global.apolloClient return window.apolloClient
} }
} }

View File

@ -1,57 +1,53 @@
// XXX this is temporary until we have so many subs they have // XXX this is temporary until we have so many subs they have
// to be loaded from the server // to be loaded from the server
const SUBS = ['bitcoin', 'nostr', 'tech', 'meta', 'jobs'] export const SUBS = ['bitcoin', 'nostr', 'tech', 'meta', 'jobs']
const SUBS_NO_JOBS = SUBS.filter(s => s !== 'jobs') export const SUBS_NO_JOBS = SUBS.filter(s => s !== 'jobs')
module.exports = { export const NOFOLLOW_LIMIT = 100
NOFOLLOW_LIMIT: 100, export const BOOST_MIN = 5000
BOOST_MIN: 5000, export const UPLOAD_SIZE_MAX = 2 * 1024 * 1024
UPLOAD_SIZE_MAX: 2 * 1024 * 1024, export const IMAGE_PIXELS_MAX = 35000000
IMAGE_PIXELS_MAX: 35000000, export const UPLOAD_TYPES_ALLOW = [
UPLOAD_TYPES_ALLOW: [ 'image/gif',
'image/gif', 'image/heic',
'image/heic', 'image/png',
'image/png', 'image/jpeg',
'image/jpeg', 'image/webp'
'image/webp' ]
], export const COMMENT_DEPTH_LIMIT = 10
COMMENT_DEPTH_LIMIT: 10, export const MAX_TITLE_LENGTH = 80
MAX_TITLE_LENGTH: 80, export const MAX_POLL_CHOICE_LENGTH = 30
MAX_POLL_CHOICE_LENGTH: 30, export const ITEM_SPAM_INTERVAL = '10m'
ITEM_SPAM_INTERVAL: '10m', export const ANON_ITEM_SPAM_INTERVAL = '0'
ANON_ITEM_SPAM_INTERVAL: '0', export const INV_PENDING_LIMIT = 10
INV_PENDING_LIMIT: 10, export const BALANCE_LIMIT_MSATS = 1000000000 // 1m sat
BALANCE_LIMIT_MSATS: 1000000000, // 1m sats export const ANON_INV_PENDING_LIMIT = 100
ANON_INV_PENDING_LIMIT: 100, export const ANON_BALANCE_LIMIT_MSATS = 0 // disabl
ANON_BALANCE_LIMIT_MSATS: 0, // disable export const MAX_POLL_NUM_CHOICES = 10
MAX_POLL_NUM_CHOICES: 10, export const MIN_POLL_NUM_CHOICES = 2
MIN_POLL_NUM_CHOICES: 2, export const POLL_COST = 1
POLL_COST: 1, export const ITEM_FILTER_THRESHOLD = 1.2
ITEM_FILTER_THRESHOLD: 1.2, export const DONT_LIKE_THIS_COST = 1
DONT_LIKE_THIS_COST: 1, export const COMMENT_TYPE_QUERY = ['comments', 'freebies', 'outlawed', 'borderland', 'all', 'bookmarks']
COMMENT_TYPE_QUERY: ['comments', 'freebies', 'outlawed', 'borderland', 'all', 'bookmarks'], export const USER_SORTS = ['stacked', 'spent', 'comments', 'posts', 'referrals']
SUBS, export const ITEM_SORTS = ['zaprank', 'comments', 'sats']
SUBS_NO_JOBS, export const WHENS = ['day', 'week', 'month', 'year', 'forever']
USER_SORTS: ['stacked', 'spent', 'comments', 'posts', 'referrals'], export const ITEM_TYPES = context => {
ITEM_SORTS: ['zaprank', 'comments', 'sats'], const items = ['all', 'posts', 'comments', 'bounties', 'links', 'discussions', 'polls']
WHENS: ['day', 'week', 'month', 'year', 'forever'], if (!context) {
ITEM_TYPES: context => { items.push('bios', 'jobs')
const items = ['all', 'posts', 'comments', 'bounties', 'links', 'discussions', 'polls'] }
if (!context) { items.push('freebies')
items.push('bios', 'jobs') if (context === 'user') {
} items.push('jobs', 'bookmarks')
items.push('freebies') }
if (context === 'user') { return items
items.push('jobs', 'bookmarks')
}
return items
},
OLD_ITEM_DAYS: 3,
ANON_USER_ID: 27,
AD_USER_ID: 9,
ANON_POST_FEE: 1000,
ANON_COMMENT_FEE: 100,
SSR: typeof window === 'undefined',
MAX_FORWARDS: 5,
LNURLP_COMMENT_MAX_LENGTH: 1000
} }
export const OLD_ITEM_DAYS = 3
export const ANON_USER_ID = 27
export const AD_USER_ID = 9
export const ANON_POST_FEE = 1000
export const ANON_COMMENT_FEE = 100
export const SSR = typeof window === 'undefined'
export const MAX_FORWARDS = 5
export const LNURLP_COMMENT_MAX_LENGTH = 1000

3
lib/package.json Normal file
View File

@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@ -1,4 +1,4 @@
function timeSince (timeStamp) { export function timeSince (timeStamp) {
const now = new Date() const now = new Date()
const secondsPast = (now.getTime() - timeStamp) / 1000 const secondsPast = (now.getTime() - timeStamp) / 1000
if (secondsPast < 60) { if (secondsPast < 60) {
@ -20,7 +20,7 @@ function timeSince (timeStamp) {
return 'now' return 'now'
} }
function datePivot (date, export function datePivot (date,
{ years = 0, months = 0, days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0 }) { { years = 0, months = 0, days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0 }) {
return new Date( return new Date(
date.getFullYear() + years, date.getFullYear() + years,
@ -33,9 +33,9 @@ function datePivot (date,
) )
} }
const dayMonthYear = when => new Date(when).toISOString().slice(0, 10) export const dayMonthYear = when => new Date(when).toISOString().slice(0, 10)
function timeLeft (timeStamp) { export function timeLeft (timeStamp) {
const now = new Date() const now = new Date()
const secondsPast = (timeStamp - now.getTime()) / 1000 const secondsPast = (timeStamp - now.getTime()) / 1000
@ -57,6 +57,4 @@ function timeLeft (timeStamp) {
} }
} }
const sleep = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms)) export const sleep = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms))
module.exports = { timeSince, dayMonthYear, datePivot, timeLeft, sleep }

View File

@ -1,6 +1,6 @@
const { withPlausibleProxy } = require('next-plausible') const { withPlausibleProxy } = require('next-plausible')
const { InjectManifest } = require('workbox-webpack-plugin') const { InjectManifest } = require('workbox-webpack-plugin')
const { generatePrecacheManifest } = require('./sw/build') const { generatePrecacheManifest } = require('./sw/build.js')
const isProd = process.env.NODE_ENV === 'production' const isProd = process.env.NODE_ENV === 'production'
const corsHeaders = [ const corsHeaders = [

View File

@ -104,7 +104,10 @@
"next" "next"
], ],
"ignore": [ "ignore": [
"**/spawn" "**/spawn",
"**/worker",
"**/api",
"**/lib"
] ]
}, },
"devDependencies": { "devDependencies": {
@ -114,4 +117,4 @@
"eslint": "^8.47.0", "eslint": "^8.47.0",
"standard": "^17.1.0" "standard": "^17.1.0"
} }
} }

View File

@ -2,7 +2,7 @@ import '../styles/globals.scss'
import { ApolloProvider, gql } from '@apollo/client' import { ApolloProvider, gql } from '@apollo/client'
import { MeProvider } from '../components/me' import { MeProvider } from '../components/me'
import PlausibleProvider from 'next-plausible' import PlausibleProvider from 'next-plausible'
import getApolloClient from '../lib/apollo' import getApolloClient from '../lib/apollo.js'
import { PriceProvider } from '../components/price' import { PriceProvider } from '../components/price'
import { BlockHeightProvider } from '../components/block-height' import { BlockHeightProvider } from '../components/block-height'
import Head from 'next/head' import Head from 'next/head'
@ -34,7 +34,7 @@ function writeQuery (client, apollo, data) {
} }
} }
function MyApp ({ Component, pageProps: { ...props } }) { export default function MyApp ({ Component, pageProps: { ...props } }) {
const client = getApolloClient() const client = getApolloClient()
const router = useRouter() const router = useRouter()
@ -111,5 +111,3 @@ function MyApp ({ Component, pageProps: { ...props } }) {
</> </>
) )
} }
export default MyApp

View File

@ -1,18 +1,17 @@
import Document, { Html, Head, Main, NextScript } from 'next/document' import { Html, Head, Main, NextScript } from 'next/document'
import Script from 'next/script' import Script from 'next/script'
class MyDocument extends Document { export default function Document () {
render () { return (
return ( <Html lang='en'>
<Html lang='en'> <Head>
<Head> <link rel='manifest' href='/api/site.webmanifest' />
<link rel='manifest' href='/api/site.webmanifest' /> <link rel='preload' href={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.woff2`} as='font' type='font/woff2' crossOrigin='' />
<link rel='preload' href={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.woff2`} as='font' type='font/woff2' crossOrigin='' /> <link rel='preload' href={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.woff`} as='font' type='font/woff' crossOrigin='' />
<link rel='preload' href={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.woff`} as='font' type='font/woff' crossOrigin='' /> <link rel='preload' href={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.ttf`} as='font' type='font/ttf' crossOrigin='' />
<link rel='preload' href={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.ttf`} as='font' type='font/ttf' crossOrigin='' /> <style
<style dangerouslySetInnerHTML={{
dangerouslySetInnerHTML={{ __html:
__html:
` @font-face { ` @font-face {
font-family: 'lightning'; font-family: 'lightning';
src: url(${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.ttf) format('truetype'), src: url(${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.ttf) format('truetype'),
@ -20,13 +19,13 @@ class MyDocument extends Document {
url(${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.woff2) format('woff2'); url(${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.woff2) format('woff2');
font-display: swap; font-display: swap;
}` }`
}} }}
/> />
<meta name='apple-mobile-web-app-capable' content='yes' /> <meta name='apple-mobile-web-app-capable' content='yes' />
<meta name='theme-color' content='#121214' /> <meta name='theme-color' content='#121214' />
<link rel='apple-touch-icon' href='/icons/icon_x192.png' /> <link rel='apple-touch-icon' href='/icons/icon_x192.png' />
<Script id='dark-mode-js' strategy='beforeInteractive'> <Script id='dark-mode-js' strategy='beforeInteractive'>
{`const handleThemeChange = (dark) => { {`const handleThemeChange = (dark) => {
const root = window.document.documentElement const root = window.document.documentElement
root.setAttribute('data-bs-theme', dark ? 'dark' : 'light') root.setAttribute('data-bs-theme', dark ? 'dark' : 'light')
} }
@ -59,85 +58,82 @@ class MyDocument extends Document {
handleThemeChange(dark) handleThemeChange(dark)
})() })()
}`} }`}
</Script> </Script>
{/* light-theme splash screen links */} {/* light-theme splash screen links */}
<link rel='apple-touch-startup-image' media='screen and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_14_Pro_Max_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_14_Pro_Max_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_14_Pro_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_14_Pro_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_14_Plus__iPhone_13_Pro_Max__iPhone_12_Pro_Max_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_14_Plus__iPhone_13_Pro_Max__iPhone_12_Pro_Max_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_14__iPhone_13_Pro__iPhone_13__iPhone_12_Pro__iPhone_12_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_14__iPhone_13_Pro__iPhone_13__iPhone_12_Pro__iPhone_12_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_13_mini__iPhone_12_mini__iPhone_11_Pro__iPhone_XS__iPhone_X_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_13_mini__iPhone_12_mini__iPhone_11_Pro__iPhone_XS__iPhone_X_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_11_Pro_Max__iPhone_XS_Max_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_11_Pro_Max__iPhone_XS_Max_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/iPhone_11__iPhone_XR_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/iPhone_11__iPhone_XR_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_8_Plus__iPhone_7_Plus__iPhone_6s_Plus__iPhone_6_Plus_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)' href='/splash/iPhone_8_Plus__iPhone_7_Plus__iPhone_6s_Plus__iPhone_6_Plus_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/iPhone_8__iPhone_7__iPhone_6s__iPhone_6__4.7__iPhone_SE_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/iPhone_8__iPhone_7__iPhone_6s__iPhone_6__4.7__iPhone_SE_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/4__iPhone_SE__iPod_touch_5th_generation_and_later_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/4__iPhone_SE__iPod_touch_5th_generation_and_later_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/12.9__iPad_Pro_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/12.9__iPad_Pro_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/11__iPad_Pro__10.5__iPad_Pro_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/11__iPad_Pro__10.5__iPad_Pro_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/10.9__iPad_Air_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/10.9__iPad_Air_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/10.5__iPad_Air_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/10.5__iPad_Air_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/10.2__iPad_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/10.2__iPad_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/9.7__iPad_Pro__7.9__iPad_mini__9.7__iPad_Air__9.7__iPad_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/9.7__iPad_Pro__7.9__iPad_mini__9.7__iPad_Air__9.7__iPad_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/8.3__iPad_Mini_landscape.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)' href='/splash/8.3__iPad_Mini_landscape.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_14_Pro_Max_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_14_Pro_Max_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_14_Pro_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_14_Pro_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_14_Plus__iPhone_13_Pro_Max__iPhone_12_Pro_Max_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_14_Plus__iPhone_13_Pro_Max__iPhone_12_Pro_Max_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_14__iPhone_13_Pro__iPhone_13__iPhone_12_Pro__iPhone_12_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_14__iPhone_13_Pro__iPhone_13__iPhone_12_Pro__iPhone_12_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_13_mini__iPhone_12_mini__iPhone_11_Pro__iPhone_XS__iPhone_X_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_13_mini__iPhone_12_mini__iPhone_11_Pro__iPhone_XS__iPhone_X_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_11_Pro_Max__iPhone_XS_Max_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_11_Pro_Max__iPhone_XS_Max_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/iPhone_11__iPhone_XR_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/iPhone_11__iPhone_XR_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_8_Plus__iPhone_7_Plus__iPhone_6s_Plus__iPhone_6_Plus_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)' href='/splash/iPhone_8_Plus__iPhone_7_Plus__iPhone_6s_Plus__iPhone_6_Plus_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/iPhone_8__iPhone_7__iPhone_6s__iPhone_6__4.7__iPhone_SE_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/iPhone_8__iPhone_7__iPhone_6s__iPhone_6__4.7__iPhone_SE_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/4__iPhone_SE__iPod_touch_5th_generation_and_later_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/4__iPhone_SE__iPod_touch_5th_generation_and_later_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/12.9__iPad_Pro_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/12.9__iPad_Pro_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/11__iPad_Pro__10.5__iPad_Pro_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/11__iPad_Pro__10.5__iPad_Pro_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/10.9__iPad_Air_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/10.9__iPad_Air_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/10.5__iPad_Air_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/10.5__iPad_Air_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/10.2__iPad_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/10.2__iPad_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/9.7__iPad_Pro__7.9__iPad_mini__9.7__iPad_Air__9.7__iPad_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/9.7__iPad_Pro__7.9__iPad_mini__9.7__iPad_Air__9.7__iPad_portrait.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/8.3__iPad_Mini_portrait.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)' href='/splash/8.3__iPad_Mini_portrait.png' />
{/* dark-theme splash screen links */} {/* dark-theme splash screen links */}
<link rel='apple-touch-startup-image' media='screen and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Pro_Max_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Pro_Max_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Pro_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Pro_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Plus__iPhone_13_Pro_Max__iPhone_12_Pro_Max_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Plus__iPhone_13_Pro_Max__iPhone_12_Pro_Max_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_14__iPhone_13_Pro__iPhone_13__iPhone_12_Pro__iPhone_12_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_14__iPhone_13_Pro__iPhone_13__iPhone_12_Pro__iPhone_12_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_13_mini__iPhone_12_mini__iPhone_11_Pro__iPhone_XS__iPhone_X_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_13_mini__iPhone_12_mini__iPhone_11_Pro__iPhone_XS__iPhone_X_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_11_Pro_Max__iPhone_XS_Max_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_11_Pro_Max__iPhone_XS_Max_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_11__iPhone_XR_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_11__iPhone_XR_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_8_Plus__iPhone_7_Plus__iPhone_6s_Plus__iPhone_6_Plus_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_8_Plus__iPhone_7_Plus__iPhone_6s_Plus__iPhone_6_Plus_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_8__iPhone_7__iPhone_6s__iPhone_6__4.7__iPhone_SE_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/iPhone_8__iPhone_7__iPhone_6s__iPhone_6__4.7__iPhone_SE_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/4__iPhone_SE__iPod_touch_5th_generation_and_later_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/4__iPhone_SE__iPod_touch_5th_generation_and_later_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/12.9__iPad_Pro_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/12.9__iPad_Pro_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/11__iPad_Pro__10.5__iPad_Pro_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/11__iPad_Pro__10.5__iPad_Pro_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/10.9__iPad_Air_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/10.9__iPad_Air_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/10.5__iPad_Air_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/10.5__iPad_Air_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/10.2__iPad_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/10.2__iPad_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/9.7__iPad_Pro__7.9__iPad_mini__9.7__iPad_Air__9.7__iPad_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/9.7__iPad_Pro__7.9__iPad_mini__9.7__iPad_Air__9.7__iPad_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/8.3__iPad_Mini_landscape_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape) and (prefers-color-scheme: dark)' href='/splash/8.3__iPad_Mini_landscape_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Pro_Max_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Pro_Max_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Pro_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Pro_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Plus__iPhone_13_Pro_Max__iPhone_12_Pro_Max_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_14_Plus__iPhone_13_Pro_Max__iPhone_12_Pro_Max_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_14__iPhone_13_Pro__iPhone_13__iPhone_12_Pro__iPhone_12_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_14__iPhone_13_Pro__iPhone_13__iPhone_12_Pro__iPhone_12_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_13_mini__iPhone_12_mini__iPhone_11_Pro__iPhone_XS__iPhone_X_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_13_mini__iPhone_12_mini__iPhone_11_Pro__iPhone_XS__iPhone_X_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_11_Pro_Max__iPhone_XS_Max_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_11_Pro_Max__iPhone_XS_Max_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_11__iPhone_XR_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_11__iPhone_XR_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_8_Plus__iPhone_7_Plus__iPhone_6s_Plus__iPhone_6_Plus_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_8_Plus__iPhone_7_Plus__iPhone_6s_Plus__iPhone_6_Plus_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_8__iPhone_7__iPhone_6s__iPhone_6__4.7__iPhone_SE_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/iPhone_8__iPhone_7__iPhone_6s__iPhone_6__4.7__iPhone_SE_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/4__iPhone_SE__iPod_touch_5th_generation_and_later_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/4__iPhone_SE__iPod_touch_5th_generation_and_later_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/12.9__iPad_Pro_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/12.9__iPad_Pro_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/11__iPad_Pro__10.5__iPad_Pro_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/11__iPad_Pro__10.5__iPad_Pro_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/10.9__iPad_Air_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/10.9__iPad_Air_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/10.5__iPad_Air_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/10.5__iPad_Air_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/10.2__iPad_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/10.2__iPad_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/9.7__iPad_Pro__7.9__iPad_mini__9.7__iPad_Air__9.7__iPad_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/9.7__iPad_Pro__7.9__iPad_mini__9.7__iPad_Air__9.7__iPad_portrait_dark.png' />
<link rel='apple-touch-startup-image' media='screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/8.3__iPad_Mini_portrait_dark.png' /> <link rel='apple-touch-startup-image' media='screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait) and (prefers-color-scheme: dark)' href='/splash/8.3__iPad_Mini_portrait_dark.png' />
</Head> </Head>
<body> <body>
<Main /> <Main />
<NextScript /> <NextScript />
</body> </body>
</Html> </Html>
) )
}
} }
export default MyDocument

View File

@ -1,6 +1,6 @@
const serialize = require('../api/resolvers/serial') import serialize from '../api/resolvers/serial.js'
function auction ({ models }) { export function auction ({ models }) {
return async function ({ name }) { return async function ({ name }) {
console.log('running', name) console.log('running', name)
// get all items we need to check // get all items we need to check
@ -26,5 +26,3 @@ function auction ({ models }) {
console.log('done', name) console.log('done', name)
} }
} }
module.exports = { auction }

View File

@ -1,5 +1,5 @@
const serialize = require('../api/resolvers/serial') import serialize from '../api/resolvers/serial.js'
const { ANON_USER_ID } = require('../lib/constants') import { ANON_USER_ID } from '../lib/constants.js'
const ITEM_EACH_REWARD = 4.0 const ITEM_EACH_REWARD = 4.0
const UPVOTE_EACH_REWARD = 4.0 const UPVOTE_EACH_REWARD = 4.0
@ -7,7 +7,7 @@ const TOP_PERCENTILE = 33
const TOTAL_UPPER_BOUND_MSATS = 1000000000 const TOTAL_UPPER_BOUND_MSATS = 1000000000
const REDUCE_REWARDS = [616, 6030, 946, 4502] const REDUCE_REWARDS = [616, 6030, 946, 4502]
function earn ({ models }) { export function earn ({ models }) {
return async function ({ name }) { return async function ({ name }) {
console.log('running', name) console.log('running', name)
@ -157,5 +157,3 @@ function earn ({ models }) {
console.log('done', name) console.log('done', name)
} }
} }
module.exports = { earn }

View File

@ -1,20 +1,26 @@
const PgBoss = require('pg-boss') import PgBoss from 'pg-boss'
require('@next/env').loadEnvConfig('..') import nextEnv from '@next/env'
const { PrismaClient } = require('@prisma/client') import { PrismaClient } from '@prisma/client'
const { checkInvoice, checkWithdrawal } = require('./wallet') import { checkInvoice, checkWithdrawal } from './wallet.js'
const { repin } = require('./repin') import { repin } from './repin.js'
const { trust } = require('./trust') import { trust } from './trust.js'
const { auction } = require('./auction') import { auction } from './auction.js'
const { earn } = require('./earn') import { earn } from './earn.js'
const { ApolloClient, HttpLink, InMemoryCache } = require('@apollo/client') import apolloClient from '@apollo/client'
const { indexItem, indexAllItems } = require('./search') import { indexItem, indexAllItems } from './search.js'
const { timestampItem } = require('./ots') import { timestampItem } from './ots.js'
const { computeStreaks, checkStreak } = require('./streak') import { computeStreaks, checkStreak } from './streak.js'
const { nip57 } = require('./nostr') import { nip57 } from './nostr.js'
import fetch from 'cross-fetch'
import { authenticatedLndGrpc } from 'ln-service'
import { views, rankViews } from './views.js'
const { loadEnvConfig } = nextEnv
const { ApolloClient, HttpLink, InMemoryCache } = apolloClient
loadEnvConfig('..')
const fetch = require('cross-fetch')
const { authenticatedLndGrpc } = require('ln-service')
const { views, rankViews } = require('./views')
async function work () { async function work () {
const boss = new PgBoss(process.env.DATABASE_URL) const boss = new PgBoss(process.env.DATABASE_URL)

View File

@ -1,9 +1,9 @@
const { getInvoice } = require('ln-service') import { getInvoice } from 'ln-service'
const { Relay, signId, calculateId, getPublicKey } = require('nostr') import { Relay, signId, calculateId, getPublicKey } from 'nostr'
const nostrOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true } const nostrOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true }
function nip57 ({ boss, lnd, models }) { export function nip57 ({ boss, lnd, models }) {
return async function ({ data: { hash } }) { return async function ({ data: { hash } }) {
console.log('running nip57') console.log('running nip57')
@ -84,5 +84,3 @@ function nip57 ({ boss, lnd, models }) {
console.log('done running nip57') console.log('done running nip57')
} }
} }
module.exports = { nip57 }

View File

@ -1,7 +1,7 @@
const { gql } = require('graphql-tag') import { gql } from 'graphql-tag'
const stringifyCanon = require('canonical-json') import stringifyCanon from 'canonical-json'
const { createHash } = require('crypto') import { createHash } from 'crypto'
const Ots = require('opentimestamps') import Ots from 'opentimestamps'
const ITEM_OTS_FIELDS = gql` const ITEM_OTS_FIELDS = gql`
fragment ItemOTSFields on Item { fragment ItemOTSFields on Item {
@ -12,7 +12,7 @@ const ITEM_OTS_FIELDS = gql`
url url
}` }`
function timestampItem ({ apollo, models }) { export function timestampItem ({ apollo, models }) {
return async function ({ data: { id } }) { return async function ({ data: { id } }) {
console.log('timestamping item', id) console.log('timestamping item', id)
@ -48,5 +48,3 @@ function timestampItem ({ apollo, models }) {
console.log('done timestamping item', id) console.log('done timestamping item', id)
} }
} }
module.exports = { timestampItem }

3
worker/package.json Normal file
View File

@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@ -1,4 +1,4 @@
function repin ({ models }) { export function repin ({ models }) {
return async function ({ name }) { return async function ({ name }) {
console.log('doing', name) console.log('doing', name)
@ -40,5 +40,3 @@ function repin ({ models }) {
}) })
} }
} }
module.exports = { repin }

View File

@ -1,6 +1,6 @@
const { gql } = require('graphql-tag') import { gql } from 'graphql-tag'
const search = require('../api/search') import search from '../api/search/index.js'
const removeMd = require('remove-markdown') import removeMd from 'remove-markdown'
const ITEM_SEARCH_FIELDS = gql` const ITEM_SEARCH_FIELDS = gql`
fragment ItemSearchFields on Item { fragment ItemSearchFields on Item {
@ -75,7 +75,7 @@ async function _indexItem (item) {
console.log('done indexing item', item.id) console.log('done indexing item', item.id)
} }
function indexItem ({ apollo }) { export function indexItem ({ apollo }) {
return async function ({ data: { id } }) { return async function ({ data: { id } }) {
// 1. grab item from database // 1. grab item from database
// could use apollo to avoid duping logic // could use apollo to avoid duping logic
@ -95,7 +95,7 @@ function indexItem ({ apollo }) {
} }
} }
function indexAllItems ({ apollo }) { export function indexAllItems ({ apollo }) {
return async function () { return async function () {
// cursor over all items in the Item table // cursor over all items in the Item table
let items = []; let cursor = null let items = []; let cursor = null
@ -125,5 +125,3 @@ function indexAllItems ({ apollo }) {
} while (cursor) } while (cursor)
} }
} }
module.exports = { indexItem, indexAllItems }

View File

@ -1,6 +1,6 @@
const STREAK_THRESHOLD = 100 const STREAK_THRESHOLD = 100
function computeStreaks ({ models }) { export function computeStreaks ({ models }) {
return async function () { return async function () {
console.log('computing streaks') console.log('computing streaks')
@ -64,7 +64,7 @@ function computeStreaks ({ models }) {
} }
} }
function checkStreak ({ models }) { export function checkStreak ({ models }) {
return async function ({ data: { id } }) { return async function ({ data: { id } }) {
console.log('checking streak', id) console.log('checking streak', id)
@ -108,5 +108,3 @@ function checkStreak ({ models }) {
console.log('done checking streak', id) console.log('done checking streak', id)
} }
} }
module.exports = { checkStreak, computeStreaks }

View File

@ -1,7 +1,7 @@
const math = require('mathjs') import * as math from 'mathjs'
const { ANON_USER_ID } = require('../lib/constants') import { ANON_USER_ID } from '../lib/constants.js'
function trust ({ boss, models }) { export function trust ({ boss, models }) {
return async function () { return async function () {
try { try {
console.time('trust') console.time('trust')
@ -172,5 +172,3 @@ async function storeTrust (models, nodeTrust) {
FROM (values ${values}) g(id, trust) FROM (values ${values}) g(id, trust)
WHERE users.id = g.id`)]) WHERE users.id = g.id`)])
} }
module.exports = { trust }

View File

@ -1,5 +1,5 @@
// this is intended to be run everyday after midnight CT // this is intended to be run everyday after midnight CT
function views ({ models }) { export function views ({ models }) {
return async function () { return async function () {
console.log('refreshing stats views') console.log('refreshing stats views')
@ -14,7 +14,7 @@ function views ({ models }) {
} }
// this should be run regularly ... like, every 1-5 minutes // this should be run regularly ... like, every 1-5 minutes
function rankViews ({ models }) { export function rankViews ({ models }) {
return async function () { return async function () {
console.log('refreshing rank views') console.log('refreshing rank views')
@ -25,5 +25,3 @@ function rankViews ({ models }) {
console.log('done refreshing rank views') console.log('done refreshing rank views')
} }
} }
module.exports = { views, rankViews }

View File

@ -1,11 +1,11 @@
const serialize = require('../api/resolvers/serial') import serialize from '../api/resolvers/serial.js'
const { getInvoice, getPayment, cancelHodlInvoice } = require('ln-service') import { getInvoice, getPayment, cancelHodlInvoice } from 'ln-service'
const { datePivot } = require('../lib/time') import { datePivot } from '../lib/time.js'
const walletOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true } const walletOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true }
// TODO this should all be done via websockets // TODO this should all be done via websockets
function checkInvoice ({ boss, models, lnd }) { export function checkInvoice ({ boss, models, lnd }) {
return async function ({ data: { hash, isHeldSet } }) { return async function ({ data: { hash, isHeldSet } }) {
let inv let inv
try { try {
@ -67,7 +67,7 @@ function checkInvoice ({ boss, models, lnd }) {
} }
} }
function checkWithdrawal ({ boss, models, lnd }) { export function checkWithdrawal ({ boss, models, lnd }) {
return async function ({ data: { id, hash } }) { return async function ({ data: { id, hash } }) {
let wdrwl let wdrwl
let notFound = false let notFound = false
@ -110,5 +110,3 @@ function checkWithdrawal ({ boss, models, lnd }) {
} }
} }
} }
module.exports = { checkInvoice, checkWithdrawal }