@cosmoops/connect-sdk 0.34.0 → 0.35.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/CHANGELOG.md CHANGED
@@ -3,6 +3,152 @@
3
3
  `@cosmoops/connect-sdk` follows semver, with the 0.x convention that **minor
4
4
  versions may carry breaking changes** and patch versions never do.
5
5
 
6
+ ## 0.35.0
7
+
8
+ One addition, `connectProviderProps()`, for a mistake both applications built
9
+ on this SDK shipped before anyone noticed.
10
+
11
+ ### Your server and your browser can address different Connect instances
12
+
13
+ `CONNECT_APP_URL` points the server at a non-production Connect. It is not
14
+ `NEXT_PUBLIC_`, so it never reaches the browser, and `ConnectProvider` defaults
15
+ `appUrl` to the public instance independently. Point one and not the other and
16
+ nothing fails: your API reads go to staging while every destination the browser
17
+ builds opens **production** - the user menu's Profile and Billing rows, the app
18
+ launcher's link for an unsubscribed application, `ConnectAccessGate`'s subscribe
19
+ target, and checkout.
20
+
21
+ The docs said to pass both. That was a rule to remember rather than a shape to
22
+ follow, and the sentence next to it - "the provider takes no props, which is the
23
+ point" - was about `clientId` and read as being about all of them.
24
+
25
+ ```tsx title="app/layout.tsx"
26
+ import { connectProviderProps } from "@cosmoops/connect-sdk/next";
27
+ import { ConnectProvider } from "@cosmoops/connect-sdk/client";
28
+
29
+ <ConnectProvider {...connectProviderProps()}>{children}</ConnectProvider>
30
+ ```
31
+
32
+ It returns `{ appUrl }`, resolved from `CONNECT_APP_URL` and falling back to the
33
+ production origin - so the line is correct in every environment and you write it
34
+ once. Props you pass yourself still win, as long as the spread comes first.
35
+
36
+ **Call it in the Server Component, not in your shell.** It reads a server-side
37
+ variable, so it ships from `/next`, which a Client Component cannot import -
38
+ attempting it fails the build naming `this-entry-is-server-only`. A shell that
39
+ needs `usePathname()` takes the result as a prop; `{ appUrl }` is a plain
40
+ serializable object, so that costs nothing.
41
+
42
+ Already have a helper that owns this origin - because you also build your own
43
+ links into Connect - keep using it. One function owning the read beats this one
44
+ sitting beside it.
45
+
46
+ Nothing changes if you were already passing `appUrl`, and nothing breaks if you
47
+ were not: the default is unchanged.
48
+
49
+ **This was never a security issue**, which is worth saying because it sounds
50
+ like one. `appUrl` is a navigation target you set from your own environment: it
51
+ carries no credential, no gate or permission check reads it, it is never
52
+ reachable from a URL or a request body, and redirect URIs are still matched
53
+ exactly server-side. The worst case is a real charge in the wrong instance, not
54
+ a way in.
55
+
56
+ ### The sidebar's nav rows are 36px, so a ten-route rail stops scrolling
57
+
58
+ `AppShell`'s rail is the only part of the shell that grows with *your* route
59
+ count, and `ConnectSidebarBody` is the only part that scrolls - so a rail that
60
+ overflows overflows there. Ten destinations at the old 40px came to 436px of
61
+ nav against 273px of fixed chrome (header, workspace switcher, secondary-nav
62
+ footer, account footer), which needs a 709px viewport to sit still; a 1366x768
63
+ screen has about 678px of it. The nav scrolled under its own footer on the most
64
+ common small laptop, which reads as a rail with destinations missing from it.
65
+
66
+ Nav rows - and every 20px-glyph row, so the secondary nav and the app launcher
67
+ too - are now **36px**, and the scrolling region's own vertical padding drops
68
+ from 8px to 4px a side. That is 56px back on a twelve-row integration, enough
69
+ that the same rail sits still. 36px is still past WCAG 2.5.8's 24px target
70
+ minimum, with the row's full width as the hit area.
71
+
72
+ **The two 32px-glyph rows stay at 40px** - the workspace switcher and the
73
+ account row. There are exactly two of them, neither scales with your nav, and
74
+ both need the height: a 32px avatar in a 36px row has 2px of air, and the
75
+ account row's label is two lines.
76
+
77
+ **Shrinking the icons would not have done it.** The glyph box is a flex
78
+ centring container for an icon *you* supply at your own size, so a smaller box
79
+ buys no vertical space and only pushes the icon off the 32px horizontal centre
80
+ the collapsed rail is built on - `RAIL_GUTTER + inset + glyph/2 = RAIL_COLLAPSED / 2`
81
+ is what lets the rail collapse without anything drifting sideways.
82
+
83
+ Nothing in your code changes. If your nav is long enough to still overflow, the
84
+ levers are the row count and `secondaryNav` - a rail is navigation, and past a
85
+ dozen rows a scrolling one is a sitemap.
86
+
87
+ ### `doctor` now catches it
88
+
89
+ `npx @cosmoops/connect-sdk doctor` reports a **note** when `CONNECT_APP_URL` is
90
+ set and the layout holding `<ConnectProvider>` passes no `appUrl`, with the fix
91
+ in the output. A note rather than a failure, so it cannot break a CI run: the
92
+ prop may be threaded from the layout into a `"use client"` shell, which reading
93
+ the layout cannot see.
94
+
95
+ ### Fixed: the User-Agent reported the wrong version
96
+
97
+ `SDK_VERSION` was left at `0.34.0` when 0.34.1 shipped, so every request this
98
+ package made identified itself as the previous release. It is in step again, and
99
+ the assertion that pins it to `package.json` is back to green - it had been
100
+ failing since that release, unnoticed because the publish guard runs the build
101
+ and the pack check rather than the test suite. Diagnostic only; nothing behaved
102
+ differently.
103
+
104
+ ## 0.34.1
105
+
106
+ Two tabs, or one abandoned sign-in and one fresh one, could not both work -
107
+ and when the older one came back it was bounced into a loop with nothing on
108
+ screen explaining it. If a customer has told you "I signed in and it showed me
109
+ the login page again", this is it.
110
+
111
+ ### One browser may have several sign-ins in flight
112
+
113
+ `createConnectHandler`'s state cookie held a single `state:next` pair, so the
114
+ second `GET /signin` overwrote the first. The older round trip then came back
115
+ to a cookie naming a state the handler had never heard of, was answered with
116
+ `connect_error=state_mismatch`, and - because that sends the visitor to a page
117
+ your own session gate guards - got silently re-signed-in by your app, landing
118
+ them back on Connect's sign-in screen.
119
+
120
+ The cookie now holds up to **five** pending round trips, newest first, and a
121
+ callback consumes only its own entry. So finishing in one tab leaves the other
122
+ able to finish too, and each returns to the destination *it* asked for rather
123
+ than whichever one was started last.
124
+
125
+ Nothing in your code changes. The cookie's internal format did change -
126
+ entries are `state:flags:next` with the destination percent-encoded, joined by
127
+ `|` - and the handler still reads the old single-pair format, so sign-ins in
128
+ flight across your deploy complete rather than failing.
129
+
130
+ ### Both retry bounds work for the first time
131
+
132
+ A `state_mismatch` had no bound at all, and `login_required`'s was
133
+ unreachable: the callback looked for `connect_retry=1` on its own URL, but
134
+ that parameter is set on `/signin`, and the `redirect_uri` handed to Connect is
135
+ a registered exact-match value that cannot carry extra query parameters. It
136
+ never arrived, so `login_required` could loop indefinitely too.
137
+
138
+ The marker now travels on the flow's own cookie entry, stamped at `/signin`
139
+ where it can actually be read. Each case gets one retry - the usual cause is a
140
+ lost entry for someone Connect has already signed in, which completes in a
141
+ single hop - and then surfaces `connect_error` instead of going round again.
142
+
143
+ ### Changed
144
+
145
+ - A `state_mismatch` no longer replays the state cookie's destination. With no
146
+ matching entry there is no destination that flow owns, so it falls back to
147
+ `defaultRedirect` rather than one belonging to a different round trip.
148
+ - A `prompt=none` probe whose state no longer matches still returns quietly to
149
+ `next`. The silent check now happens before the state check, so the new
150
+ retry can never escalate a silent probe into a visible sign-in.
151
+
6
152
  ## 0.34.0
