@synap-core/sdk 1.2.0 → 1.4.0
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/README.md +154 -0
- package/dist/chunk-J7XHAAFJ.js +79 -0
- package/dist/chunk-J7XHAAFJ.js.map +1 -0
- package/dist/connect.browser.cjs +22 -0
- package/dist/connect.browser.cjs.map +1 -0
- package/dist/connect.browser.d.cts +2 -0
- package/dist/connect.browser.d.ts +2 -0
- package/dist/connect.browser.js +5 -0
- package/dist/connect.browser.js.map +1 -0
- package/dist/connect.cjs +1487 -0
- package/dist/connect.cjs.map +1 -0
- package/dist/connect.d.cts +647 -0
- package/dist/connect.d.ts +647 -0
- package/dist/connect.js +1365 -0
- package/dist/connect.js.map +1 -0
- package/dist/testing.cjs +431 -19
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +160 -1
- package/dist/testing.d.ts +160 -1
- package/dist/testing.js +404 -19
- package/dist/testing.js.map +1 -1
- package/dist/types-C2KGNXEn.d.cts +95 -0
- package/dist/types-C2KGNXEn.d.ts +95 -0
- package/package.json +18 -10
- package/src/connect/auth.ts +535 -0
- package/src/connect/browser.ts +10 -0
- package/src/connect/client.ts +299 -0
- package/src/connect/cloud.ts +206 -0
- package/src/connect/discovery.ts +263 -0
- package/src/connect/encoding.ts +93 -0
- package/src/connect/errors.ts +115 -0
- package/src/connect/flow.ts +489 -0
- package/src/connect/guard.ts +25 -0
- package/src/connect/index.ts +87 -0
- package/src/connect/seal.ts +119 -0
- package/src/connect/store.ts +189 -0
- package/src/connect/types.ts +100 -0
- package/src/testing/auth-server.ts +512 -0
- package/src/testing/cloud.ts +84 -0
- package/src/testing/index.ts +7 -0
package/README.md
CHANGED
|
@@ -119,6 +119,160 @@ SDK refuses out-of-bounds `offset`/`limit`/`sort` locally, without a request),
|
|
|
119
119
|
pass `ifNoneMatch` from a browser: the doors allow no cross-origin request header
|
|
120
120
|
but `Content-Type`, so the preflight fails (browsers revalidate on their own).
|
|
121
121
|
|
|
122
|
+
## Pod-app sign-in (server-only): `@synap-core/sdk/connect` (1.3.0, 1.4.0)
|
|
123
|
+
|
|
124
|
+
An app that reads and writes a person's OWN pod connects as an OAuth 2.1 public
|
|
125
|
+
client of that pod: its `client_id` is the https URL of its client metadata
|
|
126
|
+
document (CIMD), PKCE S256, every answer checked for the pod's `iss`. The pod
|
|
127
|
+
token stays on the app's server, sealed (AES-GCM) in an HttpOnly cookie; the
|
|
128
|
+
browser never sees it. Framework-free and Web-standard (`Request` → `Response`,
|
|
129
|
+
WebCrypto): Next route handlers and `proxy.ts`, Hono, Remix, edge runtimes.
|
|
130
|
+
**Server-only**: it throws when loaded in a browser, and a browser bundle
|
|
131
|
+
resolves it to a stub that throws.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// lib/synap/pod-app/auth.ts (server-only)
|
|
135
|
+
import { createPodAppAuth } from "@synap-core/sdk/connect";
|
|
136
|
+
|
|
137
|
+
export const synap = createPodAppAuth({
|
|
138
|
+
appUrl: process.env.SITE_URL!, // https://studio.example.com
|
|
139
|
+
clientName: "Studio",
|
|
140
|
+
requests: [
|
|
141
|
+
{ permission: "entity.post.read", reason: "Show your posts" },
|
|
142
|
+
{ permission: "entity.post.create", reason: "Save drafts to your pod" },
|
|
143
|
+
],
|
|
144
|
+
secret: process.env.SYNAP_APP_SESSION_SECRET!, // ≥ 32 chars, never NEXT_PUBLIC_
|
|
145
|
+
cloudUrl: process.env.SYNAP_CLOUD_URL, // optional (1.4): https://api.synap.live offers "Continue with Synap Cloud"
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// app/.well-known/synap-client.json/route.ts export const GET = synap.handlers.clientMetadata;
|
|
149
|
+
// app/api/synap/connect/route.ts export const POST = synap.handlers.connect; // form { pod, next?, space?, prompt? }
|
|
150
|
+
// app/api/synap/callback/route.ts export const GET = synap.handlers.callback;
|
|
151
|
+
// app/api/synap/disconnect/route.ts export const POST = synap.handlers.disconnect; // form { next? }
|
|
152
|
+
// app/api/synap/cloud/route.ts (1.4) export const POST = synap.handlers.cloudConnect; // form { next?, space?, prompt? }
|
|
153
|
+
// export const GET = synap.handlers.cloudReturn;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// proxy.ts: refresh before render (server components cannot set cookies)
|
|
158
|
+
const out = await synap.refreshIfNeeded(request);
|
|
159
|
+
// out.status: none | skipped | fresh | refreshed | kept | expired | revoked | unavailable
|
|
160
|
+
// out.lapsed (with none, 1.4): { podUrl } when this browser WAS connected and the connection lapsed
|
|
161
|
+
// send out.setCookies on the response; forward out.cookieHeader (when set) as the request's Cookie
|
|
162
|
+
|
|
163
|
+
// Next 16 strips its router headers before proxy code runs, so skip prefetches in the MATCHER
|
|
164
|
+
// (it still sees them; matcher values must be literals):
|
|
165
|
+
export const config = {
|
|
166
|
+
matcher: [{
|
|
167
|
+
source: "/((?!_next/static|_next/image|api/synap/|\\.well-known/|.*\\.\\w+$).*)",
|
|
168
|
+
missing: [{ type: "header", key: "next-router-prefetch" }, { type: "header", key: "purpose", value: "prefetch" }],
|
|
169
|
+
}],
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// a server component / action (no Request there: pass the Cookie header)
|
|
173
|
+
const connection = await synap.readConnection((await headers()).get("cookie"));
|
|
174
|
+
const pod = synap.client(connection!);
|
|
175
|
+
const res = await pod.post("/entities", { profileSlug: "post", title: "Hello" });
|
|
176
|
+
if (res.ok && res.status === "proposed") showPending(res.reviewUrl); // success: queued for review, never retry
|
|
177
|
+
if (!res.ok) res.error.code; // expired | unauthorized | forbidden | door_unsupported | rate_limited (retryAfterSec) | pod_unreachable | …
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- **Discovery**: `discoverPod("pod.example.com")` fetches the pod's RFC 8414
|
|
181
|
+
metadata and requires `issuer` equal to the address, CIMD, `iss` on answers and
|
|
182
|
+
S256 (`not_a_pod`, `pod_unsupported`, `pod_unreachable`). A browser launch
|
|
183
|
+
(`?iss=…&target_link_uri=…`) is read with `acceptLaunch` and only pre-fills the
|
|
184
|
+
connect page.
|
|
185
|
+
- **Failures** redirect with `?error=<ConnectErrorCode>`: `denied`,
|
|
186
|
+
`expired_transaction`, `mismatch`, `misconfigured`, `rate_limited`,
|
|
187
|
+
`pod_unreachable`, … (`parseConnectErrorCode` reads it back). `connect` and
|
|
188
|
+
`disconnect` are POST and same-origin only.
|
|
189
|
+
- **Refresh** (rotating, single-use, 30 days from issue) is feature-detected: a
|
|
190
|
+
pod that issues no `refresh_token` gives a connection that ends with its
|
|
191
|
+
1-hour access token. `refreshIfNeeded` rotates only inside the last 5
|
|
192
|
+
minutes, is single-flight per connection in the process, and persists the
|
|
193
|
+
rotated connection BEFORE returning it. The pod revokes the whole connection
|
|
194
|
+
when a spent refresh token is presented again, so for 2 minutes a request
|
|
195
|
+
still carrying the spent token (a parallel request, a second tab) gets the
|
|
196
|
+
rotation's result instead of reaching the pod. Across server instances only
|
|
197
|
+
a shared server-side `ConnectionStore` rules that replay out. A concurrent
|
|
198
|
+
loser (refused softly by the pod) keeps the still-valid access token.
|
|
199
|
+
The pod's reuse grace (PB3b) answers a spent token presented again within
|
|
200
|
+
30 s with the same pair the first use got; its `expires_in` may be under an
|
|
201
|
+
hour, and the SDK reads it as sent.
|
|
202
|
+
- **Your own store**: implement `ConnectionStore` (`read`, `write`, `clear`,
|
|
203
|
+
returning Set-Cookie values) and pass it as `store`.
|
|
204
|
+
- Lower level: `startConnect`, `handleCallback`, `refreshConnection`,
|
|
205
|
+
`revokeConnection`, `buildClientMetadata`, `seal`/`unseal`,
|
|
206
|
+
`startCloudConnect`, `acceptCloudReturn`.
|
|
207
|
+
|
|
208
|
+
### 1.4.0 (additive)
|
|
209
|
+
|
|
210
|
+
- **Re-consent**: the connect form's `prompt=consent` (or `startConnect({ prompt: "consent" })`)
|
|
211
|
+
goes to the pod's consent screen even when this browser is already connected to
|
|
212
|
+
that pod (to widen or narrow access). It is a normal sign-in: fresh PKCE verifier
|
|
213
|
+
and state, `iss` checked; a denied or forged answer leaves the live connection as it is.
|
|
214
|
+
- **Synap Cloud** (wave C1): with `cloudUrl` set, `synap.cloudConnectAvailable` is
|
|
215
|
+
true and `cloudConnectAvailable({ cloudUrl })` too. POST `handlers.cloudConnect`
|
|
216
|
+
sends the browser to `<cp>/connect/pod?client_id=<client id>&state=<nonce>`, the
|
|
217
|
+
state sealed in an HttpOnly cookie of that browser (10 minutes). The CP returns to
|
|
218
|
+
the CIMD `initiate_login_uri?iss=<pod>&state=<nonce>` (your connect page):
|
|
219
|
+
`acceptLaunch(url).cloudState` is set, so forward the request's query to GET
|
|
220
|
+
`handlers.cloudReturn`, which checks the state against the cookie (constant time,
|
|
221
|
+
single use) BEFORE the pod is used, then runs the same sign-in as a typed address.
|
|
222
|
+
`acceptLaunch` itself never verifies anything; a return without a matching state
|
|
223
|
+
starts no sign-in (`mismatch`, `expired_transaction`). A cancel on the chooser
|
|
224
|
+
(`?state=…&error=access_denied`, no `iss`) also carries `cloudState`: forwarded the
|
|
225
|
+
same way, it lands on the connect page with `?error=denied` once the state matched.
|
|
226
|
+
Security notes: after the state matches, the named pod goes through the same
|
|
227
|
+
discovery as a typed address (metadata, `issuer` equality, CIMD, `iss`, S256)
|
|
228
|
+
before the browser is sent anywhere. The cloud cookie is single-use and cleared on
|
|
229
|
+
every outcome (success, cancel, mismatch). Accepted trade-off: a third-party page
|
|
230
|
+
that sends a visitor's browser to a forged return can cancel that visitor's Cloud
|
|
231
|
+
sign-in in progress (they click again); nothing leaks and nothing is bound, the
|
|
232
|
+
same nuisance any OAuth `state` check allows.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// app/connect/page.tsx (server component)
|
|
236
|
+
const launch = acceptLaunch(url, origin);
|
|
237
|
+
if (launch.cloudState) redirect(`${synap.paths.cloud}${new URL(url).search}`);
|
|
238
|
+
```
|
|
239
|
+
- **`connection.manageUrl` / `connection.appId`**: the pod's page for this app
|
|
240
|
+
connection (pod-admin `/apps/<appId>`), read from the token response's
|
|
241
|
+
`synap_app: { id, manageUrl }` (https only). Absent when the pod does not send it:
|
|
242
|
+
hide the link, never build one.
|
|
243
|
+
- **`client.listPendingProposals({ workspaceId?, limit?, offset? })`**: "Waiting for
|
|
244
|
+
review: N" (`data.total`, plus summary rows with `reviewUrl`) from the pod's seamed
|
|
245
|
+
`GET /proposals` door. Ask for `proposal.read` in `requests`: without it the answer
|
|
246
|
+
is `forbidden` and nothing is sent (the pod would answer an empty list). It counts
|
|
247
|
+
the person's pending proposals in those spaces, from any agent.
|
|
248
|
+
- **`spaces(connection)` / `client.spaces()`**: the grant by space
|
|
249
|
+
(`{ workspaceId, workspaceName, permissions }`), each permission once.
|
|
250
|
+
- **Expired vs never connected**: each sign-in and refresh also sets a marker cookie
|
|
251
|
+
(`__Host-synap_seen`, 400 days) holding the pod's URL and nothing else.
|
|
252
|
+
`synap.readLapsed(cookieHeader)` and `RefreshOutcome.lapsed` say "this browser was
|
|
253
|
+
connected to <pod>" once the sealed session is gone; a disconnect clears it.
|
|
254
|
+
- **`isPrefetchOrAsset(request)`** is exported, and its limit documented: inside
|
|
255
|
+
Next 16's proxy a router prefetch is invisible (see the matcher above).
|
|
256
|
+
- Not added: `ensureTemplate` on the pod-app client. A pod-app key's grant is pinned
|
|
257
|
+
to the approved spaces, so creating a space is refused by the pod's grant check
|
|
258
|
+
every time; ask for templates with `templates` (CIMD `synap_templates`) instead:
|
|
259
|
+
the pod's consent offers to create the space.
|
|
260
|
+
|
|
261
|
+
Test without a pod: `createMockAuthServer()` from `@synap-core/sdk/testing`
|
|
262
|
+
(metadata, `authorize()` as the consent screen, `/token` with PKCE, rotating
|
|
263
|
+
refresh with the 30 s reuse grace, `/revoke`, Hub REST doors: `GET /entities`
|
|
264
|
+
answers an array as the pod does, `GET /proposals` lists what proposed writes
|
|
265
|
+
filed; `synap_app` is `MOCK_APP` unless `app: null`; `writeMode`, `failures`, `refresh: false`, `entities`,
|
|
266
|
+
`refreshGraceSec`), and `createMockCloud()` for the Synap Cloud pod chooser:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
const pod = createMockAuthServer();
|
|
270
|
+
const cloud = createMockCloud();
|
|
271
|
+
const synap = createPodAppAuth({ ...config, fetch: pod.fetch, cloudUrl: cloud.url });
|
|
272
|
+
// POST connect → Location → pod.authorize(location) → GET callback with that URL
|
|
273
|
+
// POST cloud → Location → cloud.choose(location, { pod: pod.issuer, metadata: synap.metadata }) → forward to GET cloud
|
|
274
|
+
```
|
|
275
|
+
|
|
122
276
|
## Mock pod: `@synap-core/sdk/testing`
|
|
123
277
|
|
|
124
278
|
A fake `fetch` that answers the public doors (forms, shares, sites) from fixtures,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/connect/encoding.ts
|
|
2
|
+
var encoder = new TextEncoder();
|
|
3
|
+
var decoder = new TextDecoder();
|
|
4
|
+
function utf8(text) {
|
|
5
|
+
return encoder.encode(text);
|
|
6
|
+
}
|
|
7
|
+
function fromUtf8(bytes) {
|
|
8
|
+
return decoder.decode(bytes);
|
|
9
|
+
}
|
|
10
|
+
function base64url(bytes) {
|
|
11
|
+
let binary = "";
|
|
12
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
13
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
14
|
+
}
|
|
15
|
+
function fromBase64url(text) {
|
|
16
|
+
if (!/^[A-Za-z0-9_-]*$/.test(text)) return null;
|
|
17
|
+
const padded = text.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((text.length + 3) % 4);
|
|
18
|
+
let binary;
|
|
19
|
+
try {
|
|
20
|
+
binary = atob(padded);
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const out = new Uint8Array(binary.length);
|
|
25
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function randomBytes(length) {
|
|
29
|
+
const out = new Uint8Array(length);
|
|
30
|
+
crypto.getRandomValues(out);
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function randomToken() {
|
|
34
|
+
return base64url(randomBytes(32));
|
|
35
|
+
}
|
|
36
|
+
async function pkceChallenge(verifier) {
|
|
37
|
+
const digest = await crypto.subtle.digest("SHA-256", utf8(verifier));
|
|
38
|
+
return base64url(new Uint8Array(digest));
|
|
39
|
+
}
|
|
40
|
+
function constantTimeEqual(a, b) {
|
|
41
|
+
const x = utf8(a);
|
|
42
|
+
const y = utf8(b);
|
|
43
|
+
let diff = x.length ^ y.length;
|
|
44
|
+
for (let i = 0; i < Math.max(x.length, y.length); i++) diff |= (x[i] ?? 0) ^ (y[i] ?? 0);
|
|
45
|
+
return diff === 0;
|
|
46
|
+
}
|
|
47
|
+
function parseCookies(header) {
|
|
48
|
+
const out = /* @__PURE__ */ new Map();
|
|
49
|
+
if (!header) return out;
|
|
50
|
+
for (const part of header.split(";")) {
|
|
51
|
+
const eq = part.indexOf("=");
|
|
52
|
+
if (eq < 0) continue;
|
|
53
|
+
const name = part.slice(0, eq).trim();
|
|
54
|
+
if (!name || out.has(name)) continue;
|
|
55
|
+
let value = part.slice(eq + 1).trim();
|
|
56
|
+
if (value.startsWith('"') && value.endsWith('"')) value = value.slice(1, -1);
|
|
57
|
+
out.set(name, value);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function serializeCookie(name, value, attrs) {
|
|
62
|
+
const parts = [`${name}=${value}`, "Path=/", "HttpOnly", "SameSite=Lax", `Max-Age=${Math.max(0, Math.floor(attrs.maxAgeSec))}`];
|
|
63
|
+
if (attrs.secure) parts.push("Secure");
|
|
64
|
+
return parts.join("; ");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
utf8,
|
|
69
|
+
fromUtf8,
|
|
70
|
+
base64url,
|
|
71
|
+
fromBase64url,
|
|
72
|
+
randomBytes,
|
|
73
|
+
randomToken,
|
|
74
|
+
pkceChallenge,
|
|
75
|
+
constantTimeEqual,
|
|
76
|
+
parseCookies,
|
|
77
|
+
serializeCookie
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=chunk-J7XHAAFJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connect/encoding.ts"],"sourcesContent":["/**\n * Web-standard primitives (WebCrypto, TextEncoder, btoa/atob): the same code\n * runs on Node 18+, edge runtimes and workers. No Node import.\n */\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/** `Uint8Array<ArrayBuffer>`: what WebCrypto's `BufferSource` takes under TS 5.7+ lib types. */\nexport type Bytes = Uint8Array<ArrayBuffer>;\n\nexport function utf8(text: string): Bytes {\n return encoder.encode(text);\n}\n\nexport function fromUtf8(bytes: Uint8Array): string {\n return decoder.decode(bytes);\n}\n\nexport function base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Null when `text` is not base64url. */\nexport function fromBase64url(text: string): Bytes | null {\n if (!/^[A-Za-z0-9_-]*$/.test(text)) return null;\n const padded = text.replace(/-/g, \"+\").replace(/_/g, \"/\") + \"===\".slice((text.length + 3) % 4);\n let binary: string;\n try {\n binary = atob(padded);\n } catch {\n return null;\n }\n const out = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);\n return out;\n}\n\nexport function randomBytes(length: number): Bytes {\n const out = new Uint8Array(length);\n crypto.getRandomValues(out);\n return out;\n}\n\n/** 32 random bytes, base64url (43 chars): a state value or a PKCE verifier. */\nexport function randomToken(): string {\n return base64url(randomBytes(32));\n}\n\n/** RFC 7636 §4.2: `BASE64URL(SHA256(ASCII(code_verifier)))`. */\nexport async function pkceChallenge(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", utf8(verifier));\n return base64url(new Uint8Array(digest));\n}\n\n/** Compare two strings without an early exit on the first differing byte. */\nexport function constantTimeEqual(a: string, b: string): boolean {\n const x = utf8(a);\n const y = utf8(b);\n let diff = x.length ^ y.length;\n for (let i = 0; i < Math.max(x.length, y.length); i++) diff |= (x[i] ?? 0) ^ (y[i] ?? 0);\n return diff === 0;\n}\n\n/** The request's cookies by name (the first value wins, as browsers order the most specific first). */\nexport function parseCookies(header: string | null): Map<string, string> {\n const out = new Map<string, string>();\n if (!header) return out;\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq < 0) continue;\n const name = part.slice(0, eq).trim();\n if (!name || out.has(name)) continue;\n let value = part.slice(eq + 1).trim();\n if (value.startsWith('\"') && value.endsWith('\"')) value = value.slice(1, -1);\n out.set(name, value);\n }\n return out;\n}\n\nexport interface CookieAttributes {\n maxAgeSec: number;\n secure: boolean;\n}\n\n/** `HttpOnly; SameSite=Lax; Path=/` always; `Secure` on https. Never a `Domain`. */\nexport function serializeCookie(name: string, value: string, attrs: CookieAttributes): string {\n const parts = [`${name}=${value}`, \"Path=/\", \"HttpOnly\", \"SameSite=Lax\", `Max-Age=${Math.max(0, Math.floor(attrs.maxAgeSec))}`];\n if (attrs.secure) parts.push(\"Secure\");\n return parts.join(\"; \");\n}\n"],"mappings":";AAKA,IAAM,UAAU,IAAI,YAAY;AAChC,IAAM,UAAU,IAAI,YAAY;AAKzB,SAAS,KAAK,MAAqB;AACxC,SAAO,QAAQ,OAAO,IAAI;AAC5B;AAEO,SAAS,SAAS,OAA2B;AAClD,SAAO,QAAQ,OAAO,KAAK;AAC7B;AAEO,SAAS,UAAU,OAA2B;AACnD,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,IAAK,WAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAC7E,SAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,EAAE;AAC/E;AAGO,SAAS,cAAc,MAA4B;AACxD,MAAI,CAAC,mBAAmB,KAAK,IAAI,EAAG,QAAO;AAC3C,QAAM,SAAS,KAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,IAAI,MAAM,OAAO,KAAK,SAAS,KAAK,CAAC;AAC7F,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,MAAM,IAAI,WAAW,OAAO,MAAM;AACxC,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,IAAK,KAAI,CAAC,IAAI,OAAO,WAAW,CAAC;AACpE,SAAO;AACT;AAEO,SAAS,YAAY,QAAuB;AACjD,QAAM,MAAM,IAAI,WAAW,MAAM;AACjC,SAAO,gBAAgB,GAAG;AAC1B,SAAO;AACT;AAGO,SAAS,cAAsB;AACpC,SAAO,UAAU,YAAY,EAAE,CAAC;AAClC;AAGA,eAAsB,cAAc,UAAmC;AACrE,QAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK,QAAQ,CAAC;AACnE,SAAO,UAAU,IAAI,WAAW,MAAM,CAAC;AACzC;AAGO,SAAS,kBAAkB,GAAW,GAAoB;AAC/D,QAAM,IAAI,KAAK,CAAC;AAChB,QAAM,IAAI,KAAK,CAAC;AAChB,MAAI,OAAO,EAAE,SAAS,EAAE;AACxB,WAAS,IAAI,GAAG,IAAI,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAK,UAAS,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC,KAAK;AACtF,SAAO,SAAS;AAClB;AAGO,SAAS,aAAa,QAA4C;AACvE,QAAM,MAAM,oBAAI,IAAoB;AACpC,MAAI,CAAC,OAAQ,QAAO;AACpB,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,KAAK,KAAK,QAAQ,GAAG;AAC3B,QAAI,KAAK,EAAG;AACZ,UAAM,OAAO,KAAK,MAAM,GAAG,EAAE,EAAE,KAAK;AACpC,QAAI,CAAC,QAAQ,IAAI,IAAI,IAAI,EAAG;AAC5B,QAAI,QAAQ,KAAK,MAAM,KAAK,CAAC,EAAE,KAAK;AACpC,QAAI,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,EAAG,SAAQ,MAAM,MAAM,GAAG,EAAE;AAC3E,QAAI,IAAI,MAAM,KAAK;AAAA,EACrB;AACA,SAAO;AACT;AAQO,SAAS,gBAAgB,MAAc,OAAe,OAAiC;AAC5F,QAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,KAAK,IAAI,UAAU,YAAY,gBAAgB,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE;AAC9H,MAAI,MAAM,OAAQ,OAAM,KAAK,QAAQ;AACrC,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
|
|
16
|
+
// src/connect/browser.ts
|
|
17
|
+
var browser_exports = {};
|
|
18
|
+
module.exports = __toCommonJS(browser_exports);
|
|
19
|
+
throw new Error(
|
|
20
|
+
"@synap-core/sdk/connect is server-only: it holds the pod's tokens and the app secret. Import it from route handlers, proxy.ts or server code, never from a client component."
|
|
21
|
+
);
|
|
22
|
+
//# sourceMappingURL=connect.browser.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connect/browser.ts"],"sourcesContent":["/**\n * What a BROWSER bundle gets for `@synap-core/sdk/connect` (the `browser`\n * export condition): no exports, and a throw on load. See ./guard.ts.\n */\nthrow new Error(\n \"@synap-core/sdk/connect is server-only: it holds the pod's tokens and the app secret. \" +\n \"Import it from route handlers, proxy.ts or server code, never from a client component.\",\n);\n\nexport {};\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;AAIA,MAAM,IAAI;AAAA,EACR;AAEF;","names":[]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// src/connect/browser.ts
|
|
2
|
+
throw new Error(
|
|
3
|
+
"@synap-core/sdk/connect is server-only: it holds the pod's tokens and the app secret. Import it from route handlers, proxy.ts or server code, never from a client component."
|
|
4
|
+
);
|
|
5
|
+
//# sourceMappingURL=connect.browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connect/browser.ts"],"sourcesContent":["/**\n * What a BROWSER bundle gets for `@synap-core/sdk/connect` (the `browser`\n * export condition): no exports, and a throw on load. See ./guard.ts.\n */\nthrow new Error(\n \"@synap-core/sdk/connect is server-only: it holds the pod's tokens and the app secret. \" +\n \"Import it from route handlers, proxy.ts or server code, never from a client component.\",\n);\n\nexport {};\n"],"mappings":";AAIA,MAAM,IAAI;AAAA,EACR;AAEF;","names":[]}
|