@cosmoops/connect-sdk 0.1.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.
Files changed (126) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +446 -0
  3. package/dist/cjs/cli/bin.js +16 -0
  4. package/dist/cjs/cli/init.js +179 -0
  5. package/dist/cjs/client/components/account-sessions.js +112 -0
  6. package/dist/cjs/client/components/apps-grid.js +46 -0
  7. package/dist/cjs/client/components/auth-buttons.js +42 -0
  8. package/dist/cjs/client/components/feature-gate.js +61 -0
  9. package/dist/cjs/client/components/link-button.js +24 -0
  10. package/dist/cjs/client/components/logo.js +27 -0
  11. package/dist/cjs/client/components/plan-pricing.js +166 -0
  12. package/dist/cjs/client/components/plans-page.js +39 -0
  13. package/dist/cjs/client/components/plans.js +212 -0
  14. package/dist/cjs/client/components/readiness.js +117 -0
  15. package/dist/cjs/client/components/subscribe-dialog.js +60 -0
  16. package/dist/cjs/client/components/subscription.js +189 -0
  17. package/dist/cjs/client/components/usage-meter.js +68 -0
  18. package/dist/cjs/client/components/user-menu.js +172 -0
  19. package/dist/cjs/client/index.js +99 -0
  20. package/dist/cjs/client/mark.js +16 -0
  21. package/dist/cjs/client/provider.js +259 -0
  22. package/dist/cjs/client/use-checkout.js +173 -0
  23. package/dist/cjs/errors.js +74 -0
  24. package/dist/cjs/format-relative-time.js +18 -0
  25. package/dist/cjs/helpers.js +30 -0
  26. package/dist/cjs/index.js +22 -0
  27. package/dist/cjs/next/index.js +311 -0
  28. package/dist/cjs/package.json +3 -0
  29. package/dist/cjs/server/client.js +114 -0
  30. package/dist/cjs/server/handler.js +333 -0
  31. package/dist/cjs/server/http.js +160 -0
  32. package/dist/cjs/server/index.js +18 -0
  33. package/dist/cjs/server/resources/access.js +28 -0
  34. package/dist/cjs/server/resources/ai.js +36 -0
  35. package/dist/cjs/server/resources/apps.js +30 -0
  36. package/dist/cjs/server/resources/billing.js +71 -0
  37. package/dist/cjs/server/resources/drive.js +39 -0
  38. package/dist/cjs/server/resources/entitlements.js +49 -0
  39. package/dist/cjs/server/resources/identity.js +194 -0
  40. package/dist/cjs/server/resources/logs.js +41 -0
  41. package/dist/cjs/server/resources/otp.js +29 -0
  42. package/dist/cjs/server/resources/sessions.js +82 -0
  43. package/dist/cjs/types.js +15 -0
  44. package/dist/esm/cli/bin.js +13 -0
  45. package/dist/esm/cli/init.js +174 -0
  46. package/dist/esm/client/components/account-sessions.js +109 -0
  47. package/dist/esm/client/components/apps-grid.js +43 -0
  48. package/dist/esm/client/components/auth-buttons.js +38 -0
  49. package/dist/esm/client/components/feature-gate.js +57 -0
  50. package/dist/esm/client/components/link-button.js +21 -0
  51. package/dist/esm/client/components/logo.js +24 -0
  52. package/dist/esm/client/components/plan-pricing.js +156 -0
  53. package/dist/esm/client/components/plans-page.js +36 -0
  54. package/dist/esm/client/components/plans.js +209 -0
  55. package/dist/esm/client/components/readiness.js +113 -0
  56. package/dist/esm/client/components/subscribe-dialog.js +57 -0
  57. package/dist/esm/client/components/subscription.js +181 -0
  58. package/dist/esm/client/components/usage-meter.js +65 -0
  59. package/dist/esm/client/components/user-menu.js +164 -0
  60. package/dist/esm/client/index.js +47 -0
  61. package/dist/esm/client/mark.js +13 -0
  62. package/dist/esm/client/provider.js +247 -0
  63. package/dist/esm/client/use-checkout.js +168 -0
  64. package/dist/esm/errors.js +69 -0
  65. package/dist/esm/format-relative-time.js +15 -0
  66. package/dist/esm/helpers.js +26 -0
  67. package/dist/esm/index.js +15 -0
  68. package/dist/esm/next/index.js +279 -0
  69. package/dist/esm/package.json +3 -0
  70. package/dist/esm/server/client.js +110 -0
  71. package/dist/esm/server/handler.js +330 -0
  72. package/dist/esm/server/http.js +156 -0
  73. package/dist/esm/server/index.js +10 -0
  74. package/dist/esm/server/resources/access.js +24 -0
  75. package/dist/esm/server/resources/ai.js +32 -0
  76. package/dist/esm/server/resources/apps.js +26 -0
  77. package/dist/esm/server/resources/billing.js +67 -0
  78. package/dist/esm/server/resources/drive.js +35 -0
  79. package/dist/esm/server/resources/entitlements.js +45 -0
  80. package/dist/esm/server/resources/identity.js +189 -0
  81. package/dist/esm/server/resources/logs.js +37 -0
  82. package/dist/esm/server/resources/otp.js +25 -0
  83. package/dist/esm/server/resources/sessions.js +78 -0
  84. package/dist/esm/types.js +14 -0
  85. package/dist/theme.css +84 -0
  86. package/dist/types/cli/bin.d.ts +1 -0
  87. package/dist/types/cli/init.d.ts +34 -0
  88. package/dist/types/client/components/account-sessions.d.ts +25 -0
  89. package/dist/types/client/components/apps-grid.d.ts +20 -0
  90. package/dist/types/client/components/auth-buttons.d.ts +31 -0
  91. package/dist/types/client/components/feature-gate.d.ts +57 -0
  92. package/dist/types/client/components/link-button.d.ts +20 -0
  93. package/dist/types/client/components/logo.d.ts +35 -0
  94. package/dist/types/client/components/plan-pricing.d.ts +96 -0
  95. package/dist/types/client/components/plans-page.d.ts +36 -0
  96. package/dist/types/client/components/plans.d.ts +34 -0
  97. package/dist/types/client/components/readiness.d.ts +52 -0
  98. package/dist/types/client/components/subscribe-dialog.d.ts +34 -0
  99. package/dist/types/client/components/subscription.d.ts +123 -0
  100. package/dist/types/client/components/usage-meter.d.ts +24 -0
  101. package/dist/types/client/components/user-menu.d.ts +86 -0
  102. package/dist/types/client/index.d.ts +47 -0
  103. package/dist/types/client/mark.d.ts +11 -0
  104. package/dist/types/client/provider.d.ts +209 -0
  105. package/dist/types/client/use-checkout.d.ts +93 -0
  106. package/dist/types/errors.d.ts +62 -0
  107. package/dist/types/format-relative-time.d.ts +2 -0
  108. package/dist/types/helpers.d.ts +33 -0
  109. package/dist/types/index.d.ts +17 -0
  110. package/dist/types/next/index.d.ts +136 -0
  111. package/dist/types/server/client.d.ts +83 -0
  112. package/dist/types/server/handler.d.ts +67 -0
  113. package/dist/types/server/http.d.ts +52 -0
  114. package/dist/types/server/index.d.ts +15 -0
  115. package/dist/types/server/resources/access.d.ts +17 -0
  116. package/dist/types/server/resources/ai.d.ts +24 -0
  117. package/dist/types/server/resources/apps.d.ts +12 -0
  118. package/dist/types/server/resources/billing.d.ts +41 -0
  119. package/dist/types/server/resources/drive.d.ts +26 -0
  120. package/dist/types/server/resources/entitlements.d.ts +31 -0
  121. package/dist/types/server/resources/identity.d.ts +76 -0
  122. package/dist/types/server/resources/logs.d.ts +20 -0
  123. package/dist/types/server/resources/otp.d.ts +17 -0
  124. package/dist/types/server/resources/sessions.d.ts +37 -0
  125. package/dist/types/types.d.ts +434 -0
  126. package/package.json +102 -0
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AiResource = void 0;
4
+ const http_js_1 = require("../http.js");
5
+ /** RAG chat and structured extraction against an organization's data. */
6
+ class AiResource {
7
+ transport;
8
+ constructor(transport) {
9
+ this.transport = transport;
10
+ }
11
+ /**
12
+ * RAG-grounded chat. Returns the raw streaming `Response` rather than a
13
+ * parsed body - this endpoint answers with server-sent events, and buffering
14
+ * it here would throw away the only reason to stream. Pipe
15
+ * `response.body` onward, or hand it to the AI SDK's own reader.
16
+ */
17
+ chat(input, options = {}) {
18
+ return (0, http_js_1.request)(this.transport, {
19
+ method: "POST",
20
+ path: "/ai/chat",
21
+ json: input,
22
+ raw: true,
23
+ signal: options.signal,
24
+ });
25
+ }
26
+ /** Extract structured JSON from a file or image URL, against a platform extraction schema referenced by id. */
27
+ async extract(input) {
28
+ const result = await (0, http_js_1.request)(this.transport, {
29
+ method: "POST",
30
+ path: "/ai/extract",
31
+ json: input,
32
+ });
33
+ return result.data;
34
+ }
35
+ }
36
+ exports.AiResource = AiResource;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppsResource = void 0;
4
+ const http_js_1 = require("../http.js");
5
+ /** The services an organization can open - the data behind a launcher grid. */
6
+ class AppsResource {
7
+ transport;
8
+ constructor(transport) {
9
+ this.transport = transport;
10
+ }
11
+ /**
12
+ * Launchable apps only, so a grid tile is never a dead end. The calling
13
+ * service is included and flagged `isCurrent` rather than filtered out.
14
+ */
15
+ async list(organizationId) {
16
+ const result = await (0, http_js_1.request)(this.transport, {
17
+ method: "GET",
18
+ path: "/apps",
19
+ query: { organizationId },
20
+ });
21
+ // `?? []` for the same reason `billing.listPlans` has it: this is a
22
+ // collection every caller immediately maps or measures, and a body without
23
+ // the key would hand them `undefined`. Worse, the bridge re-wraps this as
24
+ // `{ apps: <value> }` and `JSON.stringify` drops an `undefined` member
25
+ // entirely - so one missing key upstream reaches the browser as `{}` and
26
+ // crashes on `.length`, past every `response.ok` check in between.
27
+ return result.apps ?? [];
28
+ }
29
+ }
30
+ exports.AppsResource = AppsResource;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BillingResource = void 0;
4
+ const http_js_1 = require("../http.js");
5
+ const helpers_js_1 = require("../../helpers.js");
6
+ /** The plan catalog you sell, and metering usage back against a subscriber's add-on balance. */
7
+ class BillingResource {
8
+ transport;
9
+ constructor(transport) {
10
+ this.transport = transport;
11
+ }
12
+ /**
13
+ * Your service's active plans - render live pricing instead of hardcoding it.
14
+ *
15
+ * `features` is normalised on the way out: Connect used to send bare keys
16
+ * and now sends `{ key, name, description }`, and this SDK is versioned
17
+ * independently of the instance it talks to. Without the coercion a partner
18
+ * pointed at a Connect that has not upgraded yet renders a feature list of
19
+ * blanks - which is exactly what happened. See `normalizePlanFeatures`.
20
+ */
21
+ async listPlans() {
22
+ const response = await (0, http_js_1.request)(this.transport, { method: "GET", path: "/plans" });
23
+ return {
24
+ plans: (response.plans ?? []).map((plan) => ({
25
+ ...plan,
26
+ features: (0, helpers_js_1.normalizePlanFeatures)(plan.features),
27
+ })),
28
+ };
29
+ }
30
+ /**
31
+ * What is left of every consumable add-on on an organization's
32
+ * subscription - the read half of `consumeAddon`.
33
+ *
34
+ * Needs a key scoped to `addons:read`, which is separate from
35
+ * `addons:consume` on purpose: a key that only renders a usage meter should
36
+ * not also be able to spend the balance down.
37
+ *
38
+ * Returns an empty `addons` array rather than throwing when there is
39
+ * nothing to meter, so a caller can render it unconditionally.
40
+ */
41
+ async getAddonUsage(organizationId) {
42
+ const response = await (0, http_js_1.request)(this.transport, {
43
+ method: "GET",
44
+ path: `/addons/organizations/${(0, http_js_1.pathSegment)(organizationId, "organizationId")}`,
45
+ });
46
+ // Normalised, not passed through: the promise above resolves for any 2xx,
47
+ // and `ConnectUsageMeter` reads `addons` straight off it. A body without
48
+ // the key is not an error the meter can see - `data` is truthy, so its
49
+ // `!data` guard passes - and it crashes on `.length` instead of doing what
50
+ // it promises for an empty list, which is to render nothing.
51
+ return { ...response, addons: response.addons ?? [], resetsAt: response.resetsAt ?? null };
52
+ }
53
+ /**
54
+ * Debit a consumable add-on's per-cycle balance on behalf of an
55
+ * organization's users. Call it at whatever cadence your usage happens.
56
+ *
57
+ * An `Idempotency-Key` is always sent, generated per call when you do not
58
+ * supply one: a balance cannot be un-spent, so a retried consume without a
59
+ * key double-debits. Pass your own when the natural unit of work is
60
+ * something you can name (a job id, a message id) and might submit twice.
61
+ */
62
+ consumeAddon(input, options = {}) {
63
+ return (0, http_js_1.request)(this.transport, {
64
+ method: "POST",
65
+ path: "/addons/consume",
66
+ json: input,
67
+ headers: { "idempotency-key": options.idempotencyKey ?? crypto.randomUUID() },
68
+ });
69
+ }
70
+ }
71
+ exports.BillingResource = BillingResource;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DriveResource = void 0;
4
+ const http_js_1 = require("../http.js");
5
+ /** Files in an organization's Drive - upload, and signed URLs to read back. */
6
+ class DriveResource {
7
+ transport;
8
+ constructor(transport) {
9
+ this.transport = transport;
10
+ }
11
+ upload(input) {
12
+ const form = new FormData();
13
+ const fileName = input.fileName ?? input.file.name ?? "upload";
14
+ form.set("file", input.file, fileName);
15
+ form.set("organizationId", input.organizationId);
16
+ if (input.parentId)
17
+ form.set("parentId", input.parentId);
18
+ return (0, http_js_1.request)(this.transport, { method: "POST", path: "/drive/files", multipart: form });
19
+ }
20
+ /** A signed URL for the original bytes. Short-lived - fetch it when you need it, do not store it. */
21
+ async getFileUrl(fileId, input) {
22
+ const result = await (0, http_js_1.request)(this.transport, {
23
+ method: "GET",
24
+ path: `/drive/files/${(0, http_js_1.pathSegment)(fileId, "fileId")}/url`,
25
+ query: { organizationId: input.organizationId },
26
+ });
27
+ return result.url;
28
+ }
29
+ /** A signed URL for the thumbnail. Same lifetime caveat as `getFileUrl`. */
30
+ async getPreviewUrl(fileId, input) {
31
+ const result = await (0, http_js_1.request)(this.transport, {
32
+ method: "GET",
33
+ path: `/drive/files/${(0, http_js_1.pathSegment)(fileId, "fileId")}/preview-url`,
34
+ query: { organizationId: input.organizationId, size: input.size },
35
+ });
36
+ return result.url;
37
+ }
38
+ }
39
+ exports.DriveResource = DriveResource;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntitlementsResource = void 0;
4
+ const http_js_1 = require("../http.js");
5
+ /**
6
+ * The two checks worth making on every request: may this workspace be here at
7
+ * all, and what is it paying for.
8
+ */
9
+ class EntitlementsResource {
10
+ transport;
11
+ constructor(transport) {
12
+ this.transport = transport;
13
+ }
14
+ /**
15
+ * Every gate Connect owns between an organization and your service -
16
+ * subscription, KYC/KYB, a chargeable payment method, seat headroom -
17
+ * resolved in one call.
18
+ *
19
+ * Blocked users are the point: each blocker carries a `resolveUrl` into the
20
+ * Connect screen that fixes it, so you can route them somewhere useful
21
+ * instead of refusing them with nowhere to go.
22
+ */
23
+ getReadiness(organizationId) {
24
+ return (0, http_js_1.request)(this.transport, {
25
+ method: "GET",
26
+ path: `/onboarding/organizations/${(0, http_js_1.pathSegment)(organizationId, "organizationId")}`,
27
+ });
28
+ }
29
+ /** The live plan and subscription, including `plan.features`. */
30
+ getSubscription(organizationId) {
31
+ return (0, http_js_1.request)(this.transport, {
32
+ method: "GET",
33
+ path: `/subscriptions/organizations/${(0, http_js_1.pathSegment)(organizationId, "organizationId")}`,
34
+ });
35
+ }
36
+ /**
37
+ * Whether an active subscription unlocks a plan feature - the check that
38
+ * gates a plan-restricted module.
39
+ *
40
+ * Deliberately `false` rather than a throw when there is no subscription at
41
+ * all: "not entitled" is the same answer either way, and a feature gate
42
+ * should not need a try/catch around it.
43
+ */
44
+ async hasFeature(organizationId, feature) {
45
+ const result = await this.getSubscription(organizationId);
46
+ return result.hasActiveSubscription && (result.plan?.features.includes(feature) ?? false);
47
+ }
48
+ }
49
+ exports.EntitlementsResource = EntitlementsResource;
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IdentityResource = void 0;
4
+ exports.createState = createState;
5
+ const errors_js_1 = require("../../errors.js");
6
+ const http_js_1 = require("../http.js");
7
+ /** A URL-safe random string, suitable for `state` or a PKCE verifier. */
8
+ function createState(byteLength = 32) {
9
+ const bytes = new Uint8Array(byteLength);
10
+ crypto.getRandomValues(bytes);
11
+ return base64UrlEncode(bytes);
12
+ }
13
+ function base64UrlEncode(bytes) {
14
+ let binary = "";
15
+ for (const byte of bytes)
16
+ binary += String.fromCharCode(byte);
17
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
18
+ }
19
+ function base64UrlDecode(value) {
20
+ const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
21
+ const binary = atob(padded);
22
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
23
+ }
24
+ /**
25
+ * "Login with Connect" - the OAuth2 handshake, and the session that outlives
26
+ * it.
27
+ *
28
+ * The whole flow is five calls: `authorizeUrl` to send them out,
29
+ * `exchangeCode` when they come back, `verifyIdToken` once, then `getUser` on
30
+ * each later request and `logout` at the end.
31
+ */
32
+ class IdentityResource {
33
+ transport;
34
+ clientId;
35
+ appBaseUrl;
36
+ constructor(transport, clientId, appBaseUrl) {
37
+ this.transport = transport;
38
+ this.clientId = clientId;
39
+ this.appBaseUrl = appBaseUrl;
40
+ }
41
+ /** Step 1 - the URL to send the user's browser to. */
42
+ authorizeUrl(input) {
43
+ const url = new URL("/sso/authorize", this.appBaseUrl);
44
+ url.searchParams.set("client_id", this.requireClientId());
45
+ url.searchParams.set("redirect_uri", input.redirectUri);
46
+ url.searchParams.set("response_type", "code");
47
+ url.searchParams.set("scope", "openid");
48
+ url.searchParams.set("state", input.state);
49
+ if (input.prompt)
50
+ url.searchParams.set("prompt", input.prompt);
51
+ if (input.codeChallenge) {
52
+ url.searchParams.set("code_challenge", input.codeChallenge);
53
+ url.searchParams.set("code_challenge_method", "S256");
54
+ }
55
+ return url.toString();
56
+ }
57
+ /** Step 2 - trade the one-time `code` for a session. Server-side only. */
58
+ async exchangeCode(input) {
59
+ const wire = await this.post("/sso/token", {
60
+ grant_type: "authorization_code",
61
+ code: input.code,
62
+ redirect_uri: input.redirectUri,
63
+ code_verifier: input.codeVerifier,
64
+ });
65
+ return {
66
+ idToken: wire.id_token,
67
+ tokenType: wire.token_type,
68
+ expiresIn: wire.expires_in,
69
+ sessionToken: wire.session_token,
70
+ sessionTokenExpiresIn: wire.session_token_expires_in,
71
+ user: wire.user,
72
+ subscription: wire.subscription,
73
+ serviceRole: wire.serviceRole,
74
+ };
75
+ }
76
+ /**
77
+ * Step 3 - verify the `id_token` once, then discard it.
78
+ *
79
+ * HS256 over `header.payload`, keyed with your API key, plus the `exp` and
80
+ * `aud` checks. There is no JWKS to fetch: Connect stores only your key's
81
+ * sha256 hash, so the token is signed with the shared secret rather than an
82
+ * asymmetric key. Signature comparison goes through WebCrypto's own
83
+ * `verify`, which is why this is async - hand-rolling the compare is the
84
+ * step integrations most often get wrong.
85
+ */
86
+ async verifyIdToken(idToken) {
87
+ // Guarded before `.split`, because the failure this catches is a caller
88
+ // passing something that is not a token at all. Left ungurded it threw a
89
+ // raw TypeError, which is not a `ConnectError`, so every layer above
90
+ // reported it as an unexplained internal error.
91
+ if (typeof idToken !== "string" || idToken.length === 0) {
92
+ throw this.tokenError("id_token is missing");
93
+ }
94
+ const parts = idToken.split(".");
95
+ const [header, payload, signature] = parts;
96
+ if (parts.length !== 3 || !header || !payload || !signature) {
97
+ throw this.tokenError("id_token is not a well-formed JWT");
98
+ }
99
+ const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(this.transport.apiKey), { name: "HMAC", hash: "SHA-256" }, false, ["verify"]);
100
+ // Decoded before `verify`, and guarded for the same reason the emptiness
101
+ // check above is: `atob` throws a raw `InvalidCharacterError` on a
102
+ // signature that is not base64url, which is not a `ConnectError`, so a
103
+ // caller verifying a token it did not mint gets an unexplained 500 where
104
+ // it should get a clean 401.
105
+ let signatureBytes;
106
+ try {
107
+ signatureBytes = base64UrlDecode(signature);
108
+ }
109
+ catch {
110
+ throw this.tokenError("id_token signature is not valid base64url");
111
+ }
112
+ const valid = await crypto.subtle.verify("HMAC", key, signatureBytes, new TextEncoder().encode(`${header}.${payload}`));
113
+ if (!valid)
114
+ throw this.tokenError("id_token signature does not match this API key");
115
+ let claims;
116
+ try {
117
+ claims = JSON.parse(new TextDecoder().decode(base64UrlDecode(payload)));
118
+ }
119
+ catch {
120
+ throw this.tokenError("id_token payload is not valid JSON");
121
+ }
122
+ if (typeof claims.exp !== "number" || claims.exp * 1000 <= Date.now()) {
123
+ throw this.tokenError("id_token has expired");
124
+ }
125
+ if (claims.aud !== this.requireClientId()) {
126
+ throw this.tokenError("id_token was issued for a different client_id");
127
+ }
128
+ return claims;
129
+ }
130
+ /**
131
+ * Step 4 - who is signed in, right now. Same blocks as `exchangeCode`,
132
+ * re-resolved live, so a role change or a lapsed subscription shows up here
133
+ * without another sign-in.
134
+ *
135
+ * Any `ConnectError` with `isSessionExpired` means send them back through
136
+ * `authorizeUrl()`.
137
+ */
138
+ async getUser(sessionToken) {
139
+ const wire = await this.post("/sso/user", { session_token: sessionToken });
140
+ return {
141
+ user: wire.user,
142
+ subscription: wire.subscription,
143
+ serviceRole: wire.serviceRole,
144
+ sessionExpiresAt: wire.session_expires_at,
145
+ };
146
+ }
147
+ /**
148
+ * Step 5 - end the sign-in immediately rather than leaving it valid for the
149
+ * rest of its ~15-day life. Discard your own copy of `sessionToken` too;
150
+ * this does not do that for you.
151
+ *
152
+ * `scope` defaults to `"device"`: this sign-in, every other service signed
153
+ * into from the same browser, and Connect's own session on that one device.
154
+ * `"all"` ends every session on every device - an explicit "sign out
155
+ * everywhere" action only.
156
+ *
157
+ * Always succeeds. An unknown or already-revoked token is an
158
+ * indistinguishable no-op, so there is nothing to branch on.
159
+ */
160
+ async logout(sessionToken, options = {}) {
161
+ await this.post("/sso/logout", {
162
+ session_token: sessionToken,
163
+ scope: options.scope,
164
+ });
165
+ }
166
+ post(path, form) {
167
+ const spec = {
168
+ method: "POST",
169
+ path,
170
+ auth: "none",
171
+ form: {
172
+ ...form,
173
+ client_id: this.requireClientId(),
174
+ client_secret: this.transport.apiKey,
175
+ },
176
+ };
177
+ return (0, http_js_1.request)(this.transport, spec);
178
+ }
179
+ requireClientId() {
180
+ if (!this.clientId) {
181
+ throw new errors_js_1.ConnectError({
182
+ status: 0,
183
+ code: "missing_client_id",
184
+ message: "identity.* needs `clientId`. Pass it to `new ConnectClient({ clientId, apiKey })` - it is shown in this service's Setup tab once SSO is enabled.",
185
+ endpoint: "identity",
186
+ });
187
+ }
188
+ return this.clientId;
189
+ }
190
+ tokenError(message) {
191
+ return new errors_js_1.ConnectError({ status: 401, code: "invalid_token", message, endpoint: "verifyIdToken" });
192
+ }
193
+ }
194
+ exports.IdentityResource = IdentityResource;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LogsResource = void 0;
4
+ const http_js_1 = require("../http.js");
5
+ /** Push feature events into the Console's audit log, filterable to your service. */
6
+ class LogsResource {
7
+ transport;
8
+ constructor(transport) {
9
+ this.transport = transport;
10
+ }
11
+ /**
12
+ * Never throws. A logging call that takes down the code path it was
13
+ * observing is worse than a lost line, so a failure here is swallowed and
14
+ * reported through the return value.
15
+ *
16
+ * Pass `sessionToken` to attribute the event to the end user it belongs to.
17
+ * Both scopes on the stored entry come from something verified - the service
18
+ * from your API key, the user from that token, which Connect checks was
19
+ * minted for your service before attributing anything. `attributed` in the
20
+ * result says whether it resolved; a `false` there still means the line was
21
+ * stored, just without a user on it.
22
+ */
23
+ async write(input) {
24
+ const timestamp = input.timestamp instanceof Date ? input.timestamp.toISOString() : input.timestamp;
25
+ try {
26
+ const result = await (0, http_js_1.request)(this.transport, {
27
+ method: "POST",
28
+ path: "/logs",
29
+ json: { ...input, timestamp },
30
+ });
31
+ // `?? false` covers a Connect old enough to predate the field - an
32
+ // absent `attributed` means the server had no attribution to report,
33
+ // which is exactly what `false` says.
34
+ return { delivered: true, attributed: result?.attributed ?? false };
35
+ }
36
+ catch {
37
+ return { delivered: false, attributed: false };
38
+ }
39
+ }
40
+ }
41
+ exports.LogsResource = LogsResource;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OtpResource = void 0;
4
+ const http_js_1 = require("../http.js");
5
+ /** WhatsApp/SMS phone verification. */
6
+ class OtpResource {
7
+ transport;
8
+ constructor(transport) {
9
+ this.transport = transport;
10
+ }
11
+ /**
12
+ * Send a code. As with `billing.consumeAddon`, an `Idempotency-Key` always
13
+ * goes along - a retried send would otherwise deliver a second message and
14
+ * invalidate the first code the user is looking at.
15
+ */
16
+ send(input, options = {}) {
17
+ return (0, http_js_1.request)(this.transport, {
18
+ method: "POST",
19
+ path: "/otp/send",
20
+ json: input,
21
+ headers: { "idempotency-key": options.idempotencyKey ?? crypto.randomUUID() },
22
+ });
23
+ }
24
+ /** Check a 6-digit code against a `verificationId`. */
25
+ verify(input) {
26
+ return (0, http_js_1.request)(this.transport, { method: "POST", path: "/otp/verify", json: input });
27
+ }
28
+ }
29
+ exports.OtpResource = OtpResource;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SessionsResource = void 0;
4
+ const errors_js_1 = require("../../errors.js");
5
+ const http_js_1 = require("../http.js");
6
+ /**
7
+ * This Connect account's own login sessions - device, approximate location,
8
+ * timestamps - the same list Connect's own account settings page shows the
9
+ * user themselves.
10
+ *
11
+ * **Account-wide, not scoped to this service.** A Connect session is one
12
+ * browser's login to Connect, and it fans out to every partner service's own
13
+ * `sessionToken` - Connect has no per-service session concept to filter by.
14
+ * `list()` returns every session on the account, including ones that
15
+ * originated from a completely different, unrelated service. Requires the
16
+ * `sessions:manage` scope on your API key - unlike `identity`'s base `sso`
17
+ * capability, this is never granted to a zero-scope legacy key by default.
18
+ *
19
+ * Authenticated like `identity.*` - `client_id`+`client_secret`, never a
20
+ * Bearer key - because every call here is really "resolve identity from a
21
+ * `sessionToken`", the same shape `identity.getUser`/`identity.logout` are.
22
+ */
23
+ class SessionsResource {
24
+ transport;
25
+ clientId;
26
+ constructor(transport, clientId) {
27
+ this.transport = transport;
28
+ this.clientId = clientId;
29
+ }
30
+ /** Every active session on the account behind `sessionToken`. `isCurrent` marks the one it was minted from. */
31
+ async list(sessionToken) {
32
+ const result = await this.post("/sso/sessions", {
33
+ session_token: sessionToken,
34
+ });
35
+ // See `AppsResource.list` - a collection unwrap has to survive a body
36
+ // that omits the key, or it becomes a browser-side crash two hops away.
37
+ return result.items ?? [];
38
+ }
39
+ /**
40
+ * Ends one specific session by id - not necessarily the one `sessionToken`
41
+ * itself was minted from. Cascades to every partner-service sign-in that
42
+ * originated from that same browser, same as Connect's own "sign out this
43
+ * device".
44
+ */
45
+ async revoke(sessionToken, sessionId) {
46
+ await this.post("/sso/sessions/revoke", {
47
+ session_token: sessionToken,
48
+ sessionId,
49
+ });
50
+ }
51
+ /** "Sign out everywhere else" - keeps the session `sessionToken` was minted from, ends every other one. */
52
+ async revokeOthers(sessionToken) {
53
+ return this.post("/sso/sessions/revoke-others", {
54
+ session_token: sessionToken,
55
+ });
56
+ }
57
+ post(path, form) {
58
+ const spec = {
59
+ method: "POST",
60
+ path,
61
+ auth: "none",
62
+ form: {
63
+ ...form,
64
+ client_id: this.requireClientId(),
65
+ client_secret: this.transport.apiKey,
66
+ },
67
+ };
68
+ return (0, http_js_1.request)(this.transport, spec);
69
+ }
70
+ requireClientId() {
71
+ if (!this.clientId) {
72
+ throw new errors_js_1.ConnectError({
73
+ status: 0,
74
+ code: "missing_client_id",
75
+ message: "sessions.* needs `clientId`. Pass it to `new ConnectClient({ clientId, apiKey })` - it is shown in this service's Setup tab once SSO is enabled.",
76
+ endpoint: "sessions",
77
+ });
78
+ }
79
+ return this.clientId;
80
+ }
81
+ }
82
+ exports.SessionsResource = SessionsResource;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * Wire types for the `/api/v1/service/*` surface - the service-partner API,
4
+ * and the only thing this SDK covers.
5
+ *
6
+ * Hand-written rather than generated so the published package carries no
7
+ * dependency on Connect's internals. `src/conformance.test.ts` type-checks
8
+ * these against the server's own DTOs, so drift fails the build here instead
9
+ * of surfacing as a runtime surprise in a partner's integration.
10
+ *
11
+ * Dates cross the wire as ISO 8601 strings and are left that way - parsing
12
+ * them into `Date` would be a lossy guess about what the caller wants, and
13
+ * `new Date(value)` is right there.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The `connect-sdk` executable.
3
+ *
4
+ * A thin wrapper so `init.ts` stays importable and testable - a module that
5
+ * calls `process.exit` at import time cannot be unit tested, and this is the
6
+ * only file that does.
7
+ *
8
+ * The shebang is added by `scripts/finalize-dist.ts`: TypeScript strips leading
9
+ * comments it does not recognise as directives, and a `bin` without one is run
10
+ * by the shell rather than by Node.
11
+ */
12
+ import { main } from "./init.js";
13
+ process.exit(main(process.argv.slice(2)));