7
153
 
8
154
  Getting a blocked customer unblocked, and getting them back afterwards - plus
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  "use strict";
2
3
  /**
3
4
  * `npx @cosmoops/connect-sdk doctor` - checks a wired-up project for the
@@ -84,14 +85,14 @@ function checkBridge(appDir, root) {
84
85
  }
85
86
  return { status: "pass", title: `Bridge route handler (${(0, node_path_1.relative)(root, bridge)})` };
86
87
  }
87
- function checkProvider(appDir, root) {
88
+ function checkProvider(appDir, root, env) {
88
89
  const layouts = walk(appDir, ["layout.tsx", "layout.jsx"]);
89
90
  const hosting = layouts.filter((path) => /<ConnectProvider/.test(read(path)));
90
91
  if (hosting.length === 0) {
91
92
  return {
92
93
  status: "fail",
93
94
  title: "ConnectProvider in a layout",
94
- detail: 'Nothing renders <ConnectProvider>. Every client hook and component below it throws without one. In app/layout.tsx:\n import { ConnectProvider } from "@cosmoops/connect-sdk/client";\n <ConnectProvider>{children}</ConnectProvider>',
95
+ detail: 'Nothing renders <ConnectProvider>. Every client hook and component below it throws without one. In app/layout.tsx:\n import { connectProviderProps } from "@cosmoops/connect-sdk/next";\n import { ConnectProvider } from "@cosmoops/connect-sdk/client";\n <ConnectProvider {...connectProviderProps()}>{children}</ConnectProvider>',
95
96
  };
96
97
  }
97
98
  // A nested provider re-fetches the session and keeps a second cache that the
@@ -104,8 +105,42 @@ function checkProvider(appDir, root) {
104
105
  detail: `Found in ${hosting.length} layouts (${hosting.map((path) => (0, node_path_1.relative)(root, path)).join(", ")}). Nested providers keep separate caches, so a mutation in one is invisible to the other. Keep the outermost one only.`,
105
106
  };
106
107
  }
108
+ /*
109
+ * The split integration, and the reason this check exists at all.
110
+ *
111
+ * `CONNECT_APP_URL` points the *server* at a non-production Connect. It is
112
+ * not `NEXT_PUBLIC_`, so `ConnectProvider` cannot read it and defaults
113
+ * `appUrl` to production independently - and nothing fails. The API reads go
114
+ * to the configured instance while every destination the browser builds
115
+ * opens production: the user menu's Profile and Billing rows, the launcher's
116
+ * link for an unsubscribed application, the access gate's subscribe target,
117
+ * and checkout. Both applications built on this SDK shipped exactly that.
118
+ *
119
+ * `warn` rather than `fail`: the prop may be threaded from a parent Server
120
+ * Component into a `"use client"` shell, which is the documented shape for a
121
+ * shell needing `usePathname()` - and that assignment is not visible in the
122
+ * layout this reads. So this cannot tell from the outside, which is what
123
+ * `warn` means here.
124
+ */
125
+ const pointedElsewhere = /^[ \t]*CONNECT_APP_URL[ \t]*=[ \t]*\S/m.test(env);
126
+ const carriesOrigin = /appUrl|connectProviderProps/.test(read(hosting[0]));
127
+ if (pointedElsewhere && !carriesOrigin) {
128
+ return {
129
+ status: "warn",
130
+ title: "ConnectProvider in a layout",
131
+ detail: `CONNECT_APP_URL is set, but ${(0, node_path_1.relative)(root, hosting[0])} passes no appUrl - so your server talks to that instance while every link the browser builds opens production Connect. Spread it in:\n <ConnectProvider {...connectProviderProps()}> // from "@cosmoops/connect-sdk/next"\n Already threading it into a "use client" shell? Then this is fine and you can ignore it.`,
132
+ };
133
+ }
107
134
  return { status: "pass", title: `ConnectProvider in a layout (${(0, node_path_1.relative)(root, hosting[0])})` };
