@colixsystems/widget-sdk 0.240.0 → 0.243.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/dist/bankid-app.js +6 -0
- package/dist/contract.cjs +91 -28
- package/dist/contract.js +91 -28
- package/dist/index.d.ts +55 -4
- package/dist/index.js +1 -0
- package/dist/index.native.js +1 -0
- package/dist/signing-hooks.js +40 -14
- package/dist/signing-state.js +36 -2
- package/package.json +1 -1
- package/src/contract.js +91 -28
package/README.md
CHANGED
|
@@ -84,7 +84,39 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
84
84
|
|
|
85
85
|
## Status
|
|
86
86
|
|
|
87
|
-
`v0.
|
|
87
|
+
`v0.243.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
88
|
+
|
|
89
|
+
### What's new in 0.243.0 (contract 1.197.0)
|
|
90
|
+
|
|
91
|
+
**`bankIdAppUrl(autoStartToken)` opens BankID on this device** (from 0.243.0). The link a signing or identification widget opens from its "Open BankID on this device" button: `Linking.openURL(bankIdAppUrl(autoStartToken))`. It is the same link the built-in Files and Identification widgets and the signing page use. For BankID on another device, show the order's `qr` with `<Image>`.
|
|
92
|
+
|
|
93
|
+
`useFileSignature` does not poll by itself: while `status` is `"pending"`, call `refresh()` every 2 seconds, and render each answer's `qr`. It signs a file of any type in a shared space; outside a signing round the signature is kept beside the file, and in a round the signature is added to the PDF, which is then sealed. A `verify()` verdict reads as: `valid` (the BankID signature), `content_status` (`unchanged`, `changed` or `unavailable`), `signed_by_others_since` (later signatures in a round) and `seal.status` (`valid`, `invalid`, `certificate_revoked`, `unavailable`, or `not_applicable` for a signature kept beside the file). `useFileSignatures` is described as what it is: the signed-in user's own signatures.
|
|
94
|
+
|
|
95
|
+
```jsx
|
|
96
|
+
import { Button, Image, Linking, bankIdAppUrl, useFileSignature } from "@colixsystems/widget-sdk";
|
|
97
|
+
|
|
98
|
+
const { status, qr, autoStartToken, initiate, refresh } = useFileSignature(fileId);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (status !== "pending") return undefined;
|
|
101
|
+
const timer = setInterval(refresh, 2000);
|
|
102
|
+
return () => clearInterval(timer);
|
|
103
|
+
}, [status, refresh]);
|
|
104
|
+
// bankIdAppUrl exists from 0.243.0; a widget that must load on an older SDK
|
|
105
|
+
// reads it as sdk.bankIdAppUrl from `import * as sdk` and builds the same link itself.
|
|
106
|
+
// autoStartToken && <Button title="Open BankID on this device" onPress={() => Linking.openURL(bankIdAppUrl(autoStartToken))} />
|
|
107
|
+
// qr && <Image source={{ uri: qr }} style={{ width: 200, height: 200 }} />
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### What's new in 0.241.0 (contract 1.195.0)
|
|
111
|
+
|
|
112
|
+
**A signing round can include people without an account in the app.** `useSigningRound(fileId).prepare(signers)` takes, beside account ids, `{ name, email }` entries, mixed in the order of the places on the signature page. When the round is confirmed, each of those people is emailed a personal BankID signing link that is valid for 24 hours. Anyone who holds the link can sign, so tell the person sending the document before they send it. In the round a manager sees each place's `id`, `kind` (`"app_user"` or `"email"`), `email`, `signed_as` (the name BankID verified, which can differ from the name you entered) and, for an email place, `link`: `{ status, sent_at, expires_at, used_at, links_sent, can_resend }`, where `status` is `"sending"`, `"sent"`, `"delivery_failed"`, `"expired"`, `"used"` or `"cancelled"`. `resendLink(signerId, { email })` replaces the place's link with a new one (the old link stops working), sent to a corrected address when you pass one. Offer it only where `link.can_resend` is true. A refused signer's `errors` entry now carries `index`, its position in the list you sent, so you can mark the right entry. A new link sent too soon is refused with `SIGNING_LINK_RESEND_TOO_SOON` and a `retryAfter` in seconds. A list of account ids alone works exactly as before. **One type changes:** `SigningRoundSigner.app_user_id` widens from `string` to `string | null`, because an email place has no account; TypeScript code that read it as a string must check for null. `resendLink` needs `@colixsystems/filestore-client@0.14.0`; with an older client it is refused with code `NOT_SUPPORTED`.
|
|
113
|
+
|
|
114
|
+
```jsx
|
|
115
|
+
const { round, prepare, resendLink } = useSigningRound(fileId);
|
|
116
|
+
await prepare(["user_1", { name: "Bo Ek", email: "bo@example.se" }]);
|
|
117
|
+
// later, for an email place whose link.can_resend is true:
|
|
118
|
+
await resendLink(place.id, { email: "bo.ek@example.se" });
|
|
119
|
+
```
|
|
88
120
|
|
|
89
121
|
### What's new in 0.240.0 (contract 1.194.0)
|
|
90
122
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The one BankID app launch link: widgets, the Player's login and the signing
|
|
2
|
+
// page. Imports nothing, so every host and the exported app share it as it is.
|
|
3
|
+
|
|
4
|
+
/** The URL that opens the BankID app on this device for the order of `autoStartToken`. */
|
|
5
|
+
export const bankIdAppUrl = (autoStartToken) =>
|
|
6
|
+
`bankid:///?autostarttoken=${encodeURIComponent(autoStartToken)}&redirect=null`;
|
package/dist/contract.cjs
CHANGED
|
@@ -1624,10 +1624,30 @@ const HOOKS = [
|
|
|
1624
1624
|
signature: "useFileSignature(fileId, existingSignatureId)",
|
|
1625
1625
|
optionalArgs: ["existingSignatureId"],
|
|
1626
1626
|
description:
|
|
1627
|
-
"Drive a BankID signing flow for one Filestore file
|
|
1628
|
-
"
|
|
1629
|
-
"
|
|
1630
|
-
"
|
|
1627
|
+
"Drive a BankID signing flow for one Filestore file of ANY type in a " +
|
|
1628
|
+
"shared (project or public) space; a personal file is refused with " +
|
|
1629
|
+
"NOT_SIGNABLE. Outside a signing round the signature is kept beside " +
|
|
1630
|
+
"the file and the file is not changed; a PDF in an open signing round " +
|
|
1631
|
+
"gets the signature added to the document itself, which is then " +
|
|
1632
|
+
"sealed against changes (see useSigningRounds). initiate() opens a " +
|
|
1633
|
+
"sign order (the backend hashes the bytes + binds the digest). The " +
|
|
1634
|
+
"hook does NOT poll by itself: while status is 'pending' call " +
|
|
1635
|
+
"refresh() every 2 seconds (a setInterval in an effect, cleared when " +
|
|
1636
|
+
"status leaves 'pending' or the widget unmounts); each answer carries " +
|
|
1637
|
+
"a fresh qr frame. Offer both ways to sign: BankID on THIS device is a " +
|
|
1638
|
+
"button whose onPress calls Linking.openURL(bankIdAppUrl(autoStartToken)), " +
|
|
1639
|
+
"shown only while autoStartToken is set; BankID on ANOTHER device is " +
|
|
1640
|
+
"the qr rendered with the Image primitive, replaced by each poll's " +
|
|
1641
|
+
"frame. cancel() aborts, verify() returns the offline verdict: valid " +
|
|
1642
|
+
"is the BankID signature itself; content_status 'unchanged' means the " +
|
|
1643
|
+
"file still holds what was signed, 'changed' that it was altered " +
|
|
1644
|
+
"since, 'unavailable' that it could not be checked now; " +
|
|
1645
|
+
"signed_by_others_since counts later signatures in a round's PDF; " +
|
|
1646
|
+
"seal.status is 'valid' (protected against changes), 'invalid' or " +
|
|
1647
|
+
"'certificate_revoked' (do not rely on the copy), 'unavailable' (try " +
|
|
1648
|
+
"later) or 'not_applicable' (a signature kept beside the file). Say " +
|
|
1649
|
+
"each in plain words and never show a green mark when any of them " +
|
|
1650
|
+
"failed. Pass " +
|
|
1631
1651
|
"existingSignatureId for an already-signed file to seed the complete " +
|
|
1632
1652
|
"state + verify() without re-signing. autoStartToken is set only while " +
|
|
1633
1653
|
"opening BankID can still help (null once the user has signed or the " +
|
|
@@ -1644,19 +1664,21 @@ const HOOKS = [
|
|
|
1644
1664
|
"ctx.filestore.signatures.{initiate,status,cancel,verify}.",
|
|
1645
1665
|
returnShape: {
|
|
1646
1666
|
status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
|
|
1647
|
-
qr: "string | null // PNG data-URL of the animated BankID QR",
|
|
1648
|
-
autoStartToken: "string | null",
|
|
1667
|
+
qr: "string | null // PNG data-URL of the animated BankID QR; render with <Image>, refresh() every 2 s while pending for the next frame",
|
|
1668
|
+
autoStartToken: "string | null // BankID on this device: a button calling Linking.openURL(bankIdAppUrl(autoStartToken))",
|
|
1649
1669
|
message: "string | null",
|
|
1650
1670
|
signerName: "string | null",
|
|
1651
1671
|
signedAt: "string | null",
|
|
1652
1672
|
hintCode: "string | null // where the order stands, e.g. 'sealing'",
|
|
1653
1673
|
nameRefusal:
|
|
1654
1674
|
"{ reason: 'empty' | 'too_long' | 'unsupported_characters' | 'mixed_scripts', characters: string[] } | null",
|
|
1655
|
-
verdict:
|
|
1675
|
+
verdict:
|
|
1676
|
+
"{ valid, checks, content_status: 'unchanged' | 'changed' | 'unavailable', signed_by_others_since: number | null, " +
|
|
1677
|
+
"seal: { status: 'valid' | 'invalid' | 'certificate_revoked' | 'unavailable' | 'not_applicable', sealed_at, certificate_revoked_at, ... }, ... } | null",
|
|
1656
1678
|
loading: "boolean",
|
|
1657
1679
|
error: "PermissionError | null // { code, message, retryable }",
|
|
1658
1680
|
initiate: "() => Promise<{ signature_id, qr, auto_start_token, status, hint_code?, message? }>",
|
|
1659
|
-
refresh: "() => Promise<void>",
|
|
1681
|
+
refresh: "() => Promise<void> // the hook never polls by itself",
|
|
1660
1682
|
cancel: "() => Promise<{ status, hint_code?, message?, signer_name?, signed_at? } | undefined>",
|
|
1661
1683
|
verify: "() => Promise<verdict>",
|
|
1662
1684
|
},
|
|
@@ -1667,10 +1689,14 @@ const HOOKS = [
|
|
|
1667
1689
|
name: "useFileSignatures",
|
|
1668
1690
|
signature: "useFileSignatures(fileIds)",
|
|
1669
1691
|
description:
|
|
1670
|
-
"Batch '
|
|
1671
|
-
"of each
|
|
1672
|
-
"
|
|
1673
|
-
"
|
|
1692
|
+
"Batch 'have I signed this file?' lookup for a set of file ids. Returns " +
|
|
1693
|
+
"a map of each file the SIGNED-IN USER has signed (and can still see) " +
|
|
1694
|
+
"to their own latest signature; a file only others have signed, or " +
|
|
1695
|
+
"nobody has, is absent, so it never tells who else signed. Pass a " +
|
|
1696
|
+
"file's signature_id to useFileSignature(fileId, signatureId) to show " +
|
|
1697
|
+
"and verify it. Who has signed is useFileRoster(), for someone who " +
|
|
1698
|
+
"manages the folder. Reads ctx.filestore.signatures.list and unwraps " +
|
|
1699
|
+
"{ data, meta }.",
|
|
1674
1700
|
returnShape: {
|
|
1675
1701
|
signaturesByFileId:
|
|
1676
1702
|
"{ [fileId]: { signature_id, signer_name, signed_at } }",
|
|
@@ -1709,8 +1735,10 @@ const HOOKS = [
|
|
|
1709
1735
|
signature: "useSigningRounds(fileIds)",
|
|
1710
1736
|
description:
|
|
1711
1737
|
"What the signed-in user may know about the signing round of each file " +
|
|
1712
|
-
"in a list, in one batched request. A signing round fixes a PDF
|
|
1713
|
-
"list of signers
|
|
1738
|
+
"in a list, in one batched request. A signing round fixes a PDF in the " +
|
|
1739
|
+
"project space and a list of signers (other file types are signed one " +
|
|
1740
|
+
"person at a time with useFileSignature() and no round): it is " +
|
|
1741
|
+
"prepared and confirmed by someone who manages " +
|
|
1714
1742
|
"the file's folder, then each signer signs once with useFileSignature(), " +
|
|
1715
1743
|
"in any order (there are no turns; the list only orders the places on " +
|
|
1716
1744
|
"the signature page). byFileId maps a file id to { file_id, can_manage, " +
|
|
@@ -1719,7 +1747,8 @@ const HOOKS = [
|
|
|
1719
1747
|
"manager nor one of its signers; a signer sees it once it is confirmed, " +
|
|
1720
1748
|
"with their own place (my_place) and can_sign (the round is open and " +
|
|
1721
1749
|
"their place is unsigned), and signed_count is null " +
|
|
1722
|
-
"for them.
|
|
1750
|
+
"for them. Each signature is added to the PDF itself and the document " +
|
|
1751
|
+
"is sealed against changes. locked is 'signing_round' while the file cannot be changed, " +
|
|
1723
1752
|
"'signed' once its content cannot be replaced. roundsEnabled is false " +
|
|
1724
1753
|
"when the workspace cannot start signing rounds (offer no way to start " +
|
|
1725
1754
|
"one), and null until the first answer. can_prepare is about the file " +
|
|
@@ -1747,15 +1776,27 @@ const HOOKS = [
|
|
|
1747
1776
|
signature: "useSigningRound(fileId)",
|
|
1748
1777
|
description:
|
|
1749
1778
|
"One file's current signing round, and the actions of someone who " +
|
|
1750
|
-
"manages the file's folder. prepare(
|
|
1751
|
-
"
|
|
1752
|
-
"
|
|
1779
|
+
"manages the file's folder. prepare(signers) drafts a round for those " +
|
|
1780
|
+
"people, their places on the signature page in the order given: each " +
|
|
1781
|
+
"entry is an account id from useFileRoster() (or { app_user_id }), or " +
|
|
1782
|
+
"{ name, email } for someone without an account, who is emailed a " +
|
|
1783
|
+
"personal BankID signing link valid for 24 hours once the round is " +
|
|
1784
|
+
"confirmed (anyone holding that link can sign, so say so before " +
|
|
1785
|
+
"sending); it can take " +
|
|
1753
1786
|
"up to a minute. preparedFile() returns a five-minute link to the " +
|
|
1754
1787
|
"prepared document; show it to the user before confirming. confirm() " +
|
|
1755
1788
|
"opens the round for exactly the document preparedFile() last returned, " +
|
|
1756
1789
|
"and rejects with code PREVIEW_REQUIRED when it was not called first. " +
|
|
1757
1790
|
"cancel() ends a draft or an open round and resolves to the cancelled " +
|
|
1758
|
-
"round (status 'CANCELLED'); round is then null.
|
|
1791
|
+
"round (status 'CANCELLED'); round is then null. " +
|
|
1792
|
+
"resendLink(signerId, { email }) replaces an email place's link with " +
|
|
1793
|
+
"a new one (the old one stops working), sent to the corrected email " +
|
|
1794
|
+
"when given, and resolves to the round; offer it only where the " +
|
|
1795
|
+
"place's link.can_resend is true. A manager sees each place's id, kind " +
|
|
1796
|
+
"('app_user' | 'email'), email, signed_as (the name BankID verified) " +
|
|
1797
|
+
"and link ({ status: 'sending' | 'sent' | 'delivery_failed' | " +
|
|
1798
|
+
"'expired' | 'used' | 'cancelled', sent_at, expires_at, used_at, " +
|
|
1799
|
+
"links_sent, can_resend }, null for an account place). ONE ACTION RUNS AT A " +
|
|
1759
1800
|
"TIME: while busy is true any other action is refused at once with code " +
|
|
1760
1801
|
"ACTION_IN_PROGRESS (it is not queued), so disable your buttons on busy. " +
|
|
1761
1802
|
"round is the full round as the " +
|
|
@@ -1763,27 +1804,40 @@ const HOOKS = [
|
|
|
1763
1804
|
"every signer's name and position and their own progress only, anyone " +
|
|
1764
1805
|
"else sees null. A refused action sets error and rejects with it: code " +
|
|
1765
1806
|
"and message come from the server (show message as it is), errors has " +
|
|
1766
|
-
"one entry per refused signer ({ app_user_id, message, reason, " +
|
|
1767
|
-
"characters }
|
|
1807
|
+
"one entry per refused signer ({ index, app_user_id, message, reason, " +
|
|
1808
|
+
"characters }; index is the entry's place in the list sent), " +
|
|
1809
|
+
"retryAfter is the seconds to wait. resendLink is refused with " +
|
|
1810
|
+
"SIGNING_LINK_RESEND_TOO_SOON (with retryAfter), " +
|
|
1811
|
+
"SIGNING_LINK_RESEND_IN_PROGRESS, SIGNING_LINK_LIMIT_REACHED, " +
|
|
1812
|
+
"SIGNING_LINK_BUDGET_EXCEEDED, SIGNING_LINK_PLATFORM_LIMIT, " +
|
|
1813
|
+
"INVALID_EMAIL, DUPLICATE_SIGNER, SIGNING_ROUND_NOT_OPEN, " +
|
|
1814
|
+
"SIGNING_ROUND_CLOSED or ALREADY_SIGNED, and by the hook itself with " +
|
|
1815
|
+
"NOT_SUPPORTED when the host's filestore client cannot send links. The hook's own " +
|
|
1768
1816
|
"refusals (PREVIEW_REQUIRED, ACTION_IN_PROGRESS, NO_SIGNING_ROUND) carry " +
|
|
1769
1817
|
"a message that can be shown the same way. When a refusal means the " +
|
|
1770
1818
|
"round is no longer what was shown (it expired, was closed, or the " +
|
|
1771
1819
|
"document changed) the hook reads the round again by itself. Code " +
|
|
1772
1820
|
"SIGNING_ROUNDS_NOT_ENABLED means the workspace cannot start signing " +
|
|
1773
1821
|
"rounds; check roundsEnabled from useSigningRounds() before offering to " +
|
|
1774
|
-
"start one
|
|
1822
|
+
"start one, and when it is false say that document signing is not " +
|
|
1823
|
+
"turned on for this workspace and that the platform turns it on. " +
|
|
1824
|
+
"Rounds take PDFs in the project space only (NOT_A_PDF otherwise). A " +
|
|
1825
|
+
"signer signs their place with useFileSignature(fileId), the same " +
|
|
1826
|
+
"BankID flow as any file. When fileId or the signed-in user changes, round is null " +
|
|
1775
1827
|
"and loading true until the new answer. Reads ctx.filestore.signingRounds.",
|
|
1776
1828
|
returnShape: {
|
|
1777
1829
|
round:
|
|
1778
1830
|
"{ id, file_id, status: 'DRAFT' | 'OPEN' | 'COMPLETED', created_at, confirmed_at, completed_at, expires_at, " +
|
|
1779
|
-
"signers: { name, position, app_user_id?, signed?, signed_at?, signed_as
|
|
1831
|
+
"signers: { name, position, app_user_id?, signed?, signed_at?, signed_as?, id?, kind?: 'app_user' | 'email', email?, " +
|
|
1832
|
+
"link?: { status: 'sending' | 'sent' | 'delivery_failed' | 'expired' | 'used' | 'cancelled', sent_at, expires_at, used_at, links_sent, can_resend } | null }[] } | null",
|
|
1780
1833
|
loading: "boolean",
|
|
1781
1834
|
error: "PermissionError | null // { code, message, retryable, errors, retryAfter }",
|
|
1782
1835
|
refresh: "() => Promise<void>",
|
|
1783
|
-
prepare: "(
|
|
1836
|
+
prepare: "(signers: (string | { app_user_id } | { name, email })[]) => Promise<round> // status 'DRAFT'",
|
|
1784
1837
|
preparedFile: "() => Promise<{ url, sha256, expires_at, size_bytes }>",
|
|
1785
1838
|
confirm: "() => Promise<round> // status 'OPEN'",
|
|
1786
1839
|
cancel: "() => Promise<round> // the round it ended, status 'CANCELLED'",
|
|
1840
|
+
resendLink: "(signerId: string, options?: { email?: string }) => Promise<round> // a new link for an email place",
|
|
1787
1841
|
busy: "boolean // an action is running; another one is refused with ACTION_IN_PROGRESS",
|
|
1788
1842
|
},
|
|
1789
1843
|
requiredContextSlice: ["filestore.signingRounds"],
|
|
@@ -2598,8 +2652,9 @@ const HOOKS = [
|
|
|
2598
2652
|
"with the Image primitive) and the hook then POLLS to completion itself " +
|
|
2599
2653
|
"(pollIntervalMs, default 1000; pass 0 to drive refresh() yourself); " +
|
|
2600
2654
|
"cancel() aborts; reset() clears the flow. autoStartToken opens the " +
|
|
2601
|
-
"provider app on the SAME device (
|
|
2602
|
-
"
|
|
2655
|
+
"provider app on the SAME device: Linking.openURL(bankIdAppUrl(" +
|
|
2656
|
+
"autoStartToken)) opens bankid:///?autostarttoken=<token>&redirect=null. " +
|
|
2657
|
+
"On complete, `identity` is { provider, name, given_name, " +
|
|
2603
2658
|
"surname, personal_number_masked, subject_hash, identified_at } — there is " +
|
|
2604
2659
|
"NO raw personal number by design: write personal_number_masked " +
|
|
2605
2660
|
"(\"19900101-****\") and, if you need to trace the proof, identificationId. " +
|
|
@@ -3892,7 +3947,7 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
3892
3947
|
"{ files: { list(query) -> Promise<{ data, meta }>, get(id), upload(formData), update(id, body), copy(id, body), remove(id), preview(id) }, " +
|
|
3893
3948
|
"folders: { list, create, update, remove }, shares: { ... }, trash: { ... }, " +
|
|
3894
3949
|
"signatures: { list(fileIds), roster(fileId, query), initiate(fileId), status(id), cancel(id), verify(id) }, " +
|
|
3895
|
-
"signingRounds: { overview(fileIds), list(fileId, query), get(roundId), prepare(fileId,
|
|
3950
|
+
"signingRounds: { overview(fileIds), list(fileId, query), get(roundId), prepare(fileId, signers), preparedFile(roundId), confirm(roundId, preparedSha256), cancel(roundId), resendLink(roundId, signerId, { email }) }, " +
|
|
3896
3951
|
"objectUrl(token), fetchObject(token) }. " +
|
|
3897
3952
|
"Backs useFilestoreFiles() + useFilestoreFolders() + useFilestoreCreateFolder() + useFilestoreCopy() + useFileSignature() + useFileSignatures() + useFileRoster() + useSigningRounds() + useSigningRound(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
3898
3953
|
required: false,
|
|
@@ -6092,7 +6147,15 @@ const CONTRACT = deepFreeze({
|
|
|
6092
6147
|
// `sectionRef` propertySchema type gives the author a section picker.
|
|
6093
6148
|
// 1.194.0: more permissive (sc-11945) — a datastoreTemplate takes 100
|
|
6094
6149
|
// tables, 200 columns, 100 rows per table, 20000 cells and 32 roles.
|
|
6095
|
-
|
|
6150
|
+
// 1.195.0: additive (sc-11879) — `useSigningRound().prepare` takes
|
|
6151
|
+
// `{ name, email }` entries for signers without an account, and
|
|
6152
|
+
// `resendLink(signerId, { email })` sends such a signer a new link.
|
|
6153
|
+
// The manager's places carry id, kind, email and link.
|
|
6154
|
+
// 1.197.0: additive (sc-12063) — new export `bankIdAppUrl(autoStartToken)`,
|
|
6155
|
+
// the link that opens BankID on this device. Signing hooks document
|
|
6156
|
+
// polling, the two ways to start BankID and how to read a verdict;
|
|
6157
|
+
// useFileSignatures is described as the caller's own signatures.
|
|
6158
|
+
version: "1.197.0",
|
|
6096
6159
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6097
6160
|
hooks: HOOKS,
|
|
6098
6161
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -1624,10 +1624,30 @@ const HOOKS = [
|
|
|
1624
1624
|
signature: "useFileSignature(fileId, existingSignatureId)",
|
|
1625
1625
|
optionalArgs: ["existingSignatureId"],
|
|
1626
1626
|
description:
|
|
1627
|
-
"Drive a BankID signing flow for one Filestore file
|
|
1628
|
-
"
|
|
1629
|
-
"
|
|
1630
|
-
"
|
|
1627
|
+
"Drive a BankID signing flow for one Filestore file of ANY type in a " +
|
|
1628
|
+
"shared (project or public) space; a personal file is refused with " +
|
|
1629
|
+
"NOT_SIGNABLE. Outside a signing round the signature is kept beside " +
|
|
1630
|
+
"the file and the file is not changed; a PDF in an open signing round " +
|
|
1631
|
+
"gets the signature added to the document itself, which is then " +
|
|
1632
|
+
"sealed against changes (see useSigningRounds). initiate() opens a " +
|
|
1633
|
+
"sign order (the backend hashes the bytes + binds the digest). The " +
|
|
1634
|
+
"hook does NOT poll by itself: while status is 'pending' call " +
|
|
1635
|
+
"refresh() every 2 seconds (a setInterval in an effect, cleared when " +
|
|
1636
|
+
"status leaves 'pending' or the widget unmounts); each answer carries " +
|
|
1637
|
+
"a fresh qr frame. Offer both ways to sign: BankID on THIS device is a " +
|
|
1638
|
+
"button whose onPress calls Linking.openURL(bankIdAppUrl(autoStartToken)), " +
|
|
1639
|
+
"shown only while autoStartToken is set; BankID on ANOTHER device is " +
|
|
1640
|
+
"the qr rendered with the Image primitive, replaced by each poll's " +
|
|
1641
|
+
"frame. cancel() aborts, verify() returns the offline verdict: valid " +
|
|
1642
|
+
"is the BankID signature itself; content_status 'unchanged' means the " +
|
|
1643
|
+
"file still holds what was signed, 'changed' that it was altered " +
|
|
1644
|
+
"since, 'unavailable' that it could not be checked now; " +
|
|
1645
|
+
"signed_by_others_since counts later signatures in a round's PDF; " +
|
|
1646
|
+
"seal.status is 'valid' (protected against changes), 'invalid' or " +
|
|
1647
|
+
"'certificate_revoked' (do not rely on the copy), 'unavailable' (try " +
|
|
1648
|
+
"later) or 'not_applicable' (a signature kept beside the file). Say " +
|
|
1649
|
+
"each in plain words and never show a green mark when any of them " +
|
|
1650
|
+
"failed. Pass " +
|
|
1631
1651
|
"existingSignatureId for an already-signed file to seed the complete " +
|
|
1632
1652
|
"state + verify() without re-signing. autoStartToken is set only while " +
|
|
1633
1653
|
"opening BankID can still help (null once the user has signed or the " +
|
|
@@ -1644,19 +1664,21 @@ const HOOKS = [
|
|
|
1644
1664
|
"ctx.filestore.signatures.{initiate,status,cancel,verify}.",
|
|
1645
1665
|
returnShape: {
|
|
1646
1666
|
status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
|
|
1647
|
-
qr: "string | null // PNG data-URL of the animated BankID QR",
|
|
1648
|
-
autoStartToken: "string | null",
|
|
1667
|
+
qr: "string | null // PNG data-URL of the animated BankID QR; render with <Image>, refresh() every 2 s while pending for the next frame",
|
|
1668
|
+
autoStartToken: "string | null // BankID on this device: a button calling Linking.openURL(bankIdAppUrl(autoStartToken))",
|
|
1649
1669
|
message: "string | null",
|
|
1650
1670
|
signerName: "string | null",
|
|
1651
1671
|
signedAt: "string | null",
|
|
1652
1672
|
hintCode: "string | null // where the order stands, e.g. 'sealing'",
|
|
1653
1673
|
nameRefusal:
|
|
1654
1674
|
"{ reason: 'empty' | 'too_long' | 'unsupported_characters' | 'mixed_scripts', characters: string[] } | null",
|
|
1655
|
-
verdict:
|
|
1675
|
+
verdict:
|
|
1676
|
+
"{ valid, checks, content_status: 'unchanged' | 'changed' | 'unavailable', signed_by_others_since: number | null, " +
|
|
1677
|
+
"seal: { status: 'valid' | 'invalid' | 'certificate_revoked' | 'unavailable' | 'not_applicable', sealed_at, certificate_revoked_at, ... }, ... } | null",
|
|
1656
1678
|
loading: "boolean",
|
|
1657
1679
|
error: "PermissionError | null // { code, message, retryable }",
|
|
1658
1680
|
initiate: "() => Promise<{ signature_id, qr, auto_start_token, status, hint_code?, message? }>",
|
|
1659
|
-
refresh: "() => Promise<void>",
|
|
1681
|
+
refresh: "() => Promise<void> // the hook never polls by itself",
|
|
1660
1682
|
cancel: "() => Promise<{ status, hint_code?, message?, signer_name?, signed_at? } | undefined>",
|
|
1661
1683
|
verify: "() => Promise<verdict>",
|
|
1662
1684
|
},
|
|
@@ -1667,10 +1689,14 @@ const HOOKS = [
|
|
|
1667
1689
|
name: "useFileSignatures",
|
|
1668
1690
|
signature: "useFileSignatures(fileIds)",
|
|
1669
1691
|
description:
|
|
1670
|
-
"Batch '
|
|
1671
|
-
"of each
|
|
1672
|
-
"
|
|
1673
|
-
"
|
|
1692
|
+
"Batch 'have I signed this file?' lookup for a set of file ids. Returns " +
|
|
1693
|
+
"a map of each file the SIGNED-IN USER has signed (and can still see) " +
|
|
1694
|
+
"to their own latest signature; a file only others have signed, or " +
|
|
1695
|
+
"nobody has, is absent, so it never tells who else signed. Pass a " +
|
|
1696
|
+
"file's signature_id to useFileSignature(fileId, signatureId) to show " +
|
|
1697
|
+
"and verify it. Who has signed is useFileRoster(), for someone who " +
|
|
1698
|
+
"manages the folder. Reads ctx.filestore.signatures.list and unwraps " +
|
|
1699
|
+
"{ data, meta }.",
|
|
1674
1700
|
returnShape: {
|
|
1675
1701
|
signaturesByFileId:
|
|
1676
1702
|
"{ [fileId]: { signature_id, signer_name, signed_at } }",
|
|
@@ -1709,8 +1735,10 @@ const HOOKS = [
|
|
|
1709
1735
|
signature: "useSigningRounds(fileIds)",
|
|
1710
1736
|
description:
|
|
1711
1737
|
"What the signed-in user may know about the signing round of each file " +
|
|
1712
|
-
"in a list, in one batched request. A signing round fixes a PDF
|
|
1713
|
-
"list of signers
|
|
1738
|
+
"in a list, in one batched request. A signing round fixes a PDF in the " +
|
|
1739
|
+
"project space and a list of signers (other file types are signed one " +
|
|
1740
|
+
"person at a time with useFileSignature() and no round): it is " +
|
|
1741
|
+
"prepared and confirmed by someone who manages " +
|
|
1714
1742
|
"the file's folder, then each signer signs once with useFileSignature(), " +
|
|
1715
1743
|
"in any order (there are no turns; the list only orders the places on " +
|
|
1716
1744
|
"the signature page). byFileId maps a file id to { file_id, can_manage, " +
|
|
@@ -1719,7 +1747,8 @@ const HOOKS = [
|
|
|
1719
1747
|
"manager nor one of its signers; a signer sees it once it is confirmed, " +
|
|
1720
1748
|
"with their own place (my_place) and can_sign (the round is open and " +
|
|
1721
1749
|
"their place is unsigned), and signed_count is null " +
|
|
1722
|
-
"for them.
|
|
1750
|
+
"for them. Each signature is added to the PDF itself and the document " +
|
|
1751
|
+
"is sealed against changes. locked is 'signing_round' while the file cannot be changed, " +
|
|
1723
1752
|
"'signed' once its content cannot be replaced. roundsEnabled is false " +
|
|
1724
1753
|
"when the workspace cannot start signing rounds (offer no way to start " +
|
|
1725
1754
|
"one), and null until the first answer. can_prepare is about the file " +
|
|
@@ -1747,15 +1776,27 @@ const HOOKS = [
|
|
|
1747
1776
|
signature: "useSigningRound(fileId)",
|
|
1748
1777
|
description:
|
|
1749
1778
|
"One file's current signing round, and the actions of someone who " +
|
|
1750
|
-
"manages the file's folder. prepare(
|
|
1751
|
-
"
|
|
1752
|
-
"
|
|
1779
|
+
"manages the file's folder. prepare(signers) drafts a round for those " +
|
|
1780
|
+
"people, their places on the signature page in the order given: each " +
|
|
1781
|
+
"entry is an account id from useFileRoster() (or { app_user_id }), or " +
|
|
1782
|
+
"{ name, email } for someone without an account, who is emailed a " +
|
|
1783
|
+
"personal BankID signing link valid for 24 hours once the round is " +
|
|
1784
|
+
"confirmed (anyone holding that link can sign, so say so before " +
|
|
1785
|
+
"sending); it can take " +
|
|
1753
1786
|
"up to a minute. preparedFile() returns a five-minute link to the " +
|
|
1754
1787
|
"prepared document; show it to the user before confirming. confirm() " +
|
|
1755
1788
|
"opens the round for exactly the document preparedFile() last returned, " +
|
|
1756
1789
|
"and rejects with code PREVIEW_REQUIRED when it was not called first. " +
|
|
1757
1790
|
"cancel() ends a draft or an open round and resolves to the cancelled " +
|
|
1758
|
-
"round (status 'CANCELLED'); round is then null.
|
|
1791
|
+
"round (status 'CANCELLED'); round is then null. " +
|
|
1792
|
+
"resendLink(signerId, { email }) replaces an email place's link with " +
|
|
1793
|
+
"a new one (the old one stops working), sent to the corrected email " +
|
|
1794
|
+
"when given, and resolves to the round; offer it only where the " +
|
|
1795
|
+
"place's link.can_resend is true. A manager sees each place's id, kind " +
|
|
1796
|
+
"('app_user' | 'email'), email, signed_as (the name BankID verified) " +
|
|
1797
|
+
"and link ({ status: 'sending' | 'sent' | 'delivery_failed' | " +
|
|
1798
|
+
"'expired' | 'used' | 'cancelled', sent_at, expires_at, used_at, " +
|
|
1799
|
+
"links_sent, can_resend }, null for an account place). ONE ACTION RUNS AT A " +
|
|
1759
1800
|
"TIME: while busy is true any other action is refused at once with code " +
|
|
1760
1801
|
"ACTION_IN_PROGRESS (it is not queued), so disable your buttons on busy. " +
|
|
1761
1802
|
"round is the full round as the " +
|
|
@@ -1763,27 +1804,40 @@ const HOOKS = [
|
|
|
1763
1804
|
"every signer's name and position and their own progress only, anyone " +
|
|
1764
1805
|
"else sees null. A refused action sets error and rejects with it: code " +
|
|
1765
1806
|
"and message come from the server (show message as it is), errors has " +
|
|
1766
|
-
"one entry per refused signer ({ app_user_id, message, reason, " +
|
|
1767
|
-
"characters }
|
|
1807
|
+
"one entry per refused signer ({ index, app_user_id, message, reason, " +
|
|
1808
|
+
"characters }; index is the entry's place in the list sent), " +
|
|
1809
|
+
"retryAfter is the seconds to wait. resendLink is refused with " +
|
|
1810
|
+
"SIGNING_LINK_RESEND_TOO_SOON (with retryAfter), " +
|
|
1811
|
+
"SIGNING_LINK_RESEND_IN_PROGRESS, SIGNING_LINK_LIMIT_REACHED, " +
|
|
1812
|
+
"SIGNING_LINK_BUDGET_EXCEEDED, SIGNING_LINK_PLATFORM_LIMIT, " +
|
|
1813
|
+
"INVALID_EMAIL, DUPLICATE_SIGNER, SIGNING_ROUND_NOT_OPEN, " +
|
|
1814
|
+
"SIGNING_ROUND_CLOSED or ALREADY_SIGNED, and by the hook itself with " +
|
|
1815
|
+
"NOT_SUPPORTED when the host's filestore client cannot send links. The hook's own " +
|
|
1768
1816
|
"refusals (PREVIEW_REQUIRED, ACTION_IN_PROGRESS, NO_SIGNING_ROUND) carry " +
|
|
1769
1817
|
"a message that can be shown the same way. When a refusal means the " +
|
|
1770
1818
|
"round is no longer what was shown (it expired, was closed, or the " +
|
|
1771
1819
|
"document changed) the hook reads the round again by itself. Code " +
|
|
1772
1820
|
"SIGNING_ROUNDS_NOT_ENABLED means the workspace cannot start signing " +
|
|
1773
1821
|
"rounds; check roundsEnabled from useSigningRounds() before offering to " +
|
|
1774
|
-
"start one
|
|
1822
|
+
"start one, and when it is false say that document signing is not " +
|
|
1823
|
+
"turned on for this workspace and that the platform turns it on. " +
|
|
1824
|
+
"Rounds take PDFs in the project space only (NOT_A_PDF otherwise). A " +
|
|
1825
|
+
"signer signs their place with useFileSignature(fileId), the same " +
|
|
1826
|
+
"BankID flow as any file. When fileId or the signed-in user changes, round is null " +
|
|
1775
1827
|
"and loading true until the new answer. Reads ctx.filestore.signingRounds.",
|
|
1776
1828
|
returnShape: {
|
|
1777
1829
|
round:
|
|
1778
1830
|
"{ id, file_id, status: 'DRAFT' | 'OPEN' | 'COMPLETED', created_at, confirmed_at, completed_at, expires_at, " +
|
|
1779
|
-
"signers: { name, position, app_user_id?, signed?, signed_at?, signed_as
|
|
1831
|
+
"signers: { name, position, app_user_id?, signed?, signed_at?, signed_as?, id?, kind?: 'app_user' | 'email', email?, " +
|
|
1832
|
+
"link?: { status: 'sending' | 'sent' | 'delivery_failed' | 'expired' | 'used' | 'cancelled', sent_at, expires_at, used_at, links_sent, can_resend } | null }[] } | null",
|
|
1780
1833
|
loading: "boolean",
|
|
1781
1834
|
error: "PermissionError | null // { code, message, retryable, errors, retryAfter }",
|
|
1782
1835
|
refresh: "() => Promise<void>",
|
|
1783
|
-
prepare: "(
|
|
1836
|
+
prepare: "(signers: (string | { app_user_id } | { name, email })[]) => Promise<round> // status 'DRAFT'",
|
|
1784
1837
|
preparedFile: "() => Promise<{ url, sha256, expires_at, size_bytes }>",
|
|
1785
1838
|
confirm: "() => Promise<round> // status 'OPEN'",
|
|
1786
1839
|
cancel: "() => Promise<round> // the round it ended, status 'CANCELLED'",
|
|
1840
|
+
resendLink: "(signerId: string, options?: { email?: string }) => Promise<round> // a new link for an email place",
|
|
1787
1841
|
busy: "boolean // an action is running; another one is refused with ACTION_IN_PROGRESS",
|
|
1788
1842
|
},
|
|
1789
1843
|
requiredContextSlice: ["filestore.signingRounds"],
|
|
@@ -2598,8 +2652,9 @@ const HOOKS = [
|
|
|
2598
2652
|
"with the Image primitive) and the hook then POLLS to completion itself " +
|
|
2599
2653
|
"(pollIntervalMs, default 1000; pass 0 to drive refresh() yourself); " +
|
|
2600
2654
|
"cancel() aborts; reset() clears the flow. autoStartToken opens the " +
|
|
2601
|
-
"provider app on the SAME device (
|
|
2602
|
-
"
|
|
2655
|
+
"provider app on the SAME device: Linking.openURL(bankIdAppUrl(" +
|
|
2656
|
+
"autoStartToken)) opens bankid:///?autostarttoken=<token>&redirect=null. " +
|
|
2657
|
+
"On complete, `identity` is { provider, name, given_name, " +
|
|
2603
2658
|
"surname, personal_number_masked, subject_hash, identified_at } — there is " +
|
|
2604
2659
|
"NO raw personal number by design: write personal_number_masked " +
|
|
2605
2660
|
"(\"19900101-****\") and, if you need to trace the proof, identificationId. " +
|
|
@@ -3888,7 +3943,7 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
3888
3943
|
"{ files: { list(query) -> Promise<{ data, meta }>, get(id), upload(formData), update(id, body), copy(id, body), remove(id), preview(id) }, " +
|
|
3889
3944
|
"folders: { list, create, update, remove }, shares: { ... }, trash: { ... }, " +
|
|
3890
3945
|
"signatures: { list(fileIds), roster(fileId, query), initiate(fileId), status(id), cancel(id), verify(id) }, " +
|
|
3891
|
-
"signingRounds: { overview(fileIds), list(fileId, query), get(roundId), prepare(fileId,
|
|
3946
|
+
"signingRounds: { overview(fileIds), list(fileId, query), get(roundId), prepare(fileId, signers), preparedFile(roundId), confirm(roundId, preparedSha256), cancel(roundId), resendLink(roundId, signerId, { email }) }, " +
|
|
3892
3947
|
"objectUrl(token), fetchObject(token) }. " +
|
|
3893
3948
|
"Backs useFilestoreFiles() + useFilestoreFolders() + useFilestoreCreateFolder() + useFilestoreCopy() + useFileSignature() + useFileSignatures() + useFileRoster() + useSigningRounds() + useSigningRound(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
3894
3949
|
required: false,
|
|
@@ -6088,7 +6143,15 @@ const CONTRACT = deepFreeze({
|
|
|
6088
6143
|
// `sectionRef` propertySchema type gives the author a section picker.
|
|
6089
6144
|
// 1.194.0: more permissive (sc-11945) — a datastoreTemplate takes 100
|
|
6090
6145
|
// tables, 200 columns, 100 rows per table, 20000 cells and 32 roles.
|
|
6091
|
-
|
|
6146
|
+
// 1.195.0: additive (sc-11879) — `useSigningRound().prepare` takes
|
|
6147
|
+
// `{ name, email }` entries for signers without an account, and
|
|
6148
|
+
// `resendLink(signerId, { email })` sends such a signer a new link.
|
|
6149
|
+
// The manager's places carry id, kind, email and link.
|
|
6150
|
+
// 1.197.0: additive (sc-12063) — new export `bankIdAppUrl(autoStartToken)`,
|
|
6151
|
+
// the link that opens BankID on this device. Signing hooks document
|
|
6152
|
+
// polling, the two ways to start BankID and how to read a verdict;
|
|
6153
|
+
// useFileSignatures is described as the caller's own signatures.
|
|
6154
|
+
version: "1.197.0",
|
|
6092
6155
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6093
6156
|
hooks: HOOKS,
|
|
6094
6157
|
primitives: PRIMITIVES,
|
package/dist/index.d.ts
CHANGED
|
@@ -5065,6 +5065,13 @@ export function useFileSignature(
|
|
|
5065
5065
|
verify(): Promise<FileSignatureVerdict | null>;
|
|
5066
5066
|
};
|
|
5067
5067
|
|
|
5068
|
+
/**
|
|
5069
|
+
* The link that opens the BankID app on this device for a pending order:
|
|
5070
|
+
* `Linking.openURL(bankIdAppUrl(autoStartToken))` from a button. For BankID
|
|
5071
|
+
* on another device, show the order's `qr` instead.
|
|
5072
|
+
*/
|
|
5073
|
+
export function bankIdAppUrl(autoStartToken: string): string;
|
|
5074
|
+
|
|
5068
5075
|
export interface FileSignatureSummary {
|
|
5069
5076
|
signature_id: string;
|
|
5070
5077
|
subject_file_id: string;
|
|
@@ -5111,6 +5118,9 @@ export interface SigningRefusalEntry {
|
|
|
5111
5118
|
field?: string;
|
|
5112
5119
|
message: string;
|
|
5113
5120
|
code?: string;
|
|
5121
|
+
/** The entry's place in the list of signers that was sent. */
|
|
5122
|
+
index?: number;
|
|
5123
|
+
/** Set when the entry names an account. */
|
|
5114
5124
|
app_user_id?: string;
|
|
5115
5125
|
reason?: SignatureNameRefusal["reason"];
|
|
5116
5126
|
characters?: string[];
|
|
@@ -5171,14 +5181,43 @@ export function useSigningRounds(fileIds: string[] | null | undefined): {
|
|
|
5171
5181
|
export interface SigningRoundSigner {
|
|
5172
5182
|
name: string;
|
|
5173
5183
|
position: number;
|
|
5174
|
-
/**
|
|
5175
|
-
|
|
5184
|
+
/**
|
|
5185
|
+
* Present for every place when the user manages the folder; otherwise for
|
|
5186
|
+
* their own place only. Null for a place held by someone without an account.
|
|
5187
|
+
*/
|
|
5188
|
+
app_user_id?: string | null;
|
|
5176
5189
|
signed?: boolean;
|
|
5177
5190
|
signed_at?: string | null;
|
|
5178
5191
|
/** The name BankID verified on this place. Managers only. */
|
|
5179
5192
|
signed_as?: string | null;
|
|
5193
|
+
/** The place, as `resendLink` names it. Managers only. */
|
|
5194
|
+
id?: string;
|
|
5195
|
+
/** Who holds the place: an account, or someone signing through an emailed link. Managers only. */
|
|
5196
|
+
kind?: "app_user" | "email";
|
|
5197
|
+
/** An email place's address; null once the round has closed. Managers only. */
|
|
5198
|
+
email?: string | null;
|
|
5199
|
+
/** An email place's current link; null for an account place. Managers only. */
|
|
5200
|
+
link?: SigningRoundSignerLink | null;
|
|
5201
|
+
}
|
|
5202
|
+
|
|
5203
|
+
export interface SigningRoundSignerLink {
|
|
5204
|
+
status: "sending" | "sent" | "delivery_failed" | "expired" | "used" | "cancelled";
|
|
5205
|
+
sent_at: string | null;
|
|
5206
|
+
expires_at: string | null;
|
|
5207
|
+
used_at: string | null;
|
|
5208
|
+
links_sent: number;
|
|
5209
|
+
/** A new link may be sent now. */
|
|
5210
|
+
can_resend: boolean;
|
|
5180
5211
|
}
|
|
5181
5212
|
|
|
5213
|
+
/**
|
|
5214
|
+
* One signer to prepare, in the order of the places: an account id (or
|
|
5215
|
+
* `{ app_user_id }`), or someone without an account, who is emailed a
|
|
5216
|
+
* personal signing link valid for 24 hours when the round is confirmed.
|
|
5217
|
+
* Anyone holding that link can sign.
|
|
5218
|
+
*/
|
|
5219
|
+
export type SigningRoundSignerInput = string | { app_user_id: string } | { name: string; email: string };
|
|
5220
|
+
|
|
5182
5221
|
export interface SigningRound {
|
|
5183
5222
|
id: string;
|
|
5184
5223
|
file_id: string;
|
|
@@ -5221,13 +5260,25 @@ export function useSigningRound(fileId: string | null | undefined): {
|
|
|
5221
5260
|
loading: boolean;
|
|
5222
5261
|
error: SigningRoundError | null;
|
|
5223
5262
|
refresh(): Promise<void>;
|
|
5224
|
-
/** `
|
|
5225
|
-
prepare(
|
|
5263
|
+
/** `signers` in the order their places appear on the signature page. Resolves to the DRAFT. */
|
|
5264
|
+
prepare(signers: SigningRoundSignerInput[]): Promise<SigningRound>;
|
|
5226
5265
|
preparedFile(): Promise<SigningRoundPreparedFile>;
|
|
5227
5266
|
/** Resolves to the OPEN round. */
|
|
5228
5267
|
confirm(): Promise<SigningRound>;
|
|
5229
5268
|
/** Resolves to the round it ended, with status "CANCELLED". */
|
|
5230
5269
|
cancel(): Promise<SigningRound>;
|
|
5270
|
+
/**
|
|
5271
|
+
* Replace an email place's link with a new one, sent to `email` when given
|
|
5272
|
+
* (a corrected address). Offer it only where `link.can_resend` is true.
|
|
5273
|
+
* Resolves to the round. Refusals: `SIGNING_LINK_RESEND_TOO_SOON` (with
|
|
5274
|
+
* `retryAfter`), `SIGNING_LINK_RESEND_IN_PROGRESS`,
|
|
5275
|
+
* `SIGNING_LINK_LIMIT_REACHED`, `SIGNING_LINK_BUDGET_EXCEEDED`,
|
|
5276
|
+
* `SIGNING_LINK_PLATFORM_LIMIT`, `INVALID_EMAIL`, `DUPLICATE_SIGNER`,
|
|
5277
|
+
* `SIGNING_ROUND_NOT_OPEN`, `SIGNING_ROUND_CLOSED`, `ALREADY_SIGNED`, and
|
|
5278
|
+
* the hook's own `NOT_SUPPORTED` when the host's filestore client predates
|
|
5279
|
+
* sending links.
|
|
5280
|
+
*/
|
|
5281
|
+
resendLink(signerId: string, options?: { email?: string }): Promise<SigningRound>;
|
|
5231
5282
|
/** An action is running; another one is refused with `ACTION_IN_PROGRESS`. */
|
|
5232
5283
|
busy: boolean;
|
|
5233
5284
|
};
|
package/dist/index.js
CHANGED
|
@@ -135,6 +135,7 @@ export {
|
|
|
135
135
|
} from "./stats-hooks.js";
|
|
136
136
|
// sc-11025 — one source, bound to how each platform says the app is in front.
|
|
137
137
|
export { useFileSignature, useSigningRounds, useSigningRound } from "./signing-hooks.js";
|
|
138
|
+
export { bankIdAppUrl } from "./bankid-app.js";
|
|
138
139
|
export { useOnlineMembers, useOnlineVisibility } from "./presence.js";
|
|
139
140
|
// sc-6607 — `<Overlay>` is one shared component (./overlay-view.js) bound to
|
|
140
141
|
// each platform's primitives, so a widget's modal escapes its container box
|
package/dist/index.native.js
CHANGED
|
@@ -133,6 +133,7 @@ export {
|
|
|
133
133
|
} from "./stats-hooks.js";
|
|
134
134
|
// sc-11025 — see the note on the web mirror in ./index.js.
|
|
135
135
|
export { useFileSignature, useSigningRounds, useSigningRound } from "./signing-hooks.js";
|
|
136
|
+
export { bankIdAppUrl } from "./bankid-app.js";
|
|
136
137
|
export { useOnlineMembers, useOnlineVisibility } from "./presence.native.js";
|
|
137
138
|
// sc-6607 — see the note on the web mirror in ./index.js.
|
|
138
139
|
export { Overlay } from "./overlay.native.js";
|
package/dist/signing-hooks.js
CHANGED
|
@@ -15,11 +15,13 @@ import {
|
|
|
15
15
|
indexOverview,
|
|
16
16
|
readRefusalDetail,
|
|
17
17
|
reportedRoundId,
|
|
18
|
+
resendLinkOptions,
|
|
18
19
|
roundChangedUnderneath,
|
|
19
20
|
signOrderStateAfterAnswer,
|
|
20
21
|
signOrderStateAfterCancel,
|
|
21
22
|
signOrderStateAfterInitiate,
|
|
22
23
|
signedOrderState,
|
|
24
|
+
signerListForPrepare,
|
|
23
25
|
uniqueFileIds,
|
|
24
26
|
} from "./signing-state.js";
|
|
25
27
|
|
|
@@ -209,6 +211,10 @@ const previewRequired = () =>
|
|
|
209
211
|
const actionInProgress = () =>
|
|
210
212
|
localRefusal("ACTION_IN_PROGRESS", "Another change to this signing round is still in progress. Try again in a moment.");
|
|
211
213
|
const noFile = () => localRefusal("VALIDATION", "Choose a document first.");
|
|
214
|
+
const noResend = () =>
|
|
215
|
+
localRefusal("NOT_SUPPORTED", "Sending a new link is not available in this version of the app.");
|
|
216
|
+
const badSignerList = () =>
|
|
217
|
+
localRefusal("VALIDATION", "Each signer needs to be a person in the app, or a name and an email address.");
|
|
212
218
|
|
|
213
219
|
function signingRoundsClientOrThrow(ctx, hookName) {
|
|
214
220
|
const rounds = ctx.filestore && ctx.filestore.signingRounds;
|
|
@@ -289,19 +295,24 @@ export function useSigningRounds(fileIds) {
|
|
|
289
295
|
/**
|
|
290
296
|
* One file's current signing round and the actions of whoever manages the
|
|
291
297
|
* file's folder. Returns `{ round, loading, error, refresh, prepare,
|
|
292
|
-
* preparedFile, confirm, cancel, busy }`.
|
|
298
|
+
* preparedFile, confirm, cancel, resendLink, busy }`.
|
|
293
299
|
*
|
|
294
|
-
* `prepare(
|
|
295
|
-
* places appear on the signature page
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
*
|
|
300
|
+
* `prepare(signers)` drafts a round for those people, in the order their
|
|
301
|
+
* places appear on the signature page: each is an account id (or
|
|
302
|
+
* `{ app_user_id }`), or `{ name, email }` for someone without an account,
|
|
303
|
+
* who is emailed a 24-hour signing link once the round is confirmed;
|
|
304
|
+
* `preparedFile()` answers `{ url, sha256, expires_at, size_bytes }` for the
|
|
305
|
+
* draft's document; `confirm()` opens the round for the document that
|
|
306
|
+
* `preparedFile()` last returned, so it needs no argument; `cancel()` ends a
|
|
307
|
+
* draft or an open round and resolves to the cancelled round;
|
|
308
|
+
* `resendLink(signerId, { email })` replaces an email place's link with a new
|
|
309
|
+
* one, sent to the corrected address when given. One action runs at a time:
|
|
310
|
+
* while `busy` is true another one is refused with code `ACTION_IN_PROGRESS`,
|
|
311
|
+
* never queued. A refused action sets `error` and rejects with it: `code` and
|
|
312
|
+
* `message` come from the server, `errors` has one entry per refused signer
|
|
313
|
+
* (with `index`, its place in the list sent), `retryAfter` is the seconds to
|
|
314
|
+
* wait. When the refusal means the round is no longer what was shown, the
|
|
315
|
+
* round is read again.
|
|
305
316
|
*/
|
|
306
317
|
export function useSigningRound(fileId) {
|
|
307
318
|
const ctx = useWidgetContextOrThrow("useSigningRound");
|
|
@@ -395,10 +406,12 @@ export function useSigningRound(fileId) {
|
|
|
395
406
|
);
|
|
396
407
|
|
|
397
408
|
const prepare = useCallback(
|
|
398
|
-
(
|
|
409
|
+
(signers) =>
|
|
399
410
|
run(async (rounds, _round, forFile) => {
|
|
400
411
|
if (!forFile) throw noFile();
|
|
401
|
-
const
|
|
412
|
+
const list = signerListForPrepare(signers);
|
|
413
|
+
if (!list) throw badSignerList();
|
|
414
|
+
const draft = await rounds.prepare(forFile, list);
|
|
402
415
|
return { value: draft, round: draft };
|
|
403
416
|
}),
|
|
404
417
|
[run],
|
|
@@ -438,6 +451,18 @@ export function useSigningRound(fileId) {
|
|
|
438
451
|
[run],
|
|
439
452
|
);
|
|
440
453
|
|
|
454
|
+
const resendLink = useCallback(
|
|
455
|
+
(signerId, options) =>
|
|
456
|
+
run(async (rounds, round) => {
|
|
457
|
+
if (!round) throw noRound();
|
|
458
|
+
// A filestore client older than 0.14.0 has no resendLink.
|
|
459
|
+
if (typeof rounds.resendLink !== "function") throw noResend();
|
|
460
|
+
const resent = await rounds.resendLink(round.id, signerId, resendLinkOptions(options));
|
|
461
|
+
return { value: resent, round: resent };
|
|
462
|
+
}),
|
|
463
|
+
[run],
|
|
464
|
+
);
|
|
465
|
+
|
|
441
466
|
return {
|
|
442
467
|
round: current.round,
|
|
443
468
|
loading: Boolean(fileId) && (!current.settled || reading),
|
|
@@ -447,6 +472,7 @@ export function useSigningRound(fileId) {
|
|
|
447
472
|
preparedFile,
|
|
448
473
|
confirm,
|
|
449
474
|
cancel,
|
|
475
|
+
resendLink,
|
|
450
476
|
busy,
|
|
451
477
|
};
|
|
452
478
|
}
|
package/dist/signing-state.js
CHANGED
|
@@ -71,8 +71,9 @@ export function signOrderStateAfterCancel(previous, answer) {
|
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* The parts of a refusal a widget shows beside its message, as the filestore
|
|
74
|
-
* client put them on its error: one entry per refused signer (`
|
|
75
|
-
*
|
|
74
|
+
* client put them on its error: one entry per refused signer (`index`, its
|
|
75
|
+
* place in the list that was sent, `app_user_id` for an account, `message`,
|
|
76
|
+
* and for a name `reason` and `characters`), and how many seconds
|
|
76
77
|
* to wait before trying again. An error from anywhere else has neither.
|
|
77
78
|
*/
|
|
78
79
|
export function readRefusalDetail(err) {
|
|
@@ -81,6 +82,39 @@ export function readRefusalDetail(err) {
|
|
|
81
82
|
return { errors, retryAfter: typeof wait === "number" && Number.isFinite(wait) && wait >= 0 ? wait : null };
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
const nonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
|
|
86
|
+
const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
87
|
+
|
|
88
|
+
/** One prepare entry as the filestore client takes it, or null when it is neither form. */
|
|
89
|
+
function signerForPrepare(signer) {
|
|
90
|
+
if (nonEmptyString(signer)) return signer;
|
|
91
|
+
if (!isObject(signer)) return null;
|
|
92
|
+
const keys = Object.keys(signer).sort().join(",");
|
|
93
|
+
if (keys === "app_user_id" && nonEmptyString(signer.app_user_id)) return signer.app_user_id;
|
|
94
|
+
if (keys === "email,name" && nonEmptyString(signer.name) && nonEmptyString(signer.email)) {
|
|
95
|
+
return { name: signer.name.trim(), email: signer.email.trim() };
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The signers of a prepare, in their order, as the filestore client takes
|
|
102
|
+
* them: an account as its id (a list of ids alone keeps the request older
|
|
103
|
+
* servers read), someone without an account as `{ name, email }` trimmed.
|
|
104
|
+
* Null when an entry is neither an id, `{ app_user_id }` nor `{ name, email }`.
|
|
105
|
+
*/
|
|
106
|
+
export function signerListForPrepare(signers) {
|
|
107
|
+
if (!Array.isArray(signers)) return null;
|
|
108
|
+
const list = signers.map(signerForPrepare);
|
|
109
|
+
return list.includes(null) ? null : list;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The body of a new link: a corrected `email` when one is given, otherwise nothing. */
|
|
113
|
+
export function resendLinkOptions(options) {
|
|
114
|
+
const email = isObject(options) && typeof options.email === "string" ? options.email.trim() : "";
|
|
115
|
+
return email ? { email } : {};
|
|
116
|
+
}
|
|
117
|
+
|
|
84
118
|
// The round is no longer what the widget is showing: read it again.
|
|
85
119
|
const ROUND_CHANGED_CODES = new Set([
|
|
86
120
|
"SIGNING_ROUND_EXPIRED",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.243.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"homepage": "https://github.com/Colix-AB/AppStudio",
|
|
6
6
|
"type": "module",
|
package/src/contract.js
CHANGED
|
@@ -1624,10 +1624,30 @@ const HOOKS = [
|
|
|
1624
1624
|
signature: "useFileSignature(fileId, existingSignatureId)",
|
|
1625
1625
|
optionalArgs: ["existingSignatureId"],
|
|
1626
1626
|
description:
|
|
1627
|
-
"Drive a BankID signing flow for one Filestore file
|
|
1628
|
-
"
|
|
1629
|
-
"
|
|
1630
|
-
"
|
|
1627
|
+
"Drive a BankID signing flow for one Filestore file of ANY type in a " +
|
|
1628
|
+
"shared (project or public) space; a personal file is refused with " +
|
|
1629
|
+
"NOT_SIGNABLE. Outside a signing round the signature is kept beside " +
|
|
1630
|
+
"the file and the file is not changed; a PDF in an open signing round " +
|
|
1631
|
+
"gets the signature added to the document itself, which is then " +
|
|
1632
|
+
"sealed against changes (see useSigningRounds). initiate() opens a " +
|
|
1633
|
+
"sign order (the backend hashes the bytes + binds the digest). The " +
|
|
1634
|
+
"hook does NOT poll by itself: while status is 'pending' call " +
|
|
1635
|
+
"refresh() every 2 seconds (a setInterval in an effect, cleared when " +
|
|
1636
|
+
"status leaves 'pending' or the widget unmounts); each answer carries " +
|
|
1637
|
+
"a fresh qr frame. Offer both ways to sign: BankID on THIS device is a " +
|
|
1638
|
+
"button whose onPress calls Linking.openURL(bankIdAppUrl(autoStartToken)), " +
|
|
1639
|
+
"shown only while autoStartToken is set; BankID on ANOTHER device is " +
|
|
1640
|
+
"the qr rendered with the Image primitive, replaced by each poll's " +
|
|
1641
|
+
"frame. cancel() aborts, verify() returns the offline verdict: valid " +
|
|
1642
|
+
"is the BankID signature itself; content_status 'unchanged' means the " +
|
|
1643
|
+
"file still holds what was signed, 'changed' that it was altered " +
|
|
1644
|
+
"since, 'unavailable' that it could not be checked now; " +
|
|
1645
|
+
"signed_by_others_since counts later signatures in a round's PDF; " +
|
|
1646
|
+
"seal.status is 'valid' (protected against changes), 'invalid' or " +
|
|
1647
|
+
"'certificate_revoked' (do not rely on the copy), 'unavailable' (try " +
|
|
1648
|
+
"later) or 'not_applicable' (a signature kept beside the file). Say " +
|
|
1649
|
+
"each in plain words and never show a green mark when any of them " +
|
|
1650
|
+
"failed. Pass " +
|
|
1631
1651
|
"existingSignatureId for an already-signed file to seed the complete " +
|
|
1632
1652
|
"state + verify() without re-signing. autoStartToken is set only while " +
|
|
1633
1653
|
"opening BankID can still help (null once the user has signed or the " +
|
|
@@ -1644,19 +1664,21 @@ const HOOKS = [
|
|
|
1644
1664
|
"ctx.filestore.signatures.{initiate,status,cancel,verify}.",
|
|
1645
1665
|
returnShape: {
|
|
1646
1666
|
status: "'pending' | 'complete' | 'failed' | 'cancelled' | null",
|
|
1647
|
-
qr: "string | null // PNG data-URL of the animated BankID QR",
|
|
1648
|
-
autoStartToken: "string | null",
|
|
1667
|
+
qr: "string | null // PNG data-URL of the animated BankID QR; render with <Image>, refresh() every 2 s while pending for the next frame",
|
|
1668
|
+
autoStartToken: "string | null // BankID on this device: a button calling Linking.openURL(bankIdAppUrl(autoStartToken))",
|
|
1649
1669
|
message: "string | null",
|
|
1650
1670
|
signerName: "string | null",
|
|
1651
1671
|
signedAt: "string | null",
|
|
1652
1672
|
hintCode: "string | null // where the order stands, e.g. 'sealing'",
|
|
1653
1673
|
nameRefusal:
|
|
1654
1674
|
"{ reason: 'empty' | 'too_long' | 'unsupported_characters' | 'mixed_scripts', characters: string[] } | null",
|
|
1655
|
-
verdict:
|
|
1675
|
+
verdict:
|
|
1676
|
+
"{ valid, checks, content_status: 'unchanged' | 'changed' | 'unavailable', signed_by_others_since: number | null, " +
|
|
1677
|
+
"seal: { status: 'valid' | 'invalid' | 'certificate_revoked' | 'unavailable' | 'not_applicable', sealed_at, certificate_revoked_at, ... }, ... } | null",
|
|
1656
1678
|
loading: "boolean",
|
|
1657
1679
|
error: "PermissionError | null // { code, message, retryable }",
|
|
1658
1680
|
initiate: "() => Promise<{ signature_id, qr, auto_start_token, status, hint_code?, message? }>",
|
|
1659
|
-
refresh: "() => Promise<void>",
|
|
1681
|
+
refresh: "() => Promise<void> // the hook never polls by itself",
|
|
1660
1682
|
cancel: "() => Promise<{ status, hint_code?, message?, signer_name?, signed_at? } | undefined>",
|
|
1661
1683
|
verify: "() => Promise<verdict>",
|
|
1662
1684
|
},
|
|
@@ -1667,10 +1689,14 @@ const HOOKS = [
|
|
|
1667
1689
|
name: "useFileSignatures",
|
|
1668
1690
|
signature: "useFileSignatures(fileIds)",
|
|
1669
1691
|
description:
|
|
1670
|
-
"Batch '
|
|
1671
|
-
"of each
|
|
1672
|
-
"
|
|
1673
|
-
"
|
|
1692
|
+
"Batch 'have I signed this file?' lookup for a set of file ids. Returns " +
|
|
1693
|
+
"a map of each file the SIGNED-IN USER has signed (and can still see) " +
|
|
1694
|
+
"to their own latest signature; a file only others have signed, or " +
|
|
1695
|
+
"nobody has, is absent, so it never tells who else signed. Pass a " +
|
|
1696
|
+
"file's signature_id to useFileSignature(fileId, signatureId) to show " +
|
|
1697
|
+
"and verify it. Who has signed is useFileRoster(), for someone who " +
|
|
1698
|
+
"manages the folder. Reads ctx.filestore.signatures.list and unwraps " +
|
|
1699
|
+
"{ data, meta }.",
|
|
1674
1700
|
returnShape: {
|
|
1675
1701
|
signaturesByFileId:
|
|
1676
1702
|
"{ [fileId]: { signature_id, signer_name, signed_at } }",
|
|
@@ -1709,8 +1735,10 @@ const HOOKS = [
|
|
|
1709
1735
|
signature: "useSigningRounds(fileIds)",
|
|
1710
1736
|
description:
|
|
1711
1737
|
"What the signed-in user may know about the signing round of each file " +
|
|
1712
|
-
"in a list, in one batched request. A signing round fixes a PDF
|
|
1713
|
-
"list of signers
|
|
1738
|
+
"in a list, in one batched request. A signing round fixes a PDF in the " +
|
|
1739
|
+
"project space and a list of signers (other file types are signed one " +
|
|
1740
|
+
"person at a time with useFileSignature() and no round): it is " +
|
|
1741
|
+
"prepared and confirmed by someone who manages " +
|
|
1714
1742
|
"the file's folder, then each signer signs once with useFileSignature(), " +
|
|
1715
1743
|
"in any order (there are no turns; the list only orders the places on " +
|
|
1716
1744
|
"the signature page). byFileId maps a file id to { file_id, can_manage, " +
|
|
@@ -1719,7 +1747,8 @@ const HOOKS = [
|
|
|
1719
1747
|
"manager nor one of its signers; a signer sees it once it is confirmed, " +
|
|
1720
1748
|
"with their own place (my_place) and can_sign (the round is open and " +
|
|
1721
1749
|
"their place is unsigned), and signed_count is null " +
|
|
1722
|
-
"for them.
|
|
1750
|
+
"for them. Each signature is added to the PDF itself and the document " +
|
|
1751
|
+
"is sealed against changes. locked is 'signing_round' while the file cannot be changed, " +
|
|
1723
1752
|
"'signed' once its content cannot be replaced. roundsEnabled is false " +
|
|
1724
1753
|
"when the workspace cannot start signing rounds (offer no way to start " +
|
|
1725
1754
|
"one), and null until the first answer. can_prepare is about the file " +
|
|
@@ -1747,15 +1776,27 @@ const HOOKS = [
|
|
|
1747
1776
|
signature: "useSigningRound(fileId)",
|
|
1748
1777
|
description:
|
|
1749
1778
|
"One file's current signing round, and the actions of someone who " +
|
|
1750
|
-
"manages the file's folder. prepare(
|
|
1751
|
-
"
|
|
1752
|
-
"
|
|
1779
|
+
"manages the file's folder. prepare(signers) drafts a round for those " +
|
|
1780
|
+
"people, their places on the signature page in the order given: each " +
|
|
1781
|
+
"entry is an account id from useFileRoster() (or { app_user_id }), or " +
|
|
1782
|
+
"{ name, email } for someone without an account, who is emailed a " +
|
|
1783
|
+
"personal BankID signing link valid for 24 hours once the round is " +
|
|
1784
|
+
"confirmed (anyone holding that link can sign, so say so before " +
|
|
1785
|
+
"sending); it can take " +
|
|
1753
1786
|
"up to a minute. preparedFile() returns a five-minute link to the " +
|
|
1754
1787
|
"prepared document; show it to the user before confirming. confirm() " +
|
|
1755
1788
|
"opens the round for exactly the document preparedFile() last returned, " +
|
|
1756
1789
|
"and rejects with code PREVIEW_REQUIRED when it was not called first. " +
|
|
1757
1790
|
"cancel() ends a draft or an open round and resolves to the cancelled " +
|
|
1758
|
-
"round (status 'CANCELLED'); round is then null.
|
|
1791
|
+
"round (status 'CANCELLED'); round is then null. " +
|
|
1792
|
+
"resendLink(signerId, { email }) replaces an email place's link with " +
|
|
1793
|
+
"a new one (the old one stops working), sent to the corrected email " +
|
|
1794
|
+
"when given, and resolves to the round; offer it only where the " +
|
|
1795
|
+
"place's link.can_resend is true. A manager sees each place's id, kind " +
|
|
1796
|
+
"('app_user' | 'email'), email, signed_as (the name BankID verified) " +
|
|
1797
|
+
"and link ({ status: 'sending' | 'sent' | 'delivery_failed' | " +
|
|
1798
|
+
"'expired' | 'used' | 'cancelled', sent_at, expires_at, used_at, " +
|
|
1799
|
+
"links_sent, can_resend }, null for an account place). ONE ACTION RUNS AT A " +
|
|
1759
1800
|
"TIME: while busy is true any other action is refused at once with code " +
|
|
1760
1801
|
"ACTION_IN_PROGRESS (it is not queued), so disable your buttons on busy. " +
|
|
1761
1802
|
"round is the full round as the " +
|
|
@@ -1763,27 +1804,40 @@ const HOOKS = [
|
|
|
1763
1804
|
"every signer's name and position and their own progress only, anyone " +
|
|
1764
1805
|
"else sees null. A refused action sets error and rejects with it: code " +
|
|
1765
1806
|
"and message come from the server (show message as it is), errors has " +
|
|
1766
|
-
"one entry per refused signer ({ app_user_id, message, reason, " +
|
|
1767
|
-
"characters }
|
|
1807
|
+
"one entry per refused signer ({ index, app_user_id, message, reason, " +
|
|
1808
|
+
"characters }; index is the entry's place in the list sent), " +
|
|
1809
|
+
"retryAfter is the seconds to wait. resendLink is refused with " +
|
|
1810
|
+
"SIGNING_LINK_RESEND_TOO_SOON (with retryAfter), " +
|
|
1811
|
+
"SIGNING_LINK_RESEND_IN_PROGRESS, SIGNING_LINK_LIMIT_REACHED, " +
|
|
1812
|
+
"SIGNING_LINK_BUDGET_EXCEEDED, SIGNING_LINK_PLATFORM_LIMIT, " +
|
|
1813
|
+
"INVALID_EMAIL, DUPLICATE_SIGNER, SIGNING_ROUND_NOT_OPEN, " +
|
|
1814
|
+
"SIGNING_ROUND_CLOSED or ALREADY_SIGNED, and by the hook itself with " +
|
|
1815
|
+
"NOT_SUPPORTED when the host's filestore client cannot send links. The hook's own " +
|
|
1768
1816
|
"refusals (PREVIEW_REQUIRED, ACTION_IN_PROGRESS, NO_SIGNING_ROUND) carry " +
|
|
1769
1817
|
"a message that can be shown the same way. When a refusal means the " +
|
|
1770
1818
|
"round is no longer what was shown (it expired, was closed, or the " +
|
|
1771
1819
|
"document changed) the hook reads the round again by itself. Code " +
|
|
1772
1820
|
"SIGNING_ROUNDS_NOT_ENABLED means the workspace cannot start signing " +
|
|
1773
1821
|
"rounds; check roundsEnabled from useSigningRounds() before offering to " +
|
|
1774
|
-
"start one
|
|
1822
|
+
"start one, and when it is false say that document signing is not " +
|
|
1823
|
+
"turned on for this workspace and that the platform turns it on. " +
|
|
1824
|
+
"Rounds take PDFs in the project space only (NOT_A_PDF otherwise). A " +
|
|
1825
|
+
"signer signs their place with useFileSignature(fileId), the same " +
|
|
1826
|
+
"BankID flow as any file. When fileId or the signed-in user changes, round is null " +
|
|
1775
1827
|
"and loading true until the new answer. Reads ctx.filestore.signingRounds.",
|
|
1776
1828
|
returnShape: {
|
|
1777
1829
|
round:
|
|
1778
1830
|
"{ id, file_id, status: 'DRAFT' | 'OPEN' | 'COMPLETED', created_at, confirmed_at, completed_at, expires_at, " +
|
|
1779
|
-
"signers: { name, position, app_user_id?, signed?, signed_at?, signed_as
|
|
1831
|
+
"signers: { name, position, app_user_id?, signed?, signed_at?, signed_as?, id?, kind?: 'app_user' | 'email', email?, " +
|
|
1832
|
+
"link?: { status: 'sending' | 'sent' | 'delivery_failed' | 'expired' | 'used' | 'cancelled', sent_at, expires_at, used_at, links_sent, can_resend } | null }[] } | null",
|
|
1780
1833
|
loading: "boolean",
|
|
1781
1834
|
error: "PermissionError | null // { code, message, retryable, errors, retryAfter }",
|
|
1782
1835
|
refresh: "() => Promise<void>",
|
|
1783
|
-
prepare: "(
|
|
1836
|
+
prepare: "(signers: (string | { app_user_id } | { name, email })[]) => Promise<round> // status 'DRAFT'",
|
|
1784
1837
|
preparedFile: "() => Promise<{ url, sha256, expires_at, size_bytes }>",
|
|
1785
1838
|
confirm: "() => Promise<round> // status 'OPEN'",
|
|
1786
1839
|
cancel: "() => Promise<round> // the round it ended, status 'CANCELLED'",
|
|
1840
|
+
resendLink: "(signerId: string, options?: { email?: string }) => Promise<round> // a new link for an email place",
|
|
1787
1841
|
busy: "boolean // an action is running; another one is refused with ACTION_IN_PROGRESS",
|
|
1788
1842
|
},
|
|
1789
1843
|
requiredContextSlice: ["filestore.signingRounds"],
|
|
@@ -2598,8 +2652,9 @@ const HOOKS = [
|
|
|
2598
2652
|
"with the Image primitive) and the hook then POLLS to completion itself " +
|
|
2599
2653
|
"(pollIntervalMs, default 1000; pass 0 to drive refresh() yourself); " +
|
|
2600
2654
|
"cancel() aborts; reset() clears the flow. autoStartToken opens the " +
|
|
2601
|
-
"provider app on the SAME device (
|
|
2602
|
-
"
|
|
2655
|
+
"provider app on the SAME device: Linking.openURL(bankIdAppUrl(" +
|
|
2656
|
+
"autoStartToken)) opens bankid:///?autostarttoken=<token>&redirect=null. " +
|
|
2657
|
+
"On complete, `identity` is { provider, name, given_name, " +
|
|
2603
2658
|
"surname, personal_number_masked, subject_hash, identified_at } — there is " +
|
|
2604
2659
|
"NO raw personal number by design: write personal_number_masked " +
|
|
2605
2660
|
"(\"19900101-****\") and, if you need to trace the proof, identificationId. " +
|
|
@@ -3888,7 +3943,7 @@ const WIDGET_CONTEXT_SHAPE = {
|
|
|
3888
3943
|
"{ files: { list(query) -> Promise<{ data, meta }>, get(id), upload(formData), update(id, body), copy(id, body), remove(id), preview(id) }, " +
|
|
3889
3944
|
"folders: { list, create, update, remove }, shares: { ... }, trash: { ... }, " +
|
|
3890
3945
|
"signatures: { list(fileIds), roster(fileId, query), initiate(fileId), status(id), cancel(id), verify(id) }, " +
|
|
3891
|
-
"signingRounds: { overview(fileIds), list(fileId, query), get(roundId), prepare(fileId,
|
|
3946
|
+
"signingRounds: { overview(fileIds), list(fileId, query), get(roundId), prepare(fileId, signers), preparedFile(roundId), confirm(roundId, preparedSha256), cancel(roundId), resendLink(roundId, signerId, { email }) }, " +
|
|
3892
3947
|
"objectUrl(token), fetchObject(token) }. " +
|
|
3893
3948
|
"Backs useFilestoreFiles() + useFilestoreFolders() + useFilestoreCreateFolder() + useFilestoreCopy() + useFileSignature() + useFileSignatures() + useFileRoster() + useSigningRounds() + useSigningRound(). Optional — a host without an end-user filestore omits it and those hooks throw a clear error.",
|
|
3894
3949
|
required: false,
|
|
@@ -6088,7 +6143,15 @@ const CONTRACT = deepFreeze({
|
|
|
6088
6143
|
// `sectionRef` propertySchema type gives the author a section picker.
|
|
6089
6144
|
// 1.194.0: more permissive (sc-11945) — a datastoreTemplate takes 100
|
|
6090
6145
|
// tables, 200 columns, 100 rows per table, 20000 cells and 32 roles.
|
|
6091
|
-
|
|
6146
|
+
// 1.195.0: additive (sc-11879) — `useSigningRound().prepare` takes
|
|
6147
|
+
// `{ name, email }` entries for signers without an account, and
|
|
6148
|
+
// `resendLink(signerId, { email })` sends such a signer a new link.
|
|
6149
|
+
// The manager's places carry id, kind, email and link.
|
|
6150
|
+
// 1.197.0: additive (sc-12063) — new export `bankIdAppUrl(autoStartToken)`,
|
|
6151
|
+
// the link that opens BankID on this device. Signing hooks document
|
|
6152
|
+
// polling, the two ways to start BankID and how to read a verdict;
|
|
6153
|
+
// useFileSignatures is described as the caller's own signatures.
|
|
6154
|
+
version: "1.197.0",
|
|
6092
6155
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
6093
6156
|
hooks: HOOKS,
|
|
6094
6157
|
primitives: PRIMITIVES,
|