@colixsystems/widget-sdk 0.218.0 → 0.219.1

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/dist/hooks.js CHANGED
@@ -392,14 +392,15 @@ export function useUserPhoto() {
392
392
  }
393
393
 
394
394
  /**
395
- * Returns `true` when the host has sized this widget to fill the available
396
- * height of its layout slot — today that means a page-grid tile whose author
397
- * chose "Fill tile height" (or a widget type that fills by default, like
398
- * containers and media). When `true`, a widget that has a meaningful filled
399
- * form (Image, Chart, Map, Video, …) should switch from its intrinsic height
400
- * to a stretch style (`flex: 1` / `height: "100%"`); widgets with no useful
401
- * filled form may ignore it. Defaults to `false` everywhere the host has not
402
- * opted the widget into filling, so calling it is always safe.
395
+ * Returns `true` when the host has sized this widget to fill a box of
396
+ * definite height — a widget in the section a fit-to-screen page stretches to
397
+ * the bottom of the screen, or a widget given a fixed slot height. When
398
+ * `true`, a widget that has a meaningful filled form (Image, Chart, Map,
399
+ * Video, a chat or feed that scrolls its own content, …) should switch from
400
+ * its intrinsic height to a stretch style (`flex: 1` / `height: "100%"`) and
401
+ * scroll inside it; widgets with no useful filled form may ignore it.
402
+ * Defaults to `false` everywhere the host has not opted the widget into
403
+ * filling, so calling it is always safe.
403
404
  *
404
405
  * The SAME value is injected by the web Player host and the native export
405
406
  * host (widget-parity skill), so a widget's fill behaviour is identical on both
@@ -3775,7 +3776,7 @@ export class PermissionError extends Error {
3775
3776
  }
3776
3777
  }
3777
3778
 
3778
- function toPermissionError(err) {
3779
+ export function toPermissionError(err) {
3779
3780
  // See the note in toDatastoreError — a client rejection is read from the
3780
3781
  // envelope below rather than matching this instanceof.
3781
3782
  if (err instanceof PermissionError) return err;
@@ -5172,149 +5173,6 @@ export function useFilestoreDelete() {
5172
5173
  return { remove, removing, error };
5173
5174
  }
5174
5175
 
5175
- /**
5176
- * Drive a BankID signing flow for one Filestore file. Returns
5177
- * `{ status, qr, autoStartToken, message, signerName, signedAt, verdict,
5178
- * loading, error, initiate, refresh, cancel, verify }`.
5179
- *
5180
- * `initiate()` opens a sign order against the file (the backend hashes its
5181
- * bytes SERVER-SIDE and binds the digest into the BankID signature); `refresh()`
5182
- * polls the order (surfacing a fresh animated-QR frame while pending — render
5183
- * it with the `Image` primitive); `cancel()` aborts it; `verify()` re-runs the
5184
- * offline verification and returns the verdict (`{ valid, content_status, … }`).
5185
- * Reads `ctx.filestore.signatures.{initiate,status,cancel,verify}`. When
5186
- * `fileId` is null/empty the hook collapses to a no-op.
5187
- *
5188
- * Pass `existingSignatureId` for a file that is ALREADY signed: the hook seeds
5189
- * that order id (status `complete`) so the widget can show the signed state and
5190
- * `verify()` it WITHOUT opening a new order — re-signing then requires an
5191
- * explicit `initiate()`.
5192
- */
5193
- export function useFileSignature(fileId, existingSignatureId = null) {
5194
- const ctx = useWidgetContextOrThrow("useFileSignature");
5195
- if (!ctx.filestore || !ctx.filestore.signatures) {
5196
- throw new Error(
5197
- "useFileSignature: host did not inject a filestore client (ctx.filestore.signatures)",
5198
- );
5199
- }
5200
- const ready = Boolean(fileId);
5201
- const sigRef = useRef(ctx.filestore.signatures);
5202
- sigRef.current = ctx.filestore.signatures;
5203
- const fileIdRef = useRef(fileId);
5204
- fileIdRef.current = fileId;
5205
- // Seed the order from an already-completed signature so verify() works
5206
- // without initiating; initiate() (an explicit re-sign) overwrites it.
5207
- const orderRef = useRef(existingSignatureId || null);
5208
-
5209
- const [status, setStatus] = useState(existingSignatureId ? "complete" : null);
5210
- const [qr, setQr] = useState(null);
5211
- const [autoStartToken, setAutoStartToken] = useState(null);
5212
- const [message, setMessage] = useState(null);
5213
- const [signerName, setSignerName] = useState(null);
5214
- const [signedAt, setSignedAt] = useState(null);
5215
- const [verdict, setVerdict] = useState(null);
5216
- const [loading, setLoading] = useState(false);
5217
- const [error, setError] = useState(null);
5218
-
5219
- const initiate = useCallback(async () => {
5220
- if (!fileIdRef.current) return null;
5221
- setLoading(true);
5222
- setError(null);
5223
- setVerdict(null);
5224
- try {
5225
- const res = await sigRef.current.initiate(fileIdRef.current);
5226
- orderRef.current = res && res.signature_id ? res.signature_id : null;
5227
- setStatus(res && res.status ? res.status : "pending");
5228
- setQr(res && res.qr ? res.qr : null);
5229
- setAutoStartToken(res && res.auto_start_token ? res.auto_start_token : null);
5230
- setSignerName(null);
5231
- setSignedAt(null);
5232
- setMessage(null);
5233
- setLoading(false);
5234
- return res;
5235
- } catch (err) {
5236
- setError(toPermissionError(err));
5237
- setLoading(false);
5238
- throw toPermissionError(err);
5239
- }
5240
- }, []);
5241
-
5242
- const refresh = useCallback(async () => {
5243
- const id = orderRef.current;
5244
- if (!id) return;
5245
- try {
5246
- const res = await sigRef.current.status(id);
5247
- if (res) {
5248
- if (res.status != null) setStatus(res.status);
5249
- if (res.qr !== undefined) setQr(res.qr || null);
5250
- if (res.message !== undefined) setMessage(res.message || null);
5251
- if (res.signer_name !== undefined) setSignerName(res.signer_name || null);
5252
- if (res.signed_at !== undefined) setSignedAt(res.signed_at || null);
5253
- }
5254
- } catch (err) {
5255
- setError(toPermissionError(err));
5256
- }
5257
- }, []);
5258
-
5259
- const cancel = useCallback(async () => {
5260
- const id = orderRef.current;
5261
- if (!id) return;
5262
- try {
5263
- await sigRef.current.cancel(id);
5264
- setStatus("cancelled");
5265
- setQr(null);
5266
- } catch (err) {
5267
- throw toPermissionError(err);
5268
- }
5269
- }, []);
5270
-
5271
- const verify = useCallback(async () => {
5272
- const id = orderRef.current;
5273
- if (!id) return null;
5274
- try {
5275
- const v = await sigRef.current.verify(id);
5276
- setVerdict(v);
5277
- return v;
5278
- } catch (err) {
5279
- setError(toPermissionError(err));
5280
- throw toPermissionError(err);
5281
- }
5282
- }, []);
5283
-
5284
- if (!ready) {
5285
- return {
5286
- status: null,
5287
- qr: null,
5288
- autoStartToken: null,
5289
- message: null,
5290
- signerName: null,
5291
- signedAt: null,
5292
- verdict: null,
5293
- loading: false,
5294
- error: null,
5295
- initiate: async () => null,
5296
- refresh: async () => undefined,
5297
- cancel: async () => undefined,
5298
- verify: async () => null,
5299
- };
5300
- }
5301
- return {
5302
- status,
5303
- qr,
5304
- autoStartToken,
5305
- message,
5306
- signerName,
5307
- signedAt,
5308
- verdict,
5309
- loading,
5310
- error,
5311
- initiate,
5312
- refresh,
5313
- cancel,
5314
- verify,
5315
- };
5316
- }
5317
-
5318
5176
  /**
5319
5177
  * Batch "is this file signed?" lookup for a set of file ids. Returns
5320
5178
  * `{ signaturesByFileId, loading, error, refetch }` where `signaturesByFileId`
package/dist/index.d.ts CHANGED
@@ -728,8 +728,8 @@ export interface WidgetContext<TProps = unknown> {
728
728
  };
729
729
  /**
730
730
  * REQ-LAY-08 — optional host layout hint backing `useFill()`. `true` when
731
- * the host sized this widget to fill its layout slot's available height (a
732
- * page-grid tile set to "Fill tile height", or a default-fill widget type).
731
+ * the host sized this widget to fill a box of definite height (a
732
+ * fit-to-screen page's stretched section, or a fixed slot height).
733
733
  * Absent / `false` everywhere the host has not opted the widget into filling.
734
734
  */