108
135
  }
136
+ /** The env files `checkEnv` reads, as one string, shared with `checkProvider`. */
137
+ function envText(root) {
138
+ return [".env.local", ".env.development.local", ".env"]
139
+ .map((name) => (0, node_path_1.join)(root, name))
140
+ .filter(node_fs_1.existsSync)
141
+ .map(read)
142
+ .join("\n");
143
+ }
109
144
  /**
110
145
  * Order is the whole check.
111
146
  *
@@ -146,8 +181,7 @@ function checkTheme(root) {
146
181
  * loaded it - exactly the shell `next dev` will inherit.
147
182
  */
148
183
  function checkEnv(root) {
149
- const files = [".env.local", ".env.development.local", ".env"].map((name) => (0, node_path_1.join)(root, name));
150
- const combined = files.filter(node_fs_1.existsSync).map(read).join("\n");
184
+ const combined = envText(root);
151
185
  return ["CONNECT_API_KEY", "CONNECT_CLIENT_ID"].map((name) => {
152
186
  // Horizontal whitespace only. `\s*` also matches the newline, so a bare
153
187
  // `CONNECT_API_KEY=` was satisfied by the *next* line's value and an empty
@@ -191,7 +225,7 @@ function runDoctor(argv = []) {
191
225
  console.log(`[connect-sdk] Checking ${(0, node_path_1.relative)(process.cwd(), root) || root}\n`);
192
226
  const checks = [
193
227
  checkBridge(appDir, root),
194
- checkProvider(appDir, root),
228
+ checkProvider(appDir, root, envText(root)),
195
229
  checkTheme(root),
196
230
  ...checkEnv(root),
197
231
  checkRedirectUri(),
@@ -36,22 +36,51 @@ exports.RAIL_GUTTER = 12;
36
36
  * The row shape every sidebar control shares - nav items, the workspace tile,
37
37
  * the launcher, the user button.
38
38
  *
39
+ * **The height is not here.** It rides on `SIDEBAR_ROW_ICON` /
40
+ * `SIDEBAR_ROW_TILE` instead, because the two glyph sizes want different
41
+ * heights and only one of them is in the region that overflows - see those
42
+ * two constants.
43
+ *
39
44
  * `overflow-hidden` + `whitespace-nowrap` is what makes the collapse smooth:
40
45
  * the label is *clipped* by the shrinking rail rather than re-wrapped or
41
46
  * re-measured, so a width animation costs no reflow. Fading the label with
42
47
  * `SidebarLabel` on top of that hides the clip itself.
43
48
  */
44
- exports.SIDEBAR_ROW = "group/row relative flex h-10 w-full shrink-0 items-center justify-start gap-3 overflow-hidden whitespace-nowrap rounded-lg pe-2 text-sm font-normal transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus";
49
+ exports.SIDEBAR_ROW = "group/row relative flex w-full shrink-0 items-center justify-start gap-3 overflow-hidden whitespace-nowrap rounded-lg pe-2 text-sm font-normal transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus";
45
50
  /**
46
- * Leading inset for a row whose glyph is 20px wide (a nav icon, the launcher).
51
+ * Leading inset *and height* for a row whose glyph is 20px wide (a nav icon,
52
+ * the launcher, a secondary-nav row).
53
+ *
47
54
  * `10 + 10 = 20`, and `RAIL_GUTTER + 20 = 32`. See `RAIL_COLLAPSED`.
55
+ *
56
+ * **36px, not 40px, and the height belongs on this axis rather than on
57
+ * `SIDEBAR_ROW`.** The nav is the only region of the rail that grows with the
58
+ * host's route count, and `ConnectSidebarBody` is the only region that
59
+ * scrolls - so a rail that overflows overflows here. Ten destinations at 40px
60
+ * came to 436px of nav against 273px of fixed chrome (header, switcher, two
61
+ * footers), which needs a 709px viewport to sit still; a 1366x768 screen has
62
+ * about 678px of it. Four pixels a row is 48px back across the twelve rows a
63
+ * real integration draws, and 36px is still comfortably past the 24px
64
+ * WCAG 2.5.8 target minimum with the row's full width as the hit area.
65
+ *
66
+ * It is deliberately *not* a smaller glyph. The 20px box is a flex centring
67
+ * container for an icon the **host** supplies at its own size, so shrinking
68
+ * the box buys no vertical space and only pushes the icon off the 32px centre
69
+ * the collapsed rail is built on.
48
70
  */
49
- exports.SIDEBAR_ROW_ICON = "ps-[10px]";
71
+ exports.SIDEBAR_ROW_ICON = "h-9 ps-[10px]";
50
72
  /**
51
- * Leading inset for a row whose glyph is 32px wide (the avatar, the workspace
52
- * tile). `4 + 16 = 20`, landing on the same 32px centre as `SIDEBAR_ROW_ICON`.
73
+ * Leading inset and height for a row whose glyph is 32px wide (the avatar, the
74
+ * workspace tile). `4 + 16 = 20`, landing on the same 32px centre as
75
+ * `SIDEBAR_ROW_ICON`.
76
+ *
77
+ * **These stay at 40px.** There are exactly two of them and neither scales
78
+ * with the host's nav, so they are not what overflows - and both need the
79
+ * height: a 32px tile in a 36px row has 2px of air, and the account row's
80
+ * label is two lines (name over email, `leading-tight`) which measures ~32px
81
+ * on its own.
53
82
  */
54
- exports.SIDEBAR_ROW_TILE = "ps-[4px]";
83
+ exports.SIDEBAR_ROW_TILE = "h-10 ps-[4px]";
55
84
  /**
56
85
  * The class list for one sidebar row, in one place.
57
86
  *
@@ -116,7 +116,12 @@ function ConnectSidebarBody({ children }) {
116
116
  // zoom away over a region of text is a WCAG 1.4.4 failure, and nothing
117
117
  // here needs it suppressed. On the desktop rail, where no ancestor
118
118
  // restricts anything, the pair is equivalent to the default.
119
- (0, jsx_runtime_1.jsx)("div", { className: "flex min-h-0 flex-1 touch-pan-y touch-pinch-zoom flex-col gap-1 overflow-y-auto overflow-x-hidden px-3 py-2", children: children }));
119
+ // `py-1` rather than the `py-2` every other region carries: this is the
120
+ // one region that scrolls, so it is the first place to give up air when
121
+ // the nav is a row or two from overflowing. The rows paint their own
122
+ // rounded background, so the hairline above the footer is still clear of
123
+ // them. See `SIDEBAR_ROW_ICON` for the arithmetic this is part of.
124
+ (0, jsx_runtime_1.jsx)("div", { className: "flex min-h-0 flex-1 touch-pan-y touch-pinch-zoom flex-col gap-1 overflow-y-auto overflow-x-hidden px-3 py-1", children: children }));
120
125
  }
121
126
  /**
122
127
  * A pinned region at the foot of the rail - the secondary nav, the launcher,
@@ -127,6 +127,12 @@ function NextEvent({ detail, state, }) {
127
127
  * count against, so it renders nothing rather than a meter that is always
128
128
  * empty. Under `"per-seat"` going over is billable rather than blocked, so it
129
129
  * says what the extra costs instead of warning.
130
+ *
131
+ * `detail.seatCount` is a live count of the members holding a role for this
132
+ * application. It used to be the subscription's last-billed snapshot, which
133
+ * only ever moved on a `"per-seat"` plan - so a `"fixed"` single-seat plan
134
+ * read "0 of 1 seat used" permanently, including when its one seat was taken
135
+ * and Connect was already refusing the next assignment.
130
136
  */
131
137
  function ConnectSeatUsage({ plan, detail, className, }) {
132
138
  if (plan.seats.model === "unlimited")
@@ -36,6 +36,7 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.ConnectClient = exports.createConnectHandler = void 0;
38
38
  exports.connectClient = connectClient;
39
+ exports.connectProviderProps = connectProviderProps;
39
40
  exports.cookieSessionStore = cookieSessionStore;
40
41
  exports.createConnectHandlers = createConnectHandlers;
41
42
  exports.GET = handle;
@@ -68,8 +69,9 @@ const errors_js_1 = require("../errors.js");
68
69
  *
69
70
  * ```tsx
70
71
  * // app/layout.tsx
72
+ * import { connectProviderProps } from "@cosmoops/connect-sdk/next";
71
73
  * import { ConnectProvider } from "@cosmoops/connect-sdk/client";
72
- * <ConnectProvider>{children}</ConnectProvider>
74
+ * <ConnectProvider {...connectProviderProps()}>{children}</ConnectProvider>
73
75
  * ```
74
76
  *
75
77
  * No `next.config.ts` changes needed - configuration comes from the
@@ -86,6 +88,10 @@ const errors_js_1 = require("../errors.js");
86
88
  * `ConnectProvider` defaults `appUrl` to the production origin independently,
87
89
  * so an app pointed at another instance must pass that prop as well or its
88
90
  * portal links and its browser-built authorize URL still go to production.
91
+ * That is what the `connectProviderProps()` spread above is for, and why the
92
+ * layout is two imports rather than one - leave it off and the server half and
93
+ * the browser half of the same integration address different instances, with
94
+ * nothing failing to say so. See that function's own docs.
89
95
  *
90
96
  * Register `{your origin}/api/connect/callback` as a redirect URI in the
91
97
  * application's Connect client section. That is the only step this cannot do
@@ -126,6 +132,55 @@ function connectClient(overrides = {}) {
126
132
  cached = client;
127
133
  return client;
128
134
  }
135
+ /**
136
+ * The `ConnectProvider` props that come from the environment.
137
+ *
138
+ * ```tsx
139
+ * // app/layout.tsx - a Server Component
140
+ * import { connectProviderProps } from "@cosmoops/connect-sdk/next";
141
+ * import { ConnectProvider } from "@cosmoops/connect-sdk/client";
142
+ *
143
+ * <ConnectProvider {...connectProviderProps()}>{children}</ConnectProvider>
144
+ * ```
145
+ *
146
+ * ## What it is for
147
+ *
148
+ * `CONNECT_APP_URL` points the *server* at a non-production Connect. It is not
149
+ * `NEXT_PUBLIC_`, so it never reaches the browser, and `ConnectProvider`
150
+ * defaults `appUrl` to the production origin independently - so pointing the
151
+ * server somewhere else and leaving the prop off splits the integration in
152
+ * half. The server talks to dev and every destination the browser builds opens
153
+ * production: the user menu's Profile and Billing rows, the app launcher's link
154
+ * for an unsubscribed application, `ConnectAccessGate`'s subscribe target, and
155
+ * checkout. Nothing errors. The customer is simply asked to buy a second
156
+ * subscription in an instance where they have none.
157
+ *
158
+ * Both apps built on this SDK shipped that bug before anyone noticed, from
159
+ * reading "the provider takes no props" as a rule rather than as a remark about
160
+ * `clientId`. A root layout is a Server Component, so one spread fixes it with
161
+ * no browser-visible copy of the variable.
162
+ *
163
+ * ## What it deliberately leaves out
164
+ *
165
+ * `clientId` and `basePath`. The first is only needed alongside an explicit
166
+ * `redirectUri`, when you build the authorize URL in the browser rather than
167
+ * letting `signIn()` bounce through your own `/signin` route - and handing the
168
+ * browser a value it does not need is surface for nothing. The second is where
169
+ * *you* mounted the handler, which the environment does not know. Pass either
170
+ * yourself; a prop you pass always wins over this spread, whichever order you
171
+ * write them in, as long as the spread comes first.
172
+ *
173
+ * ## If you already have an origin helper
174
+ *
175
+ * Use it instead. An app that builds its own links into Connect (a "Manage
176
+ * team" button, a pricing page's subscribe target) needs that origin on the
177
+ * server too, and one function owning the read is better than this helper
178
+ * beside it - two readers of one variable is the shape that caused this. This
179
+ * exists for the integration that has no such helper, which is most of them.
180
+ */
181
+ function connectProviderProps() {
182
+ return { appUrl: (process.env.CONNECT_APP_URL ?? client_js_1.DEFAULT_BASE_URL).replace(/\/+$/, "") };
183
+ }
129
184
  /**
130
185
  * An httpOnly cookie holding the opaque `sessionToken`.
131
186
  *
@@ -179,4 +179,4 @@ class ConnectClient {
179
179
  }
180
180
  exports.ConnectClient = ConnectClient;
181
181
  /** Kept in step with `package.json` by `sdk.test.ts`. */
182
- exports.SDK_VERSION = "0.34.0";
182
+ exports.SDK_VERSION = "0.35.0";