Signature Storage

PSPDFKit allows you to implement your own mechanism to use for storing signatures.

If you provide such a mechanism, then signatures may optionally be saved at the same time they’re added to a document. The saving of signatures is based on the SIGNATURE_SAVE_MODE : This defaults to showing a toggle in the UI that allows the user to choose whether to save, but you can change this option to hide the toggle and instead always save signatures without giving the user the choice.

Screenshot of enabled toggle for saving signatures.

If you provide stored signatures to PSPDFKit, then when the user selects a signature form field or the signature tool, the list of stored signatures will be shown instead of the signature creation UI.

Screenshot showing list with two signatures: John Appleseed and J.A.

To do this, you can register event listeners for the storedSignatures.create and storedSignatures.delete events. Once these event listeners are registered, the signatures UI is updated to display a checkbox to allow users to decide if they want their signatures stored or not.

storedSignatures.create is invoked with the signature annotation created by the user. There, you can serialize the signature and send it to a backend server, or add it to a browser storage mechanism such as IndexedDB or local storage.

As an example, the code below shows how to add signatures using window.localStorage. Please note that local storage is a blocking API, so it isn’t ideal for production applications, especially when dealing with images:

const STORAGE_KEY = 'signatures_storage';
const ATTACHMENTS_KEY = 'attachments_storage';

instance.addEventListener('storedSignatures.create', async (annotation) => {
	const signaturesString = localStorage.getItem(STORAGE_KEY);
	const storedSignatures = signaturesString
		? JSON.parse(signaturesString)
		: [];

	const serializedAnnotation = PSPDFKit.Annotations.toSerializableObject(
		annotation,
	);
	if (annotation.imageAttachmentId) {
		const attachment = await instance.getAttachment(
			annotation.imageAttachmentId,
		);

		// Create the data URL and add it to local storage.
		// Note: This is done only for demonstration purposes.
		// Storing potentially large chunks of data using local storage is
		// considered bad practice due to the synchronous nature of the API.
		// For production applications, please consider alternatives such as
		// dedicated backend storage or IndexedDB.
		const url = await fileToDataURL(attachment);
		const attachmentsString = localStorage.getItem(ATTACHMENTS_KEY);
		const attachmentsArray = attachmentsString
			? JSON.parse(attachmentsString)
			: [];
		attachmentsArray.push({ url, id: annotation.imageAttachmentId });
		// Separate the `localStorage` item to store attachments.
		localStorage.setItem(ATTACHMENTS_KEY, JSON.stringify(attachmentsArray));
	}
	storedSignatures.push(serializedAnnotation);
	localStorage.setItem(STORAGE_KEY, JSON.stringify(storedSignatures));
	// Add a new annotation so that it renders as part of the UI on the current session.
	instance.setStoredSignatures((signatures) => signatures.push(annotation));
});

Similarly, it’s necessary to implement deleting the signature from the underlying storage when the user chooses to delete one of the existing records through the UI:

instance.addEventListener('storedSignatures.delete', (annotation) => {
	const signaturesString = localStorage.getItem(STORAGE_KEY);
	const storedSignatures = signaturesString
		? JSON.parse(signaturesString)
		: [];
	const annotations = storedSignatures.map(
		PSPDFKit.Annotations.fromSerializableObject,
	);
	const updatedAnnotations = annotations.filter(
		(currentAnnotation) => !currentAnnotation.equals(annotation),
	);
	localStorage.setItem(
		STORAGE_KEY,
		JSON.stringify(
			updatedAnnotations.map(PSPDFKit.Annotations.toSerializableObject),
		),
	);
	// Use the `setStoredSignatures` API so that the current UI is properly updated.
	instance.setStoredSignatures((signatures) =>
		signatures.filter((signature) => !signature.equals(annotation)),
	);

	if (annotation.imageAttachmentId) {
		// Remove the attachment from the array.
		const attachmentsString = localStorage.getItem(ATTACHMENTS_KEY);
		if (attachmentsString) {
			let attachmentsArray = JSON.parse(attachmentsString);
			attachmentsArray = attachmentsArray.filter(
				(attachment) => attachment.id !== annotation.imageAttachmentId,
			);
			localStorage.setItem(
				ATTACHMENTS_KEY,
				JSON.stringify(attachmentsArray),
			);
		}
	}
});

To set the list of available signatures to pick from, you can use the PSPDFKit.Configuration#populateStoredSignatures configuration option or the PSPDFKit.Instance#setStoredSignatures instance method:

const signaturesString = localStorage.getItem(STORAGE_KEY);
const storedSignatures = JSON.parse(signaturesString);
// Construct annotations from serialized entries and call the `setStoredSignatures` API.
const list = PSPDFKit.Immutable.List(
	storedSignatures.map(PSPDFKit.Annotations.fromSerializableObject),
);
instance.setStoredSignatures(list);

// Retrieve attachments and add them to the instance.
const attachmentsString = localStorage.getItem(ATTACHMENTS_KEY);
if (attachmentsString) {
	const attachmentsArray = JSON.parse(attachmentsString);
	// Instantiate blob objects from the data URLs on local storage.
	const blobs = await Promise.all(
		attachmentsArray.map(({ url }) => fetch(url).then((res) => res.blob())),
	);
	// Create an attachment for each blob.
	blobs.forEach(instance.createAttachment);
}