@sajn/embed-react 0.1.7 → 1.0.0-beta.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/README.md CHANGED
@@ -1,30 +1,49 @@
1
1
  # @sajn/embed-react
2
2
 
3
- React component for embedding sajn's e-signature functionality directly into your applications.
3
+ Show the sajn signing page in a React app. Your server creates an embed session through the sajn API, and `<SigningEmbed>` shows the signing page in an iframe. Works with React 18 and 19.
4
4
 
5
- ## Installation
5
+ > **Beta.** 1.0 is in beta and published under the `beta` tag on npm. It can change in ways that break your integration before the first stable release, estimated for November 2026. Pin the exact version. For production, 0.x (embedding v1) is stable.
6
+
7
+ ## Install
6
8
 
7
9
  ```bash
8
- npm install @sajn/embed-react
10
+ npm install @sajn/embed-react@1.0.0-beta.1
9
11
  ```
10
12
 
11
- ## Quick Start
13
+ ## Use
14
+
15
+ Your server calls `POST /api/v1/embed-sessions` and returns the `clientSecret`. In your app:
12
16
 
13
17
  ```tsx
14
- import { EmbedSignDocument } from '@sajn/embed-react';
15
-
16
- function App() {
17
- return (
18
- <EmbedSignDocument
19
- documentId="your-document-id"
20
- token="your-token"
21
- onSignerCompleted={(data) => console.log('Signed!', data)}
22
- onSignerRejected={(data) => console.log('Rejected', data)}
23
- />
24
- );
25
- }
18
+ "use client";
19
+
20
+ import { SigningEmbed } from "@sajn/embed-react";
21
+
22
+ const fetchClientSecret = async () => {
23
+ const response = await fetch("/api/sajn/embed-session", { method: "POST" });
24
+ if (!response.ok) throw new Error("Signing isn't available right now.");
25
+ return (await response.json()).clientSecret;
26
+ };
27
+
28
+ export const SigningPage = () => (
29
+ <SigningEmbed
30
+ fetchClientSecret={fetchClientSecret}
31
+ appearance={{ colors: { primary: "#0f766e" } }}
32
+ style={{ height: "80vh" }}
33
+ onSigned={({ documentId }) => console.log("Signed", documentId)}
34
+ onError={({ code, message }) => console.error(code, message)}
35
+ />
36
+ );
26
37
  ```
27
38
 
39
+ The component calls `fetchClientSecret` again each time a session ends, so your endpoint must create a new session on every call. `className` and `style` apply to the container, so give it a height there.
40
+
41
+ ## Props
42
+
43
+ `fetchClientSecret` (required), `host`, `appearance`, `locale`, `title`, `fallbackHeight`, `className`, and `style`, plus the callbacks `onReady`, `onSigned`, `onRejected`, `onFieldsSubmitted`, `onVerified`, `onStateChange`, `onResize`, `onSessionExpired`, and `onError`.
44
+
45
+ A change to `appearance` or `locale` updates the open frame. A change to `host`, `title`, or `fallbackHeight` mounts a new frame with a new session. Callbacks are read from the latest render, so inline functions never reload the frame.
46
+
28
47
  ## Documentation
29
48
 
