diff --git a/platforms/esigner-api/src/services/NotificationService.ts b/platforms/esigner-api/src/services/NotificationService.ts index f746faba..560e73cd 100644 --- a/platforms/esigner-api/src/services/NotificationService.ts +++ b/platforms/esigner-api/src/services/NotificationService.ts @@ -285,6 +285,8 @@ export class NotificationService { const inviterText = inviterName ? ` from ${inviterName}` : ''; const containerName = file.displayName || file.name; const descriptionText = file.description ? `\nDescription: ${file.description}` : ''; + const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004'; + const fileLink = `${esignerUrl}/files/${file.id}`; return `📝 Signature Invitation @@ -296,7 +298,7 @@ Size: ${this.formatFileSize(file.size)} Type: ${file.mimeType} Time: ${formattedTime} -Please review and sign the signature container when ready.`; +View Signature Container in eSigner`; } /** @@ -314,6 +316,8 @@ Please review and sign the signature container when ready.`; const signerText = signerName ? ` by ${signerName}` : ''; const containerName = file.displayName || file.name; const descriptionText = file.description ? `\nDescription: ${file.description}` : ''; + const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004'; + const fileLink = `${esignerUrl}/files/${file.id}`; return `✅ Signature Completed @@ -323,7 +327,7 @@ Signature Container: ${containerName}${descriptionText} File: ${file.name} Time: ${formattedTime} -The signature has been recorded and verified.`; +View Signature Container in eSigner`; } /** @@ -340,6 +344,8 @@ The signature has been recorded and verified.`; const containerName = file.displayName || file.name; const descriptionText = file.description ? `\nDescription: ${file.description}` : ''; + const esignerUrl = process.env.PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004'; + const fileLink = `${esignerUrl}/files/${file.id}`; return `🎉 Signature Container Fully Signed @@ -349,7 +355,7 @@ Signature Container: ${containerName}${descriptionText} File: ${file.name} Time: ${formattedTime} -The signature container is now complete. You can download the proof from the eSigner platform.`; +Download Proof from eSigner`; } /** diff --git a/platforms/esigner/.svelte-kit/generated/client/app.js b/platforms/esigner/.svelte-kit/generated/client/app.js index b03d8aad..dee17bec 100644 --- a/platforms/esigner/.svelte-kit/generated/client/app.js +++ b/platforms/esigner/.svelte-kit/generated/client/app.js @@ -8,7 +8,8 @@ export const nodes = [ () => import('./nodes/4'), () => import('./nodes/5'), () => import('./nodes/6'), - () => import('./nodes/7') + () => import('./nodes/7'), + () => import('./nodes/8') ]; export const server_loads = []; @@ -16,9 +17,10 @@ export const server_loads = []; export const dictionary = { "/": [3], "/(auth)/auth": [4], - "/(protected)/files": [5,[2]], - "/(protected)/files/new": [7,[2]], - "/(protected)/files/[id]": [6,[2]] + "/(auth)/deeplink-login": [5], + "/(protected)/files": [6,[2]], + "/(protected)/files/new": [8,[2]], + "/(protected)/files/[id]": [7,[2]] }; export const hooks = { diff --git a/platforms/esigner/.svelte-kit/generated/client/nodes/5.js b/platforms/esigner/.svelte-kit/generated/client/nodes/5.js index 5362e6db..2b85113b 100644 --- a/platforms/esigner/.svelte-kit/generated/client/nodes/5.js +++ b/platforms/esigner/.svelte-kit/generated/client/nodes/5.js @@ -1 +1 @@ -export { default as component } from "../../../../src/routes/(protected)/files/+page.svelte"; \ No newline at end of file +export { default as component } from "../../../../src/routes/(auth)/deeplink-login/+page.svelte"; \ No newline at end of file diff --git a/platforms/esigner/.svelte-kit/generated/client/nodes/6.js b/platforms/esigner/.svelte-kit/generated/client/nodes/6.js index 8bd5dab8..5362e6db 100644 --- a/platforms/esigner/.svelte-kit/generated/client/nodes/6.js +++ b/platforms/esigner/.svelte-kit/generated/client/nodes/6.js @@ -1 +1 @@ -export { default as component } from "../../../../src/routes/(protected)/files/[id]/+page.svelte"; \ No newline at end of file +export { default as component } from "../../../../src/routes/(protected)/files/+page.svelte"; \ No newline at end of file diff --git a/platforms/esigner/.svelte-kit/generated/client/nodes/7.js b/platforms/esigner/.svelte-kit/generated/client/nodes/7.js index 86a2cd32..8bd5dab8 100644 --- a/platforms/esigner/.svelte-kit/generated/client/nodes/7.js +++ b/platforms/esigner/.svelte-kit/generated/client/nodes/7.js @@ -1 +1 @@ -export { default as component } from "../../../../src/routes/(protected)/files/new/+page.svelte"; \ No newline at end of file +export { default as component } from "../../../../src/routes/(protected)/files/[id]/+page.svelte"; \ No newline at end of file diff --git a/platforms/esigner/.svelte-kit/generated/client/nodes/8.js b/platforms/esigner/.svelte-kit/generated/client/nodes/8.js new file mode 100644 index 00000000..86a2cd32 --- /dev/null +++ b/platforms/esigner/.svelte-kit/generated/client/nodes/8.js @@ -0,0 +1 @@ +export { default as component } from "../../../../src/routes/(protected)/files/new/+page.svelte"; \ No newline at end of file diff --git a/platforms/esigner/.svelte-kit/generated/server/internal.js b/platforms/esigner/.svelte-kit/generated/server/internal.js index 360a5921..22b9bf2f 100644 --- a/platforms/esigner/.svelte-kit/generated/server/internal.js +++ b/platforms/esigner/.svelte-kit/generated/server/internal.js @@ -24,7 +24,7 @@ export const options = { app: ({ head, body, assets, nonce, env }) => "\n\n\t\n\t\t\n\t\t\n\t\t" + head + "\n\t\n\t\n\t\t
" + body + "
\n\t\n\n\n", error: ({ status, message }) => "\n\n\t\n\t\t\n\t\t" + message + "\n\n\t\t\n\t\n\t\n\t\t
\n\t\t\t" + status + "\n\t\t\t
\n\t\t\t\t

" + message + "

\n\t\t\t
\n\t\t
\n\t\n\n" }, - version_hash: "jgao1c" + version_hash: "he2m2" }; export async function get_hooks() { diff --git a/platforms/esigner/.svelte-kit/non-ambient.d.ts b/platforms/esigner/.svelte-kit/non-ambient.d.ts index 2d6d6dde..8dca0d20 100644 --- a/platforms/esigner/.svelte-kit/non-ambient.d.ts +++ b/platforms/esigner/.svelte-kit/non-ambient.d.ts @@ -27,7 +27,7 @@ export {}; declare module "$app/types" { export interface AppTypes { - RouteId(): "/(protected)" | "/(auth)" | "/" | "/(auth)/auth" | "/(protected)/files" | "/(protected)/files/new" | "/(protected)/files/[id]"; + RouteId(): "/(protected)" | "/(auth)" | "/" | "/(auth)/auth" | "/(auth)/deeplink-login" | "/(protected)/files" | "/(protected)/files/new" | "/(protected)/files/[id]"; RouteParams(): { "/(protected)/files/[id]": { id: string } }; @@ -36,11 +36,12 @@ declare module "$app/types" { "/(auth)": Record; "/": { id?: string }; "/(auth)/auth": Record; + "/(auth)/deeplink-login": Record; "/(protected)/files": { id?: string }; "/(protected)/files/new": Record; "/(protected)/files/[id]": { id: string } }; - Pathname(): "/" | "/auth" | "/auth/" | "/files" | "/files/" | "/files/new" | "/files/new/" | `/files/${string}` & {} | `/files/${string}/` & {}; + Pathname(): "/" | "/auth" | "/auth/" | "/deeplink-login" | "/deeplink-login/" | "/files" | "/files/" | "/files/new" | "/files/new/" | `/files/${string}` & {} | `/files/${string}/` & {}; ResolvedPathname(): `${"" | `/${string}`}${ReturnType}`; Asset(): string & {}; } diff --git a/platforms/esigner/.svelte-kit/types/route_meta_data.json b/platforms/esigner/.svelte-kit/types/route_meta_data.json index d45ad958..bffb4f4f 100644 --- a/platforms/esigner/.svelte-kit/types/route_meta_data.json +++ b/platforms/esigner/.svelte-kit/types/route_meta_data.json @@ -2,6 +2,7 @@ "/(protected)": [], "/": [], "/(auth)/auth": [], + "/(auth)/deeplink-login": [], "/(protected)/files": [], "/(protected)/files/new": [], "/(protected)/files/[id]": [] diff --git a/platforms/esigner/.svelte-kit/types/src/routes/$types.d.ts b/platforms/esigner/.svelte-kit/types/src/routes/$types.d.ts index 32f48a48..68302399 100644 --- a/platforms/esigner/.svelte-kit/types/src/routes/$types.d.ts +++ b/platforms/esigner/.svelte-kit/types/src/routes/$types.d.ts @@ -12,7 +12,7 @@ type EnsureDefined = T extends null | undefined ? {} : T; type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; export type Snapshot = Kit.Snapshot; type PageParentData = EnsureDefined; -type LayoutRouteId = RouteId | "/" | "/(auth)/auth" | "/(protected)/files" | "/(protected)/files/[id]" | "/(protected)/files/new" | null +type LayoutRouteId = RouteId | "/" | "/(auth)/auth" | "/(auth)/deeplink-login" | "/(protected)/files" | "/(protected)/files/[id]" | "/(protected)/files/new" | null type LayoutParams = RouteParams & { id?: string } type LayoutParentData = EnsureDefined<{}>; diff --git a/platforms/esigner/.svelte-kit/types/src/routes/(auth)/deeplink-login/$types.d.ts b/platforms/esigner/.svelte-kit/types/src/routes/(auth)/deeplink-login/$types.d.ts new file mode 100644 index 00000000..d6aa873d --- /dev/null +++ b/platforms/esigner/.svelte-kit/types/src/routes/(auth)/deeplink-login/$types.d.ts @@ -0,0 +1,18 @@ +import type * as Kit from '@sveltejs/kit'; + +type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; +// @ts-ignore +type MatcherParam = M extends (param : string) => param is infer U ? U extends string ? U : string : string; +type RouteParams = { }; +type RouteId = '/(auth)/deeplink-login'; +type MaybeWithVoid = {} extends T ? T | void : T; +export type RequiredKeys = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; }[keyof T]; +type OutputDataShape = MaybeWithVoid> & Partial> & Record> +type EnsureDefined = T extends null | undefined ? {} : T; +type OptionalUnion, A extends keyof U = U extends U ? keyof U : never> = U extends unknown ? { [P in Exclude]?: never } & U : never; +export type Snapshot = Kit.Snapshot; +type PageParentData = EnsureDefined; + +export type PageServerData = null; +export type PageData = Expand; +export type PageProps = { params: RouteParams; data: PageData } \ No newline at end of file diff --git a/platforms/esigner/src/lib/utils/mobile-detection.ts b/platforms/esigner/src/lib/utils/mobile-detection.ts new file mode 100644 index 00000000..e99efff3 --- /dev/null +++ b/platforms/esigner/src/lib/utils/mobile-detection.ts @@ -0,0 +1,10 @@ +export function isMobileDevice(): boolean { + if (typeof window === 'undefined') return false; + + return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || + (window.innerWidth <= 768); +} + +export function getDeepLinkUrl(qrData: string): string { + return qrData; +} diff --git a/platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte b/platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte new file mode 100644 index 00000000..af0fd585 --- /dev/null +++ b/platforms/esigner/src/routes/(auth)/deeplink-login/+page.svelte @@ -0,0 +1,112 @@ + + +{#if isLoading} +
+
+
+

Authenticating...

+
+
+{:else if error} +
+
+
{error}
+ +
+
+{/if} diff --git a/platforms/esigner/src/routes/(protected)/files/+page.svelte b/platforms/esigner/src/routes/(protected)/files/+page.svelte index dd5f8304..a6a0b7f9 100644 --- a/platforms/esigner/src/routes/(protected)/files/+page.svelte +++ b/platforms/esigner/src/routes/(protected)/files/+page.svelte @@ -40,46 +40,46 @@ }); -
- -
-
-

