@mekari-officeless/sdk 0.1.0 → 1.0.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 +40 -0
- package/README.md +253 -123
- package/dist/index.cjs +396 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +135 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +393 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -11
- package/src/client.js +0 -84
- package/src/errors.js +0 -8
- package/src/index.js +0 -2
- package/src/table.js +0 -22
- package/src/workflow.js +0 -10
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@mekari-officeless/sdk` are recorded here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
|
|
6
|
+
project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
> **Release process** — publishing is manual this phase. Merge to `main`, run
|
|
9
|
+
> `npm version <patch|minor|major>`, push the commit and the tag, then
|
|
10
|
+
> `npm publish --access public` from a clean checkout as an existing maintainer.
|
|
11
|
+
> Move the entries below from _Unreleased_ into the new version heading in the same
|
|
12
|
+
> pull request that bumps the version.
|
|
13
|
+
|
|
14
|
+
## [Unreleased]
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `officeless.init({ sdkKey, gatewayUrl? })` — checks the key's shape and returns an
|
|
19
|
+
instance bound to it. Sends no request and returns no project data: the key's
|
|
20
|
+
company, project and stage stay server-side. Several instances can be held on one
|
|
21
|
+
page at once.
|
|
22
|
+
- `app.auth.login(email)` — sends a login code and returns a `sessionId`.
|
|
23
|
+
- `app.auth.verify(email, sessionId, code)` — confirms the code and starts the
|
|
24
|
+
session.
|
|
25
|
+
- `app.auth.user()` — returns the signed-in person from the held session, without
|
|
26
|
+
sending a request.
|
|
27
|
+
- `app.auth.refresh()` — renews the session.
|
|
28
|
+
- `app.auth.logout()` — ends the session. Signing out twice does not throw.
|
|
29
|
+
- `app.action.hitSDKFunction(name, data, withAuth?)` — runs a published automation
|
|
30
|
+
created with the **SDK Function** trigger and returns its output.
|
|
31
|
+
- `OfficelessError`, carrying a `type` (`AuthorizationError`, `ValidationError`,
|
|
32
|
+
`WorkflowExecutionError`, `InternalServerError`) and the HTTP `status`, so a 429
|
|
33
|
+
can be told apart from a 401. A company without the feature turned on answers 404
|
|
34
|
+
like an unknown path, so there is deliberately no separate type for it.
|
|
35
|
+
- The session survives a page reload: it is stored encrypted with AES-GCM, using a
|
|
36
|
+
non-extractable key held in IndexedDB.
|
|
37
|
+
- TypeScript definitions for the whole surface, bundled to ESM and CommonJS with no
|
|
38
|
+
runtime dependencies.
|
|
39
|
+
|
|
40
|
+
[Unreleased]: https://bitbucket.org/jojocoders/officeless-sdk/branches/compare/main
|
package/README.md
CHANGED
|
@@ -1,194 +1,324 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Officeless SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run Officeless automations and sign users in, from your own web application.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Installation
|
|
5
|
+
- TypeScript, with full type definitions
|
|
6
|
+
- ESM and CommonJS
|
|
7
|
+
- No runtime dependencies
|
|
8
|
+
- **Browser only** — see [Environment support](#environment-support)
|
|
10
9
|
|
|
11
10
|
```bash
|
|
12
|
-
npm install @officeless/sdk
|
|
11
|
+
npm install @mekari-officeless/sdk
|
|
13
12
|
```
|
|
14
13
|
|
|
15
14
|
---
|
|
16
15
|
|
|
17
|
-
##
|
|
16
|
+
## Getting started
|
|
17
|
+
|
|
18
|
+
**1. Create an SDK key.** In Officeless Studio, open your project and go to
|
|
19
|
+
**Settings › Developer access**, then create a key. Keys are fixed to one
|
|
20
|
+
environment — `Development` or `Production` — when you create them, and that cannot
|
|
21
|
+
be changed afterwards.
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
**2. Add your domain.** On the same page, add the domain your application is served
|
|
24
|
+
from to **Allowed domains**. Calls from any other domain are rejected. The list is
|
|
25
|
+
shared by both environments of the project.
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
**3. Set the library up.**
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
```ts
|
|
30
|
+
import { officeless } from '@mekari-officeless/sdk'
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
const app = await officeless.init({
|
|
33
|
+
sdkKey: 'ofc_sdk_prod_0123456789abcdef0123456789abcdef',
|
|
34
|
+
})
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
// app → an instance: app.auth.login, app.auth.verify, app.action.hitSDKFunction, …
|
|
37
|
+
// no project id, name, or stage — setup asks Officeless nothing
|
|
30
38
|
```
|
|
31
39
|
|
|
32
|
-
|
|
40
|
+
`init` sends no request. It checks the key's shape, keeps it with the gateway URL,
|
|
41
|
+
and returns. So setup cannot fail on the network and adds nothing to your startup
|
|
42
|
+
time — but it also cannot tell you the key is wrong. A wrong, revoked, or deleted
|
|
43
|
+
key, a domain that is not on the allowed list, and a company without the feature
|
|
44
|
+
turned on are all reported by your first real call: `login`, `verify`, `refresh`,
|
|
45
|
+
`logout`, or `hitSDKFunction`.
|
|
33
46
|
|
|
34
|
-
|
|
47
|
+
Call it once, when your application starts. In a framework that server-renders —
|
|
48
|
+
Next.js, Nuxt — call it from client-side code only (see
|
|
49
|
+
[Environment support](#environment-support)).
|
|
35
50
|
|
|
36
|
-
|
|
51
|
+
**4. Run an automation.**
|
|
37
52
|
|
|
38
|
-
```
|
|
39
|
-
|
|
53
|
+
```ts
|
|
54
|
+
const result = await app.action.hitSDKFunction('calculate_payroll', { month: '2026-08' })
|
|
55
|
+
// result → { status: 'success', output: { total: 8500000, period: '2026-08' } }
|
|
56
|
+
```
|
|
40
57
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
})
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Capabilities
|
|
61
|
+
|
|
62
|
+
### `officeless.init({ sdkKey, gatewayUrl? })`
|
|
63
|
+
|
|
64
|
+
Sets the library up and returns an instance bound to one key. Sends no request, and
|
|
65
|
+
returns no project data — the key's company, project and stage stay server-side and
|
|
66
|
+
are read from the key on every later call.
|
|
67
|
+
|
|
68
|
+
The only failure it can raise is a value that is not shaped like an SDK key
|
|
69
|
+
(`ofc_sdk_<stage>_<32 hex characters>`), thrown as a `ValidationError` with no HTTP
|
|
70
|
+
status. A well-formed key that belongs to nothing passes here and fails on the first
|
|
71
|
+
real call.
|
|
72
|
+
|
|
73
|
+
Calling `init` again with a different key returns another, independent instance — one
|
|
74
|
+
page can hold several at once, and they do not share a signed-in user.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const production = await officeless.init({ sdkKey: 'ofc_sdk_prod_…' })
|
|
78
|
+
const staging = await officeless.init({ sdkKey: 'ofc_sdk_dev_…' })
|
|
46
79
|
```
|
|
47
80
|
|
|
48
|
-
|
|
81
|
+
`gatewayUrl` is optional and defaults to `https://officeless-gateway.mekari.com/v1`.
|
|
82
|
+
See [Single-tenant setup](#single-tenant-setup).
|
|
49
83
|
|
|
50
|
-
|
|
51
|
-
|--------|------|----------|-------------|
|
|
52
|
-
| `baseUrl` | `string` | yes | Officeless API base URL |
|
|
53
|
-
| `apiKey` | `string` | no | Sent as `X-Officeless-Key` header |
|
|
54
|
-
| `projectId` | `string` | no | Your Officeless project ID |
|
|
55
|
-
| `workflows` | `object` | no | Explicit map of workflow name → `{ url, method }` |
|
|
84
|
+
### `app.auth.login(email)`
|
|
56
85
|
|
|
57
|
-
|
|
86
|
+
Sends a login code to the email and returns a `sessionId` for this attempt.
|
|
58
87
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Loads client settings from a plain object or a parsed `officeless.config.json`.
|
|
62
|
-
|
|
63
|
-
```js
|
|
64
|
-
const client = OfficelessClient.fromConfig({
|
|
65
|
-
officeless: {
|
|
66
|
-
project_id: '2BfWnyXYk5mz',
|
|
67
|
-
base_url: 'https://api.officeless.io',
|
|
68
|
-
api_key: 'olk_live_xxxx',
|
|
69
|
-
workflows: {
|
|
70
|
-
'employee-list': { url: '/app/wh/abc123', method: 'post' },
|
|
71
|
-
'employee-create': { url: '/app/wh/def456', method: 'post' },
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
})
|
|
88
|
+
```ts
|
|
89
|
+
const { sessionId, otpExpiresAt } = await app.auth.login('user@example.com')
|
|
75
90
|
```
|
|
76
91
|
|
|
77
|
-
|
|
92
|
+
This resolves the same way whether or not the address belongs to a user of your
|
|
93
|
+
project — and whether or not it is even a valid address. Nothing about the address
|
|
94
|
+
changes the response, so this call cannot be used to discover who has an account.
|
|
95
|
+
Do not treat a successful response as proof the address exists.
|
|
78
96
|
|
|
79
|
-
|
|
97
|
+
### `app.auth.verify(email, sessionId, code)`
|
|
80
98
|
|
|
81
|
-
|
|
99
|
+
Confirms the code and starts the session.
|
|
82
100
|
|
|
83
|
-
|
|
101
|
+
```ts
|
|
102
|
+
const session = await app.auth.verify('user@example.com', sessionId, '482913')
|
|
103
|
+
// session → { accessToken, refreshToken, expiredAt, user }
|
|
104
|
+
```
|
|
84
105
|
|
|
85
|
-
|
|
86
|
-
const result = await client.workflow('send-welcome-email').trigger({
|
|
87
|
-
userId: 42,
|
|
88
|
-
email: 'user@example.com',
|
|
89
|
-
})
|
|
106
|
+
The session is stored for you, so a page reload keeps the user signed in.
|
|
90
107
|
|
|
91
|
-
|
|
108
|
+
### `app.auth.user()`
|
|
109
|
+
|
|
110
|
+
Returns the signed-in person. Reads the session your application already holds and
|
|
111
|
+
sends no request.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const user = await app.auth.user()
|
|
115
|
+
// user → { userId, email, name, companyId, companyName }
|
|
92
116
|
```
|
|
93
117
|
|
|
94
|
-
|
|
118
|
+
Throws `AuthorizationError` when nobody is signed in or the session has expired.
|
|
95
119
|
|
|
96
|
-
|
|
120
|
+
### `app.auth.refresh()`
|
|
97
121
|
|
|
98
|
-
|
|
122
|
+
Renews the session with the renewal token.
|
|
99
123
|
|
|
100
|
-
|
|
124
|
+
```ts
|
|
125
|
+
const { accessToken, expiredAt } = await app.auth.refresh()
|
|
126
|
+
```
|
|
101
127
|
|
|
102
|
-
|
|
128
|
+
There is no automatic refresh: the library does not retry a failed call by renewing
|
|
129
|
+
in the background. Call `refresh()` yourself — for example when a call fails with
|
|
130
|
+
`AuthorizationError` and `Session expired. Please log in again.`
|
|
103
131
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
132
|
+
### `app.auth.logout()`
|
|
133
|
+
|
|
134
|
+
Ends the session, on the server and locally.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
await app.auth.logout()
|
|
107
138
|
```
|
|
108
139
|
|
|
109
|
-
|
|
140
|
+
Calling it again on an already-ended session does not throw.
|
|
110
141
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
142
|
+
### `app.action.hitSDKFunction(name, data, withAuth?)`
|
|
143
|
+
|
|
144
|
+
Runs a published automation by name and returns its output.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
const result = await app.action.hitSDKFunction('calculate_payroll', { month: '2026-08' })
|
|
117
148
|
```
|
|
118
149
|
|
|
119
|
-
|
|
150
|
+
`name` is the **Function Name** set on the workflow's SDK Function trigger, not the
|
|
151
|
+
workflow's display title. The automation must have been created with the **SDK
|
|
152
|
+
Function** trigger — one built with any other trigger (Form event, API call, Webhook)
|
|
153
|
+
is not reachable this way and reports as not found.
|
|
154
|
+
|
|
155
|
+
`output` is whatever the automation returns: any JSON value, including `null`. It is
|
|
156
|
+
passed through exactly as the automation produced it, so your own field names are
|
|
157
|
+
never rewritten. Type it if you like:
|
|
120
158
|
|
|
121
|
-
```
|
|
122
|
-
const
|
|
123
|
-
|
|
159
|
+
```ts
|
|
160
|
+
const result = await app.action.hitSDKFunction<{ total: number }>('calculate_payroll', {
|
|
161
|
+
month: '2026-08',
|
|
162
|
+
})
|
|
163
|
+
result.output.total // number
|
|
124
164
|
```
|
|
125
165
|
|
|
126
|
-
|
|
166
|
+
`withAuth` defaults to `true` and only decides whether the SDK attaches the signed-in
|
|
167
|
+
session to the call. Whether a session is actually _required_ is decided by the
|
|
168
|
+
workflow's own **Required Authorization** setting, on the server. Passing
|
|
169
|
+
`withAuth: false` to a workflow that requires one does not bypass it — the call is
|
|
170
|
+
rejected.
|
|
127
171
|
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
|
|
172
|
+
```ts
|
|
173
|
+
// A workflow whose Required Authorization is off — no sign-in needed.
|
|
174
|
+
await app.action.hitSDKFunction('submit_public_enquiry', data, false)
|
|
131
175
|
```
|
|
132
176
|
|
|
133
177
|
---
|
|
134
178
|
|
|
135
|
-
##
|
|
179
|
+
## Errors
|
|
136
180
|
|
|
137
|
-
|
|
181
|
+
Every failure is thrown as an `OfficelessError` carrying a `type` and the HTTP
|
|
182
|
+
`status`. Branch on `type` — messages are written for people and may change.
|
|
138
183
|
|
|
139
|
-
```
|
|
140
|
-
import {
|
|
184
|
+
```ts
|
|
185
|
+
import { OfficelessError } from '@mekari-officeless/sdk'
|
|
141
186
|
|
|
142
187
|
try {
|
|
143
|
-
|
|
144
|
-
} catch (
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
188
|
+
await app.action.hitSDKFunction('calculate_payroll', { month: '2026-08' })
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (error instanceof OfficelessError) {
|
|
191
|
+
switch (error.type) {
|
|
192
|
+
case 'AuthorizationError':
|
|
193
|
+
// Includes rate limiting — check error.status === 429 to tell it apart.
|
|
194
|
+
break
|
|
195
|
+
case 'ValidationError':
|
|
196
|
+
break
|
|
197
|
+
case 'WorkflowExecutionError':
|
|
198
|
+
break
|
|
199
|
+
case 'InternalServerError':
|
|
200
|
+
break
|
|
201
|
+
}
|
|
151
202
|
}
|
|
152
203
|
}
|
|
153
204
|
```
|
|
154
205
|
|
|
155
|
-
|
|
206
|
+
| `type` | What it means | What to do |
|
|
207
|
+
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
208
|
+
| `AuthorizationError` | The key is wrong, revoked or deleted; the domain is not allowed; the session expired; or you are being rate limited (`status` 429). | Check `status`. On 401 with `Session expired`, call `refresh()` or sign the user in again. On 403 for the domain, add it in Studio. On 429, back off and retry. |
|
|
209
|
+
| `ValidationError` | An input did not match what the automation expects, or no function of that name exists in this project and stage. | Fix the input, or check the Function Name and that the workflow uses the SDK Function trigger and is published. |
|
|
210
|
+
| `WorkflowExecutionError` | The automation itself failed partway, or ran past the time limit. | Check the run in Studio's Run History. A run that fails halfway is not rolled back. |
|
|
211
|
+
| `InternalServerError` | Something failed on Officeless's side, or the call never reached it. | Retry. If it persists, contact support. |
|
|
212
|
+
|
|
213
|
+
`status` is `null` when the call never reached the server — offline, DNS failure, or
|
|
214
|
+
a blocked CORS preflight.
|
|
156
215
|
|
|
157
216
|
---
|
|
158
217
|
|
|
159
|
-
##
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
218
|
+
## Limits
|
|
219
|
+
|
|
220
|
+
| Limit | Value |
|
|
221
|
+
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
222
|
+
| Function calls | 100 requests per minute **per project** — shared across every key the project holds, Development and Production together. Exceeding it returns `AuthorizationError` with `status` 429. |
|
|
223
|
+
| Setup calls | Rate limited per key. |
|
|
224
|
+
| Session length | 1 hour. Renew with `refresh()`. |
|
|
225
|
+
| Renewal window | 1 week. |
|
|
226
|
+
| Active sessions | At most 3 per person. A fourth sign-in ends the oldest. |
|
|
227
|
+
| Automation run time | 170 seconds. A longer run returns `WorkflowExecutionError`. |
|
|
228
|
+
|
|
229
|
+
Revoking or deleting a key in Studio ends every session started with it, immediately.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Single-tenant setup
|
|
234
|
+
|
|
235
|
+
`gatewayUrl` points the SDK at a different Officeless deployment. It exists for
|
|
236
|
+
single-tenant customers who run Officeless on their own domain. Most applications
|
|
237
|
+
never set it.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const app = await officeless.init({
|
|
241
|
+
sdkKey: 'ofc_sdk_prod_…',
|
|
242
|
+
gatewayUrl: 'https://officeless.your-company.com/v1',
|
|
243
|
+
})
|
|
177
244
|
```
|
|
178
245
|
|
|
179
|
-
|
|
246
|
+
**What the SDK builds from it.** Every call is sent to
|
|
247
|
+
`{gatewayUrl}/nocode/sdk/v1/…`. So `gatewayUrl` is the origin **plus whatever prefix
|
|
248
|
+
your gateway is mounted under** — not the origin alone. The shared gateway mounts its
|
|
249
|
+
services under `/v1` and strips that prefix before forwarding, which is why the
|
|
250
|
+
default ends in it. Get this wrong and every call returns the gateway's own HTML 404
|
|
251
|
+
rather than a JSON error, because the request never reaches Officeless at all.
|
|
252
|
+
|
|
253
|
+
> **Set `gatewayUrl` once, in your own deployment configuration.**
|
|
254
|
+
>
|
|
255
|
+
> Never take it from a query parameter, a remotely-fetched config, or any other input
|
|
256
|
+
> an attacker could influence. Your SDK key — and, once a user signs in, their live
|
|
257
|
+
> session tokens — are sent to whatever host this resolves to. A `gatewayUrl` an
|
|
258
|
+
> attacker controls is a `gatewayUrl` that receives those credentials.
|
|
259
|
+
>
|
|
260
|
+
> It cannot be restricted to a Mekari-owned allowlist, because for single-tenant
|
|
261
|
+
> customers any domain may legitimately be correct. That makes it your build's
|
|
262
|
+
> responsibility rather than something the library can enforce.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## Security notes
|
|
267
|
+
|
|
268
|
+
**The SDK key is not a secret.** It ships inside your client-side code, and anyone
|
|
269
|
+
can read it with browser developer tools. That is expected. Its protection is the
|
|
270
|
+
allowed-domains list and the rate limit, not secrecy. If a key is misused, revoke it
|
|
271
|
+
in Studio — that takes effect at once and ends every session started with it.
|
|
272
|
+
|
|
273
|
+
**Stored sessions are encrypted, but that is not a defence against XSS.** The access
|
|
274
|
+
and refresh tokens are kept in local storage, encrypted with AES-GCM using a
|
|
275
|
+
non-extractable key held in IndexedDB, so the session survives a page reload. This
|
|
276
|
+
raises the bar against a passive read — browser devtools on a shared machine, a copied
|
|
277
|
+
browser profile. It does **not** protect against a script already running on your
|
|
278
|
+
page: an XSS hole in your application can call the same decryption path the SDK does
|
|
279
|
+
and read the tokens in the clear. Keeping the tokens in memory instead would be no
|
|
280
|
+
better, since a script on the page can reach those too. Treat XSS in your own
|
|
281
|
+
application as the control that matters here.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Environment support
|
|
286
|
+
|
|
287
|
+
This release is a **browser library**. It uses `fetch`, `localStorage`, `IndexedDB`
|
|
288
|
+
and the Web Crypto API, and it relies on the browser setting the `Origin` header —
|
|
289
|
+
that is what the allowed-domains check is based on.
|
|
290
|
+
|
|
291
|
+
Running it on a server (Node, NestJS, Express, a Next.js route handler or server
|
|
292
|
+
component) is **not supported in this release**. A server build needs a different
|
|
293
|
+
security model, and is planned separately.
|
|
180
294
|
|
|
181
|
-
|
|
182
|
-
|-------|-------------|
|
|
183
|
-
| `project_id` | Unique Officeless project identifier |
|
|
184
|
-
| `base_url` | Base API URL for your Officeless instance |
|
|
185
|
-
| `api_key` | Secret key for authenticating requests |
|
|
186
|
-
| `workflows` | Map of workflow name → `{ url, method }`. `url` may be relative (resolved against `base_url`) or absolute. |
|
|
295
|
+
For frameworks that server-render, keep `init` and every call on the client side:
|
|
187
296
|
|
|
188
|
-
|
|
297
|
+
```tsx
|
|
298
|
+
'use client'
|
|
299
|
+
|
|
300
|
+
import { useEffect, useState } from 'react'
|
|
301
|
+
import { officeless, type OfficelessApp } from '@mekari-officeless/sdk'
|
|
302
|
+
|
|
303
|
+
export function useOfficeless(sdkKey: string) {
|
|
304
|
+
const [app, setApp] = useState<OfficelessApp | null>(null)
|
|
305
|
+
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
let cancelled = false
|
|
308
|
+
officeless.init({ sdkKey }).then((instance) => {
|
|
309
|
+
if (!cancelled) setApp(instance)
|
|
310
|
+
})
|
|
311
|
+
return () => {
|
|
312
|
+
cancelled = true
|
|
313
|
+
}
|
|
314
|
+
}, [sdkKey])
|
|
315
|
+
|
|
316
|
+
return app
|
|
317
|
+
}
|
|
318
|
+
```
|
|
189
319
|
|
|
190
320
|
---
|
|
191
321
|
|
|
192
322
|
## License
|
|
193
323
|
|
|
194
|
-
|
|
324
|
+
Proprietary — © Mekari. Distributed for use with Officeless.
|