@substrat-run/connector-scrive 0.0.2

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 ADDED
@@ -0,0 +1,98 @@
1
+ # @substrat-run/connector-scrive
2
+
3
+ Scrive eSign (BankID) — the **outbound half** of external signing.
4
+
5
+ `engine-protocol` emits `protocol.signatures-requested` when a vertical freezes a document and
6
+ sends it for signature. This turns that into a Scrive document: create → set file → set parties
7
+ with `se_bankid` → start.
8
+
9
+ ## ⚠️ This connector cannot complete a signature
10
+
11
+ It is `private` and unpublished on purpose. Four things are missing, none of them here:
12
+
13
+ 1. ~~**Dispatch is not idempotent.**~~ **Done.** A redelivery once created a *second* Scrive
14
+ document — duplicate legal paperwork to real signatories. The connector now records each
15
+ dispatch in a directory-side ledger (`ctx.admin.putConnectorState`, keyed by the connection)
16
+ and skips if a prior dispatch is found. Directory-side because a connector runs *inside* the
17
+ scope's dispatch and re-entering the scope actor deadlocks (verified). A narrow residual
18
+ remains — if the ledger write itself fails after the provider `start` succeeds, the retry
19
+ still duplicates — closable with provider-side dedup via the `substrat_instance` tag the
20
+ connector already sets, once a list-by-tag query lands.
21
+
22
+ 2. ~~**Connector state has no home.**~~ **Done** — the ledger above is that home.
23
+
24
+ 3. ~~**Recording the signature back**~~ **Done** ([#97](https://github.com/substrat-run/substrat/issues/97)).
25
+ When a party signs, the signature belongs on the protocol instance in the *scope*.
26
+ `reconcileScriveDispatch(host, connectionId, instanceId, { fetch })` reads
27
+ `documents/{id}/get`, maps each signed party back to its request, and records it by invoking
28
+ `protocol/record-signature` through `getConnectorScope` — the connection acting as itself,
29
+ as a top-level operation (not the dispatch handler, where re-entering the scope deadlocks).
30
+ The connection must hold `protocol:record-signature` (`grantToConnection`), which shows up in
31
+ the permission diff. Idempotent across polls; verified against `ScriveMock` advanced to
32
+ `closed`. The one thing a mock can't prove — Scrive's real `get` shape and party order —
33
+ waits on a testbed BankID round-trip (BankID-to-sign is disabled on the account).
34
+
35
+ 4. **The poll driver exists; nothing calls it on a timer.**
36
+ `sweepScriveReconciliations(host, connectionId, { fetch })` is the scheduler's unit of work:
37
+ it enumerates the dispatch ledger (`HostAdmin.listConnectorState(id, 'scrive:dispatch:')`) and
38
+ reconciles every outstanding instance, skipping ones the ledger already shows complete and
39
+ stepping past a provider error on any one instance. It is idempotent and scoped to one
40
+ connection (a connection never crosses a tenant). What is *still* missing is the **timer** —
41
+ there is no cron, queue or Durable Object alarm in any wrangler config, the same trigger
42
+ `drainDue` still lacks. A platform sweeper would, on a schedule, iterate the connections it
43
+ owns and call this for each:
44
+
45
+ ```ts
46
+ // in a Cloudflare Worker's scheduled() handler, or a DO alarm:
47
+ for (const connectionId of scriveConnections) {
48
+ await sweepScriveReconciliations(host, connectionId, { fetch });
49
+ }
50
+ ```
51
+
52
+ That trigger is a deployment concern, not connector code — which is why it, not the seam or
53
+ the driver, is the remaining reason the connector stays unpublished.
54
+
55
+ 5. **No document store.** `attachmentTargets` is declared in the manifest contract and
56
+ implemented nowhere, so there is no place for rendered bytes.
57
+
58
+ ## What it therefore sends
59
+
60
+ An **attestation sheet**, not the avtal: the template, the parties, and the content hash the
61
+ signature refers to. That is honest for a hash-attestation model and enough to exercise the
62
+ seam, but it is not a contract anybody should sign.
63
+
64
+ Rendering the real document belongs to the **vertical**, which owns the content — a connector
65
+ cannot read another module's tables and should not learn a vertical's vocabulary to try. It
66
+ needs (4) to hand the bytes over.
67
+
68
+ ## Verified against the testbed
69
+
70
+ The API layer was **checked against `api-testbed.scrive.com`**, not just the docs — and the
71
+ first version, written from the docs, was wrong in three ways one live call exposed at once:
72
+
73
+ - **auth is OAuth1 PLAINTEXT**, not OAuth2 bearer (the UI's "Client" + "Token" credentials are
74
+ two halves of one four-part signature; the `oauth2.scrive.com` endpoint rejects them)
75
+ - **`documents/new` returns no `status`** — only `get` does, so mutation responses are parsed
76
+ for their id and status is re-read
77
+ - **`setfile` is `multipart/form-data`**, not a base64 body
78
+
79
+ `test/live.test.ts` runs the real lifecycle (`new → setfile → update → get`) when
80
+ `connectors/scrive/.dev.vars` holds a complete OAuth1 credential, and **skips** otherwise — so
81
+ CI without secrets stays offline and a local run with the testbed verifies the actual API. It
82
+ uses `standard` auth because **`se_bankid`-to-sign is disabled on the testbed account** (`start`
83
+ returns 409); the BankID round-trip waits on that setting.
84
+
85
+ ## Testing
86
+
87
+ `ScriveMock` implements the documented endpoints in memory, so the whole lifecycle runs without
88
+ a provider account.
89
+
90
+ **What a mock proves:** that our shape works — credential resolution, egress, health, retry,
91
+ the document lifecycle.
92
+
93
+ **What it cannot prove:** that our reading of Scrive's API is correct. The mock *is* that
94
+ reading — same author, same misunderstandings, on both sides of the call. Green here means
95
+ *ready to check against `api-testbed.scrive.com`*, never *verified*.
96
+
97
+ It stays useful afterwards: a real provider will not return 503 on demand, or let you
98
+ fast-forward two days to a signature.
package/dist/api.d.ts ADDED
@@ -0,0 +1,134 @@
1
+ import { z } from 'zod';
2
+ import type { ConnectorConnection } from '@substrat-run/kernel';
3
+ /**
4
+ * A thin, typed client over the Scrive eSign v2 endpoints.
5
+ *
6
+ * Every call goes through the connection's `fetch`, never a global one: that is
7
+ * what gets it a timeout, an egress policy, and health recorded against the
8
+ * right connection. Module code cannot reach any of this — boundary-lint bans
9
+ * `fetch` outright — and a connector is host code.
10
+ *
11
+ * **The shapes here were verified against `api-testbed.scrive.com`, not just the
12
+ * docs.** The first version of this file was written from the documentation and
13
+ * was wrong in three ways a live call exposed at once (auth scheme, the upload
14
+ * encoding, the create-response shape). Each is called out below where it bit.
15
+ */
16
+ export declare const SCRIVE_TESTBED = "https://api-testbed.scrive.com";
17
+ export declare const SCRIVE_PRODUCTION = "https://scrive.com";
18
+ /**
19
+ * A Scrive connection's credential — OAuth1 "personal access credentials".
20
+ *
21
+ * NOT OAuth2 bearer, which the first version assumed. Scrive's UI labels these
22
+ * "Client credentials" and "Token credentials", which reads like two schemes but
23
+ * is one: the four parts combine into a PLAINTEXT OAuth signature. The
24
+ * `oauth2.scrive.com` token endpoint rejects them with `invalid_client` — it is
25
+ * a different mechanism entirely.
26
+ */
27
+ export declare const scriveSecret: z.ZodObject<{
28
+ clientId: z.ZodString;
29
+ clientSecret: z.ZodString;
30
+ tokenId: z.ZodString;
31
+ tokenSecret: z.ZodString;
32
+ }, z.core.$strip>;
33
+ export type ScriveSecret = z.infer<typeof scriveSecret>;
34
+ /**
35
+ * An id-bearing response — what `new` / `setfile` / `update` / `start` return.
36
+ *
37
+ * `POST /documents/new` returns NO top-level `status` (verified) — only
38
+ * `/documents/{id}/get` returns the full object. The first version parsed every
39
+ * response as a full document and would have thrown on call one. So mutation
40
+ * responses are parsed for their id only, and status is read from `get` — which
41
+ * is the right design anyway: don't trust a mutation's echo, re-read the truth.
42
+ */
43
+ export declare const scriveDocRef: z.ZodObject<{
44
+ id: z.ZodString;
45
+ }, z.core.$strip>;
46
+ export type ScriveDocRef = z.infer<typeof scriveDocRef>;
47
+ /** The full document, as `get` returns it — extra fields ignored. */
48
+ export declare const scriveDocument: z.ZodObject<{
49
+ id: z.ZodString;
50
+ status: z.ZodEnum<{
51
+ preparation: "preparation";
52
+ pending: "pending";
53
+ closed: "closed";
54
+ canceled: "canceled";
55
+ timedout: "timedout";
56
+ rejected: "rejected";
57
+ }>;
58
+ parties: z.ZodDefault<z.ZodArray<z.ZodObject<{
59
+ id: z.ZodString;
60
+ is_author: z.ZodOptional<z.ZodBoolean>;
61
+ is_signatory: z.ZodOptional<z.ZodBoolean>;
62
+ signatory_role: z.ZodOptional<z.ZodString>;
63
+ sign_time: z.ZodOptional<z.ZodNullable<z.ZodString>>;
64
+ authentication_method_to_sign: z.ZodOptional<z.ZodString>;
65
+ fields: z.ZodOptional<z.ZodArray<z.ZodObject<{
66
+ type: z.ZodString;
67
+ value: z.ZodUnknown;
68
+ }, z.core.$strip>>>;
69
+ }, z.core.$strip>>>;
70
+ }, z.core.$strip>;
71
+ export type ScriveDocument = z.infer<typeof scriveDocument>;
72
+ export interface ScriveParty {
73
+ /** Display name for the signing page. */
74
+ name: string;
75
+ email?: string;
76
+ /**
77
+ * Swedish personnummer, when the flow authenticates to sign with BankID.
78
+ *
79
+ * Passed THROUGH to the provider and never persisted by us: it is `direct`
80
+ * PII, and `engine-protocol` stores an opaque `DataSubjectId` as the signatory
81
+ * instead. The provider needs it; our tables must not have it.
82
+ */
83
+ personalNumber?: string;
84
+ /** `se_bankid` for Swedish BankID; `standard` otherwise. */
85
+ authenticationMethodToSign: 'standard' | 'se_bankid';
86
+ /**
87
+ * The sender/author. Scrive auto-adds the API user as an author party on
88
+ * `new`; exactly one party across the set must be the author, so the connector
89
+ * marks the issuing (primary) party as it. Verified: sending an explicit
90
+ * author party in `update` replaces the auto one.
91
+ */
92
+ isAuthor?: boolean;
93
+ /** A viewer rather than a signer — an author who does not sign. */
94
+ isSignatory?: boolean;
95
+ }
96
+ export declare class ScriveApi {
97
+ private readonly conn;
98
+ private readonly baseUrl;
99
+ private readonly secret;
100
+ constructor(conn: ConnectorConnection, baseUrl?: string);
101
+ /**
102
+ * The OAuth1 PLAINTEXT authorization header. The signature is
103
+ * `<clientSecret>&<tokenSecret>` — literally the two secrets joined by `&`,
104
+ * which is what "PLAINTEXT" means: no HMAC, TLS is the confidentiality.
105
+ */
106
+ private headers;
107
+ createDocument(): Promise<ScriveDocRef>;
108
+ /**
109
+ * Attach the PDF. **`multipart/form-data`**, verified — not the raw base64 body
110
+ * the first version sent. The multipart envelope is built as bytes because the
111
+ * file is binary and a string body would corrupt it (which is why
112
+ * `ConnectorRequestInit.body` accepts `Uint8Array`).
113
+ */
114
+ setFile(documentId: string, filename: string, pdf: Uint8Array): Promise<void>;
115
+ /** Parties, callback URL and title, in one `document=` form field. */
116
+ update(documentId: string, patch: {
117
+ title?: string;
118
+ parties?: ScriveParty[];
119
+ callbackUrl?: string;
120
+ tags?: {
121
+ name: string;
122
+ value: string;
123
+ }[];
124
+ }): Promise<ScriveDocRef>;
125
+ /** Send it. After this the document is `pending` and the parties are invited. */
126
+ start(documentId: string): Promise<ScriveDocRef>;
127
+ /**
128
+ * Current state — the polling path, and the only call that returns `status`.
129
+ * Webhook ingress (#96) is not on the critical path precisely because this
130
+ * exists and Scrive's callbacks are unauthenticated anyway.
131
+ */
132
+ get(documentId: string): Promise<ScriveDocument>;
133
+ }
134
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAMhE;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,cAAc,mCAAmC,CAAC;AAC/D,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AAEtD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY;;;;;iBAKvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY;;iBAAsC,CAAC;AAChE,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD,qEAAqE;AACrE,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;iBAyBzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,0BAA0B,EAAE,UAAU,GAAG,WAAW,CAAC;IACrD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA2BD,qBAAa,SAAS;IAIlB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJ1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAGnB,IAAI,EAAE,mBAAmB,EACzB,OAAO,GAAE,MAAuB;IAKnD;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAUT,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC;IAQ7C;;;;;OAKG;IACG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAWnF,sEAAsE;IAChE,MAAM,CACV,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KAC1C,GACA,OAAO,CAAC,YAAY,CAAC;IAiCxB,iFAAiF;IAC3E,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAQtD;;;;OAIG;IACG,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CAOvD"}
package/dist/api.js ADDED
@@ -0,0 +1,203 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * A thin, typed client over the Scrive eSign v2 endpoints.
4
+ *
5
+ * Every call goes through the connection's `fetch`, never a global one: that is
6
+ * what gets it a timeout, an egress policy, and health recorded against the
7
+ * right connection. Module code cannot reach any of this — boundary-lint bans
8
+ * `fetch` outright — and a connector is host code.
9
+ *
10
+ * **The shapes here were verified against `api-testbed.scrive.com`, not just the
11
+ * docs.** The first version of this file was written from the documentation and
12
+ * was wrong in three ways a live call exposed at once (auth scheme, the upload
13
+ * encoding, the create-response shape). Each is called out below where it bit.
14
+ */
15
+ export const SCRIVE_TESTBED = 'https://api-testbed.scrive.com';
16
+ export const SCRIVE_PRODUCTION = 'https://scrive.com';
17
+ /**
18
+ * A Scrive connection's credential — OAuth1 "personal access credentials".
19
+ *
20
+ * NOT OAuth2 bearer, which the first version assumed. Scrive's UI labels these
21
+ * "Client credentials" and "Token credentials", which reads like two schemes but
22
+ * is one: the four parts combine into a PLAINTEXT OAuth signature. The
23
+ * `oauth2.scrive.com` token endpoint rejects them with `invalid_client` — it is
24
+ * a different mechanism entirely.
25
+ */
26
+ export const scriveSecret = z.object({
27
+ clientId: z.string().min(1),
28
+ clientSecret: z.string().min(1),
29
+ tokenId: z.string().min(1),
30
+ tokenSecret: z.string().min(1),
31
+ });
32
+ /**
33
+ * An id-bearing response — what `new` / `setfile` / `update` / `start` return.
34
+ *
35
+ * `POST /documents/new` returns NO top-level `status` (verified) — only
36
+ * `/documents/{id}/get` returns the full object. The first version parsed every
37
+ * response as a full document and would have thrown on call one. So mutation
38
+ * responses are parsed for their id only, and status is read from `get` — which
39
+ * is the right design anyway: don't trust a mutation's echo, re-read the truth.
40
+ */
41
+ export const scriveDocRef = z.object({ id: z.string().min(1) });
42
+ /** The full document, as `get` returns it — extra fields ignored. */
43
+ export const scriveDocument = z.object({
44
+ id: z.string().min(1),
45
+ status: z.enum(['preparation', 'pending', 'closed', 'canceled', 'timedout', 'rejected']),
46
+ parties: z
47
+ .array(z.object({
48
+ id: z.string().min(1),
49
+ is_author: z.boolean().optional(),
50
+ is_signatory: z.boolean().optional(),
51
+ signatory_role: z.string().optional(),
52
+ /** Set once that party has signed. */
53
+ sign_time: z.string().nullable().optional(),
54
+ authentication_method_to_sign: z.string().optional(),
55
+ /**
56
+ * The party's fields — read by the poll driver to cross-check that the
57
+ * provider's Nth party is still the dispatch's Nth party (name), before
58
+ * attributing a signature to a request. Kept so the reconcile can fail
59
+ * closed on a reorder rather than mis-record.
60
+ */
61
+ fields: z
62
+ .array(z.object({ type: z.string(), value: z.unknown() }))
63
+ .optional(),
64
+ }))
65
+ .default([]),
66
+ });
67
+ const asJson = async (res, what) => {
68
+ const body = await res.text();
69
+ if (!res.ok) {
70
+ // Scrive's error body is JSON with `error_message`; surface it rather than a
71
+ // bare status, because "This feature is disabled" is the difference between a
72
+ // bug and an account setting.
73
+ let detail = body.slice(0, 400);
74
+ try {
75
+ const parsed = JSON.parse(body);
76
+ if (parsed.error_message)
77
+ detail = parsed.error_message;
78
+ }
79
+ catch {
80
+ /* not JSON; keep the raw slice */
81
+ }
82
+ throw new Error(`scrive ${what} failed: HTTP ${res.status} ${detail}`);
83
+ }
84
+ try {
85
+ return JSON.parse(body);
86
+ }
87
+ catch {
88
+ throw new Error(`scrive ${what}: response was not JSON (${body.slice(0, 200)})`);
89
+ }
90
+ };
91
+ export class ScriveApi {
92
+ conn;
93
+ baseUrl;
94
+ secret;
95
+ constructor(conn, baseUrl = SCRIVE_TESTBED) {
96
+ this.conn = conn;
97
+ this.baseUrl = baseUrl;
98
+ this.secret = scriveSecret.parse(conn.secret);
99
+ }
100
+ /**
101
+ * The OAuth1 PLAINTEXT authorization header. The signature is
102
+ * `<clientSecret>&<tokenSecret>` — literally the two secrets joined by `&`,
103
+ * which is what "PLAINTEXT" means: no HMAC, TLS is the confidentiality.
104
+ */
105
+ headers(extra = {}) {
106
+ const s = this.secret;
107
+ const auth = `oauth_signature_method="PLAINTEXT", ` +
108
+ `oauth_consumer_key="${s.clientId}", ` +
109
+ `oauth_token="${s.tokenId}", ` +
110
+ `oauth_signature="${s.clientSecret}&${s.tokenSecret}"`;
111
+ return { authorization: auth, ...extra };
112
+ }
113
+ async createDocument() {
114
+ const res = await this.conn.fetch(`${this.baseUrl}/api/v2/documents/new`, {
115
+ method: 'POST',
116
+ headers: this.headers(),
117
+ });
118
+ return scriveDocRef.parse(await asJson(res, 'documents/new'));
119
+ }
120
+ /**
121
+ * Attach the PDF. **`multipart/form-data`**, verified — not the raw base64 body
122
+ * the first version sent. The multipart envelope is built as bytes because the
123
+ * file is binary and a string body would corrupt it (which is why
124
+ * `ConnectorRequestInit.body` accepts `Uint8Array`).
125
+ */
126
+ async setFile(documentId, filename, pdf) {
127
+ const boundary = `----substrat${filename.length}${pdf.length}`;
128
+ const body = multipartFile(boundary, 'file', filename, pdf);
129
+ const res = await this.conn.fetch(`${this.baseUrl}/api/v2/documents/${documentId}/setfile`, {
130
+ method: 'POST',
131
+ headers: this.headers({ 'content-type': `multipart/form-data; boundary=${boundary}` }),
132
+ body,
133
+ });
134
+ await asJson(res, 'setfile');
135
+ }
136
+ /** Parties, callback URL and title, in one `document=` form field. */
137
+ async update(documentId, patch) {
138
+ const document = {
139
+ ...(patch.title ? { title: patch.title } : {}),
140
+ ...(patch.callbackUrl ? { api_callback_url: patch.callbackUrl } : {}),
141
+ ...(patch.tags ? { tags: patch.tags } : {}),
142
+ ...(patch.parties
143
+ ? {
144
+ parties: patch.parties.map((p) => ({
145
+ is_author: p.isAuthor ?? false,
146
+ is_signatory: p.isSignatory ?? true,
147
+ authentication_method_to_sign: p.authenticationMethodToSign,
148
+ fields: [
149
+ { type: 'name', order: 1, value: p.name },
150
+ ...(p.email ? [{ type: 'email', value: p.email }] : []),
151
+ ...(p.personalNumber
152
+ ? [{ type: 'personal_number', value: p.personalNumber }]
153
+ : []),
154
+ ],
155
+ })),
156
+ }
157
+ : {}),
158
+ };
159
+ // Scrive takes the document JSON as a url-encoded `document=` form field, not
160
+ // a JSON request body — another shape the docs left ambiguous and the testbed
161
+ // settled.
162
+ const res = await this.conn.fetch(`${this.baseUrl}/api/v2/documents/${documentId}/update`, {
163
+ method: 'POST',
164
+ headers: this.headers({ 'content-type': 'application/x-www-form-urlencoded' }),
165
+ body: `document=${encodeURIComponent(JSON.stringify(document))}`,
166
+ });
167
+ return scriveDocRef.parse(await asJson(res, 'update'));
168
+ }
169
+ /** Send it. After this the document is `pending` and the parties are invited. */
170
+ async start(documentId) {
171
+ const res = await this.conn.fetch(`${this.baseUrl}/api/v2/documents/${documentId}/start`, {
172
+ method: 'POST',
173
+ headers: this.headers(),
174
+ });
175
+ return scriveDocRef.parse(await asJson(res, 'start'));
176
+ }
177
+ /**
178
+ * Current state — the polling path, and the only call that returns `status`.
179
+ * Webhook ingress (#96) is not on the critical path precisely because this
180
+ * exists and Scrive's callbacks are unauthenticated anyway.
181
+ */
182
+ async get(documentId) {
183
+ const res = await this.conn.fetch(`${this.baseUrl}/api/v2/documents/${documentId}/get`, {
184
+ method: 'GET',
185
+ headers: this.headers(),
186
+ });
187
+ return scriveDocument.parse(await asJson(res, 'get'));
188
+ }
189
+ }
190
+ /** A one-file `multipart/form-data` body, as bytes. Web-standard, no node:buffer. */
191
+ function multipartFile(boundary, field, filename, file) {
192
+ const enc = new TextEncoder();
193
+ const head = enc.encode(`--${boundary}\r\n` +
194
+ `content-disposition: form-data; name="${field}"; filename="${filename}"\r\n` +
195
+ `content-type: application/pdf\r\n\r\n`);
196
+ const tail = enc.encode(`\r\n--${boundary}--\r\n`);
197
+ const out = new Uint8Array(head.length + file.length + tail.length);
198
+ out.set(head, 0);
199
+ out.set(file, head.length);
200
+ out.set(tail, head.length + file.length);
201
+ return out;
202
+ }
203
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}