Skip to content

Commit

Permalink
feat: add worker to provide Prophet script (#143)
Browse files Browse the repository at this point in the history
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
  • Loading branch information
DanSnow and deepsource-autofix[bot] authored Apr 8, 2024
1 parent 3a7f018 commit ab2b0d5
Show file tree
Hide file tree
Showing 36 changed files with 6,650 additions and 5,586 deletions.
1 change: 1 addition & 0 deletions .moon/workspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
projects:
- .
- terraform
- packages/*

# Configures the version control system to utilize within the workspace. A VCS
# is required for determining touched (added, modified, etc) files, calculating file hashes,
Expand Down
9 changes: 0 additions & 9 deletions auto-imports.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ declare global {
const $config: typeof import('./src/stores/config')['$config']
const $paywall: typeof import('./src/stores/paywall-events')['$paywall']
const CONFIG_VAR_NAME: typeof import('./src/stores/config')['CONFIG_VAR_NAME']
const CombinedError: typeof import('@urql/vue')['CombinedError']
const EffectScope: typeof import('vue')['EffectScope']
const SIGN_IN: typeof import('./src/composables/query-action')['SIGN_IN']
const VERIFY_EMAIL: typeof import('./src/composables/query-action')['VERIFY_EMAIL']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
const atom: typeof import('nanostores')['atom']
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
Expand Down Expand Up @@ -48,7 +45,6 @@ declare global {
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const getFakeArticle: typeof import('./src/utils/fake-article')['getFakeArticle']
const graphql: typeof import('~/gql/gql')['graphql']
const h: typeof import('vue')['h']
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
const initConfig: typeof import('./src/stores/config')['initConfig']
Expand Down Expand Up @@ -122,7 +118,6 @@ declare global {
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const trackAtom: typeof import('./src/stores/paywall-events')['trackAtom']
const triggerRef: typeof import('vue')['triggerRef']
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
Expand All @@ -132,7 +127,6 @@ declare global {
const unref: typeof import('vue')['unref']
const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until']
const unwrapParagraph: typeof import('./src/composables/track-link')['unwrapParagraph']
const useAbs: typeof import('@vueuse/math')['useAbs']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useAnimate: typeof import('@vueuse/core')['useAnimate']
Expand All @@ -151,7 +145,6 @@ declare global {
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs']
const useAuth: typeof import('./src/composables/login')['useAuth']
const useAverage: typeof import('@vueuse/math')['useAverage']
const useBase64: typeof import('@vueuse/core')['useBase64']
const useBattery: typeof import('@vueuse/core')['useBattery']
Expand Down Expand Up @@ -243,7 +236,6 @@ declare global {
const useParallax: typeof import('@vueuse/core')['useParallax']
const useParentElement: typeof import('@vueuse/core')['useParentElement']
const usePaywallEnabled: typeof import('./src/composables/paywall-enable')['usePaywallEnabled']
const usePaywallMode: typeof import('./src/composables/paywall-mode')['usePaywallMode']
const usePerformanceObserver: typeof import('@vueuse/core')['usePerformanceObserver']
const usePermission: typeof import('@vueuse/core')['usePermission']
const usePointer: typeof import('@vueuse/core')['usePointer']
Expand All @@ -258,7 +250,6 @@ declare global {
const usePrevious: typeof import('@vueuse/core')['usePrevious']
const useProjection: typeof import('@vueuse/math')['useProjection']
const useQuery: typeof import('@urql/vue')['useQuery']
const useQueryAction: typeof import('./src/composables/query-action')['useQueryAction']
const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
Expand Down
8 changes: 7 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export default antfu(
},
},
{
ignores: ['src/vite-env.d.ts', 'auto-imports.d.ts', 'components.d.ts', 'src/gql/**'],
ignores: [
'src/vite-env.d.ts',
'auto-imports.d.ts',
'components.d.ts',
'src/gql/**',
'packages/storipress-client/gql/**',
],
},
)
6 changes: 6 additions & 0 deletions moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ tasks:
- dist
deps:
- ~:typecheck
- storipress-client:generate
platform: node
lib:
env:
Expand All @@ -38,6 +39,7 @@ tasks:
- --mode=lib
deps:
- ~:typecheck
- storipress-client:generate
platform: node
lib-analyze:
extends: lib
Expand Down Expand Up @@ -85,9 +87,13 @@ tasks:
- tsconfig.json
- package.json
- yarn.lock
deps:
- storipress-client:generate
dev:
command: vite
args:
- --open
local: true
platform: node
deps:
- storipress-client:generate-watch
21 changes: 11 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
"private": true,
"packageManager": "yarn@4.1.1",
"engines": {
"node": "20.12.1"
"node": "20.11.1"
},
"workspaces": [
".",
"packages/*"
],
"scripts": {
"dev": "moon run dev",
"build": "moon run build",
Expand All @@ -24,17 +28,18 @@
"@formkit/auto-animate": "^0.8.1",
"@nanostores/persistent": "^0.10.1",
"@nanostores/vue": "^0.10.0",
"@urql/vue": "^1.1.2",
"@urql/vue": "^1.1.3",
"@vee-validate/zod": "^4.12.6",
"@vueuse/math": "^10.9.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"defu": "^6.1.4",
"destr": "^2.0.3",
"graphql-web-lite": "^16.6.0-4",
"nanostores": "^0.10.0",
"radix-vue": "^1.5.1",
"tailwind-merge": "^2.2.1",
"nanostores": "^0.10.2",
"radix-vue": "^1.6.2",
"storipress-client": "workspace:^",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"vee-validate": "^4.12.6",
"vue": "^3.4.21",
Expand All @@ -45,11 +50,8 @@
"@0no-co/graphqlsp": "1.9.1",
"@antfu/eslint-config": "2.12.2",
"@egoist/tailwindcss-icons": "1.7.4",
"@graphql-codegen/cli": "5.0.2",
"@graphql-codegen/client-preset": "4.2.5",
"@iconify-json/lucide": "1.1.180",
"@iconify-json/lucide": "1.1.179",
"@moonrepo/cli": "1.23.3",
"@parcel/watcher": "2.4.1",
"@sindresorhus/slugify": "2.2.1",
"@size-limit/file": "11.1.2",
"@tailwindcss/typography": "0.5.12",
Expand All @@ -62,7 +64,6 @@
"autoprefixer": "10.4.19",
"esbuild": "0.20.2",
"eslint": "9.0.0",
"graphql": "16.8.1",
"lorem-ipsum": "2.0.8",
"open-cli": "8.0.0",
"polished": "4.3.1",
Expand Down
10 changes: 10 additions & 0 deletions packages/script-provider/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules
dist
.wrangler
.dev.vars

# Change them to your taste:
package-lock.json
yarn.lock
pnpm-lock.yaml
bun.lockb
8 changes: 8 additions & 0 deletions packages/script-provider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
```
yarn install
yarn run dev
```

```
yarn run deploy
```
15 changes: 15 additions & 0 deletions packages/script-provider/moon.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
tasks:
deploy:
command:
- wrangler
- deploy
- --minify
- src/index.ts
platform: node
dev:
command:
- wrangler
- dev
- src/index.ts
local: true
platform: node
21 changes: 21 additions & 0 deletions packages/script-provider/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "script-provider",
"scripts": {
"dev": "moon run dev",
"deploy": "moon run deploy"
},
"dependencies": {
"@ts-rest/core": "^3.41.0",
"@urql/core": "^5.0.0",
"effect": "^2.4.18",
"esbuild-wasm": "^0.20.2",
"hono": "^4.2.2",
"proper-tags": "^2.0.2",
"ts-pattern": "^5.1.0",
"ufo": "^1.5.3"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240404.0",
"wrangler": "^3.47.1"
}
}
86 changes: 86 additions & 0 deletions packages/script-provider/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Hono } from 'hono'
import { oneLineTrim } from 'proper-tags'
import { Effect, SynchronizedRef, pipe } from 'effect'
import esbuild from 'esbuild-wasm'
import { SiteSubscriptionInfo } from 'storipress-client'
// ref: https://blog.interactions.rest/blog/esbuild-on-the-edge/
// @ts-expect-error no type
// eslint-disable-next-line antfu/no-import-node-modules-by-path
import wasm from '../../../node_modules/esbuild-wasm/esbuild.wasm'
import { GraphqlService } from './services/GraphqlService'

const PRODUCTION_URL = 'https://assets.stori.press/storipress/leaky-paywall.min.js'
const CONFIG_VAR_NAME = 'SP_PAYWALL'

// @ts-expect-error polyfill
globalThis.performance = Date

const javascript = oneLineTrim

const app = new Hono()

app.get('/', (c) => {
return c.text('Not found', 404)
})

const initializedRef = SynchronizedRef.unsafeMake(false)

app.get('/:clientId/prophet.js', (c) => {
const clientId = c.req.param('clientId')

return pipe(
SynchronizedRef.updateEffect(initializedRef, () =>
pipe(
Effect.promise(() =>
esbuild.initialize({
wasmModule: wasm,
worker: false,
}),
),
Effect.as(true),
),
),
Effect.flatMap(() =>
pipe(
GraphqlService,
Effect.flatMap(({ query }) => query(SiteSubscriptionInfo, {})),
Effect.flatMap((res) => {
const config = JSON.stringify({
flags: {
paywall: true,
tracking: true,
},
freeLimit: 3,
pathPattern: null,
all: false,
clientId,
logo: '',
title: res.data?.siteSubscriptionInfo.name ?? 'Welcome',
description: res.data?.siteSubscriptionInfo.description ?? '',
// TODO: need primary color config
primaryColor: 'rgb(29 78 216)',
})
const code = javascript`
window.${CONFIG_VAR_NAME} = ${config};
let s=document.createElement('script');
s.type='module';
s.src='${PRODUCTION_URL}';
document.head.append(s);
`
return Effect.promise(() => esbuild.transform(code, { loader: 'js', minify: true }))
}),
Effect.map((minified) => c.text(minified.code, 200, { 'content-type': 'text/javascript' })),
),
),
Effect.provide(GraphqlService.layer(clientId)),
Effect.catchTag('NotFoundError', () => Effect.succeed(c.text('Not Found', 404))),
Effect.catchAll((error) => {
// eslint-disable-next-line no-console
console.log(error)
return Effect.succeed(c.text('Internal Server Error', 500))
}),
Effect.runPromise,
)
})

export default app
51 changes: 51 additions & 0 deletions packages/script-provider/src/services/GraphqlService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Context, Effect, Inspectable, Layer, pipe } from 'effect'
import type { AnyVariables, CombinedError, OperationResult, TypedDocumentNode } from '@urql/core'
import { Client, cacheExchange, fetchExchange } from '@urql/core'
import { joinURL } from 'ufo'
import { createClient } from '../utils/rest-client'
import { getApiHostUrl, getEnvironmentFromClientId } from '../utils/api-host-url'
import { GraphqlError, NotFoundError } from '../utils/errors'

interface GraphqlServiceImpl {
query: <Data, Variables extends AnyVariables>(
document: TypedDocumentNode<Data, Variables>,
variables: Variables,
) => Effect.Effect<OperationResult<Data, Variables>, GraphqlError>
}

export class GraphqlService extends Context.Tag('@app/GraphqlService')<GraphqlService, GraphqlServiceImpl>() {
static layer = (clientId: string) => {
const env = getEnvironmentFromClientId(clientId)
const restClient = createClient(env)

return Layer.effect(
this,
pipe(
Effect.promise(() => restClient.getTenantState({ params: { clientId } })),
Effect.tap((res) => Effect.log(`check ${clientId} state: ${Inspectable.format(res.body)}`)),
Effect.filterOrFail(
(res) => res.status === 200 && res.body.state === 'online',
() => new NotFoundError(),
),
Effect.map(() => {
const client = new Client({
url: joinURL(getApiHostUrl(env), `/client/${clientId}/graphql`),
exchanges: [cacheExchange, fetchExchange],
})

return GraphqlService.of({
query: (document, variables) => {
return pipe(
Effect.promise(() => client.query(document, variables)),
Effect.filterOrFail(
(res) => res.error === undefined,
(res) => new GraphqlError({ cause: res.error as CombinedError }),
),
)
},
})
}),
),
)
}
}
25 changes: 25 additions & 0 deletions packages/script-provider/src/utils/api-host-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { z } from 'zod'
import { P, match } from 'ts-pattern'

export const EnvironmentSchema = z.enum(['production', 'staging', 'development'])

export type Environment = z.infer<typeof EnvironmentSchema>

export function getEnvironmentFromClientId(clientId: string): Environment {
return match(clientId)
.returnType<Environment>()
.with(P.string.startsWith('P'), () => 'production')
.with(P.string.startsWith('S'), () => 'staging')
.with(P.string.startsWith('D'), () => 'development')
.otherwise(() => 'development')
}

const apiHostMap: Record<Environment, string> = {
production: 'https://api.stori.press',
staging: 'https://api.storipress.pro',
development: 'https://api.storipress.dev',
}

export function getApiHostUrl(environment: Environment): string {
return apiHostMap[environment]
}
6 changes: 6 additions & 0 deletions packages/script-provider/src/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { CombinedError } from '@urql/core'
import { Data } from 'effect'

export class NotFoundError extends Data.TaggedError('NotFoundError') {}

export class GraphqlError extends Data.TaggedError('GraphqlError')<{ cause: CombinedError }> {}
Loading

0 comments on commit ab2b0d5

Please sign in to comment.