Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy-dev2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
- name: Verify the site answers
run: |
for i in $(seq 1 10); do
code=$(curl -s -o /dev/null -w '%{http_code}' "https://saasrow.com/" || true)
code=$(curl -s -o /dev/null -w '%{http_code}' "https://saasrow.pwa.pit.world/" || true)
case "$code" in 2*|3*|401|403) echo "saasrow.com $code"; exit 0;; esac
echo "attempt $i: $code"; sleep 10
done
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ you can create and manage free listings and API keys through the same API,
the MCP write tools, or the CLI in `cli/`:

```bash
curl -fsSL https://saasrow.com/install.sh | sh # or: npm install -g @profullstack/saasrow
curl -fsSL https://saasrow.pwa.pit.world/install.sh | sh # or: npm install -g @profullstack/saasrow
saasrow login
```

Expand Down
8 changes: 4 additions & 4 deletions __tests__/functions/approvalNotify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ describe('network promo', () => {
})

describe('approvalEmail', () => {
const email = approvalEmail('Acme [CRM](https://evil.test)', 'https://saasrow.com/software/abc', 'https://saasrow.com')
const email = approvalEmail('Acme [CRM](https://evil.test)', 'https://saasrow.pwa.pit.world/software/abc', 'https://saasrow.pwa.pit.world')

it('names the listing without letting the title become a link', () => {
expect(email.html).not.toContain('href="https://evil.test"')
expect(email.html).toContain('href="https://saasrow.com/software/abc"')
expect(email.html).toContain('href="https://saasrow.pwa.pit.world/software/abc"')
})

it('carries the promo in both parts', () => {
Expand All @@ -56,7 +56,7 @@ describe('notifyListingApproved', () => {
it('sends one email per contact through the Resend batch API', async () => {
const fetchMock = vi.fn().mockResolvedValue({ ok: true })
vi.stubGlobal('fetch', fetchMock)
vi.stubGlobal('Deno', { env: { get: (k: string) => ({ RESEND_API_KEY: 're_x', SITE_URL: 'https://saasrow.com/' } as any)[k] } })
vi.stubGlobal('Deno', { env: { get: (k: string) => ({ RESEND_API_KEY: 're_x', SITE_URL: 'https://saasrow.pwa.pit.world/' } as any)[k] } })

await notifyListingApproved(fakeDb('Acme', ['a@acme.io', 'b@example.com', 'c@acme.io']), 'abc')

Expand All @@ -66,7 +66,7 @@ describe('notifyListingApproved', () => {
const batch = JSON.parse(init.body)
expect(batch.map((m: any) => m.to)).toEqual(['a@acme.io', 'c@acme.io'])
expect(batch[0].subject).toBe('Acme is live on SaaSRow')
expect(batch[0].html).toContain('href="https://saasrow.com/software/abc"')
expect(batch[0].html).toContain('href="https://saasrow.pwa.pit.world/software/abc"')
})

it('does nothing without a Resend key', async () => {
Expand Down
4 changes: 2 additions & 2 deletions __tests__/functions/coinpay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ describe('buildSessionRequest', () => {
mode: 'subscription',
interval: 'month',
customer_email: ' Riotcoder+saasrow-cs-test@profullstack.com ',
success_url: 'https://saasrow.com/featured?success=true&session={CHECKOUT_SESSION_ID}',
cancel_url: 'https://saasrow.com/featured?cancelled=true',
success_url: 'https://saasrow.pwa.pit.world/featured?success=true&session={CHECKOUT_SESSION_ID}',
cancel_url: 'https://saasrow.pwa.pit.world/featured?cancelled=true',
}

it('prices on the server and sends the CoinPay contract fields', () => {
Expand Down
2 changes: 1 addition & 1 deletion __tests__/functions/managementAccess.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,6 @@ describe('listingIdsForEmail', () => {

describe('managementUrlFor', () => {
it('keeps a base64 user token in one path segment', () => {
expect(managementUrlFor('https://saasrow.com/', 'Jm8g/e0V+A=')).toBe('https://saasrow.com/manage/Jm8g%2Fe0V%2BA%3D')
expect(managementUrlFor('https://saasrow.pwa.pit.world/', 'Jm8g/e0V+A=')).toBe('https://saasrow.pwa.pit.world/manage/Jm8g%2Fe0V%2BA%3D')
})
})
2 changes: 1 addition & 1 deletion __tests__/lib/crawlGateway.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const CHROME =
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36'

function req(method: string, ip: string, path = SUBMIT_PATH) {
return new Request(`https://saasrow.com${path}`, {
return new Request(`https://saasrow.pwa.pit.world${path}`, {
method,
headers: {
'user-agent': CHROME,
Expand Down
8 changes: 4 additions & 4 deletions __tests__/lib/credits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ describe('settleBonus', () => {

describe('acceptInvite', () => {
it('refuses an established account before touching the invite', async () => {
const req = new Request('https://saasrow.com/api/v1/me/invites/accept')
const req = new Request('https://saasrow.pwa.pit.world/api/v1/me/invites/accept')
const r = await acceptInvite({ userId: 'u-alice', email: 'alice@example.com' }, 'INV-AAAA-BBBB-CCCC', req)
expect(r.accepted).toBe(false)
expect(rpc).not.toHaveBeenCalled()
Expand All @@ -117,7 +117,7 @@ describe('acceptInvite', () => {
rpc.mockImplementation(async (fn: string) =>
fn === 'aff_invited_by' ? { data: 'u-alice', error: null } : { data: null, error: null },
)
const req = new Request('https://saasrow.com/api/v1/me/invites/accept')
const req = new Request('https://saasrow.pwa.pit.world/api/v1/me/invites/accept')
const r = await acceptInvite({ userId: 'u-carol', email: 'carol@example.org' }, 'INV-AAAA-BBBB-CCCC', req)
expect(r).toEqual({ accepted: false, message: 'You have already joined on an invite.' })
expect(rpc.mock.calls.map((c) => c[0])).not.toContain('aff_claim_invite')
Expand All @@ -132,14 +132,14 @@ describe('acceptInvite', () => {
return { data: null, error: null }
})
tables.invite_bonuses = []
const req = new Request('https://saasrow.com/api/v1/me/invites/accept')
const req = new Request('https://saasrow.pwa.pit.world/api/v1/me/invites/accept')
const r = await acceptInvite({ userId: 'u-carol', email: 'carol@example.org' }, 'INV-AAAA-BBBB-CCCC', req)
expect(r).toEqual({ accepted: false, message: 'You have already joined on an invite.' })
expect(rpc.mock.calls.map((c) => c[0])).toContain('aff_claim_invite')
expect(rpc.mock.calls.map((c) => c[0])).not.toContain('settle_invite_bonus')
})
it('refuses something that is not an invite code', async () => {
const req = new Request('https://saasrow.com/')
const req = new Request('https://saasrow.pwa.pit.world/')
expect((await acceptInvite({ userId: 'u-carol', email: 'c' }, 'CR-AAAA-BBBB-CCCC-DDDD', req)).accepted).toBe(false)
})
})
Expand Down
24 changes: 12 additions & 12 deletions __tests__/lib/emailMarkdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ describe('markdownToEmailHtml', () => {
})

it('renders markdown links and bare URLs', () => {
const html = markdownToEmailHtml('See [SaaSRow](https://saasrow.com) or https://saasrow.com/pricing')
expect(html).toContain('<a href="https://saasrow.com" style=')
const html = markdownToEmailHtml('See [SaaSRow](https://saasrow.pwa.pit.world) or https://saasrow.pwa.pit.world/pricing')
expect(html).toContain('<a href="https://saasrow.pwa.pit.world" style=')
expect(html).toContain('>SaaSRow</a>')
expect(html).toContain('<a href="https://saasrow.com/pricing"')
expect(html).toContain('<a href="https://saasrow.pwa.pit.world/pricing"')
})

it('links bare email addresses with mailto', () => {
Expand Down Expand Up @@ -82,8 +82,8 @@ describe('markdownToEmailHtml', () => {
})

it('renders images', () => {
const html = markdownToEmailHtml('![logo](https://saasrow.com/logo.png)')
expect(html).toContain('<img src="https://saasrow.com/logo.png" alt="logo"')
const html = markdownToEmailHtml('![logo](https://saasrow.pwa.pit.world/logo.png)')
expect(html).toContain('<img src="https://saasrow.pwa.pit.world/logo.png" alt="logo"')
})

it('escapes pasted HTML instead of rendering it', () => {
Expand Down Expand Up @@ -114,9 +114,9 @@ describe('markdownToEmailHtml', () => {

describe('markdownToPlainText', () => {
it('strips markdown syntax but keeps the text readable', () => {
const text = markdownToPlainText('# Title\n\n**Bold** and [a link](https://saasrow.com)\n\n- one\n- two')
const text = markdownToPlainText('# Title\n\n**Bold** and [a link](https://saasrow.pwa.pit.world)\n\n- one\n- two')
expect(text).toContain('Title')
expect(text).toContain('Bold and a link (https://saasrow.com)')
expect(text).toContain('Bold and a link (https://saasrow.pwa.pit.world)')
expect(text).toContain('- one')
expect(text).not.toContain('**')
expect(text).not.toContain('#')
Expand All @@ -130,16 +130,16 @@ describe('markdownToPlainText', () => {
describe('buildEmailParts', () => {
const footer = [
{ text: 'You subscribed.' },
{ text: 'Unsubscribe', href: 'https://saasrow.com/unsubscribe?email=a%40b.co' },
{ text: 'Unsubscribe', href: 'https://saasrow.pwa.pit.world/unsubscribe?email=a%40b.co' },
]

it('builds an HTML document and a matching text/plain part from one markdown source', () => {
const { html, text } = buildEmailParts({ markdown: '## Hi\n\nSee [the site](https://saasrow.com).', heading: 'SaaSRow', footer })
const { html, text } = buildEmailParts({ markdown: '## Hi\n\nSee [the site](https://saasrow.pwa.pit.world).', heading: 'SaaSRow', footer })
expect(html.startsWith('<!DOCTYPE html>')).toBe(true)
expect(html).toContain('>Hi</h2>')
expect(html).toContain('href="https://saasrow.com/unsubscribe?email=a%40b.co"')
expect(text).toContain('Hi\n\nSee the site (https://saasrow.com).')
expect(text).toContain('\n--\nYou subscribed.\nUnsubscribe: https://saasrow.com/unsubscribe?email=a%40b.co\n')
expect(html).toContain('href="https://saasrow.pwa.pit.world/unsubscribe?email=a%40b.co"')
expect(text).toContain('Hi\n\nSee the site (https://saasrow.pwa.pit.world).')
expect(text).toContain('\n--\nYou subscribed.\nUnsubscribe: https://saasrow.pwa.pit.world/unsubscribe?email=a%40b.co\n')
expect(text).not.toContain('<')
})

Expand Down
10 changes: 5 additions & 5 deletions __tests__/lib/inviteRules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ describe('helpers', () => {
expect(looksLikeInviteCode(42)).toBe(false)
})
it('siteOriginFrom uses the request host, never a foreign one', () => {
expect(siteOriginFrom(new Headers({ host: 'saasrow.com' }))).toBe('https://saasrow.com')
expect(siteOriginFrom(new Headers({ host: 'www.saasrow.com' }))).toBe('https://saasrow.com')
expect(siteOriginFrom(new Headers({ host: 'saasrow.pwa.pit.world' }))).toBe('https://saasrow.pwa.pit.world')
expect(siteOriginFrom(new Headers({ host: 'www.saasrow.com' }))).toBe('https://saasrow.pwa.pit.world')
expect(siteOriginFrom(new Headers({ host: 'localhost:3000' }))).toBe('http://localhost:3000')
expect(siteOriginFrom(new Headers({ 'x-forwarded-host': 'evil.example', host: 'saasrow.com' }))).toBe('https://saasrow.com')
expect(siteOriginFrom(new Headers({ 'x-forwarded-host': 'saasrow.com', 'x-forwarded-proto': 'https', host: '127.0.0.1:3000' }))).toBe('https://saasrow.com')
expect(siteOriginFrom(new Headers({ 'x-forwarded-host': 'evil.example', host: 'saasrow.pwa.pit.world' }))).toBe('https://saasrow.pwa.pit.world')
expect(siteOriginFrom(new Headers({ 'x-forwarded-host': 'saasrow.pwa.pit.world', 'x-forwarded-proto': 'https', host: '127.0.0.1:3000' }))).toBe('https://saasrow.pwa.pit.world')
})
it('inviteUrl', () => {
expect(inviteUrl('https://saasrow.com', 'INV-AAAA-BBBB-CCCC')).toBe('https://saasrow.com/i/INV-AAAA-BBBB-CCCC')
expect(inviteUrl('https://saasrow.pwa.pit.world', 'INV-AAAA-BBBB-CCCC')).toBe('https://saasrow.pwa.pit.world/i/INV-AAAA-BBBB-CCCC')
})
})
2 changes: 1 addition & 1 deletion __tests__/lib/memberSession.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { clearMemberCookie, readMemberCookie, setMemberCookie } from '@/lib/memberSession'

const req = (cookie?: string) => new Request('https://saasrow.com/', { headers: cookie ? { cookie } : {} })
const req = (cookie?: string) => new Request('https://saasrow.pwa.pit.world/', { headers: cookie ? { cookie } : {} })
const valueOf = (setCookie: string) => setCookie.split(';')[0]

describe('member session cookie', () => {
Expand Down
6 changes: 3 additions & 3 deletions __tests__/lib/submissionGuard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest'
import { screenSubmission } from '@/lib/submission-guard'

const post = (ip: string, d: string, path = '/api/fn/submissions') =>
new Request(`https://saasrow.com${path}`, {
new Request(`https://saasrow.pwa.pit.world${path}`, {
method: 'POST',
headers: { 'x-real-ip': ip, 'content-type': 'application/json' },
body: JSON.stringify({ title: d, url: `https://${d}.com`, email: `hello@${d}.com`, description: 'x', category: 'Software' }),
Expand All @@ -24,12 +24,12 @@ describe('screenSubmission', () => {
})

it('leaves reads and other routes alone', async () => {
expect(await screenSubmission(new Request('https://saasrow.com/api/fn/submissions'))).toBeNull()
expect(await screenSubmission(new Request('https://saasrow.pwa.pit.world/api/fn/submissions'))).toBeNull()
expect(await screenSubmission(post('192.0.2.67', 'x', '/api/fn/vote'))).toBeNull()
})

it('passes a malformed body through to the edge function', async () => {
const r = new Request('https://saasrow.com/api/fn/submissions', { method: 'POST', body: 'not json' })
const r = new Request('https://saasrow.pwa.pit.world/api/fn/submissions', { method: 'POST', body: 'not json' })
expect(await screenSubmission(r)).toBeNull()
})
})
2 changes: 1 addition & 1 deletion __tests__/lib/webhooks/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ vi.mock('@/lib/webhooks/hub', async (orig) => ({ ...(await orig<object>()), getH

const { GET, POST, DELETE } = await import('../../../app/api/v1/webhooks/[[...path]]/route')

const req = (path: string, init: RequestInit = {}) => new Request(`https://saasrow.com/api/v1/webhooks${path}`, init)
const req = (path: string, init: RequestInit = {}) => new Request(`https://saasrow.pwa.pit.world/api/v1/webhooks${path}`, init)

describe('/api/v1/webhooks', () => {
beforeEach(() => {
Expand Down
6 changes: 3 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import JsonLd from '@/components/JsonLd'
import { websiteLd, organizationLd } from '@/lib/structuredData'
import './globals.css'

const SITE_URL = 'https://saasrow.com'
const SITE_URL = 'https://saasrow.pwa.pit.world'

// Re-render hourly so @profullstack/footer and the pit.world footer stay fresh.
export const revalidate = 3600
Expand Down Expand Up @@ -60,7 +60,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<KarmaToast />
</ReferralProvider>
<PfsFooter
site="https://saasrow.com/"
site="https://saasrow.pwa.pit.world/"
since={2025}
links={[
{ label: 'About', href: '/about' },
Expand All @@ -81,7 +81,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<Script
src="https://datafa.st/js/script.js"
data-website-id="dfid_ymwgZYB7fCWFQQX3c74DG"
data-domain="saasrow.com"
data-domain="saasrow.pwa.pit.world"
strategy="afterInteractive"
defer
/>
Expand Down
2 changes: 1 addition & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ terminal. Search the directory without an account; create and manage your own
free listings and API keys with one.

```bash
curl -fsSL https://saasrow.com/install.sh | sh
curl -fsSL https://saasrow.pwa.pit.world/install.sh | sh
# or with npm
npm install -g @profullstack/saasrow
# or run it without installing
Expand Down
4 changes: 2 additions & 2 deletions cli/test/cli.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,11 @@ test('redeem shows the refusal and exits non-zero', async () => {

test('invites create prints the link once; invites lists them', async () => {
const h = harness({
'POST /api/v1/me/invites': (c) => [201, { id: 'i1', code: 'INV-AAAA-BBBB-CCCC', url: 'https://saasrow.com/i/INV-AAAA-BBBB-CCCC', expires_at: '2026-11-25T00:00:00Z', quota: { total: 3, used: 1, left: 2 }, note: c.body.note }],
'POST /api/v1/me/invites': (c) => [201, { id: 'i1', code: 'INV-AAAA-BBBB-CCCC', url: 'https://saasrow.pwa.pit.world/i/INV-AAAA-BBBB-CCCC', expires_at: '2026-11-25T00:00:00Z', quota: { total: 3, used: 1, left: 2 }, note: c.body.note }],
'GET /api/v1/me/invites': () => [200, {
quota: { total: 3, used: 1, left: 2 }, can_send: true, can_send_reason: null, bonus_days: 7,
invites: [{ id: 'i1', note: 'for Carol', status: 'unused', bonus: null, created_at: '2026-10-26T00:00:00Z', expires_at: null }],
affiliate_link: 'https://saasrow.com/?ref=ABCD1234',
affiliate_link: 'https://saasrow.pwa.pit.world/?ref=ABCD1234',
}],
}, { env: { SAASROW_API_KEY: 'sr_env' } })
try {
Expand Down
6 changes: 3 additions & 3 deletions public/.well-known/openaccess.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"openaccess": "0.1",
"name": "saasrow",
"url": "https://saasrow.com",
"url": "https://saasrow.pwa.pit.world",
"operator": "https://logicsrc.com/.well-known/openprofile.md",
"redirect_uris": [
"https://saasrow.com/api/v1/openaccess/callback"
"https://saasrow.pwa.pit.world/api/v1/openaccess/callback"
],
"jwks": {
"keys": [
Expand All @@ -22,7 +22,7 @@
"honours": [
"profullstack.com/all-access"
],
"webhooks": "https://saasrow.com/api/v1/openaccess/events",
"webhooks": "https://saasrow.pwa.pit.world/api/v1/openaccess/events",
"hubs": [
"https://openaccess.logicsrc.com"
]
Expand Down
4 changes: 2 additions & 2 deletions public/.well-known/openmcp.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"openmcp": "0.1",
"mcp": "https://saasrow.com/api/mcp",
"mcp": "https://saasrow.pwa.pit.world/api/mcp",
"name": "saasrow",
"description": "A directory of SaaS products, searchable by what they do.",
"url": "https://saasrow.com",
"url": "https://saasrow.pwa.pit.world",
"auth": {
"kind": "none"
},
Expand Down
2 changes: 1 addition & 1 deletion public/.well-known/openwebring.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"openwebring": "0.1",
"site": { "url": "https://saasrow.com/", "name": "SaaSRow" },
"site": { "url": "https://saasrow.pwa.pit.world/", "name": "SaaSRow" },
"made_by": "both",
"rings": [{ "ring": "https://rssamplifier.com/ring/profullstack", "slug": "saasrow-blog" }]
}
2 changes: 1 addition & 1 deletion public/install.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
# Install saasrow, the saasrow.com CLI (npm: @profullstack/saasrow).
#
# curl -fsSL https://saasrow.com/install.sh | sh
# curl -fsSL https://saasrow.pwa.pit.world/install.sh | sh
#
# Uses bun if present, otherwise npm (Node >= 20.11). When the global npm
# prefix is not writable it installs into ~/.local instead of asking for sudo.
Expand Down
2 changes: 1 addition & 1 deletion src/lib/broadcastDraft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ subject: 35-70 characters, plain text, no emojis, no clickbait, no trailing peri
content: the email body in Markdown. Allowed syntax: ## headings, **bold**, *italic*, [links](https://...), - bullets, 1. numbered lists, > quotes, --- rules. No HTML, no images, no tables, no code fences.
- Open with the point, not a greeting paragraph. A short "Hi there," line is fine.
- Keep it to what the description asks for; never invent numbers, dates, prices, features or quotes. If a detail is needed but missing, leave a clear [placeholder] in square brackets.
- One clear call to action, linked, when the description implies one. Use https://saasrow.com links unless the description gives others.
- One clear call to action, linked, when the description implies one. Use https://saasrow.pwa.pit.world links unless the description gives others.
- 80-250 words unless the description asks for more.
- Sign off as "The SaaSRow team".
- Never use em dashes or en dashes; use commas or hyphens. No emojis.
Expand Down
2 changes: 1 addition & 1 deletion src/lib/crawl-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function submitQuotaKey(request: Request): string | null {
}

export const gateway = createGateway({
siteUrl: env("SITE_URL") || env("NEXT_PUBLIC_SITE_URL") || "https://saasrow.com",
siteUrl: env("SITE_URL") || env("NEXT_PUBLIC_SITE_URL") || "https://saasrow.pwa.pit.world",
siteName: "saasrow",
coinpay: { apiKey: env("COINPAY_X402_KEY") },
payTo: env("CRAWL_PAY_TO"),
Expand Down
4 changes: 2 additions & 2 deletions src/lib/inviteRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ const ALLOWED_HOSTS = /^(?:www\.)?saasrow\.com$|^localhost(?::\d+)?$|^127\.0\.0\
* dev or preview host builds links to itself), falling back to the canonical
* apex for anything we do not recognise.
*/
export function siteOriginFrom(headers: Headers, fallback = 'https://saasrow.com'): string {
export function siteOriginFrom(headers: Headers, fallback = 'https://saasrow.pwa.pit.world'): string {
const host = (headers.get('x-forwarded-host') ?? headers.get('host') ?? '').split(',')[0].trim().toLowerCase()
if (!host || !ALLOWED_HOSTS.test(host)) return fallback
if (host === 'www.saasrow.com') return 'https://saasrow.com'
if (host === 'www.saasrow.com') return 'https://saasrow.pwa.pit.world'
const local = host.startsWith('localhost') || host.startsWith('127.0.0.1')
const proto = (headers.get('x-forwarded-proto') ?? (local ? 'http' : 'https')).split(',')[0].trim()
return `${proto === 'http' ? 'http' : 'https'}://${host}`
Expand Down
Loading
Loading