@commish/next 0.1.0-beta.10
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 +21 -0
- package/README.md +161 -0
- package/bin/init.mjs +329 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +1 -0
- package/dist/capture.d.ts +5 -0
- package/dist/capture.js +123 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +11 -0
- package/dist/metadata.d.ts +12 -0
- package/dist/metadata.js +22 -0
- package/dist/provider.d.ts +10 -0
- package/dist/provider.js +14 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oftring Ventures LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# @commish/next
|
|
2
|
+
|
|
3
|
+
MIT-licensed Next.js App Router integration for permanent Commish TEST and LIVE modes.
|
|
4
|
+
This source checkpoint is verified with Node 24.15.0, Next 16.3.4 and React 19.2.8.
|
|
5
|
+
It does not establish npm availability or acceptance against a hosted Commish API.
|
|
6
|
+
Use the exact paired `0.1.0-beta.10` SDK/Next packages and keep the consumer lockfile.
|
|
7
|
+
Accepted candidate tarballs remain available for prepublication qualification.
|
|
8
|
+
|
|
9
|
+
## Capture route
|
|
10
|
+
|
|
11
|
+
Set `COMMISH_API_URL` to the selected mode's API base, including `/api/v1`, and
|
|
12
|
+
`COMMISH_SECRET_KEY` to its application-scoped secret. Keep both on the server.
|
|
13
|
+
Only the publishable key and application ID belong in the public variables below.
|
|
14
|
+
Create `app/api/commish/attribution/route.ts` (or under `src/app`):
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { createAttributionHandler } from "@commish/next";
|
|
18
|
+
|
|
19
|
+
export const runtime = "nodejs";
|
|
20
|
+
export const POST = createAttributionHandler({
|
|
21
|
+
apiUrl: process.env.COMMISH_API_URL,
|
|
22
|
+
secretKey: process.env.COMMISH_SECRET_KEY,
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The route exchanges the referral with Commish and stores successful attribution
|
|
27
|
+
in an HttpOnly cookie. Configure the API base explicitly for the selected deployment;
|
|
28
|
+
the helper otherwise uses its default Commish API origin.
|
|
29
|
+
|
|
30
|
+
## Application layout
|
|
31
|
+
|
|
32
|
+
Wrap the existing content in `app/layout.tsx` (or `src/app/layout.tsx`):
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { CommishProvider } from "@commish/next/react";
|
|
36
|
+
import type { ReactNode } from "react";
|
|
37
|
+
|
|
38
|
+
export default function Layout({ children }: { children: ReactNode }) {
|
|
39
|
+
return (
|
|
40
|
+
<html lang="en">
|
|
41
|
+
<body>
|
|
42
|
+
<CommishProvider
|
|
43
|
+
publishableKey={process.env.NEXT_PUBLIC_COMMISH_PUBLISHABLE_KEY!}
|
|
44
|
+
applicationId={process.env.NEXT_PUBLIC_COMMISH_APPLICATION_ID!}
|
|
45
|
+
>
|
|
46
|
+
{children}
|
|
47
|
+
</CommishProvider>
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The provider captures on client navigation through the same-origin route above.
|
|
55
|
+
Set `capturePath` on the provider if that route uses another same-origin path.
|
|
56
|
+
Successful capture removes `commish_ref` while preserving navigation state;
|
|
57
|
+
failure keeps the referral for retry. Wait for capture before starting Checkout.
|
|
58
|
+
|
|
59
|
+
## Agent-driven installation and Checkout
|
|
60
|
+
|
|
61
|
+
In your existing authenticated server Checkout route, await
|
|
62
|
+
`withCommishStripeMetadata(params)` from `@commish/next` before passing the result
|
|
63
|
+
to your existing Stripe Checkout integration. The helper preserves other
|
|
64
|
+
parameters and applies the request's attribution to payment or subscription
|
|
65
|
+
metadata. `getCommishAttribution()` reads that cookie directly; both helpers
|
|
66
|
+
require a Next request context. The root export is blocked in browser builds.
|
|
67
|
+
The provider belongs at `@commish/next/react`; `@commish/next/browser` also exports
|
|
68
|
+
`captureReferral` for direct browser integration.
|
|
69
|
+
|
|
70
|
+
After installing an accepted pair, run from the consumer project root:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
pnpm exec commish-next --json # inspect the plan; no writes
|
|
74
|
+
pnpm exec commish-next --write --json # install the integration files
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The CLI creates the capture route above and `app/commish-provider.tsx` (under
|
|
78
|
+
`src/app` when there is no root `app`). JavaScript apps receive `.js`/`.jsx`
|
|
79
|
+
files; the installer does not enable TypeScript. Repeated runs preserve identical files;
|
|
80
|
+
custom files, conflicting extensions and symbolic links stop installation before
|
|
81
|
+
writing. It never reads or writes credentials. Supply matching application keys
|
|
82
|
+
for the selected mode through your environment configuration.
|
|
83
|
+
|
|
84
|
+
The coding agent must finish these existing application edits:
|
|
85
|
+
|
|
86
|
+
1. Import `CommishRootProvider` from `./commish-provider` in the root layout and
|
|
87
|
+
wrap its existing children. Preserve other providers, metadata and layout content.
|
|
88
|
+
2. Add the awaited metadata helper to the authenticated Checkout handler as above.
|
|
89
|
+
3. Build, then exercise a TEST referral through capture and Checkout. Successful
|
|
90
|
+
capture must set the HttpOnly cookie; Checkout must carry its attribution.
|
|
91
|
+
|
|
92
|
+
JSON output lists file outcomes, required environment variable names and next
|
|
93
|
+
steps. `files_installed` means those files exist; `integrationVerified: false`
|
|
94
|
+
explicitly leaves wiring, configuration and end-to-end verification outstanding.
|
|
95
|
+
If a custom route/provider already exists, integrate using the examples above;
|
|
96
|
+
the installer will not overwrite it or guess at customer code.
|
|
97
|
+
|
|
98
|
+
Package installation does not connect a Stripe account, configure a program,
|
|
99
|
+
activate a creator, or prove a conversion. Complete the hosted TEST setup and
|
|
100
|
+
record its acceptance separately from package build and installation evidence.
|
|
101
|
+
|
|
102
|
+
## Provision TEST or LIVE credentials from the CLI
|
|
103
|
+
|
|
104
|
+
An owner or admin can authorize an agent-driven setup without copying a secret
|
|
105
|
+
from the dashboard:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
pnpm exec commish-next setup \
|
|
109
|
+
--workspace wrk_your_workspace_id \
|
|
110
|
+
--application-name "Your product" \
|
|
111
|
+
--key-label "Local agent setup" \
|
|
112
|
+
--output .env.commish \
|
|
113
|
+
--json
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The command defaults to TEST; add `--mode live` for LIVE credentials. LIVE requires
|
|
117
|
+
current workspace eligibility and provider readiness at approval and exchange;
|
|
118
|
+
it never falls back to TEST. The command generates the selected mode's keys on the developer's
|
|
119
|
+
machine, opens a ten-minute approval page on `https://app.commish.sh`, and polls
|
|
120
|
+
for the approved exchange. The browser requires an authenticated workspace owner
|
|
121
|
+
or admin with a recent authenticator check. Only hashes and the publishable key
|
|
122
|
+
cross the browser boundary; Commish never receives or stores the raw secret.
|
|
123
|
+
|
|
124
|
+
After approval, the CLI atomically creates the explicitly named output with mode
|
|
125
|
+
`0600`. It never overwrites an existing path. The file contains
|
|
126
|
+
`COMMISH_API_URL`, `COMMISH_SECRET_KEY`,
|
|
127
|
+
`NEXT_PUBLIC_COMMISH_PUBLISHABLE_KEY`, and
|
|
128
|
+
`NEXT_PUBLIC_COMMISH_APPLICATION_ID`. Add that path to the product's local secret
|
|
129
|
+
handling policy; do not commit it. An interrupted exchange can safely retry the
|
|
130
|
+
same verifier and receives the same application and key.
|
|
131
|
+
|
|
132
|
+
Use `--no-open` when the controlling agent will present the printed approval URL
|
|
133
|
+
itself. For a trusted local Commish runtime, `--app-url` accepts an explicit
|
|
134
|
+
loopback HTTP origin; other custom origins require HTTPS. Setup creates application
|
|
135
|
+
credentials in the approved mode. Program configuration and the complete transaction
|
|
136
|
+
journey remain separate verification steps.
|
|
137
|
+
|
|
138
|
+
## Verify configuration from the CLI
|
|
139
|
+
|
|
140
|
+
With the installed packages and configured environment, set `COMMISH_PROGRAM_ID`
|
|
141
|
+
to your existing program ID and run `pnpm exec commish-next verify --json`.
|
|
142
|
+
The command reads `COMMISH_SECRET_KEY`, `NEXT_PUBLIC_COMMISH_PUBLISHABLE_KEY`,
|
|
143
|
+
`NEXT_PUBLIC_COMMISH_APPLICATION_ID` and `COMMISH_PROGRAM_ID` from the process
|
|
144
|
+
environment. Use Node's `--env-file` support or your existing environment runner
|
|
145
|
+
if needed; the CLI does not search for or change dotenv files.
|
|
146
|
+
|
|
147
|
+
It performs one authenticated, read-only program lookup in the key's TEST or LIVE
|
|
148
|
+
mode. `configuration_verified` means the key can read that program and its
|
|
149
|
+
application/mode match the configured values. The receipt includes program status;
|
|
150
|
+
a paused or draft program is not declared ready for transactions. Publishable-key
|
|
151
|
+
format/mode is checked locally; its server-side application binding is **not** proved.
|
|
152
|
+
`integrationVerified: false` and `unverified` identify the remaining real journey.
|
|
153
|
+
|
|
154
|
+
The API defaults to `https://app.commish.sh/api/v1`. Set `COMMISH_API_URL` only to a
|
|
155
|
+
Commish deployment you trust: this destination receives the secret key. Custom
|
|
156
|
+
bases require HTTPS, except explicit loopback development addresses. Redirects are
|
|
157
|
+
rejected; responses and request duration are bounded. The CLI does not provision resources or issue mutation requests. Authentication
|
|
158
|
+
may update key-usage metadata. It never prints credentials/provider bodies or retries a request. Invalid configuration,
|
|
159
|
+
denied/revoked keys, inaccessible programs, mismatches and unavailable responses exit
|
|
160
|
+
with code 1 and a stable JSON error code. On older deployments that reject LIVE reads,
|
|
161
|
+
verification fails with `access_denied`; it never falls back to TEST.
|
package/bin/init.mjs
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
4
|
+
import {
|
|
5
|
+
linkSync,
|
|
6
|
+
lstatSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
unlinkSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
13
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
14
|
+
|
|
15
|
+
const hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
16
|
+
const token = () => randomBytes(24).toString("base64url");
|
|
17
|
+
const safeUrl = (raw) => {
|
|
18
|
+
let value;
|
|
19
|
+
try { value = new URL(raw); } catch { throw new Error("invalid_app_url"); }
|
|
20
|
+
if (value.username || value.password || value.search || value.hash || value.pathname !== "/" ||
|
|
21
|
+
!(value.protocol === "https:" || value.protocol === "http:" &&
|
|
22
|
+
["127.0.0.1", "[::1]", "localhost"].includes(value.hostname)))
|
|
23
|
+
throw new Error("invalid_app_url");
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
26
|
+
const responseJson = async (response) => {
|
|
27
|
+
if (Number(response.headers.get("content-length")) > 65_536 || !response.body)
|
|
28
|
+
throw new Error("invalid_response");
|
|
29
|
+
const chunks = []; let size = 0;
|
|
30
|
+
for await (const chunk of response.body) {
|
|
31
|
+
size += chunk.byteLength;
|
|
32
|
+
if (size > 65_536) throw new Error("invalid_response");
|
|
33
|
+
chunks.push(chunk);
|
|
34
|
+
}
|
|
35
|
+
try { return JSON.parse(Buffer.concat(chunks).toString("utf8")); }
|
|
36
|
+
catch { throw new Error("invalid_response"); }
|
|
37
|
+
};
|
|
38
|
+
const setupOptions = (values) => {
|
|
39
|
+
const result = { appUrl: "https://app.commish.sh", keyLabel: "Agent setup", mode: "test", open: true };
|
|
40
|
+
const keys = new Set();
|
|
41
|
+
for (let index = 0; index < values.length; index++) {
|
|
42
|
+
const name = values[index];
|
|
43
|
+
if (name === "--json" || name === "--no-open") {
|
|
44
|
+
if (keys.has(name)) throw new Error("invalid_arguments");
|
|
45
|
+
keys.add(name);
|
|
46
|
+
if (name === "--json") result.json = true;
|
|
47
|
+
else result.open = false;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const fields = {
|
|
51
|
+
"--workspace": "workspaceId", "--output": "output",
|
|
52
|
+
"--application-name": "applicationName", "--key-label": "keyLabel",
|
|
53
|
+
"--app-url": "appUrl", "--mode": "mode",
|
|
54
|
+
};
|
|
55
|
+
const field = fields[name], value = values[++index];
|
|
56
|
+
if (!field || keys.has(name) || !value || value.startsWith("--"))
|
|
57
|
+
throw new Error("invalid_arguments");
|
|
58
|
+
keys.add(name); result[field] = value;
|
|
59
|
+
}
|
|
60
|
+
if (!/^wrk_[A-Za-z0-9_-]{12,}$/.test(result.workspaceId ?? "") ||
|
|
61
|
+
!result.output || !result.applicationName?.trim() || result.applicationName.length > 100 ||
|
|
62
|
+
!result.keyLabel.trim() || result.keyLabel.length > 100 ||
|
|
63
|
+
!["test", "live"].includes(result.mode))
|
|
64
|
+
throw new Error("invalid_arguments");
|
|
65
|
+
result.applicationName = result.applicationName.trim();
|
|
66
|
+
result.keyLabel = result.keyLabel.trim();
|
|
67
|
+
result.appUrl = safeUrl(result.appUrl);
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
const openBrowser = (url) => {
|
|
71
|
+
const command = process.platform === "darwin" ? ["open", [url]]
|
|
72
|
+
: process.platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", url]]
|
|
73
|
+
: ["xdg-open", [url]];
|
|
74
|
+
const child = spawn(command[0], command[1], { detached: true, stdio: "ignore" });
|
|
75
|
+
child.on("error", () => {}); child.unref();
|
|
76
|
+
};
|
|
77
|
+
const credentialTarget = (output) => {
|
|
78
|
+
const target = resolve(output), parent = dirname(target);
|
|
79
|
+
const parentEntry = lstatSync(parent);
|
|
80
|
+
if (!parentEntry.isDirectory() || parentEntry.isSymbolicLink())
|
|
81
|
+
throw new Error("unsafe_output_path");
|
|
82
|
+
try {
|
|
83
|
+
lstatSync(target);
|
|
84
|
+
const error = new Error("output_exists"); error.code = "EEXIST"; throw error;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error.code !== "ENOENT") throw error;
|
|
87
|
+
}
|
|
88
|
+
return target;
|
|
89
|
+
};
|
|
90
|
+
const saveCredentials = (output, values) => {
|
|
91
|
+
const target = credentialTarget(output), parent = dirname(target);
|
|
92
|
+
const temporary = join(parent, `.${basename(target)}.${randomUUID()}.tmp`);
|
|
93
|
+
const body = Object.entries(values).map(([name, value]) => `${name}=${value}\n`).join("");
|
|
94
|
+
try {
|
|
95
|
+
writeFileSync(temporary, body, { flag: "wx", mode: 0o600 });
|
|
96
|
+
linkSync(temporary, target);
|
|
97
|
+
} finally {
|
|
98
|
+
try { unlinkSync(temporary); } catch {}
|
|
99
|
+
}
|
|
100
|
+
return target;
|
|
101
|
+
};
|
|
102
|
+
const setupReceipt = (body, expected) => {
|
|
103
|
+
const value = body?.data, app = value?.application, key = value?.apiKey;
|
|
104
|
+
if (value?.protocol !== "commish-cli-setup-v1" || value.status !== "complete" ||
|
|
105
|
+
value.workspaceId !== expected.workspaceId || typeof value.expiresAt !== "string" ||
|
|
106
|
+
typeof value.replayed !== "boolean" || !/^app_[A-Za-z0-9_-]{12,}$/.test(app?.id ?? "") ||
|
|
107
|
+
app?.name !== expected.applicationName || !Array.isArray(app?.verifiedOrigins) ||
|
|
108
|
+
typeof app?.createdAt !== "string" || !/^key_[A-Za-z0-9_-]{12,}$/.test(key?.id ?? "") ||
|
|
109
|
+
key?.applicationId !== app.id || key?.mode !== expected.mode ||
|
|
110
|
+
(value.mode ?? "test") !== expected.mode ||
|
|
111
|
+
key?.publishableKey !== expected.publishableKey || key?.label !== expected.keyLabel ||
|
|
112
|
+
typeof key?.createdAt !== "string" || key?.lastUsedAt !== null || key?.revokedAt !== null)
|
|
113
|
+
throw new Error("invalid_response");
|
|
114
|
+
return value;
|
|
115
|
+
};
|
|
116
|
+
const setup = async (values) => {
|
|
117
|
+
const options = setupOptions(values), output = credentialTarget(options.output),
|
|
118
|
+
verifier = randomBytes(32).toString("base64url"),
|
|
119
|
+
publishableKey = `cm_${options.mode}_pk_${token()}`, secretKey = `cm_${options.mode}_sk_${token()}`,
|
|
120
|
+
expiresAt = new Date(Date.now() + 570_000).toISOString(),
|
|
121
|
+
request = {
|
|
122
|
+
workspaceId: options.workspaceId, mode: options.mode, challengeHash: hash(verifier),
|
|
123
|
+
applicationName: options.applicationName, keyLabel: options.keyLabel,
|
|
124
|
+
publishableKey, secretHash: hash(secretKey),
|
|
125
|
+
idempotencyKey: `cli-setup:${randomUUID()}`, expiresAt,
|
|
126
|
+
}, approval = new URL("/cli/setup", options.appUrl);
|
|
127
|
+
approval.search = new URLSearchParams(request).toString();
|
|
128
|
+
process.stderr.write(`Authorize ${options.mode.toUpperCase()} setup in your browser:\n${approval.href}\n`);
|
|
129
|
+
if (options.open) openBrowser(approval.href);
|
|
130
|
+
const exchange = new URL("/api/cli/setup-grants/exchange", options.appUrl);
|
|
131
|
+
let receipt;
|
|
132
|
+
while (Date.now() < Date.parse(expiresAt)) {
|
|
133
|
+
let response;
|
|
134
|
+
try {
|
|
135
|
+
response = await fetch(exchange, {
|
|
136
|
+
method: "POST", headers: { "content-type": "application/json", accept: "application/json" },
|
|
137
|
+
body: JSON.stringify({ verifier }), redirect: "error", cache: "no-store",
|
|
138
|
+
signal: AbortSignal.timeout(10_000),
|
|
139
|
+
});
|
|
140
|
+
const body = await responseJson(response);
|
|
141
|
+
if (response.ok) { receipt = setupReceipt(body, { ...options, publishableKey }); break; }
|
|
142
|
+
if (response.status !== 408 && response.status !== 429 && response.status !== 428 &&
|
|
143
|
+
response.status < 500)
|
|
144
|
+
throw new Error(body?.error?.code === "setup_grant_expired" ? "setup_expired" : "setup_denied");
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (["setup_expired", "setup_denied", "invalid_response"].includes(error.message)) throw error;
|
|
147
|
+
}
|
|
148
|
+
await delay(2_000);
|
|
149
|
+
}
|
|
150
|
+
if (!receipt) throw new Error("setup_expired");
|
|
151
|
+
const file = saveCredentials(output, {
|
|
152
|
+
COMMISH_API_URL: new URL("/api/v1", options.appUrl).href,
|
|
153
|
+
COMMISH_SECRET_KEY: secretKey,
|
|
154
|
+
NEXT_PUBLIC_COMMISH_PUBLISHABLE_KEY: publishableKey,
|
|
155
|
+
NEXT_PUBLIC_COMMISH_APPLICATION_ID: receipt.application.id,
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
status: `${options.mode}_credentials_configured`, mode: options.mode, workspaceId: options.workspaceId,
|
|
159
|
+
applicationId: receipt.application.id, apiKeyId: receipt.apiKey.id, output: file,
|
|
160
|
+
replayed: receipt.replayed, integrationVerified: false,
|
|
161
|
+
next: `Configure a ${options.mode.toUpperCase()} program, then run commish-next verify --json with COMMISH_PROGRAM_ID.`,
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const args = process.argv.slice(2);
|
|
166
|
+
const json = args.includes("--json");
|
|
167
|
+
if (args[0] === "setup") {
|
|
168
|
+
try {
|
|
169
|
+
const result = await setup(args.slice(1));
|
|
170
|
+
console.log(json ? JSON.stringify(result) :
|
|
171
|
+
`Commish ${result.mode.toUpperCase()} credentials saved to ${result.output}.\n${result.next}`);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
const code = ["invalid_arguments", "invalid_app_url", "unsafe_output_path", "invalid_response",
|
|
174
|
+
"setup_expired", "setup_denied"].includes(error?.message) ? error.message :
|
|
175
|
+
error?.code === "EEXIST" ? "output_exists" : "setup_unavailable";
|
|
176
|
+
console.error(json ? JSON.stringify({ status: "error", code }) : `Setup failed: ${code}.`);
|
|
177
|
+
process.exitCode = 1;
|
|
178
|
+
}
|
|
179
|
+
} else if (args[0] === "verify") {
|
|
180
|
+
const fail = (code) => { throw new Error(code); };
|
|
181
|
+
try {
|
|
182
|
+
if (args.slice(1).some((arg) => arg !== "--json") || new Set(args).size !== args.length)
|
|
183
|
+
fail("invalid_arguments");
|
|
184
|
+
const env = process.env;
|
|
185
|
+
const secret = env.COMMISH_SECRET_KEY ?? "";
|
|
186
|
+
const key = secret.match(/^cm_(test|live)_sk_[A-Za-z0-9_-]{12,}$/);
|
|
187
|
+
const publishable = (env.NEXT_PUBLIC_COMMISH_PUBLISHABLE_KEY ?? "").match(/^cm_(test|live)_pk_[A-Za-z0-9_-]{12,}$/);
|
|
188
|
+
const applicationId = env.NEXT_PUBLIC_COMMISH_APPLICATION_ID;
|
|
189
|
+
const programId = env.COMMISH_PROGRAM_ID;
|
|
190
|
+
if (!key || !publishable || !/^app_[A-Za-z0-9_-]{12,}$/.test(applicationId ?? "") ||
|
|
191
|
+
!/^prg_[A-Za-z0-9_-]{12,}$/.test(programId ?? "")) fail("invalid_configuration");
|
|
192
|
+
const mode = key[1] === "live" ? "live" : "test";
|
|
193
|
+
if (mode !== publishable[1]) fail("key_mode_mismatch");
|
|
194
|
+
let api;
|
|
195
|
+
try { api = new URL(env.COMMISH_API_URL ?? "https://app.commish.sh/api/v1"); }
|
|
196
|
+
catch { fail("invalid_api_url"); }
|
|
197
|
+
if (api.username || api.password || api.search || api.hash ||
|
|
198
|
+
!/^\/api\/v1\/?$/.test(api.pathname) ||
|
|
199
|
+
!(api.protocol === "https:" || api.protocol === "http:" &&
|
|
200
|
+
["127.0.0.1", "[::1]", "localhost"].includes(api.hostname))) fail("invalid_api_url");
|
|
201
|
+
const response = await fetch(`${api.href.replace(/\/$/, "")}/programs/${programId}`, {
|
|
202
|
+
method: "GET", headers: { authorization: `Bearer ${secret}`, accept: "application/json" },
|
|
203
|
+
redirect: "error", cache: "no-store", signal: AbortSignal.timeout(10_000),
|
|
204
|
+
});
|
|
205
|
+
if (!response.ok) {
|
|
206
|
+
await response.body?.cancel();
|
|
207
|
+
fail(({ 401: "invalid_api_key", 403: "access_denied", 404: "program_not_found" })[response.status] ?? "verification_unavailable");
|
|
208
|
+
}
|
|
209
|
+
const chunks = []; let size = 0;
|
|
210
|
+
if (!response.body) fail("invalid_response");
|
|
211
|
+
for await (const chunk of response.body) {
|
|
212
|
+
size += chunk.byteLength;
|
|
213
|
+
if (size > 65_536) fail("invalid_response");
|
|
214
|
+
chunks.push(chunk);
|
|
215
|
+
}
|
|
216
|
+
let program;
|
|
217
|
+
try { program = JSON.parse(Buffer.concat(chunks).toString("utf8"))?.data; }
|
|
218
|
+
catch { fail("invalid_response"); }
|
|
219
|
+
if (!program || program.id !== programId ||
|
|
220
|
+
!["test", "live"].includes(program.mode) ||
|
|
221
|
+
!["draft", "active", "paused", "suspended", "archived"].includes(program.status)) fail("invalid_response");
|
|
222
|
+
if (program.applicationId !== applicationId) fail("application_mismatch");
|
|
223
|
+
if (program.mode !== mode) fail("program_mode_mismatch");
|
|
224
|
+
const result = {
|
|
225
|
+
status: "configuration_verified", mode, applicationId, programId,
|
|
226
|
+
programStatus: program.status,
|
|
227
|
+
checks: ["secret_key_authenticated", "program_accessible", "application_matches", "mode_matches", "publishable_key_mode_matches"],
|
|
228
|
+
unverified: ["publishable_key_binding", "consumer_wiring", "attribution", "checkout", "webhooks", "refunds", "renewals", "payouts"],
|
|
229
|
+
integrationVerified: false,
|
|
230
|
+
};
|
|
231
|
+
console.log(json ? JSON.stringify(result) :
|
|
232
|
+
`Configuration verified (${result.mode}, program ${programId}, status ${program.status}).\n` +
|
|
233
|
+
"Publishable-key binding and end-to-end integration remain unverified. Use --json for individual checks.");
|
|
234
|
+
} catch (error) {
|
|
235
|
+
const codes = ["invalid_arguments", "invalid_configuration", "key_mode_mismatch", "invalid_api_url",
|
|
236
|
+
"invalid_api_key", "access_denied", "program_not_found", "verification_unavailable",
|
|
237
|
+
"invalid_response", "application_mismatch", "program_mode_mismatch"];
|
|
238
|
+
const code = codes.includes(error?.message) ? error.message : "verification_unavailable";
|
|
239
|
+
console.error(json ? JSON.stringify({ status: "error", code, integrationVerified: false }) : `Verification failed: ${code}.`);
|
|
240
|
+
process.exitCode = 1;
|
|
241
|
+
}
|
|
242
|
+
} else try {
|
|
243
|
+
if (args.some((arg) => !["init", "--write", "--json"].includes(arg)) ||
|
|
244
|
+
new Set(args).size !== args.length) throw new Error("Usage: commish-next [init] [--write] [--json]");
|
|
245
|
+
const stat = (path) => {
|
|
246
|
+
try { return lstatSync(path); } catch (error) {
|
|
247
|
+
if (error.code === "ENOENT") return null;
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
// Next ignores src/app when a root app directory exists.
|
|
252
|
+
const app = stat("app") ? "app" : stat("src/app") ? "src/app" : null;
|
|
253
|
+
if (!app) throw new Error("No Next.js App Router directory found.");
|
|
254
|
+
const safePath = (path) => {
|
|
255
|
+
let current = ".";
|
|
256
|
+
for (const part of path.split("/")) {
|
|
257
|
+
current = join(current, part);
|
|
258
|
+
const entry = stat(current);
|
|
259
|
+
if (entry && (entry.isSymbolicLink() || (current !== path && !entry.isDirectory())))
|
|
260
|
+
throw new Error(`Refusing unsafe path: ${current}`);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
safePath(`${app}/`);
|
|
264
|
+
const typed = Boolean(stat("tsconfig.json") || stat(`${app}/layout.tsx`));
|
|
265
|
+
const files = {
|
|
266
|
+
[`${app}/api/commish/attribution/route.${typed ? "ts" : "js"}`]: `import { createAttributionHandler } from "@commish/next";
|
|
267
|
+
|
|
268
|
+
export const runtime = "nodejs";
|
|
269
|
+
export const POST = createAttributionHandler({
|
|
270
|
+
apiUrl: process.env.COMMISH_API_URL,
|
|
271
|
+
secretKey: process.env.COMMISH_SECRET_KEY,
|
|
272
|
+
});
|
|
273
|
+
`,
|
|
274
|
+
[`${app}/commish-provider.${typed ? "tsx" : "jsx"}`]: `import { CommishProvider } from "@commish/next/react";
|
|
275
|
+
${typed ? 'import type { ReactNode } from "react";\n' : ""}
|
|
276
|
+
export default function CommishRootProvider({ children }${typed ? ": { children: ReactNode }" : ""}) {
|
|
277
|
+
return (
|
|
278
|
+
<CommishProvider
|
|
279
|
+
publishableKey={process.env.NEXT_PUBLIC_COMMISH_PUBLISHABLE_KEY${typed ? "!" : ""}}
|
|
280
|
+
applicationId={process.env.NEXT_PUBLIC_COMMISH_APPLICATION_ID${typed ? "!" : ""}}
|
|
281
|
+
>
|
|
282
|
+
{children}
|
|
283
|
+
</CommishProvider>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
`,
|
|
287
|
+
};
|
|
288
|
+
for (const extension of ["js", "jsx", "ts", "tsx"])
|
|
289
|
+
if (stat(`${app}/api/commish/attribution/page.${extension}`))
|
|
290
|
+
throw new Error("The attribution URL already has a page. Choose a different capturePath and integrate using the Next setup guide.");
|
|
291
|
+
const plan = Object.entries(files).map(([path, content]) => {
|
|
292
|
+
safePath(path);
|
|
293
|
+
for (const extension of ["js", "jsx", "ts", "tsx"])
|
|
294
|
+
if (path.replace(/\.[^.]+$/, `.${extension}`) !== path &&
|
|
295
|
+
stat(path.replace(/\.[^.]+$/, `.${extension}`)))
|
|
296
|
+
throw new Error(`Conflicting file extension for ${path}; integrate using the Next setup guide.`);
|
|
297
|
+
const entry = stat(path);
|
|
298
|
+
return { path, status: !entry ? "create" :
|
|
299
|
+
entry.isFile() && readFileSync(path, "utf8") === content ? "unchanged" : "conflict" };
|
|
300
|
+
});
|
|
301
|
+
const conflicts = plan.filter(({ status }) => status === "conflict");
|
|
302
|
+
if (conflicts.length) throw new Error(`Existing custom files: ${conflicts.map(({ path }) => path).join(", ")}. No files changed; integrate using the Next setup guide.`);
|
|
303
|
+
const write = args.includes("--write");
|
|
304
|
+
if (write) for (const { path, status } of plan) if (status === "create") {
|
|
305
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
306
|
+
writeFileSync(path, files[path], { flag: "wx" });
|
|
307
|
+
}
|
|
308
|
+
const result = {
|
|
309
|
+
status: write ? "files_installed" : "plan", app, files: plan,
|
|
310
|
+
requiredEnvironment: ["COMMISH_API_URL", "COMMISH_SECRET_KEY",
|
|
311
|
+
"NEXT_PUBLIC_COMMISH_PUBLISHABLE_KEY", "NEXT_PUBLIC_COMMISH_APPLICATION_ID"],
|
|
312
|
+
next: [
|
|
313
|
+
`Import CommishRootProvider from './commish-provider' in the existing ${app}/layout file and wrap its existing children. Preserve the layout's other content.`,
|
|
314
|
+
"Set matching application credentials for the selected TEST or LIVE mode. Keep COMMISH_SECRET_KEY server-only.",
|
|
315
|
+
"Await referral capture before Checkout; await withCommishStripeMetadata(params) in your authenticated server Checkout handler.",
|
|
316
|
+
"Build the application, then verify a referral capture and attributed Checkout in TEST before LIVE activation.",
|
|
317
|
+
],
|
|
318
|
+
integrationVerified: false,
|
|
319
|
+
};
|
|
320
|
+
console.log(json ? JSON.stringify(result) : [
|
|
321
|
+
write ? "Commish integration files installed." : "Commish setup plan (use --write to install).",
|
|
322
|
+
...plan.map(({ path, status }) => `${status}: ${path}`),
|
|
323
|
+
`Required environment: ${result.requiredEnvironment.join(", ")}`,
|
|
324
|
+
...result.next,
|
|
325
|
+
].join("\n"));
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error(json ? JSON.stringify({ status: "error", message: error.message }) : error.message);
|
|
328
|
+
process.exitCode = 1;
|
|
329
|
+
}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { captureReferral } from "@commish/sdk/browser";
|
package/dist/capture.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { cookies } from "next/headers.js";
|
|
2
|
+
export const COMMISH_COOKIE = "commish_attribution";
|
|
3
|
+
export function createAttributionHandler(options) {
|
|
4
|
+
return async function POST(request) {
|
|
5
|
+
// Loaded per request so the root module stays importable wherever the
|
|
6
|
+
// cookie helpers are: only the handler needs the server response builder
|
|
7
|
+
// and the Node hash implementation.
|
|
8
|
+
const { NextResponse } = await import("next/server.js");
|
|
9
|
+
const body = await request.text();
|
|
10
|
+
const captureId = request.headers.get("x-commish-capture-id");
|
|
11
|
+
if (!captureId || !/^[a-f\d]{32}$/.test(captureId))
|
|
12
|
+
return NextResponse.json({
|
|
13
|
+
error: {
|
|
14
|
+
code: "invalid_capture_id",
|
|
15
|
+
message: "A valid browser capture ID is required.",
|
|
16
|
+
},
|
|
17
|
+
}, { status: 400 });
|
|
18
|
+
const configuredUrl = options.apiUrl ?? "https://app.commish.sh/api/v1";
|
|
19
|
+
let urlEnd = configuredUrl.length;
|
|
20
|
+
while (urlEnd > 0 && configuredUrl[urlEnd - 1] === "/")
|
|
21
|
+
urlEnd--;
|
|
22
|
+
const apiUrl = configuredUrl.slice(0, urlEnd);
|
|
23
|
+
const clientIp = request.headers.get("x-vercel-forwarded-for");
|
|
24
|
+
const userAgent = request.headers.get("user-agent");
|
|
25
|
+
const country = request.headers.get("x-vercel-ip-country");
|
|
26
|
+
const previousAttributionId = (await cookies()).get(COMMISH_COOKIE)?.value;
|
|
27
|
+
let idempotencyBody = body;
|
|
28
|
+
let forwardedBody = body;
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(body);
|
|
31
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
32
|
+
const { previousAttributionId: _untrusted, ...capture } = parsed;
|
|
33
|
+
void _untrusted;
|
|
34
|
+
idempotencyBody = JSON.stringify(capture);
|
|
35
|
+
forwardedBody = JSON.stringify({
|
|
36
|
+
...capture,
|
|
37
|
+
...(previousAttributionId &&
|
|
38
|
+
/^atr_[A-Za-z0-9_-]{12,}$/.test(previousAttributionId)
|
|
39
|
+
? { previousAttributionId }
|
|
40
|
+
: {}),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Preserve malformed input for the API contract to reject consistently.
|
|
46
|
+
}
|
|
47
|
+
const { createHash } = await import("node:crypto");
|
|
48
|
+
const idempotencyKey = `attribution:${createHash("sha256")
|
|
49
|
+
.update(`${captureId}.${idempotencyBody}`)
|
|
50
|
+
.digest("hex")}`;
|
|
51
|
+
let response;
|
|
52
|
+
let responseText;
|
|
53
|
+
try {
|
|
54
|
+
response = await fetch(`${apiUrl}/attributions/capture`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"content-type": "application/json",
|
|
58
|
+
"idempotency-key": idempotencyKey,
|
|
59
|
+
"x-commish-publishable-key": request.headers.get("x-commish-publishable-key") ?? "",
|
|
60
|
+
...(options.secretKey
|
|
61
|
+
? { authorization: `Bearer ${options.secretKey}` }
|
|
62
|
+
: {}),
|
|
63
|
+
...(clientIp ? { "x-commish-client-ip": clientIp } : {}),
|
|
64
|
+
...(userAgent ? { "user-agent": userAgent } : {}),
|
|
65
|
+
...(country ? { "x-vercel-ip-country": country } : {}),
|
|
66
|
+
},
|
|
67
|
+
body: forwardedBody,
|
|
68
|
+
});
|
|
69
|
+
responseText = await response.text();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// An unreachable or truncated upstream is a transport failure, not an
|
|
73
|
+
// attribution outcome: answer the caller instead of rejecting the route.
|
|
74
|
+
return NextResponse.json({
|
|
75
|
+
error: {
|
|
76
|
+
code: "attribution_capture_unavailable",
|
|
77
|
+
message: "Attribution capture is temporarily unavailable.",
|
|
78
|
+
},
|
|
79
|
+
}, { status: 502 });
|
|
80
|
+
}
|
|
81
|
+
let payload;
|
|
82
|
+
try {
|
|
83
|
+
payload = responseText ? JSON.parse(responseText) : undefined;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
payload = undefined;
|
|
87
|
+
}
|
|
88
|
+
if (!response.ok)
|
|
89
|
+
return NextResponse.json(payload && typeof payload === "object"
|
|
90
|
+
? payload
|
|
91
|
+
: {
|
|
92
|
+
error: {
|
|
93
|
+
code: "attribution_capture_failed",
|
|
94
|
+
message: "Attribution capture failed.",
|
|
95
|
+
},
|
|
96
|
+
}, { status: response.status });
|
|
97
|
+
const capturePayload = payload && typeof payload === "object"
|
|
98
|
+
? payload
|
|
99
|
+
: undefined;
|
|
100
|
+
const token = capturePayload?.data?.token;
|
|
101
|
+
const expiresAt = capturePayload?.data?.expiresAt;
|
|
102
|
+
const expiresAtMs = typeof expiresAt === "string" ? Date.parse(expiresAt) : Number.NaN;
|
|
103
|
+
const maxAge = Math.floor((expiresAtMs - Date.now()) / 1000);
|
|
104
|
+
if (typeof token !== "string" || !token || typeof expiresAt !== "string" ||
|
|
105
|
+
!Number.isFinite(expiresAtMs) || maxAge <= 0)
|
|
106
|
+
return NextResponse.json({
|
|
107
|
+
error: {
|
|
108
|
+
code: "invalid_capture_response",
|
|
109
|
+
message: "Commish returned an invalid attribution response.",
|
|
110
|
+
},
|
|
111
|
+
}, { status: 502 });
|
|
112
|
+
const outgoing = NextResponse.json({ data: { captured: true, expiresAt } }, { status: response.status });
|
|
113
|
+
outgoing.cookies.set(COMMISH_COOKIE, token, {
|
|
114
|
+
httpOnly: true,
|
|
115
|
+
sameSite: "lax",
|
|
116
|
+
secure: process.env.NODE_ENV === "production",
|
|
117
|
+
path: "/",
|
|
118
|
+
expires: new Date(expiresAtMs),
|
|
119
|
+
maxAge,
|
|
120
|
+
});
|
|
121
|
+
return outgoing;
|
|
122
|
+
};
|
|
123
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { COMMISH_COOKIE, createAttributionHandler } from "./capture.js";
|
|
2
|
+
import { applyCommishStripeMetadata, type StripeCheckoutParams } from "./metadata.js";
|
|
3
|
+
export { applyCommishStripeMetadata };
|
|
4
|
+
export declare function getCommishAttribution(): Promise<string | null>;
|
|
5
|
+
export declare function withCommishStripeMetadata<T extends StripeCheckoutParams>(params: T): Promise<T>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { cookies } from "next/headers.js";
|
|
2
|
+
import { COMMISH_COOKIE } from "./capture.js";
|
|
3
|
+
export { COMMISH_COOKIE, createAttributionHandler } from "./capture.js";
|
|
4
|
+
import { applyCommishStripeMetadata } from "./metadata.js";
|
|
5
|
+
export { applyCommishStripeMetadata };
|
|
6
|
+
export async function getCommishAttribution() {
|
|
7
|
+
return (await cookies()).get(COMMISH_COOKIE)?.value ?? null;
|
|
8
|
+
}
|
|
9
|
+
export async function withCommishStripeMetadata(params) {
|
|
10
|
+
return applyCommishStripeMetadata(params, await getCommishAttribution());
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type StripeMetadata = Record<string, string | number | null>;
|
|
2
|
+
export type StripeCheckoutParams = {
|
|
3
|
+
mode?: string;
|
|
4
|
+
client_reference_id?: string;
|
|
5
|
+
metadata?: StripeMetadata;
|
|
6
|
+
subscription_data?: {
|
|
7
|
+
metadata?: StripeMetadata;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export declare function applyCommishStripeMetadata<T extends StripeCheckoutParams>(params: T, attribution: string | null): T;
|
|
12
|
+
export {};
|
package/dist/metadata.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function applyCommishStripeMetadata(params, attribution) {
|
|
2
|
+
if (!attribution)
|
|
3
|
+
return params;
|
|
4
|
+
return {
|
|
5
|
+
...params,
|
|
6
|
+
metadata: { ...params.metadata, commish_attribution: attribution },
|
|
7
|
+
...(params.mode === "subscription"
|
|
8
|
+
? {
|
|
9
|
+
subscription_data: {
|
|
10
|
+
...params.subscription_data,
|
|
11
|
+
metadata: {
|
|
12
|
+
...params.subscription_data?.metadata,
|
|
13
|
+
commish_attribution: attribution,
|
|
14
|
+
...(params.client_reference_id
|
|
15
|
+
? { commish_customer_id: params.client_reference_id }
|
|
16
|
+
: {}),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
: {}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
type CommishCaptureProps = {
|
|
3
|
+
publishableKey: string;
|
|
4
|
+
applicationId: string;
|
|
5
|
+
capturePath?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function CommishProvider({ children, ...captureProps }: CommishCaptureProps & {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
export {};
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Suspense, useEffect } from "react";
|
|
4
|
+
import { captureReferral } from "@commish/sdk/browser";
|
|
5
|
+
import { usePathname, useSearchParams } from "next/navigation.js";
|
|
6
|
+
function CommishCapture({ publishableKey, applicationId, capturePath, }) {
|
|
7
|
+
const pathname = usePathname();
|
|
8
|
+
const searchParams = useSearchParams();
|
|
9
|
+
useEffect(() => void captureReferral({ publishableKey, applicationId, capturePath }), [publishableKey, applicationId, capturePath, pathname, searchParams]);
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
export function CommishProvider({ children, ...captureProps }) {
|
|
13
|
+
return (_jsxs(_Fragment, { children: [_jsx(Suspense, { fallback: null, children: _jsx(CommishCapture, { ...captureProps }) }), children] }));
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@commish/next",
|
|
3
|
+
"version": "0.1.0-beta.10",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/oftring-ventures/commish-sdk.git",
|
|
8
|
+
"directory": "packages/next"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=24 <25"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public",
|
|
16
|
+
"tag": "beta"
|
|
17
|
+
},
|
|
18
|
+
"description": "Next.js referral capture and Stripe metadata helpers for Commish",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"browser": null,
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./browser": {
|
|
26
|
+
"types": "./dist/browser.d.ts",
|
|
27
|
+
"default": "./dist/browser.js"
|
|
28
|
+
},
|
|
29
|
+
"./react": {
|
|
30
|
+
"types": "./dist/provider.d.ts",
|
|
31
|
+
"default": "./dist/provider.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@commish/sdk": "0.1.0-beta.10",
|
|
36
|
+
"next": ">=16.2.12 <17",
|
|
37
|
+
"react": ">=19.2.8 <20"
|
|
38
|
+
},
|
|
39
|
+
"bin": {
|
|
40
|
+
"commish-next": "./bin/init.mjs"
|
|
41
|
+
}
|
|
42
|
+
}
|