Signature Containers

-

Manage your signature containers and signed files

-
- - + New Signature Container - +
+ +
+
+

Signature Containers

+

Manage your signature containers and signed files

+ + + New Signature Container + +
- - {#if $invitations.length > 0} -
-

Pending Signing Requests

-
- {#each $invitations as inv} -
-
-

{inv.file?.displayName || inv.file?.name || 'Unknown Signature Container'}

- {#if inv.file?.description} -

{inv.file.description}

- {/if} -

Invited {new Date(inv.invitedAt).toLocaleDateString()}

-
- - View & Sign - + + {#if $invitations.length > 0} +
+

Pending Signing Requests

+
+ {#each $invitations as inv} +
+
+

{inv.file?.displayName || inv.file?.name || 'Unknown Signature Container'}

+ {#if inv.file?.description} +

{inv.file.description}

+ {/if} +

Invited {new Date(inv.invitedAt).toLocaleDateString()}

- {/each} -
+ + View & Sign + +
+ {/each}
- {/if} +
+ {/if}
@@ -88,80 +88,80 @@
{#if $isLoading} -
-

Loading documents...

-
- {:else if $documents.length === 0} -
-
📄
-

No signature containers yet

-

Create your first signature container to get started

- - Create Signature Container - -
- {:else} -
- {#each $documents as doc} - -
-
-
-

{doc.displayName || doc.name}

- - {getStatusBadge(doc.status).text} - -
- {#if doc.description} -

{doc.description}

+
+

Loading documents...

+
+ {:else if $documents.length === 0} +
+ {:else} + + + {/each} +
+ {/if}
diff --git a/platforms/esigner/src/routes/(protected)/files/[id]/+page.svelte b/platforms/esigner/src/routes/(protected)/files/[id]/+page.svelte index 06b17c36..2b1e7a42 100644 --- a/platforms/esigner/src/routes/(protected)/files/[id]/+page.svelte +++ b/platforms/esigner/src/routes/(protected)/files/[id]/+page.svelte @@ -8,6 +8,7 @@ import { qrcode } from 'svelte-qrcode-action'; import { PUBLIC_ESIGNER_BASE_URL } from '$env/static/public'; import { toast } from '$lib/stores/toast'; + import { isMobileDevice, getDeepLinkUrl } from '$lib/utils/mobile-detection'; let file = $state(null); let invitations = $state([]); @@ -19,8 +20,8 @@ let hasUserSigned = $state(false); let showDownloadModal = $state(false); - onMount(async () => { - isAuthenticated.subscribe((auth) => { + onMount(() => { + const authUnsubscribe = isAuthenticated.subscribe((auth) => { if (!auth) { goto('/auth'); } @@ -32,18 +33,33 @@ goto('/(protected)/files'); return; } - await loadFile(fileId); - await fetchFileSignatures(fileId); + + // Load data asynchronously + (async () => { + await loadFile(fileId); + await fetchFileSignatures(fileId); + })(); // Watch for signature changes - signatures.subscribe(() => { + const signaturesUnsubscribe = signatures.subscribe(() => { checkIfUserSigned(); }); // Watch for user changes - currentUser.subscribe(() => { + const userUnsubscribe = currentUser.subscribe(() => { checkIfUserSigned(); }); + + // Cleanup function + return () => { + authUnsubscribe(); + signaturesUnsubscribe(); + userUnsubscribe(); + if (sseConnection) { + sseConnection.close(); + sseConnection = null; + } + }; }); async function loadFile(fileId: string) { @@ -119,34 +135,69 @@ } function watchSigningSession(sessionId: string) { + // Close existing connection if any + if (sseConnection) { + sseConnection.close(); + sseConnection = null; + } + const baseUrl = PUBLIC_ESIGNER_BASE_URL || 'http://localhost:3004'; const sseUrl = new URL(`/api/signatures/session/${sessionId}`, baseUrl).toString(); + + console.log('Starting SSE connection to:', sseUrl); sseConnection = new EventSource(sseUrl); + sseConnection.onopen = () => { + console.log('SSE connection opened'); + }; + sseConnection.onmessage = (e) => { - const data = JSON.parse(e.data); - if (data.type === 'signed' && data.status === 'completed') { - showSignModal = false; - sseConnection?.close(); - toast.success('Signature container signed successfully!'); - setTimeout(async () => { - await loadFile(file.id); - await fetchFileSignatures(file.id); - checkIfUserSigned(); - }, 500); - } else if (data.type === 'expired') { - showSignModal = false; - sseConnection?.close(); - toast.error('Signing session expired. Please try again.'); - } else if (data.type === 'security_violation') { - showSignModal = false; - sseConnection?.close(); - toast.error('Security violation detected. Signing failed.'); + console.log('SSE message received:', e.data); + try { + const data = JSON.parse(e.data); + console.log('Parsed SSE data:', data); + + if (data.type === 'signed' && data.status === 'completed') { + console.log('Signature completed!'); + showSignModal = false; + if (sseConnection) { + sseConnection.close(); + sseConnection = null; + } + toast.success('Signature container signed successfully!'); + setTimeout(async () => { + await loadFile(file.id); + await fetchFileSignatures(file.id); + checkIfUserSigned(); + }, 500); + } else if (data.type === 'expired' || data.status === 'expired') { + console.log('Session expired'); + showSignModal = false; + if (sseConnection) { + sseConnection.close(); + sseConnection = null; + } + toast.error('Signing session expired. Please try again.'); + } else if (data.type === 'security_violation') { + console.log('Security violation'); + showSignModal = false; + if (sseConnection) { + sseConnection.close(); + sseConnection = null; + } + toast.error('Security violation detected. Signing failed.'); + } + } catch (err) { + console.error('Failed to parse SSE message:', err); } }; - sseConnection.onerror = () => { - sseConnection?.close(); + sseConnection.onerror = (err) => { + console.error('SSE connection error:', err); + if (sseConnection) { + sseConnection.close(); + sseConnection = null; + } }; } @@ -282,22 +333,22 @@ -
+
-
+
Back to Signature Containers -

{file?.displayName || file?.name || 'Signature Container'}

+

{file?.displayName || file?.name || 'Signature Container'}

{#if file?.description}

{file.description}

{/if}
-
+
{#if isLoading}
@@ -306,8 +357,8 @@
{:else if file} - -
+ +