735
735
  fill?: boolean;
@@ -1948,11 +1948,13 @@ export interface UserPhotoApi {
1948
1948
  export function useUserPhoto(): UserPhotoApi;
1949
1949
 
1950
1950
  /**
1951
- * REQ-LAY-08 — returns `true` when the host has sized this widget to fill its
1952
- * layout slot's available height (a page-grid tile set to "Fill tile height",
1953
- * or a default-fill widget type — containers + media). Widgets that can
1954
- * stretch (Image, Chart, Map, Video, …) should switch to a fill style
1955
- * (`flex: 1` / `height: "100%"`) when this is `true`; others may ignore it.
1951
+ * REQ-LAY-08 — returns `true` when the host has sized this widget to fill a
1952
+ * box of definite height (a widget in the section a fit-to-screen page
1953
+ * stretches to the bottom of the screen, or one given a fixed slot height).
1954
+ * Widgets that can stretch (Image, Chart, Map, Video, a chat or feed that
1955
+ * scrolls itself, …) should switch to a fill style (`flex: 1` /
1956
+ * `height: "100%"`) and scroll inside it when this is `true`; others may
1957
+ * ignore it.
1956
1958
  * Defaults to `false`, so calling it is always safe, and the same value is
1957
1959
  * injected on web and native so fill behaviour is identical on both platforms.
1958
1960
  */
@@ -4567,3 +4569,237 @@ export function pressableLift(state?: {
4567
4569
  export function styleGroup(
4568
4570
  name: string,
4569
4571
  ): { dataSet: Record<string, string> } | undefined;
4572
+
4573
+ /* ---------------------------------------------------------------------------
4574
+ * Signing files with BankID, and signing rounds. All read `ctx.filestore`.
4575
+ * Rows are snake_case verbatim.
4576
+ * ------------------------------------------------------------------------- */
4577
+
4578
+ /** Why a name cannot be shown on a signed document; `characters` lists what cannot be shown. */
4579
+ export interface SignatureNameRefusal {
4580
+ reason: "empty" | "too_long" | "unsupported_characters" | "mixed_scripts";
4581
+ characters: string[];
4582
+ }
4583
+
4584
+ /** What a sign order answers when started, polled or cancelled. */
4585
+ export interface FileSignOrderAnswer {
4586
+ status: "pending" | "complete" | "failed" | "cancelled";
4587
+ signature_id?: string;
4588
+ qr?: string | null;
4589
+ auto_start_token?: string | null;
4590
+ hint_code?: string | null;
4591
+ message?: string;
4592
+ signer_name?: string | null;
4593
+ signed_at?: string | null;
4594
+ name_refusal?: SignatureNameRefusal;
4595
+ }
4596
+
4597
+ export interface FileSignatureVerdict {
4598
+ valid: boolean;
4599
+ checks: Record<string, boolean>;
4600
+ reason: string | null;
4601
+ signer_name: string | null;
4602
+ personal_number: string | null;
4603
+ signed_at: string | null;
4604
+ content_status: "unchanged" | "changed" | "unavailable";
4605
+ signed_by_others_since: number | null;
4606
+ seal: Record<string, unknown>;
4607
+ }
4608
+
4609
+ /**
4610
+ * Drive a BankID signing flow for one file. Pass `existingSignatureId` for a
4611
+ * file the user has already signed to show that state and `verify()` it
4612
+ * without opening a new order. A null `fileId` makes every action a no-op.
4613
+ */
4614
+ export function useFileSignature(
4615
+ fileId: string | null | undefined,
4616
+ existingSignatureId?: string | null,
4617
+ ): {
4618
+ status: "pending" | "complete" | "failed" | "cancelled" | null;
4619
+ /** PNG data-URL of the animated BankID QR code while there is one to scan. */
4620
+ qr: string | null;
4621
+ /** Opens BankID on this device; null once the user has signed or the order has ended. */
4622
+ autoStartToken: string | null;
4623
+ message: string | null;
4624
+ signerName: string | null;
4625
+ signedAt: string | null;
4626
+ /** Where the order stands. "sealing": signed, and being added to the document. */
4627
+ hintCode: string | null;
4628
+ /** Set when the order failed because the signer's name cannot be shown. */
4629
+ nameRefusal: SignatureNameRefusal | null;
4630
+ verdict: FileSignatureVerdict | null;
4631
+ loading: boolean;
4632
+ error: PermissionError | null;
4633
+ initiate(): Promise<FileSignOrderAnswer | null>;
4634
+ refresh(): Promise<void>;
4635
+ /** Resolves to what became of the order, which is not always "cancelled". */
4636
+ cancel(): Promise<FileSignOrderAnswer | undefined>;
4637
+ verify(): Promise<FileSignatureVerdict | null>;
4638
+ };
4639
+
4640
+ export interface FileSignatureSummary {
4641
+ signature_id: string;
4642
+ subject_file_id: string;
4643
+ signer_name: string | null;
4644
+ signed_at: string | null;
4645
+ }
4646
+
4647
+ /** The signed-in user's own latest signature per file they have signed; other files are absent. */
4648
+ export function useFileSignatures(fileIds: string[] | null | undefined): {
4649
+ signaturesByFileId: Record<string, FileSignatureSummary>;
4650
+ loading: boolean;
4651
+ error: Error | null;
4652
+ refetch(): Promise<void>;
4653
+ };
4654
+
4655
+ export interface FileRosterEntry {
4656
+ app_user_id: string;
4657
+ name: string;
4658
+ signed: boolean;
4659
+ signature_id: string | null;
4660
+ signer_name: string | null;
4661
+ signed_at: string | null;
4662
+ }
4663
+
4664
+ /**
4665
+ * Who is expected to sign a file, for someone who manages the file's folder:
4666
+ * everyone who can open the folder, or the signers of the file's signing round
4667
+ * in the order of their places on the signature page. `error` is set for a user who does not manage the folder.
4668
+ */
4669
+ export function useFileRoster(
4670
+ fileId: string | null | undefined,
4671
+ options?: { limit?: number; offset?: number; enabled?: boolean },
4672
+ ): {
4673
+ roster: FileRosterEntry[];
4674
+ total: number;
4675
+ signedCount: number;
4676
+ loading: boolean;
4677
+ error: Error | null;
4678
+ refetch(): Promise<void>;
4679
+ };
4680
+
4681
+ /** One entry of a refused signing-round action: which signer, and why. */
4682
+ export interface SigningRefusalEntry {
4683
+ field?: string;
4684
+ message: string;
4685
+ code?: string;
4686
+ app_user_id?: string;
4687
+ reason?: SignatureNameRefusal["reason"];
4688
+ characters?: string[];
4689
+ }
4690
+
4691
+ /** A refusal: the server's `code` and `message`, the refused signers, and the seconds to wait. */
4692
+ export type SigningRoundError = PermissionError & {
4693
+ errors: SigningRefusalEntry[];
4694
+ retryAfter: number | null;
4695
+ };
4696
+
4697
+ export interface SigningRoundPlace {
4698
+ position: number;
4699
+ signed: boolean;
4700
+ signed_at: string | null;
4701
+ }
4702
+
4703
+ export interface SigningRoundSummary {
4704
+ id: string;
4705
+ status: "DRAFT" | "OPEN" | "COMPLETED";
4706
+ signer_count: number;
4707
+ /** For someone who manages the folder; null for a signer. */
4708
+ signed_count: number | null;
4709
+ /** The user's own place when they are one of the signers. */
4710
+ my_place: SigningRoundPlace | null;
4711
+ /** The round is open and the user's place is unsigned. Signers sign in any order. */
4712
+ can_sign: boolean;
4713
+ }
4714
+
4715
+ export interface FileSigningOverview {
4716
+ file_id: string;
4717
+ can_manage: boolean;
4718
+ /**
4719
+ * The user manages the folder, the file is a shared PDF and has no round.
4720
+ * About the file and the user only: offer "prepare" when this AND
4721
+ * `roundsEnabled` are true. Preparing can still be refused.
4722
+ */
4723
+ can_prepare: boolean;
4724
+ /** Null without a round, and for a user who is neither a manager nor one of its signers. */
4725
+ round: SigningRoundSummary | null;
4726
+ /** "signing_round": the file cannot be changed. "signed": its content cannot be replaced. */
4727
+ locked: "signing_round" | "signed" | null;
4728
+ }
4729
+
4730
+ /**
4731
+ * What the signed-in user may know about the signing round of each file, in
4732
+ * one batched read. A file the user cannot see has no entry. `roundsEnabled`
4733
+ * is false when the workspace cannot start signing rounds, null until known.
4734
+ */
4735
+ export function useSigningRounds(fileIds: string[] | null | undefined): {
4736
+ byFileId: Record<string, FileSigningOverview>;
4737
+ roundsEnabled: boolean | null;
4738
+ loading: boolean;
4739
+ error: SigningRoundError | null;
4740
+ refresh(): Promise<void>;
4741
+ };
4742
+
4743
+ export interface SigningRoundSigner {
4744
+ name: string;
4745
+ position: number;
4746
+ /** Present for every place when the user manages the folder; otherwise for their own place only. */
4747
+ app_user_id?: string;
4748
+ signed?: boolean;
4749
+ signed_at?: string | null;
4750
+ /** The name BankID verified on this place. Managers only. */
4751
+ signed_as?: string | null;
4752
+ }
4753
+
4754
+ export interface SigningRound {
4755
+ id: string;
4756
+ file_id: string;
4757
+ status: "DRAFT" | "OPEN" | "COMPLETED" | "CANCELLED";
4758
+ prepared_size_bytes: number;
4759
+ original_version: number | null;
4760
+ created_by_id?: string | null;
4761
+ created_at: string;
4762
+ updated_at: string;
4763
+ confirmed_at: string | null;
4764
+ completed_at: string | null;
4765
+ cancelled_at: string | null;
4766
+ /** When an unconfirmed draft stops being usable; null once confirmed. */
4767
+ expires_at: string | null;
4768
+ signers: SigningRoundSigner[];
4769
+ }
4770
+
4771
+ export interface SigningRoundPreparedFile {
4772
+ url: string;
4773
+ sha256: string;
4774
+ expires_at: string;
4775
+ size_bytes: number;
4776
+ }
4777
+
4778
+ /**
4779
+ * One file's current signing round and the actions of someone who manages the
4780
+ * file's folder. `confirm()` opens the round for the document `preparedFile()`
4781
+ * last returned and rejects with code `PREVIEW_REQUIRED` without it. A refused
4782
+ * action sets `error` and rejects with it; code `SIGNING_ROUNDS_NOT_ENABLED`
4783
+ * means the workspace cannot start signing rounds.
4784
+ *
4785
+ * One action runs at a time: while `busy` is true another one rejects at once
4786
+ * with code `ACTION_IN_PROGRESS` and is not queued. `round` is never a
4787
+ * cancelled round; `cancel()` resolves to the round it ended. When `fileId`
4788
+ * or the signed-in user changes, `round` is null and `loading` true until the
4789
+ * new answer arrives.
4790
+ */
4791
+ export function useSigningRound(fileId: string | null | undefined): {
4792
+ round: SigningRound | null;
4793
+ loading: boolean;
4794
+ error: SigningRoundError | null;
4795
+ refresh(): Promise<void>;
4796
+ /** `signerIds` in the order their places appear on the signature page. Resolves to the DRAFT. */
4797
+ prepare(signerIds: string[]): Promise<SigningRound>;
4798
+ preparedFile(): Promise<SigningRoundPreparedFile>;
4799
+ /** Resolves to the OPEN round. */
4800
+ confirm(): Promise<SigningRound>;
4801
+ /** Resolves to the round it ended, with status "CANCELLED". */
4802
+ cancel(): Promise<SigningRound>;
4803
+ /** An action is running; another one is refused with `ACTION_IN_PROGRESS`. */
4804
+ busy: boolean;
4805
+ };
package/dist/index.js CHANGED
@@ -48,7 +48,6 @@ export {
48
48
  useFilestoreCreateFolder,
49
49
  useFilestoreCopy,
50
50
  useFilestoreDelete,
51
- useFileSignature,
52
51
  useFileSignatures,
53
52
  useFileRoster,
54
53
  useFolderPermissions,
@@ -131,6 +130,7 @@ export {
131
130
  useInactiveUsers,
132
131
  } from "./stats-hooks.js";
133
132
  // sc-11025 — one source, bound to how each platform says the app is in front.
133
+ export { useFileSignature, useSigningRounds, useSigningRound } from "./signing-hooks.js";
134
134
  export { useOnlineMembers, useOnlineVisibility } from "./presence.js";
135
135
  // sc-6607 — `<Overlay>` is one shared component (./overlay-view.js) bound to
136
136
  // each platform's primitives, so a widget's modal escapes its container box
@@ -48,7 +48,6 @@ export {
48
48
  useFilestoreCreateFolder,
49
49
  useFilestoreCopy,
50
50
  useFilestoreDelete,
51
- useFileSignature,
52
51
  useFileSignatures,
53
52
  useFileRoster,
54
53
  useFolderPermissions,
@@ -129,6 +128,7 @@ export {
129
128
  useInactiveUsers,
130
129
  } from "./stats-hooks.js";
131
130
  // sc-11025 — see the note on the web mirror in ./index.js.
131
+ export { useFileSignature, useSigningRounds, useSigningRound } from "./signing-hooks.js";
132
132
  export { useOnlineMembers, useOnlineVisibility } from "./presence.native.js";
133
133
  // sc-6607 — see the note on the web mirror in ./index.js.
134
134
  export { Overlay } from "./overlay.native.js";