@fewchurs/next 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/actions.js +1 -1
- package/dist/actions.js.map +1 -1
- package/dist/actions.mjs +1 -1
- package/dist/actions.mjs.map +1 -1
- package/dist/{chunk-VY4D2Z6L.mjs → chunk-KPJHLDQU.mjs} +2 -2
- package/dist/{chunk-P6QQPQVC.mjs → chunk-QULKPZB4.mjs} +2 -2
- package/dist/{chunk-SSHXXI5K.mjs → chunk-W6QR6DEB.mjs} +2 -2
- package/dist/{chunk-SSHXXI5K.mjs.map → chunk-W6QR6DEB.mjs.map} +1 -1
- package/dist/handler.js +1 -1
- package/dist/handler.js.map +1 -1
- package/dist/handler.mjs +2 -2
- package/dist/identity.js +1 -1
- package/dist/identity.js.map +1 -1
- package/dist/identity.mjs +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/package.json +3 -3
- /package/dist/{chunk-VY4D2Z6L.mjs.map → chunk-KPJHLDQU.mjs.map} +0 -0
- /package/dist/{chunk-P6QQPQVC.mjs.map → chunk-QULKPZB4.mjs.map} +0 -0
package/LICENSE
CHANGED
|
@@ -38,4 +38,4 @@ The source code is published for transparency only. It is not open source.
|
|
|
38
38
|
OF OR OTHER DEALINGS IN THE SDK.
|
|
39
39
|
|
|
40
40
|
Your use of the Fewchurs service itself is governed separately by your
|
|
41
|
-
agreement with Fewchurs (https://www.fewchurs.
|
|
41
|
+
agreement with Fewchurs (https://www.fewchurs.com).
|
package/dist/actions.js
CHANGED
package/dist/actions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/actions.ts","../src/config.ts","../src/identity.ts","../src/version.ts","../src/constants.ts"],"sourcesContent":["\"use server\";\n\nimport { revalidatePath } from \"next/cache\";\nimport { userMessage } from \"@fewchurs/react/core\";\nimport { serverClient } from \"./config\";\nimport { resolveDeviceId } from \"./identity\";\n\nexport interface ActionResult {\n ok: boolean;\n message?: string;\n}\n\n/**\n * Server actions for boards built with plain forms — the client board talks to the route\n * handler instead. Pass `path` when the page should re-render with the new data.\n */\nexport async function submitFeatureAction(formData: FormData): Promise<ActionResult> {\n const client = serverClient(await resolveDeviceId());\n const result = await client.submitFeature({\n title: String(formData.get(\"title\") ?? \"\"),\n description: String(formData.get(\"description\") ?? \"\"),\n email: optional(formData.get(\"email\")),\n });\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function voteAction(formData: FormData): Promise<ActionResult> {\n const featureId = String(formData.get(\"featureId\") ?? \"\");\n const hasVoted = formData.get(\"hasVoted\") === \"true\";\n const client = serverClient(await resolveDeviceId());\n const result = hasVoted ? await client.unvote(featureId) : await client.vote(featureId);\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function commentAction(formData: FormData): Promise<ActionResult> {\n const client = serverClient(await resolveDeviceId());\n const result = await client.createComment({\n featureId: String(formData.get(\"featureId\") ?? \"\"),\n text: String(formData.get(\"text\") ?? \"\"),\n authorName: optional(formData.get(\"authorName\")),\n });\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function followAction(formData: FormData): Promise<ActionResult> {\n const featureId = String(formData.get(\"featureId\") ?? \"\");\n const email = optional(formData.get(\"email\"));\n const client = serverClient(await resolveDeviceId());\n const result = email ? await client.follow(featureId, email) : await client.unfollow(featureId);\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nfunction optional(value: FormDataEntryValue | null): string | undefined {\n const text = typeof value === \"string\" ? value.trim() : \"\";\n return text.length > 0 ? text : undefined;\n}\n\nfunction revalidate(formData: FormData): void {\n const path = formData.get(\"path\");\n if (typeof path === \"string\" && path.startsWith(\"/\")) revalidatePath(path);\n}\n","import \"server-only\";\nimport { createFewchursClient, DEFAULT_BASE_URL, type FewchursClient } from \"@fewchurs/react/core\";\n\nexport const DEFAULT_API_PATH = \"/api/fewchurs\";\n\nexport interface ServerConfig {\n apiKey?: string;\n baseUrl?: string;\n language?: string;\n}\n\nexport function resolveApiKey(apiKey?: string): string | undefined {\n return apiKey ?? process.env.FEWCHURS_API_KEY;\n}\n\nexport function resolveBaseUrl(baseUrl?: string): string {\n return baseUrl ?? process.env.FEWCHURS_BASE_URL ?? DEFAULT_BASE_URL;\n}\n\nexport function resolveApiPath(apiPath?: string): string {\n return (apiPath ?? process.env.FEWCHURS_API_PATH ?? DEFAULT_API_PATH).replace(/\\/$/, \"\");\n}\n\nexport function serverClient(deviceId: string | null, config: ServerConfig = {}): FewchursClient {\n const apiKey = resolveApiKey(config.apiKey);\n if (!apiKey) {\n throw new Error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY in your environment, or pass apiKey to the server component or createHandler().\",\n );\n }\n\n return createFewchursClient({\n apiKey,\n baseUrl: resolveBaseUrl(config.baseUrl),\n deviceId,\n language: config.language,\n });\n}\n","import \"server-only\";\nimport { cookies } from \"next/headers\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE } from \"./constants\";\n\nexport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE };\n\nexport type DeviceIdResolver = () => string | Promise<string>;\n\n/** The visitor's id, or null when nothing has been stored for them yet. */\nexport async function readDeviceId(): Promise<string | null> {\n const store = await cookies();\n return store.get(DEVICE_ID_COOKIE)?.value ?? null;\n}\n\n/**\n * Reads the cookie, falling back to a fresh id. The value is not persisted here: a server\n * component cannot set cookies, so the route handler stores it on the first request instead.\n */\nexport async function resolveDeviceId(custom?: DeviceIdResolver): Promise<string> {\n if (custom) return custom();\n return (await readDeviceId()) ?? createDeviceId();\n}\n\nexport function deviceIdCookie(deviceId: string): {\n name: string;\n value: string;\n options: {\n httpOnly: true;\n sameSite: \"lax\";\n path: string;\n maxAge: number;\n secure: boolean;\n };\n} {\n return {\n name: DEVICE_ID_COOKIE,\n value: deviceId,\n options: {\n httpOnly: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: DEVICE_ID_MAX_AGE,\n secure: process.env.NODE_ENV === \"production\",\n },\n };\n}\n\nexport { createDeviceId };\n","// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.
|
|
1
|
+
{"version":3,"sources":["../src/actions.ts","../src/config.ts","../src/identity.ts","../src/version.ts","../src/constants.ts"],"sourcesContent":["\"use server\";\n\nimport { revalidatePath } from \"next/cache\";\nimport { userMessage } from \"@fewchurs/react/core\";\nimport { serverClient } from \"./config\";\nimport { resolveDeviceId } from \"./identity\";\n\nexport interface ActionResult {\n ok: boolean;\n message?: string;\n}\n\n/**\n * Server actions for boards built with plain forms — the client board talks to the route\n * handler instead. Pass `path` when the page should re-render with the new data.\n */\nexport async function submitFeatureAction(formData: FormData): Promise<ActionResult> {\n const client = serverClient(await resolveDeviceId());\n const result = await client.submitFeature({\n title: String(formData.get(\"title\") ?? \"\"),\n description: String(formData.get(\"description\") ?? \"\"),\n email: optional(formData.get(\"email\")),\n });\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function voteAction(formData: FormData): Promise<ActionResult> {\n const featureId = String(formData.get(\"featureId\") ?? \"\");\n const hasVoted = formData.get(\"hasVoted\") === \"true\";\n const client = serverClient(await resolveDeviceId());\n const result = hasVoted ? await client.unvote(featureId) : await client.vote(featureId);\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function commentAction(formData: FormData): Promise<ActionResult> {\n const client = serverClient(await resolveDeviceId());\n const result = await client.createComment({\n featureId: String(formData.get(\"featureId\") ?? \"\"),\n text: String(formData.get(\"text\") ?? \"\"),\n authorName: optional(formData.get(\"authorName\")),\n });\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function followAction(formData: FormData): Promise<ActionResult> {\n const featureId = String(formData.get(\"featureId\") ?? \"\");\n const email = optional(formData.get(\"email\"));\n const client = serverClient(await resolveDeviceId());\n const result = email ? await client.follow(featureId, email) : await client.unfollow(featureId);\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nfunction optional(value: FormDataEntryValue | null): string | undefined {\n const text = typeof value === \"string\" ? value.trim() : \"\";\n return text.length > 0 ? text : undefined;\n}\n\nfunction revalidate(formData: FormData): void {\n const path = formData.get(\"path\");\n if (typeof path === \"string\" && path.startsWith(\"/\")) revalidatePath(path);\n}\n","import \"server-only\";\nimport { createFewchursClient, DEFAULT_BASE_URL, type FewchursClient } from \"@fewchurs/react/core\";\n\nexport const DEFAULT_API_PATH = \"/api/fewchurs\";\n\nexport interface ServerConfig {\n apiKey?: string;\n baseUrl?: string;\n language?: string;\n}\n\nexport function resolveApiKey(apiKey?: string): string | undefined {\n return apiKey ?? process.env.FEWCHURS_API_KEY;\n}\n\nexport function resolveBaseUrl(baseUrl?: string): string {\n return baseUrl ?? process.env.FEWCHURS_BASE_URL ?? DEFAULT_BASE_URL;\n}\n\nexport function resolveApiPath(apiPath?: string): string {\n return (apiPath ?? process.env.FEWCHURS_API_PATH ?? DEFAULT_API_PATH).replace(/\\/$/, \"\");\n}\n\nexport function serverClient(deviceId: string | null, config: ServerConfig = {}): FewchursClient {\n const apiKey = resolveApiKey(config.apiKey);\n if (!apiKey) {\n throw new Error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY in your environment, or pass apiKey to the server component or createHandler().\",\n );\n }\n\n return createFewchursClient({\n apiKey,\n baseUrl: resolveBaseUrl(config.baseUrl),\n deviceId,\n language: config.language,\n });\n}\n","import \"server-only\";\nimport { cookies } from \"next/headers\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE } from \"./constants\";\n\nexport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE };\n\nexport type DeviceIdResolver = () => string | Promise<string>;\n\n/** The visitor's id, or null when nothing has been stored for them yet. */\nexport async function readDeviceId(): Promise<string | null> {\n const store = await cookies();\n return store.get(DEVICE_ID_COOKIE)?.value ?? null;\n}\n\n/**\n * Reads the cookie, falling back to a fresh id. The value is not persisted here: a server\n * component cannot set cookies, so the route handler stores it on the first request instead.\n */\nexport async function resolveDeviceId(custom?: DeviceIdResolver): Promise<string> {\n if (custom) return custom();\n return (await readDeviceId()) ?? createDeviceId();\n}\n\nexport function deviceIdCookie(deviceId: string): {\n name: string;\n value: string;\n options: {\n httpOnly: true;\n sameSite: \"lax\";\n path: string;\n maxAge: number;\n secure: boolean;\n };\n} {\n return {\n name: DEVICE_ID_COOKIE,\n value: deviceId,\n options: {\n httpOnly: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: DEVICE_ID_MAX_AGE,\n secure: process.env.NODE_ENV === \"production\",\n },\n };\n}\n\nexport { createDeviceId };\n","// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.1\";\n","import { SDK_VERSION } from \"./version\";\n\nexport const DEVICE_ID_COOKIE = \"fewchurs_did\";\nexport const DEVICE_ID_MAX_AGE = 60 * 60 * 24 * 365;\nexport { SDK_VERSION };\nexport const SDK_HEADER = `next/${SDK_VERSION}`;\n"],"mappings":"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","names":["import_core","import_server_only","import_core"]}
|
package/dist/actions.mjs
CHANGED
package/dist/actions.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/actions.ts","../src/config.ts","../src/identity.ts","../src/version.ts","../src/constants.ts"],"sourcesContent":["\"use server\";\n\nimport { revalidatePath } from \"next/cache\";\nimport { userMessage } from \"@fewchurs/react/core\";\nimport { serverClient } from \"./config\";\nimport { resolveDeviceId } from \"./identity\";\n\nexport interface ActionResult {\n ok: boolean;\n message?: string;\n}\n\n/**\n * Server actions for boards built with plain forms — the client board talks to the route\n * handler instead. Pass `path` when the page should re-render with the new data.\n */\nexport async function submitFeatureAction(formData: FormData): Promise<ActionResult> {\n const client = serverClient(await resolveDeviceId());\n const result = await client.submitFeature({\n title: String(formData.get(\"title\") ?? \"\"),\n description: String(formData.get(\"description\") ?? \"\"),\n email: optional(formData.get(\"email\")),\n });\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function voteAction(formData: FormData): Promise<ActionResult> {\n const featureId = String(formData.get(\"featureId\") ?? \"\");\n const hasVoted = formData.get(\"hasVoted\") === \"true\";\n const client = serverClient(await resolveDeviceId());\n const result = hasVoted ? await client.unvote(featureId) : await client.vote(featureId);\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function commentAction(formData: FormData): Promise<ActionResult> {\n const client = serverClient(await resolveDeviceId());\n const result = await client.createComment({\n featureId: String(formData.get(\"featureId\") ?? \"\"),\n text: String(formData.get(\"text\") ?? \"\"),\n authorName: optional(formData.get(\"authorName\")),\n });\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function followAction(formData: FormData): Promise<ActionResult> {\n const featureId = String(formData.get(\"featureId\") ?? \"\");\n const email = optional(formData.get(\"email\"));\n const client = serverClient(await resolveDeviceId());\n const result = email ? await client.follow(featureId, email) : await client.unfollow(featureId);\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nfunction optional(value: FormDataEntryValue | null): string | undefined {\n const text = typeof value === \"string\" ? value.trim() : \"\";\n return text.length > 0 ? text : undefined;\n}\n\nfunction revalidate(formData: FormData): void {\n const path = formData.get(\"path\");\n if (typeof path === \"string\" && path.startsWith(\"/\")) revalidatePath(path);\n}\n","import \"server-only\";\nimport { createFewchursClient, DEFAULT_BASE_URL, type FewchursClient } from \"@fewchurs/react/core\";\n\nexport const DEFAULT_API_PATH = \"/api/fewchurs\";\n\nexport interface ServerConfig {\n apiKey?: string;\n baseUrl?: string;\n language?: string;\n}\n\nexport function resolveApiKey(apiKey?: string): string | undefined {\n return apiKey ?? process.env.FEWCHURS_API_KEY;\n}\n\nexport function resolveBaseUrl(baseUrl?: string): string {\n return baseUrl ?? process.env.FEWCHURS_BASE_URL ?? DEFAULT_BASE_URL;\n}\n\nexport function resolveApiPath(apiPath?: string): string {\n return (apiPath ?? process.env.FEWCHURS_API_PATH ?? DEFAULT_API_PATH).replace(/\\/$/, \"\");\n}\n\nexport function serverClient(deviceId: string | null, config: ServerConfig = {}): FewchursClient {\n const apiKey = resolveApiKey(config.apiKey);\n if (!apiKey) {\n throw new Error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY in your environment, or pass apiKey to the server component or createHandler().\",\n );\n }\n\n return createFewchursClient({\n apiKey,\n baseUrl: resolveBaseUrl(config.baseUrl),\n deviceId,\n language: config.language,\n });\n}\n","import \"server-only\";\nimport { cookies } from \"next/headers\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE } from \"./constants\";\n\nexport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE };\n\nexport type DeviceIdResolver = () => string | Promise<string>;\n\n/** The visitor's id, or null when nothing has been stored for them yet. */\nexport async function readDeviceId(): Promise<string | null> {\n const store = await cookies();\n return store.get(DEVICE_ID_COOKIE)?.value ?? null;\n}\n\n/**\n * Reads the cookie, falling back to a fresh id. The value is not persisted here: a server\n * component cannot set cookies, so the route handler stores it on the first request instead.\n */\nexport async function resolveDeviceId(custom?: DeviceIdResolver): Promise<string> {\n if (custom) return custom();\n return (await readDeviceId()) ?? createDeviceId();\n}\n\nexport function deviceIdCookie(deviceId: string): {\n name: string;\n value: string;\n options: {\n httpOnly: true;\n sameSite: \"lax\";\n path: string;\n maxAge: number;\n secure: boolean;\n };\n} {\n return {\n name: DEVICE_ID_COOKIE,\n value: deviceId,\n options: {\n httpOnly: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: DEVICE_ID_MAX_AGE,\n secure: process.env.NODE_ENV === \"production\",\n },\n };\n}\n\nexport { createDeviceId };\n","// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.
|
|
1
|
+
{"version":3,"sources":["../src/actions.ts","../src/config.ts","../src/identity.ts","../src/version.ts","../src/constants.ts"],"sourcesContent":["\"use server\";\n\nimport { revalidatePath } from \"next/cache\";\nimport { userMessage } from \"@fewchurs/react/core\";\nimport { serverClient } from \"./config\";\nimport { resolveDeviceId } from \"./identity\";\n\nexport interface ActionResult {\n ok: boolean;\n message?: string;\n}\n\n/**\n * Server actions for boards built with plain forms — the client board talks to the route\n * handler instead. Pass `path` when the page should re-render with the new data.\n */\nexport async function submitFeatureAction(formData: FormData): Promise<ActionResult> {\n const client = serverClient(await resolveDeviceId());\n const result = await client.submitFeature({\n title: String(formData.get(\"title\") ?? \"\"),\n description: String(formData.get(\"description\") ?? \"\"),\n email: optional(formData.get(\"email\")),\n });\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function voteAction(formData: FormData): Promise<ActionResult> {\n const featureId = String(formData.get(\"featureId\") ?? \"\");\n const hasVoted = formData.get(\"hasVoted\") === \"true\";\n const client = serverClient(await resolveDeviceId());\n const result = hasVoted ? await client.unvote(featureId) : await client.vote(featureId);\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function commentAction(formData: FormData): Promise<ActionResult> {\n const client = serverClient(await resolveDeviceId());\n const result = await client.createComment({\n featureId: String(formData.get(\"featureId\") ?? \"\"),\n text: String(formData.get(\"text\") ?? \"\"),\n authorName: optional(formData.get(\"authorName\")),\n });\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nexport async function followAction(formData: FormData): Promise<ActionResult> {\n const featureId = String(formData.get(\"featureId\") ?? \"\");\n const email = optional(formData.get(\"email\"));\n const client = serverClient(await resolveDeviceId());\n const result = email ? await client.follow(featureId, email) : await client.unfollow(featureId);\n\n revalidate(formData);\n return result.ok ? { ok: true } : { ok: false, message: userMessage(result.error) };\n}\n\nfunction optional(value: FormDataEntryValue | null): string | undefined {\n const text = typeof value === \"string\" ? value.trim() : \"\";\n return text.length > 0 ? text : undefined;\n}\n\nfunction revalidate(formData: FormData): void {\n const path = formData.get(\"path\");\n if (typeof path === \"string\" && path.startsWith(\"/\")) revalidatePath(path);\n}\n","import \"server-only\";\nimport { createFewchursClient, DEFAULT_BASE_URL, type FewchursClient } from \"@fewchurs/react/core\";\n\nexport const DEFAULT_API_PATH = \"/api/fewchurs\";\n\nexport interface ServerConfig {\n apiKey?: string;\n baseUrl?: string;\n language?: string;\n}\n\nexport function resolveApiKey(apiKey?: string): string | undefined {\n return apiKey ?? process.env.FEWCHURS_API_KEY;\n}\n\nexport function resolveBaseUrl(baseUrl?: string): string {\n return baseUrl ?? process.env.FEWCHURS_BASE_URL ?? DEFAULT_BASE_URL;\n}\n\nexport function resolveApiPath(apiPath?: string): string {\n return (apiPath ?? process.env.FEWCHURS_API_PATH ?? DEFAULT_API_PATH).replace(/\\/$/, \"\");\n}\n\nexport function serverClient(deviceId: string | null, config: ServerConfig = {}): FewchursClient {\n const apiKey = resolveApiKey(config.apiKey);\n if (!apiKey) {\n throw new Error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY in your environment, or pass apiKey to the server component or createHandler().\",\n );\n }\n\n return createFewchursClient({\n apiKey,\n baseUrl: resolveBaseUrl(config.baseUrl),\n deviceId,\n language: config.language,\n });\n}\n","import \"server-only\";\nimport { cookies } from \"next/headers\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE } from \"./constants\";\n\nexport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE };\n\nexport type DeviceIdResolver = () => string | Promise<string>;\n\n/** The visitor's id, or null when nothing has been stored for them yet. */\nexport async function readDeviceId(): Promise<string | null> {\n const store = await cookies();\n return store.get(DEVICE_ID_COOKIE)?.value ?? null;\n}\n\n/**\n * Reads the cookie, falling back to a fresh id. The value is not persisted here: a server\n * component cannot set cookies, so the route handler stores it on the first request instead.\n */\nexport async function resolveDeviceId(custom?: DeviceIdResolver): Promise<string> {\n if (custom) return custom();\n return (await readDeviceId()) ?? createDeviceId();\n}\n\nexport function deviceIdCookie(deviceId: string): {\n name: string;\n value: string;\n options: {\n httpOnly: true;\n sameSite: \"lax\";\n path: string;\n maxAge: number;\n secure: boolean;\n };\n} {\n return {\n name: DEVICE_ID_COOKIE,\n value: deviceId,\n options: {\n httpOnly: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: DEVICE_ID_MAX_AGE,\n secure: process.env.NODE_ENV === \"production\",\n },\n };\n}\n\nexport { createDeviceId };\n","// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.1\";\n","import { SDK_VERSION } from \"./version\";\n\nexport const DEVICE_ID_COOKIE = \"fewchurs_did\";\nexport const DEVICE_ID_MAX_AGE = 60 * 60 * 24 * 365;\nexport { SDK_VERSION };\nexport const SDK_HEADER = `next/${SDK_VERSION}`;\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
DEVICE_ID_COOKIE,
|
|
3
3
|
DEVICE_ID_MAX_AGE,
|
|
4
4
|
SDK_HEADER
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-W6QR6DEB.mjs";
|
|
6
6
|
|
|
7
7
|
// src/handler.ts
|
|
8
8
|
import "server-only";
|
|
@@ -142,4 +142,4 @@ export {
|
|
|
142
142
|
POST,
|
|
143
143
|
DELETE
|
|
144
144
|
};
|
|
145
|
-
//# sourceMappingURL=chunk-
|
|
145
|
+
//# sourceMappingURL=chunk-KPJHLDQU.mjs.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEVICE_ID_COOKIE,
|
|
3
3
|
DEVICE_ID_MAX_AGE
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-W6QR6DEB.mjs";
|
|
5
5
|
|
|
6
6
|
// src/identity.ts
|
|
7
7
|
import "server-only";
|
|
@@ -35,4 +35,4 @@ export {
|
|
|
35
35
|
resolveDeviceId,
|
|
36
36
|
deviceIdCookie
|
|
37
37
|
};
|
|
38
|
-
//# sourceMappingURL=chunk-
|
|
38
|
+
//# sourceMappingURL=chunk-QULKPZB4.mjs.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/version.ts
|
|
2
|
-
var SDK_VERSION = "1.0.
|
|
2
|
+
var SDK_VERSION = "1.0.1";
|
|
3
3
|
|
|
4
4
|
// src/constants.ts
|
|
5
5
|
var DEVICE_ID_COOKIE = "fewchurs_did";
|
|
@@ -11,4 +11,4 @@ export {
|
|
|
11
11
|
DEVICE_ID_MAX_AGE,
|
|
12
12
|
SDK_HEADER
|
|
13
13
|
};
|
|
14
|
-
//# sourceMappingURL=chunk-
|
|
14
|
+
//# sourceMappingURL=chunk-W6QR6DEB.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts","../src/constants.ts"],"sourcesContent":["// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/constants.ts"],"sourcesContent":["// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.1\";\n","import { SDK_VERSION } from \"./version\";\n\nexport const DEVICE_ID_COOKIE = \"fewchurs_did\";\nexport const DEVICE_ID_MAX_AGE = 60 * 60 * 24 * 365;\nexport { SDK_VERSION };\nexport const SDK_HEADER = `next/${SDK_VERSION}`;\n"],"mappings":";AACO,IAAM,cAAc;;;ACCpB,IAAM,mBAAmB;AACzB,IAAM,oBAAoB,KAAK,KAAK,KAAK;AAEzC,IAAM,aAAa,QAAQ,WAAW;","names":[]}
|
package/dist/handler.js
CHANGED
package/dist/handler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/handler.ts","../src/config.ts","../src/version.ts","../src/constants.ts"],"sourcesContent":["import \"server-only\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { resolveApiKey, resolveApiPath, resolveBaseUrl } from \"./config\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE, SDK_HEADER } from \"./constants\";\nimport type { DeviceIdResolver } from \"./identity\";\n\nexport interface HandlerOptions {\n apiKey?: string;\n baseUrl?: string;\n /** Where the handler is mounted, when it is not `/api/fewchurs`. */\n apiPath?: string;\n /** Return a stable, hashed id for the signed-in visitor so votes follow the account. */\n deviceId?: DeviceIdResolver;\n}\n\ntype RouteParams = { route?: string[] };\n\n/** What Next passes: a Promise in 15, a plain object in 14. Both are awaited the same way. */\ntype IncomingContext = { params?: RouteParams | Promise<RouteParams> };\n\n// Next 15's route check wants a required second argument whose `params` is a Promise. The\n// handler itself takes `IncomingContext`, so Next 14's plain object still works at runtime.\ntype RouteContext = { params: Promise<RouteParams> };\n\nexport type RouteHandler = (request: Request, context: RouteContext) => Promise<Response>;\n\n/** Only the endpoints the board needs: everything else is a 404, not a proxy to the API. */\nconst ALLOWED: Array<{ method: string; pattern: RegExp }> = [\n { method: \"GET\", pattern: /^config$/ },\n { method: \"GET\", pattern: /^features$/ },\n { method: \"POST\", pattern: /^features$/ },\n { method: \"GET\", pattern: /^features\\/[^/]+$/ },\n { method: \"POST\", pattern: /^features\\/[^/]+\\/vote$/ },\n { method: \"DELETE\", pattern: /^features\\/[^/]+\\/vote$/ },\n { method: \"POST\", pattern: /^features\\/[^/]+\\/follow$/ },\n { method: \"DELETE\", pattern: /^features\\/[^/]+\\/follow$/ },\n { method: \"GET\", pattern: /^features\\/[^/]+\\/comments$/ },\n { method: \"POST\", pattern: /^comments$/ },\n];\n\nconst DEVICE_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/;\n\nexport function createHandler(options: HandlerOptions = {}): {\n GET: RouteHandler;\n POST: RouteHandler;\n DELETE: RouteHandler;\n} {\n async function handle(request: Request, context?: IncomingContext): Promise<Response> {\n const path = await routePath(request, context, resolveApiPath(options.apiPath));\n\n if (!ALLOWED.some((route) => route.method === request.method && route.pattern.test(path))) {\n return json({ error: { code: \"not_found\", message: \"Unknown Fewchurs route.\" } }, 404);\n }\n\n const apiKey = resolveApiKey(options.apiKey);\n if (!apiKey) {\n console.error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY, or pass apiKey to createHandler().\",\n );\n return json({ error: { code: \"internal\", message: \"Fewchurs is not configured.\" } }, 500);\n }\n\n const cookieValue = readCookie(request, DEVICE_ID_COOKIE);\n const custom = options.deviceId ? await options.deviceId() : null;\n const deviceId = custom ?? cookieValue ?? claimedDeviceId(request) ?? createDeviceId();\n\n const target = `${resolveBaseUrl(options.baseUrl)}/${path}${new URL(request.url).search}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${apiKey}`,\n \"X-Device-Id\": deviceId,\n \"X-Fewchurs-Sdk\": request.headers.get(\"x-fewchurs-sdk\") ?? SDK_HEADER,\n Accept: \"application/json\",\n };\n const language = request.headers.get(\"accept-language\");\n if (language) headers[\"Accept-Language\"] = language;\n\n const body = request.method === \"GET\" ? undefined : await request.text();\n if (body) headers[\"Content-Type\"] = \"application/json\";\n\n let upstream: Response;\n try {\n upstream = await fetch(target, { method: request.method, headers, body, cache: \"no-store\" });\n } catch {\n return json({ error: { code: \"internal\", message: \"Could not reach Fewchurs.\" } }, 502);\n }\n\n const response = new Response(await upstream.text(), {\n status: upstream.status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" },\n });\n\n // A visitor keeps the same id across requests without the browser ever seeing the key,\n // and an httpOnly cookie means page scripts cannot read or change it either.\n if (!custom && deviceId !== cookieValue) {\n response.headers.append(\"Set-Cookie\", serializeCookie(deviceId));\n }\n\n return response;\n }\n\n return { GET: handle, POST: handle, DELETE: handle };\n}\n\nconst defaultHandler = createHandler();\n\nexport const GET = defaultHandler.GET;\nexport const POST = defaultHandler.POST;\nexport const DELETE = defaultHandler.DELETE;\n\nasync function routePath(\n request: Request,\n context: IncomingContext | undefined,\n apiPath: string,\n): Promise<string> {\n const params = await context?.params;\n if (params?.route?.length) return params.route.map(encodeURIComponent).join(\"/\");\n\n const { pathname } = new URL(request.url);\n const index = pathname.indexOf(apiPath);\n return index === -1 ? \"\" : pathname.slice(index + apiPath.length).replace(/^\\//, \"\");\n}\n\n/**\n * The first request from a server-rendered board carries the id the page was rendered with,\n * so adopting it keeps the server's `hasVoted` answers and the browser's in agreement.\n */\nfunction claimedDeviceId(request: Request): string | null {\n const claimed = request.headers.get(\"x-device-id\");\n return claimed && DEVICE_ID_PATTERN.test(claimed) ? claimed : null;\n}\n\nfunction readCookie(request: Request, name: string): string | null {\n const header = request.headers.get(\"cookie\");\n if (!header) return null;\n const match = header.match(new RegExp(`(?:^|;\\\\s*)${name}=([^;]*)`));\n return match?.[1] ? decodeURIComponent(match[1]) : null;\n}\n\nfunction serializeCookie(deviceId: string): string {\n const parts = [\n `${DEVICE_ID_COOKIE}=${encodeURIComponent(deviceId)}`,\n \"Path=/\",\n `Max-Age=${DEVICE_ID_MAX_AGE}`,\n \"HttpOnly\",\n \"SameSite=Lax\",\n ];\n if (process.env.NODE_ENV === \"production\") parts.push(\"Secure\");\n return parts.join(\"; \");\n}\n\nfunction json(body: unknown, status: number): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" },\n });\n}\n","import \"server-only\";\nimport { createFewchursClient, DEFAULT_BASE_URL, type FewchursClient } from \"@fewchurs/react/core\";\n\nexport const DEFAULT_API_PATH = \"/api/fewchurs\";\n\nexport interface ServerConfig {\n apiKey?: string;\n baseUrl?: string;\n language?: string;\n}\n\nexport function resolveApiKey(apiKey?: string): string | undefined {\n return apiKey ?? process.env.FEWCHURS_API_KEY;\n}\n\nexport function resolveBaseUrl(baseUrl?: string): string {\n return baseUrl ?? process.env.FEWCHURS_BASE_URL ?? DEFAULT_BASE_URL;\n}\n\nexport function resolveApiPath(apiPath?: string): string {\n return (apiPath ?? process.env.FEWCHURS_API_PATH ?? DEFAULT_API_PATH).replace(/\\/$/, \"\");\n}\n\nexport function serverClient(deviceId: string | null, config: ServerConfig = {}): FewchursClient {\n const apiKey = resolveApiKey(config.apiKey);\n if (!apiKey) {\n throw new Error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY in your environment, or pass apiKey to the server component or createHandler().\",\n );\n }\n\n return createFewchursClient({\n apiKey,\n baseUrl: resolveBaseUrl(config.baseUrl),\n deviceId,\n language: config.language,\n });\n}\n","// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.0\";\n","import { SDK_VERSION } from \"./version\";\n\nexport const DEVICE_ID_COOKIE = \"fewchurs_did\";\nexport const DEVICE_ID_MAX_AGE = 60 * 60 * 24 * 365;\nexport { SDK_VERSION };\nexport const SDK_HEADER = `next/${SDK_VERSION}`;\n"],"mappings":"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","names":["import_server_only","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/handler.ts","../src/config.ts","../src/version.ts","../src/constants.ts"],"sourcesContent":["import \"server-only\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { resolveApiKey, resolveApiPath, resolveBaseUrl } from \"./config\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE, SDK_HEADER } from \"./constants\";\nimport type { DeviceIdResolver } from \"./identity\";\n\nexport interface HandlerOptions {\n apiKey?: string;\n baseUrl?: string;\n /** Where the handler is mounted, when it is not `/api/fewchurs`. */\n apiPath?: string;\n /** Return a stable, hashed id for the signed-in visitor so votes follow the account. */\n deviceId?: DeviceIdResolver;\n}\n\ntype RouteParams = { route?: string[] };\n\n/** What Next passes: a Promise in 15, a plain object in 14. Both are awaited the same way. */\ntype IncomingContext = { params?: RouteParams | Promise<RouteParams> };\n\n// Next 15's route check wants a required second argument whose `params` is a Promise. The\n// handler itself takes `IncomingContext`, so Next 14's plain object still works at runtime.\ntype RouteContext = { params: Promise<RouteParams> };\n\nexport type RouteHandler = (request: Request, context: RouteContext) => Promise<Response>;\n\n/** Only the endpoints the board needs: everything else is a 404, not a proxy to the API. */\nconst ALLOWED: Array<{ method: string; pattern: RegExp }> = [\n { method: \"GET\", pattern: /^config$/ },\n { method: \"GET\", pattern: /^features$/ },\n { method: \"POST\", pattern: /^features$/ },\n { method: \"GET\", pattern: /^features\\/[^/]+$/ },\n { method: \"POST\", pattern: /^features\\/[^/]+\\/vote$/ },\n { method: \"DELETE\", pattern: /^features\\/[^/]+\\/vote$/ },\n { method: \"POST\", pattern: /^features\\/[^/]+\\/follow$/ },\n { method: \"DELETE\", pattern: /^features\\/[^/]+\\/follow$/ },\n { method: \"GET\", pattern: /^features\\/[^/]+\\/comments$/ },\n { method: \"POST\", pattern: /^comments$/ },\n];\n\nconst DEVICE_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/;\n\nexport function createHandler(options: HandlerOptions = {}): {\n GET: RouteHandler;\n POST: RouteHandler;\n DELETE: RouteHandler;\n} {\n async function handle(request: Request, context?: IncomingContext): Promise<Response> {\n const path = await routePath(request, context, resolveApiPath(options.apiPath));\n\n if (!ALLOWED.some((route) => route.method === request.method && route.pattern.test(path))) {\n return json({ error: { code: \"not_found\", message: \"Unknown Fewchurs route.\" } }, 404);\n }\n\n const apiKey = resolveApiKey(options.apiKey);\n if (!apiKey) {\n console.error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY, or pass apiKey to createHandler().\",\n );\n return json({ error: { code: \"internal\", message: \"Fewchurs is not configured.\" } }, 500);\n }\n\n const cookieValue = readCookie(request, DEVICE_ID_COOKIE);\n const custom = options.deviceId ? await options.deviceId() : null;\n const deviceId = custom ?? cookieValue ?? claimedDeviceId(request) ?? createDeviceId();\n\n const target = `${resolveBaseUrl(options.baseUrl)}/${path}${new URL(request.url).search}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${apiKey}`,\n \"X-Device-Id\": deviceId,\n \"X-Fewchurs-Sdk\": request.headers.get(\"x-fewchurs-sdk\") ?? SDK_HEADER,\n Accept: \"application/json\",\n };\n const language = request.headers.get(\"accept-language\");\n if (language) headers[\"Accept-Language\"] = language;\n\n const body = request.method === \"GET\" ? undefined : await request.text();\n if (body) headers[\"Content-Type\"] = \"application/json\";\n\n let upstream: Response;\n try {\n upstream = await fetch(target, { method: request.method, headers, body, cache: \"no-store\" });\n } catch {\n return json({ error: { code: \"internal\", message: \"Could not reach Fewchurs.\" } }, 502);\n }\n\n const response = new Response(await upstream.text(), {\n status: upstream.status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" },\n });\n\n // A visitor keeps the same id across requests without the browser ever seeing the key,\n // and an httpOnly cookie means page scripts cannot read or change it either.\n if (!custom && deviceId !== cookieValue) {\n response.headers.append(\"Set-Cookie\", serializeCookie(deviceId));\n }\n\n return response;\n }\n\n return { GET: handle, POST: handle, DELETE: handle };\n}\n\nconst defaultHandler = createHandler();\n\nexport const GET = defaultHandler.GET;\nexport const POST = defaultHandler.POST;\nexport const DELETE = defaultHandler.DELETE;\n\nasync function routePath(\n request: Request,\n context: IncomingContext | undefined,\n apiPath: string,\n): Promise<string> {\n const params = await context?.params;\n if (params?.route?.length) return params.route.map(encodeURIComponent).join(\"/\");\n\n const { pathname } = new URL(request.url);\n const index = pathname.indexOf(apiPath);\n return index === -1 ? \"\" : pathname.slice(index + apiPath.length).replace(/^\\//, \"\");\n}\n\n/**\n * The first request from a server-rendered board carries the id the page was rendered with,\n * so adopting it keeps the server's `hasVoted` answers and the browser's in agreement.\n */\nfunction claimedDeviceId(request: Request): string | null {\n const claimed = request.headers.get(\"x-device-id\");\n return claimed && DEVICE_ID_PATTERN.test(claimed) ? claimed : null;\n}\n\nfunction readCookie(request: Request, name: string): string | null {\n const header = request.headers.get(\"cookie\");\n if (!header) return null;\n const match = header.match(new RegExp(`(?:^|;\\\\s*)${name}=([^;]*)`));\n return match?.[1] ? decodeURIComponent(match[1]) : null;\n}\n\nfunction serializeCookie(deviceId: string): string {\n const parts = [\n `${DEVICE_ID_COOKIE}=${encodeURIComponent(deviceId)}`,\n \"Path=/\",\n `Max-Age=${DEVICE_ID_MAX_AGE}`,\n \"HttpOnly\",\n \"SameSite=Lax\",\n ];\n if (process.env.NODE_ENV === \"production\") parts.push(\"Secure\");\n return parts.join(\"; \");\n}\n\nfunction json(body: unknown, status: number): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" },\n });\n}\n","import \"server-only\";\nimport { createFewchursClient, DEFAULT_BASE_URL, type FewchursClient } from \"@fewchurs/react/core\";\n\nexport const DEFAULT_API_PATH = \"/api/fewchurs\";\n\nexport interface ServerConfig {\n apiKey?: string;\n baseUrl?: string;\n language?: string;\n}\n\nexport function resolveApiKey(apiKey?: string): string | undefined {\n return apiKey ?? process.env.FEWCHURS_API_KEY;\n}\n\nexport function resolveBaseUrl(baseUrl?: string): string {\n return baseUrl ?? process.env.FEWCHURS_BASE_URL ?? DEFAULT_BASE_URL;\n}\n\nexport function resolveApiPath(apiPath?: string): string {\n return (apiPath ?? process.env.FEWCHURS_API_PATH ?? DEFAULT_API_PATH).replace(/\\/$/, \"\");\n}\n\nexport function serverClient(deviceId: string | null, config: ServerConfig = {}): FewchursClient {\n const apiKey = resolveApiKey(config.apiKey);\n if (!apiKey) {\n throw new Error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY in your environment, or pass apiKey to the server component or createHandler().\",\n );\n }\n\n return createFewchursClient({\n apiKey,\n baseUrl: resolveBaseUrl(config.baseUrl),\n deviceId,\n language: config.language,\n });\n}\n","// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.1\";\n","import { SDK_VERSION } from \"./version\";\n\nexport const DEVICE_ID_COOKIE = \"fewchurs_did\";\nexport const DEVICE_ID_MAX_AGE = 60 * 60 * 24 * 365;\nexport { SDK_VERSION };\nexport const SDK_HEADER = `next/${SDK_VERSION}`;\n"],"mappings":"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","names":["import_server_only","import_core"]}
|
package/dist/handler.mjs
CHANGED
package/dist/identity.js
CHANGED
package/dist/identity.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/identity.ts","../src/version.ts","../src/constants.ts"],"sourcesContent":["import \"server-only\";\nimport { cookies } from \"next/headers\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE } from \"./constants\";\n\nexport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE };\n\nexport type DeviceIdResolver = () => string | Promise<string>;\n\n/** The visitor's id, or null when nothing has been stored for them yet. */\nexport async function readDeviceId(): Promise<string | null> {\n const store = await cookies();\n return store.get(DEVICE_ID_COOKIE)?.value ?? null;\n}\n\n/**\n * Reads the cookie, falling back to a fresh id. The value is not persisted here: a server\n * component cannot set cookies, so the route handler stores it on the first request instead.\n */\nexport async function resolveDeviceId(custom?: DeviceIdResolver): Promise<string> {\n if (custom) return custom();\n return (await readDeviceId()) ?? createDeviceId();\n}\n\nexport function deviceIdCookie(deviceId: string): {\n name: string;\n value: string;\n options: {\n httpOnly: true;\n sameSite: \"lax\";\n path: string;\n maxAge: number;\n secure: boolean;\n };\n} {\n return {\n name: DEVICE_ID_COOKIE,\n value: deviceId,\n options: {\n httpOnly: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: DEVICE_ID_MAX_AGE,\n secure: process.env.NODE_ENV === \"production\",\n },\n };\n}\n\nexport { createDeviceId };\n","// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.
|
|
1
|
+
{"version":3,"sources":["../src/identity.ts","../src/version.ts","../src/constants.ts"],"sourcesContent":["import \"server-only\";\nimport { cookies } from \"next/headers\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE } from \"./constants\";\n\nexport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE };\n\nexport type DeviceIdResolver = () => string | Promise<string>;\n\n/** The visitor's id, or null when nothing has been stored for them yet. */\nexport async function readDeviceId(): Promise<string | null> {\n const store = await cookies();\n return store.get(DEVICE_ID_COOKIE)?.value ?? null;\n}\n\n/**\n * Reads the cookie, falling back to a fresh id. The value is not persisted here: a server\n * component cannot set cookies, so the route handler stores it on the first request instead.\n */\nexport async function resolveDeviceId(custom?: DeviceIdResolver): Promise<string> {\n if (custom) return custom();\n return (await readDeviceId()) ?? createDeviceId();\n}\n\nexport function deviceIdCookie(deviceId: string): {\n name: string;\n value: string;\n options: {\n httpOnly: true;\n sameSite: \"lax\";\n path: string;\n maxAge: number;\n secure: boolean;\n };\n} {\n return {\n name: DEVICE_ID_COOKIE,\n value: deviceId,\n options: {\n httpOnly: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: DEVICE_ID_MAX_AGE,\n secure: process.env.NODE_ENV === \"production\",\n },\n };\n}\n\nexport { createDeviceId };\n","// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.1\";\n","import { SDK_VERSION } from \"./version\";\n\nexport const DEVICE_ID_COOKIE = \"fewchurs_did\";\nexport const DEVICE_ID_MAX_AGE = 60 * 60 * 24 * 365;\nexport { SDK_VERSION };\nexport const SDK_HEADER = `next/${SDK_VERSION}`;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAO;AACP,qBAAwB;AACxB,kBAA+B;;;ACDxB,IAAM,cAAc;;;ACCpB,IAAM,mBAAmB;AACzB,IAAM,oBAAoB,KAAK,KAAK,KAAK;AAEzC,IAAM,aAAa,QAAQ,WAAW;;;AFK7C,eAAsB,eAAuC;AAC3D,QAAM,QAAQ,UAAM,wBAAQ;AAC5B,SAAO,MAAM,IAAI,gBAAgB,GAAG,SAAS;AAC/C;AAMA,eAAsB,gBAAgB,QAA4C;AAChF,MAAI,OAAQ,QAAO,OAAO;AAC1B,SAAQ,MAAM,aAAa,SAAM,4BAAe;AAClD;AAEO,SAAS,eAAe,UAU7B;AACA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,MACP,UAAU;AAAA,MACV,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ,QAAQ,IAAI,aAAa;AAAA,IACnC;AAAA,EACF;AACF;","names":[]}
|
package/dist/identity.mjs
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
deviceIdCookie,
|
|
4
4
|
readDeviceId,
|
|
5
5
|
resolveDeviceId
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QULKPZB4.mjs";
|
|
7
7
|
import {
|
|
8
8
|
DEVICE_ID_COOKIE,
|
|
9
9
|
DEVICE_ID_MAX_AGE
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-W6QR6DEB.mjs";
|
|
11
11
|
export {
|
|
12
12
|
DEVICE_ID_COOKIE,
|
|
13
13
|
DEVICE_ID_MAX_AGE,
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/board.tsx","../src/config.ts","../src/identity.ts","../src/version.ts","../src/constants.ts","../src/handler.ts"],"sourcesContent":["export { FewchursBoard } from \"./board\";\nexport type { FewchursBoardProps } from \"./board\";\nexport { createHandler } from \"./handler\";\nexport type { HandlerOptions, RouteHandler } from \"./handler\";\nexport { DEVICE_ID_COOKIE, readDeviceId, resolveDeviceId } from \"./identity\";\nexport type { DeviceIdResolver } from \"./identity\";\nexport { DEFAULT_API_PATH } from \"./config\";\nexport type { ServerConfig } from \"./config\";\n\nexport type {\n Comment,\n CommentPage,\n Feature,\n FeaturePage,\n FeatureSort,\n FeatureStatus,\n FewchursConfig,\n FewchursError,\n} from \"@fewchurs/react/core\";\n","import \"server-only\";\nimport { FewchursBoard as ClientBoard, FewchursProvider } from \"@fewchurs/react\";\nimport type { FewchursBoardProps as ClientBoardProps } from \"@fewchurs/react\";\nimport {\n featureKey,\n listKey,\n ROADMAP_STATUSES,\n type FeatureSort,\n type StoreState,\n} from \"@fewchurs/react/core\";\nimport { resolveApiPath, serverClient } from \"./config\";\nimport { resolveDeviceId, type DeviceIdResolver } from \"./identity\";\n\n/** Function props cannot cross the server/client boundary — compose in your own client file. */\ntype SerializableBoardProps = Omit<\n ClientBoardProps,\n \"renderHeader\" | \"renderEmpty\" | \"renderFeature\" | \"children\"\n>;\n\nexport interface FewchursBoardProps extends SerializableBoardProps {\n /** Defaults to `process.env.FEWCHURS_API_KEY`. Never reaches the browser. */\n apiKey?: string;\n baseUrl?: string;\n /** Where your route handler is mounted. Defaults to `/api/fewchurs`. */\n apiPath?: string;\n language?: string;\n deviceId?: DeviceIdResolver;\n}\n\nexport async function FewchursBoard({\n apiKey,\n baseUrl,\n apiPath,\n language,\n deviceId: deviceIdOption,\n ...board\n}: FewchursBoardProps): Promise<React.JSX.Element> {\n const deviceId = await resolveDeviceId(deviceIdOption);\n const client = serverClient(deviceId, { apiKey, baseUrl, language });\n\n const tab = board.defaultTab ?? board.tabs?.[0] ?? \"top\";\n const sort: FeatureSort = tab === \"roadmap\" ? \"top\" : tab;\n const status = tab === \"roadmap\" ? ROADMAP_STATUSES : undefined;\n const pageSize = board.pageSize ?? 20;\n\n const [config, page, pinned] = await Promise.all([\n client.getConfig(),\n client.listFeatures({ sort, status, limit: pageSize }),\n board.initialFeatureId ? client.getFeature(board.initialFeatureId) : null,\n ]);\n\n // Seeded under the same key the client hook will ask for, so the first paint is the\n // rendered HTML and no second request goes out just to fetch what is already there.\n const lists: StoreState[\"lists\"] = {};\n if (page.ok) {\n lists[listKey({ sort, status })] = {\n features: page.data.features,\n cursor: page.data.nextCursor,\n status: \"ready\",\n error: null,\n };\n }\n // A deep-linked request is rendered on the server too, so the link lands on real content.\n if (board.initialFeatureId && pinned?.ok) {\n lists[featureKey(board.initialFeatureId)] = {\n features: [pinned.data],\n cursor: null,\n status: \"ready\",\n error: null,\n };\n }\n\n const initialState: Partial<StoreState> = {\n config: config.ok ? config.data : null,\n lists,\n };\n\n return (\n <FewchursProvider\n baseUrl={resolveApiPath(apiPath)}\n deviceId={deviceId}\n pageSize={pageSize}\n initialState={initialState}\n >\n <ClientBoard {...board} />\n </FewchursProvider>\n );\n}\n","import \"server-only\";\nimport { createFewchursClient, DEFAULT_BASE_URL, type FewchursClient } from \"@fewchurs/react/core\";\n\nexport const DEFAULT_API_PATH = \"/api/fewchurs\";\n\nexport interface ServerConfig {\n apiKey?: string;\n baseUrl?: string;\n language?: string;\n}\n\nexport function resolveApiKey(apiKey?: string): string | undefined {\n return apiKey ?? process.env.FEWCHURS_API_KEY;\n}\n\nexport function resolveBaseUrl(baseUrl?: string): string {\n return baseUrl ?? process.env.FEWCHURS_BASE_URL ?? DEFAULT_BASE_URL;\n}\n\nexport function resolveApiPath(apiPath?: string): string {\n return (apiPath ?? process.env.FEWCHURS_API_PATH ?? DEFAULT_API_PATH).replace(/\\/$/, \"\");\n}\n\nexport function serverClient(deviceId: string | null, config: ServerConfig = {}): FewchursClient {\n const apiKey = resolveApiKey(config.apiKey);\n if (!apiKey) {\n throw new Error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY in your environment, or pass apiKey to the server component or createHandler().\",\n );\n }\n\n return createFewchursClient({\n apiKey,\n baseUrl: resolveBaseUrl(config.baseUrl),\n deviceId,\n language: config.language,\n });\n}\n","import \"server-only\";\nimport { cookies } from \"next/headers\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE } from \"./constants\";\n\nexport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE };\n\nexport type DeviceIdResolver = () => string | Promise<string>;\n\n/** The visitor's id, or null when nothing has been stored for them yet. */\nexport async function readDeviceId(): Promise<string | null> {\n const store = await cookies();\n return store.get(DEVICE_ID_COOKIE)?.value ?? null;\n}\n\n/**\n * Reads the cookie, falling back to a fresh id. The value is not persisted here: a server\n * component cannot set cookies, so the route handler stores it on the first request instead.\n */\nexport async function resolveDeviceId(custom?: DeviceIdResolver): Promise<string> {\n if (custom) return custom();\n return (await readDeviceId()) ?? createDeviceId();\n}\n\nexport function deviceIdCookie(deviceId: string): {\n name: string;\n value: string;\n options: {\n httpOnly: true;\n sameSite: \"lax\";\n path: string;\n maxAge: number;\n secure: boolean;\n };\n} {\n return {\n name: DEVICE_ID_COOKIE,\n value: deviceId,\n options: {\n httpOnly: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: DEVICE_ID_MAX_AGE,\n secure: process.env.NODE_ENV === \"production\",\n },\n };\n}\n\nexport { createDeviceId };\n","// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.0\";\n","import { SDK_VERSION } from \"./version\";\n\nexport const DEVICE_ID_COOKIE = \"fewchurs_did\";\nexport const DEVICE_ID_MAX_AGE = 60 * 60 * 24 * 365;\nexport { SDK_VERSION };\nexport const SDK_HEADER = `next/${SDK_VERSION}`;\n","import \"server-only\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { resolveApiKey, resolveApiPath, resolveBaseUrl } from \"./config\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE, SDK_HEADER } from \"./constants\";\nimport type { DeviceIdResolver } from \"./identity\";\n\nexport interface HandlerOptions {\n apiKey?: string;\n baseUrl?: string;\n /** Where the handler is mounted, when it is not `/api/fewchurs`. */\n apiPath?: string;\n /** Return a stable, hashed id for the signed-in visitor so votes follow the account. */\n deviceId?: DeviceIdResolver;\n}\n\ntype RouteParams = { route?: string[] };\n\n/** What Next passes: a Promise in 15, a plain object in 14. Both are awaited the same way. */\ntype IncomingContext = { params?: RouteParams | Promise<RouteParams> };\n\n// Next 15's route check wants a required second argument whose `params` is a Promise. The\n// handler itself takes `IncomingContext`, so Next 14's plain object still works at runtime.\ntype RouteContext = { params: Promise<RouteParams> };\n\nexport type RouteHandler = (request: Request, context: RouteContext) => Promise<Response>;\n\n/** Only the endpoints the board needs: everything else is a 404, not a proxy to the API. */\nconst ALLOWED: Array<{ method: string; pattern: RegExp }> = [\n { method: \"GET\", pattern: /^config$/ },\n { method: \"GET\", pattern: /^features$/ },\n { method: \"POST\", pattern: /^features$/ },\n { method: \"GET\", pattern: /^features\\/[^/]+$/ },\n { method: \"POST\", pattern: /^features\\/[^/]+\\/vote$/ },\n { method: \"DELETE\", pattern: /^features\\/[^/]+\\/vote$/ },\n { method: \"POST\", pattern: /^features\\/[^/]+\\/follow$/ },\n { method: \"DELETE\", pattern: /^features\\/[^/]+\\/follow$/ },\n { method: \"GET\", pattern: /^features\\/[^/]+\\/comments$/ },\n { method: \"POST\", pattern: /^comments$/ },\n];\n\nconst DEVICE_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/;\n\nexport function createHandler(options: HandlerOptions = {}): {\n GET: RouteHandler;\n POST: RouteHandler;\n DELETE: RouteHandler;\n} {\n async function handle(request: Request, context?: IncomingContext): Promise<Response> {\n const path = await routePath(request, context, resolveApiPath(options.apiPath));\n\n if (!ALLOWED.some((route) => route.method === request.method && route.pattern.test(path))) {\n return json({ error: { code: \"not_found\", message: \"Unknown Fewchurs route.\" } }, 404);\n }\n\n const apiKey = resolveApiKey(options.apiKey);\n if (!apiKey) {\n console.error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY, or pass apiKey to createHandler().\",\n );\n return json({ error: { code: \"internal\", message: \"Fewchurs is not configured.\" } }, 500);\n }\n\n const cookieValue = readCookie(request, DEVICE_ID_COOKIE);\n const custom = options.deviceId ? await options.deviceId() : null;\n const deviceId = custom ?? cookieValue ?? claimedDeviceId(request) ?? createDeviceId();\n\n const target = `${resolveBaseUrl(options.baseUrl)}/${path}${new URL(request.url).search}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${apiKey}`,\n \"X-Device-Id\": deviceId,\n \"X-Fewchurs-Sdk\": request.headers.get(\"x-fewchurs-sdk\") ?? SDK_HEADER,\n Accept: \"application/json\",\n };\n const language = request.headers.get(\"accept-language\");\n if (language) headers[\"Accept-Language\"] = language;\n\n const body = request.method === \"GET\" ? undefined : await request.text();\n if (body) headers[\"Content-Type\"] = \"application/json\";\n\n let upstream: Response;\n try {\n upstream = await fetch(target, { method: request.method, headers, body, cache: \"no-store\" });\n } catch {\n return json({ error: { code: \"internal\", message: \"Could not reach Fewchurs.\" } }, 502);\n }\n\n const response = new Response(await upstream.text(), {\n status: upstream.status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" },\n });\n\n // A visitor keeps the same id across requests without the browser ever seeing the key,\n // and an httpOnly cookie means page scripts cannot read or change it either.\n if (!custom && deviceId !== cookieValue) {\n response.headers.append(\"Set-Cookie\", serializeCookie(deviceId));\n }\n\n return response;\n }\n\n return { GET: handle, POST: handle, DELETE: handle };\n}\n\nconst defaultHandler = createHandler();\n\nexport const GET = defaultHandler.GET;\nexport const POST = defaultHandler.POST;\nexport const DELETE = defaultHandler.DELETE;\n\nasync function routePath(\n request: Request,\n context: IncomingContext | undefined,\n apiPath: string,\n): Promise<string> {\n const params = await context?.params;\n if (params?.route?.length) return params.route.map(encodeURIComponent).join(\"/\");\n\n const { pathname } = new URL(request.url);\n const index = pathname.indexOf(apiPath);\n return index === -1 ? \"\" : pathname.slice(index + apiPath.length).replace(/^\\//, \"\");\n}\n\n/**\n * The first request from a server-rendered board carries the id the page was rendered with,\n * so adopting it keeps the server's `hasVoted` answers and the browser's in agreement.\n */\nfunction claimedDeviceId(request: Request): string | null {\n const claimed = request.headers.get(\"x-device-id\");\n return claimed && DEVICE_ID_PATTERN.test(claimed) ? claimed : null;\n}\n\nfunction readCookie(request: Request, name: string): string | null {\n const header = request.headers.get(\"cookie\");\n if (!header) return null;\n const match = header.match(new RegExp(`(?:^|;\\\\s*)${name}=([^;]*)`));\n return match?.[1] ? decodeURIComponent(match[1]) : null;\n}\n\nfunction serializeCookie(deviceId: string): string {\n const parts = [\n `${DEVICE_ID_COOKIE}=${encodeURIComponent(deviceId)}`,\n \"Path=/\",\n `Max-Age=${DEVICE_ID_MAX_AGE}`,\n \"HttpOnly\",\n \"SameSite=Lax\",\n ];\n if (process.env.NODE_ENV === \"production\") parts.push(\"Secure\");\n return parts.join(\"; \");\n}\n\nfunction json(body: unknown, status: number): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" },\n });\n}\n"],"mappings":"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","names":["import_server_only","import_core","import_server_only","import_core","ClientBoard","import_server_only","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/board.tsx","../src/config.ts","../src/identity.ts","../src/version.ts","../src/constants.ts","../src/handler.ts"],"sourcesContent":["export { FewchursBoard } from \"./board\";\nexport type { FewchursBoardProps } from \"./board\";\nexport { createHandler } from \"./handler\";\nexport type { HandlerOptions, RouteHandler } from \"./handler\";\nexport { DEVICE_ID_COOKIE, readDeviceId, resolveDeviceId } from \"./identity\";\nexport type { DeviceIdResolver } from \"./identity\";\nexport { DEFAULT_API_PATH } from \"./config\";\nexport type { ServerConfig } from \"./config\";\n\nexport type {\n Comment,\n CommentPage,\n Feature,\n FeaturePage,\n FeatureSort,\n FeatureStatus,\n FewchursConfig,\n FewchursError,\n} from \"@fewchurs/react/core\";\n","import \"server-only\";\nimport { FewchursBoard as ClientBoard, FewchursProvider } from \"@fewchurs/react\";\nimport type { FewchursBoardProps as ClientBoardProps } from \"@fewchurs/react\";\nimport {\n featureKey,\n listKey,\n ROADMAP_STATUSES,\n type FeatureSort,\n type StoreState,\n} from \"@fewchurs/react/core\";\nimport { resolveApiPath, serverClient } from \"./config\";\nimport { resolveDeviceId, type DeviceIdResolver } from \"./identity\";\n\n/** Function props cannot cross the server/client boundary — compose in your own client file. */\ntype SerializableBoardProps = Omit<\n ClientBoardProps,\n \"renderHeader\" | \"renderEmpty\" | \"renderFeature\" | \"children\"\n>;\n\nexport interface FewchursBoardProps extends SerializableBoardProps {\n /** Defaults to `process.env.FEWCHURS_API_KEY`. Never reaches the browser. */\n apiKey?: string;\n baseUrl?: string;\n /** Where your route handler is mounted. Defaults to `/api/fewchurs`. */\n apiPath?: string;\n language?: string;\n deviceId?: DeviceIdResolver;\n}\n\nexport async function FewchursBoard({\n apiKey,\n baseUrl,\n apiPath,\n language,\n deviceId: deviceIdOption,\n ...board\n}: FewchursBoardProps): Promise<React.JSX.Element> {\n const deviceId = await resolveDeviceId(deviceIdOption);\n const client = serverClient(deviceId, { apiKey, baseUrl, language });\n\n const tab = board.defaultTab ?? board.tabs?.[0] ?? \"top\";\n const sort: FeatureSort = tab === \"roadmap\" ? \"top\" : tab;\n const status = tab === \"roadmap\" ? ROADMAP_STATUSES : undefined;\n const pageSize = board.pageSize ?? 20;\n\n const [config, page, pinned] = await Promise.all([\n client.getConfig(),\n client.listFeatures({ sort, status, limit: pageSize }),\n board.initialFeatureId ? client.getFeature(board.initialFeatureId) : null,\n ]);\n\n // Seeded under the same key the client hook will ask for, so the first paint is the\n // rendered HTML and no second request goes out just to fetch what is already there.\n const lists: StoreState[\"lists\"] = {};\n if (page.ok) {\n lists[listKey({ sort, status })] = {\n features: page.data.features,\n cursor: page.data.nextCursor,\n status: \"ready\",\n error: null,\n };\n }\n // A deep-linked request is rendered on the server too, so the link lands on real content.\n if (board.initialFeatureId && pinned?.ok) {\n lists[featureKey(board.initialFeatureId)] = {\n features: [pinned.data],\n cursor: null,\n status: \"ready\",\n error: null,\n };\n }\n\n const initialState: Partial<StoreState> = {\n config: config.ok ? config.data : null,\n lists,\n };\n\n return (\n <FewchursProvider\n baseUrl={resolveApiPath(apiPath)}\n deviceId={deviceId}\n pageSize={pageSize}\n initialState={initialState}\n >\n <ClientBoard {...board} />\n </FewchursProvider>\n );\n}\n","import \"server-only\";\nimport { createFewchursClient, DEFAULT_BASE_URL, type FewchursClient } from \"@fewchurs/react/core\";\n\nexport const DEFAULT_API_PATH = \"/api/fewchurs\";\n\nexport interface ServerConfig {\n apiKey?: string;\n baseUrl?: string;\n language?: string;\n}\n\nexport function resolveApiKey(apiKey?: string): string | undefined {\n return apiKey ?? process.env.FEWCHURS_API_KEY;\n}\n\nexport function resolveBaseUrl(baseUrl?: string): string {\n return baseUrl ?? process.env.FEWCHURS_BASE_URL ?? DEFAULT_BASE_URL;\n}\n\nexport function resolveApiPath(apiPath?: string): string {\n return (apiPath ?? process.env.FEWCHURS_API_PATH ?? DEFAULT_API_PATH).replace(/\\/$/, \"\");\n}\n\nexport function serverClient(deviceId: string | null, config: ServerConfig = {}): FewchursClient {\n const apiKey = resolveApiKey(config.apiKey);\n if (!apiKey) {\n throw new Error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY in your environment, or pass apiKey to the server component or createHandler().\",\n );\n }\n\n return createFewchursClient({\n apiKey,\n baseUrl: resolveBaseUrl(config.baseUrl),\n deviceId,\n language: config.language,\n });\n}\n","import \"server-only\";\nimport { cookies } from \"next/headers\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE } from \"./constants\";\n\nexport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE };\n\nexport type DeviceIdResolver = () => string | Promise<string>;\n\n/** The visitor's id, or null when nothing has been stored for them yet. */\nexport async function readDeviceId(): Promise<string | null> {\n const store = await cookies();\n return store.get(DEVICE_ID_COOKIE)?.value ?? null;\n}\n\n/**\n * Reads the cookie, falling back to a fresh id. The value is not persisted here: a server\n * component cannot set cookies, so the route handler stores it on the first request instead.\n */\nexport async function resolveDeviceId(custom?: DeviceIdResolver): Promise<string> {\n if (custom) return custom();\n return (await readDeviceId()) ?? createDeviceId();\n}\n\nexport function deviceIdCookie(deviceId: string): {\n name: string;\n value: string;\n options: {\n httpOnly: true;\n sameSite: \"lax\";\n path: string;\n maxAge: number;\n secure: boolean;\n };\n} {\n return {\n name: DEVICE_ID_COOKIE,\n value: deviceId,\n options: {\n httpOnly: true,\n sameSite: \"lax\",\n path: \"/\",\n maxAge: DEVICE_ID_MAX_AGE,\n secure: process.env.NODE_ENV === \"production\",\n },\n };\n}\n\nexport { createDeviceId };\n","// Generated by tools/stamp-version.mjs from @fewchurs/next's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.0.1\";\n","import { SDK_VERSION } from \"./version\";\n\nexport const DEVICE_ID_COOKIE = \"fewchurs_did\";\nexport const DEVICE_ID_MAX_AGE = 60 * 60 * 24 * 365;\nexport { SDK_VERSION };\nexport const SDK_HEADER = `next/${SDK_VERSION}`;\n","import \"server-only\";\nimport { createDeviceId } from \"@fewchurs/react/core\";\nimport { resolveApiKey, resolveApiPath, resolveBaseUrl } from \"./config\";\nimport { DEVICE_ID_COOKIE, DEVICE_ID_MAX_AGE, SDK_HEADER } from \"./constants\";\nimport type { DeviceIdResolver } from \"./identity\";\n\nexport interface HandlerOptions {\n apiKey?: string;\n baseUrl?: string;\n /** Where the handler is mounted, when it is not `/api/fewchurs`. */\n apiPath?: string;\n /** Return a stable, hashed id for the signed-in visitor so votes follow the account. */\n deviceId?: DeviceIdResolver;\n}\n\ntype RouteParams = { route?: string[] };\n\n/** What Next passes: a Promise in 15, a plain object in 14. Both are awaited the same way. */\ntype IncomingContext = { params?: RouteParams | Promise<RouteParams> };\n\n// Next 15's route check wants a required second argument whose `params` is a Promise. The\n// handler itself takes `IncomingContext`, so Next 14's plain object still works at runtime.\ntype RouteContext = { params: Promise<RouteParams> };\n\nexport type RouteHandler = (request: Request, context: RouteContext) => Promise<Response>;\n\n/** Only the endpoints the board needs: everything else is a 404, not a proxy to the API. */\nconst ALLOWED: Array<{ method: string; pattern: RegExp }> = [\n { method: \"GET\", pattern: /^config$/ },\n { method: \"GET\", pattern: /^features$/ },\n { method: \"POST\", pattern: /^features$/ },\n { method: \"GET\", pattern: /^features\\/[^/]+$/ },\n { method: \"POST\", pattern: /^features\\/[^/]+\\/vote$/ },\n { method: \"DELETE\", pattern: /^features\\/[^/]+\\/vote$/ },\n { method: \"POST\", pattern: /^features\\/[^/]+\\/follow$/ },\n { method: \"DELETE\", pattern: /^features\\/[^/]+\\/follow$/ },\n { method: \"GET\", pattern: /^features\\/[^/]+\\/comments$/ },\n { method: \"POST\", pattern: /^comments$/ },\n];\n\nconst DEVICE_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/;\n\nexport function createHandler(options: HandlerOptions = {}): {\n GET: RouteHandler;\n POST: RouteHandler;\n DELETE: RouteHandler;\n} {\n async function handle(request: Request, context?: IncomingContext): Promise<Response> {\n const path = await routePath(request, context, resolveApiPath(options.apiPath));\n\n if (!ALLOWED.some((route) => route.method === request.method && route.pattern.test(path))) {\n return json({ error: { code: \"not_found\", message: \"Unknown Fewchurs route.\" } }, 404);\n }\n\n const apiKey = resolveApiKey(options.apiKey);\n if (!apiKey) {\n console.error(\n \"[fewchurs] No API key. Set FEWCHURS_API_KEY, or pass apiKey to createHandler().\",\n );\n return json({ error: { code: \"internal\", message: \"Fewchurs is not configured.\" } }, 500);\n }\n\n const cookieValue = readCookie(request, DEVICE_ID_COOKIE);\n const custom = options.deviceId ? await options.deviceId() : null;\n const deviceId = custom ?? cookieValue ?? claimedDeviceId(request) ?? createDeviceId();\n\n const target = `${resolveBaseUrl(options.baseUrl)}/${path}${new URL(request.url).search}`;\n const headers: Record<string, string> = {\n Authorization: `Bearer ${apiKey}`,\n \"X-Device-Id\": deviceId,\n \"X-Fewchurs-Sdk\": request.headers.get(\"x-fewchurs-sdk\") ?? SDK_HEADER,\n Accept: \"application/json\",\n };\n const language = request.headers.get(\"accept-language\");\n if (language) headers[\"Accept-Language\"] = language;\n\n const body = request.method === \"GET\" ? undefined : await request.text();\n if (body) headers[\"Content-Type\"] = \"application/json\";\n\n let upstream: Response;\n try {\n upstream = await fetch(target, { method: request.method, headers, body, cache: \"no-store\" });\n } catch {\n return json({ error: { code: \"internal\", message: \"Could not reach Fewchurs.\" } }, 502);\n }\n\n const response = new Response(await upstream.text(), {\n status: upstream.status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" },\n });\n\n // A visitor keeps the same id across requests without the browser ever seeing the key,\n // and an httpOnly cookie means page scripts cannot read or change it either.\n if (!custom && deviceId !== cookieValue) {\n response.headers.append(\"Set-Cookie\", serializeCookie(deviceId));\n }\n\n return response;\n }\n\n return { GET: handle, POST: handle, DELETE: handle };\n}\n\nconst defaultHandler = createHandler();\n\nexport const GET = defaultHandler.GET;\nexport const POST = defaultHandler.POST;\nexport const DELETE = defaultHandler.DELETE;\n\nasync function routePath(\n request: Request,\n context: IncomingContext | undefined,\n apiPath: string,\n): Promise<string> {\n const params = await context?.params;\n if (params?.route?.length) return params.route.map(encodeURIComponent).join(\"/\");\n\n const { pathname } = new URL(request.url);\n const index = pathname.indexOf(apiPath);\n return index === -1 ? \"\" : pathname.slice(index + apiPath.length).replace(/^\\//, \"\");\n}\n\n/**\n * The first request from a server-rendered board carries the id the page was rendered with,\n * so adopting it keeps the server's `hasVoted` answers and the browser's in agreement.\n */\nfunction claimedDeviceId(request: Request): string | null {\n const claimed = request.headers.get(\"x-device-id\");\n return claimed && DEVICE_ID_PATTERN.test(claimed) ? claimed : null;\n}\n\nfunction readCookie(request: Request, name: string): string | null {\n const header = request.headers.get(\"cookie\");\n if (!header) return null;\n const match = header.match(new RegExp(`(?:^|;\\\\s*)${name}=([^;]*)`));\n return match?.[1] ? decodeURIComponent(match[1]) : null;\n}\n\nfunction serializeCookie(deviceId: string): string {\n const parts = [\n `${DEVICE_ID_COOKIE}=${encodeURIComponent(deviceId)}`,\n \"Path=/\",\n `Max-Age=${DEVICE_ID_MAX_AGE}`,\n \"HttpOnly\",\n \"SameSite=Lax\",\n ];\n if (process.env.NODE_ENV === \"production\") parts.push(\"Secure\");\n return parts.join(\"; \");\n}\n\nfunction json(body: unknown, status: number): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" },\n });\n}\n"],"mappings":"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","names":["import_server_only","import_core","import_server_only","import_core","ClientBoard","import_server_only","import_core"]}
|
package/dist/index.mjs
CHANGED
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
createHandler,
|
|
4
4
|
resolveApiPath,
|
|
5
5
|
serverClient
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KPJHLDQU.mjs";
|
|
7
7
|
import {
|
|
8
8
|
readDeviceId,
|
|
9
9
|
resolveDeviceId
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-QULKPZB4.mjs";
|
|
11
11
|
import {
|
|
12
12
|
DEVICE_ID_COOKIE
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-W6QR6DEB.mjs";
|
|
14
14
|
|
|
15
15
|
// src/board.tsx
|
|
16
16
|
import "server-only";
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fewchurs/next",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A server-rendered Fewchurs feature-request board for Next.js, with the API key kept on the server.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "commonjs",
|
|
7
|
-
"homepage": "https://www.fewchurs.
|
|
7
|
+
"homepage": "https://www.fewchurs.com",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/fewchurs/fewchurs-react.git",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"server-only": "^0.0.1",
|
|
89
|
-
"@fewchurs/react": "^1.0.
|
|
89
|
+
"@fewchurs/react": "^1.0.1"
|
|
90
90
|
},
|
|
91
91
|
"peerDependencies": {
|
|
92
92
|
"next": ">=14",
|
|
File without changes
|
|
File without changes
|