30
- For full documentation, events, and configuration options, see the [main README](https://github.com/sajn-se/embeds#readme).
49
+ [Embed signing with React](https://docs.sajn.se/guides/embedding-v2/react) has the server step, a Next.js example, the events, the appearance options, the error codes, and troubleshooting.
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const E=require("react/jsx-runtime"),n=require("react"),C=require("@sajn/embed-js"),b=({fetchClientSecret:r,host:u,appearance:o,locale:i,title:g,fallbackHeight:f,openUrl:s,className:y,style:j,...S})=>{const a=n.useRef(null),d=n.useRef(null),t=n.useRef({fetchClientSecret:r,callbacks:S,openUrl:s});t.current={fetchClientSecret:r,callbacks:S,openUrl:s};const l=!!s;n.useEffect(()=>{const R=a.current;if(!R)return;const e=c=>((...x)=>t.current.callbacks[c]?.(...x)),m=C.createSigningEmbed({container:R,host:u,title:g,fallbackHeight:f,appearance:o,locale:i,fetchClientSecret:()=>t.current.fetchClientSecret(),openUrl:l?c=>t.current.openUrl?.(c):void 0,onReady:e("onReady"),onSigned:e("onSigned"),onRejected:e("onRejected"),onFieldsSubmitted:e("onFieldsSubmitted"),onFieldsProgress:e("onFieldsProgress"),onFieldsChanged:e("onFieldsChanged"),onVerified:e("onVerified"),onStateChange:e("onStateChange"),onResize:e("onResize"),onSessionExpired:e("onSessionExpired"),onError:e("onError")});return d.current=m,()=>{m.destroy(),d.current=null}},[u,g,f,l]);const F=JSON.stringify(o??null);return n.useEffect(()=>{d.current?.update({appearance:o,locale:i})},[F,i]),E.jsx("div",{ref:a,className:y,style:j})},V=r=>E.jsx(b,{title:"Visa dokument",...r});exports.SigningEmbed=b;exports.ViewingEmbed=V;
@@ -0,0 +1,43 @@
1
+ import { Appearance } from '@sajn/embed-js';
2
+ import { CSSProperties } from 'react';
3
+ import { EmbedErrorCode } from '@sajn/embed-js';
4
+ import { EmbedIds } from '@sajn/embed-js';
5
+ import { EmbedState } from '@sajn/embed-js';
6
+ import { JSX } from 'react/jsx-runtime';
7
+ import { Locale } from '@sajn/embed-js';
8
+ import { PartyStatus } from '@sajn/embed-js';
9
+ import { SigningEmbedCallbacks } from '@sajn/embed-js';
10
+ import { SigningEmbedError } from '@sajn/embed-js';
11
+
12
+ export { Appearance }
13
+
14
+ export { EmbedErrorCode }
15
+
16
+ export { EmbedIds }
17
+
18
+ export { EmbedState }
19
+
20
+ export { Locale }
21
+
22
+ export { PartyStatus }
23
+
24
+ export declare const SigningEmbed: ({ fetchClientSecret, host, appearance, locale, title, fallbackHeight, openUrl, className, style, ...callbacks }: SigningEmbedProps) => JSX.Element;
25
+
26
+ export { SigningEmbedError }
27
+
28
+ export declare type SigningEmbedProps = SigningEmbedCallbacks & {
29
+ fetchClientSecret: () => Promise<string>;
30
+ host?: string;
31
+ appearance?: Appearance;
32
+ locale?: Locale;
33
+ title?: string;
34
+ fallbackHeight?: string;
35
+ /** Opens the eID pages and the BankID app link yourself, where the frame can't open a window. */
36
+ openUrl?: (url: string) => void;
37
+ className?: string;
38
+ style?: CSSProperties;
39
+ };
40
+
41
+ export declare const ViewingEmbed: (props: SigningEmbedProps) => JSX.Element;
42
+
43
+ export { }
package/dist/index.d.mts CHANGED
@@ -1,4 +1,43 @@
1
- export { default as EmbedSignDocument } from './sign-document';
2
- export type { Language, SignatureInputMode, EmbedSignDocumentProps } from './sign-document';
3
- export { default as EmbedViewDocument } from './view-document';
4
- export type { EmbedViewDocumentProps } from './view-document';
1
+ import { Appearance } from '@sajn/embed-js';
2
+ import { CSSProperties } from 'react';
3
+ import { EmbedErrorCode } from '@sajn/embed-js';
4
+ import { EmbedIds } from '@sajn/embed-js';
5
+ import { EmbedState } from '@sajn/embed-js';
6
+ import { JSX } from 'react/jsx-runtime';
7
+ import { Locale } from '@sajn/embed-js';
8
+ import { PartyStatus } from '@sajn/embed-js';
9
+ import { SigningEmbedCallbacks } from '@sajn/embed-js';
10
+ import { SigningEmbedError } from '@sajn/embed-js';
11
+
12
+ export { Appearance }
13
+
14
+ export { EmbedErrorCode }
15
+
16
+ export { EmbedIds }
17
+
18
+ export { EmbedState }
19
+
20
+ export { Locale }
21
+
22
+ export { PartyStatus }
23
+
24
+ export declare const SigningEmbed: ({ fetchClientSecret, host, appearance, locale, title, fallbackHeight, openUrl, className, style, ...callbacks }: SigningEmbedProps) => JSX.Element;
25
+
26
+ export { SigningEmbedError }
27
+
28
+ export declare type SigningEmbedProps = SigningEmbedCallbacks & {
29
+ fetchClientSecret: () => Promise<string>;
30
+ host?: string;
31
+ appearance?: Appearance;
32
+ locale?: Locale;
33
+ title?: string;
34
+ fallbackHeight?: string;
35
+ /** Opens the eID pages and the BankID app link yourself, where the frame can't open a window. */
36
+ openUrl?: (url: string) => void;
37
+ className?: string;
38
+ style?: CSSProperties;
39
+ };
40
+
41
+ export declare const ViewingEmbed: (props: SigningEmbedProps) => JSX.Element;
42
+
43
+ export { }
package/dist/index.mjs CHANGED
@@ -1,86 +1,44 @@
1
- import { jsx as s } from "react/jsx-runtime";
2
- import { useRef as r, useCallback as d, useEffect as i } from "react";
3
- function g(e) {
4
- const t = r(null);
5
- function o() {
6
- const n = e.host || "https://app.sajn.se", c = btoa(
7
- encodeURIComponent(
8
- JSON.stringify({
9
- language: e.language ?? "en",
10
- cssVars: e.cssVars,
11
- allowDocumentRejection: e.allowDocumentRejection,
12
- showScrollIndicator: e.showScrollIndicator ?? !0,
13
- signatureInputModes: e.signatureInputModes,
14
- ...e.additionalProps
15
- })
16
- )
17
- );
18
- return `${new URL(`/embed/sign/${e.documentId}?token=${e.token}`, n)}#${c}`;
19
- }
20
- const a = d(
21
- (n) => {
22
- if (t.current?.contentWindow === n.source)
23
- switch (n.data.action) {
24
- case "document-ready":
25
- t.current?.contentWindow?.postMessage(
26
- { action: "embed-init" },
27
- new URL(e.host || "https://app.sajn.se").origin
28
- ), e.onDocumentReady?.();
29
- break;
30
- case "signer-completed":
31
- e.onSignerCompleted?.(n.data.data);
32
- break;
33
- case "document-error":
34
- e.onDocumentError?.(n.data.data);
35
- break;
36
- case "signer-rejected":
37
- e.onSignerRejected?.(n.data.data);
38
- break;
39
- }
40
- },
41
- [e.host, e.onDocumentReady, e.onSignerCompleted, e.onDocumentError, e.onSignerRejected]
42
- );
43
- return i(() => (window.addEventListener("message", a), () => {
44
- window.removeEventListener("message", a);
45
- }), [a]), /* @__PURE__ */ s("iframe", { ref: t, className: e.className, src: o() });
46
- }
47
- function w(e) {
48
- const t = r(null);
49
- function o() {
50
- const n = e.host || "https://app.sajn.se", c = btoa(
51
- encodeURIComponent(
52
- JSON.stringify({
53
- language: e.language ?? "en",
54
- cssVars: e.cssVars,
55
- showScrollIndicator: e.showScrollIndicator ?? !0,
56
- ...e.additionalProps
57
- })
58
- )
59
- );
60
- return `${new URL(`/embed/view/${e.documentId}?token=${e.token}`, n)}#${c}`;
61
- }
62
- const a = d(
63
- (n) => {
64
- if (t.current?.contentWindow === n.source)
65
- switch (n.data.action) {
66
- case "document-ready":
67
- t.current?.contentWindow?.postMessage(
68
- { action: "embed-init" },
69
- new URL(e.host || "https://app.sajn.se").origin
70
- ), e.onDocumentReady?.();
71
- break;
72
- case "document-error":
73
- e.onDocumentError?.(n.data.data);
74
- break;
75
- }
76
- },
77
- [e.host, e.onDocumentReady, e.onDocumentError]
78
- );
79
- return i(() => (window.addEventListener("message", a), () => {
80
- window.removeEventListener("message", a);
81
- }), [a]), /* @__PURE__ */ s("iframe", { ref: t, className: e.className, src: o() });
82
- }
1
+ import { jsx as p } from "react/jsx-runtime";
2
+ import { useRef as c, useEffect as E } from "react";
3
+ import { createSigningEmbed as h } from "@sajn/embed-js";
4
+ const x = ({ fetchClientSecret: n, host: u, appearance: o, locale: t, title: f, fallbackHeight: g, openUrl: i, className: F, style: y, ...m }) => {
5
+ const S = c(null), s = c(null), r = c({ fetchClientSecret: n, callbacks: m, openUrl: i });
6
+ r.current = { fetchClientSecret: n, callbacks: m, openUrl: i };
7
+ const a = !!i;
8
+ E(() => {
9
+ const l = S.current;
10
+ if (!l) return;
11
+ const e = (d) => ((...b) => r.current.callbacks[d]?.(...b)), R = h({
12
+ container: l,
13
+ host: u,
14
+ title: f,
15
+ fallbackHeight: g,
16
+ appearance: o,
17
+ locale: t,
18
+ fetchClientSecret: () => r.current.fetchClientSecret(),
19
+ openUrl: a ? (d) => r.current.openUrl?.(d) : void 0,
20
+ onReady: e("onReady"),
21
+ onSigned: e("onSigned"),
22
+ onRejected: e("onRejected"),
23
+ onFieldsSubmitted: e("onFieldsSubmitted"),
24
+ onFieldsProgress: e("onFieldsProgress"),
25
+ onFieldsChanged: e("onFieldsChanged"),
26
+ onVerified: e("onVerified"),
27
+ onStateChange: e("onStateChange"),
28
+ onResize: e("onResize"),
29
+ onSessionExpired: e("onSessionExpired"),
30
+ onError: e("onError")
31
+ });
32
+ return s.current = R, () => {
33
+ R.destroy(), s.current = null;
34
+ };
35
+ }, [u, f, g, a]);
36
+ const C = JSON.stringify(o ?? null);
37
+ return E(() => {
38
+ s.current?.update({ appearance: o, locale: t });
39
+ }, [C, t]), /* @__PURE__ */ p("div", { ref: S, className: F, style: y });
40
+ }, w = (n) => /* @__PURE__ */ p(x, { title: "Visa dokument", ...n });
83
41
  export {
84
- g as EmbedSignDocument,
85
- w as EmbedViewDocument
42
+ x as SigningEmbed,
43
+ w as ViewingEmbed
86
44
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sajn/embed-react",
3
- "version": "0.1.7",
4
- "description": "React component for embedding sajn e-signature functionality",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "React component for embedded sajn e-signing",
5
5
  "homepage": "https://github.com/sajn-se/embeds#readme",
6
6
  "bugs": {
7
7
  "url": "https://github.com/sajn-se/embeds/issues"
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "license": "MIT",
14
14
  "author": "sajn",
15
- "type": "commonjs",
15
+ "type": "module",
16
16
  "exports": {
17
17
  "./package.json": "./package.json",
18
18
  ".": {
@@ -21,13 +21,13 @@
21
21
  "default": "./dist/index.mjs"
22
22
  },
23
23
  "require": {
24
- "types": "./dist/index.d.ts",
25
- "default": "./dist/index.js"
24
+ "types": "./dist/index.d.cts",
25
+ "default": "./dist/index.cjs"
26
26
  }
27
27
  }
28
28
  },
29
- "main": "dist/index.js",
30
- "types": "dist/index.d.ts",
29
+ "main": "dist/index.cjs",
30
+ "types": "dist/index.d.cts",
31
31
  "files": [
32
32
  "dist"
33
33
  ],
@@ -45,6 +45,9 @@
45
45
  "access": "public"
46
46
  },
47
47
  "module": "dist/index.mjs",
48
+ "dependencies": {
49
+ "@sajn/embed-js": "^1.0.0-beta.1"
50
+ },
48
51
  "scripts": {
49
52
  "build": "vite build",
50
53
  "clean": "rimraf node_modules && rimraf dist",
@@ -1,10 +0,0 @@
1
- export type CssVars = {
2
- /** Page background color */
3
- background?: string | null;
4
- /** Primary action/button color (brand color) */
5
- primary?: string | null;
6
- /** Main text color (for dark theme support) */
7
- foreground?: string | null;
8
- /** Secondary/muted text color */
9
- mutedForeground?: string | null;
10
- };
package/dist/index.d.ts DELETED
@@ -1,4 +0,0 @@
1
- export { default as EmbedSignDocument } from './sign-document';
2
- export type { Language, SignatureInputMode, EmbedSignDocumentProps } from './sign-document';
3
- export { default as EmbedViewDocument } from './view-document';
4
- export type { EmbedViewDocumentProps } from './view-document';
package/dist/index.js DELETED
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),c=require("react");function d(e){const t=c.useRef(null);function o(){const n=e.host||"https://app.sajn.se",s=btoa(encodeURIComponent(JSON.stringify({language:e.language??"en",cssVars:e.cssVars,allowDocumentRejection:e.allowDocumentRejection,showScrollIndicator:e.showScrollIndicator??!0,signatureInputModes:e.signatureInputModes,...e.additionalProps})));return`${new URL(`/embed/sign/${e.documentId}?token=${e.token}`,n)}#${s}`}const a=c.useCallback(n=>{if(t.current?.contentWindow===n.source)switch(n.data.action){case"document-ready":t.current?.contentWindow?.postMessage({action:"embed-init"},new URL(e.host||"https://app.sajn.se").origin),e.onDocumentReady?.();break;case"signer-completed":e.onSignerCompleted?.(n.data.data);break;case"document-error":e.onDocumentError?.(n.data.data);break;case"signer-rejected":e.onSignerRejected?.(n.data.data);break}},[e.host,e.onDocumentReady,e.onSignerCompleted,e.onDocumentError,e.onSignerRejected]);return c.useEffect(()=>(window.addEventListener("message",a),()=>{window.removeEventListener("message",a)}),[a]),r.jsx("iframe",{ref:t,className:e.className,src:o()})}function u(e){const t=c.useRef(null);function o(){const n=e.host||"https://app.sajn.se",s=btoa(encodeURIComponent(JSON.stringify({language:e.language??"en",cssVars:e.cssVars,showScrollIndicator:e.showScrollIndicator??!0,...e.additionalProps})));return`${new URL(`/embed/view/${e.documentId}?token=${e.token}`,n)}#${s}`}const a=c.useCallback(n=>{if(t.current?.contentWindow===n.source)switch(n.data.action){case"document-ready":t.current?.contentWindow?.postMessage({action:"embed-init"},new URL(e.host||"https://app.sajn.se").origin),e.onDocumentReady?.();break;case"document-error":e.onDocumentError?.(n.data.data);break}},[e.host,e.onDocumentReady,e.onDocumentError]);return c.useEffect(()=>(window.addEventListener("message",a),()=>{window.removeEventListener("message",a)}),[a]),r.jsx("iframe",{ref:t,className:e.className,src:o()})}exports.EmbedSignDocument=d;exports.EmbedViewDocument=u;
@@ -1,35 +0,0 @@
1
- import { CssVars } from './css-vars';
2
- export type Language = 'sv' | 'en' | 'no' | 'da' | 'fi' | 'de' | 'is' | 'es' | 'fr' | 'it';
3
- export type SignatureInputMode = 'draw' | 'type' | 'upload';
4
- export type EmbedSignDocumentProps = {
5
- language?: Language;
6
- className?: string;
7
- host?: string;
8
- token: string;
9
- documentId: string;
10
- cssVars?: (CssVars & Record<string, string>) | undefined;
11
- allowDocumentRejection?: boolean | undefined;
12
- showScrollIndicator?: boolean | undefined;
13
- /** Which signature input modes the pad offers (default: all). Only narrows the workspace allowlist. */
14
- signatureInputModes?: SignatureInputMode[] | undefined;
15
- additionalProps?: Record<string, string | number | boolean> | undefined;
16
- onDocumentReady?: () => void;
17
- onSignerCompleted?: (data: {
18
- token: string;
19
- documentId: string;
20
- signerId: string;
21
- failed?: string;
22
- }) => void;
23
- onDocumentError?: (data: {
24
- code: string;
25
- message: string;
26
- }) => void;
27
- onSignerRejected?: (data: {
28
- token: string;
29
- documentId: string;
30
- signerId: string;
31
- reason: string;
32
- }) => void;
33
- };
34
- declare function EmbedSignDocument(props: EmbedSignDocumentProps): import("react/jsx-runtime").JSX.Element;
35
- export default EmbedSignDocument;
@@ -1,19 +0,0 @@
1
- import { CssVars } from './css-vars';
2
- export type Language = 'sv' | 'en' | 'no' | 'da' | 'fi' | 'de' | 'is' | 'es' | 'fr' | 'it';
3
- export type EmbedViewDocumentProps = {
4
- language?: Language;
5
- className?: string;
6
- host?: string;
7
- token: string;
8
- documentId: string;
9
- cssVars?: (CssVars & Record<string, string>) | undefined;
10
- showScrollIndicator?: boolean | undefined;
11
- additionalProps?: Record<string, string | number | boolean> | undefined;
12
- onDocumentReady?: () => void;
13
- onDocumentError?: (data: {
14
- code: string;
15
- message: string;
16
- }) => void;
17
- };
18
- declare function EmbedViewDocument(props: EmbedViewDocumentProps): import("react/jsx-runtime").JSX.Element;
19
- export default EmbedViewDocument;